diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index a31ef56a08..59c9de7bec 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -4,6 +4,43 @@ JUCE breaking changes develop ======= +Change +------ +The constructor of WebBrowserComponent now requires passing in an instance of +a new Options class instead of a single option boolean. The +WindowsWebView2WebBrowserComponent class was removed. + +Possible Issues +--------------- +Code using the WebBrowserComponent's boolean parameter to indicate if a +webpage should be unloaded when the component is hidden, will now fail to +compile. Additionally, any code using the WindowsWebView2WebBrowserComponent +class will fail to compile. Code relying on the default value of the +WebBrowserComponent's constructor are not affected. + +Workaround +---------- +Instead of passing in a single boolean to the WebBrowserComponent's +constructor you should now set this option via tha +WebBrowserComponent::Options::withKeepPageLoadedWhenBrowserIsHidden method. + +If you were previously using WindowsWebView2WebBrowserComponent to indicate to +JUCE that you prefer JUCE to use Windows' Webview2 browser backend, you now do +this by setting the WebBrowserComponent::Options::withBackend method. The +WebView2Preferences can now be modified with the methods in +WebBrowserComponent::Options::WinWebView2. + +Rationale +--------- +The old API made adding further options to the WebBrowserComponent cumbersome +especially as the WindowsWebView2WebBrowserComponent already had a parameter +very similar to the above Options class, whereas the base class did not use +such a parameter. Furthermore, using an option to specify the preferred +browser backend is more intuitive then requiring the user to derive from a +special class, especially if additional browser backends are added in the +future. + + Change ------ The function AudioIODeviceCallback::audioDeviceIOCallback() was removed. diff --git a/modules/juce_gui_extra/embedding/juce_ActiveXControlComponent.h b/modules/juce_gui_extra/embedding/juce_ActiveXControlComponent.h index bb99430f51..f789948fdc 100644 --- a/modules/juce_gui_extra/embedding/juce_ActiveXControlComponent.h +++ b/modules/juce_gui_extra/embedding/juce_ActiveXControlComponent.h @@ -107,6 +107,11 @@ public: */ bool areMouseEventsAllowed() const noexcept { return mouseEventsAllowed; } + //============================================================================== + /** Set an instance of IDispatch where dispatch events should be delivered to + */ + void setEventHandler (void* eventHandler); + //============================================================================== /** @internal */ void paint (Graphics&) override; diff --git a/modules/juce_gui_extra/juce_gui_extra.cpp b/modules/juce_gui_extra/juce_gui_extra.cpp index 4f60cb8fc9..173cd09bd1 100644 --- a/modules/juce_gui_extra/juce_gui_extra.cpp +++ b/modules/juce_gui_extra/juce_gui_extra.cpp @@ -185,11 +185,3 @@ #include "native/juce_android_WebBrowserComponent.cpp" #endif #endif - -//============================================================================== -#if ! JUCE_WINDOWS && JUCE_WEB_BROWSER - juce::WebBrowserComponent::WebBrowserComponent (ConstructWithoutPimpl) {} - juce::WindowsWebView2WebBrowserComponent::WindowsWebView2WebBrowserComponent (bool unloadWhenHidden, - const WebView2Preferences&) - : WebBrowserComponent (unloadWhenHidden) {} -#endif diff --git a/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h b/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h index cdace6bd8b..e65fbea5c2 100644 --- a/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h +++ b/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h @@ -46,6 +46,136 @@ class JUCE_API WebBrowserComponent : public Component { public: //============================================================================== + class JUCE_API Options + { + public: + //============================================================================== + enum class Backend + { + /** + Default web browser backend. WebKit will be used on macOS, gtk-webkit2 on Linux and internet + explorer on Windows. On Windows, the default may change to webview2 in the fututre. + */ + defaultBackend, + + /** + Use Internet Explorer as the backend on Windows. By default, IE will use an ancient version + of IE. To change this behaviour, you either need to add the following html element into your page's + head section: + + + + or you need to change windows registry values for your application. More infromation on the latter + can be found here: + + https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/general-info/ee330730(v=vs.85)?redirectedfrom=MSDN#browser-emulation + */ + ie, + + /** + Use the chromium based WebView2 engine on Windows + */ + webview2 + }; + + /** + Use a particular backend to create the WebViewBrowserComponent. JUCE will silently + fallback to the default backend if the selected backend is not supported. To check + if a specific backend is supported on your platform or not, use + WebBrowserComponent::areOptionsSupported. + */ + [[nodiscard]] Options withBackend (Backend backend) const { return withMember (*this, &Options::browserBackend, backend); } + + //============================================================================== + /** + Tell JUCE to keep the web page alive when the WebBrowserComponent is not visible. + By default, JUCE will replace the current page with a blank page - this can be + handy to stop the browser using resources in the background when it's not + actually being used. + */ + [[nodiscard]] Options withKeepPageLoadedWhenBrowserIsHidden () const { return withMember (*this, &Options::keepPageLoadedWhenBrowserIsHidden, true); } + + /** + Use a specific user agent string when requesting web pages. + */ + [[nodiscard]] Options withUserAgent (String ua) const { return withMember (*this, &Options::userAgent, std::move (ua)); } + + //============================================================================== + /** Options specific to the WebView2 backend. These options will be ignored + if another backend is used. + */ + class WinWebView2 + { + public: + //============================================================================== + /** Sets a custom location for the WebView2Loader.dll that is not a part of the + standard system DLL search paths. + */ + [[nodiscard]] WinWebView2 withDLLLocation (const File& location) const { return withMember (*this, &WinWebView2::dllLocation, location); } + + /** Sets a non-default location for storing user data for the browser instance. */ + [[nodiscard]] WinWebView2 withUserDataFolder (const File& folder) const { return withMember (*this, &WinWebView2::userDataFolder, folder); } + + /** If this is set, the status bar usually displayed in the lower-left of the webview + will be disabled. + */ + [[nodiscard]] WinWebView2 withStatusBarDisabled() const { return withMember (*this, &WinWebView2::disableStatusBar, true); } + + /** If this is set, a blank page will be displayed on error instead of the default + built-in error page. + */ + [[nodiscard]] WinWebView2 withBuiltInErrorPageDisabled() const { return withMember (*this, &WinWebView2::disableBuiltInErrorPage, true); } + + /** Sets the background colour that WebView2 renders underneath all web content. + + This colour must either be fully opaque or transparent. On Windows 7 this + colour must be opaque. + */ + [[nodiscard]] WinWebView2 withBackgroundColour (const Colour& colour) const + { + // the background colour must be either fully opaque or transparent! + jassert (colour.isOpaque() || colour.isTransparent()); + + return withMember (*this, &WinWebView2::backgroundColour, colour); + } + + //============================================================================== + File getDLLLocation() const { return dllLocation; } + File getUserDataFolder() const { return userDataFolder; } + bool getIsStatusBarDisabled() const noexcept { return disableStatusBar; } + bool getIsBuiltInErrorPageDisabled() const noexcept { return disableBuiltInErrorPage; } + Colour getBackgroundColour() const { return backgroundColour; } + + private: + //============================================================================== + File dllLocation, userDataFolder; + bool disableStatusBar = false, disableBuiltInErrorPage = false; + Colour backgroundColour; + }; + + [[nodiscard]] Options withWinWebView2Options (const WinWebView2& winWebView2Options) const + { + return withMember (*this, &Options::winWebView2, winWebView2Options); + } + + //============================================================================== + Backend getBackend() const noexcept { return browserBackend; } + bool keepsPageLoadedWhenBrowserIsHidden() const noexcept { return keepPageLoadedWhenBrowserIsHidden; } + String getUserAgent() const { return userAgent; } + WinWebView2 getWinWebView2BackendOptions() const { return winWebView2; } + + private: + //============================================================================== + Backend browserBackend = Backend::defaultBackend; + bool keepPageLoadedWhenBrowserIsHidden = false; + String userAgent; + WinWebView2 winWebView2; + }; + + //============================================================================== + /** Creates a WebBrowserComponent with default options*/ + WebBrowserComponent() : WebBrowserComponent (Options {}) {} + /** Creates a WebBrowserComponent. Once it's created and visible, send the browser to a URL using goToURL(). @@ -56,11 +186,15 @@ public: the browser using resources in the background when it's not actually being used. */ - explicit WebBrowserComponent (bool unloadPageWhenBrowserIsHidden = true); + explicit WebBrowserComponent (const Options& options); /** Destructor. */ ~WebBrowserComponent() override; + //============================================================================== + /** Check if the specified options are supported on this platform. */ + static bool areOptionsSupported (const Options& options); + //============================================================================== /** Sends the browser to a particular URL. @@ -141,17 +275,6 @@ public: /** @internal */ class Pimpl; -protected: - friend class WindowsWebView2WebBrowserComponent; - - /** @internal */ - struct ConstructWithoutPimpl - { - explicit ConstructWithoutPimpl (bool unloadOnHide) : unloadWhenHidden (unloadOnHide) {} - const bool unloadWhenHidden; - }; - explicit WebBrowserComponent (ConstructWithoutPimpl); - private: //============================================================================== std::unique_ptr browser; @@ -165,123 +288,6 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebBrowserComponent) }; - -//============================================================================== -/** Class used to create a set of preferences to pass to the WindowsWebView2WebBrowserComponent - wrapper constructor to modify aspects of its behaviour and settings. - - You can chain together a series of calls to this class's methods to create a set of whatever - preferences you want to specify. - - @tags{GUI} -*/ -class JUCE_API WebView2Preferences -{ -public: - //============================================================================== - /** Sets a custom location for the WebView2Loader.dll that is not a part of the - standard system DLL search paths. - */ - [[nodiscard]] WebView2Preferences withDLLLocation (const File& location) const { return with (&WebView2Preferences::dllLocation, location); } - - /** Sets a non-default location for storing user data for the browser instance. */ - WebView2Preferences withUserDataFolder (const File& folder) const { return with (&WebView2Preferences::userDataFolder, folder); } - - /** If this is set, the status bar usually displayed in the lower-left of the webview - will be disabled. - */ - [[nodiscard]] WebView2Preferences withStatusBarDisabled() const { return with (&WebView2Preferences::disableStatusBar, true); } - - /** If this is set, a blank page will be displayed on error instead of the default - built-in error page. - */ - [[nodiscard]] WebView2Preferences withBuiltInErrorPageDisabled() const { return with (&WebView2Preferences::disableBuiltInErrorPage, true); } - - /** Sets the background colour that WebView2 renders underneath all web content. - - This colour must either be fully opaque or transparent. On Windows 7 this - colour must be opaque. - */ - [[nodiscard]] WebView2Preferences withBackgroundColour (const Colour& colour) const - { - // the background colour must be either fully opaque or transparent! - jassert (colour.isOpaque() || colour.isTransparent()); - - return with (&WebView2Preferences::backgroundColour, colour); - } - - //============================================================================== - File getDLLLocation() const { return dllLocation; } - File getUserDataFolder() const { return userDataFolder; } - bool getIsStatusBarDisabled() const noexcept { return disableStatusBar; } - bool getIsBuiltInErrorPageDisabled() const noexcept { return disableBuiltInErrorPage; } - Colour getBackgroundColour() const { return backgroundColour; } - -private: - //============================================================================== - template - WebView2Preferences with (Member&& member, Item&& item) const - { - auto options = *this; - options.*member = std::forward (item); - - return options; - } - - File dllLocation, userDataFolder; - bool disableStatusBar = false, disableBuiltInErrorPage = false; - Colour backgroundColour = Colours::white; -}; - -/** - If you have enabled the JUCE_USE_WIN_WEBVIEW2 flag then this wrapper will attempt to - use the Microsoft Edge (Chromium) WebView2 control instead of IE on Windows. It will - behave the same as WebBrowserComponent on all other platforms and will fall back to - IE on Windows if the WebView2 requirements are not met. - - This requires Microsoft Edge (minimum version 82.0.488.0) to be installed at runtime. - - Currently this also requires that WebView2Loader.dll, which can be found in the - Microsoft.Web.WebView package, is installed at runtime. As this is not a standard - system DLL, we can't rely on it being found via the normal system DLL search paths. - Therefore in order to use WebView2 you need to ensure that WebView2Loader.dll is - installed either to a location covered by the Windows DLL system search paths or - to the folder specified in the WebView2Preferences. - - @tags{GUI} -*/ -class WindowsWebView2WebBrowserComponent : public WebBrowserComponent -{ -public: - //============================================================================== - /** Creates a WebBrowserComponent that is compatible with the WebView2 control - on Windows. - - @param unloadPageWhenBrowserIsHidden if this is true, then when the browser - component is taken offscreen, it'll clear the current page - and replace it with a blank page - this can be handy to stop - the browser using resources in the background when it's not - actually being used. - @param preferences a set of preferences used to control aspects of the webview's - behaviour. - - @see WebView2Preferences - */ - WindowsWebView2WebBrowserComponent (bool unloadPageWhenBrowserIsHidden = true, - const WebView2Preferences& preferences = {}); - - // This constructor has been deprecated. Use the new constructor that takes a - // WebView2Preferences instead. - explicit WindowsWebView2WebBrowserComponent (bool unloadPageWhenBrowserIsHidden = true, - const File& dllLocation = {}, - const File& userDataFolder = {}) - : WindowsWebView2WebBrowserComponent (unloadPageWhenBrowserIsHidden, - WebView2Preferences().withDLLLocation (dllLocation) - .withUserDataFolder (userDataFolder)) - { - } -}; - #endif } // namespace juce diff --git a/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp b/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp index 35adf391c1..462bc43936 100644 --- a/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp +++ b/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp @@ -175,7 +175,8 @@ DECLARE_JNI_CLASS (AndroidCookieManager, "android/webkit/CookieManager") METHOD (setBuiltInZoomControls, "setBuiltInZoomControls", "(Z)V") \ METHOD (setDisplayZoomControls, "setDisplayZoomControls", "(Z)V") \ METHOD (setJavaScriptEnabled, "setJavaScriptEnabled", "(Z)V") \ - METHOD (setSupportMultipleWindows, "setSupportMultipleWindows", "(Z)V") + METHOD (setSupportMultipleWindows, "setSupportMultipleWindows", "(Z)V") \ + METHOD (setUserAgentString, "setUserAgentString", "(Ljava/lang/String;)V") DECLARE_JNI_CLASS (WebSettings, "android/webkit/WebSettings") #undef JNI_CLASS_MEMBERS @@ -197,7 +198,7 @@ class WebBrowserComponent::Pimpl : public AndroidViewComponent, public AsyncUpdater { public: - Pimpl (WebBrowserComponent& o) + Pimpl (WebBrowserComponent& o, const String& userAgent) : owner (o) { auto* env = getEnv(); @@ -210,6 +211,9 @@ public: env->CallVoidMethod (settings, WebSettings.setDisplayZoomControls, false); env->CallVoidMethod (settings, WebSettings.setSupportMultipleWindows, true); + if (userAgent.isNotEmpty()) + env->CallVoidMethod (settings, WebSettings.setUserAgentString, javaString (userAgent).get()); + juceWebChromeClient = GlobalRef (LocalRef (env->NewObject (JuceWebChromeClient, JuceWebChromeClient.constructor, reinterpret_cast (this)))); env->CallVoidMethod ((jobject) getView(), AndroidWebView.setWebChromeClient, juceWebChromeClient.get()); @@ -582,13 +586,13 @@ private: }; //============================================================================== -WebBrowserComponent::WebBrowserComponent (const bool unloadWhenHidden) +WebBrowserComponent::WebBrowserComponent (const Options& options) : blankPageShown (false), - unloadPageWhenHidden (unloadWhenHidden) + unloadPageWhenHidden (! options.keepsPageLoadedWhenBrowserIsHidden()) { setOpaque (true); - browser.reset (new Pimpl (*this)); + browser.reset (new Pimpl (*this, options.getUserAgent())); addAndMakeVisible (browser.get()); } @@ -719,6 +723,11 @@ void WebBrowserComponent::clearCookies() } } +bool WebBrowserComponent::areOptionsSupported (const Options& options) +{ + return (options.getBackend() == Options::Backend::defaultBackend); +} + WebBrowserComponent::Pimpl::JuceWebViewClient16_Class WebBrowserComponent::Pimpl::JuceWebViewClient16; WebBrowserComponent::Pimpl::JuceWebViewClient21_Class WebBrowserComponent::Pimpl::JuceWebViewClient21; WebBrowserComponent::Pimpl::JuceWebChromeClient_Class WebBrowserComponent::Pimpl::JuceWebChromeClient; diff --git a/modules/juce_gui_extra/native/juce_linux_X11_WebBrowserComponent.cpp b/modules/juce_gui_extra/native/juce_linux_X11_WebBrowserComponent.cpp index bb4b9c43ef..287131b511 100644 --- a/modules/juce_gui_extra/native/juce_linux_X11_WebBrowserComponent.cpp +++ b/modules/juce_gui_extra/native/juce_linux_X11_WebBrowserComponent.cpp @@ -40,6 +40,9 @@ public: JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_settings_set_hardware_acceleration_policy, juce_webkit_settings_set_hardware_acceleration_policy, (WebKitSettings*, int), void) + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_settings_set_user_agent, juce_webkit_settings_set_user_agent, + (WebKitSettings*, const gchar*), void) + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_new_with_settings, juce_webkit_web_view_new_with_settings, (WebKitSettings*), GtkWidget*) @@ -168,6 +171,7 @@ private: return loadSymbols (webkitLib, makeSymbolBinding (juce_webkit_settings_new, "webkit_settings_new"), makeSymbolBinding (juce_webkit_settings_set_hardware_acceleration_policy, "webkit_settings_set_hardware_acceleration_policy"), + makeSymbolBinding (juce_webkit_settings_set_user_agent, "webkit_settings_set_user_agent"), makeSymbolBinding (juce_webkit_web_view_new_with_settings, "webkit_web_view_new_with_settings"), makeSymbolBinding (juce_webkit_policy_decision_use, "webkit_policy_decision_use"), makeSymbolBinding (juce_webkit_policy_decision_ignore, "webkit_policy_decision_ignore"), @@ -344,9 +348,10 @@ class GtkChildProcess : private CommandReceiver::Responder { public: //============================================================================== - GtkChildProcess (int inChannel, int outChannelToUse) + GtkChildProcess (int inChannel, int outChannelToUse, const String& userAgentToUse) : outChannel (outChannelToUse), - receiver (this, inChannel) + receiver (this, inChannel), + userAgent (userAgentToUse) {} int entry() @@ -361,6 +366,8 @@ public: auto* settings = WebKitSymbols::getInstance()->juce_webkit_settings_new(); WebKitSymbols::getInstance()->juce_webkit_settings_set_hardware_acceleration_policy (settings, /* WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER */ 2); + if (userAgent.isNotEmpty()) + WebKitSymbols::getInstance()->juce_webkit_settings_set_user_agent (settings, userAgent.toRawUTF8()); auto* plug = WebKitSymbols::getInstance()->juce_gtk_plug_new (0); auto* container = WebKitSymbols::getInstance()->juce_gtk_scrolled_window_new (nullptr, nullptr); @@ -606,6 +613,7 @@ private: int outChannel = 0; CommandReceiver receiver; + String userAgent; WebKitWebView* webview = nullptr; Array decisions; }; @@ -615,8 +623,8 @@ class WebBrowserComponent::Pimpl : private Thread, private CommandReceiver::Responder { public: - Pimpl (WebBrowserComponent& parent) - : Thread ("Webview"), owner (parent) + Pimpl (WebBrowserComponent& parent, const String& userAgentToUse) + : Thread ("Webview"), owner (parent), userAgent (userAgentToUse) { webKitIsAvailable = WebKitSymbols::getInstance()->isWebKitAvailable(); } @@ -776,7 +784,6 @@ private: close (inPipe[0]); close (outPipe[1]); - HeapBlock argv (5); StringArray arguments; arguments.add (File::getSpecialLocation (File::currentExecutableFile).getFullPathName()); @@ -784,15 +791,21 @@ private: arguments.add (String (outPipe[0])); arguments.add (String (inPipe [1])); - for (int i = 0; i < arguments.size(); ++i) - argv[i] = arguments[i].toRawUTF8(); + if (userAgent.isNotEmpty()) + arguments.add (userAgent); - argv[4] = nullptr; + std::vector argv; + argv.reserve (static_cast (arguments.size() + 1)); + + for (const auto& arg : arguments) + argv.push_back (arg.toRawUTF8()); + + argv.push_back (nullptr); if (JUCEApplicationBase::isStandaloneApp()) - execv (arguments[0].toRawUTF8(), (char**) argv.getData()); + execv (arguments[0].toRawUTF8(), (char**) argv.data()); else - juce_gtkWebkitMain (4, (const char**) argv.getData()); + juce_gtkWebkitMain (arguments.size(), (const char**) argv.data()); exit (0); } @@ -904,6 +917,7 @@ private: bool webKitIsAvailable = false; WebBrowserComponent& owner; + String userAgent; std::unique_ptr receiver; int childProcess = 0, inChannel = 0, outChannel = 0; int threadControl[2]; @@ -913,9 +927,8 @@ private: }; //============================================================================== -WebBrowserComponent::WebBrowserComponent (const bool unloadWhenHidden) - : browser (new Pimpl (*this)), - unloadPageWhenHidden (unloadWhenHidden) +WebBrowserComponent::WebBrowserComponent (const Options& options) + : browser (new Pimpl (*this, options.getUserAgent())) { ignoreUnused (blankPageShown); ignoreUnused (unloadPageWhenHidden); @@ -1018,13 +1031,19 @@ void WebBrowserComponent::clearCookies() jassertfalse; } +bool WebBrowserComponent::areOptionsSupported (const Options& options) +{ + return (options.getBackend() == Options::Backend::defaultBackend); +} + int juce_gtkWebkitMain (int argc, const char* argv[]) { - if (argc != 4) + if (argc < 4) return -1; GtkChildProcess child (String (argv[2]).getIntValue(), - String (argv[3]).getIntValue()); + String (argv[3]).getIntValue(), + argc >= 5 ? String (argv[4]) : String()); return child.entry(); } diff --git a/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm b/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm index 574aa1655b..1d0fef01d9 100644 --- a/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm +++ b/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm @@ -412,7 +412,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") class WebViewImpl : public WebViewBase { public: - WebViewImpl (WebBrowserComponent* owner) + WebViewImpl (WebBrowserComponent* owner, const String& userAgent) { static WebViewKeyEquivalentResponder webviewClass; @@ -420,6 +420,8 @@ public: frameName: nsEmptyString() groupName: nsEmptyString()]); + webView.get().customUserAgent = juceStringToNS (userAgent); + static DownloadClickDetectorClass cls; clickListener.reset ([cls.createInstance() init]); DownloadClickDetectorClass::setOwner (clickListener.get(), owner); @@ -497,7 +499,7 @@ JUCE_END_IGNORE_WARNINGS_GCC_LIKE class API_AVAILABLE (macos (10.11)) WKWebViewImpl : public WebViewBase { public: - WKWebViewImpl (WebBrowserComponent* owner) + WKWebViewImpl (WebBrowserComponent* owner, const String& userAgent) { #if JUCE_MAC static WebViewKeyEquivalentResponder webviewClass; @@ -507,6 +509,9 @@ public: webView.reset ([[WKWebView alloc] initWithFrame: CGRectMake (0, 0, 100.0f, 100.0f)]); #endif + if (userAgent.isNotEmpty()) + webView.get().customUserAgent = juceStringToNS (userAgent); + static WebViewDelegateClass cls; webViewDelegate.reset ([cls.createInstance() init]); WebViewDelegateClass::setOwner (webViewDelegate.get(), owner); @@ -576,13 +581,13 @@ class WebBrowserComponent::Pimpl #endif { public: - Pimpl (WebBrowserComponent* owner) + Pimpl (WebBrowserComponent* owner, const String& userAgent) { if (@available (macOS 10.11, *)) - webView = std::make_unique (owner); + webView = std::make_unique (owner, userAgent); #if JUCE_MAC else - webView = std::make_unique (owner); + webView = std::make_unique (owner, userAgent); #endif setView (webView->getWebView()); @@ -612,11 +617,11 @@ private: }; //============================================================================== -WebBrowserComponent::WebBrowserComponent (bool unloadWhenHidden) - : unloadPageWhenHidden (unloadWhenHidden) +WebBrowserComponent::WebBrowserComponent (const Options& options) + : unloadPageWhenHidden (! options.keepsPageLoadedWhenBrowserIsHidden()) { setOpaque (true); - browser.reset (new Pimpl (this)); + browser.reset (new Pimpl (this, options.getUserAgent())); addAndMakeVisible (browser.get()); } @@ -738,4 +743,10 @@ void WebBrowserComponent::clearCookies() [[NSUserDefaults standardUserDefaults] synchronize]; } +//============================================================================== +bool WebBrowserComponent::areOptionsSupported (const Options& options) +{ + return (options.getBackend() == Options::Backend::defaultBackend); +} + } // namespace juce diff --git a/modules/juce_gui_extra/native/juce_win32_ActiveXComponent.cpp b/modules/juce_gui_extra/native/juce_win32_ActiveXComponent.cpp index 7f1743329e..d5f98f874d 100644 --- a/modules/juce_gui_extra/native/juce_win32_ActiveXComponent.cpp +++ b/modules/juce_gui_extra/native/juce_win32_ActiveXComponent.cpp @@ -147,6 +147,12 @@ namespace ActiveXHelpers ~JuceIOleClientSite() { inplaceSite->Release(); + + if (dispatchEventHandler != nullptr) + { + dispatchEventHandler->Release(); + dispatchEventHandler = nullptr; + } } JUCE_COMRESULT QueryInterface (REFIID type, void** result) @@ -159,6 +165,12 @@ namespace ActiveXHelpers *result = static_cast (inplaceSite); return S_OK; } + else if (type == __uuidof(IDispatch) && dispatchEventHandler != nullptr) + { + dispatchEventHandler->AddRef(); + *result = dispatchEventHandler; + return S_OK; + } return ComBaseClassHelper ::QueryInterface (type, result); @@ -180,7 +192,31 @@ namespace ActiveXHelpers return S_FALSE; } + void setEventHandler (void* eventHandler) + { + IDispatch* newEventHandler = nullptr; + + if (eventHandler != nullptr) + { + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token") + + auto iidIDispatch = __uuidof (IDispatch); + + if (static_cast(eventHandler)->QueryInterface (iidIDispatch, (void**) &newEventHandler) != S_OK + || newEventHandler == nullptr) + return; + + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + } + + if (dispatchEventHandler != nullptr) + dispatchEventHandler->Release(); + + dispatchEventHandler = newEventHandler; + } + JuceIOleInPlaceSite* inplaceSite; + IDispatch* dispatchEventHandler = nullptr; }; //============================================================================== @@ -490,6 +526,12 @@ intptr_t ActiveXControlComponent::offerEventToActiveXControlStatic (void* ptr) return S_FALSE; } +void ActiveXControlComponent::setEventHandler (void* eventHandler) +{ + if (control->clientSite != nullptr) + control->clientSite->setEventHandler (eventHandler); +} + LRESULT juce_offerEventToActiveXControl (::MSG& msg); LRESULT juce_offerEventToActiveXControl (::MSG& msg) { diff --git a/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp b/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp index 9d3f7a6e59..7e4c168f4e 100644 --- a/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp +++ b/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp @@ -52,7 +52,9 @@ class Win32WebView : public InternalWebViewType, public ActiveXControlComponent { public: - Win32WebView (WebBrowserComponent& owner) + Win32WebView (WebBrowserComponent& parent, const String& userAgentToUse) + : owner (parent), + userAgent (userAgentToUse) { owner.addAndMakeVisible (this); } @@ -78,6 +80,7 @@ public: auto iidWebBrowser2 = __uuidof (IWebBrowser2); auto iidConnectionPointContainer = __uuidof (IConnectionPointContainer); + auto iidOleControl = __uuidof (IOleControl); browser = (IWebBrowser2*) queryInterface (&iidWebBrowser2); @@ -87,17 +90,21 @@ public: if (connectionPoint != nullptr) { - if (auto* owner = dynamic_cast (Component::getParentComponent())) - { - auto handler = new EventHandler (*owner); - connectionPoint->Advise (handler, &adviseCookie); - handler->Release(); - } + auto handler = new EventHandler (*this); + connectionPoint->Advise (handler, &adviseCookie); + setEventHandler (handler); + handler->Release(); } connectionPointContainer->Release(); } + if (auto oleControl = (IOleControl*) queryInterface (&iidOleControl)) + { + oleControl->OnAmbientPropertyChange (/*DISPID_AMBIENT_USERAGENT*/-5513); + oleControl->Release(); + } + JUCE_END_IGNORE_WARNINGS_GCC_LIKE } @@ -106,7 +113,7 @@ public: return browser != nullptr; } - void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) override + void goToURL (const String& url, const StringArray* requestedHeaders, const MemoryBlock* postData) override { if (browser != nullptr) { @@ -116,10 +123,18 @@ public: VariantInit (&postDataVar); VariantInit (&headersVar); - if (headers != nullptr) + StringArray headers; + + if (userAgent.isNotEmpty()) + headers.add("User-Agent: " + userAgent); + + if (requestedHeaders != nullptr) + headers.addArray (*requestedHeaders); + + if (headers.size() > 0) { V_VT (&headersVar) = VT_BSTR; - V_BSTR (&headersVar) = SysAllocString ((const OLECHAR*) headers->joinIntoString ("\r\n").toWideCharPointer()); + V_BSTR (&headersVar) = SysAllocString ((const OLECHAR*) headers.joinIntoString ("\r\n").toWideCharPointer()); } if (postData != nullptr && postData->getSize() > 0) @@ -225,41 +240,51 @@ public: } private: + WebBrowserComponent& owner; IWebBrowser2* browser = nullptr; IConnectionPoint* connectionPoint = nullptr; DWORD adviseCookie = 0; + String userAgent; //============================================================================== struct EventHandler : public ComBaseClassHelper, public ComponentMovementWatcher { - EventHandler (WebBrowserComponent& w) : ComponentMovementWatcher (&w), owner (w) {} + EventHandler (Win32WebView& w) : ComponentMovementWatcher (&w.owner), owner (w) {} JUCE_COMRESULT GetTypeInfoCount (UINT*) override { return E_NOTIMPL; } JUCE_COMRESULT GetTypeInfo (UINT, LCID, ITypeInfo**) override { return E_NOTIMPL; } JUCE_COMRESULT GetIDsOfNames (REFIID, LPOLESTR*, UINT, LCID, DISPID*) override { return E_NOTIMPL; } JUCE_COMRESULT Invoke (DISPID dispIdMember, REFIID /*riid*/, LCID /*lcid*/, WORD /*wFlags*/, DISPPARAMS* pDispParams, - VARIANT* /*pVarResult*/, EXCEPINFO* /*pExcepInfo*/, UINT* /*puArgErr*/) override + VARIANT* pVarResult, EXCEPINFO* /*pExcepInfo*/, UINT* /*puArgErr*/) override { + + if (dispIdMember == /*DISPID_AMBIENT_USERAGENT*/-5513) + { + V_VT( pVarResult ) = VT_BSTR; + V_BSTR( pVarResult ) = SysAllocString ((const OLECHAR*) String(owner.userAgent).toWideCharPointer());; + return S_OK; + } + if (dispIdMember == DISPID_BEFORENAVIGATE2) { *pDispParams->rgvarg->pboolVal - = owner.pageAboutToLoad (getStringFromVariant (pDispParams->rgvarg[5].pvarVal)) ? VARIANT_FALSE + = owner.owner.pageAboutToLoad (getStringFromVariant (pDispParams->rgvarg[5].pvarVal)) ? VARIANT_FALSE : VARIANT_TRUE; return S_OK; } if (dispIdMember == 273 /*DISPID_NEWWINDOW3*/) { - owner.newWindowAttemptingToLoad (pDispParams->rgvarg[0].bstrVal); + owner.owner.newWindowAttemptingToLoad (pDispParams->rgvarg[0].bstrVal); *pDispParams->rgvarg[3].pboolVal = VARIANT_TRUE; return S_OK; } if (dispIdMember == DISPID_DOCUMENTCOMPLETE) { - owner.pageFinishedLoading (getStringFromVariant (pDispParams->rgvarg[0].pvarVal)); + owner.owner.pageFinishedLoading (getStringFromVariant (pDispParams->rgvarg[0].pvarVal)); return S_OK; } @@ -280,7 +305,7 @@ private: String message (messageBuffer, size); LocalFree (messageBuffer); - if (! owner.pageLoadHadNetworkError (message)) + if (! owner.owner.pageLoadHadNetworkError (message)) *pDispParams->rgvarg[0].pboolVal = VARIANT_TRUE; } @@ -289,7 +314,7 @@ private: if (dispIdMember == 263 /*DISPID_WINDOWCLOSING*/) { - owner.windowCloseRequest(); + owner.owner.windowCloseRequest(); // setting this bool tells the browser to ignore the event - we'll handle it. if (pDispParams->cArgs > 0 && pDispParams->rgvarg[0].vt == (VT_BYREF | VT_BOOL)) @@ -309,7 +334,7 @@ private: using ComponentMovementWatcher::componentMovedOrResized; private: - WebBrowserComponent& owner; + Win32WebView& owner; static String getStringFromVariant (VARIANT* v) { @@ -333,12 +358,15 @@ class WebView2 : public InternalWebViewType, public ComponentMovementWatcher { public: - WebView2 (WebBrowserComponent& o, const WebView2Preferences& prefs) + WebView2 (WebBrowserComponent& o, const WebBrowserComponent::Options& prefs) : ComponentMovementWatcher (&o), owner (o), - preferences (prefs) + preferences (prefs.getWinWebView2BackendOptions()), + userAgent (prefs.getUserAgent()) { - if (! createWebViewEnvironment()) + if (auto handle = createWebViewHandle (preferences)) + webViewHandle = std::move (*handle); + else throw std::runtime_error ("Failed to create the CoreWebView2Environemnt"); owner.addAndMakeVisible (this); @@ -348,16 +376,13 @@ public: { removeEventHandlers(); closeWebView(); - - if (webView2LoaderHandle != nullptr) - ::FreeLibrary (webView2LoaderHandle); } void createBrowser() override { if (webView == nullptr) { - jassert (webViewEnvironment != nullptr); + jassert (webViewHandle.environment != nullptr); createWebView(); } } @@ -437,6 +462,56 @@ public: owner.visibilityChanged(); } + //============================================================================== + struct WebViewHandle + { + using LibraryRef = std::unique_ptr::element_type, decltype(&::FreeLibrary)>; + LibraryRef loaderHandle {nullptr, &::FreeLibrary}; + ComSmartPtr environment; + }; + + static std::optional createWebViewHandle(const WebBrowserComponent::Options::WinWebView2& options) + { + using CreateWebViewEnvironmentWithOptionsFunc = HRESULT (*) (PCWSTR, PCWSTR, + ICoreWebView2EnvironmentOptions*, + ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler*); + + auto dllPath = options.getDLLLocation().getFullPathName(); + + if (dllPath.isEmpty()) + dllPath = "WebView2Loader.dll"; + + WebViewHandle result; + + result.loaderHandle = WebViewHandle::LibraryRef (LoadLibraryA (dllPath.toUTF8()), &::FreeLibrary); + + if (result.loaderHandle == nullptr) + return {}; + + auto* createWebViewEnvironmentWithOptions = (CreateWebViewEnvironmentWithOptionsFunc) GetProcAddress (result.loaderHandle.get(), + "CreateCoreWebView2EnvironmentWithOptions"); + if (createWebViewEnvironmentWithOptions == nullptr) + return {}; + + auto webViewOptions = Microsoft::WRL::Make(); + const auto userDataFolder = options.getUserDataFolder().getFullPathName(); + + auto hr = createWebViewEnvironmentWithOptions (nullptr, + userDataFolder.isNotEmpty() ? userDataFolder.toWideCharPointer() : nullptr, + webViewOptions.Get(), + Callback( + [&result] (HRESULT, ICoreWebView2Environment* env) -> HRESULT + { + result.environment = env; + return S_OK; + }).Get()); + + if (! SUCCEEDED (hr)) + return {}; + + return result; + } + private: //============================================================================== template @@ -618,52 +693,19 @@ private: { settings->put_IsStatusBarEnabled (! preferences.getIsStatusBarDisabled()); settings->put_IsBuiltInErrorPageEnabled (! preferences.getIsBuiltInErrorPageDisabled()); + + if (userAgent.isNotEmpty()) + { + ComSmartPtr settings2; + + settings.QueryInterface (settings2); + + if (settings2 != nullptr) + settings2->put_UserAgent (userAgent.toWideCharPointer()); + } } } - bool createWebViewEnvironment() - { - using CreateWebViewEnvironmentWithOptionsFunc = HRESULT (*) (PCWSTR, PCWSTR, - ICoreWebView2EnvironmentOptions*, - ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler*); - - auto dllPath = preferences.getDLLLocation().getFullPathName(); - - if (dllPath.isEmpty()) - dllPath = "WebView2Loader.dll"; - - webView2LoaderHandle = LoadLibraryA (dllPath.toUTF8()); - - if (webView2LoaderHandle == nullptr) - return false; - - auto* createWebViewEnvironmentWithOptions = (CreateWebViewEnvironmentWithOptionsFunc) GetProcAddress (webView2LoaderHandle, - "CreateCoreWebView2EnvironmentWithOptions"); - if (createWebViewEnvironmentWithOptions == nullptr) - { - // failed to load WebView2Loader.dll - jassertfalse; - return false; - } - - auto options = Microsoft::WRL::Make(); - const auto userDataFolder = preferences.getUserDataFolder().getFullPathName(); - - auto hr = createWebViewEnvironmentWithOptions (nullptr, - userDataFolder.isNotEmpty() ? userDataFolder.toWideCharPointer() : nullptr, - options.Get(), - Callback( - [weakThis = WeakReference { this }] (HRESULT, ICoreWebView2Environment* env) -> HRESULT - { - if (weakThis != nullptr) - weakThis->webViewEnvironment = env; - - return S_OK; - }).Get()); - - return SUCCEEDED (hr); - } - void createWebView() { if (auto* peer = getPeer()) @@ -672,7 +714,7 @@ private: WeakReference weakThis (this); - webViewEnvironment->CreateCoreWebView2Controller ((HWND) peer->getNativeHandle(), + webViewHandle.environment->CreateCoreWebView2Controller ((HWND) peer->getNativeHandle(), Callback ( [weakThis = WeakReference { this }] (HRESULT, ICoreWebView2Controller* controller) -> HRESULT { @@ -711,7 +753,7 @@ private: webView = nullptr; } - webViewEnvironment = nullptr; + webViewHandle.environment = nullptr; } //============================================================================== @@ -737,11 +779,10 @@ private: //============================================================================== WebBrowserComponent& owner; - WebView2Preferences preferences; + WebBrowserComponent::Options::WinWebView2 preferences; + String userAgent; - HMODULE webView2LoaderHandle = nullptr; - - ComSmartPtr webViewEnvironment; + WebViewHandle webViewHandle; ComSmartPtr webViewController; ComSmartPtr webView; @@ -774,8 +815,8 @@ class WebBrowserComponent::Pimpl { public: Pimpl (WebBrowserComponent& owner, - const WebView2Preferences& preferences, - bool useWebView2) + const Options& preferences, + bool useWebView2, const String& userAgent) { if (useWebView2) { @@ -791,7 +832,7 @@ public: ignoreUnused (preferences); if (internal == nullptr) - internal.reset (new Win32WebView (owner)); + internal.reset (new Win32WebView (owner, userAgent)); } InternalWebViewType& getInternalWebView() @@ -804,15 +845,10 @@ private: }; //============================================================================== -WebBrowserComponent::WebBrowserComponent (bool unloadWhenHidden) - : browser (new Pimpl (*this, {}, false)), - unloadPageWhenHidden (unloadWhenHidden) -{ - setOpaque (true); -} - -WebBrowserComponent::WebBrowserComponent (ConstructWithoutPimpl args) - : unloadPageWhenHidden (args.unloadWhenHidden) +WebBrowserComponent::WebBrowserComponent (const Options& options) + : browser (new Pimpl (*this, options, + options.getBackend() == Options::Backend::webview2, options.getUserAgent())), + unloadPageWhenHidden (! options.keepsPageLoadedWhenBrowserIsHidden()) { setOpaque (true); } @@ -821,13 +857,6 @@ WebBrowserComponent::~WebBrowserComponent() { } -WindowsWebView2WebBrowserComponent::WindowsWebView2WebBrowserComponent (bool unloadWhenHidden, - const WebView2Preferences& preferences) - : WebBrowserComponent (ConstructWithoutPimpl { unloadWhenHidden }) -{ - browser = std::make_unique (*this, preferences, true); -} - //============================================================================== void WebBrowserComponent::goToURL (const String& url, const StringArray* headers, @@ -982,4 +1011,21 @@ void WebBrowserComponent::clearCookies() } } +//============================================================================== +bool WebBrowserComponent::areOptionsSupported (const Options& options) +{ + if (options.getBackend() == Options::Backend::defaultBackend || options.getBackend() == Options::Backend::ie) + return true; + + #if JUCE_USE_WIN_WEBVIEW2 + if (options.getBackend() != Options::Backend::webview2) + return false; + + if (auto webView = WebView2::createWebViewHandle (options.getWinWebView2BackendOptions())) + return true; + #endif + + return false; +} + } // namespace juce