Bug 1637869 - P2. Allow ParentProcessDocumentChannel to perform process switching. r=nika,mattwoodrow
authorJean-Yves Avenard <jyavenard@mozilla.com>
Thu, 28 May 2020 00:07:39 +0000
changeset 596496 d08bb712835684001850881bbe70cdf6130f610e
parent 596495 e73a92c045b82e25a658e7966dd509f7a24eef53
child 596497 723a7cddf523a40b2d1b93e836a1e044b4ffb896
push id13186
push userffxbld-merge
push dateMon, 01 Jun 2020 09:52:46 +0000
treeherdermozilla-beta@3e7c70a1e4a1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnika, mattwoodrow
bugs1637869
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 1637869 - P2. Allow ParentProcessDocumentChannel to perform process switching. r=nika,mattwoodrow The moves all decisions to perform a process switch into the DocumentLoadListerner. This removes the unnecessary need to go via a content process to start the load. Differential Revision: https://phabricator.services.mozilla.com/D76315
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
netwerk/ipc/DocumentLoadListener.cpp
netwerk/ipc/ParentProcessDocumentChannel.cpp
toolkit/modules/E10SUtils.jsm
toolkit/modules/nsIE10SUtils.idl
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -8750,58 +8750,22 @@ nsresult nsDocShell::InternalLoad(nsDocS
         aLoadState->Csp(), &shouldLoad);
     if (NS_SUCCEEDED(rv) && !shouldLoad) {
       return NS_OK;
     }
   }
 
   // In e10s, in the parent process, we refuse to load anything other than
   // "safe" resources that we ship or trust enough to give "special" URLs.
