Bug 1561079 - Add a `GeckoSession.loadUri()` overload that takes a referring `GeckoSession` r=geckoview-reviewers,ckerschb,esawin,agi
☠☠ backed out by 7e55309837b2 ☠ ☠
authorJames Willcox <snorp@snorp.net>
Wed, 24 Jul 2019 16:33:52 +0000
changeset 484056 1d1be67a595042cf5a22da98c7c6336d8a21c888
parent 484055 528a46ffe2af6714a22720e7fef5f110841eeb7e
child 484057 0d1eaf86253f2e00bacc7a18ab37068685da6adf
push id36339
push userdvarga@mozilla.com
push dateThu, 25 Jul 2019 03:53:31 +0000
treeherdermozilla-central@922be4adb708 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgeckoview-reviewers, ckerschb, esawin, agi
bugs1561079
milestone70.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 1561079 - Add a `GeckoSession.loadUri()` overload that takes a referring `GeckoSession` r=geckoview-reviewers,ckerschb,esawin,agi Differential Revision: https://phabricator.services.mozilla.com/D36526
mobile/android/geckoview/api.txt
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/modules/geckoview/GeckoViewNavigation.jsm
--- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -422,16 +422,17 @@ package org.mozilla.geckoview {
     method @AnyThread public void goForward();
     method @AnyThread public void gotoHistoryIndex(int);
     method @AnyThread public boolean isOpen();
     method @AnyThread public void loadData(@NonNull byte[], @Nullable String);
     method @AnyThread public void loadString(@NonNull String, @Nullable String);
     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);
     method @UiThread public void open(@NonNull GeckoRuntime);
     method @AnyThread public void readFromParcel(@NonNull Parcel);
     method @UiThread public void releaseDisplay(@NonNull GeckoDisplay);
     method @AnyThread public void reload();
     method @AnyThread public void restoreState(@NonNull GeckoSession.SessionState);
--- 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
@@ -1169,16 +1169,50 @@ class NavigationDelegateTest : BaseSessi
         sessionRule.session.loadUri(uri, referrer, GeckoSession.LOAD_FLAGS_NONE)
         sessionRule.session.waitForPageStop()
 
         assertThat("Referrer should match",
                    sessionRule.session.evaluateJS("document.referrer") as String,
                    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<String>? {
+                return null
+            }
+        })
+    }
+
+
     @Test(expected = GeckoResult.UncaughtException::class)
     fun onNewSession_doesNotAllowOpened() {
         // Disable popup blocker.
         sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to false))
 
         sessionRule.session.loadTestPath(NEW_SESSION_HTML_PATH)
         sessionRule.session.waitForPageStop()
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -1529,28 +1529,28 @@ public class GeckoSession implements Par
     public static final int LOAD_FLAGS_FORCE_ALLOW_DATA_URI = 1 << 5;
 
     /**
      * Load the given URI.
      * @param uri The URI of the resource to load.
      */
     @AnyThread
     public void loadUri(final @NonNull String uri) {
-        loadUri(uri, null, LOAD_FLAGS_NONE);
+        loadUri(uri, (GeckoSession)null, LOAD_FLAGS_NONE);
     }
 
     /**
      * Load the given URI with the specified referrer and load type.
      *
      * @param uri the URI to load
      * @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 @LoadFlags int flags) {
-        loadUri(uri, null, flags);
+        loadUri(uri, (GeckoSession)null, flags);
     }
 
     /**
      * Load the given URI with the specified referrer and load type.
      *
      * @param uri the URI to load
      * @param referrer the referrer, may be null
      * @param flags the load flags to use, an OR-ed value of {@link #LOAD_FLAGS_NONE LOAD_FLAGS_*}
@@ -1558,17 +1558,39 @@ public class GeckoSession implements Par
     @AnyThread
     public void loadUri(final @NonNull String uri, final @Nullable String referrer,
                         final @LoadFlags int flags) {
         final GeckoBundle msg = new GeckoBundle();
         msg.putString("uri", uri);
         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);
     }
 
     /**
      * Load the given URI.
      * @param uri The URI of the resource to load.
      */
