Commit 66a61d94 authored by Jean-Yves Avenard's avatar Jean-Yves Avenard
Browse files

Bug 1637869 - P2. Allow ParentProcessDocumentChannel to perform process...

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
parent 3943449a
Loading
Loading
Loading
Loading
+51 −42
Original line number Diff line number Diff line
@@ -8755,48 +8755,12 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,

  // 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;
      }
  // Similar check will be performed by the ParentProcessDocumentChannel if in
  // use.
  if (XRE_IsE10sParentProcess() &&
      !DocumentChannel::CanUseDocumentChannel(aLoadState) &&
      !CanLoadInParentProcess(aLoadState->URI())) {
    return NS_ERROR_FAILURE;
    } while (0);
  }

  // Whenever a top-level browsing context is navigated, the user agent MUST
@@ -8957,6 +8921,51 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
  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;
+2 −0
Original line number Diff line number Diff line
@@ -454,6 +454,8 @@ class nsDocShell final : public nsDocLoader,
    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();
+36 −22
Original line number Diff line number Diff line
@@ -10,22 +10,24 @@
#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"
@@ -35,12 +37,10 @@
#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"
@@ -536,6 +536,7 @@ bool DocumentLoadListener::Open(
  return true;
}

/* static */
bool DocumentLoadListener::OpenFromParent(
    dom::CanonicalBrowsingContext* aBrowsingContext,
    nsDocShellLoadState* aLoadState, uint64_t aOuterWindowId,
@@ -1162,6 +1163,7 @@ void DocumentLoadListener::SerializeRedirectData(
}

bool DocumentLoadListener::MaybeTriggerProcessSwitch() {
  MOZ_ASSERT(XRE_IsParentProcess());
  MOZ_DIAGNOSTIC_ASSERT(!mDoingProcessSwitch,
                        "Already in the middle of switching?");
  MOZ_DIAGNOSTIC_ASSERT(mChannel);
@@ -1185,6 +1187,12 @@ bool DocumentLoadListener::MaybeTriggerProcessSwitch() {
    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.
@@ -1234,11 +1242,9 @@ bool DocumentLoadListener::MaybeTriggerProcessSwitch() {
          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;
@@ -1249,11 +1255,6 @@ bool DocumentLoadListener::MaybeTriggerProcessSwitch() {
    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();
@@ -1266,7 +1267,13 @@ bool DocumentLoadListener::MaybeTriggerProcessSwitch() {
    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
@@ -1282,13 +1289,13 @@ bool DocumentLoadListener::MaybeTriggerProcessSwitch() {
    // 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 =
@@ -1300,7 +1307,7 @@ bool DocumentLoadListener::MaybeTriggerProcessSwitch() {

  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))) {
@@ -1308,9 +1315,12 @@ bool DocumentLoadListener::MaybeTriggerProcessSwitch() {
    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;
@@ -1321,7 +1331,7 @@ bool DocumentLoadListener::MaybeTriggerProcessSwitch() {
  }

  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.
@@ -1475,6 +1485,10 @@ DocumentLoadListener::RedirectToRealChannel(

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.
+15 −0
Original line number Diff line number Diff line
@@ -7,6 +7,8 @@

#include "ParentProcessDocumentChannel.h"

#include "mozilla/StaticPrefs_extensions.h"
#include "nsDocShell.h"
#include "nsIObserverService.h"

extern mozilla::LazyLogModule gDocumentChannelLog;
@@ -45,6 +47,19 @@ ParentProcessDocumentChannel::RedirectToRealChannel(
    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 =
+32 −21
Original line number Diff line number Diff line
@@ -10,6 +10,9 @@ 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,
@@ -567,6 +570,7 @@ var E10SUtils = {

  getRemoteTypeForPrincipal(
    aPrincipal,
    aOriginalURI,
    aMultiProcess,
    aRemoteSubframes,
    aPreferredRemoteType = DEFAULT_REMOTE_TYPE,
@@ -577,8 +581,14 @@ var E10SUtils = {
      return NOT_REMOTE;
    }

    // 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. In fact, we should never end up with one here!
      // principal.
      if (aPrincipal.isSystemPrincipal || aPrincipal.isExpandedPrincipal) {
        throw Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
      }
@@ -595,7 +605,7 @@ var E10SUtils = {
        }
        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.
@@ -603,8 +613,9 @@ var E10SUtils = {
      aCurrentPrincipal && aCurrentPrincipal.isContentPrincipal
        ? aCurrentPrincipal.URI
        : null;

    return E10SUtils.getRemoteTypeForURIObject(
      aPrincipal.URI,
      useOriginalURI ? aOriginalURI : aPrincipal.URI,
      aMultiProcess,
      aRemoteSubframes,
      aPreferredRemoteType,
@@ -812,7 +823,6 @@ var E10SUtils = {
    // 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) &&
@@ -844,7 +854,6 @@ var E10SUtils = {

    if (
      (aRemoteSubframes || documentChannel) &&
      remoteType != NOT_REMOTE &&
      wantRemoteType != NOT_REMOTE &&
      documentChannelPermittedForURI(aURI)
    ) {
@@ -880,10 +889,12 @@ var E10SUtils = {

    // 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)
    ) {
Loading