зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1696815 - Handle browser.loadURI() calls that happen before the browser is fully restored, r=annyG,nika,zombie
Differential Revision: https://phabricator.services.mozilla.com/D118101
This commit is contained in:
Родитель
1f50de5e84
Коммит
145f291ac7
|
@ -231,7 +231,6 @@ skip-if = os == "linux" && debug && bits == 32 # Bug 1350189
|
|||
[browser_ext_tabs_discard.js]
|
||||
skip-if = !e10s
|
||||
[browser_ext_tabs_discard_reversed.js]
|
||||
skip-if = fission # bug 1696815
|
||||
[browser_ext_tabs_discarded.js]
|
||||
[browser_ext_tabs_duplicate.js]
|
||||
[browser_ext_tabs_events.js]
|
||||
|
|
|
@ -544,15 +544,11 @@ var SessionStoreInternal = {
|
|||
// For each <browser> element, records the SHistoryListener.
|
||||
_browserSHistoryListener: new WeakMap(),
|
||||
|
||||
// For each <browser> element, records the SHistoryListener.
|
||||
_browserSHistoryListenerForRestore: new WeakMap(),
|
||||
|
||||
// For each <browser> element that's being restored, holds a web progress
|
||||
// listener that watches for STATE_START and STATE_STOP events.
|
||||
_browserProgressListenerForRestore: new WeakMap(),
|
||||
// Tracks the various listeners that are used throughout the restore.
|
||||
_restoreListeners: new WeakMap(),
|
||||
|
||||
// The history data needed to be restored in the parent.
|
||||
_shistoryToRestore: new WeakMap(),
|
||||
_tabStateToRestore: new WeakMap(),
|
||||
|
||||
// For each <browser> element, records the current epoch.
|
||||
_browserEpochs: new WeakMap(),
|
||||
|
@ -1138,130 +1134,6 @@ var SessionStoreInternal = {
|
|||
return listener;
|
||||
},
|
||||
|
||||
/**
|
||||
* This listener detects when a page being restored is reloaded. It triggers a
|
||||
* callback and cancels the reload. The callback will send a message to
|
||||
* SessionStore.jsm so that it can restore the content immediately.
|
||||
*/
|
||||
addSHistoryListenerForRestore(aBrowser, aCallbacks) {
|
||||
if (!Services.appinfo.sessionHistoryInParent) {
|
||||
throw new Error("This function should only be used with SHIP");
|
||||
}
|
||||
function SHistoryListener(browser, callbacks) {
|
||||
browser.browsingContext.sessionHistory.addSHistoryListener(this);
|
||||
this.browser = browser;
|
||||
this.callbacks = callbacks;
|
||||
}
|
||||
SHistoryListener.prototype = {
|
||||
QueryInterface: ChromeUtils.generateQI([
|
||||
"nsISHistoryListener",
|
||||
"nsISupportsWeakReference",
|
||||
]),
|
||||
|
||||
uninstall() {
|
||||
let shistory = this.browser.browsingContext?.sessionHistory;
|
||||
if (shistory) {
|
||||
shistory.removeSHistoryListener(this);
|
||||
}
|
||||
SessionStoreInternal._browserSHistoryListenerForRestore.delete(
|
||||
this.browser.permanentKey
|
||||
);
|
||||
},
|
||||
|
||||
OnHistoryGotoIndex() {},
|
||||
OnHistoryPurge() {},
|
||||
OnHistoryReplaceEntry() {},
|
||||
|
||||
// This will be called for a pending tab when loadURI(uri) is called where
|
||||
// the given |uri| only differs in the fragment.
|
||||
OnHistoryNewEntry(newURI) {
|
||||
let currentURI = this.browser.currentURI;
|
||||
|
||||
// Ignore new SHistory entries with the same URI as those do not indicate
|
||||
// a navigation inside a document by changing the #hash part of the URL.
|
||||
// We usually hit this when purging session history for browsers.
|
||||
if (currentURI && currentURI.displaySpec == newURI.spec) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.callbacks.onHistoryNewEntry) {
|
||||
this.callbacks.onHistoryNewEntry(newURI);
|
||||
}
|
||||
},
|
||||
|
||||
OnHistoryReload() {
|
||||
if (this.callbacks.onHistoryReload) {
|
||||
return this.callbacks.onHistoryReload();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
// XXX: When can this happen?
|
||||
if (!aBrowser.browsingContext?.sessionHistory) {
|
||||
throw new Error("no SessionHistory object");
|
||||
}
|
||||
|
||||
// Ensure we only have 1 active listener per browser.
|
||||
if (this._browserSHistoryListenerForRestore.has(aBrowser.permanentKey)) {
|
||||
this._browserSHistoryListenerForRestore
|
||||
.get(aBrowser.permanentKey)
|
||||
.uninstall();
|
||||
}
|
||||
|
||||
let listener = new SHistoryListener(aBrowser, aCallbacks);
|
||||
this._browserSHistoryListenerForRestore.set(
|
||||
aBrowser.permanentKey,
|
||||
listener
|
||||
);
|
||||
},
|
||||
|
||||
addProgressListenerForRestore(browser, callbacks) {
|
||||
if (!Services.appinfo.sessionHistoryInParent) {
|
||||
throw new Error("This function should only be used with SHIP");
|
||||
}
|
||||
|
||||
class ProgressListener {
|
||||
constructor() {
|
||||
browser.addProgressListener(
|
||||
this,
|
||||
Ci.nsIWebProgress.NOTIFY_STATE_WINDOW
|
||||
);
|
||||
}
|
||||
uninstall() {
|
||||
browser.removeProgressListener(this);
|
||||
SessionStoreInternal._browserProgressListenerForRestore.delete(
|
||||
browser.permanentKey
|
||||
);
|
||||
}
|
||||
onStateChange(webProgress, request, stateFlags, status) {
|
||||
if (
|
||||
webProgress.isTopLevel &&
|
||||
stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW &&
|
||||
stateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
callbacks.onStopRequest
|
||||
) {
|
||||
callbacks.onStopRequest(request, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProgressListener.prototype.QueryInterface = ChromeUtils.generateQI([
|
||||
"nsIWebProgressListener",
|
||||
"nsISupportsWeakReference",
|
||||
]);
|
||||
|
||||
// Ensure we only have 1 listener per browser.
|
||||
if (this._browserProgressListenerForRestore.has(browser.permanentKey)) {
|
||||
this._browserProgressListenerForRestore
|
||||
.get(browser.permanentKey)
|
||||
.uninstall();
|
||||
}
|
||||
|
||||
let listener = new ProgressListener();
|
||||
this._browserProgressListenerForRestore.set(browser.permanentKey, listener);
|
||||
},
|
||||
|
||||
onTabStateUpdate(browser, data) {
|
||||
// Ignore messages from <browser> elements that have crashed
|
||||
// and not yet been revived.
|
||||
|
@ -1321,7 +1193,7 @@ var SessionStoreInternal = {
|
|||
TabStateFlusher.resolveAll(browser);
|
||||
|
||||
this._browserSHistoryListener.get(permanentKey)?.uninstall();
|
||||
this._browserSHistoryListenerForRestore.get(permanentKey)?.uninstall();
|
||||
this._restoreListeners.get(permanentKey)?.unregister();
|
||||
|
||||
Services.obs.notifyObservers(browser, NOTIFY_BROWSER_SHUTDOWN_FLUSH);
|
||||
},
|
||||
|
@ -5673,11 +5545,7 @@ var SessionStoreInternal = {
|
|||
TAB_STATE_FOR_BROWSER.delete(browser);
|
||||
|
||||
if (Services.appinfo.sessionHistoryInParent) {
|
||||
if (this._browserProgressListenerForRestore.has(browser.permanentKey)) {
|
||||
this._browserProgressListenerForRestore
|
||||
.get(browser.permanentKey)
|
||||
.uninstall();
|
||||
}
|
||||
this._restoreListeners.get(browser.permanentKey)?.unregister();
|
||||
browser.browsingContext.clearRestoreState();
|
||||
}
|
||||
|
||||
|
@ -5878,46 +5746,195 @@ var SessionStoreInternal = {
|
|||
return root;
|
||||
},
|
||||
|
||||
_waitForStateStop(browser, expectedURL = null) {
|
||||
const deferred = PromiseUtils.defer();
|
||||
|
||||
const listener = {
|
||||
unregister(reject = true) {
|
||||
if (reject) {
|
||||
deferred.reject();
|
||||
}
|
||||
|
||||
SessionStoreInternal._restoreListeners.delete(browser.permanentKey);
|
||||
|
||||
try {
|
||||
browser.removeProgressListener(
|
||||
this,
|
||||
Ci.nsIWebProgress.NOTIFY_STATE_WINDOW
|
||||
);
|
||||
} catch {} // May have already gotten rid of the browser's webProgress.
|
||||
},
|
||||
|
||||
onStateChange(webProgress, request, stateFlags, status) {
|
||||
if (
|
||||
webProgress.isTopLevel &&
|
||||
stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW &&
|
||||
stateFlags & Ci.nsIWebProgressListener.STATE_STOP
|
||||
) {
|
||||
// FIXME: We sometimes see spurious STATE_STOP events for about:blank
|
||||
// loads, so we have to account for that here.
|
||||
let aboutBlankOK = !expectedURL || expectedURL === "about:blank";
|
||||
let url = request.QueryInterface(Ci.nsIChannel).originalURI.spec;
|
||||
if (url !== "about:blank" || aboutBlankOK) {
|
||||
this.unregister(false);
|
||||
deferred.resolve();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface: ChromeUtils.generateQI([
|
||||
"nsIWebProgressListener",
|
||||
"nsISupportsWeakReference",
|
||||
]),
|
||||
};
|
||||
|
||||
this._restoreListeners.get(browser.permanentKey)?.unregister();
|
||||
this._restoreListeners.set(browser.permanentKey, listener);
|
||||
|
||||
browser.addProgressListener(
|
||||
listener,
|
||||
Ci.nsIWebProgress.NOTIFY_STATE_WINDOW
|
||||
);
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
_listenForNavigations(browser, callbacks) {
|
||||
const listener = {
|
||||
unregister() {
|
||||
browser.browsingContext?.sessionHistory?.removeSHistoryListener(this);
|
||||
|
||||
try {
|
||||
browser.removeProgressListener(
|
||||
this,
|
||||
Ci.nsIWebProgress.NOTIFY_STATE_WINDOW
|
||||
);
|
||||
} catch {} // May have already gotten rid of the browser's webProgress.
|
||||
|
||||
SessionStoreInternal._restoreListeners.delete(browser.permanentKey);
|
||||
},
|
||||
|
||||
OnHistoryReload() {
|
||||
this.unregister();
|
||||
return callbacks.onHistoryReload();
|
||||
},
|
||||
|
||||
// TODO(kashav): ContentRestore.jsm handles OnHistoryNewEntry separately,
|
||||
// so we should eventually support that here as well.
|
||||
OnHistoryNewEntry() {},
|
||||
OnHistoryGotoIndex() {},
|
||||
OnHistoryPurge() {},
|
||||
OnHistoryReplaceEntry() {},
|
||||
|
||||
onStateChange(webProgress, request, stateFlags, status) {
|
||||
if (
|
||||
webProgress.isTopLevel &&
|
||||
stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW &&
|
||||
stateFlags & Ci.nsIWebProgressListener.STATE_START
|
||||
) {
|
||||
this.unregister();
|
||||
callbacks.onStartRequest();
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface: ChromeUtils.generateQI([
|
||||
"nsISHistoryListener",
|
||||
"nsIWebProgressListener",
|
||||
"nsISupportsWeakReference",
|
||||
]),
|
||||
};
|
||||
|
||||
this._restoreListeners.get(browser.permanentKey)?.unregister();
|
||||
this._restoreListeners.set(browser.permanentKey, listener);
|
||||
|
||||
browser.browsingContext?.sessionHistory?.addSHistoryListener(listener);
|
||||
|
||||
browser.addProgressListener(
|
||||
listener,
|
||||
Ci.nsIWebProgress.NOTIFY_STATE_WINDOW
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* This mirrors ContentRestore.restoreHistory() for parent process session
|
||||
* history restores, but we're not actually restoring history here.
|
||||
* history restores.
|
||||
*/
|
||||
_restoreHistory(browser, data) {
|
||||
if (!Services.appinfo.sessionHistoryInParent) {
|
||||
throw new Error("This function should only be used with SHIP");
|
||||
}
|
||||
|
||||
this._tabStateToRestore.set(browser.permanentKey, data);
|
||||
|
||||
// In case about:blank isn't done yet.
|
||||
// XXX(kashav): Does this actually accomplish anything? Can we remove?
|
||||
browser.stop();
|
||||
|
||||
let uri = data.tabData?.entries[data.tabData.index - 1]?.url;
|
||||
let disallow = data.tabData?.disallow;
|
||||
|
||||
let promise = SessionStoreUtils.restoreDocShellState(
|
||||
browser.browsingContext,
|
||||
uri,
|
||||
disallow
|
||||
);
|
||||
|
||||
promise
|
||||
.then(() => {
|
||||
this._restoreHistoryComplete(browser, data);
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
this._shistoryToRestore.set(browser.permanentKey, data);
|
||||
|
||||
SessionHistory.restoreFromParent(
|
||||
browser.browsingContext.sessionHistory,
|
||||
data.tabData
|
||||
);
|
||||
|
||||
this.addSHistoryListenerForRestore(browser, {
|
||||
onHistoryReload: () => {
|
||||
this._restoreTabContent(browser);
|
||||
return false;
|
||||
},
|
||||
let url = data.tabData?.entries[data.tabData.index - 1]?.url;
|
||||
let disallow = data.tabData?.disallow;
|
||||
let restorePromise = SessionStoreUtils.restoreDocShellState(
|
||||
browser.browsingContext,
|
||||
url,
|
||||
disallow
|
||||
);
|
||||
|
||||
const onResolve = () => {
|
||||
if (TAB_STATE_FOR_BROWSER.get(browser) !== TAB_STATE_RESTORING) {
|
||||
this._listenForNavigations(browser, {
|
||||
// The history entry was reloaded before we began restoring tab
|
||||
// content, just proceed as we would normally.
|
||||
onHistoryReload: () => {
|
||||
this._restoreTabContent(browser);
|
||||
return false;
|
||||
},
|
||||
|
||||
// Some foreign code, like an extension, loaded a new URI on the
|
||||
// browser. We no longer want to restore saved tab data, but may
|
||||
// still have browser state that needs to be restored.
|
||||
onStartRequest: () => {
|
||||
this._tabStateToRestore.delete(browser.permanentKey);
|
||||
this._restoreTabContent(browser);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this._restoreHistoryComplete(browser, data);
|
||||
};
|
||||
|
||||
restorePromise.then(onResolve).catch(() => {});
|
||||
},
|
||||
|
||||
/**
|
||||
* Either load the saved typed value or restore the active history entry.
|
||||
* If neither is possible, just load an empty document.
|
||||
*/
|
||||
_restoreTabEntry(browser, tabData) {
|
||||
let url = "about:blank";
|
||||
let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY;
|
||||
|
||||
if (tabData.userTypedValue && tabData.userTypedClear) {
|
||||
url = tabData.userTypedValue;
|
||||
loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
|
||||
} else if (tabData.entries.length) {
|
||||
return SessionStoreUtils.initializeRestore(
|
||||
browser.browsingContext,
|
||||
this.buildRestoreData(tabData.formdata, tabData.scroll)
|
||||
);
|
||||
}
|
||||
|
||||
let loadPromise = this._waitForStateStop(browser, url);
|
||||
|
||||
browser.browsingContext.loadURI(url, {
|
||||
loadFlags,
|
||||
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
|
||||
});
|
||||
|
||||
return loadPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -5929,75 +5946,28 @@ var SessionStoreInternal = {
|
|||
throw new Error("This function should only be used with SHIP");
|
||||
}
|
||||
|
||||
for (let map of [
|
||||
this._browserProgressListenerForRestore,
|
||||
this._browserSHistoryListenerForRestore,
|
||||
]) {
|
||||
let listener = map.get(browser.permanentKey);
|
||||
if (listener) {
|
||||
listener.uninstall();
|
||||
}
|
||||
}
|
||||
let state = this._tabStateToRestore.get(browser.permanentKey);
|
||||
this._tabStateToRestore.delete(browser.permanentKey);
|
||||
|
||||
let data = {
|
||||
...options,
|
||||
...this._shistoryToRestore.get(browser.permanentKey),
|
||||
this._restoreListeners.get(browser.permanentKey)?.unregister();
|
||||
|
||||
this._restoreTabContentStarted(browser, options);
|
||||
const onResolve = () => {
|
||||
this._restoreTabContentComplete(browser, options);
|
||||
};
|
||||
this._shistoryToRestore.delete(browser.permanentKey);
|
||||
|
||||
this._restoreTabContentStarted(browser, data);
|
||||
let promise;
|
||||
|
||||
let tabData = data.tabData || {};
|
||||
let uri = null;
|
||||
let loadFlags = null;
|
||||
let promises = [];
|
||||
|
||||
if (tabData.userTypedValue && tabData.userTypedClear) {
|
||||
uri = tabData.userTypedValue;
|
||||
loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
|
||||
} else if (tabData.entries.length) {
|
||||
promises.push(
|
||||
SessionStoreUtils.initializeRestore(
|
||||
browser.browsingContext,
|
||||
this.buildRestoreData(tabData.formdata, tabData.scroll)
|
||||
)
|
||||
);
|
||||
if (state) {
|
||||
promise = this._restoreTabEntry(browser, state.tabData);
|
||||
} else {
|
||||
uri = "about:blank";
|
||||
loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY;
|
||||
// The browser started another load, so we decided to not restore
|
||||
// saved tab data. We should wait for that new load to finish before
|
||||
// proceeding.
|
||||
promise = this._waitForStateStop(browser);
|
||||
}
|
||||
|
||||
if (uri && loadFlags) {
|
||||
let deferred = PromiseUtils.defer();
|
||||
promises.push(deferred.promise);
|
||||
|
||||
this.addProgressListenerForRestore(browser, {
|
||||
onStopRequest: (request, listener) => {
|
||||
let requestURI = request.QueryInterface(Ci.nsIChannel)?.originalURI;
|
||||
// FIXME: We sometimes see spurious STATE_STOP events for about:blank
|
||||
// URIs, so we have to manually drop those here (unless we're actually
|
||||
// expecting an about:blank load).
|
||||
if (requestURI?.spec !== "about:blank" || uri === "about:blank") {
|
||||
listener.uninstall();
|
||||
deferred.resolve();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
browser.browsingContext.loadURI(uri, {
|
||||
loadFlags,
|
||||
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
|
||||
});
|
||||
}
|
||||
|
||||
Promise.allSettled(promises).then(() => {
|
||||
// We may have stopped or restarted the restore for this browser prior to
|
||||
// the resolution of the promises, so this ensures that we're only
|
||||
// "completing the restore" for browsers that are actively being restored.
|
||||
if (TAB_STATE_FOR_BROWSER.get(browser) === TAB_STATE_RESTORING) {
|
||||
this._restoreTabContentComplete(browser, data);
|
||||
}
|
||||
});
|
||||
promise.then(onResolve).catch(() => {});
|
||||
},
|
||||
|
||||
_sendRestoreTabContent(browser, options) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче