diff --git a/browser/base/content/tabbrowser.js b/browser/base/content/tabbrowser.js index 1c90f0444df6..2ccbfa4794f7 100644 --- a/browser/base/content/tabbrowser.js +++ b/browser/base/content/tabbrowser.js @@ -5671,51 +5671,6 @@ } } }, - - async performProcessSwitch( - aBrowser, - aRemoteType, - aSwitchId, - aReplaceBrowsingContext - ) { - E10SUtils.log().info( - `performing switch from ${aBrowser.remoteType} to ${aRemoteType}` - ); - - // Don't try to switch tabs before delayed startup is completed. - await window.delayedStartupPromise; - - // Perform a navigateAndRestore to trigger the process switch. - let tab = this.getTabForBrowser(aBrowser); - let loadArguments = { - newFrameloader: true, // Switch even if remoteType hasn't changed. - remoteType: aRemoteType, // Don't derive remoteType to switch to. - - // Information about which channel should be performing the load. - redirectLoadSwitchId: aSwitchId, - - // True if this is a process switch due to a policy mismatch, means we - // shouldn't preserve our browsing context. - replaceBrowsingContext: aReplaceBrowsingContext, - }; - - await SessionStore.navigateAndRestore(tab, loadArguments, -1); - - // If the process switch seems to have failed, send an error over to our - // caller, to give it a chance to kill our channel. - if ( - aBrowser.remoteType != aRemoteType || - !aBrowser.frameLoader || - !aBrowser.frameLoader.remoteTab - ) { - throw Cr.NS_ERROR_FAILURE; - } - - // Tell our caller to redirect the load into this newly created process. - let remoteTab = aBrowser.frameLoader.remoteTab; - E10SUtils.log().debug(`new tabID: ${remoteTab.tabId}`); - return remoteTab.contentProcessId; - }, }; /** diff --git a/browser/components/sessionstore/SessionStore.jsm b/browser/components/sessionstore/SessionStore.jsm index bcb6aad7f172..4557105583df 100644 --- a/browser/components/sessionstore/SessionStore.jsm +++ b/browser/components/sessionstore/SessionStore.jsm @@ -54,6 +54,7 @@ const OBSERVING = [ "browser:purge-session-history-for-domain", "idle-daily", "clear-origin-attributes-data", + "channel-on-may-change-process", ]; // XUL Window properties to (re)store @@ -518,6 +519,9 @@ var SessionStoreInternal = { // A counter to be used to generate a unique ID for each closed tab or window. _nextClosedId: 0, + // A monotonic value used to generate a unique ID for each process switch. + _switchIdMonotonic: 0, + // During the initial restore and setBrowserState calls tracks the number of // windows yet to be restored _restoreCount: -1, @@ -943,6 +947,9 @@ var SessionStoreInternal = { this._forgetTabsWithUserContextId(userContextId); } break; + case "channel-on-may-change-process": + this.onMayChangeProcess(aSubject); + break; } }, @@ -2912,6 +2919,237 @@ var SessionStoreInternal = { } }, + async _doTabProcessSwitch( + aBrowser, + aRemoteType, + aChannel, + aSwitchId, + aReplaceBrowsingContext + ) { + E10SUtils.log().info( + `performing switch from ${aBrowser.remoteType} to ${aRemoteType}` + ); + + // Don't try to switch tabs before delayed startup is completed. + await aBrowser.ownerGlobal.delayedStartupPromise; + + // Perform a navigateAndRestore to trigger the process switch. + let tab = aBrowser.ownerGlobal.gBrowser.getTabForBrowser(aBrowser); + let loadArguments = { + newFrameloader: true, // Switch even if remoteType hasn't changed. + remoteType: aRemoteType, // Don't derive remoteType to switch to. + + // Information about which channel should be performing the load. + redirectLoadSwitchId: aSwitchId, + + // True if this is a process switch due to a policy mismatch, means we + // shouldn't preserve our browsing context. + replaceBrowsingContext: aReplaceBrowsingContext, + }; + + await SessionStore.navigateAndRestore(tab, loadArguments, -1); + + // If the process switch seems to have failed, send an error over to our + // caller, to give it a chance to kill our channel. + if ( + aBrowser.remoteType != aRemoteType || + !aBrowser.frameLoader || + !aBrowser.frameLoader.remoteTab + ) { + throw Cr.NS_ERROR_FAILURE; + } + + // Tell our caller to redirect the load into this newly created process. + let remoteTab = aBrowser.frameLoader.remoteTab; + E10SUtils.log().debug(`new tabID: ${remoteTab.tabId}`); + return remoteTab.contentProcessId; + }, + + /** + * Perform a destructive process switch into a distinct process. + * This method is asynchronous, as it requires multiple calls into content + * processes. + */ + async _doProcessSwitch( + aBrowsingContext, + aRemoteType, + aChannel, + aSwitchId, + aReplaceBrowsingContext + ) { + // There are two relevant cases when performing a process switch for a + // browsing context: in-process and out-of-process embedders. + + // If our embedder is in-process (e.g. we're a xul:browser element embedded + // within ), then we can perform a process switch using the + // traditional mechanism. + if (aBrowsingContext.embedderElement) { + return this._doTabProcessSwitch( + aBrowsingContext.embedderElement, + aRemoteType, + aChannel, + aSwitchId, + aReplaceBrowsingContext + ); + } + + return aBrowsingContext.changeFrameRemoteness(aRemoteType, aSwitchId); + }, + + // Examine the channel response to see if we should change the process + // performing the given load. aRequestor implements nsIProcessSwitchRequestor + onMayChangeProcess(aRequestor) { + if (!E10SUtils.documentChannel()) { + throw new Error("This code is only used by document channel"); + } + + let switchRequestor; + try { + switchRequestor = aRequestor.QueryInterface(Ci.nsIProcessSwitchRequestor); + } catch (e) { + E10SUtils.log().warn(`object not compatible with process switching `); + return; + } + + const channel = switchRequestor.channel; + if (!channel.isDocument || !channel.loadInfo) { + return; // Not a document load. + } + + // Check that the document has a corresponding BrowsingContext. + let browsingContext = channel.loadInfo.targetBrowsingContext; + let isSubframe = + channel.loadInfo.externalContentPolicyType != + Ci.nsIContentPolicy.TYPE_DOCUMENT; + + if (!browsingContext) { + E10SUtils.log().debug(`no BrowsingContext - ignoring`); + return; + } + + // Determine if remote subframes should be used for this load. + let topBC = browsingContext.top; + if (!topBC.embedderElement) { + E10SUtils.log().debug(`no embedder for top - ignoring`); + return; + } + + let topDocShell = topBC.embedderElement.ownerGlobal.docShell; + let { useRemoteSubframes } = topDocShell.QueryInterface(Ci.nsILoadContext); + if (!useRemoteSubframes && isSubframe) { + E10SUtils.log().debug(`remote subframes disabled - ignoring`); + return; + } + + // Get principal for a document already loaded in the BrowsingContext. + let currentPrincipal = null; + if (browsingContext.currentWindowGlobal) { + currentPrincipal = browsingContext.currentWindowGlobal.documentPrincipal; + } + + // We can only perform a process switch on in-process frames if they are + // embedded within a normal tab. We can't do one of these swaps for a + // cross-origin frame. + if (browsingContext.embedderElement) { + let tabbrowser = browsingContext.embedderElement.getTabBrowser(); + if (!tabbrowser) { + E10SUtils.log().debug( + `cannot find tabbrowser for loading tab - ignoring` + ); + return; + } + + let tab = tabbrowser.getTabForBrowser(browsingContext.embedderElement); + if (!tab) { + E10SUtils.log().debug( + `not a normal tab, so cannot swap processes - ignoring` + ); + return; + } + } else if (!browsingContext.parent) { + E10SUtils.log().debug( + `no parent or in-process embedder element - ignoring` + ); + return; + } + + // Get the current remote type for the BrowsingContext. + let currentRemoteType = browsingContext.currentRemoteType; + if (currentRemoteType == E10SUtils.NOT_REMOTE) { + E10SUtils.log().debug(`currently not remote - ignoring`); + return; + } + + // Determine the process type the load should be performed in. + let resultPrincipal = Services.scriptSecurityManager.getChannelResultPrincipal( + channel + ); + + const isCOOPSwitch = + E10SUtils.useCrossOriginOpenerPolicy() && + switchRequestor.hasCrossOriginOpenerPolicyMismatch(); + + let preferredRemoteType = currentRemoteType; + if ( + E10SUtils.useCrossOriginOpenerPolicy() && + switchRequestor.crossOriginOpenerPolicy == + Ci.nsILoadInfo.OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP + ) { + // We want documents with a SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP + // COOP policy to be loaded in a separate process for which we can enable + // high resolution timers. + preferredRemoteType = + E10SUtils.WEB_REMOTE_COOP_COEP_TYPE_PREFIX + resultPrincipal.siteOrigin; + } else if (isCOOPSwitch) { + // If it is a coop switch, but doesn't have this flag, we want to switch + // to a default remoteType + preferredRemoteType = E10SUtils.DEFAULT_REMOTE_TYPE; + } + E10SUtils.log().info( + `currentRemoteType (${currentRemoteType}) preferredRemoteType: ${preferredRemoteType}` + ); + + let remoteType = E10SUtils.getRemoteTypeForPrincipal( + resultPrincipal, + true, + useRemoteSubframes, + preferredRemoteType, + currentPrincipal, + isSubframe + ); + + E10SUtils.log().debug( + `${currentRemoteType}, ${remoteType}, ${isCOOPSwitch}` + ); + + if (currentRemoteType == remoteType && !isCOOPSwitch) { + E10SUtils.log().debug(`type (${remoteType}) is compatible - ignoring`); + return; + } + + if ( + remoteType == E10SUtils.NOT_REMOTE || + currentRemoteType == E10SUtils.NOT_REMOTE + ) { + E10SUtils.log().debug(`non-remote source/target - ignoring`); + return; + } + + // ------------------------------------------------------------------------ + // DANGER ZONE: Perform a process switch into the new process. This is + // destructive. + // ------------------------------------------------------------------------ + let identifier = ++this._switchIdMonotonic; + let tabPromise = this._doProcessSwitch( + browsingContext, + remoteType, + channel, + identifier, + isCOOPSwitch + ); + switchRequestor.switchProcessTo(tabPromise, identifier); + }, + /* ........ nsISessionStore API .............. */ getBrowserState: function ssi_getBrowserState() { diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp index 04c90d702cd4..7fbfd78c271f 100644 --- a/docshell/base/CanonicalBrowsingContext.cpp +++ b/docshell/base/CanonicalBrowsingContext.cpp @@ -501,6 +501,25 @@ CanonicalBrowsingContext::ChangeFrameRemoteness(const nsAString& aRemoteType, return promise.forget(); } +already_AddRefed CanonicalBrowsingContext::ChangeFrameRemoteness( + const nsAString& aRemoteType, uint64_t aPendingSwitchId, ErrorResult& aRv) { + nsIGlobalObject* global = xpc::NativeGlobal(xpc::PrivilegedJunkScope()); + + RefPtr promise = Promise::Create(global, aRv); + if (aRv.Failed()) { + return nullptr; + } + + ChangeFrameRemoteness(aRemoteType, aPendingSwitchId) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [promise](BrowserParent* aBrowserParent) { + promise->MaybeResolve(aBrowserParent->Manager()->ChildID()); + }, + [promise](nsresult aRv) { promise->MaybeReject(aRv); }); + return promise.forget(); +} + MediaController* CanonicalBrowsingContext::GetMediaController() { // We would only create one media controller per tab, so accessing the // controller via the top-level browsing context. diff --git a/docshell/base/CanonicalBrowsingContext.h b/docshell/base/CanonicalBrowsingContext.h index 325057fef764..cd1230655052 100644 --- a/docshell/base/CanonicalBrowsingContext.h +++ b/docshell/base/CanonicalBrowsingContext.h @@ -102,6 +102,12 @@ class CanonicalBrowsingContext final : public BrowsingContext { RefPtr ChangeFrameRemoteness(const nsAString& aRemoteType, uint64_t aPendingSwitchId); + // Helper version for WebIDL - resolves to the PID where the load is being + // resumed. + already_AddRefed ChangeFrameRemoteness(const nsAString& aRemoteType, + uint64_t aPendingSwitchId, + ErrorResult& aRv); + // Return a media controller from the top-level browsing context that can // control all media belonging to this browsing context tree. Return nullptr // if the top-level browsing context has been discarded. diff --git a/dom/chrome-webidl/BrowsingContext.webidl b/dom/chrome-webidl/BrowsingContext.webidl index bf61f2b1d520..4ab0ccf5a27c 100644 --- a/dom/chrome-webidl/BrowsingContext.webidl +++ b/dom/chrome-webidl/BrowsingContext.webidl @@ -98,6 +98,10 @@ interface CanonicalBrowsingContext : BrowsingContext { [Throws] void loadURI(DOMString aURI, optional LoadURIOptions aOptions = {}); + [Throws] + Promise changeFrameRemoteness( + DOMString remoteType, unsigned long long pendingSwitchId); + readonly attribute nsISHistory? sessionHistory; }; diff --git a/dom/interfaces/base/nsIBrowser.idl b/dom/interfaces/base/nsIBrowser.idl index 01efc36b6d01..242c6b98cc4f 100644 --- a/dom/interfaces/base/nsIBrowser.idl +++ b/dom/interfaces/base/nsIBrowser.idl @@ -173,40 +173,4 @@ interface nsIBrowser : nsISupports void updateSecurityUIForSecurityChange(in nsITransportSecurityInfo aSecurityInfo, in uint32_t aState, in boolean aIsSecureContext); - - /** - * Called by Gecko when it wants to change the process which is currently - * being used to render content in this tab. - * - * Returns a promise which will be resolved with the ChildID of the new - * process. - * - * @param aRemoteType the new remote type to switch this browser into - * @param aPendingSwitchId unique identifier for the ongoing - * process-switching load - * @param aReplaceBrowsingContext if true, do not preserve the - * BrowsingContext loaded inside this - * browser after changing processes. - */ - Promise performProcessSwitch(in AString aRemoteType, - in uint64_t aPendingSwitchId, - in boolean aReplaceBrowsingContext); - - /** If `true`, this browser supports the `performProcessSwitch` method */ - readonly attribute bool canPerformProcessSwitch; - - /** - * Helper method for invoking `E10SUtils.getRemoteTypeForPrincipal` from - * `DocumentLoadListener`. - * - * FIXME: DocumentLoadListener should make process selection decisions without - * an `nsIBrowser`, as the implementation does not depend on which browser - * it's being called on. - */ - AString getRemoteTypeForPrincipal(in nsIPrincipal aPrincipal, - in boolean aRemoteTabs, - in boolean aRemoteSubframes, - in AString aPreferredRemoteType, - in nsIPrincipal aCurrentPrincipal, - in boolean aIsSubframe); }; diff --git a/netwerk/base/moz.build b/netwerk/base/moz.build index 6513f24b94e9..948646ad0f26 100644 --- a/netwerk/base/moz.build +++ b/netwerk/base/moz.build @@ -75,6 +75,7 @@ XPIDL_SOURCES += [ 'nsIPermission.idl', 'nsIPermissionManager.idl', 'nsIPrivateBrowsingChannel.idl', + 'nsIProcessSwitchRequestor.idl', 'nsIProgressEventSink.idl', 'nsIPrompt.idl', 'nsIProtocolHandler.idl', diff --git a/netwerk/base/nsIProcessSwitchRequestor.idl b/netwerk/base/nsIProcessSwitchRequestor.idl new file mode 100644 index 000000000000..3656485ee597 --- /dev/null +++ b/netwerk/base/nsIProcessSwitchRequestor.idl @@ -0,0 +1,52 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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 "nsIChannel.idl" + +/** + * The nsIProcessSwitchRequestor interface allows clients to instruct + * SessionStore.jsm that a channel setup has completed and a process switch + * may be required. This works alongside the on-may-change-process observer + * notification. + * This interface must be used only from the XPCOM main thread. + */ +[scriptable, uuid(fce8497b-c57c-4557-b360-3efefc83eff5)] +interface nsIProcessSwitchRequestor : nsISupports +{ + /** + * The underlying channel object that was intercepted and that could trigger + * a process. + */ + readonly attribute nsIChannel channel; + + /** + * Instructs the callee to be loaded in a new process. Like 'redirectTo' + * this can only be used on channels that have not yet called their + * listener's OnStartRequest(). Can only be called during the + * channel-on-may-change-process observer notification. + * + * @param aTabPromise a promise which resolves to a nsIRemotTab object + * which the load will proceed in. + * @param aIdentifier a 64-bit ID which will be provided to the + * ChildProcessChannelListener. + */ + [must_use] void switchProcessTo(in Promise aTabPromise, + in unsigned long long aIdentifier); + + /** + * Used to determine if there is a Cross-Origin-Opener-Policy mismatch + * that would require switching the channel to another process. + * @throws NS_ERROR_NOT_AVAILABLE if we don't have a responseHead + */ + [must_use] boolean hasCrossOriginOpenerPolicyMismatch(); + + /** + * Returns a cached CrossOriginOpenerPolicy that is computed just before we + * determine if there is a policy mismatch. + * @throws NS_ERROR_NOT_AVAILABLE if called before onStartRequest + */ + [must_use, binaryname(CachedCrossOriginOpenerPolicy)] readonly attribute nsILoadInfo_CrossOriginOpenerPolicy crossOriginOpenerPolicy; +}; diff --git a/netwerk/ipc/DocumentLoadListener.cpp b/netwerk/ipc/DocumentLoadListener.cpp index ff4ee147788c..d4794a1191eb 100644 --- a/netwerk/ipc/DocumentLoadListener.cpp +++ b/netwerk/ipc/DocumentLoadListener.cpp @@ -39,7 +39,6 @@ #include "nsIOService.h" #include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/StaticPrefs_security.h" -#include "nsIBrowser.h" mozilla::LazyLogModule gDocumentChannelLog("DocumentChannel"); #define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt) @@ -233,6 +232,7 @@ NS_INTERFACE_MAP_BEGIN(DocumentLoadListener) NS_INTERFACE_MAP_ENTRY(nsIParentChannel) NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectReadyCallback) NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink) + NS_INTERFACE_MAP_ENTRY(nsIProcessSwitchRequestor) NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener) NS_INTERFACE_MAP_ENTRY_CONCRETE(DocumentLoadListener) NS_INTERFACE_MAP_END @@ -826,187 +826,26 @@ void DocumentLoadListener::SerializeRedirectData( aArgs.loadStateLoadType() = mLoadStateLoadType; } -bool DocumentLoadListener::MaybeTriggerProcessSwitch() { - MOZ_DIAGNOSTIC_ASSERT(!mDoingProcessSwitch, - "Already in the middle of switching?"); - MOZ_DIAGNOSTIC_ASSERT(mChannel); - MOZ_DIAGNOSTIC_ASSERT(mParentChannelListener); +void DocumentLoadListener::TriggerCrossProcessSwitch() { + MOZ_ASSERT(mRedirectContentProcessIdPromise); + MOZ_ASSERT(!mDoingProcessSwitch, "Already in the middle of switching?"); + MOZ_ASSERT(NS_IsMainThread()); - LOG(("DocumentLoadListener MaybeTriggerProcessSwitch [this=%p]", this)); + LOG(("DocumentLoadListener TriggerCrossProcessSwitch [this=%p]", this)); - // Get the BrowsingContext which will be switching processes. - RefPtr browsingContext = - mParentChannelListener->GetBrowsingContext(); - if (NS_WARN_IF(!browsingContext)) { - LOG(("Process Switch Abort: no browsing context")); - return false; - } - if (!browsingContext->IsContent()) { - LOG(("Process Switch Abort: non-content browsing context")); - return false; - } - if (browsingContext->GetParent() && !browsingContext->UseRemoteSubframes()) { - LOG(("Process Switch Abort: remote subframes disabled")); - return false; - } - - // We currently can't switch processes for toplevel loads unless they're - // loaded within a browser tab. We also enforce this for non-toplevel tabs, as - // otherwise cross-origin subframes could get out of sync. - // FIXME: Ideally we won't do this in the future. - Element* browserElement = browsingContext->Top()->GetEmbedderElement(); - if (!browserElement) { - LOG(("Process Switch Abort: cannot get browser element")); - return false; - } - nsCOMPtr browser = browserElement->AsBrowser(); - if (!browser) { - LOG(("Process Switch Abort: not loaded within nsIBrowser")); - return false; - } - bool loadedInTab = false; - if (NS_FAILED(browser->GetCanPerformProcessSwitch(&loadedInTab)) || - !loadedInTab) { - LOG(("Process Switch Abort: browser is not loaded in a tab")); - return false; - } - - // Get information about the current document loaded in our BrowsingContext. - nsCOMPtr currentPrincipal; - if (RefPtr wgp = - browsingContext->GetCurrentWindowGlobal()) { - currentPrincipal = wgp->DocumentPrincipal(); - } - RefPtr currentProcess = browsingContext->GetContentParent(); - if (!currentProcess) { - LOG(("Process Switch Abort: frame currently not remote")); - return false; - } - - // Get the final principal, used to select which process to load into. - nsCOMPtr 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 (RefPtr httpChannel = do_QueryObject(mChannel)) { - MOZ_ALWAYS_SUCCEEDS(httpChannel->GetCrossOriginOpenerPolicy(&coop)); - } - - nsAutoString preferredRemoteType(currentProcess->GetRemoteType()); - 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(), - "Unexpected empty remote type!"); - - LOG( - ("DocumentLoadListener GetRemoteTypeForPrincipal " - "[this=%p, currentProcess=%s, preferredRemoteType=%s]", - this, NS_ConvertUTF16toUTF8(currentProcess->GetRemoteType()).get(), - NS_ConvertUTF16toUTF8(preferredRemoteType).get())); - - nsAutoString remoteType; - rv = browser->GetRemoteTypeForPrincipal( - resultPrincipal, 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; - } - - // Check if a process switch is needed. - if (currentProcess->GetRemoteType() == remoteType && !isCOOPSwitch) { - 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(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 self = this; - // At this point, we're actually going to perform a process switch, which - // involves calling into other logic. - if (browsingContext->GetParent()) { - LOG(("Process Switch: Calling ChangeFrameRemoteness")); - // If we're switching a subframe, ask BrowsingContext to do it for us. - MOZ_ASSERT(!isCOOPSwitch); - browsingContext - ->ChangeFrameRemoteness(remoteType, mCrossProcessRedirectIdentifier) - ->Then( - GetMainThreadSerialEventTarget(), __func__, - [self](BrowserParent* aBrowserParent) { - MOZ_ASSERT(self->mChannel, - "Something went wrong, channel got cancelled"); - self->TriggerRedirectToRealChannel( - Some(aBrowserParent->Manager()->ChildID())); - }, - [self](nsresult aStatusCode) { - MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error"); - self->RedirectToRealChannelFinished(aStatusCode); - }); - return true; - } - - LOG(("Process Switch: Calling nsIBrowser::PerformProcessSwitch")); - // We're switching a toplevel BrowsingContext's process. This has to be done - // using nsIBrowser. - RefPtr domPromise; - browser->PerformProcessSwitch(remoteType, mCrossProcessRedirectIdentifier, - isCOOPSwitch, getter_AddRefs(domPromise)); - MOZ_DIAGNOSTIC_ASSERT(domPromise, - "PerformProcessSwitch didn't return a promise"); - - MozPromise::FromDomPromise(domPromise) - ->Then( - GetMainThreadSerialEventTarget(), __func__, - [self](uint64_t aCpId) { - MOZ_ASSERT(self->mChannel, - "Something went wrong, channel got cancelled"); - self->TriggerRedirectToRealChannel(Some(aCpId)); - }, - [self](nsresult aStatusCode) { - MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error"); - self->RedirectToRealChannelFinished(aStatusCode); - }); - return true; + mRedirectContentProcessIdPromise->Then( + GetMainThreadSerialEventTarget(), __func__, + [self, this](uint64_t aCpId) { + MOZ_ASSERT(mChannel, "Something went wrong, channel got cancelled"); + TriggerRedirectToRealChannel(Some(aCpId)); + }, + [self](nsresult aStatusCode) { + MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error"); + self->RedirectToRealChannelFinished(aStatusCode); + }); } RefPtr @@ -1145,10 +984,18 @@ DocumentLoadListener::OnStartRequest(nsIRequest* aRequest) { httpChannel->SetApplyConversion(false); } - // Determine if a new process needs to be spawned. If it does, this will - // trigger a cross process switch, and we should hold off on redirecting to - // the real channel. - if (status != NS_BINDING_ABORTED && MaybeTriggerProcessSwitch()) { + // notify "channel-on-may-change-process" observers which is typically + // SessionStore.jsm. This will determine if a new process needs to be + // spawned and if so SwitchProcessTo() will be called which will set a + // ContentProcessIdPromise. + if (status != NS_BINDING_ABORTED) { + nsCOMPtr obsService = services::GetObserverService(); + obsService->NotifyObservers(ToSupports(this), + "channel-on-may-change-process", nullptr); + } + + if (mRedirectContentProcessIdPromise) { + TriggerCrossProcessSwitch(); return NS_OK; } @@ -1308,8 +1155,10 @@ DocumentLoadListener::AsyncOnChannelRedirect( // include the state of all channels we redirected through. RefPtr httpChannel = do_QueryObject(aOldChannel); if (httpChannel) { - mHasCrossOriginOpenerPolicyMismatch |= - httpChannel->HasCrossOriginOpenerPolicyMismatch(); + bool mismatch = false; + MOZ_ALWAYS_SUCCEEDS( + httpChannel->HasCrossOriginOpenerPolicyMismatch(&mismatch)); + mHasCrossOriginOpenerPolicyMismatch |= mismatch; } // We don't need to confirm internal redirects or record any @@ -1394,22 +1243,70 @@ DocumentLoadListener::AsyncOnChannelRedirect( return NS_OK; } +//----------------------------------------------------------------------------- +// DocumentLoadListener::nsIProcessSwitchRequestor +//----------------------------------------------------------------------------- + +NS_IMETHODIMP DocumentLoadListener::GetChannel(nsIChannel** aChannel) { + MOZ_ASSERT(mChannel); + nsCOMPtr channel(mChannel); + channel.forget(aChannel); + return NS_OK; +} + +NS_IMETHODIMP DocumentLoadListener::SwitchProcessTo( + dom::Promise* aContentProcessIdPromise, uint64_t aIdentifier) { + MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG(aContentProcessIdPromise); + + mRedirectContentProcessIdPromise = + ContentProcessIdPromise::FromDomPromise(aContentProcessIdPromise); + mCrossProcessRedirectIdentifier = aIdentifier; + return NS_OK; +} + // This method returns the cached result of running the Cross-Origin-Opener // policy compare algorithm by calling ComputeCrossOriginOpenerPolicyMismatch -bool DocumentLoadListener::HasCrossOriginOpenerPolicyMismatch() { +NS_IMETHODIMP +DocumentLoadListener::HasCrossOriginOpenerPolicyMismatch(bool* aMismatch) { + MOZ_ASSERT(aMismatch); + + if (!aMismatch) { + return NS_ERROR_INVALID_ARG; + } + // If we found a COOP mismatch on an earlier channel and then // redirected away from that, we should use that result. if (mHasCrossOriginOpenerPolicyMismatch) { - return true; + *aMismatch = true; + return NS_OK; } RefPtr httpChannel = do_QueryObject(mChannel); if (!httpChannel) { // Not an nsHttpChannel assume it's okay to switch. - return false; + *aMismatch = false; + return NS_OK; } - return httpChannel->HasCrossOriginOpenerPolicyMismatch(); + return httpChannel->HasCrossOriginOpenerPolicyMismatch(aMismatch); +} + +NS_IMETHODIMP +DocumentLoadListener::GetCachedCrossOriginOpenerPolicy( + nsILoadInfo::CrossOriginOpenerPolicy* aPolicy) { + MOZ_ASSERT(aPolicy); + if (!aPolicy) { + return NS_ERROR_INVALID_ARG; + } + + RefPtr httpChannel = do_QueryObject(mChannel); + if (!httpChannel) { + *aPolicy = nsILoadInfo::OPENER_POLICY_UNSAFE_NONE; + return NS_OK; + } + + return httpChannel->GetCrossOriginOpenerPolicy(aPolicy); } } // namespace net diff --git a/netwerk/ipc/DocumentLoadListener.h b/netwerk/ipc/DocumentLoadListener.h index bda10a88c2aa..ee78fbdbd79e 100644 --- a/netwerk/ipc/DocumentLoadListener.h +++ b/netwerk/ipc/DocumentLoadListener.h @@ -20,6 +20,7 @@ #include "nsIObserver.h" #include "nsIParentChannel.h" #include "nsIParentRedirectingChannel.h" +#include "nsIProcessSwitchRequestor.h" #include "nsIRedirectResultListener.h" #include "nsIMultiPartChannel.h" @@ -59,6 +60,7 @@ class DocumentLoadListener : public nsIInterfaceRequestor, public nsIParentChannel, public nsIChannelEventSink, public HttpChannelSecurityWarningReporter, + public nsIProcessSwitchRequestor, public nsIMultiPartChannelListener { public: explicit DocumentLoadListener(dom::CanonicalBrowsingContext* aBrowsingContext, @@ -79,6 +81,7 @@ class DocumentLoadListener : public nsIInterfaceRequestor, NS_DECL_NSIINTERFACEREQUESTOR NS_DECL_NSIASYNCVERIFYREDIRECTREADYCALLBACK NS_DECL_NSICHANNELEVENTSINK + NS_DECL_NSIPROCESSSWITCHREQUESTOR NS_DECL_NSIMULTIPARTCHANNELLISTENER // We suspend the underlying channel when replacing ourselves with @@ -172,10 +175,10 @@ class DocumentLoadListener : public nsIInterfaceRequestor, // by us, and resumes the underlying source channel. void FinishReplacementChannelSetup(bool aSucceeded); - // Called from `OnStartRequest` to make the decision about whether or not to - // change process. This method will return `nullptr` if the current target - // process is appropriate. - bool MaybeTriggerProcessSwitch(); + // Called when we have a cross-process switch promise. Waits on the + // promise, and then call TriggerRedirectToRealChannel with the + // provided content process id. + void TriggerCrossProcessSwitch(); // A helper for TriggerRedirectToRealChannel that abstracts over // the same-process and cross-process switch cases and returns @@ -191,8 +194,6 @@ class DocumentLoadListener : public nsIInterfaceRequestor, dom::CanonicalBrowsingContext* aBrowsingContext, nsDocShellLoadState* aLoadState, uint64_t aOuterWindowId); - bool HasCrossOriginOpenerPolicyMismatch(); - // This defines a variant that describes all the attribute setters (and their // parameters) from nsIParentChannel // @@ -345,8 +346,16 @@ class DocumentLoadListener : public nsIInterfaceRequestor, // channel. bool mIsFinished = false; - // This identifier is set by MaybeTriggerProcessSwitch, and is later - // passed to the childChannel in order to identify it in the new process. + typedef MozPromise + ContentProcessIdPromise; + // This promise is set following a on-may-change-process observer + // notification when the associated channel is getting relocated to another + // process. It will be resolved when that process is set up. + RefPtr mRedirectContentProcessIdPromise; + // This identifier is set at the same time as the + // mRedirectContentProcessIdPromise. + // This identifier is later passed to the childChannel in order to identify it + // once the promise is resolved. uint64_t mCrossProcessRedirectIdentifier = 0; }; diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 7afc2b03bf3e..3a478094af3d 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -6215,6 +6215,7 @@ NS_INTERFACE_MAP_BEGIN(nsHttpChannel) NS_INTERFACE_MAP_ENTRY(nsITimerCallback) NS_INTERFACE_MAP_ENTRY(nsIChannelWithDivertableParentListener) NS_INTERFACE_MAP_ENTRY(nsIRequestTailUnblockCallback) + NS_INTERFACE_MAP_ENTRY(nsIProcessSwitchRequestor) NS_INTERFACE_MAP_ENTRY_CONCRETE(nsHttpChannel) NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel) @@ -7394,6 +7395,59 @@ nsHttpChannel::GetRequestMethod(nsACString& aMethod) { return HttpBaseChannel::GetRequestMethod(aMethod); } +//----------------------------------------------------------------------------- +// nsHttpChannel::nsIProcessSwitchRequestor +//----------------------------------------------------------------------------- + +NS_IMETHODIMP nsHttpChannel::GetChannel(nsIChannel** aChannel) { + *aChannel = do_AddRef(this).take(); + return NS_OK; +} + +NS_IMETHODIMP nsHttpChannel::SwitchProcessTo( + dom::Promise* aContentProcessIdPromise, uint64_t aIdentifier) { + MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG(aContentProcessIdPromise); + + LOG(("nsHttpChannel::SwitchProcessTo [this=%p]", this)); + LogCallingScriptLocation(this); + + nsCOMPtr parentChannel; + NS_QueryNotificationCallbacks(this, parentChannel); + RefPtr documentChannelParent = + do_QueryObject(parentChannel); + // This is a temporary change as the DocumentChannelParent currently must go + // through the nsHttpChannel to perform a process switch via SessionStore. + if (!documentChannelParent) { + // We cannot do this after OnStartRequest of the listener has been called. + NS_ENSURE_FALSE(mOnStartRequestCalled, NS_ERROR_NOT_AVAILABLE); + } + + mRedirectContentProcessIdPromise = + ContentProcessIdPromise::FromDomPromise(aContentProcessIdPromise); + mCrossProcessRedirectIdentifier = aIdentifier; + return NS_OK; +} + +// This method returns the cached result of running the Cross-Origin-Opener +// policy compare algorithm by calling ComputeCrossOriginOpenerPolicyMismatch +NS_IMETHODIMP +nsHttpChannel::HasCrossOriginOpenerPolicyMismatch(bool* aMismatch) { + MOZ_ASSERT(aMismatch); + if (!aMismatch) { + return NS_ERROR_INVALID_ARG; + } + + *aMismatch = mHasCrossOriginOpenerPolicyMismatch; + return NS_OK; +} + +NS_IMETHODIMP +nsHttpChannel::GetCachedCrossOriginOpenerPolicy( + nsILoadInfo::CrossOriginOpenerPolicy* aPolicy) { + return HttpBaseChannel::GetCrossOriginOpenerPolicy(aPolicy); +} + // See https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e // This method runs steps 1-4 of the algorithm to compare // cross-origin-opener policies diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h index 900c7a0cbd68..1a9baaa0048b 100644 --- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -32,6 +32,7 @@ #include "mozilla/Atomics.h" #include "mozilla/extensions/PStreamFilterParent.h" #include "mozilla/Mutex.h" +#include "nsIProcessSwitchRequestor.h" class nsDNSPrefetch; class nsICancelable; @@ -78,7 +79,8 @@ class nsHttpChannel final : public HttpBaseChannel, public nsIChannelWithDivertableParentListener, public nsIRaceCacheWithNetwork, public nsIRequestTailUnblockCallback, - public nsITimerCallback { + public nsITimerCallback, + public nsIProcessSwitchRequestor { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIREQUESTOBSERVER @@ -100,6 +102,7 @@ class nsHttpChannel final : public HttpBaseChannel, NS_DECL_NSIRACECACHEWITHNETWORK NS_DECL_NSITIMERCALLBACK NS_DECL_NSIREQUESTTAILUNBLOCKCALLBACK + NS_DECL_NSIPROCESSSWITCHREQUESTOR // nsIHttpAuthenticableChannel. We can't use // NS_DECL_NSIHTTPAUTHENTICABLECHANNEL because it duplicates cancel() and @@ -274,6 +277,16 @@ class nsHttpChannel final : public HttpBaseChannel, } TransactionObserver* GetTransactionObserver() { return mTransactionObserver; } + typedef MozPromise + ContentProcessIdPromise; + already_AddRefed + TakeRedirectContentProcessIdPromise() { + return mRedirectContentProcessIdPromise.forget(); + } + uint64_t CrossProcessRedirectIdentifier() { + return mCrossProcessRedirectIdentifier; + } + CacheDisposition mCacheDisposition; protected: @@ -482,11 +495,6 @@ class nsHttpChannel final : public HttpBaseChannel, nsresult ProcessCrossOriginResourcePolicyHeader(); nsresult ComputeCrossOriginOpenerPolicyMismatch(); - // This method returns the cached result of running the Cross-Origin-Opener - // policy compare algorithm by calling ComputeCrossOriginOpenerPolicyMismatch - bool HasCrossOriginOpenerPolicyMismatch() { - return mHasCrossOriginOpenerPolicyMismatch; - } /** * A function to process a single security header (STS or PKP), assumes @@ -574,6 +582,12 @@ class nsHttpChannel final : public HttpBaseChannel, nsCOMPtr mRedirectChannel; nsCOMPtr mPreflightChannel; + // The associated childChannel is getting relocated to another process. + // This promise will be resolved when that process is set up. + RefPtr mRedirectContentProcessIdPromise; + // This identifier is passed to the childChannel in order to identify it. + uint64_t mCrossProcessRedirectIdentifier = 0; + // nsChannelClassifier checks this channel's URI against // the URI classifier service. // nsChannelClassifier will be invoked twice in InitLocalBlockList() and diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp index d6ed015ff1ec..ba55c56c508b 100644 --- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -18,6 +18,7 @@ #include "nsStandardURL.h" #include "LoadContextInfo.h" #include "nsCategoryManagerUtils.h" +#include "nsIProcessSwitchRequestor.h" #include "nsSocketProviderService.h" #include "nsISocketProvider.h" #include "nsPrintfCString.h" diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h index cff9e8200b62..1e0d7ced339c 100644 --- a/netwerk/protocol/http/nsHttpHandler.h +++ b/netwerk/protocol/http/nsHttpHandler.h @@ -35,6 +35,7 @@ class nsIHttpUpgradeListener; class nsIPrefBranch; class nsICancelable; class nsICookieService; +class nsIProcessSwitchRequestor; class nsIIOService; class nsIRequestContextService; class nsISiteSecurityService; diff --git a/toolkit/content/widgets/browser-custom-element.js b/toolkit/content/widgets/browser-custom-element.js index 6e4cd4edb218..9105238da776 100644 --- a/toolkit/content/widgets/browser-custom-element.js +++ b/toolkit/content/widgets/browser-custom-element.js @@ -2034,41 +2034,6 @@ } return origins; } - - get canPerformProcessSwitch() { - return this.getTabBrowser() != null; - } - - performProcessSwitch( - remoteType, - redirectLoadSwitchId, - replaceBrowsingContext - ) { - return this.getTabBrowser().performProcessSwitch( - this, - remoteType, - redirectLoadSwitchId, - replaceBrowsingContext - ); - } - - getRemoteTypeForPrincipal( - principal, - remoteTabs, - remoteSubframes, - preferredRemoteType, - currentPrincipal, - isSubframe - ) { - return E10SUtils.getRemoteTypeForPrincipal( - principal, - remoteTabs, - remoteSubframes, - preferredRemoteType, - currentPrincipal, - isSubframe - ); - } } MozXULElement.implementCustomInterface(MozBrowser, [Ci.nsIBrowser]);