From 5ca49dc56c2a2d95c0c352bf5e3743e4c6a8e37c Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 17 Feb 2024 18:34:15 +0100 Subject: [PATCH] Allow specifying a custom CefClient to use with wxWebViewChromium This opens many customization possibilities beyond those provided by wxWebView API and also allows to process CEF IPC messages much more efficiently than what is possible using wxEvents. Closes #24336. --- Makefile.in | 3 +- build/bakefiles/files.bkl | 1 + build/cmake/files.cmake | 1 + build/files | 1 + build/msw/wx_webview.vcxproj | 1 + build/msw/wx_webview.vcxproj.filters | 3 + include/wx/webview_chromium.h | 19 ++++++ include/wx/webview_chromium_impl.h | 97 ++++++++++++++++++++++++++++ interface/wx/webview_chromium.h | 37 +++++++++++ interface/wx/webview_chromium_impl.h | 64 ++++++++++++++++++ src/common/webview_chromium.cpp | 25 ++++++- 11 files changed, 249 insertions(+), 3 deletions(-) create mode 100644 include/wx/webview_chromium_impl.h create mode 100644 interface/wx/webview_chromium_impl.h diff --git a/Makefile.in b/Makefile.in index 9f05c9bca9..f6b3ff20b1 100644 --- a/Makefile.in +++ b/Makefile.in @@ -4062,7 +4062,8 @@ COND_USE_GUI_1_ALL_GUI_HEADERS = \ wx/webview.h \ wx/webviewarchivehandler.h \ wx/webviewfshandler.h \ - wx/webview_chromium.h + wx/webview_chromium.h \ + wx/webview_chromium_impl.h @COND_USE_GUI_1@ALL_GUI_HEADERS = $(COND_USE_GUI_1_ALL_GUI_HEADERS) COND_MONOLITHIC_1_SHARED_1___monodll___depname = \ $(LIBDIRNAME)/$(DLLPREFIX)$(WXDLLNAMEPREFIXGUI)u$(WXDEBUGFLAG)$(WX_LIB_FLAVOUR)$(WXCOMPILER)$(VENDORTAG)$(WXDLLVERSIONTAG)$(dll___targetsuf3) diff --git a/build/bakefiles/files.bkl b/build/bakefiles/files.bkl index 35165a0455..600f877471 100644 --- a/build/bakefiles/files.bkl +++ b/build/bakefiles/files.bkl @@ -2708,6 +2708,7 @@ IMPORTANT: please read docs/tech/tn0016.txt before modifying this file! wx/webviewarchivehandler.h wx/webviewfshandler.h wx/webview_chromium.h + wx/webview_chromium_impl.h $(WEBVIEW_HDR_PLATFORM) diff --git a/build/cmake/files.cmake b/build/cmake/files.cmake index 540208158f..4f153ceea8 100644 --- a/build/cmake/files.cmake +++ b/build/cmake/files.cmake @@ -2533,6 +2533,7 @@ set(WEBVIEW_CMN_HDR wx/webviewarchivehandler.h wx/webviewfshandler.h wx/webview_chromium.h + wx/webview_chromium_impl.h ) set(WEBVIEW_OSX_SHARED_HDR diff --git a/build/files b/build/files index 1a6102efe7..77dd21df01 100644 --- a/build/files +++ b/build/files @@ -2486,6 +2486,7 @@ WEBVIEW_CMN_HDR = wx/webviewarchivehandler.h wx/webviewfshandler.h wx/webview_chromium.h + wx/webview_chromium_impl.h WEBVIEW_OSX_SHARED_HDR = wx/osx/webviewhistoryitem_webkit.h diff --git a/build/msw/wx_webview.vcxproj b/build/msw/wx_webview.vcxproj index d350da412e..e8ff5db009 100644 --- a/build/msw/wx_webview.vcxproj +++ b/build/msw/wx_webview.vcxproj @@ -507,6 +507,7 @@ + diff --git a/build/msw/wx_webview.vcxproj.filters b/build/msw/wx_webview.vcxproj.filters index d92aa9bd94..9cd2b021bd 100644 --- a/build/msw/wx_webview.vcxproj.filters +++ b/build/msw/wx_webview.vcxproj.filters @@ -61,6 +61,9 @@ Common Headers + + Common Headers + Common Headers diff --git a/include/wx/webview_chromium.h b/include/wx/webview_chromium.h index 1a587f76e7..4074ad8562 100644 --- a/include/wx/webview_chromium.h +++ b/include/wx/webview_chromium.h @@ -18,6 +18,8 @@ class WXDLLIMPEXP_FWD_BASE wxFileName; extern WXDLLIMPEXP_DATA_WEBVIEW(const char) wxWebViewBackendChromium[]; +class CefClient; + // Private namespace containing classes used only in the implementation. namespace wxCEF { @@ -170,6 +172,10 @@ private: friend class wxCEF::ClientHandler; wxCEF::ClientHandler* m_clientHandler = nullptr; + // Actual client used by CEF: this can be either m_clientHandler itself or + // a custom client provided by the application. + CefClient* m_actualClient = nullptr; + friend class wxWebViewChromiumModule; static bool ms_cefInitialized; @@ -198,6 +204,19 @@ public: // Logging level must be one of cef_log_severity_t values (0 means default). int m_logLevel = 0; + + // Function to create the custom CefClient to use if non-null. + // + // The CefClient subclass must delegate all not otherwise implemented + // functions to the provided client (and should always delegate the + // lifetime-related callbacks). + // + // It is recommended, although not required, to derive the custom client + // from wxDelegatingCefClient defined in wx/webview_chromium_impl.h. + CefClient* (*m_clientCreate)(CefClient* client, void* data) = nullptr; + + // Data to pass to m_clientCreate if it is used. + void* m_clientCreateData = nullptr; }; diff --git a/include/wx/webview_chromium_impl.h b/include/wx/webview_chromium_impl.h new file mode 100644 index 0000000000..d12e19c4f0 --- /dev/null +++ b/include/wx/webview_chromium_impl.h @@ -0,0 +1,97 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: wx/webview_chromium_impl.h +// Purpose: Helpers for implementing custom CefClient for wxWebViewChromium +// Author: Vadim Zeitlin +// Created: 2024-02-17 +// Copyright: (c) 2024 Vadim Zeitlin +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_WEBVIEW_CHROMIUM_IMPL_H_ +#define _WX_WEBVIEW_CHROMIUM_IMPL_H_ + +// Note that this header includes CEF headers and so the appropriate include +// path should be set up when using it. + +#ifdef __VISUALC__ +#pragma warning(push) +#pragma warning(disable:4100) +#endif + +wxGCC_WARNING_SUPPRESS(unused-parameter) + +#include "include/cef_client.h" + +wxGCC_WARNING_RESTORE(unused-parameter) + +#ifdef __VISUALC__ +#pragma warning(pop) +#endif + +// ---------------------------------------------------------------------------- +// Convenient base class for custom CefClient implementations. +// ---------------------------------------------------------------------------- + +class wxDelegatingCefClient : public CefClient +{ +public: + // Forward all CefClient methods to the original client. + virtual CefRefPtr GetAudioHandler() override + { return m_clientOrig->GetAudioHandler(); } + virtual CefRefPtr GetCommandHandler() override + { return m_clientOrig->GetCommandHandler(); } + virtual CefRefPtr GetContextMenuHandler() override + { return m_clientOrig->GetContextMenuHandler(); } + virtual CefRefPtr GetDialogHandler() override + { return m_clientOrig->GetDialogHandler(); } + virtual CefRefPtr GetDisplayHandler() override + { return m_clientOrig->GetDisplayHandler(); } + virtual CefRefPtr GetDownloadHandler() override + { return m_clientOrig->GetDownloadHandler(); } + virtual CefRefPtr GetDragHandler() override + { return m_clientOrig->GetDragHandler(); } + virtual CefRefPtr GetFindHandler() override + { return m_clientOrig->GetFindHandler(); } + virtual CefRefPtr GetFocusHandler() override + { return m_clientOrig->GetFocusHandler(); } + virtual CefRefPtr GetFrameHandler() override + { return m_clientOrig->GetFrameHandler(); } + virtual CefRefPtr GetPermissionHandler() override + { return m_clientOrig->GetPermissionHandler(); } + virtual CefRefPtr GetJSDialogHandler() override + { return m_clientOrig->GetJSDialogHandler(); } + virtual CefRefPtr GetKeyboardHandler() override + { return m_clientOrig->GetKeyboardHandler(); } + virtual CefRefPtr GetLifeSpanHandler() override + { return m_clientOrig->GetLifeSpanHandler(); } + virtual CefRefPtr GetLoadHandler() override + { return m_clientOrig->GetLoadHandler(); } + virtual CefRefPtr GetPrintHandler() override + { return m_clientOrig->GetPrintHandler(); } + virtual CefRefPtr GetRenderHandler() override + { return m_clientOrig->GetRenderHandler(); } + virtual CefRefPtr GetRequestHandler() override + { return m_clientOrig->GetRequestHandler(); } + virtual bool OnProcessMessageReceived(CefRefPtr browser, + CefRefPtr frame, + CefProcessId source_process, + CefRefPtr message) override + { + return m_clientOrig->OnProcessMessageReceived(browser, frame, + source_process, message); + } + +protected: + // Objects of this class shouldn't be created, only derived classes should + // be used, hence the constructor is protected. + explicit wxDelegatingCefClient(CefClient* clientOrig) + : m_clientOrig{clientOrig} + { + } + + CefRefPtr const m_clientOrig; + + IMPLEMENT_REFCOUNTING(wxDelegatingCefClient); +}; + +#endif // _WX_WEBVIEW_CHROMIUM_IMPL_H_ diff --git a/interface/wx/webview_chromium.h b/interface/wx/webview_chromium.h index 8a5f137602..528aec53e6 100644 --- a/interface/wx/webview_chromium.h +++ b/interface/wx/webview_chromium.h @@ -386,6 +386,36 @@ public: Default value 0 means to use default "INFO" log level. */ int m_logLevel = 0; + + /** + Function to create the custom CefClient to use if non-null. + + CEF uses an object of CefClient class to customize handling of many + operations, by allowing to return custom objects from its callbacks, + and for processing IPC messages received from the other processes used + by CEF. By defining this function pointer, the application can use its + own CefClient subclass to customize many aspects of CEF behaviour + beyond what is possible using the standard wxWebView API. + + Please note that the returned object must delegate all not otherwise + implemented functions to the provided @a client (and should always + delegate the lifetime-related callbacks). You can ensure that this is + the case by deriving your custom CefClient subclass from + wxDelegatingCefClient, but you still need to do it manually if not + using this class. + */ + CefClient* (*m_clientCreate)(CefClient* client, void* data) = nullptr; + + /** + Data to pass to m_clientCreate if it is used. + + This is just an arbitrary pointer, which is passed as argument to + m_clientCreate function if it is non-null. + + This pointer itself may be null if it is not necessary to pass any + extra data to the client creation function. + */ + void* m_clientCreateData = nullptr; }; /** @@ -416,6 +446,13 @@ public: ); @endcode + @note Using wxEvent dispatching adds a significant overhead to handling of + CEF IPC messages, so if performance is important (i.e. many such messages + are expected), it is recommended to configure wxWebViewChromium to use a + custom `CefClient` as described in wxWebViewConfigurationChromium + documentation and handle the messages directly in the overridden + `OnProcessMessageReceived()` of the custom client class. + @since 3.3.0 @library{wxwebview} @category{webview} diff --git a/interface/wx/webview_chromium_impl.h b/interface/wx/webview_chromium_impl.h new file mode 100644 index 0000000000..a5a0963d48 --- /dev/null +++ b/interface/wx/webview_chromium_impl.h @@ -0,0 +1,64 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: webview_chromium_impl.h +// Purpose: Documentation for wxWebViewChromium implementation helpers +// Author: Vadim Zeitlin +// Created: 2024-02-17 +// Copyright: (c) 2024 Vadim Zeitlin +// Licence: wxWindows licence +///////////////////////////////////////////////////////////////////////////// + +/** + Helper class to simplify creating custom CefClient subclasses. + + Please note that the application including this header must set up the + compiler options to include the CEF directory as one of the include paths, + as this header -- unlike wx/webview_chromium.h -- includes CEF headers. + + Here is a simple example of using this class to customize processing the + messages received from the other CEF processes: + + @code + #include + + struct CustomClient : wxDelegatingCefClient + { + using wxDelegatingCefClient::wxDelegatingCefClient; + + static CefClient* Create(CefClient* client, void* WXUNUSED(data) + { + return new CustomClient(client); + } + + bool OnProcessMessageReceived(CefRefPtr browser, + CefRefPtr frame, + CefProcessId source_process, + CefRefPtr message) override + { + // Handle the message here. + + return true; // or false if not handled + } + }; + + void MyFunction() + { + wxWebViewConfiguration config = wxWebView::NewConfiguration(wxWebViewBackendChromium); + + auto configChrome = + static_cast(config.GetNativeConfiguration()); + configChrome->m_clientCreate = &CustomClient::Create; + + auto webview = new wxWebViewChromium(config); + if ( !webview->Create(this, wxID_ANY, url) ) + { + // Handle error. + } + } + @endcode + + @since 3.3.0 + @category{webview} + */ +class wxDelegatingCefClient : CefClient +{ +}; diff --git a/src/common/webview_chromium.cpp b/src/common/webview_chromium.cpp index e958d605cc..6c886fd668 100644 --- a/src/common/webview_chromium.cpp +++ b/src/common/webview_chromium.cpp @@ -829,9 +829,22 @@ bool wxWebViewChromium::DoCreateBrowser(const wxString& url) } } + // Check if we need to create a custom client handler. + const auto configChrome = + static_cast(m_implData->m_config.GetNativeConfiguration()); + if ( auto create = configChrome->m_clientCreate ) + { + m_actualClient = create(m_clientHandler, configChrome->m_clientCreateData); + if ( m_actualClient ) + m_actualClient->AddRef(); + } + + if ( !m_actualClient ) + m_actualClient = m_clientHandler; + if ( !CefBrowserHost::CreateBrowser( info, - CefRefPtr{m_clientHandler}, + CefRefPtr{m_actualClient}, url.ToStdString(), browsersettings, nullptr, // No extra info @@ -856,7 +869,10 @@ wxWebViewChromium::~wxWebViewChromium() constexpr bool forceClose = true; m_clientHandler->GetHost()->CloseBrowser(forceClose); - m_clientHandler->Release(); + + // We need to destroy the client handler used by the browser to allow + // it to close. + m_actualClient->Release(); // We need to wait until the browser is really closed, which happens // asynchronously, as otherwise we could exit the program and call @@ -905,6 +921,11 @@ wxWebViewChromium::~wxWebViewChromium() // really destroying the object before CefShutdown() is called. wxUnusedVar(handle); #endif + + // If we hadn't release our own client handler above, we need to do it + // now (notice that it's safe to do it, as it can't be used any more). + if ( m_clientHandler != m_actualClient ) + m_clientHandler->Release(); } delete m_implData;