Make wxWebViewChromium work with wxGTK3 and X11

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.
This commit is contained in:
Vadim Zeitlin 2023-09-02 23:13:07 +02:00
parent d3cc4678d5
commit f5e2af9a28
4 changed files with 158 additions and 60 deletions

View file

@ -118,10 +118,18 @@ public:
//Virtual Filesystem Support
virtual void RegisterHandler(wxSharedPtr<wxWebViewHandler> 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<wxSharedPtr<wxWebViewHistoryItem> > 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;

View file

@ -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.

View file

@ -460,14 +460,17 @@ WebFrame::WebFrame(const wxString& url, bool isMain, wxWebViewWindowFeatures* wi
m_browser->RegisterHandler(wxSharedPtr<wxWebViewHandler>(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

View file

@ -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<CefClient>{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<CefClient>{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<CefBrowserHost> 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)