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

WebBrowserComponent: Improve native integrations

This commit is contained in:
attila 2024-04-08 17:39:34 +02:00 committed by Anthony Nicholls
parent 624fed2c7f
commit 5f638157f7
63 changed files with 5155 additions and 1288 deletions

View file

@ -115,13 +115,98 @@ static NSMutableURLRequest* getRequestForURL (const String& url, const StringArr
return nullptr;
}
static var fromObject (id object)
{
// An undefined var serialises to 'undefined' i.e. an expression not returning a value
if (object == nil)
return var::undefined();
if ([object isKindOfClass:[NSNumber class]])
{
// The object returned by evaluateJavaScript is a __NSCFBoolean*, which is a private class
// to the framework, but a handle to this class can be obtained through @YES. When cast to
// an NSNumber this object would have the wrong type encoding, so [number objCType]; would
// return 'c' instead of 'B', hence that approach wouldn't work.
if ([object isKindOfClass: [@YES class]])
return static_cast<NSNumber*> (object).boolValue == YES ? true : false;
return static_cast<NSNumber*> (object).doubleValue;
}
if ([object isKindOfClass:[NSString class]])
return nsStringToJuce (object);
if ([object isKindOfClass:[NSArray class]])
{
Array<var> result;
auto* array = static_cast<NSArray*> (object);
for (id elem in array)
result.add (fromObject (elem));
return result;
}
if ([object isKindOfClass:[NSDictionary class]])
{
const auto* dict = static_cast<NSDictionary*> (object);
DynamicObject::Ptr result (new DynamicObject());
for (id key in dict)
result->setProperty (nsStringToJuce (key), fromObject ([dict objectForKey:key]));
return result.get();
}
if ([object isKindOfClass:[NSDate class]])
{
JUCE_AUTORELEASEPOOL
{
auto* date = static_cast<NSDate*> (object);
auto* formatter = [[NSDateFormatter alloc] init];
const auto javascriptDateFormatString = @"yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSS'Z'";
[formatter setDateFormat: javascriptDateFormatString];
[formatter setTimeZone: [NSTimeZone timeZoneWithName: @"UTC"]];
NSString* dateString = [formatter stringFromDate: date];
return nsStringToJuce (dateString);
}
}
// Returning a Void var, which serialises to 'null'
if ([object isKindOfClass:[NSNull class]])
return {};
jassertfalse;
return {};
}
using LastFocusChange = std::optional<Component::FocusChangeDirection>;
static const char* lastFocusChangeMemberName = "lastFocusChangeHandle";
[[maybe_unused]] static void setLastFocusChangeHandle (id instance, LastFocusChange* object)
{
object_setInstanceVariable (instance, lastFocusChangeMemberName, object);
}
[[maybe_unused]] static LastFocusChange* getLastFocusChangeHandle (id instance)
{
return getIvar<LastFocusChange*> (instance, lastFocusChangeMemberName);
}
#if JUCE_MAC
template <class WebViewClass>
struct WebViewKeyEquivalentResponder final : public ObjCClass<WebViewClass>
{
using Base = ObjCClass<WebViewClass>;
WebViewKeyEquivalentResponder()
: ObjCClass<WebViewClass> ("WebViewKeyEquivalentResponder_")
: Base ("WebViewKeyEquivalentResponder_")
{
this->template addIvar<LastFocusChange*> (lastFocusChangeMemberName);
this->addMethod (@selector (performKeyEquivalent:),
[] (id self, SEL selector, NSEvent* event)
{
@ -156,8 +241,48 @@ struct WebViewKeyEquivalentResponder final : public ObjCClass<WebViewClass>
return sendAction (@selector (selectAll:));
}
return ObjCClass<WebViewClass>::template sendSuperclassMessage<BOOL> (self, selector, event);
return Base::template sendSuperclassMessage<BOOL> (self, selector, event);
});
this->addMethod (@selector (resignFirstResponder),
[] (id self, SEL selector)
{
const auto result = Base::template sendSuperclassMessage<BOOL> (self, selector);
auto* focusChangeTypeHandle = getLastFocusChangeHandle (self);
jassert (focusChangeTypeHandle != nullptr); // Forgot to call setLastFocusChangeHandle?
focusChangeTypeHandle->emplace (Component::FocusChangeDirection::unknown);
auto* currentEvent = [NSApp currentEvent];
if (currentEvent == nullptr)
return result;
const auto eventType = [currentEvent type];
if ( eventType != NSEventTypeKeyUp
&& eventType != NSEventTypeKeyDown
&& eventType != NSEventTypeFlagsChanged)
{
return result;
}
// Device independent key numbers should be compared with Carbon
// constants, but we define the one we need here to avoid importing
// Carbon.h
static constexpr unsigned short carbonTabKeycode = 0x30;
if ([currentEvent keyCode] != carbonTabKeycode)
return result;
const auto shiftKeyDown = ([currentEvent modifierFlags] & NSEventModifierFlagShift) != 0;
focusChangeTypeHandle->emplace (shiftKeyDown ? Component::FocusChangeDirection::backward
: Component::FocusChangeDirection::forward);
return result;
});
this->registerClass();
}
};
@ -267,16 +392,56 @@ private:
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
#endif
// Connects the delegate to the rest of the implementation without making WebViewDelegateClass
// a nested class as well.
class DelegateConnector
{
public:
DelegateConnector (WebBrowserComponent& browserIn,
std::function<void (const var&)> handleNativeEventFnIn,
std::function<std::optional<WebBrowserComponent::Resource> (const String&)> handleResourceRequestFnIn,
const WebBrowserComponent::Options& optionsIn)
: browser (browserIn),
handleNativeEventFn (std::move (handleNativeEventFnIn)),
handleResourceRequestFn (std::move (handleResourceRequestFnIn)),
options (optionsIn)
{
}
auto& getBrowser() { return browser; }
void handleNativeEvent (const var& message)
{
handleNativeEventFn (message);
}
auto handleResourceRequest (const String& url)
{
return handleResourceRequestFn (url);
}
[[nodiscard]] const auto& getOptions() const
{
return options;
}
private:
WebBrowserComponent& browser;
std::function<void (const var&)> handleNativeEventFn;
std::function<std::optional<WebBrowserComponent::Resource> (const String&)> handleResourceRequestFn;
WebBrowserComponent::Options options;
};
struct API_AVAILABLE (macos (10.10)) WebViewDelegateClass final : public ObjCClass<NSObject>
{
WebViewDelegateClass() : ObjCClass ("JUCEWebViewDelegate_")
{
addIvar<WebBrowserComponent*> ("owner");
addIvar<DelegateConnector*> ("connector");
addMethod (@selector (webView:decidePolicyForNavigationAction:decisionHandler:),
[] (id self, SEL, WKWebView*, WKNavigationAction* navigationAction, void (^decisionHandler) (WKNavigationActionPolicy))
{
if (getOwner (self)->pageAboutToLoad (nsStringToJuce ([[[navigationAction request] URL] absoluteString])))
if (getConnector (self)->getBrowser().pageAboutToLoad (nsStringToJuce ([[[navigationAction request] URL] absoluteString])))
decisionHandler (WKNavigationActionPolicyAllow);
else
decisionHandler (WKNavigationActionPolicyCancel);
@ -285,34 +450,112 @@ struct API_AVAILABLE (macos (10.10)) WebViewDelegateClass final : public ObjCCla
addMethod (@selector (webView:didFinishNavigation:),
[] (id self, SEL, WKWebView* webview, WKNavigation*)
{
getOwner (self)->pageFinishedLoading (nsStringToJuce ([[webview URL] absoluteString]));
getConnector (self)->getBrowser().pageFinishedLoading (nsStringToJuce ([[webview URL] absoluteString]));
});
addMethod (@selector (webView:didFailNavigation:withError:),
[] (id self, SEL, WKWebView*, WKNavigation*, NSError* error)
{
displayError (getOwner (self), error);
displayError (&getConnector (self)->getBrowser(), error);
});
addMethod (@selector (webView:didFailProvisionalNavigation:withError:),
[] (id self, SEL, WKWebView*, WKNavigation*, NSError* error)
{
displayError (getOwner (self), error);
displayError (&getConnector (self)->getBrowser(), error);
});
addMethod (@selector (webViewDidClose:),
[] (id self, SEL, WKWebView*)
{
getOwner (self)->windowCloseRequest();
getConnector (self)->getBrowser().windowCloseRequest();
});
addMethod (@selector (webView:createWebViewWithConfiguration:forNavigationAction:windowFeatures:),
[] (id self, SEL, WKWebView*, WKWebViewConfiguration*, WKNavigationAction* navigationAction, WKWindowFeatures*)
{
getOwner (self)->newWindowAttemptingToLoad (nsStringToJuce ([[[navigationAction request] URL] absoluteString]));
getConnector (self)->getBrowser().newWindowAttemptingToLoad (nsStringToJuce ([[[navigationAction request] URL] absoluteString]));
return nil;
});
addMethod (@selector (userContentController:didReceiveScriptMessage:),
[] (id self, SEL, id, id message)
{
const auto object = fromObject ([message body]);
if (! object.isString())
{
jassertfalse;
return;
}
getConnector (self)->handleNativeEvent (JSON::fromString (object.toString()));
});
addMethod (@selector (webView:startURLSchemeTask:),
[] (id self, SEL, id, id urlSchemeTask)
{
const auto request = [urlSchemeTask request];
auto* url = [&]
{
auto r = [request URL];
return r == nil ? [NSURL URLWithString:@""] : (NSURL* _Nonnull) r;
}();
const auto path = nsStringToJuce ([url path]);
const auto resource = getConnector (self)->handleResourceRequest (path);
JUCE_AUTORELEASEPOOL
{
const auto makeResponse = [&url] (auto responseCode, id headers=nil)
{
auto response = [[NSHTTPURLResponse alloc] initWithURL:url
statusCode:responseCode
HTTPVersion:@"HTTP/1.1"
headerFields:headers];
if (response == nil)
return [[NSHTTPURLResponse alloc] autorelease];
return (NSHTTPURLResponse* _Nonnull) [response autorelease];
};
if (resource.has_value())
{
NSMutableDictionary* headers = [@ {
@"Content-Length" : juceStringToNS (String { resource->data.size() }),
@"Content-Type" : juceStringToNS (resource->mimeType),
} mutableCopy];
if (auto allowedOrigin = getConnector (self)->getOptions().getAllowedOrigin())
{
[headers setObject:juceStringToNS (*allowedOrigin)
forKey:@"Access-Control-Allow-Origin"];
}
auto response = makeResponse (200, headers);
[urlSchemeTask didReceiveResponse:response];
[urlSchemeTask didReceiveData:[NSData dataWithBytes:resource->data.data()
length:resource->data.size()]];
}
else
{
[urlSchemeTask didReceiveResponse:makeResponse (404)];
}
[urlSchemeTask didFinish];
}
});
addMethod (@selector (webView:stopURLSchemeTask:),
[] (id, SEL, id, id)
{
});
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
if (@available (macOS 10.12, *))
{
@ -384,8 +627,15 @@ struct API_AVAILABLE (macos (10.10)) WebViewDelegateClass final : public ObjCCla
registerClass();
}
static void setOwner (id self, WebBrowserComponent* owner) { object_setInstanceVariable (self, "owner", owner); }
static WebBrowserComponent* getOwner (id self) { return getIvar<WebBrowserComponent*> (self, "owner"); }
static void setConnector (id self, DelegateConnector* connector)
{
object_setInstanceVariable (self, "connector", connector);
}
static DelegateConnector* getConnector (id self)
{
return getIvar<DelegateConnector*> (self, "connector");
}
private:
static void displayError (WebBrowserComponent* owner, NSError* error)
@ -403,25 +653,32 @@ private:
};
//==============================================================================
struct WebViewBase
struct WebBrowserComponent::Impl::Platform
{
virtual ~WebViewBase() = default;
virtual void goToURL (const String&, const StringArray*, const MemoryBlock*) = 0;
virtual void goBack() = 0;
virtual void goForward() = 0;
virtual void stop() = 0;
virtual void refresh() = 0;
virtual id getWebView() = 0;
class WKWebViewImpl;
class WebViewImpl;
};
static constexpr const char* platformSpecificIntegrationScript = R"(
window.__JUCE__ = {
postMessage: function (object) {
window.webkit.messageHandlers.__JUCE__.postMessage(object);
},
};
)";
//==============================================================================
#if JUCE_MAC
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
class WebViewImpl : public WebViewBase
class WebBrowserComponent::Impl::Platform::WebViewImpl : public WebBrowserComponent::Impl::PlatformInterface,
#if JUCE_MAC
public NSViewComponent
#else
public UIViewComponent
#endif
{
public:
WebViewImpl (WebBrowserComponent* owner, const String& userAgent)
WebViewImpl (WebBrowserComponent::Impl& implIn, const String& userAgent) : browser (implIn.owner)
{
static WebViewKeyEquivalentResponder<WebView> webviewClass;
@ -429,24 +686,59 @@ public:
frameName: nsEmptyString()
groupName: nsEmptyString()]);
setLastFocusChangeHandle (webView.get(), &lastFocusChange);
webView.get().customUserAgent = juceStringToNS (userAgent);
static DownloadClickDetectorClass cls;
clickListener.reset ([cls.createInstance() init]);
DownloadClickDetectorClass::setOwner (clickListener.get(), owner);
DownloadClickDetectorClass::setOwner (clickListener.get(), &browser);
[webView.get() setPolicyDelegate: clickListener.get()];
[webView.get() setFrameLoadDelegate: clickListener.get()];
[webView.get() setUIDelegate: clickListener.get()];
setView (webView.get());
browser.addAndMakeVisible (this);
}
~WebViewImpl() override
{
setView (nil);
[webView.get() setPolicyDelegate: nil];
[webView.get() setFrameLoadDelegate: nil];
[webView.get() setUIDelegate: nil];
}
void setWebViewSize (int width, int height) override
{
setSize (width, height);
}
void checkWindowAssociation() override
{
if (browser.isShowing())
{
browser.reloadLastURL();
if (browser.blankPageShown)
browser.goBack();
}
else
{
if (browser.unloadPageWhenHidden && ! browser.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, (and send it back when it's made visible again).
browser.blankPageShown = true;
goToURL ("about:blank", nullptr, nullptr);
}
}
}
void goToURL (const String& url,
const StringArray* headers,
const MemoryBlock* postData) override
@ -486,9 +778,7 @@ public:
void stop() override { [webView.get() stopLoading: nil]; }
void refresh() override { [webView.get() reload: nil]; }
id getWebView() override { return webView.get(); }
void mouseMove (const MouseEvent&)
void mouseMove (const MouseEvent&) override
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
// WebKit doesn't capture mouse-moves itself, so it seems the only way to make
@ -498,47 +788,185 @@ public:
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
void evaluateJavascript (const String&, WebBrowserComponent::EvaluationCallback) override
{
// This feature is only available on MacOS 10.11 and above
jassertfalse;
}
private:
WebBrowserComponent& browser;
LastFocusChange lastFocusChange;
ObjCObjectHandle<WebView*> webView;
ObjCObjectHandle<id> clickListener;
};
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
#endif
class API_AVAILABLE (macos (10.11)) WKWebViewImpl : public WebViewBase
class API_AVAILABLE (macos (10.11)) WebBrowserComponent::Impl::Platform::WKWebViewImpl : public WebBrowserComponent::Impl::PlatformInterface,
#if JUCE_MAC
public NSViewComponent
#else
public UIViewComponent
#endif
{
public:
WKWebViewImpl (WebBrowserComponent* owner, const String& userAgent)
WKWebViewImpl (WebBrowserComponent::Impl& implIn,
const WebBrowserComponent::Options& browserOptions,
const StringArray& userScripts)
: owner (implIn),
delegateConnector (implIn.owner,
[this] (const auto& m) { owner.handleNativeEvent (m); },
[this] (const auto& r) { return owner.handleResourceRequest (r); },
browserOptions)
{
#if JUCE_MAC
static WebViewKeyEquivalentResponder<WKWebView> webviewClass;
ObjCObjectHandle<WKWebViewConfiguration*> config { [WKWebViewConfiguration new] };
id preferences = [config.get() preferences];
webView.reset ([webviewClass.createInstance() initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)]);
#else
webView.reset ([[WKWebView alloc] initWithFrame: CGRectMake (0, 0, 100.0f, 100.0f)]);
#endif
if (userAgent.isNotEmpty())
webView.get().customUserAgent = juceStringToNS (userAgent);
[preferences setValue:@(true) forKey:@"fullScreenEnabled"];
[preferences setValue:@(true) forKey:@"DOMPasteAllowed"];
[preferences setValue:@(true) forKey:@"javaScriptCanAccessClipboard"];
static WebViewDelegateClass cls;
webViewDelegate.reset ([cls.createInstance() init]);
WebViewDelegateClass::setOwner (webViewDelegate.get(), owner);
WebViewDelegateClass::setConnector (webViewDelegate.get(), &delegateConnector);
if (browserOptions.getNativeIntegrationsEnabled())
{
[[config.get() userContentController] addScriptMessageHandler:webViewDelegate.get()
name:@"__JUCE__"];
}
// It isn't necessary to concatenate all scripts and add them as one. They will still work
// when added separately. But in the latter case sometimes only the first one is visible in
// the WebView developer console, so concatenating them helps with debugging.
auto allUserScripts = userScripts;
allUserScripts.insert (0, platformSpecificIntegrationScript);
NSUniquePtr<WKUserScript> script { [[WKUserScript alloc]
initWithSource:juceStringToNS (allUserScripts.joinIntoString ("\n"))
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
forMainFrameOnly:YES] };
[[config.get() userContentController] addUserScript:script.get()];
if (@available (macOS 10.13, iOS 11.0, *))
{
if (browserOptions.getResourceProvider() != nullptr)
[config.get() setURLSchemeHandler:webViewDelegate.get() forURLScheme:@"juce"];
}
#if JUCE_DEBUG
[preferences setValue: @(true) forKey: @"developerExtrasEnabled"];
#endif
#if JUCE_MAC
static WebViewKeyEquivalentResponder<WKWebView> webviewClass;
webView.reset ([webviewClass.createInstance() initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)
configuration: config.get()]);
setLastFocusChangeHandle (webView.get(), &lastFocusChange);
#else
webView.reset ([[WKWebView alloc] initWithFrame: CGRectMake (0, 0, 100.0f, 100.0f)
configuration: config.get()]);
#endif
if (const auto userAgent = browserOptions.getUserAgent(); userAgent.isNotEmpty())
webView.get().customUserAgent = juceStringToNS (userAgent);
[webView.get() setNavigationDelegate: webViewDelegate.get()];
[webView.get() setUIDelegate: webViewDelegate.get()];
#if JUCE_DEBUG
[[[webView.get() configuration] preferences] setValue: @(true) forKey: @"developerExtrasEnabled"];
#endif
setView (webView.get());
owner.owner.addAndMakeVisible (this);
}
~WKWebViewImpl() override
{
setView (nil);
[webView.get() setNavigationDelegate: nil];
[webView.get() setUIDelegate: nil];
}
void setWebViewSize (int width, int height) override
{
setSize (width, height);
}
void checkWindowAssociation() override
{
auto& browser = owner.owner;
if (browser.isShowing())
{
browser.reloadLastURL();
if (browser.blankPageShown)
browser.goBack();
}
else
{
if (browser.unloadPageWhenHidden && ! browser.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, (and send it back when it's made visible again).
browser.blankPageShown = true;
goToURL ("about:blank", nullptr, nullptr);
}
}
}
void focusGainedWithDirection (FocusChangeType, FocusChangeDirection) override
{
const auto webViewFocusLossDirection = std::exchange (lastFocusChange, std::nullopt);
// We didn't receive the focus from the WebView, so we need to pass it onto it
if (! webViewFocusLossDirection.has_value())
{
#if JUCE_MAC
[[webView.get() window] makeFirstResponder: webView.get()];
#endif
return;
}
auto* comp = [&]() -> Component*
{
auto* c = owner.owner.getParentComponent();
if (c == nullptr)
return nullptr;
const auto traverser = c->createFocusTraverser();
if (*webViewFocusLossDirection == FocusChangeDirection::forward)
{
if (auto* next = traverser->getNextComponent (this); next != nullptr)
return next;
return traverser->getDefaultComponent (c);
}
if (*webViewFocusLossDirection == FocusChangeDirection::backward)
{
if (auto* previous = traverser->getPreviousComponent (&owner.owner); previous != nullptr)
return previous;
if (auto all = traverser->getAllComponents (c); ! all.empty())
return all.back();
}
return nullptr;
}();
if (comp != nullptr)
comp->getAccessibilityHandler()->grabFocus();
else
giveAwayKeyboardFocus();
}
void goToURL (const String& url,
const StringArray* headers,
const MemoryBlock* postData) override
@ -574,169 +1002,87 @@ public:
void stop() override { [webView.get() stopLoading]; }
void refresh() override { [webView.get() reload]; }
id getWebView() override { return webView.get(); }
void evaluateJavascript (const String& script, WebBrowserComponent::EvaluationCallback callback) override
{
[webView.get() evaluateJavaScript: juceStringToNS (script)
completionHandler: ^(id obj, NSError* error)
{
if (callback == nullptr)
return;
if (error != nil)
{
const auto resultError = [&]() -> WebBrowserComponent::EvaluationResult::Error
{
const auto errorCode = [error code];
if (errorCode == 4)
{
String errorMsgTemplate { "JAVASCRIPT_ERROR at (EVALUATION_SOURCE:LINE_NUMBER:COLUMN_NUMBER)" };
if (id m = [error.userInfo objectForKey:@"WKJavaScriptExceptionMessage"]; m != nil)
errorMsgTemplate = errorMsgTemplate.replace ("JAVASCRIPT_ERROR", nsStringToJuce (m));
if (id m = [error.userInfo objectForKey:@"WKJavaScriptExceptionSourceURL"]; m != nil)
errorMsgTemplate = errorMsgTemplate.replace ("EVALUATION_SOURCE", nsStringToJuce ([m absoluteString]));
if (id m = [error.userInfo objectForKey:@"WKJavaScriptExceptionLineNumber"]; m != nil)
errorMsgTemplate = errorMsgTemplate.replace ("LINE_NUMBER", String { [m intValue] });
if (id m = [error.userInfo objectForKey:@"WKJavaScriptExceptionColumnNumber"]; m != nil)
errorMsgTemplate = errorMsgTemplate.replace ("COLUMN_NUMBER", String { [m intValue] });
return { WebBrowserComponent::EvaluationResult::Error::Type::javascriptException,
errorMsgTemplate };
}
else if (errorCode == 5)
{
String errorMessage;
if (id m = [[error userInfo] objectForKey:@"NSLocalizedDescription"]; m != nil)
errorMessage = nsStringToJuce (m);
return { WebBrowserComponent::EvaluationResult::Error::Type::unsupportedReturnType,
errorMessage };
}
return { WebBrowserComponent::EvaluationResult::Error::Type::unknown, "Unknown error" };
}();
callback (EvaluationResult { resultError });
}
else
{
callback (EvaluationResult { fromObject (obj) });
}
}];
}
private:
WebBrowserComponent::Impl& owner;
DelegateConnector delegateConnector;
LastFocusChange lastFocusChange;
ObjCObjectHandle<WKWebView*> webView;
ObjCObjectHandle<id> webViewDelegate;
};
//==============================================================================
class WebBrowserComponent::Pimpl
#if JUCE_MAC
: public NSViewComponent
#else
: public UIViewComponent
#endif
auto WebBrowserComponent::Impl::createAndInitPlatformDependentPart (WebBrowserComponent::Impl& impl,
const WebBrowserComponent::Options& options,
const StringArray& userScripts)
-> std::unique_ptr<PlatformInterface>
{
public:
Pimpl (WebBrowserComponent* owner, const String& userAgent)
{
if (@available (macOS 10.11, *))
webView = std::make_unique<WKWebViewImpl> (owner, userAgent);
#if JUCE_MAC
else
webView = std::make_unique<WebViewImpl> (owner, userAgent);
#endif
if (@available (macOS 10.11, *))
return std::make_unique<Platform::WKWebViewImpl> (impl, options, userScripts);
setView (webView->getWebView());
}
#if JUCE_MAC
return std::make_unique<Platform::WebViewImpl> (impl, options.getUserAgent());
#endif
~Pimpl() override
{
webView = nullptr;
setView (nil);
}
void goToURL (const String& url,
const StringArray* headers,
const MemoryBlock* postData)
{
webView->goToURL (url, headers, postData);
}
void goBack() { webView->goBack(); }
void goForward() { webView->goForward(); }
void stop() { webView->stop(); }
void refresh() { webView->refresh(); }
private:
std::unique_ptr<WebViewBase> webView;
};
//==============================================================================
WebBrowserComponent::WebBrowserComponent (const Options& options)
: unloadPageWhenHidden (! options.keepsPageLoadedWhenBrowserIsHidden())
{
setOpaque (true);
browser.reset (new Pimpl (this, options.getUserAgent()));
addAndMakeVisible (browser.get());
}
WebBrowserComponent::~WebBrowserComponent() = default;
//==============================================================================
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;
browser->goToURL (url, headers, postData);
}
void WebBrowserComponent::stop()
{
browser->stop();
}
void WebBrowserComponent::goBack()
{
lastURL.clear();
blankPageShown = false;
browser->goBack();
}
void WebBrowserComponent::goForward()
{
lastURL.clear();
browser->goForward();
}
void WebBrowserComponent::refresh()
{
browser->refresh();
return {};
}
//==============================================================================
void WebBrowserComponent::paint (Graphics&)
{
}
void WebBrowserComponent::checkWindowAssociation()
{
if (isShowing())
{
reloadLastURL();
if (blankPageShown)
goBack();
}
else
{
if (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, (and send it back when it's made visible again).
blankPageShown = true;
browser->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->setSize (getWidth(), getHeight());
}
void WebBrowserComponent::visibilityChanged()
{
checkWindowAssociation();
}
void WebBrowserComponent::focusGainedWithDirection (FocusChangeType, FocusChangeDirection)
{
}
void WebBrowserComponent::clearCookies()
{
NSHTTPCookieStorage* storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];