Bug 1596825 - Add NavigationDelegate.onSubframeLoadRequest to handle non-top-level load requests. r=snorp,mattwoodrow,geckoview-reviewers,agi
authorDylan Roeh <droeh@mozilla.com>
Tue, 19 May 2020 17:05:42 +0000
changeset 530846 328927902c0607dd2600063e259bda1fa51c2e7b
parent 530845 36ca759b04e6e72d627655914cdd3635bbaf5c2c
child 530847 d69e32a6f3b0144635c7b93c81589f31a84fcc36
push id37433
push userdluca@mozilla.com
push dateWed, 20 May 2020 03:39:31 +0000
treeherdermozilla-central@855249e545c3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp, mattwoodrow, geckoview-reviewers, agi
bugs1596825
milestone78.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1596825 - Add NavigationDelegate.onSubframeLoadRequest to handle non-top-level load requests. r=snorp,mattwoodrow,geckoview-reviewers,agi Differential Revision: https://phabricator.services.mozilla.com/D74939
mobile/android/geckoview/api.txt
mobile/android/geckoview/src/androidTest/assets/www/iframe_unknown_protocol.html
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
netwerk/ipc/DocumentLoadListener.cpp
widget/android/nsWindow.cpp
widget/android/nsWindow.h
--- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -739,16 +739,17 @@ package org.mozilla.geckoview {
 
   public static interface GeckoSession.NavigationDelegate {
     method @UiThread default public void onCanGoBack(@NonNull GeckoSession, boolean);
     method @UiThread default public void onCanGoForward(@NonNull GeckoSession, boolean);
     method @UiThread @Nullable default public GeckoResult<String> onLoadError(@NonNull GeckoSession, @Nullable String, @NonNull WebRequestError);
     method @UiThread @Nullable default public GeckoResult<AllowOrDeny> onLoadRequest(@NonNull GeckoSession, @NonNull GeckoSession.NavigationDelegate.LoadRequest);
     method @UiThread default public void onLocationChange(@NonNull GeckoSession, @Nullable String);
     method @UiThread @Nullable default public GeckoResult<GeckoSession> onNewSession(@NonNull GeckoSession, @NonNull String);
+    method @UiThread @Nullable default public GeckoResult<AllowOrDeny> onSubframeLoadRequest(@NonNull GeckoSession, @NonNull GeckoSession.NavigationDelegate.LoadRequest);
     field public static final int LOAD_REQUEST_IS_REDIRECT = 8388608;
     field public static final int TARGET_WINDOW_CURRENT = 1;
     field public static final int TARGET_WINDOW_NEW = 2;
     field public static final int TARGET_WINDOW_NONE = 0;
   }
 
   public static class GeckoSession.NavigationDelegate.LoadRequest {
     ctor protected LoadRequest();
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/androidTest/assets/www/iframe_unknown_protocol.html
@@ -0,0 +1,9 @@
+<html>
+    <head>
+        <title>Hello, world!</title>
+    </head>
+    <body>
+        <p>Hello, world! From Top Level.</p>
+        <iframe src="foo://bar"></iframe>
+    </body>
+</html>
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
@@ -64,16 +64,17 @@ open class BaseSessionTest(noErrorCollec
         const val FIXED_VH = "/assets/www/fixedvh.html"
         const val FIXED_PERCENT = "/assets/www/fixedpercent.html"
         const val STORAGE_TITLE_HTML_PATH = "/assets/www/reflect_local_storage_into_title.html"
         const val HUNG_SCRIPT = "/assets/www/hungScript.html"
         const val PUSH_HTML_PATH = "/assets/www/push/push.html"
         const val OPEN_WINDOW_PATH = "/assets/www/worker/open_window.html"
         const val OPEN_WINDOW_TARGET_PATH = "/assets/www/worker/open_window_target.html"
         const val DATA_URI_PATH = "/assets/www/data_uri.html"
+        const val IFRAME_UNKNOWN_PROTOCOL = "/assets/www/iframe_unknown_protocol.html"
 
         const val TEST_ENDPOINT = GeckoSessionTestRule.TEST_ENDPOINT
     }
 
     @get:Rule val sessionRule = GeckoSessionTestRule()
 
     @get:Rule val errors = ErrorCollector()
 
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
@@ -202,16 +202,41 @@ class NavigationDelegateTest : BaseSessi
 
     @Ignore // Disabled for bug 1619344.
     @Test fun loadUnknownProtocol() {
         testLoadEarlyError(UNKNOWN_PROTOCOL_URI,
                 WebRequestError.ERROR_CATEGORY_URI,
                 WebRequestError.ERROR_UNKNOWN_PROTOCOL)
     }
 
+    @Test fun loadUnknownProtocolIframe() {
+        // Should match iframe URI from IFRAME_UNKNOWN_PROTOCOL
+        val iframeUri = "foo://bar"
+        sessionRule.session.loadTestPath(IFRAME_UNKNOWN_PROTOCOL)
+        sessionRule.session.waitForPageStop()
+
+        sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
+            @AssertCalled(count = 1)
+            override fun onLoadRequest(session: GeckoSession, request: LoadRequest) : GeckoResult<AllowOrDeny>? {
+                assertThat("URI should not be null", request.uri, notNullValue())
+                assertThat("URI should match", request.uri, endsWith(IFRAME_UNKNOWN_PROTOCOL))
+                return null
+            }
+
+            @AssertCalled(count = 1)
+            override fun onSubframeLoadRequest(session: GeckoSession,
+                                               request: LoadRequest):
+                                               GeckoResult<AllowOrDeny>? {
+                assertThat("URI should not be null", request.uri, notNullValue())
+                assertThat("URI should match", request.uri, endsWith(iframeUri))
+                return null
+            }
+        })
+    }
+
     @Setting(key = Setting.Key.USE_TRACKING_PROTECTION, value = "true")
     @Ignore // TODO: Bug 1564373
     @Test fun trackingProtection() {
         val category = ContentBlocking.AntiTracking.TEST
         sessionRule.runtime.settings.contentBlocking.setAntiTracking(category)
         sessionRule.session.loadTestPath(TRACKERS_PATH)
 
         sessionRule.waitUntilCalled(
@@ -304,21 +329,32 @@ class NavigationDelegateTest : BaseSessi
 
         // We shouldn't be firing onLoadRequest for iframes, including redirects.
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1)
             override fun onLoadRequest(session: GeckoSession,
                                        request: LoadRequest):
                     GeckoResult<AllowOrDeny>? {
                 assertThat("Session should not be null", session, notNullValue())
-                assertThat("App requested this load", request.isDirectNavigation,
-                        equalTo(true))
+                assertThat("App requested this load", request.isDirectNavigation, equalTo(true))
                 assertThat("URI should not be null", request.uri, notNullValue())
-                assertThat("URI should match", request.uri,
-                        startsWith(GeckoSessionTestRule.TEST_ENDPOINT))
+                assertThat("URI should match", request.uri, endsWith(path))
+                assertThat("isRedirect should match", request.isRedirect, equalTo(false))
+                return null
+            }
+
+            @AssertCalled(count = 2)
+            override fun onSubframeLoadRequest(session: GeckoSession,
+                                               request: LoadRequest):
+                    GeckoResult<AllowOrDeny>? {
+                assertThat("Session should not be null", session, notNullValue())
+                assertThat("App did not request this load", request.isDirectNavigation, equalTo(false))
+                assertThat("URI should not be null", request.uri, notNullValue())
+                assertThat("isRedirect should match", request.isRedirect,
+                        equalTo(forEachCall(false, true)))
                 return null
             }
         })
     }
 
     @Test fun redirectDenyLoad() {
         val redirectUri = if (sessionRule.env.isAutomation) {
             "http://example.org/tests/junit/hello.html"
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -1144,17 +1144,17 @@ public class GeckoSession implements Par
         protected void finalize() throws Throwable {
             close();
             disposeNative();
         }
 
         @WrapForJNI(calledFrom = "gecko")
         private GeckoResult<Boolean> onLoadRequest(final @NonNull String uri, final int windowType,
                                                    final int flags, final @Nullable String triggeringUri,
-                                                   final boolean hasUserGesture) {
+                                                   final boolean hasUserGesture, final boolean isTopLevel) {
             final GeckoSession session = (mOwner == null) ? null : mOwner.get();
             if (session == null) {
                 // Don't handle any load request if we can't get the session for some reason.
                 return GeckoResult.fromValue(false);
             }
             GeckoResult<Boolean> res = new GeckoResult<>();
 
             ThreadUtils.postToUiThread(new Runnable() {
@@ -1165,17 +1165,19 @@ public class GeckoSession implements Par
                     if (delegate == null) {
                         res.complete(false);
                         return;
                     }
 
                     final String trigger = TextUtils.isEmpty(triggeringUri) ? null : triggeringUri;
                     final NavigationDelegate.LoadRequest req = new NavigationDelegate.LoadRequest(uri,
                             trigger, windowType, flags, hasUserGesture, false /* isDirectNavigation */);
-                    final GeckoResult<AllowOrDeny> reqResponse = delegate.onLoadRequest(session, req);
+                    final GeckoResult<AllowOrDeny> reqResponse = isTopLevel ?
+                            delegate.onLoadRequest(session, req) :
+                            delegate.onSubframeLoadRequest(session, req);
 
                     if (reqResponse == null) {
                         res.complete(false);
                         return;
                     }
 
                     reqResponse.accept(value -> {
                         if (value == AllowOrDeny.DENY) {
@@ -3701,16 +3703,34 @@ public class GeckoSession implements Par
          */
         @UiThread
         default @Nullable GeckoResult<AllowOrDeny> onLoadRequest(@NonNull GeckoSession session,
                                                                  @NonNull LoadRequest request) {
             return null;
         }
 
         /**
+         * A request to load a URI in a non-top-level context.
+         *
+         * @param session The GeckoSession that initiated the callback.
+         * @param request The {@link LoadRequest} containing the request details.
+         *
+         * @return A {@link GeckoResult} with a {@link AllowOrDeny} value which indicates whether
+         *         or not the load was handled. If unhandled, Gecko will continue the
+         *         load as normal. If handled (a {@link AllowOrDeny#DENY DENY} value), Gecko
+         *         will abandon the load. A null return value is interpreted as
+         *         {@link AllowOrDeny#ALLOW ALLOW} (unhandled).
+         */
+        @UiThread
+        default @Nullable GeckoResult<AllowOrDeny> onSubframeLoadRequest(@NonNull GeckoSession session,
+                                                                         @NonNull LoadRequest request) {
+            return null;
+        }
+
+        /**
         * A request has been made to open a new session. The URI is provided only for
         * informational purposes. Do not call GeckoSession.loadUri() here. Additionally, the
         * returned GeckoSession must be a newly-created one.
         *
         * @param session The GeckoSession that initiated the callback.
         * @param uri The URI to be loaded.
         *
         * @return A {@link GeckoResult} which holds the returned GeckoSession. May be null, in
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
@@ -20,21 +20,24 @@ exclude: true
   and will be removed in GeckoView 81.
 - Added [`CookieBehavior.ACCEPT_FIRST_PARTY_AND_ISOLATE_OTHERS`][78.2] to allow
   enabling dynamic first party isolation; this will block tracking cookies and
   isolate all other third party cookies by keying them based on the first party
   from which they are accessed.
 - Added `cookieStoreId` field to [`WebExtension.CreateTabDetails`][78.3]. This adds the optional
   ability to create a tab with a given cookie store ID for its [`contextual identity`][78.4].
   ([bug 1622500]({{bugzilla}}1622500))
+- Added [`NavigationDelegate.onSubframeLoadRequest`][78.5] to allow intercepting
+  non-top-level navigations.
 
 [78.1]: {{javadoc_uri}}/WebExtensionController.html#installBuiltIn-java.lang.String-
 [78.2]: {{javadoc_uri}}/ContentBlocking.CookieBehavior.html#ACCEPT_FIRST_PARTY_AND_ISOLATE_OTHERS
 [78.3]: {{javadoc_uri}}/WebExtension.CreateTabDetails.html
 [78.4]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/contextualIdentities
+[78.5]: {{javadoc_uri}}/GeckoSession.NavigationDelegate.html#onSubframeLoadRequest-org.mozilla.geckoview.GeckoSession-org.mozilla.geckoview.GeckoSession.NavigationDelegate.LoadRequest-
 
 ## v77
 - Added [`GeckoRuntime.appendAppNotesToCrashReport`][77.1] For adding app notes to the crash report.
   ([bug 1626979]({{bugzilla}}1626979))
 - ⚠️ Remove the `DynamicToolbarAnimator` API along with accesors on `GeckoView` and `GeckoSession`.
   ([bug 1627716]({{bugzilla}}1627716))
 
 [77.1]: {{javadoc_uri}}/GeckoRuntime.html#appendAppNotesToCrashReport-java.lang.String-
@@ -688,9 +691,9 @@ exclude: true
 [65.19]: {{javadoc_uri}}/GeckoSession.NavigationDelegate.LoadRequest.html#isRedirect
 [65.20]: {{javadoc_uri}}/GeckoSession.html#LOAD_FLAGS_BYPASS_CLASSIFIER
 [65.21]: {{javadoc_uri}}/GeckoSession.ContentDelegate.ContextElement.html
 [65.22]: {{javadoc_uri}}/GeckoSession.ContentDelegate.html#onContextMenu-org.mozilla.geckoview.GeckoSession-int-int-org.mozilla.geckoview.GeckoSession.ContentDelegate.ContextElement-
 [65.23]: {{javadoc_uri}}/GeckoSession.FinderResult.html
 [65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
 [65.25]: {{javadoc_uri}}/GeckoResult.html
 
-[api-version]: 5460cbfe03322d19964ce94082ac2ae99af6d791
+[api-version]: bde8001c948235193636d0d21f684baeb551e739
--- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
+++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
@@ -1659,17 +1659,29 @@ public class GeckoViewActivity
         }
 
         @Override
         public GeckoResult<AllowOrDeny> onLoadRequest(final GeckoSession session,
                                                       final LoadRequest request) {
             Log.d(LOGTAG, "onLoadRequest=" + request.uri +
                   " triggerUri=" + request.triggerUri +
                   " where=" + request.target +
-                  " isRedirect=" + request.isRedirect);
+                  " isRedirect=" + request.isRedirect +
+                  " isDirectNavigation=" + request.isDirectNavigation);
+
+            return GeckoResult.fromValue(AllowOrDeny.ALLOW);
+        }
+
+        @Override
+        public GeckoResult<AllowOrDeny> onSubframeLoadRequest(final GeckoSession session,
+                                                              final LoadRequest request) {
+            Log.d(LOGTAG, "onSubframeLoadRequest=" + request.uri +
+                  " triggerUri=" + request.triggerUri +
+                  " isRedirect=" + request.isRedirect +
+                  "isDirectNavigation=" + request.isDirectNavigation);
 
             return GeckoResult.fromValue(AllowOrDeny.ALLOW);
         }
 
         @Override
         public GeckoResult<GeckoSession> onNewSession(final GeckoSession session, final String uri) {
             final TabSession newSession = createSession();
             mToolbarView.updateTabCount();
--- a/netwerk/ipc/DocumentLoadListener.cpp
+++ b/netwerk/ipc/DocumentLoadListener.cpp
@@ -477,27 +477,26 @@ bool DocumentLoadListener::Open(
                                         browsingContext);
   openInfo->Prepare();
 
 #ifdef ANDROID
   RefPtr<MozPromise<bool, bool, false>> promise;
   if (aLoadState->LoadType() != LOAD_ERROR_PAGE &&
       !(aLoadState->HasLoadFlags(
           nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE)) &&
-      browsingContext->IsTopContent() &&
       !(aLoadState->LoadType() & LOAD_HISTORY)) {
     nsCOMPtr<nsIWidget> widget =
         browsingContext->GetParentProcessWidgetContaining();
     RefPtr<nsWindow> window = nsWindow::From(widget);
 
     if (window) {
       promise = window->OnLoadRequest(
           aLoadState->URI(), nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
           aLoadState->LoadFlags(), aLoadState->TriggeringPrincipal(),
-          aHasGesture);
+          aHasGesture, browsingContext->IsTopContent());
     }
   }
 
   if (promise) {
     RefPtr<DocumentLoadListener> self = this;
     promise->Then(
         GetCurrentThreadSerialEventTarget(), __func__,
         [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) {
@@ -1836,25 +1835,24 @@ DocumentLoadListener::AsyncOnChannelRedi
 
 #ifdef ANDROID
   nsCOMPtr<nsIURI> uriBeingLoaded =
       AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(mChannel);
   RefPtr<CanonicalBrowsingContext> bc =
       mParentChannelListener->GetBrowsingContext();
 
   RefPtr<MozPromise<bool, bool, false>> promise;
-  if (bc->IsTopContent()) {
-    nsCOMPtr<nsIWidget> widget = bc->GetParentProcessWidgetContaining();
-    RefPtr<nsWindow> window = nsWindow::From(widget);
+  nsCOMPtr<nsIWidget> widget = bc->GetParentProcessWidgetContaining();
+  RefPtr<nsWindow> window = nsWindow::From(widget);
 
-    if (window) {
-      promise = window->OnLoadRequest(
-          uriBeingLoaded, nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
-          nsIWebNavigation::LOAD_FLAGS_IS_REDIRECT, nullptr, false);
-    }
+  if (window) {
+    promise = window->OnLoadRequest(uriBeingLoaded,
+                                    nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
+                                    nsIWebNavigation::LOAD_FLAGS_IS_REDIRECT,
+                                    nullptr, false, bc->IsTopContent());
   }
 
   if (promise) {
     RefPtr<nsIAsyncVerifyRedirectCallback> cb = aCallback;
     promise->Then(
         GetCurrentThreadSerialEventTarget(), __func__,
         [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) {
           if (aValue.IsResolve()) {
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -359,17 +359,18 @@ class nsWindow::GeckoViewSupport final
 
   void AttachAccessibility(const GeckoSession::Window::LocalRef& inst,
                            jni::Object::Param aSessionAccessibility);
 
   void OnReady(jni::Object::Param aQueue = nullptr);
 
   auto OnLoadRequest(mozilla::jni::String::Param aUri, int32_t aWindowType,
                      int32_t aFlags, mozilla::jni::String::Param aTriggeringUri,
-                     bool aHasUserGesture) const -> java::GeckoResult::LocalRef;
+                     bool aHasUserGesture, bool aIsTopLevel) const
+      -> java::GeckoResult::LocalRef;
 };
 
 /**
  * PanZoomController handles its native calls on the UI thread, so make
  * it separate from GeckoViewSupport.
  */
 class nsWindow::NPZCSupport final
     : public java::PanZoomController::NativeProvider::Natives<NPZCSupport> {
@@ -1660,17 +1661,18 @@ void nsWindow::SetParent(nsIWidget* aNew
   // if we are now in the toplevel window's hierarchy, schedule a redraw
   if (FindTopLevel() == nsWindow::TopWindow()) RedrawAll();
 }
 
 nsIWidget* nsWindow::GetParent() { return mParent; }
 
 RefPtr<MozPromise<bool, bool, false>> nsWindow::OnLoadRequest(
     nsIURI* aUri, int32_t aWindowType, int32_t aFlags,
-    nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture) {
+    nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture,
+    bool aIsTopLevel) {
   if (!mGeckoViewSupport) {
     return MozPromise<bool, bool, false>::CreateAndResolve(false, __func__);
   }
   nsAutoCString spec, triggeringSpec;
   if (aUri) {
     aUri->GetDisplaySpec(spec);
   }
 
@@ -1685,17 +1687,18 @@ RefPtr<MozPromise<bool, bool, false>> ns
       if (triggeringUri) {
         triggeringUri->GetDisplaySpec(triggeringSpec);
       }
     }
   }
 
   auto geckoResult = mGeckoViewSupport->OnLoadRequest(
       spec.get(), aWindowType, aFlags,
-      isNullPrincipal ? nullptr : triggeringSpec.get(), aHasUserGesture);
+      isNullPrincipal ? nullptr : triggeringSpec.get(), aHasUserGesture,
+      aIsTopLevel);
   return geckoResult
              ? MozPromise<bool, bool, false>::FromGeckoResult(geckoResult)
              : nullptr;
 }
 
 float nsWindow::GetDPI() {
   float dpi = 160.0f;
 
@@ -2411,24 +2414,24 @@ void nsWindow::UpdateSafeAreaInsets(cons
 
   if (mAttachedWidgetListener) {
     mAttachedWidgetListener->SafeAreaInsetsChanged(aSafeAreaInsets);
   }
 }
 
 auto nsWindow::GeckoViewSupport::OnLoadRequest(
     mozilla::jni::String::Param aUri, int32_t aWindowType, int32_t aFlags,
-    mozilla::jni::String::Param aTriggeringUri, bool aHasUserGesture) const
-    -> java::GeckoResult::LocalRef {
+    mozilla::jni::String::Param aTriggeringUri, bool aHasUserGesture,
+    bool aIsTopLevel) const -> java::GeckoResult::LocalRef {
   GeckoSession::Window::LocalRef window(mGeckoViewWindow);
   if (!window) {
     return nullptr;
   }
   return window->OnLoadRequest(aUri, aWindowType, aFlags, aTriggeringUri,
-                               aHasUserGesture);
+                               aHasUserGesture, aIsTopLevel);
 }
 
 already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
   nsCOMPtr<nsIWidget> window = new nsWindow();
   return window.forget();
 }
 
 already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
--- a/widget/android/nsWindow.h
+++ b/widget/android/nsWindow.h
@@ -56,17 +56,18 @@ class nsWindow final : public nsBaseWidg
 
   NS_INLINE_DECL_REFCOUNTING_INHERITED(nsWindow, nsBaseWidget)
 
   static void InitNatives();
   void SetScreenId(uint32_t aScreenId) { mScreenId = aScreenId; }
   void OnGeckoViewReady();
   RefPtr<mozilla::MozPromise<bool, bool, false>> OnLoadRequest(
       nsIURI* aUri, int32_t aWindowType, int32_t aFlags,
-      nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture);
+      nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture,
+      bool aIsTopLevel);
 
  private:
   uint32_t mScreenId;
 
   // An Event subclass that guards against stale events.
   template <typename Lambda, bool IsStatic = Lambda::isStatic,
             typename InstanceType = typename Lambda::ThisArgType,
             class Impl = typename Lambda::TargetClass>