@@ -1579,17 +1601,17 @@ public class GeckoSession implements Par
 
     /**
      * Load the given URI with the specified referrer and load type.
      * @param uri the URI to load
      * @param flags the load flags to use, an OR-ed value of {@link #LOAD_FLAGS_NONE LOAD_FLAGS_*}
      */
     @AnyThread
     public void loadUri(final @NonNull Uri uri, final @LoadFlags int flags) {
-        loadUri(uri.toString(), null, flags);
+        loadUri(uri.toString(), (GeckoSession)null, flags);
     }
 
     /**
      * Load the given URI with the specified referrer and load type.
      * @param uri the URI to load
      * @param referrer the Uri to use as the referrer
      * @param flags the load flags to use, an OR-ed value of {@link #LOAD_FLAGS_NONE LOAD_FLAGS_*}
      */
@@ -1608,33 +1630,33 @@ public class GeckoSession implements Par
      *
      */
     @AnyThread
     public void loadString(@NonNull final String data, @Nullable final String mimeType) {
         if (data == null) {
             throw new IllegalArgumentException("data cannot be null");
         }
 
-        loadUri(createDataUri(data, mimeType), null, LOAD_FLAGS_NONE);
+        loadUri(createDataUri(data, mimeType), (GeckoSession)null, LOAD_FLAGS_NONE);
     }
 
     /**
      * Load the specified bytes. Internally this is converted to a data URI.
      *
      * @param bytes    the data to load
      * @param mimeType the mime type of the data, e.g. video/mp4. May be null, in which
      *                 case the type is guessed.
      */
     @AnyThread
     public void loadData(@NonNull final byte[] bytes, @Nullable final String mimeType) {
         if (bytes == null) {
             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);
     }
 
     /**
      * Creates a data URI of of the form "data:&lt;mime type&gt;,&lt;base64-encoded data&gt;"
      * @param bytes the bytes that should be contained in the URL
      * @param mimeType optional mime type, e.g. text/plain
      * @return a URI String
      */
--- 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
@@ -17,16 +17,20 @@ exclude: true
 
 [70.1]: ../GeckoSessionSettings.Builder.html#contextId-java.lang.String-
 [70.2]: ../StorageController.html#clearDataForSessionContext-java.lang.String-
 
 - Removed `setSession(session, runtime)` from `GeckoView`. With this change, `GeckoView` will no longer
   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]
 
 - Add GeckoSession.LOAD_FLAGS_FORCE_ALLOW_DATA_URI
 
 [69.1]: ./GeckoRuntimeSettings.html#setAutomaticFontSizeAdjustment-boolean-
 [69.2]: ./GeckoRuntimeSettings.html#setFontInflationEnabled-boolean-
@@ -358,9 +362,9 @@ exclude: true
 [65.23]: ../GeckoSession.FinderResult.html
 
 - Update [`CrashReporter#sendCrashReport`][65.24] to return the crash ID as a
   [`GeckoResult<String>`][65.25].
 
 [65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
 [65.25]: ../GeckoResult.html
 
-[api-version]: d770e67f7e5b87640574810468c76208ce4c1a43
+[api-version]: 1df14e65ca0dd11e84a014040c0fb3544478827f
--- a/mobile/android/modules/geckoview/GeckoViewNavigation.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewNavigation.jsm
@@ -100,17 +100,17 @@ class GeckoViewNavigation extends GeckoV
         break;
       case "GeckoView:GoForward":
         this.browser.goForward();
         break;
       case "GeckoView:GotoHistoryIndex":
         this.browser.gotoIndex(aData.index);
         break;
       case "GeckoView:LoadUri":
-        const { uri, referrer, flags } = aData;
+        const { uri, referrerUri, referrerSessionId, flags } = aData;
 
         let navFlags = 0;
 
         // These need to match the values in GeckoSession.LOAD_FLAGS_*
         if (flags & (1 << 0)) {
           navFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
         }
 
@@ -133,44 +133,82 @@ class GeckoViewNavigation extends GeckoV
         if (flags & (1 << 5)) {
           navFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
         }
 
         if (this.settings.useMultiprocess) {
           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":
         // At the moment, GeckoView only supports one reload, which uses
         // nsIWebNavigation.LOAD_FLAGS_NONE flag, and the telemetry doesn't
         // do anything to differentiate reloads (i.e normal vs skip caches)
         // So whenever we add more reload methods, please make sure the
         // telemetry probe is adjusted