mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-09 23:34:20 +00:00
MacOS: Fix WebBrowserComponent going blank in FL Studio
The issue could be triggered by opening the plugin in FL Studio, and
then using the TAB button to switch between FL Studio UI elements, until
the plugin became invisible and then it became visible again. This would
cause the WebBrowserComponent to navigate to about:blank permanently.
This was caused by the component becoming invisible and visible again in
rapid succession. This triggered a navigation to about:blank. To
understand the root cause of this, some undocumented behaviour of
WkWebView had to be uncovered. To understand this, see the following
test code, where the test1, test2 and test3 functions are called with
ample time in between one after the other.
void test1()
{
goToURL ("A");
}
void test2()
{
goToURL ("B");
goToURL ("C");
// B, C ignored completely, only D inserted into back-forward navigation queue
goToURL ("D");
}
void test3()
{
goToURL ("E");
goToURL ("F");
// E, F ignored completely, back navigation executed from D to A
goBack();
}
This commit is contained in:
parent
bc8e9e05af
commit
7449867337
1 changed files with 57 additions and 6 deletions
|
|
@ -383,10 +383,12 @@ public:
|
||||||
DelegateConnector (WebBrowserComponent& browserIn,
|
DelegateConnector (WebBrowserComponent& browserIn,
|
||||||
std::function<void (const var&)> handleNativeEventFnIn,
|
std::function<void (const var&)> handleNativeEventFnIn,
|
||||||
std::function<std::optional<WebBrowserComponent::Resource> (const String&)> handleResourceRequestFnIn,
|
std::function<std::optional<WebBrowserComponent::Resource> (const String&)> handleResourceRequestFnIn,
|
||||||
|
std::function<void (const String&)> didFinishNavigationCallbackIn,
|
||||||
const WebBrowserComponent::Options& optionsIn)
|
const WebBrowserComponent::Options& optionsIn)
|
||||||
: browser (browserIn),
|
: browser (browserIn),
|
||||||
handleNativeEventFn (std::move (handleNativeEventFnIn)),
|
handleNativeEventFn (std::move (handleNativeEventFnIn)),
|
||||||
handleResourceRequestFn (std::move (handleResourceRequestFnIn)),
|
handleResourceRequestFn (std::move (handleResourceRequestFnIn)),
|
||||||
|
didFinishNavigationCallback (std::move (didFinishNavigationCallbackIn)),
|
||||||
options (optionsIn)
|
options (optionsIn)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -408,10 +410,16 @@ public:
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void didFinishNavigation (const String& url)
|
||||||
|
{
|
||||||
|
didFinishNavigationCallback (url);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
WebBrowserComponent& browser;
|
WebBrowserComponent& browser;
|
||||||
std::function<void (const var&)> handleNativeEventFn;
|
std::function<void (const var&)> handleNativeEventFn;
|
||||||
std::function<std::optional<WebBrowserComponent::Resource> (const String&)> handleResourceRequestFn;
|
std::function<std::optional<WebBrowserComponent::Resource> (const String&)> handleResourceRequestFn;
|
||||||
|
std::function<void (const String&)> didFinishNavigationCallback;
|
||||||
WebBrowserComponent::Options options;
|
WebBrowserComponent::Options options;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -437,7 +445,7 @@ struct WebViewDelegateClass final : public ObjCClass<NSObject>
|
||||||
[] (id self, SEL, WKWebView* webview, WKNavigation*)
|
[] (id self, SEL, WKWebView* webview, WKNavigation*)
|
||||||
{
|
{
|
||||||
if (auto* connector = getConnector (self))
|
if (auto* connector = getConnector (self))
|
||||||
connector->getBrowser().pageFinishedLoading (nsStringToJuce ([[webview URL] absoluteString]));
|
connector->didFinishNavigation (nsStringToJuce ([[webview URL] absoluteString]));
|
||||||
});
|
});
|
||||||
|
|
||||||
addMethod (@selector (webView:didFailNavigation:withError:),
|
addMethod (@selector (webView:didFailNavigation:withError:),
|
||||||
|
|
@ -811,6 +819,7 @@ JUCE_END_IGNORE_DEPRECATION_WARNINGS
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class WebBrowserComponent::Impl::Platform::WKWebViewImpl : public WebBrowserComponent::Impl::PlatformInterface,
|
class WebBrowserComponent::Impl::Platform::WKWebViewImpl : public WebBrowserComponent::Impl::PlatformInterface,
|
||||||
|
private AsyncUpdater,
|
||||||
#if JUCE_MAC
|
#if JUCE_MAC
|
||||||
public NSViewComponent
|
public NSViewComponent
|
||||||
#else
|
#else
|
||||||
|
|
@ -825,6 +834,10 @@ public:
|
||||||
delegateConnector (implIn.owner,
|
delegateConnector (implIn.owner,
|
||||||
[this] (const auto& m) { owner.handleNativeEvent (m); },
|
[this] (const auto& m) { owner.handleNativeEvent (m); },
|
||||||
[this] (const auto& r) { return owner.handleResourceRequest (r); },
|
[this] (const auto& r) { return owner.handleResourceRequest (r); },
|
||||||
|
[this] (const auto& url) {
|
||||||
|
lastLoadedUrl = url;
|
||||||
|
owner.owner.pageFinishedLoading (url);
|
||||||
|
},
|
||||||
browserOptions),
|
browserOptions),
|
||||||
allowAccessToEnclosingDirectory (browserOptions.getAppleWkWebViewOptions()
|
allowAccessToEnclosingDirectory (browserOptions.getAppleWkWebViewOptions()
|
||||||
.getAllowAccessToEnclosingDirectory())
|
.getAllowAccessToEnclosingDirectory())
|
||||||
|
|
@ -917,6 +930,39 @@ public:
|
||||||
setSize (width, height);
|
setSize (width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handleAsyncUpdate() override
|
||||||
|
{
|
||||||
|
auto& browser = owner.owner;
|
||||||
|
|
||||||
|
if (! browser.blankPageShown)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (lastRequestedUrl != blankPageUrl)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// According to our logic, a blank page was shown, and now we are trying to go back to the
|
||||||
|
// page before that.
|
||||||
|
//
|
||||||
|
// But WkWebView seems to be doing some asynchronous batching, and if you send loadRequest:
|
||||||
|
// and goBack in quick succession, loadRequest: will be ignored entirely and goBack will be
|
||||||
|
// executed on the backForwardList as if it never happened.
|
||||||
|
//
|
||||||
|
// Although none of this is documented, it seems we can reliably query the current contents
|
||||||
|
// of the backForwardList to see, if we would be navigating away from the URL with the
|
||||||
|
// actual contents if we executed goBack now, and we can wait until loadRequest: has taken
|
||||||
|
// effect.
|
||||||
|
//
|
||||||
|
// This behaviour initially caused a bug in FL Studio, where the plugin window can become
|
||||||
|
// invisible and visible again in very rapid succession, when using the TAB button.
|
||||||
|
if (lastLoadedUrl != blankPageUrl)
|
||||||
|
{
|
||||||
|
triggerAsyncUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
browser.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
void checkWindowAssociation() override
|
void checkWindowAssociation() override
|
||||||
{
|
{
|
||||||
auto& browser = owner.owner;
|
auto& browser = owner.owner;
|
||||||
|
|
@ -924,20 +970,21 @@ public:
|
||||||
if (browser.isShowing())
|
if (browser.isShowing())
|
||||||
{
|
{
|
||||||
browser.reloadLastURL();
|
browser.reloadLastURL();
|
||||||
|
handleAsyncUpdate();
|
||||||
if (browser.blankPageShown)
|
|
||||||
browser.goBack();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (browser.unloadPageWhenHidden && ! browser.blankPageShown)
|
if ( browser.unloadPageWhenHidden
|
||||||
|
&& ! browser.blankPageShown
|
||||||
|
&& lastLoadedUrl.isNotEmpty()
|
||||||
|
&& lastLoadedUrl != blankPageUrl)
|
||||||
{
|
{
|
||||||
// when the component becomes invisible, some stuff like flash
|
// when the component becomes invisible, some stuff like flash
|
||||||
// carries on playing audio, so we need to force it onto a blank
|
// 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).
|
// page to avoid this, (and send it back when it's made visible again).
|
||||||
|
|
||||||
browser.blankPageShown = true;
|
browser.blankPageShown = true;
|
||||||
goToURL ("about:blank", nullptr, nullptr);
|
goToURL (blankPageUrl, nullptr, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1030,6 +1077,7 @@ public:
|
||||||
}
|
}
|
||||||
else if (NSMutableURLRequest* request = getRequestForURL (url, headers, postData))
|
else if (NSMutableURLRequest* request = getRequestForURL (url, headers, postData))
|
||||||
{
|
{
|
||||||
|
lastRequestedUrl = url;
|
||||||
[webView.get() loadRequest: request];
|
[webView.get() loadRequest: request];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1097,12 +1145,15 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static inline auto blankPageUrl = "about:blank";
|
||||||
|
|
||||||
WebBrowserComponent::Impl& owner;
|
WebBrowserComponent::Impl& owner;
|
||||||
DelegateConnector delegateConnector;
|
DelegateConnector delegateConnector;
|
||||||
bool allowAccessToEnclosingDirectory = false;
|
bool allowAccessToEnclosingDirectory = false;
|
||||||
LastFocusChange lastFocusChange;
|
LastFocusChange lastFocusChange;
|
||||||
ObjCObjectHandle<WKWebView*> webView;
|
ObjCObjectHandle<WKWebView*> webView;
|
||||||
ObjCObjectHandle<id> webViewDelegate;
|
ObjCObjectHandle<id> webViewDelegate;
|
||||||
|
String lastRequestedUrl, lastLoadedUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue