Skip to content

Commit 7449867

Browse files
committed
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(); }
1 parent bc8e9e0 commit 7449867

File tree

1 file changed

+57
-6
lines changed

1 file changed

+57
-6
lines changed

modules/juce_gui_extra/native/juce_WebBrowserComponent_mac.mm

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -383,10 +383,12 @@ static void didFailLoadWithError (id self, SEL, WebView* sender, NSError* error,
383383
DelegateConnector (WebBrowserComponent& browserIn,
384384
std::function<void (const var&)> handleNativeEventFnIn,
385385
std::function<std::optional<WebBrowserComponent::Resource> (const String&)> handleResourceRequestFnIn,
386+
std::function<void (const String&)> didFinishNavigationCallbackIn,
386387
const WebBrowserComponent::Options& optionsIn)
387388
: browser (browserIn),
388389
handleNativeEventFn (std::move (handleNativeEventFnIn)),
389390
handleResourceRequestFn (std::move (handleResourceRequestFnIn)),
391+
didFinishNavigationCallback (std::move (didFinishNavigationCallbackIn)),
390392
options (optionsIn)
391393
{
392394
}
@@ -408,10 +410,16 @@ auto handleResourceRequest (const String& url)
408410
return options;
409411
}
410412

413+
void didFinishNavigation (const String& url)
414+
{
415+
didFinishNavigationCallback (url);
416+
}
417+
411418
private:
412419
WebBrowserComponent& browser;
413420
std::function<void (const var&)> handleNativeEventFn;
414421
std::function<std::optional<WebBrowserComponent::Resource> (const String&)> handleResourceRequestFn;
422+
std::function<void (const String&)> didFinishNavigationCallback;
415423
WebBrowserComponent::Options options;
416424
};
417425

@@ -437,7 +445,7 @@ auto handleResourceRequest (const String& url)
437445
[] (id self, SEL, WKWebView* webview, WKNavigation*)
438446
{
439447
if (auto* connector = getConnector (self))
440-
connector->getBrowser().pageFinishedLoading (nsStringToJuce ([[webview URL] absoluteString]));
448+
connector->didFinishNavigation (nsStringToJuce ([[webview URL] absoluteString]));
441449
});
442450

443451
addMethod (@selector (webView:didFailNavigation:withError:),
@@ -811,6 +819,7 @@ void evaluateJavascript (const String&, WebBrowserComponent::EvaluationCallback)
811819
#endif
812820

813821
class WebBrowserComponent::Impl::Platform::WKWebViewImpl : public WebBrowserComponent::Impl::PlatformInterface,
822+
private AsyncUpdater,
814823
#if JUCE_MAC
815824
public NSViewComponent
816825
#else
@@ -825,6 +834,10 @@ void evaluateJavascript (const String&, WebBrowserComponent::EvaluationCallback)
825834
delegateConnector (implIn.owner,
826835
[this] (const auto& m) { owner.handleNativeEvent (m); },
827836
[this] (const auto& r) { return owner.handleResourceRequest (r); },
837+
[this] (const auto& url) {
838+
lastLoadedUrl = url;
839+
owner.owner.pageFinishedLoading (url);
840+
},
828841
browserOptions),
829842
allowAccessToEnclosingDirectory (browserOptions.getAppleWkWebViewOptions()
830843
.getAllowAccessToEnclosingDirectory())
@@ -917,27 +930,61 @@ void setWebViewSize (int width, int height) override
917930
setSize (width, height);
918931
}
919932

933+
void handleAsyncUpdate() override
934+
{
935+
auto& browser = owner.owner;
936+
937+
if (! browser.blankPageShown)
938+
return;
939+
940+
if (lastRequestedUrl != blankPageUrl)
941+
return;
942+
943+
// According to our logic, a blank page was shown, and now we are trying to go back to the
944+
// page before that.
945+
//
946+
// But WkWebView seems to be doing some asynchronous batching, and if you send loadRequest:
947+
// and goBack in quick succession, loadRequest: will be ignored entirely and goBack will be
948+
// executed on the backForwardList as if it never happened.
949+
//
950+
// Although none of this is documented, it seems we can reliably query the current contents
951+
// of the backForwardList to see, if we would be navigating away from the URL with the
952+
// actual contents if we executed goBack now, and we can wait until loadRequest: has taken
953+
// effect.
954+
//
955+
// This behaviour initially caused a bug in FL Studio, where the plugin window can become
956+
// invisible and visible again in very rapid succession, when using the TAB button.
957+
if (lastLoadedUrl != blankPageUrl)
958+
{
959+
triggerAsyncUpdate();
960+
return;
961+
}
962+
963+
browser.goBack();
964+
}
965+
920966
void checkWindowAssociation() override
921967
{
922968
auto& browser = owner.owner;
923969

924970
if (browser.isShowing())
925971
{
926972
browser.reloadLastURL();
927-
928-
if (browser.blankPageShown)
929-
browser.goBack();
973+
handleAsyncUpdate();
930974
}
931975
else
932976
{
933-
if (browser.unloadPageWhenHidden && ! browser.blankPageShown)
977+
if ( browser.unloadPageWhenHidden
978+
&& ! browser.blankPageShown
979+
&& lastLoadedUrl.isNotEmpty()
980+
&& lastLoadedUrl != blankPageUrl)
934981
{
935982
// when the component becomes invisible, some stuff like flash
936983
// carries on playing audio, so we need to force it onto a blank
937984
// page to avoid this, (and send it back when it's made visible again).
938985

939986
browser.blankPageShown = true;
940-
goToURL ("about:blank", nullptr, nullptr);
987+
goToURL (blankPageUrl, nullptr, nullptr);
941988
}
942989
}
943990
}
@@ -1030,6 +1077,7 @@ void goToURL (const String& url,
10301077
}
10311078
else if (NSMutableURLRequest* request = getRequestForURL (url, headers, postData))
10321079
{
1080+
lastRequestedUrl = url;
10331081
[webView.get() loadRequest: request];
10341082
}
10351083
}
@@ -1097,12 +1145,15 @@ void evaluateJavascript (const String& script, WebBrowserComponent::EvaluationCa
10971145
}
10981146

10991147
private:
1148+
static inline auto blankPageUrl = "about:blank";
1149+
11001150
WebBrowserComponent::Impl& owner;
11011151
DelegateConnector delegateConnector;
11021152
bool allowAccessToEnclosingDirectory = false;
11031153
LastFocusChange lastFocusChange;
11041154
ObjCObjectHandle<WKWebView*> webView;
11051155
ObjCObjectHandle<id> webViewDelegate;
1156+
String lastRequestedUrl, lastLoadedUrl;
11061157
};
11071158

11081159
//==============================================================================

0 commit comments

Comments
 (0)