-  if (XRE_IsE10sParentProcess()) {
-    nsCOMPtr<nsIURI> uri = aLoadState->URI();
-    do {
-      bool canLoadInParent = false;
-      if (NS_SUCCEEDED(NS_URIChainHasFlags(
-              uri, nsIProtocolHandler::URI_IS_UI_RESOURCE, &canLoadInParent)) &&
-          canLoadInParent) {
-        // We allow UI resources.
-        break;
-      }
-      // For about: and extension-based URIs, which don't get
-      // URI_IS_UI_RESOURCE, first remove layers of view-source:, if present.
-      while (uri && uri->SchemeIs("view-source")) {
-        nsCOMPtr<nsINestedURI> nested = do_QueryInterface(uri);
-        if (nested) {
-          nested->GetInnerURI(getter_AddRefs(uri));
-        } else {
-          break;
-        }
-      }
-      // Allow about: URIs, and allow moz-extension ones if we're running
-      // extension content in the parent process.
-      if (!uri || uri->SchemeIs("about") ||
-          (!StaticPrefs::extensions_webextensions_remote() &&
-           uri->SchemeIs("moz-extension"))) {
-        break;
-      }
-      nsAutoCString scheme;
-      uri->GetScheme(scheme);
-      // Allow ext+foo URIs (extension-registered custom protocols). See
-      // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/protocol_handlers
-      if (StringBeginsWith(scheme, NS_LITERAL_CSTRING("ext+")) &&
-          !StaticPrefs::extensions_webextensions_remote()) {
-        break;
-      }
-      // Final exception for some legacy automated tests:
-      if (xpc::IsInAutomation() &&
-          Preferences::GetBool("security.allow_unsafe_parent_loads", false)) {
-        break;
-      }
-      return NS_ERROR_FAILURE;
-    } while (0);
+  // Similar check will be performed by the ParentProcessDocumentChannel if in
+  // use.
+  if (XRE_IsE10sParentProcess() &&
+      !DocumentChannel::CanUseDocumentChannel(aLoadState) &&
+      !CanLoadInParentProcess(aLoadState->URI())) {
+    return NS_ERROR_FAILURE;
   }
 
   // Whenever a top-level browsing context is navigated, the user agent MUST
   // lock the orientation of the document to the document's default
   // orientation. We don't explicitly check for a top-level browsing context
   // here because orientation is only set on top-level browsing contexts.
   if (mBrowsingContext->GetOrientationLock() != hal::eScreenOrientation_None) {
     MOZ_ASSERT(mBrowsingContext->IsTop());
@@ -8952,16 +8916,61 @@ nsresult nsDocShell::InternalLoad(nsDocS
     if (NS_ERROR_UNKNOWN_PROTOCOL == rv) {
       return NS_OK;
     }
   }
 
   return rv;
 }
 
+/* static */
+bool nsDocShell::CanLoadInParentProcess(nsIURI* aURI) {
+  nsCOMPtr<nsIURI> uri = aURI;
+  // In e10s, in the parent process, we refuse to load anything other than
+  // "safe" resources that we ship or trust enough to give "special" URLs.
+  bool canLoadInParent = false;
+  if (NS_SUCCEEDED(NS_URIChainHasFlags(
+          uri, nsIProtocolHandler::URI_IS_UI_RESOURCE, &canLoadInParent)) &&
+      canLoadInParent) {
+    // We allow UI resources.
+    return true;
+  }
+  // For about: and extension-based URIs, which don't get
+  // URI_IS_UI_RESOURCE, first remove layers of view-source:, if present.
+  while (uri && uri->SchemeIs("view-source")) {
+    nsCOMPtr<nsINestedURI> nested = do_QueryInterface(uri);
+    if (nested) {
+      nested->GetInnerURI(getter_AddRefs(uri));
+    } else {
+      break;
+    }
+  }
+  // Allow about: URIs, and allow moz-extension ones if we're running
+  // extension content in the parent process.
+  if (!uri || uri->SchemeIs("about") ||
+      (!StaticPrefs::extensions_webextensions_remote() &&
+       uri->SchemeIs("moz-extension"))) {
+    return true;
+  }
+  nsAutoCString scheme;
+  uri->GetScheme(scheme);
+  // Allow ext+foo URIs (extension-registered custom protocols). See
+  // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/protocol_handlers
+  if (StringBeginsWith(scheme, NS_LITERAL_CSTRING("ext+")) &&
+      !StaticPrefs::extensions_webextensions_remote()) {
+    return true;
+  }
+  // Final exception for some legacy automated tests:
+  if (xpc::IsInAutomation() &&
+      Preferences::GetBool("security.allow_unsafe_parent_loads", false)) {
+    return true;
+  }
+  return false;
+}
+
 nsIPrincipal* nsDocShell::GetInheritedPrincipal(
     bool aConsiderCurrentDocument, bool aConsiderStoragePrincipal) {
   RefPtr<Document> document;
   bool inheritedFromCurrent = false;
 
   if (aConsiderCurrentDocument && mContentViewer) {
     document = mContentViewer->GetDocument();
     inheritedFromCurrent = true;
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -449,16 +449,18 @@ class nsDocShell final : public nsDocLoa
   static void CopyFavicon(nsIURI* aOldURI, nsIURI* aNewURI,
                           nsIPrincipal* aLoadingPrincipal,
                           bool aInPrivateBrowsing);
 
   static nsDocShell* Cast(nsIDocShell* aDocShell) {
     return static_cast<nsDocShell*>(aDocShell);
   }
 
+  static bool CanLoadInParentProcess(nsIURI* aURI);
+
   // Returns true if the current load is a force reload (started by holding
   // shift while triggering reload)
   bool IsForceReloading();
 
   mozilla::dom::WindowProxyHolder GetWindowProxy() {
     EnsureScriptEnvironment();
     return mozilla::dom::WindowProxyHolder(mBrowsingContext);
   }
--- a/netwerk/ipc/DocumentLoadListener.cpp
+++ b/netwerk/ipc/DocumentLoadListener.cpp
@@ -5,47 +5,47 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "DocumentLoadListener.h"
 
 #include "mozilla/AntiTrackingUtils.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/MozPromiseInlines.h"  // For MozPromise::FromDomPromise
+#include "mozilla/StaticPrefs_extensions.h"
 #include "mozilla/StaticPrefs_fission.h"
 #include "mozilla/StaticPrefs_security.h"
 #include "mozilla/dom/CanonicalBrowsingContext.h"
 #include "mozilla/dom/ClientChannelHelper.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ContentProcessManager.h"
+#include "mozilla/dom/SessionHistoryEntry.h"
 #include "mozilla/dom/WindowGlobalParent.h"
 #include "mozilla/dom/ipc/IdType.h"
 #include "mozilla/net/CookieJarSettings.h"
-#include "mozilla/dom/SessionHistoryEntry.h"
 #include "mozilla/net/HttpChannelParent.h"
 #include "mozilla/net/RedirectChannelRegistrar.h"
 #include "mozilla/net/UrlClassifierCommon.h"
 #include "nsContentSecurityUtils.h"
 #include "nsDocShell.h"
 #include "nsDocShellLoadState.h"
+#include "nsDocShellLoadTypes.h"
 #include "nsExternalHelperAppService.h"
 #include "nsHttpChannel.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIBrowser.h"
 #include "nsIE10SUtils.h"
 #include "nsIStreamConverterService.h"
 #include "nsIViewSourceChannel.h"
 #include "nsImportModule.h"
 #include "nsMimeTypes.h"
-#include "mozilla/dom/CanonicalBrowsingContext.h"
 #include "nsRedirectHistoryEntry.h"
+#include "nsSandboxFlags.h"
 #include "nsURILoader.h"
 #include "nsWebNavigationInfo.h"
-#include "nsDocShellLoadTypes.h"
-#include "nsSandboxFlags.h"
 
 #ifdef ANDROID
 #  include "mozilla/widget/nsWindow.h"
 #endif /* ANDROID */
 
 mozilla::LazyLogModule gDocumentChannelLog("DocumentChannel");
 #define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt)
 
@@ -531,16 +531,17 @@ bool DocumentLoadListener::Open(
   }
 
   if (auto* ctx = GetBrowsingContext()) {
     ctx->StartDocumentLoad(this);
   }
   return true;
 }
 
+/* static */
 bool DocumentLoadListener::OpenFromParent(
     dom::CanonicalBrowsingContext* aBrowsingContext,
     nsDocShellLoadState* aLoadState, uint64_t aOuterWindowId,
     uint32_t* aOutIdent) {
   LOG(("DocumentLoadListener::OpenFromParent"));
 
   // We currently only support passing nullptr for aLoadInfo for
   // top level browsing contexts.
@@ -1157,16 +1158,17 @@ void DocumentLoadListener::SerializeRedi
   if (mSessionHistoryInfo) {
     aArgs.sessionHistoryInfo().emplace(
         mSessionHistoryInfo->mId, MakeUnique<mozilla::dom::SessionHistoryInfo>(
                                       *mSessionHistoryInfo->mInfo));
   }
 }
 
 bool DocumentLoadListener::MaybeTriggerProcessSwitch() {
+  MOZ_ASSERT(XRE_IsParentProcess());
   MOZ_DIAGNOSTIC_ASSERT(!mDoingProcessSwitch,
                         "Already in the middle of switching?");
   MOZ_DIAGNOSTIC_ASSERT(mChannel);
   MOZ_DIAGNOSTIC_ASSERT(mParentChannelListener);
 
   LOG(("DocumentLoadListener MaybeTriggerProcessSwitch [this=%p]", this));
 
   // Get the BrowsingContext which will be switching processes.
@@ -1180,16 +1182,22 @@ bool DocumentLoadListener::MaybeTriggerP
     LOG(("Process Switch Abort: non-content browsing context"));
     return false;
   }
   if (browsingContext->GetParent() && !browsingContext->UseRemoteSubframes()) {
     LOG(("Process Switch Abort: remote subframes disabled"));
     return false;
   }
 
+  if (browsingContext->GetParentWindowContext() &&
+      browsingContext->GetParentWindowContext()->IsInProcess()) {
+    LOG(("Process Switch Abort: Subframe with in-process parent"));
+    return false;
+  }
+
   // We currently can't switch processes for toplevel loads unless they're
   // loaded within a browser tab.
   // FIXME: Ideally we won't do this in the future.
   nsCOMPtr<nsIBrowser> browser;
   bool isPreloadSwitch = false;
   if (!browsingContext->GetParent()) {
     Element* browserElement = browsingContext->GetEmbedderElement();
     if (!browserElement) {
@@ -1229,104 +1237,106 @@ bool DocumentLoadListener::MaybeTriggerP
   }
 
   // Get information about the current document loaded in our BrowsingContext.
   nsCOMPtr<nsIPrincipal> currentPrincipal;
   if (RefPtr<WindowGlobalParent> wgp =
           browsingContext->GetCurrentWindowGlobal()) {
     currentPrincipal = wgp->DocumentPrincipal();
   }
-  RefPtr<ContentParent> currentProcess = browsingContext->GetContentParent();
-  if (!currentProcess) {
-    LOG(("Process Switch Abort: frame currently not remote"));
-    return false;
-  }
+  RefPtr<ContentParent> contentParent = browsingContext->GetContentParent();
+  MOZ_ASSERT(!OtherPid() || contentParent,
+             "Only PPDC is allowed to not have an existing ContentParent");
 
   // Get the final principal, used to select which process to load into.
   nsCOMPtr<nsIPrincipal> resultPrincipal;
   nsresult rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
       mChannel, getter_AddRefs(resultPrincipal));
   if (NS_FAILED(rv)) {
     LOG(("Process Switch Abort: failed to get channel result principal"));
     return false;
   }
 
-  if (resultPrincipal->IsSystemPrincipal()) {
-    LOG(("Process Switch Abort: cannot switch process for system principal"));
-    return false;
-  }
-
   // Determine our COOP status, which will be used to determine our preferred
   // remote type.
   bool isCOOPSwitch = HasCrossOriginOpenerPolicyMismatch();
   nsILoadInfo::CrossOriginOpenerPolicy coop =
       nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
   if (!browsingContext->IsTop()) {
     coop = browsingContext->Top()->GetOpenerPolicy();
   } else if (nsCOMPtr<nsIHttpChannelInternal> httpChannel =
                  do_QueryInterface(mChannel)) {
     MOZ_ALWAYS_SUCCEEDS(httpChannel->GetCrossOriginOpenerPolicy(&coop));
   }
 
-  nsAutoString preferredRemoteType(currentProcess->GetRemoteType());
+  nsAutoString currentRemoteType;
+  if (contentParent) {
+    currentRemoteType = contentParent->GetRemoteType();
+  } else {
+    currentRemoteType = VoidString();
+  }
+  nsAutoString preferredRemoteType = currentRemoteType;
   if (coop ==
       nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP) {
     // We want documents with SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP COOP
     // policy to be loaded in a separate process in which we can enable
     // high-resolution timers.
     nsAutoCString siteOrigin;
     resultPrincipal->GetSiteOrigin(siteOrigin);
     preferredRemoteType.Assign(
         NS_LITERAL_STRING(WITH_COOP_COEP_REMOTE_TYPE_PREFIX));
     preferredRemoteType.Append(NS_ConvertUTF8toUTF16(siteOrigin));
   } else if (isCOOPSwitch) {
     // If we're doing a COOP switch, we do not need any affinity to the current
     // remote type. Clear it back to the default value.
     preferredRemoteType.Assign(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE));
   }
-  MOZ_DIAGNOSTIC_ASSERT(!preferredRemoteType.IsEmpty(),
+  MOZ_DIAGNOSTIC_ASSERT(!contentParent || !preferredRemoteType.IsEmpty(),
                         "Unexpected empty remote type!");
 
   LOG(
       ("DocumentLoadListener GetRemoteTypeForPrincipal "
-       "[this=%p, currentProcess=%s, preferredRemoteType=%s]",
-       this, NS_ConvertUTF16toUTF8(currentProcess->GetRemoteType()).get(),
+       "[this=%p, contentParent=%s, preferredRemoteType=%s]",
+       this, NS_ConvertUTF16toUTF8(currentRemoteType).get(),
        NS_ConvertUTF16toUTF8(preferredRemoteType).get()));
 
   nsCOMPtr<nsIE10SUtils> e10sUtils =
       do_ImportModule("resource://gre/modules/E10SUtils.jsm", "E10SUtils");
   if (!e10sUtils) {
     LOG(("Process Switch Abort: Could not import E10SUtils"));
     return false;
   }
 
   nsAutoString remoteType;
   rv = e10sUtils->GetRemoteTypeForPrincipal(
-      resultPrincipal, browsingContext->UseRemoteTabs(),
+      resultPrincipal, mChannelCreationURI, browsingContext->UseRemoteTabs(),
       browsingContext->UseRemoteSubframes(), preferredRemoteType,
       currentPrincipal, browsingContext->GetParent(), remoteType);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     LOG(("Process Switch Abort: getRemoteTypeForPrincipal threw an exception"));
     return false;
   }
 
+  LOG(("GetRemoteTypeForPrincipal -> current:%s remoteType:%s",
+       NS_ConvertUTF16toUTF8(currentRemoteType).get(),
+       NS_ConvertUTF16toUTF8(remoteType).get()));
+
   // Check if a process switch is needed.
-  if (currentProcess->GetRemoteType() == remoteType && !isCOOPSwitch &&
-      !isPreloadSwitch) {
+  if (currentRemoteType == remoteType && !isCOOPSwitch && !isPreloadSwitch) {
     LOG(("Process Switch Abort: type (%s) is compatible",
          NS_ConvertUTF16toUTF8(remoteType).get()));
     return false;
   }
   if (NS_WARN_IF(remoteType.IsEmpty())) {
     LOG(("Process Switch Abort: non-remote target process"));
     return false;
   }
 
   LOG(("Process Switch: Changing Remoteness from '%s' to '%s'",
-       NS_ConvertUTF16toUTF8(currentProcess->GetRemoteType()).get(),
+       NS_ConvertUTF16toUTF8(currentRemoteType).get(),
        NS_ConvertUTF16toUTF8(remoteType).get()));
 
   // XXX: This is super hacky, and we should be able to do something better.
   static uint64_t sNextCrossProcessRedirectIdentifier = 0;
   mCrossProcessRedirectIdentifier = ++sNextCrossProcessRedirectIdentifier;
   mDoingProcessSwitch = true;
 
   RefPtr<DocumentLoadListener> self = this;
@@ -1470,16 +1480,20 @@ DocumentLoadListener::RedirectToRealChan
         return PDocumentChannelParent::RedirectToRealChannelPromise::
             CreateAndReject(ipc::ResponseRejectReason::ActorDestroyed,
                             __func__);
       });
 }
 
 void DocumentLoadListener::TriggerRedirectToRealChannel(
     const Maybe<uint64_t>& aDestinationProcess) {
+  LOG((
+      "DocumentLoadListener::TriggerRedirectToRealChannel [this=%p] "
+      "aDestinationProcess=%" PRId64,
+      this, aDestinationProcess ? int64_t(*aDestinationProcess) : int64_t(-1)));
   // This initiates replacing the current DocumentChannel with a
   // protocol specific 'real' channel, maybe in a different process than
   // the current DocumentChannelChild, if aDestinationProces is set.
   // It registers the current mChannel with the registrar to get an ID
   // so that the remote end can setup a new IPDL channel and lookup
   // the same underlying channel.
   // We expect this process to finish with FinishReplacementChannelSetup
   // (for both in-process and process switch cases), where we cleanup
--- a/netwerk/ipc/ParentProcessDocumentChannel.cpp
+++ b/netwerk/ipc/ParentProcessDocumentChannel.cpp
@@ -2,16 +2,18 @@
 /* vim: set sw=2 ts=8 et tw=80 : */
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ParentProcessDocumentChannel.h"
 
+#include "mozilla/StaticPrefs_extensions.h"
+#include "nsDocShell.h"
 #include "nsIObserverService.h"
 
 extern mozilla::LazyLogModule gDocumentChannelLog;
 #define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt)
 
 namespace mozilla {
 namespace net {
 
@@ -40,16 +42,29 @@ ParentProcessDocumentChannel::RedirectTo
   nsCOMPtr<nsIChannel> channel = mDocumentLoadListener->GetChannel();
   channel->SetLoadFlags(aLoadFlags);
   channel->SetNotificationCallbacks(mCallbacks);
 
   if (mLoadGroup) {
     channel->SetLoadGroup(mLoadGroup);
   }
 
+  if (XRE_IsE10sParentProcess()) {
+    nsCOMPtr<nsIURI> uri;
+    MOZ_ALWAYS_SUCCEEDS(NS_GetFinalChannelURI(channel, getter_AddRefs(uri)));
+    if (!nsDocShell::CanLoadInParentProcess(uri)) {
+      nsAutoCString msg;
+      uri->GetSpec(msg);
+      msg.Insert(
+          "Attempt to load a non-authorised load in the parent process: ", 0);
+      NS_ASSERTION(false, msg.get());
+      return PDocumentChannelParent::RedirectToRealChannelPromise::
+          CreateAndResolve(NS_BINDING_ABORTED, __func__);
+    }
+  }
   mStreamFilterEndpoints = std::move(aStreamFilterEndpoints);
 
   RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise> p =
       mPromise.Ensure(__func__);
 
   nsresult rv =
       gHttpHandler->AsyncOnChannelRedirect(this, channel, aRedirectFlags);
   if (NS_FAILED(rv)) {
--- a/toolkit/modules/E10SUtils.jsm
+++ b/toolkit/modules/E10SUtils.jsm
@@ -5,16 +5,19 @@
 "use strict";
 
 var EXPORTED_SYMBOLS = ["E10SUtils"];
 
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
+const { AppConstants } = ChromeUtils.import(
+  "resource://gre/modules/AppConstants.jsm"
+);
 
 ChromeUtils.defineModuleGetter(
   this,
   "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm"
 );
 
 XPCOMUtils.defineLazyPreferenceGetter(
@@ -562,54 +565,62 @@ var E10SUtils = {
         );
         log.debug(`  validatedWebRemoteType() returning: ${remoteType}`);
         return remoteType;
     }
   },
 
   getRemoteTypeForPrincipal(
     aPrincipal,
+    aOriginalURI,
     aMultiProcess,
     aRemoteSubframes,
     aPreferredRemoteType = DEFAULT_REMOTE_TYPE,
     aCurrentPrincipal,
     aIsSubframe
   ) {
     if (!aMultiProcess) {
       return NOT_REMOTE;
     }
 
-    // We can't pick a process based on a system principal or expanded
-    // principal. In fact, we should never end up with one here!
-    if (aPrincipal.isSystemPrincipal || aPrincipal.isExpandedPrincipal) {
-      throw Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
-    }
+    // We want to use the original URI for "about:" and "chrome://" scheme,
+    // so that we can properly determine the remote type.
+    let useOriginalURI =
+      aOriginalURI.scheme == "about" || aOriginalURI.scheme == "chrome";
+
+    if (!useOriginalURI) {
+      // We can't pick a process based on a system principal or expanded
+      // principal.
+      if (aPrincipal.isSystemPrincipal || aPrincipal.isExpandedPrincipal) {
+        throw Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
+      }
 
-    // Null principals can be loaded in any remote process, but when
-    // using fission we add the option to force them into the default
-    // web process for better test coverage.
-    if (aPrincipal.isNullPrincipal) {
-      if (
-        (aRemoteSubframes && useSeparateDataUriProcess) ||
-        aPreferredRemoteType == NOT_REMOTE
-      ) {
-        return WEB_REMOTE_TYPE;
+      // Null principals can be loaded in any remote process, but when
+      // using fission we add the option to force them into the default
+      // web process for better test coverage.
+      if (aPrincipal.isNullPrincipal) {
+        if (
+          (aRemoteSubframes && useSeparateDataUriProcess) ||
+          aPreferredRemoteType == NOT_REMOTE
+        ) {
+          return WEB_REMOTE_TYPE;
+        }
+        return aPreferredRemoteType;
       }
-      return aPreferredRemoteType;
     }
-
     // We might care about the currently loaded URI. Pull it out of our current
     // principal. We never care about the current URI when working with a
     // non-content principal.
     let currentURI =
       aCurrentPrincipal && aCurrentPrincipal.isContentPrincipal
         ? aCurrentPrincipal.URI
         : null;
+
     return E10SUtils.getRemoteTypeForURIObject(
-      aPrincipal.URI,
+      useOriginalURI ? aOriginalURI : aPrincipal.URI,
       aMultiProcess,
       aRemoteSubframes,
       aPreferredRemoteType,
       currentURI,
       aPrincipal,
       aIsSubframe
     );
   },
@@ -807,17 +818,16 @@ var E10SUtils = {
       mustChangeProcess = true;
       newFrameloader = true;
     }
 
     // If we already have a content process, and the load will be
     // handled using DocumentChannel, then we can skip switching
     // for now, and let DocumentChannel do it during the response.
     if (
-      currentRemoteType != NOT_REMOTE &&
       requiredRemoteType != NOT_REMOTE &&
       uriObject &&
       (remoteSubframes || documentChannel) &&
       documentChannelPermittedForURI(uriObject)
     ) {
       mustChangeProcess = false;
       newFrameloader = false;
     }
@@ -839,17 +849,16 @@ var E10SUtils = {
       remoteType
     );
     this.log().info(
       `shouldLoadURIInThisProcess: have ${remoteType} want ${wantRemoteType}`
     );
 
     if (
       (aRemoteSubframes || documentChannel) &&
-      remoteType != NOT_REMOTE &&
       wantRemoteType != NOT_REMOTE &&
       documentChannelPermittedForURI(aURI)
     ) {
       // We can switch later with documentchannel.
       return true;
     }
 
     return remoteType == wantRemoteType;
@@ -875,20 +884,22 @@ var E10SUtils = {
       true,
       useRemoteSubframes,
       remoteType,
       webNav.currentURI
     );
 
     // If we are using DocumentChannel or remote subframes (fission), we
     // can start the load in the current process, and then perform the
-    // switch later-on using the nsIProcessSwitchRequestor mechanism.
+    // switch later-on using the DocumentLoadListener mechanism.
+    // This mechanism isn't available on Android/GeckoView at present (see bug
+    // 1640019).
     if (
+      AppConstants.MOZ_WIDGET_TOOLKIT != "android" &&
       (useRemoteSubframes || documentChannel) &&
-      remoteType != NOT_REMOTE &&
       wantRemoteType != NOT_REMOTE &&
       documentChannelPermittedForURI(aURI)
     ) {
       return true;
     }
 
     if (
       !aHasPostData &&
--- a/toolkit/modules/nsIE10SUtils.idl
+++ b/toolkit/modules/nsIE10SUtils.idl
@@ -2,36 +2,41 @@
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIPrincipal;
+interface nsIURI;
 
 /**
  * C++ exposed interface for the `E10SUtils` object from the
  * `resource://gre/modules/E10SUtils.jsm` module.
  */
 [scriptable, uuid(1e18680e-052d-4509-a17e-678f5c495e02)]
 interface nsIE10SUtils : nsISupports {
   /**
    * Determine what remote type should be used to load a document with the given
    * principal.
    *
    * @param aPrincipal  The result principal for the document being loaded.
+   * @param aChannelOriginalURI. The original URI being loaded
+   *                             (which isn't always the same as the Principal's
+   *                              URI)
    * @param aMultiProcess  Does the browser have remote tabs enabled.
    * @param aRemoteSubframes  Does the browser have remote subframes enabled.
    * @param aPreferredRemoteType  If multiple remote types are compatible with
    *                              the load, prefer staying in this remote type.
    * @param aCurrentPrincipal  The principal of the currently loaded document.
    * @param aIsSubframe  Is the process switch occuring in a subframe.
    *
    * @return  The remote type to complete this load in.
    */
   AString getRemoteTypeForPrincipal(in nsIPrincipal aPrincipal,
+                                    in nsIURI aChannelOriginalURI,
                                     in boolean aMultiProcess,
                                     in boolean aRemoteSubframes,
                                     in AString aPreferredRemoteType,
                                     in nsIPrincipal aCurrentPrincipal,
                                     in boolean aIsSubframe);
 };