From f5e2af9a28d7f9b8d22972dd15f4b4e4c4f7a014 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 2 Sep 2023 23:13:07 +0200 Subject: [PATCH] Make wxWebViewChromium work with wxGTK3 and X11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make browser creation actually work by postponing it until the host window is realized and so has a valid X11 Window. Remove unnecessary code manually creating GTK widget and just use the standard wxGTK wxWindow instead. Add code for setting the visual compatible with CEF to avoid X11 errors, see https://github.com/chromiumembedded/cef/issues/3564, with many thanks to Jiří Janoušek for finding and solving this problem originally. Adjust the sample to handle wxWebView::Create() failure (not very gracefully, but still better than just crashing) and to avoid using it until it is fully created. Update documentation to mention GTK limitations. --- include/wx/webview_chromium.h | 12 +++ interface/wx/webview_chromium.h | 17 ++++ samples/webview/webview.cpp | 16 ++- src/common/webview_chromium.cpp | 173 +++++++++++++++++++++----------- 4 files changed, 158 insertions(+), 60 deletions(-) diff --git a/include/wx/webview_chromium.h b/include/wx/webview_chromium.h index 14840691f5..c72ff41b4a 100644 --- a/include/wx/webview_chromium.h +++ b/include/wx/webview_chromium.h @@ -118,10 +118,18 @@ public: //Virtual Filesystem Support virtual void RegisterHandler(wxSharedPtr handler) override; +#ifdef __WXGTK__ + virtual void GTKHandleRealized() override; +#endif + protected: virtual void DoSetPage(const wxString& html, const wxString& baseUrl) override; private: + // Actually create the browser: this can only be done once the window is + // created in wxGTK. + bool DoCreateBrowser(const wxString& url); + //History related variables, we currently use our own implementation wxVector > m_historyList; int m_historyPosition; @@ -142,6 +150,10 @@ private: friend class ClientHandler; ClientHandler* m_clientHandler; +#ifdef __WXGTK__ + wxString m_url; +#endif + friend class wxWebViewChromiumModule; static bool ms_cefInitialized; diff --git a/interface/wx/webview_chromium.h b/interface/wx/webview_chromium.h index d1ac554215..b036a1479b 100644 --- a/interface/wx/webview_chromium.h +++ b/interface/wx/webview_chromium.h @@ -64,6 +64,23 @@ Only Microsoft Visual C++ 2015 or newer can be used to build wxWebViewChromium. + __Linux with GTK__ + + wxWebviewChromium currently only supports X11 and not Wayland, i.e. you + need to either ensure that `GDK_BACKEND` environment variable is set to + "x11" before running your program using it or call + + @code + gdk_set_allowed_backends("x11") + @endcode + + in your application code. + + Moreover, the actual browser is only created once the window is shown, and + can't be used until then. You can bind an event handler for wxEVT_CREATE to + know when it is usable. + + __Mac OS X Platform__ OS X 10.9 or above is required. diff --git a/samples/webview/webview.cpp b/samples/webview/webview.cpp index 0832a8445e..2cfb7f4e14 100644 --- a/samples/webview/webview.cpp +++ b/samples/webview/webview.cpp @@ -460,14 +460,17 @@ WebFrame::WebFrame(const wxString& url, bool isMain, wxWebViewWindowFeatures* wi m_browser->RegisterHandler(wxSharedPtr(new AdvancedWebViewHandler())); } #endif - m_browser->Create(this, wxID_ANY, url, wxDefaultPosition, + if ( !m_browser->Create(this, wxID_ANY, url, wxDefaultPosition, #if defined(wxWEBVIEW_SAMPLE_CHROMIUM) && defined(__WXOSX__) // OSX implementation currently cannot handle the default size wxSize(800, 600) #else wxDefaultSize #endif - ); + ) ) + { + wxLogFatalError("Failed to create wxWebView"); + } topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1)); @@ -476,7 +479,14 @@ WebFrame::WebFrame(const wxString& url, bool isMain, wxWebViewWindowFeatures* wi // Log backend information wxLogMessage("Backend: %s Version: %s", m_browser->GetClassInfo()->GetClassName(), wxWebView::GetBackendVersionInfo().ToString()); - wxLogMessage("User Agent: %s", m_browser->GetUserAgent()); + + // Chromium backend can't be used immediately after creation, so wait + // until the browser is created before calling GetUserAgent(). + m_browser->Bind(wxEVT_CREATE, [this](wxWindowCreateEvent& event) { + wxLogMessage("User Agent: %s", m_browser->GetUserAgent()); + + event.Skip(); + }); #ifndef __WXMAC__ //We register the wxfs:// protocol for testing purposes diff --git a/src/common/webview_chromium.cpp b/src/common/webview_chromium.cpp index 8ef0981c9b..4d1e37ffe9 100644 --- a/src/common/webview_chromium.cpp +++ b/src/common/webview_chromium.cpp @@ -305,20 +305,18 @@ bool wxWebViewChromium::Create(wxWindow* parent, const wxString& name) { #ifdef __WXGTK__ - style |= wxHSCROLL | wxVSCROLL; - if ( !PreCreation( parent, pos, size ) || - !CreateBase( parent, id, pos, size, style, wxDefaultValidator, name ) ) - { - wxFAIL_MSG( wxT("wxWebViewChromium creation failed") ); + // Currently CEF works only with X11. + if ( wxGetDisplayInfo().type != wxDisplayX11 ) return false; - } -#else + + style |= wxHSCROLL | wxVSCROLL; +#endif + if ( !wxControl::Create(parent, id, pos, size, style, wxDefaultValidator, name) ) { return false; } -#endif if ( !InitCEF() ) return false; @@ -331,46 +329,51 @@ bool wxWebViewChromium::Create(wxWindow* parent, m_historyPosition = -1; m_zoomLevel = wxWEBVIEW_ZOOM_MEDIUM; - CefBrowserSettings browsersettings; - CefWindowInfo info; - m_clientHandler = new ClientHandler{*this}; m_clientHandler->AddRef(); - // Initialize window info to the defaults for a child window #ifdef __WXGTK__ - m_widget = gtk_scrolled_window_new( nullptr, nullptr ); - g_object_ref( m_widget ); - GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW( m_widget ); - // Hide the scroll bar. - gtk_scrolled_window_set_policy( scrolled_window, GTK_POLICY_NEVER, GTK_POLICY_NEVER); - GtkWidget* view_port = gtk_viewport_new( nullptr, nullptr ); + #ifdef __WXGTK3__ - gtk_container_add( GTK_CONTAINER(m_widget), view_port ); -#else - gtk_scrolled_window_add_with_viewport( scrolled_window, view_port ); -#endif - // TODO: figure out correct parameters for Linux SetAsChild() call - ::Window xid = GDK_WINDOW_XID(gtk_widget_get_window(view_port)); - wxASSERT(xid != 0); - info.SetAsChild(xid, CefRect(0, 0, size.GetX(), size.GetY())); - m_parent->DoAddChild( this ); - PostCreation( size ); + // CEF window creation fails with Match error unless we use the default X11 + // visual, which is not the case by default since GTK 3.15, see dae447728d + // (X11: Pick better system and rgba visuals for GL, 2014-10-29). + // + // We do this unconditionally instead of checking for GTK version because + // it shouldn't hurt even with earlier version and nobody uses them anyhow. + GdkScreen* screen = gdk_screen_get_default(); + GdkX11Screen* x11_screen = GDK_X11_SCREEN(screen); + Visual* default_xvisual = DefaultVisual(GDK_SCREEN_XDISPLAY(x11_screen), + GDK_SCREEN_XNUMBER(x11_screen)); + GdkVisual* default_visual = nullptr; - gtk_widget_show( view_port ); -#else - const wxSize sz = GetClientSize(); - info.SetAsChild(GetHandle(), {0, 0, sz.x, sz.y}); -#endif + for ( GList* visuals = gdk_screen_list_visuals(screen); + visuals; + visuals = visuals->next) + { + GdkVisual* visual = GDK_X11_VISUAL (visuals->data); + Visual* xvisual = gdk_x11_visual_get_xvisual(GDK_X11_VISUAL (visuals->data)); - CefBrowserHost::CreateBrowser( - info, - CefRefPtr{m_clientHandler}, - url.ToStdString(), - browsersettings, - nullptr, // No extra info - nullptr // Use global request context - ); + if ( xvisual->visualid == default_xvisual->visualid ) + { + default_visual = visual; + break; + } + } + + if ( default_visual ) + gtk_widget_set_visual(m_widget, default_visual); +#endif // wxGTK3 + + // Under wxGTK we need to wait until the window becomes realized in order + // to get the X11 window handle, so postpone calling DoCreateBrowser() + // until GTKHandleRealized(). + m_url = url; +#else + // Under the other platforms we can call it immediately. + if ( !DoCreateBrowser(url) ) + return false; +#endif this->Bind(wxEVT_SIZE, &wxWebViewChromium::OnSize, this); @@ -379,6 +382,51 @@ bool wxWebViewChromium::Create(wxWindow* parent, return true; } +#ifdef __WXGTK__ + +void wxWebViewChromium::GTKHandleRealized() +{ + // Unfortunately there is nothing we can do here if it fails, so just + // ignore the return value. + DoCreateBrowser(m_url); +} + +#endif // __WXGTK__ + +bool wxWebViewChromium::DoCreateBrowser(const wxString& url) +{ + CefBrowserSettings browsersettings; + + // Initialize window info to the defaults for a child window + CefWindowInfo info; + + // In wxGTK the handle returned by GetHandle() is the GtkWidget, but we + // need the underlying X11 window here. +#ifdef __WXGTK__ + const auto handle = GDK_WINDOW_XID(GTKGetDrawingWindow()); +#else + const auto handle = GetHandle(); +#endif + + const wxSize sz = GetClientSize(); + info.SetAsChild(handle, {0, 0, sz.x, sz.y}); + + if ( !CefBrowserHost::CreateBrowser( + info, + CefRefPtr{m_clientHandler}, + url.ToStdString(), + browsersettings, + nullptr, // No extra info + nullptr // Use global request context + ) ) + { + wxLogTrace(TRACE_CEF, "CefBrowserHost::CreateBrowser() failed"); + return false; + } + + return true; +} + wxWebViewChromium::~wxWebViewChromium() { if (m_clientHandler) @@ -461,23 +509,34 @@ void wxWebViewChromium::ShutdownCEF() void wxWebViewChromium::OnSize(wxSizeEvent& event) { -#ifdef __WXMSW__ - wxSize size = GetClientSize(); - if ( m_clientHandler && m_clientHandler->GetBrowser() && m_clientHandler->GetBrowser()->GetHost() ) - { - HWND handle = m_clientHandler->GetBrowser()->GetHost()->GetWindowHandle(); - - if ( handle ) - { - HDWP hdwp = BeginDeferWindowPos(1); - hdwp = DeferWindowPos(hdwp, handle, nullptr, 0, 0, - size.GetWidth(), size.GetHeight(), SWP_NOZORDER); - EndDeferWindowPos(hdwp); - } - } -#endif - event.Skip(); + + CefRefPtr host; + if ( m_clientHandler ) + { + if ( auto browser = m_clientHandler->GetBrowser() ) + host = browser->GetHost(); + } + + if ( !host ) + return; + + auto handle = host->GetWindowHandle(); + if ( !handle ) + return; + + wxSize size = GetClientSize(); + +#ifdef __WXMSW__ + HDWP hdwp = BeginDeferWindowPos(1); + hdwp = DeferWindowPos(hdwp, handle, nullptr, 0, 0, + size.GetWidth(), size.GetHeight(), SWP_NOZORDER); + EndDeferWindowPos(hdwp); +#elif defined(__WXGTK__) + size *= GetDPIScaleFactor(); + + ::XResizeWindow(wxGetX11Display(), handle, size.x, size.y); +#endif } void wxWebViewChromium::SetPageSource(const wxString& pageSource)