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

1030 lines
35 KiB
C++

/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
struct InternalWebViewType
{
InternalWebViewType() = default;
virtual ~InternalWebViewType() = default;
virtual void createBrowser() = 0;
virtual bool hasBrowserBeenCreated() = 0;
virtual void goToURL (const String&, const StringArray*, const MemoryBlock*) = 0;
virtual void stop() = 0;
virtual void goBack() = 0;
virtual void goForward() = 0;
virtual void refresh() = 0;
virtual void focusGained() {}
virtual void setWebViewSize (int, int) = 0;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InternalWebViewType)
};
//==============================================================================
class Win32WebView : public InternalWebViewType,
public ActiveXControlComponent
{
public:
Win32WebView (WebBrowserComponent& parent, const String& userAgentToUse)
: owner (parent),
userAgent (userAgentToUse)
{
owner.addAndMakeVisible (this);
}
~Win32WebView() override
{
if (connectionPoint != nullptr)
{
connectionPoint->Unadvise (adviseCookie);
connectionPoint->Release();
}
if (browser != nullptr)
browser->Release();
}
void createBrowser() override
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
auto webCLSID = __uuidof (WebBrowser);
createControl (&webCLSID);
auto iidWebBrowser2 = __uuidof (IWebBrowser2);
auto iidConnectionPointContainer = __uuidof (IConnectionPointContainer);
auto iidOleControl = __uuidof (IOleControl);
browser = (IWebBrowser2*) queryInterface (&iidWebBrowser2);
if (auto connectionPointContainer = (IConnectionPointContainer*) queryInterface (&iidConnectionPointContainer))
{
connectionPointContainer->FindConnectionPoint (__uuidof (DWebBrowserEvents2), &connectionPoint);
if (connectionPoint != nullptr)
{
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
}
bool hasBrowserBeenCreated() override
{
return browser != nullptr;
}
void goToURL (const String& url, const StringArray* requestedHeaders, const MemoryBlock* postData) override
{
if (browser != nullptr)
{
VARIANT headerFlags, frame, postDataVar, headersVar; // (_variant_t isn't available in all compilers)
VariantInit (&headerFlags);
VariantInit (&frame);
VariantInit (&postDataVar);
VariantInit (&headersVar);
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());
}
if (postData != nullptr && postData->getSize() > 0)
{
auto sa = SafeArrayCreateVector (VT_UI1, 0, (ULONG) postData->getSize());
if (sa != nullptr)
{
void* data = nullptr;
SafeArrayAccessData (sa, &data);
jassert (data != nullptr);
if (data != nullptr)
{
postData->copyTo (data, 0, postData->getSize());
SafeArrayUnaccessData (sa);
VARIANT postDataVar2;
VariantInit (&postDataVar2);
V_VT (&postDataVar2) = VT_ARRAY | VT_UI1;
V_ARRAY (&postDataVar2) = sa;
sa = nullptr;
postDataVar = postDataVar2;
}
else
{
SafeArrayDestroy (sa);
}
}
}
auto urlBSTR = SysAllocString ((const OLECHAR*) url.toWideCharPointer());
browser->Navigate (urlBSTR, &headerFlags, &frame, &postDataVar, &headersVar);
SysFreeString (urlBSTR);
VariantClear (&headerFlags);
VariantClear (&frame);
VariantClear (&postDataVar);
VariantClear (&headersVar);
}
}
void stop() override
{
if (browser != nullptr)
browser->Stop();
}
void goBack() override
{
if (browser != nullptr)
browser->GoBack();
}
void goForward() override
{
if (browser != nullptr)
browser->GoForward();
}
void refresh() override
{
if (browser != nullptr)
browser->Refresh();
}
void focusGained() override
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
auto iidOleObject = __uuidof (IOleObject);
auto iidOleWindow = __uuidof (IOleWindow);
if (auto oleObject = (IOleObject*) queryInterface (&iidOleObject))
{
if (auto oleWindow = (IOleWindow*) queryInterface (&iidOleWindow))
{
IOleClientSite* oleClientSite = nullptr;
if (SUCCEEDED (oleObject->GetClientSite (&oleClientSite)))
{
HWND hwnd;
oleWindow->GetWindow (&hwnd);
oleObject->DoVerb (OLEIVERB_UIACTIVATE, nullptr, oleClientSite, 0, hwnd, nullptr);
oleClientSite->Release();
}
oleWindow->Release();
}
oleObject->Release();
}
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
using ActiveXControlComponent::focusGained;
void setWebViewSize (int width, int height) override
{
setSize (width, height);
}
private:
WebBrowserComponent& owner;
IWebBrowser2* browser = nullptr;
IConnectionPoint* connectionPoint = nullptr;
DWORD adviseCookie = 0;
String userAgent;
//==============================================================================
struct EventHandler : public ComBaseClassHelper<IDispatch>,
public ComponentMovementWatcher
{
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
{
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.owner.pageAboutToLoad (getStringFromVariant (pDispParams->rgvarg[5].pvarVal)) ? VARIANT_FALSE
: VARIANT_TRUE;
return S_OK;
}
if (dispIdMember == 273 /*DISPID_NEWWINDOW3*/)
{
owner.owner.newWindowAttemptingToLoad (pDispParams->rgvarg[0].bstrVal);
*pDispParams->rgvarg[3].pboolVal = VARIANT_TRUE;
return S_OK;
}
if (dispIdMember == DISPID_DOCUMENTCOMPLETE)
{
owner.owner.pageFinishedLoading (getStringFromVariant (pDispParams->rgvarg[0].pvarVal));
return S_OK;
}
if (dispIdMember == 271 /*DISPID_NAVIGATEERROR*/)
{
int statusCode = pDispParams->rgvarg[1].pvarVal->intVal;
*pDispParams->rgvarg[0].pboolVal = VARIANT_FALSE;
// IWebBrowser2 also reports http status codes here, we need
// report only network errors
if (statusCode < 0)
{
LPTSTR messageBuffer = nullptr;
auto size = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, (DWORD) statusCode, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &messageBuffer, 0, nullptr);
String message (messageBuffer, size);
LocalFree (messageBuffer);
if (! owner.owner.pageLoadHadNetworkError (message))
*pDispParams->rgvarg[0].pboolVal = VARIANT_TRUE;
}
return S_OK;
}
if (dispIdMember == 263 /*DISPID_WINDOWCLOSING*/)
{
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))
*pDispParams->rgvarg[0].pboolVal = VARIANT_TRUE;
return S_OK;
}
return E_NOTIMPL;
}
void componentMovedOrResized (bool, bool) override {}
void componentPeerChanged() override {}
void componentVisibilityChanged() override { owner.visibilityChanged(); }
using ComponentMovementWatcher::componentVisibilityChanged;
using ComponentMovementWatcher::componentMovedOrResized;
private:
Win32WebView& owner;
static String getStringFromVariant (VARIANT* v)
{
return (v->vt & VT_BYREF) != 0 ? *v->pbstrVal
: v->bstrVal;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EventHandler)
};
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32WebView)
};
#if JUCE_USE_WIN_WEBVIEW2
using namespace Microsoft::WRL;
class WebView2 : public InternalWebViewType,
public Component,
public ComponentMovementWatcher
{
public:
WebView2 (WebBrowserComponent& o, const WebBrowserComponent::Options& prefs)
: ComponentMovementWatcher (&o),
owner (o),
preferences (prefs.getWinWebView2BackendOptions()),
userAgent (prefs.getUserAgent())
{
if (auto handle = createWebViewHandle (preferences))
webViewHandle = std::move (*handle);
else
throw std::runtime_error ("Failed to create the CoreWebView2Environment");
owner.addAndMakeVisible (this);
}
~WebView2() override
{
removeEventHandlers();
closeWebView();
}
void createBrowser() override
{
if (webView == nullptr)
{
jassert (webViewHandle.environment != nullptr);
createWebView();
}
}
bool hasBrowserBeenCreated() override
{
return webView != nullptr || isCreating;
}
void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) override
{
urlRequest = { url,
headers != nullptr ? *headers : StringArray(),
postData != nullptr && postData->getSize() > 0 ? *postData : MemoryBlock() };
if (webView != nullptr)
webView->Navigate (urlRequest.url.toWideCharPointer());
}
void stop() override
{
if (webView != nullptr)
webView->Stop();
}
void goBack() override
{
if (webView != nullptr)
{
BOOL canGoBack = false;
webView->get_CanGoBack (&canGoBack);
if (canGoBack)
webView->GoBack();
}
}
void goForward() override
{
if (webView != nullptr)
{
BOOL canGoForward = false;
webView->get_CanGoForward (&canGoForward);
if (canGoForward)
webView->GoForward();
}
}
void refresh() override
{
if (webView != nullptr)
webView->Reload();
}
void setWebViewSize (int width, int height) override
{
setSize (width, height);
}
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
{
if (auto* peer = owner.getTopLevelComponent()->getPeer())
setControlBounds (peer->getAreaCoveredBy (owner));
}
void componentPeerChanged() override
{
componentMovedOrResized (true, true);
}
void componentVisibilityChanged() override
{
setControlVisible (owner.isShowing());
componentPeerChanged();
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>
static String getUriStringFromArgs (ArgType* args)
{
if (args != nullptr)
{
LPWSTR uri;
args->get_Uri (&uri);
return uri;
}
return {};
}
//==============================================================================
void addEventHandlers()
{
if (webView != nullptr)
{
webView->add_NavigationStarting (Callback<ICoreWebView2NavigationStartingEventHandler> (
[this] (ICoreWebView2*, ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT
{
auto uriString = getUriStringFromArgs (args);
if (uriString.isNotEmpty() && ! owner.pageAboutToLoad (uriString))
args->put_Cancel (true);
return S_OK;
}).Get(), &navigationStartingToken);
webView->add_NewWindowRequested (Callback<ICoreWebView2NewWindowRequestedEventHandler> (
[this] (ICoreWebView2*, ICoreWebView2NewWindowRequestedEventArgs* args) -> HRESULT
{
auto uriString = getUriStringFromArgs (args);
if (uriString.isNotEmpty())
{
owner.newWindowAttemptingToLoad (uriString);
args->put_Handled (true);
}
return S_OK;
}).Get(), &newWindowRequestedToken);
webView->add_WindowCloseRequested (Callback<ICoreWebView2WindowCloseRequestedEventHandler> (
[this] (ICoreWebView2*, IUnknown*) -> HRESULT
{
owner.windowCloseRequest();
return S_OK;
}).Get(), &windowCloseRequestedToken);
webView->add_NavigationCompleted (Callback<ICoreWebView2NavigationCompletedEventHandler> (
[this] (ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args) -> HRESULT
{
LPWSTR uri;
sender->get_Source (&uri);
String uriString (uri);
if (uriString.isNotEmpty())
{
BOOL success = false;
args->get_IsSuccess (&success);
COREWEBVIEW2_WEB_ERROR_STATUS errorStatus;
args->get_WebErrorStatus (&errorStatus);
if (success
|| errorStatus == COREWEBVIEW2_WEB_ERROR_STATUS_OPERATION_CANCELED) // this error seems to happen erroneously so ignore
{
owner.pageFinishedLoading (uriString);
}
else
{
auto errorString = "Error code: " + String (errorStatus);
if (owner.pageLoadHadNetworkError (errorString))
owner.goToURL ("data:text/plain;charset=UTF-8," + errorString);
}
}
return S_OK;
}).Get(), &navigationCompletedToken);
webView->AddWebResourceRequestedFilter (L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT);
webView->add_WebResourceRequested (Callback<ICoreWebView2WebResourceRequestedEventHandler> (
[this] (ICoreWebView2*, ICoreWebView2WebResourceRequestedEventArgs* args) -> HRESULT
{
if (urlRequest.url.isEmpty())
return S_OK;
ComSmartPtr<ICoreWebView2WebResourceRequest> request;
args->get_Request (request.resetAndGetPointerAddress());
auto uriString = getUriStringFromArgs<ICoreWebView2WebResourceRequest> (request);
if (uriString == urlRequest.url
|| (uriString.endsWith ("/") && uriString.upToLastOccurrenceOf ("/", false, false) == urlRequest.url))
{
String method ("GET");
if (! urlRequest.postData.isEmpty())
{
method = "POST";
ComSmartPtr<IStream> content (SHCreateMemStream ((BYTE*) urlRequest.postData.getData(),
(UINT) urlRequest.postData.getSize()));
request->put_Content (content);
}
if (! urlRequest.headers.isEmpty())
{
ComSmartPtr<ICoreWebView2HttpRequestHeaders> headers;
request->get_Headers (headers.resetAndGetPointerAddress());
for (auto& header : urlRequest.headers)
{
headers->SetHeader (header.upToFirstOccurrenceOf (":", false, false).trim().toWideCharPointer(),
header.fromFirstOccurrenceOf (":", false, false).trim().toWideCharPointer());
}
}
request->put_Method (method.toWideCharPointer());
urlRequest = {};
}
return S_OK;
}).Get(), &webResourceRequestedToken);
}
}
void removeEventHandlers()
{
if (webView != nullptr)
{
if (navigationStartingToken.value != 0)
webView->remove_NavigationStarting (navigationStartingToken);
if (newWindowRequestedToken.value != 0)
webView->remove_NewWindowRequested (newWindowRequestedToken);
if (windowCloseRequestedToken.value != 0)
webView->remove_WindowCloseRequested (windowCloseRequestedToken);
if (navigationCompletedToken.value != 0)
webView->remove_NavigationCompleted (navigationCompletedToken);
if (webResourceRequestedToken.value != 0)
{
webView->RemoveWebResourceRequestedFilter (L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT);
webView->remove_WebResourceRequested (webResourceRequestedToken);
}
}
}
void setWebViewPreferences()
{
ComSmartPtr<ICoreWebView2Controller2> controller2;
webViewController->QueryInterface (controller2.resetAndGetPointerAddress());
if (controller2 != nullptr)
{
const auto bgColour = preferences.getBackgroundColour();
controller2->put_DefaultBackgroundColor ({ (BYTE) bgColour.getAlpha(),
(BYTE) bgColour.getRed(),
(BYTE) bgColour.getGreen(),
(BYTE) bgColour.getBlue() });
}
ComSmartPtr<ICoreWebView2Settings> settings;
webView->get_Settings (settings.resetAndGetPointerAddress());
if (settings != nullptr)
{
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());
}
}
}
void createWebView()
{
if (auto* peer = getPeer())
{
isCreating = true;
WeakReference<WebView2> weakThis (this);
webViewHandle.environment->CreateCoreWebView2Controller ((HWND) peer->getNativeHandle(),
Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler> (
[weakThis = WeakReference<WebView2> { this }] (HRESULT, ICoreWebView2Controller* controller) -> HRESULT
{
if (weakThis != nullptr)
{
weakThis->isCreating = false;
if (controller != nullptr)
{
weakThis->webViewController = controller;
controller->get_CoreWebView2 (weakThis->webView.resetAndGetPointerAddress());
if (weakThis->webView != nullptr)
{
weakThis->addEventHandlers();
weakThis->setWebViewPreferences();
weakThis->componentMovedOrResized (true, true);
if (weakThis->urlRequest.url.isNotEmpty())
weakThis->webView->Navigate (weakThis->urlRequest.url.toWideCharPointer());
}
}
}
return S_OK;
}).Get());
}
}
void closeWebView()
{
if (webViewController != nullptr)
{
webViewController->Close();
webViewController = nullptr;
webView = nullptr;
}
webViewHandle.environment = nullptr;
}
//==============================================================================
void setControlBounds (Rectangle<int> newBounds) const
{
if (webViewController != nullptr)
{
#if JUCE_WIN_PER_MONITOR_DPI_AWARE
if (auto* peer = owner.getTopLevelComponent()->getPeer())
newBounds = (newBounds.toDouble() * peer->getPlatformScaleFactor()).toNearestInt();
#endif
webViewController->put_Bounds({ newBounds.getX(), newBounds.getY(),
newBounds.getRight(), newBounds.getBottom() });
}
}
void setControlVisible (bool shouldBeVisible) const
{
if (webViewController != nullptr)
webViewController->put_IsVisible (shouldBeVisible);
}
//==============================================================================
WebBrowserComponent& owner;
WebBrowserComponent::Options::WinWebView2 preferences;
String userAgent;
WebViewHandle webViewHandle;
ComSmartPtr<ICoreWebView2Controller> webViewController;
ComSmartPtr<ICoreWebView2> webView;
EventRegistrationToken navigationStartingToken { 0 },
newWindowRequestedToken { 0 },
windowCloseRequestedToken { 0 },
navigationCompletedToken { 0 },
webResourceRequestedToken { 0 };
struct URLRequest
{
String url;
StringArray headers;
MemoryBlock postData;
};
URLRequest urlRequest;
bool isCreating = false;
//==============================================================================
JUCE_DECLARE_WEAK_REFERENCEABLE (WebView2)
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebView2)
};
#endif
//==============================================================================
class WebBrowserComponent::Pimpl
{
public:
Pimpl (WebBrowserComponent& owner,
[[maybe_unused]] const Options& preferences,
bool useWebView2,
const String& userAgent)
{
if (useWebView2)
{
#if JUCE_USE_WIN_WEBVIEW2
try
{
internal.reset (new WebView2 (owner, preferences));
}
catch (const std::runtime_error&) {}
#endif
}
if (internal == nullptr)
internal.reset (new Win32WebView (owner, userAgent));
}
InternalWebViewType& getInternalWebView()
{
return *internal;
}
private:
std::unique_ptr<InternalWebViewType> internal;
};
//==============================================================================
WebBrowserComponent::WebBrowserComponent (const Options& options)
: browser (new Pimpl (*this, options,
options.getBackend() == Options::Backend::webview2, options.getUserAgent())),
unloadPageWhenHidden (! options.keepsPageLoadedWhenBrowserIsHidden())
{
setOpaque (true);
}
WebBrowserComponent::~WebBrowserComponent()
{
}
//==============================================================================
void WebBrowserComponent::goToURL (const String& url,
const StringArray* headers,
const MemoryBlock* postData)
{
lastURL = url;
if (headers != nullptr)
lastHeaders = *headers;
else
lastHeaders.clear();
if (postData != nullptr)
lastPostData = *postData;
else
lastPostData.reset();
blankPageShown = false;
if (! browser->getInternalWebView().hasBrowserBeenCreated())
checkWindowAssociation();
browser->getInternalWebView().goToURL (url, headers, postData);
}
void WebBrowserComponent::stop()
{
browser->getInternalWebView().stop();
}
void WebBrowserComponent::goBack()
{
lastURL.clear();
blankPageShown = false;
browser->getInternalWebView().goBack();
}
void WebBrowserComponent::goForward()
{
lastURL.clear();
browser->getInternalWebView().goForward();
}
void WebBrowserComponent::refresh()
{
browser->getInternalWebView().refresh();
}
//==============================================================================
void WebBrowserComponent::paint (Graphics& g)
{
if (! browser->getInternalWebView().hasBrowserBeenCreated())
{
g.fillAll (Colours::white);
checkWindowAssociation();
}
}
void WebBrowserComponent::checkWindowAssociation()
{
if (isShowing())
{
if (! browser->getInternalWebView().hasBrowserBeenCreated() && getPeer() != nullptr)
{
browser->getInternalWebView().createBrowser();
reloadLastURL();
}
else
{
if (blankPageShown)
goBack();
}
}
else
{
if (browser != nullptr && unloadPageWhenHidden && ! blankPageShown)
{
// when the component becomes invisible, some stuff like flash
// carries on playing audio, so we need to force it onto a blank
// page to avoid this..
blankPageShown = true;
browser->getInternalWebView().goToURL ("about:blank", nullptr, nullptr);
}
}
}
void WebBrowserComponent::reloadLastURL()
{
if (lastURL.isNotEmpty())
{
goToURL (lastURL, &lastHeaders, &lastPostData);
lastURL.clear();
}
}
void WebBrowserComponent::parentHierarchyChanged()
{
checkWindowAssociation();
}
void WebBrowserComponent::resized()
{
browser->getInternalWebView().setWebViewSize (getWidth(), getHeight());
}
void WebBrowserComponent::visibilityChanged()
{
checkWindowAssociation();
}
void WebBrowserComponent::focusGained (FocusChangeType)
{
browser->getInternalWebView().focusGained();
}
void WebBrowserComponent::clearCookies()
{
HeapBlock<::INTERNET_CACHE_ENTRY_INFOA> entry;
::DWORD entrySize = sizeof (::INTERNET_CACHE_ENTRY_INFOA);
::HANDLE urlCacheHandle = ::FindFirstUrlCacheEntryA ("cookie:", entry.getData(), &entrySize);
if (urlCacheHandle == nullptr && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
entry.realloc (1, entrySize);
urlCacheHandle = ::FindFirstUrlCacheEntryA ("cookie:", entry.getData(), &entrySize);
}
if (urlCacheHandle != nullptr)
{
for (;;)
{
::DeleteUrlCacheEntryA (entry.getData()->lpszSourceUrlName);
if (::FindNextUrlCacheEntryA (urlCacheHandle, entry.getData(), &entrySize) == 0)
{
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
entry.realloc (1, entrySize);
if (::FindNextUrlCacheEntryA (urlCacheHandle, entry.getData(), &entrySize) != 0)
continue;
}
break;
}
}
FindCloseUrlCache (urlCacheHandle);
}
}
//==============================================================================
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