зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1625513 - Part 1: Perform onMayChangeProcess handling within DocumentLoadListener, r=mattwoodrow,pbone,droeh,necko-reviewers,valentin
When I first added this method last year, I added it in JS, handled from within SessionStore.jsm, as that was the easiest place to do it. Now that DocumentLoadListener exists, it makes more sense to handle this logic directly from within that code. Many parts of the process switch are still handled by frontend JS, such as selecting remote types, and performing toplevel process switches. Differential Revision: https://phabricator.services.mozilla.com/D68594
This commit is contained in:
Родитель
c9d7201a13
Коммит
975ef89398
|
@ -5682,6 +5682,51 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
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;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -54,7 +54,6 @@ const OBSERVING = [
|
||||||
"browser:purge-session-history-for-domain",
|
"browser:purge-session-history-for-domain",
|
||||||
"idle-daily",
|
"idle-daily",
|
||||||
"clear-origin-attributes-data",
|
"clear-origin-attributes-data",
|
||||||
"channel-on-may-change-process",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// XUL Window properties to (re)store
|
// XUL Window properties to (re)store
|
||||||
|
@ -519,9 +518,6 @@ var SessionStoreInternal = {
|
||||||
// A counter to be used to generate a unique ID for each closed tab or window.
|
// A counter to be used to generate a unique ID for each closed tab or window.
|
||||||
_nextClosedId: 0,
|
_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
|
// During the initial restore and setBrowserState calls tracks the number of
|
||||||
// windows yet to be restored
|
// windows yet to be restored
|
||||||
_restoreCount: -1,
|
_restoreCount: -1,
|
||||||
|
@ -947,9 +943,6 @@ var SessionStoreInternal = {
|
||||||
this._forgetTabsWithUserContextId(userContextId);
|
this._forgetTabsWithUserContextId(userContextId);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "channel-on-may-change-process":
|
|
||||||
this.onMayChangeProcess(aSubject);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -2919,237 +2912,6 @@ 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 <tabbrowser>), 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 .............. */
|
/* ........ nsISessionStore API .............. */
|
||||||
|
|
||||||
getBrowserState: function ssi_getBrowserState() {
|
getBrowserState: function ssi_getBrowserState() {
|
||||||
|
|
|
@ -507,25 +507,6 @@ CanonicalBrowsingContext::ChangeFrameRemoteness(const nsAString& aRemoteType,
|
||||||
return promise.forget();
|
return promise.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
already_AddRefed<Promise> CanonicalBrowsingContext::ChangeFrameRemoteness(
|
|
||||||
const nsAString& aRemoteType, uint64_t aPendingSwitchId, ErrorResult& aRv) {
|
|
||||||
nsIGlobalObject* global = xpc::NativeGlobal(xpc::PrivilegedJunkScope());
|
|
||||||
|
|
||||||
RefPtr<Promise> 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() {
|
MediaController* CanonicalBrowsingContext::GetMediaController() {
|
||||||
// We would only create one media controller per tab, so accessing the
|
// We would only create one media controller per tab, so accessing the
|
||||||
// controller via the top-level browsing context.
|
// controller via the top-level browsing context.
|
||||||
|
|
|
@ -105,12 +105,6 @@ class CanonicalBrowsingContext final : public BrowsingContext {
|
||||||
RefPtr<RemotenessPromise> ChangeFrameRemoteness(const nsAString& aRemoteType,
|
RefPtr<RemotenessPromise> ChangeFrameRemoteness(const nsAString& aRemoteType,
|
||||||
uint64_t aPendingSwitchId);
|
uint64_t aPendingSwitchId);
|
||||||
|
|
||||||
// Helper version for WebIDL - resolves to the PID where the load is being
|
|
||||||
// resumed.
|
|
||||||
already_AddRefed<Promise> ChangeFrameRemoteness(const nsAString& aRemoteType,
|
|
||||||
uint64_t aPendingSwitchId,
|
|
||||||
ErrorResult& aRv);
|
|
||||||
|
|
||||||
// Return a media controller from the top-level browsing context that can
|
// Return a media controller from the top-level browsing context that can
|
||||||
// control all media belonging to this browsing context tree. Return nullptr
|
// control all media belonging to this browsing context tree. Return nullptr
|
||||||
// if the top-level browsing context has been discarded.
|
// if the top-level browsing context has been discarded.
|
||||||
|
|
|
@ -102,10 +102,6 @@ interface CanonicalBrowsingContext : BrowsingContext {
|
||||||
[Throws]
|
[Throws]
|
||||||
void loadURI(DOMString aURI, optional LoadURIOptions aOptions = {});
|
void loadURI(DOMString aURI, optional LoadURIOptions aOptions = {});
|
||||||
|
|
||||||
[Throws]
|
|
||||||
Promise<unsigned long long> changeFrameRemoteness(
|
|
||||||
DOMString remoteType, unsigned long long pendingSwitchId);
|
|
||||||
|
|
||||||
readonly attribute nsISHistory? sessionHistory;
|
readonly attribute nsISHistory? sessionHistory;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -173,4 +173,40 @@ interface nsIBrowser : nsISupports
|
||||||
void updateSecurityUIForSecurityChange(in nsITransportSecurityInfo aSecurityInfo,
|
void updateSecurityUIForSecurityChange(in nsITransportSecurityInfo aSecurityInfo,
|
||||||
in uint32_t aState,
|
in uint32_t aState,
|
||||||
in boolean aIsSecureContext);
|
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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,20 +22,6 @@ interface nsIProcessSwitchRequestor : nsISupports
|
||||||
*/
|
*/
|
||||||
readonly attribute nsIChannel channel;
|
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
|
* Used to determine if there is a Cross-Origin-Opener-Policy mismatch
|
||||||
* that would require switching the channel to another process.
|
* that would require switching the channel to another process.
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
#include "mozilla/dom/WindowGlobalParent.h"
|
#include "mozilla/dom/WindowGlobalParent.h"
|
||||||
#include "mozilla/StaticPrefs_security.h"
|
#include "mozilla/StaticPrefs_security.h"
|
||||||
#include "nsICookieService.h"
|
#include "nsICookieService.h"
|
||||||
|
#include "nsIBrowser.h"
|
||||||
|
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
# include "mozilla/widget/nsWindow.h"
|
# include "mozilla/widget/nsWindow.h"
|
||||||
|
@ -883,26 +884,186 @@ void DocumentLoadListener::SerializeRedirectData(
|
||||||
aArgs.loadStateLoadType() = mLoadStateLoadType;
|
aArgs.loadStateLoadType() = mLoadStateLoadType;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DocumentLoadListener::TriggerCrossProcessSwitch() {
|
bool DocumentLoadListener::MaybeTriggerProcessSwitch() {
|
||||||
MOZ_ASSERT(mRedirectContentProcessIdPromise);
|
MOZ_DIAGNOSTIC_ASSERT(!mDoingProcessSwitch,
|
||||||
MOZ_ASSERT(!mDoingProcessSwitch, "Already in the middle of switching?");
|
"Already in the middle of switching?");
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_DIAGNOSTIC_ASSERT(mChannel);
|
||||||
|
MOZ_DIAGNOSTIC_ASSERT(mParentChannelListener);
|
||||||
|
|
||||||
LOG(("DocumentLoadListener TriggerCrossProcessSwitch [this=%p]", this));
|
LOG(("DocumentLoadListener MaybeTriggerProcessSwitch [this=%p]", this));
|
||||||
|
|
||||||
|
// Get the BrowsingContext which will be switching processes.
|
||||||
|
RefPtr<CanonicalBrowsingContext> 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<nsIBrowser> 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<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 = false;
|
||||||
|
nsILoadInfo::CrossOriginOpenerPolicy coop =
|
||||||
|
nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
|
||||||
|
MOZ_ALWAYS_SUCCEEDS(HasCrossOriginOpenerPolicyMismatch(&isCOOPSwitch));
|
||||||
|
MOZ_ALWAYS_SUCCEEDS(GetCachedCrossOriginOpenerPolicy(&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;
|
mDoingProcessSwitch = true;
|
||||||
|
|
||||||
RefPtr<DocumentLoadListener> self = this;
|
RefPtr<DocumentLoadListener> self = this;
|
||||||
mRedirectContentProcessIdPromise->Then(
|
// At this point, we're actually going to perform a process switch, which
|
||||||
GetMainThreadSerialEventTarget(), __func__,
|
// involves calling into other logic.
|
||||||
[self, this](uint64_t aCpId) {
|
if (browsingContext->GetParent()) {
|
||||||
MOZ_ASSERT(mChannel, "Something went wrong, channel got cancelled");
|
LOG(("Process Switch: Calling ChangeFrameRemoteness"));
|
||||||
TriggerRedirectToRealChannel(Some(aCpId));
|
// If we're switching a subframe, ask BrowsingContext to do it for us.
|
||||||
},
|
MOZ_ASSERT(!isCOOPSwitch);
|
||||||
[self](nsresult aStatusCode) {
|
browsingContext
|
||||||
MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error");
|
->ChangeFrameRemoteness(remoteType, mCrossProcessRedirectIdentifier)
|
||||||
self->RedirectToRealChannelFinished(aStatusCode);
|
->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<Promise> domPromise;
|
||||||
|
browser->PerformProcessSwitch(remoteType, mCrossProcessRedirectIdentifier,
|
||||||
|
isCOOPSwitch, getter_AddRefs(domPromise));
|
||||||
|
MOZ_DIAGNOSTIC_ASSERT(domPromise,
|
||||||
|
"PerformProcessSwitch didn't return a promise");
|
||||||
|
|
||||||
|
MozPromise<uint64_t, nsresult, true>::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;
|
||||||
}
|
}
|
||||||
|
|
||||||
RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
|
RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
|
||||||
|
@ -1083,18 +1244,10 @@ DocumentLoadListener::OnStartRequest(nsIRequest* aRequest) {
|
||||||
httpChannel->SetApplyConversion(false);
|
httpChannel->SetApplyConversion(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify "channel-on-may-change-process" observers which is typically
|
// Determine if a new process needs to be spawned. If it does, this will
|
||||||
// SessionStore.jsm. This will determine if a new process needs to be
|
// trigger a cross process switch, and we should hold off on redirecting to
|
||||||
// spawned and if so SwitchProcessTo() will be called which will set a
|
// the real channel.
|
||||||
// ContentProcessIdPromise.
|
if (status != NS_BINDING_ABORTED && MaybeTriggerProcessSwitch()) {
|
||||||
if (status != NS_BINDING_ABORTED) {
|
|
||||||
nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
|
|
||||||
obsService->NotifyObservers(ToSupports(this),
|
|
||||||
"channel-on-may-change-process", nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mRedirectContentProcessIdPromise) {
|
|
||||||
TriggerCrossProcessSwitch();
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1362,17 +1515,6 @@ NS_IMETHODIMP DocumentLoadListener::GetChannel(nsIChannel** aChannel) {
|
||||||
return NS_OK;
|
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
|
// This method returns the cached result of running the Cross-Origin-Opener
|
||||||
// policy compare algorithm by calling ComputeCrossOriginOpenerPolicyMismatch
|
// policy compare algorithm by calling ComputeCrossOriginOpenerPolicyMismatch
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
|
|
|
@ -200,10 +200,10 @@ class DocumentLoadListener : public nsIInterfaceRequestor,
|
||||||
// by us, and resumes the underlying source channel.
|
// by us, and resumes the underlying source channel.
|
||||||
void FinishReplacementChannelSetup(bool aSucceeded);
|
void FinishReplacementChannelSetup(bool aSucceeded);
|
||||||
|
|
||||||
// Called when we have a cross-process switch promise. Waits on the
|
// Called from `OnStartRequest` to make the decision about whether or not to
|
||||||
// promise, and then call TriggerRedirectToRealChannel with the
|
// change process. This method will return `nullptr` if the current target
|
||||||
// provided content process id.
|
// process is appropriate.
|
||||||
void TriggerCrossProcessSwitch();
|
bool MaybeTriggerProcessSwitch();
|
||||||
|
|
||||||
// A helper for TriggerRedirectToRealChannel that abstracts over
|
// A helper for TriggerRedirectToRealChannel that abstracts over
|
||||||
// the same-process and cross-process switch cases and returns
|
// the same-process and cross-process switch cases and returns
|
||||||
|
@ -372,16 +372,8 @@ class DocumentLoadListener : public nsIInterfaceRequestor,
|
||||||
// channel.
|
// channel.
|
||||||
bool mIsFinished = false;
|
bool mIsFinished = false;
|
||||||
|
|
||||||
typedef MozPromise<uint64_t, nsresult, true /* exclusive */>
|
// This identifier is set by MaybeTriggerProcessSwitch, and is later
|
||||||
ContentProcessIdPromise;
|
// passed to the childChannel in order to identify it in the new process.
|
||||||
// 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<ContentProcessIdPromise> 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;
|
uint64_t mCrossProcessRedirectIdentifier = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7431,31 +7431,6 @@ NS_IMETHODIMP nsHttpChannel::GetChannel(nsIChannel** aChannel) {
|
||||||
return NS_OK;
|
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<nsIParentChannel> parentChannel;
|
|
||||||
NS_QueryNotificationCallbacks(this, parentChannel);
|
|
||||||
RefPtr<DocumentLoadListener> 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
|
// This method returns the cached result of running the Cross-Origin-Opener
|
||||||
// policy compare algorithm by calling ComputeCrossOriginOpenerPolicyMismatch
|
// policy compare algorithm by calling ComputeCrossOriginOpenerPolicyMismatch
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
|
|
|
@ -280,16 +280,6 @@ class nsHttpChannel final : public HttpBaseChannel,
|
||||||
}
|
}
|
||||||
TransactionObserver* GetTransactionObserver() { return mTransactionObserver; }
|
TransactionObserver* GetTransactionObserver() { return mTransactionObserver; }
|
||||||
|
|
||||||
typedef MozPromise<uint64_t, nsresult, true /* exclusive */>
|
|
||||||
ContentProcessIdPromise;
|
|
||||||
already_AddRefed<ContentProcessIdPromise>
|
|
||||||
TakeRedirectContentProcessIdPromise() {
|
|
||||||
return mRedirectContentProcessIdPromise.forget();
|
|
||||||
}
|
|
||||||
uint64_t CrossProcessRedirectIdentifier() {
|
|
||||||
return mCrossProcessRedirectIdentifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
CacheDisposition mCacheDisposition;
|
CacheDisposition mCacheDisposition;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -585,12 +575,6 @@ class nsHttpChannel final : public HttpBaseChannel,
|
||||||
nsCOMPtr<nsIChannel> mRedirectChannel;
|
nsCOMPtr<nsIChannel> mRedirectChannel;
|
||||||
nsCOMPtr<nsIChannel> mPreflightChannel;
|
nsCOMPtr<nsIChannel> mPreflightChannel;
|
||||||
|
|
||||||
// The associated childChannel is getting relocated to another process.
|
|
||||||
// This promise will be resolved when that process is set up.
|
|
||||||
RefPtr<ContentProcessIdPromise> mRedirectContentProcessIdPromise;
|
|
||||||
// This identifier is passed to the childChannel in order to identify it.
|
|
||||||
uint64_t mCrossProcessRedirectIdentifier = 0;
|
|
||||||
|
|
||||||
// nsChannelClassifier checks this channel's URI against
|
// nsChannelClassifier checks this channel's URI against
|
||||||
// the URI classifier service.
|
// the URI classifier service.
|
||||||
// nsChannelClassifier will be invoked twice in InitLocalBlockList() and
|
// nsChannelClassifier will be invoked twice in InitLocalBlockList() and
|
||||||
|
|
|
@ -2052,6 +2052,41 @@
|
||||||
}
|
}
|
||||||
return origins;
|
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]);
|
MozXULElement.implementCustomInterface(MozBrowser, [Ci.nsIBrowser]);
|
||||||
|
|
Загрузка…
Ссылка в новой задаче