diff --git a/mobile/android/geckoview/api.txt b/mobile/android/geckoview/api.txt index d3ec6fe4b6a7..62ecd52b32b1 100644 --- a/mobile/android/geckoview/api.txt +++ b/mobile/android/geckoview/api.txt @@ -427,6 +427,7 @@ package org.mozilla.geckoview { method @AnyThread public void loadUri(@NonNull String); method @AnyThread public void loadUri(@NonNull String, int); method @AnyThread public void loadUri(@NonNull String, @Nullable String, int); + method @AnyThread public void loadUri(@NonNull String, @Nullable GeckoSession, int); method @AnyThread public void loadUri(@NonNull Uri); method @AnyThread public void loadUri(@NonNull Uri, int); method @AnyThread public void loadUri(@NonNull Uri, @Nullable Uri, int); diff --git 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 index 144ba439f72d..c9e752e993b2 100644 --- 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 @@ -1174,6 +1174,40 @@ class NavigationDelegateTest : BaseSessionTest() { equalTo(referrer)) } + @Test fun loadUriReferrerSession() { + val uri = "https://example.com/bar" + val referrer = "https://example.org/foo" + + sessionRule.session.loadUri(referrer) + sessionRule.session.waitForPageStop() + + val newSession = sessionRule.createOpenSession() + newSession.loadUri(uri, sessionRule.session, GeckoSession.LOAD_FLAGS_NONE) + newSession.waitForPageStop() + + assertThat("Referrer should match", + newSession.evaluateJS("document.referrer") as String, + equalTo(referrer)) + } + + @Test fun loadUriReferrerSessionFileUrl() { + val uri = "file:///system/etc/fonts.xml" + val referrer = "https://example.org" + + sessionRule.session.loadUri(referrer) + sessionRule.session.waitForPageStop() + + val newSession = sessionRule.createOpenSession() + newSession.loadUri(uri, sessionRule.session, GeckoSession.LOAD_FLAGS_NONE) + newSession.waitUntilCalled(object : Callbacks.NavigationDelegate { + @AssertCalled + override fun onLoadError(session: GeckoSession, uri: String?, error: WebRequestError): GeckoResult? { + return null + } + }) + } + + @Test(expected = GeckoResult.UncaughtException::class) fun onNewSession_doesNotAllowOpened() { // Disable popup blocker. diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java index f4522f16061f..eb06d21b4a6b 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java @@ -1534,7 +1534,7 @@ public class GeckoSession implements Parcelable { */ @AnyThread public void loadUri(final @NonNull String uri) { - loadUri(uri, null, LOAD_FLAGS_NONE); + loadUri(uri, (GeckoSession)null, LOAD_FLAGS_NONE); } /** @@ -1545,7 +1545,7 @@ public class GeckoSession implements Parcelable { */ @AnyThread public void loadUri(final @NonNull String uri, final @LoadFlags int flags) { - loadUri(uri, null, flags); + loadUri(uri, (GeckoSession)null, flags); } /** @@ -1563,7 +1563,29 @@ public class GeckoSession implements Parcelable { msg.putInt("flags", flags); if (referrer != null) { - msg.putString("referrer", referrer); + msg.putString("referrerUri", referrer); + } + mEventDispatcher.dispatch("GeckoView:LoadUri", msg); + } + + /** + * Load the given URI with the specified referrer and load type. This method will also do any + * applicable checks to ensure that the specified URI is both safe and allowable + * according to the referring GeckoSession. + * + * @param uri the URI to load + * @param referrer the referring GeckoSession, may be null + * @param flags the load flags to use, an OR-ed value of {@link #LOAD_FLAGS_NONE LOAD_FLAGS_*} + */ + @AnyThread + public void loadUri(final @NonNull String uri, final @Nullable GeckoSession referrer, + final @LoadFlags int flags) { + final GeckoBundle msg = new GeckoBundle(); + msg.putString("uri", uri); + msg.putInt("flags", flags); + + if (referrer != null) { + msg.putString("referrerSessionId", referrer.mId); } mEventDispatcher.dispatch("GeckoView:LoadUri", msg); } @@ -1584,7 +1606,7 @@ public class GeckoSession implements Parcelable { */ @AnyThread public void loadUri(final @NonNull Uri uri, final @LoadFlags int flags) { - loadUri(uri.toString(), null, flags); + loadUri(uri.toString(), (GeckoSession)null, flags); } /** @@ -1613,7 +1635,7 @@ public class GeckoSession implements Parcelable { throw new IllegalArgumentException("data cannot be null"); } - loadUri(createDataUri(data, mimeType), null, LOAD_FLAGS_NONE); + loadUri(createDataUri(data, mimeType), (GeckoSession)null, LOAD_FLAGS_NONE); } /** @@ -1629,7 +1651,7 @@ public class GeckoSession implements Parcelable { throw new IllegalArgumentException("data cannot be null"); } - loadUri(createDataUri(bytes, mimeType), null, LOAD_FLAGS_FORCE_ALLOW_DATA_URI); + loadUri(createDataUri(bytes, mimeType), (GeckoSession)null, LOAD_FLAGS_FORCE_ALLOW_DATA_URI); } /** diff --git 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 index 518c4ffcef98..2d66de944ecf 100644 --- 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 @@ -22,6 +22,10 @@ exclude: true manage opening/closing of the `GeckoSession` and instead leave that up to the app. It's also now allowed to call `setSession` with a closed `GeckoSession`. +- Added an overload of `GeckoSession.loadUri()` that accepts a referring `GeckoSession`. This should be used + when the URI we're loading originates from another page. A common example of this would be long pressing + a link and then opening that in a new `GeckoSession`. + ## v69 - Modified behavior of ['setAutomaticFontSizeAdjustment'][69.1] so that it no longer has any effect on ['setFontInflationEnabled'][69.2] @@ -363,4 +367,4 @@ exclude: true [65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String- [65.25]: ../GeckoResult.html -[api-version]: d770e67f7e5b87640574810468c76208ce4c1a43 +[api-version]: 1df14e65ca0dd11e84a014040c0fb3544478827f diff --git a/mobile/android/modules/geckoview/GeckoViewNavigation.jsm b/mobile/android/modules/geckoview/GeckoViewNavigation.jsm index 656bfa80086f..3221912d5c38 100644 --- a/mobile/android/modules/geckoview/GeckoViewNavigation.jsm +++ b/mobile/android/modules/geckoview/GeckoViewNavigation.jsm @@ -105,7 +105,7 @@ class GeckoViewNavigation extends GeckoViewModule { this.browser.gotoIndex(aData.index); break; case "GeckoView:LoadUri": - const { uri, referrer, flags } = aData; + const { uri, referrerUri, referrerSessionId, flags } = aData; let navFlags = 0; @@ -138,34 +138,72 @@ class GeckoViewNavigation extends GeckoViewModule { this.moduleManager.updateRemoteTypeForURI(uri); } - let parsedUri; - let triggeringPrincipal; - try { - parsedUri = Services.io.newURI(uri); - if ( - parsedUri.schemeIs("about") || - parsedUri.schemeIs("data") || - parsedUri.schemeIs("file") || - parsedUri.schemeIs("resource") || - parsedUri.schemeIs("moz-extension") - ) { - // Only allow privileged loading for certain URIs. - triggeringPrincipal = Services.scriptSecurityManager.createContentPrincipal( - parsedUri, - {} - ); - } - } catch (ignored) {} + let triggeringPrincipal, referrerInfo, csp; + if (referrerSessionId) { + const referrerWindow = Services.ww.getWindowByName( + referrerSessionId, + this.window + ); + triggeringPrincipal = referrerWindow.browser.contentPrincipal; + csp = referrerWindow.browser.csp; + + const referrerPolicy = referrerWindow.browser.referrerInfo + ? referrerWindow.browser.referrerInfo.referrerPolicy + : Ci.nsIHttpChannel.REFERRER_POLICY_UNSET; + + referrerInfo = new ReferrerInfo( + referrerPolicy, + true, + referrerWindow.browser.documentURI + ); + } else { + try { + const parsedUri = Services.io.newURI(uri); + if ( + parsedUri.schemeIs("about") || + parsedUri.schemeIs("data") || + parsedUri.schemeIs("file") || + parsedUri.schemeIs("resource") || + parsedUri.schemeIs("moz-extension") + ) { + // Only allow privileged loading for certain URIs. + triggeringPrincipal = Services.scriptSecurityManager.createContentPrincipal( + parsedUri, + {} + ); + } + } catch (ignored) {} + + referrerInfo = createReferrerInfo(referrerUri); + } + if (!triggeringPrincipal) { triggeringPrincipal = Services.scriptSecurityManager.createNullPrincipal( {} ); } - this.browser.loadURI(parsedUri ? parsedUri.spec : uri, { + // For any navigation here, we should have an appropriate triggeringPrincipal: + // + // 1) If we have a referring session, triggeringPrincipal is the contentPrincipal from the + // referring document. + // 2) For certain URI schemes listed above, we will have a codebase principal. + // 3) In all other cases, we create a NullPrincipal. + // + // The navigation flags are driven by the app. We purposely do not propagate these from + // the referring document, but expect that the app will in most cases. + // + // The referrerInfo is derived from the referring document, if present, by propagating any + // referrer policy. If we only have the referrerUri from the app, we create a referrerInfo + // with the specified URI and no policy set. If no referrerUri is present and we have no + // referring session, the referrerInfo is null. + // + // csp is only present if we have a referring document, null otherwise. + this.browser.loadURI(uri, { flags: navFlags, - referrerInfo: createReferrerInfo(referrer), + referrerInfo, triggeringPrincipal, + csp, }); break; case "GeckoView:Reload":