зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
b2ddc107de
Коммит
45b85eee03
|
@ -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,8 +810,15 @@ 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,60 +3167,74 @@ 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);
|
||||
|
||||
// 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) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tabState = TabState.clone(tab, TAB_CUSTOM_VALUES.get(tab));
|
||||
let options = {
|
||||
restoreImmediately: true,
|
||||
// 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,
|
||||
// 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,
|
||||
};
|
||||
|
||||
if (historyIndex >= 0) {
|
||||
tabState.index = historyIndex + 1;
|
||||
tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
|
||||
} else {
|
||||
options.loadArguments = recentLoadArguments;
|
||||
}
|
||||
|
||||
// Need to reset restoring tabs.
|
||||
if (TAB_STATE_FOR_BROWSER.has(tab.linkedBrowser)) {
|
||||
this._resetLocalTabRestoringState(tab);
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// The tab or its window might be gone.
|
||||
let window = tab.ownerGlobal;
|
||||
if (!window || !window.__SSi || window.closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tabState = TabState.clone(tab, TAB_CUSTOM_VALUES.get(tab));
|
||||
let options = {
|
||||
restoreImmediately: true,
|
||||
// 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: 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,
|
||||
};
|
||||
|
||||
if (historyIndex >= 0) {
|
||||
tabState.index = historyIndex + 1;
|
||||
tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
|
||||
} else {
|
||||
options.loadArguments = loadArguments;
|
||||
}
|
||||
|
||||
// Need to reset restoring tabs.
|
||||
if (TAB_STATE_FOR_BROWSER.has(tab.linkedBrowser)) {
|
||||
this._resetLocalTabRestoringState(tab);
|
||||
}
|
||||
|
||||
// Restore the state into the tab.
|
||||
this.restoreTab(tab, tabState, options);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче