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