Bug 1467223 - Part 5: Perform parent-process interception for HTTP loads, r=qdot,valentin

This will only happen if the pref is enabled, and works through the existing
mechanism for process switching loads. It should enable POST data to be
preserved when performing a process switch, for example when submitting
a form on a file:// or moz-extension:// URI to a http:// URI.

Depends on D15611

Differential Revision: https://phabricator.services.mozilla.com/D15612

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Nika Layzell 2019-01-23 21:07:10 +00:00
Родитель b2ddc107de
Коммит 45b85eee03
3 изменённых файлов: 257 добавлений и 66 удалений

Просмотреть файл

@ -186,6 +186,13 @@ ContentRestoreInternal.prototype = {
try {
if (loadArguments) {
// If the load was started in another process, and the in-flight channel
// was redirected into this process, resume that load within our process.
if (loadArguments.redirectLoadSwitchId) {
webNavigation.resumeRedirectedLoad(loadArguments.redirectLoadSwitchId);
return true;
}
// A load has been redirected to a new process so get history into the
// same state it was before the load started then trigger the load.
let referrer = loadArguments.referrer ?

Просмотреть файл

@ -48,6 +48,9 @@ const OBSERVING = [
"quit-application", "browser:purge-session-history",
"browser:purge-session-history-for-domain",
"idle-daily", "clear-origin-attributes-data",
"http-on-examine-response",
"http-on-examine-merged-response",
"http-on-examine-cached-response",
];
// XUL Window properties to (re)store
@ -171,6 +174,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm",
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
DevToolsShim: "chrome://devtools-startup/content/DevToolsShim.jsm",
E10SUtils: "resource://gre/modules/E10SUtils.jsm",
GlobalState: "resource:///modules/sessionstore/GlobalState.jsm",
HomePage: "resource:///modules/HomePage.jsm",
PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.jsm",
@ -448,6 +452,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,
@ -803,9 +810,16 @@ var SessionStoreInternal = {
try {
userContextId = JSON.parse(aData).userContextId;
} catch (e) {}
if (userContextId)
if (userContextId) {
this._forgetTabsWithUserContextId(userContextId);
}
break;
case "http-on-examine-response":
case "http-on-examine-cached-response":
case "http-on-examine-merged-response":
this.onExamineResponse(aSubject);
break;
}
},
/**
@ -2266,6 +2280,104 @@ var SessionStoreInternal = {
}
},
/**
* Perform a destructive process switch into a distinct process.
* This method is asynchronous, as it requires multiple calls into content
* processes.
*/
async _doProcessSwitch(aBrowser, aRemoteType, aChannel, aSwitchId) {
// 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,
};
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.tabParent) {
throw Cr.NS_ERROR_FAILURE;
}
// Tell our caller to redirect the load into this newly created process.
return aBrowser.frameLoader.tabParent;
},
// Examine the channel response to see if we should change the process
// performing the given load.
onExamineResponse(aChannel) {
if (!E10SUtils.useHttpResponseProcessSelection()) {
return;
}
if (!aChannel.isDocument || !aChannel.loadInfo) {
return; // Not a document load.
}
let browsingContext = aChannel.loadInfo.browsingContext;
if (!browsingContext) {
return; // Not loading in a browsing context.
}
if (browsingContext.parent) {
return; // Not a toplevel load, can't flip procs.
}
// Get principal for a document already loaded in the BrowsingContext.
let currentPrincipal = null;
if (browsingContext.currentWindowGlobal) {
currentPrincipal = browsingContext.currentWindowGlobal.documentPrincipal;
}
let parentChannel = aChannel.notificationCallbacks
.getInterface(Ci.nsIParentChannel);
if (!parentChannel) {
return; // Not an actor channel
}
let tabParent = parentChannel.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsITabParent);
if (!tabParent || !tabParent.ownerElement) {
console.warn("warning: Missing tabParent");
return; // Not an embedded browsing context
}
let browser = tabParent.ownerElement;
if (browser.tagName !== "browser") {
console.warn("warning: Not a xul:browser element:", browser.tagName);
return; // Not a vanilla xul:browser element performing embedding.
}
let resultPrincipal =
Services.scriptSecurityManager.getChannelResultPrincipal(aChannel);
let useRemoteTabs = browser.ownerGlobal.gMultiProcessBrowser;
let remoteType = E10SUtils.getRemoteTypeForPrincipal(resultPrincipal,
useRemoteTabs,
browser.remoteType,
currentPrincipal);
if (browser.remoteType == remoteType) {
return; // Already in compatible process.
}
// ------------------------------------------------------------------------
// DANGER ZONE: Perform a process switch into the new process. This is
// destructive.
// ------------------------------------------------------------------------
let identifier = ++this._switchIdMonotonic;
let tabPromise = this._doProcessSwitch(browser, remoteType,
aChannel, identifier);
aChannel.switchProcessTo(tabPromise, identifier);
},
/* ........ nsISessionStore API .............. */
getBrowserState: function ssi_getBrowserState() {
@ -3003,32 +3115,41 @@ var SessionStoreInternal = {
* flushing the browser tab. If that occurs, the loadArguments from
* the most recent call to navigateAndRestore will be used once the
* flush has finished.
*
* This method returns a promise which will be resolved when the browser
* element's process has been swapped. The load is not guaranteed to have
* been completed at this point.
*/
navigateAndRestore(tab, loadArguments, historyIndex) {
let window = tab.ownerGlobal;
if (!window.__SSi) {
Cu.reportError("Tab's window must be tracked.");
return;
return Promise.reject();
}
let browser = tab.linkedBrowser;
// Were we already waiting for a flush from a previous call to
// navigateAndRestore on this tab?
let alreadyRestoring =
this._remotenessChangingBrowsers.has(browser.permanentKey);
// Stash the most recent loadArguments in this WeakMap so that
// we know to use it when the TabStateFlusher.flush resolves.
this._remotenessChangingBrowsers.set(browser.permanentKey, loadArguments);
if (alreadyRestoring) {
// This tab was already being restored to run in the
// correct process. We're done here.
return;
// If we were alerady waiting for a flush from a previous call to
// navigateAndRestore on this tab, update the loadArguments stored, and
// asynchronously wait on the flush's promise.
if (this._remotenessChangingBrowsers.has(browser.permanentKey)) {
let opts = this._remotenessChangingBrowsers.get(browser.permanentKey);
// XXX(nika): In the existing logic, we always use the initial
// historyIndex value, and don't update it if multiple navigateAndRestore
// calls are made. Should we update it here?
opts.loadArguments = loadArguments;
return opts.promise;
}
// Begin the asynchronous NavigateAndRestore process, and store the current
// load arguments and promise in our _remotenessChangingBrowsers weakmap.
let promise = this._asyncNavigateAndRestore(tab);
this._remotenessChangingBrowsers.set(
browser.permanentKey, {loadArguments, historyIndex, promise});
// Set up the browser UI to look like we're doing something while waiting
// for a TabStateFlush from our frame scripts.
let uriObj;
try {
uriObj = Services.io.newURI(loadArguments.uri);
@ -3046,24 +3167,44 @@ var SessionStoreInternal = {
// perceived performance.
window.gBrowser.setDefaultIcon(tab, uriObj);
// Flush to get the latest tab state.
TabStateFlusher.flush(browser).then(() => {
// loadArguments might have been overwritten by multiple calls
// to navigateAndRestore while we waited for the tab to flush,
// so we use the most recently stored one.
let recentLoadArguments =
this._remotenessChangingBrowsers.get(browser.permanentKey);
this._remotenessChangingBrowsers.delete(browser.permanentKey);
TAB_STATE_FOR_BROWSER.set(tab.linkedBrowser, TAB_STATE_WILL_RESTORE);
// Notify of changes to closed objects.
this._notifyOfClosedObjectsChange();
return promise;
},
/**
* Internal logic called by navigateAndRestore to flush tab state, and
* trigger a remoteness changing load with the most recent load arguments.
*
* This method's promise will resolve when the process for the given
* xul:browser element has successfully been swapped.
*
* @param tab to navigate and restore.
*/
async _asyncNavigateAndRestore(tab) {
let initialBrowser = tab.linkedBrowser;
// NOTE: This is currently the only async operation used, but this is likely
// to change in the future.
await TabStateFlusher.flush(initialBrowser);
// Now that we have flushed state, our loadArguments, etc. may have been
// overwritten by multiple calls to navigateAndRestore. Load the most
// recently stored one.
let {loadArguments, historyIndex} =
this._remotenessChangingBrowsers.get(initialBrowser.permanentKey);
this._remotenessChangingBrowsers.delete(initialBrowser.permanentKey);
// The tab might have been closed/gone in the meantime.
if (tab.closing || !tab.linkedBrowser) {
return;
}
let refreshedWindow = tab.ownerGlobal;
// The tab or its window might be gone.
if (!refreshedWindow || !refreshedWindow.__SSi || refreshedWindow.closed) {
let window = tab.ownerGlobal;
if (!window || !window.__SSi || window.closed) {
return;
}
@ -3073,8 +3214,8 @@ var SessionStoreInternal = {
// We want to make sure that this information is passed to restoreTab
// whether or not a historyIndex is passed in. Thus, we extract it from
// the loadArguments.
newFrameloader: recentLoadArguments.newFrameloader,
remoteType: recentLoadArguments.remoteType,
newFrameloader: loadArguments.newFrameloader,
remoteType: loadArguments.remoteType,
// Make sure that SessionStore knows that this restoration is due
// to a navigation, as opposed to us restoring a closed window or tab.
restoreContentReason: RESTORE_TAB_CONTENT_REASON.NAVIGATE_AND_RESTORE,
@ -3084,7 +3225,7 @@ var SessionStoreInternal = {
tabState.index = historyIndex + 1;
tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
} else {
options.loadArguments = recentLoadArguments;
options.loadArguments = loadArguments;
}
// Need to reset restoring tabs.
@ -3094,12 +3235,6 @@ var SessionStoreInternal = {
// Restore the state into the tab.
this.restoreTab(tab, tabState, options);
});
TAB_STATE_FOR_BROWSER.set(tab.linkedBrowser, TAB_STATE_WILL_RESTORE);
// Notify of changes to closed objects.
this._notifyOfClosedObjectsChange();
},
/**

Просмотреть файл

@ -15,6 +15,8 @@ XPCOMUtils.defineLazyPreferenceGetter(this, "allowLinkedWebInFileUriProcess",
"browser.tabs.remote.allowLinkedWebInFileUriProcess", false);
XPCOMUtils.defineLazyPreferenceGetter(this, "useSeparatePrivilegedContentProcess",
"browser.tabs.remote.separatePrivilegedContentProcess", false);
XPCOMUtils.defineLazyPreferenceGetter(this, "useHttpResponseProcessSelection",
"browser.tabs.remote.useHTTPResponseProcessSelection", false);
ChromeUtils.defineModuleGetter(this, "Utils",
"resource://gre/modules/sessionstore/Utils.jsm");
@ -90,6 +92,10 @@ var E10SUtils = {
PRIVILEGED_REMOTE_TYPE,
LARGE_ALLOCATION_REMOTE_TYPE,
useHttpResponseProcessSelection() {
return useHttpResponseProcessSelection;
},
canLoadURIInRemoteType(aURL, aRemoteType = DEFAULT_REMOTE_TYPE) {
// We need a strict equality here because the value of `NOT_REMOTE` is
// `null`, and there is a possibility that `undefined` is passed as the
@ -226,6 +232,36 @@ var E10SUtils = {
}
},
getRemoteTypeForPrincipal(aPrincipal, aMultiProcess,
aPreferredRemoteType = DEFAULT_REMOTE_TYPE,
aCurrentPrincipal) {
if (!aMultiProcess) {
return NOT_REMOTE;
}
// We can't pick a process based on a system principal or expanded
// principal. In fact, we should never end up with one here!
if (aPrincipal.isSystemPrincipal || aPrincipal.isExpandedPrincipal) {
throw Cr.NS_ERROR_UNEXPECTED;
}
// Null principals can be loaded in any remote process.
if (aPrincipal.isNullPrincipal) {
return aPreferredRemoteType == NOT_REMOTE ? DEFAULT_REMOTE_TYPE
: 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-codebase principal.
let currentURI = (aCurrentPrincipal && aCurrentPrincipal.isCodebasePrincipal)
? aCurrentPrincipal.URI : null;
return E10SUtils.getRemoteTypeForURIObject(aPrincipal.URI,
aMultiProcess,
aPreferredRemoteType,
currentURI);
},
shouldLoadURIInBrowser(browser, uri, multiProcess = true,
flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE) {
let currentRemoteType = browser.remoteType;
@ -276,8 +312,21 @@ var E10SUtils = {
shouldLoadURI(aDocShell, aURI, aReferrer, aHasPostData) {
// Inner frames should always load in the current process
if (aDocShell.sameTypeParent)
if (aDocShell.sameTypeParent) {
return true;
}
// If we are performing HTTP response process selection, and are loading an
// HTTP URI, we can start the load in the current process, and then perform
// the switch later-on using the RedirectProcessChooser mechanism.
//
// We should never be sending a POST request from the parent process to a
// http(s) uri, so make sure we switch if we're currently in that process.
if (useHttpResponseProcessSelection &&
(aURI.scheme == "http" || aURI.scheme == "https") &&
Services.appinfo.remoteType != NOT_REMOTE) {
return true;
}
// If we are in a Large-Allocation process, and it wouldn't be content visible
// to change processes, we want to load into a new process so that we can throw