Bug 1637869 - P2. Allow ParentProcessDocumentChannel to perform process switching. r=nika,mattwoodrow
☠☠ backed out by 128a2c474755 ☠ ☠
authorJean-Yves Avenard <jyavenard@mozilla.com>
Fri, 22 May 2020 10:28:41 +0000
changeset 531631 d300b61ed89f7025a445e9bd04c3682ac65d7971
parent 531630 34389f9c86e41f0701991dd2d02729bbbedb8546
child 531632 75359ba23865c590d0a84b94a0073ba548c3962e
push id37441
push userapavel@mozilla.com
push dateFri, 22 May 2020 21:38:53 +0000
treeherdermozilla-central@d6abd35b54ad [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
@@ -8793,58 +8793,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());
@@ -8995,16 +8959,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
@@ -6,46 +6,46 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "DocumentLoadListener.h"
 
 #include "mozilla/AntiTrackingUtils.h"
 #include "mozilla/ContentBlockingAllowList.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 "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)
 
@@ -544,16 +544,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.
@@ -1170,16 +1171,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.
@@ -1193,16 +1195,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) {
@@ -1242,103 +1250,105 @@ 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 (RefPtr<nsHttpChannel> httpChannel = do_QueryObject(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;
@@ -1482,16 +1492,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
@@ -562,54 +562,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 +815,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 +846,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;
@@ -878,17 +884,16 @@ var E10SUtils = {
       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.
     if (
       (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);
 };