1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

WebBrowserComponent: Added a user agent option to change the browser's user agent

This commit is contained in:
Fabian Renn-Giles 2022-10-14 11:46:50 +00:00
parent 57b07997d3
commit 542312296f
9 changed files with 425 additions and 258 deletions

View file

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

View file

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

View file

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

View file

@ -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:
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
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<Pimpl> 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 <typename Member, typename Item>
WebView2Preferences with (Member&& member, Item&& item) const
{
auto options = *this;
options.*member = std::forward<Item> (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

View file

@ -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<jobject> (env->NewObject (JuceWebChromeClient, JuceWebChromeClient.constructor,
reinterpret_cast<jlong> (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;

View file

@ -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<WebKitPolicyDecision*> 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<const char*> 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<const char*> argv;
argv.reserve (static_cast<std::size_t> (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<CommandReceiver> 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();
}

View file

@ -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<WebView> 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<WKWebView> 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<WKWebViewImpl> (owner);
webView = std::make_unique<WKWebViewImpl> (owner, userAgent);
#if JUCE_MAC
else
webView = std::make_unique<WebViewImpl> (owner);
webView = std::make_unique<WebViewImpl> (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

View file

@ -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<IOleInPlaceSite*> (inplaceSite);
return S_OK;
}
else if (type == __uuidof(IDispatch) && dispatchEventHandler != nullptr)
{
dispatchEventHandler->AddRef();
*result = dispatchEventHandler;
return S_OK;
}
return ComBaseClassHelper <IOleClientSite>::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<IUnknown*>(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)
{

View file

@ -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<WebBrowserComponent*> (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<IDispatch>,
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<typename std::pointer_traits<HMODULE>::element_type, decltype(&::FreeLibrary)>;
LibraryRef loaderHandle {nullptr, &::FreeLibrary};
ComSmartPtr<ICoreWebView2Environment> environment;
};
static std::optional<WebViewHandle> 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<CoreWebView2EnvironmentOptions>();
const auto userDataFolder = options.getUserDataFolder().getFullPathName();
auto hr = createWebViewEnvironmentWithOptions (nullptr,
userDataFolder.isNotEmpty() ? userDataFolder.toWideCharPointer() : nullptr,
webViewOptions.Get(),
Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
[&result] (HRESULT, ICoreWebView2Environment* env) -> HRESULT
{
result.environment = env;
return S_OK;
}).Get());
if (! SUCCEEDED (hr))
return {};
return result;
}
private:
//==============================================================================
template <class ArgType>
@ -618,52 +693,19 @@ private:
{
settings->put_IsStatusBarEnabled (! preferences.getIsStatusBarDisabled());
settings->put_IsBuiltInErrorPageEnabled (! preferences.getIsBuiltInErrorPageDisabled());
if (userAgent.isNotEmpty())
{
ComSmartPtr<ICoreWebView2Settings2> 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<CoreWebView2EnvironmentOptions>();
const auto userDataFolder = preferences.getUserDataFolder().getFullPathName();
auto hr = createWebViewEnvironmentWithOptions (nullptr,
userDataFolder.isNotEmpty() ? userDataFolder.toWideCharPointer() : nullptr,
options.Get(),
Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
[weakThis = WeakReference<WebView2> { 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<WebView2> weakThis (this);
webViewEnvironment->CreateCoreWebView2Controller ((HWND) peer->getNativeHandle(),
webViewHandle.environment->CreateCoreWebView2Controller ((HWND) peer->getNativeHandle(),
Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler> (
[weakThis = WeakReference<WebView2> { 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<ICoreWebView2Environment> webViewEnvironment;
WebViewHandle webViewHandle;
ComSmartPtr<ICoreWebView2Controller> webViewController;
ComSmartPtr<ICoreWebView2> 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<Pimpl> (*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