зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1507287 - Make sessionRestore work with session history living in the parent process. r=peterv,mikedeboer
Differential Revision: https://phabricator.services.mozilla.com/D46281 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
93ec4f0381
Коммит
cb65e20cb3
|
@ -37,7 +37,6 @@ const whitelist = {
|
|||
|
||||
// Session store
|
||||
"resource:///modules/sessionstore/ContentSessionStore.jsm",
|
||||
"resource://gre/modules/sessionstore/SessionHistory.jsm",
|
||||
|
||||
// Browser front-end
|
||||
"resource:///actors/AboutReaderChild.jsm",
|
||||
|
@ -86,6 +85,10 @@ const intermittently_loaded_whitelist = {
|
|||
"resource://gre/modules/nsAsyncShutdown.jsm",
|
||||
"resource://gre/modules/sessionstore/Utils.jsm",
|
||||
|
||||
// Session store.
|
||||
"resource://gre/modules/sessionstore/SessionHistory.jsm",
|
||||
"resource:///modules/sessionstore/SessionHistoryListener.jsm",
|
||||
|
||||
"resource://specialpowers/SpecialPowersChild.jsm",
|
||||
"resource://specialpowers/WrapPrivileged.jsm",
|
||||
|
||||
|
|
|
@ -58,6 +58,8 @@ function ContentRestore(chromeGlobal) {
|
|||
|
||||
let EXPORTED_METHODS = [
|
||||
"restoreHistory",
|
||||
"finishRestoreHistory",
|
||||
"restoreOnNewEntry",
|
||||
"restoreTabContent",
|
||||
"restoreDocument",
|
||||
"resetRestore",
|
||||
|
@ -94,6 +96,25 @@ function ContentRestoreInternal(chromeGlobal) {
|
|||
// data from the network. Set in restoreHistory() and restoreTabContent(),
|
||||
// removed in resetRestore().
|
||||
this._progressListener = null;
|
||||
|
||||
this._shistoryInParent = false;
|
||||
}
|
||||
|
||||
function kickOffNewLoadFromBlankPage(webNavigation, newURI) {
|
||||
// Reset the tab's URL to what it's actually showing. Without this loadURI()
|
||||
// would use the current document and change the displayed URL only.
|
||||
webNavigation.setCurrentURI(Services.io.newURI("about:blank"));
|
||||
|
||||
// Kick off a new load so that we navigate away from about:blank to the
|
||||
// new URL that was passed to loadURI(). The new load will cause a
|
||||
// STATE_START notification to be sent and the ProgressListener will then
|
||||
// notify the parent and do the rest.
|
||||
let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
|
||||
let loadURIOptions = {
|
||||
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
|
||||
loadFlags,
|
||||
};
|
||||
webNavigation.loadURI(newURI, loadURIOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -111,7 +132,7 @@ ContentRestoreInternal.prototype = {
|
|||
* non-zero) is passed through to all the callbacks. If a load in the tab
|
||||
* is started while it is pending, the appropriate callbacks are called.
|
||||
*/
|
||||
restoreHistory(tabData, loadArguments, callbacks) {
|
||||
restoreHistory(tabData, loadArguments, callbacks, shistoryInParent) {
|
||||
this._tabData = tabData;
|
||||
|
||||
// In case about:blank isn't done yet.
|
||||
|
@ -130,27 +151,46 @@ ContentRestoreInternal.prototype = {
|
|||
webNavigation.setCurrentURI(Services.io.newURI(uri));
|
||||
}
|
||||
|
||||
SessionHistory.restore(this.docShell, tabData);
|
||||
this._shistoryInParent = shistoryInParent;
|
||||
|
||||
// Add a listener to watch for reloads.
|
||||
let listener = new HistoryListener(this.docShell, () => {
|
||||
// On reload, restore tab contents.
|
||||
this.restoreTabContent(null, false, callbacks.onLoadFinished);
|
||||
});
|
||||
if (this._shistoryInParent) {
|
||||
callbacks.requestRestoreSHistory();
|
||||
} else {
|
||||
SessionHistory.restore(this.docShell, tabData);
|
||||
|
||||
webNavigation.sessionHistory.legacySHistory.addSHistoryListener(listener);
|
||||
this._historyListener = listener;
|
||||
// Add a listener to watch for reloads.
|
||||
let listener = new HistoryListener(this.docShell, () => {
|
||||
// On reload, restore tab contents.
|
||||
this.restoreTabContent(
|
||||
null,
|
||||
false,
|
||||
callbacks.onLoadFinished,
|
||||
null,
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
webNavigation.sessionHistory.legacySHistory.addSHistoryListener(listener);
|
||||
this._historyListener = listener;
|
||||
|
||||
this.finishRestoreHistory(callbacks);
|
||||
}
|
||||
},
|
||||
|
||||
finishRestoreHistory(callbacks) {
|
||||
// Make sure to reset the capabilities and attributes in case this tab gets
|
||||
// reused.
|
||||
SessionStoreUtils.restoreDocShellCapabilities(
|
||||
this.docShell,
|
||||
tabData.disallow
|
||||
this._tabData.disallow
|
||||
);
|
||||
|
||||
if (tabData.storage && this.docShell instanceof Ci.nsIDocShell) {
|
||||
SessionStoreUtils.restoreSessionStorage(this.docShell, tabData.storage);
|
||||
delete tabData.storage;
|
||||
if (this._tabData.storage && this.docShell instanceof Ci.nsIDocShell) {
|
||||
SessionStoreUtils.restoreSessionStorage(
|
||||
this.docShell,
|
||||
this._tabData.storage
|
||||
);
|
||||
delete this._tabData.storage;
|
||||
}
|
||||
|
||||
// Add a progress listener to correctly handle browser.loadURI()
|
||||
|
@ -162,7 +202,10 @@ ContentRestoreInternal.prototype = {
|
|||
this._tabData = null;
|
||||
|
||||
// Listen for the tab to finish loading.
|
||||
this.restoreTabContentStarted(callbacks.onLoadFinished);
|
||||
this.restoreTabContentStarted(
|
||||
callbacks.onLoadFinished,
|
||||
callbacks.removeRestoreListener
|
||||
);
|
||||
|
||||
// Notify the parent.
|
||||
callbacks.onLoadStarted();
|
||||
|
@ -170,11 +213,22 @@ ContentRestoreInternal.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
restoreOnNewEntry(newURI) {
|
||||
let webNavigation = this.docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
kickOffNewLoadFromBlankPage(webNavigation, newURI);
|
||||
},
|
||||
|
||||
/**
|
||||
* Start loading the current page. When the data has finished loading from the
|
||||
* network, finishCallback is called. Returns true if the load was successful.
|
||||
*/
|
||||
restoreTabContent(loadArguments, isRemotenessUpdate, finishCallback) {
|
||||
restoreTabContent(
|
||||
loadArguments,
|
||||
isRemotenessUpdate,
|
||||
finishCallback,
|
||||
removeListenerCallback,
|
||||
reloadSHistoryCallback
|
||||
) {
|
||||
let tabData = this._tabData;
|
||||
this._tabData = null;
|
||||
|
||||
|
@ -182,7 +236,7 @@ ContentRestoreInternal.prototype = {
|
|||
let history = webNavigation.sessionHistory.legacySHistory;
|
||||
|
||||
// Listen for the tab to finish loading.
|
||||
this.restoreTabContentStarted(finishCallback);
|
||||
this.restoreTabContentStarted(finishCallback, removeListenerCallback);
|
||||
|
||||
// Reset the current URI to about:blank. We changed it above for
|
||||
// switch-to-tab, but now it must go back to the correct value before the
|
||||
|
@ -273,7 +327,11 @@ ContentRestoreInternal.prototype = {
|
|||
// In order to work around certain issues in session history, we need to
|
||||
// force session history to update its internal index and call reload
|
||||
// instead of gotoIndex. See bug 597315.
|
||||
history.reloadCurrentEntry();
|
||||
if (this._shistoryInParent) {
|
||||
reloadSHistoryCallback();
|
||||
} else {
|
||||
history.reloadCurrentEntry();
|
||||
}
|
||||
} else {
|
||||
// If there's nothing to restore, we should still blank the page.
|
||||
let loadURIOptions = {
|
||||
|
@ -298,10 +356,14 @@ ContentRestoreInternal.prototype = {
|
|||
* To be called after restoreHistory(). Removes all listeners needed for
|
||||
* pending tabs and makes sure to notify when the tab finished loading.
|
||||
*/
|
||||
restoreTabContentStarted(finishCallback) {
|
||||
restoreTabContentStarted(finishCallback, removeListenerCallback) {
|
||||
// The reload listener is no longer needed.
|
||||
this._historyListener.uninstall();
|
||||
this._historyListener = null;
|
||||
if (!this._shistoryInParent) {
|
||||
this._historyListener.uninstall();
|
||||
this._historyListener = null;
|
||||
} else {
|
||||
removeListenerCallback();
|
||||
}
|
||||
|
||||
// Remove the old progress listener.
|
||||
this._progressListener.uninstall();
|
||||
|
@ -415,20 +477,7 @@ HistoryListener.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
// Reset the tab's URL to what it's actually showing. Without this loadURI()
|
||||
// would use the current document and change the displayed URL only.
|
||||
this.webNavigation.setCurrentURI(Services.io.newURI("about:blank"));
|
||||
|
||||
// Kick off a new load so that we navigate away from about:blank to the
|
||||
// new URL that was passed to loadURI(). The new load will cause a
|
||||
// STATE_START notification to be sent and the ProgressListener will then
|
||||
// notify the parent and do the rest.
|
||||
let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
|
||||
let loadURIOptions = {
|
||||
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
|
||||
loadFlags,
|
||||
};
|
||||
this.webNavigation.loadURI(newURI.spec, loadURIOptions);
|
||||
kickOffNewLoadFromBlankPage(this.webNavigation, newURI);
|
||||
},
|
||||
|
||||
OnHistoryReload() {
|
||||
|
|
|
@ -21,8 +21,8 @@ ChromeUtils.defineModuleGetter(
|
|||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"SessionHistory",
|
||||
"resource://gre/modules/sessionstore/SessionHistory.jsm"
|
||||
"SessionHistoryListener",
|
||||
"resource:///modules/sessionstore/SessionHistoryListener.jsm"
|
||||
);
|
||||
|
||||
// This pref controls whether or not we send updates to the parent on a timeout
|
||||
|
@ -31,9 +31,6 @@ const TIMEOUT_DISABLED_PREF = "browser.sessionstore.debug.no_auto_updates";
|
|||
|
||||
const PREF_INTERVAL = "browser.sessionstore.interval";
|
||||
|
||||
const kNoIndex = Number.MAX_SAFE_INTEGER;
|
||||
const kLastIndex = Number.MAX_SAFE_INTEGER - 1;
|
||||
|
||||
class Handler {
|
||||
constructor(store) {
|
||||
this.store = store;
|
||||
|
@ -54,83 +51,8 @@ class Handler {
|
|||
get messageQueue() {
|
||||
return this.store.messageQueue;
|
||||
}
|
||||
|
||||
get stateChangeNotifier() {
|
||||
return this.store.stateChangeNotifier;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for state change notifcations from webProgress and notifies each
|
||||
* registered observer for either the start of a page load, or its completion.
|
||||
*/
|
||||
class StateChangeNotifier extends Handler {
|
||||
constructor(store) {
|
||||
super(store);
|
||||
|
||||
this._observers = new Set();
|
||||
let ifreq = this.mm.docShell.QueryInterface(Ci.nsIInterfaceRequestor);
|
||||
let webProgress = ifreq.getInterface(Ci.nsIWebProgress);
|
||||
webProgress.addProgressListener(
|
||||
this,
|
||||
Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a given observer |obs| to the set of observers that will be notified
|
||||
* when when a new document starts or finishes loading.
|
||||
*
|
||||
* @param obs (object)
|
||||
*/
|
||||
addObserver(obs) {
|
||||
this._observers.add(obs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies all observers that implement the given |method|.
|
||||
*
|
||||
* @param method (string)
|
||||
*/
|
||||
notifyObservers(method) {
|
||||
for (let obs of this._observers) {
|
||||
if (typeof obs[method] == "function") {
|
||||
obs[method]();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see nsIWebProgressListener.onStateChange
|
||||
*/
|
||||
onStateChange(webProgress, request, stateFlags, status) {
|
||||
// Ignore state changes for subframes because we're only interested in the
|
||||
// top-document starting or stopping its load.
|
||||
if (!webProgress.isTopLevel || webProgress.DOMWindow != this.mm.content) {
|
||||
return;
|
||||
}
|
||||
|
||||
// onStateChange will be fired when loading the initial about:blank URI for
|
||||
// a browser, which we don't actually care about. This is particularly for
|
||||
// the case of unrestored background tabs, where the content has not yet
|
||||
// been restored: we don't want to accidentally send any updates to the
|
||||
// parent when the about:blank placeholder page has loaded.
|
||||
if (!this.mm.docShell.hasLoadedNonBlankURI) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
|
||||
this.notifyObservers("onPageLoadStarted");
|
||||
} else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
|
||||
this.notifyObservers("onPageLoadCompleted");
|
||||
}
|
||||
}
|
||||
}
|
||||
StateChangeNotifier.prototype.QueryInterface = ChromeUtils.generateQI([
|
||||
Ci.nsIWebProgressListener,
|
||||
Ci.nsISupportsWeakReference,
|
||||
]);
|
||||
|
||||
/**
|
||||
* Listens for and handles content events that we need for the
|
||||
* session store service to be notified of state changes in content.
|
||||
|
@ -177,139 +99,6 @@ class EventListener extends Handler {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for changes to the session history. Whenever the user navigates
|
||||
* we will collect URLs and everything belonging to session history.
|
||||
*
|
||||
* Causes a SessionStore:update message to be sent that contains the current
|
||||
* session history.
|
||||
*
|
||||
* Example:
|
||||
* {entries: [{url: "about:mozilla", ...}, ...], index: 1}
|
||||
*/
|
||||
class SessionHistoryListener extends Handler {
|
||||
constructor(store) {
|
||||
super(store);
|
||||
|
||||
this._fromIdx = kNoIndex;
|
||||
|
||||
// The state change observer is needed to handle initial subframe loads.
|
||||
// It will redundantly invalidate with the SHistoryListener in some cases
|
||||
// but these invalidations are very cheap.
|
||||
this.stateChangeNotifier.addObserver(this);
|
||||
|
||||
// By adding the SHistoryListener immediately, we will unfortunately be
|
||||
// notified of every history entry as the tab is restored. We don't bother
|
||||
// waiting to add the listener later because these notifications are cheap.
|
||||
// We will likely only collect once since we are batching collection on
|
||||
// a delay.
|
||||
this.mm.docShell
|
||||
.QueryInterface(Ci.nsIWebNavigation)
|
||||
.sessionHistory.legacySHistory.addSHistoryListener(this);
|
||||
|
||||
// Collect data if we start with a non-empty shistory.
|
||||
if (!SessionHistory.isEmpty(this.mm.docShell)) {
|
||||
this.collect();
|
||||
// When a tab is detached from the window, for the new window there is a
|
||||
// new SessionHistoryListener created. Normally it is empty at this point
|
||||
// but in a test env. the initial about:blank might have a children in which
|
||||
// case we fire off a history message here with about:blank in it. If we
|
||||
// don't do it ASAP then there is going to be a browser swap and the parent
|
||||
// will be all confused by that message.
|
||||
this.messageQueue.send();
|
||||
}
|
||||
|
||||
// Listen for page title changes.
|
||||
this.mm.addEventListener("DOMTitleChanged", this);
|
||||
}
|
||||
|
||||
uninit() {
|
||||
let sessionHistory = this.mm.docShell.QueryInterface(Ci.nsIWebNavigation)
|
||||
.sessionHistory;
|
||||
if (sessionHistory) {
|
||||
sessionHistory.legacySHistory.removeSHistoryListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
collect() {
|
||||
// We want to send down a historychange even for full collects in case our
|
||||
// session history is a partial session history, in which case we don't have
|
||||
// enough information for a full update. collectFrom(-1) tells the collect
|
||||
// function to collect all data avaliable in this process.
|
||||
if (this.mm.docShell) {
|
||||
this.collectFrom(-1);
|
||||
}
|
||||
}
|
||||
|
||||
// History can grow relatively big with the nested elements, so if we don't have to, we
|
||||
// don't want to send the entire history all the time. For a simple optimization
|
||||
// we keep track of the smallest index from after any change has occured and we just send
|
||||
// the elements from that index. If something more complicated happens we just clear it
|
||||
// and send the entire history. We always send the additional info like the current selected
|
||||
// index (so for going back and forth between history entries we set the index to kLastIndex
|
||||
// if nothing else changed send an empty array and the additonal info like the selected index)
|
||||
collectFrom(idx) {
|
||||
if (this._fromIdx <= idx) {
|
||||
// If we already know that we need to update history fromn index N we can ignore any changes
|
||||
// tha happened with an element with index larger than N.
|
||||
// Note: initially we use kNoIndex which is MAX_SAFE_INTEGER which means we don't ignore anything
|
||||
// here, and in case of navigation in the history back and forth we use kLastIndex which ignores
|
||||
// only the subsequent navigations, but not any new elements added.
|
||||
return;
|
||||
}
|
||||
|
||||
this._fromIdx = idx;
|
||||
this.messageQueue.push("historychange", () => {
|
||||
if (this._fromIdx === kNoIndex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let history = SessionHistory.collect(this.mm.docShell, this._fromIdx);
|
||||
this._fromIdx = kNoIndex;
|
||||
return history;
|
||||
});
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
this.collect();
|
||||
}
|
||||
|
||||
onPageLoadCompleted() {
|
||||
this.collect();
|
||||
}
|
||||
|
||||
onPageLoadStarted() {
|
||||
this.collect();
|
||||
}
|
||||
|
||||
OnHistoryNewEntry(newURI, oldIndex) {
|
||||
// We ought to collect the previously current entry as well, see bug 1350567.
|
||||
this.collectFrom(oldIndex);
|
||||
}
|
||||
|
||||
OnHistoryGotoIndex() {
|
||||
// We ought to collect the previously current entry as well, see bug 1350567.
|
||||
this.collectFrom(kLastIndex);
|
||||
}
|
||||
|
||||
OnHistoryPurge() {
|
||||
this.collect();
|
||||
}
|
||||
|
||||
OnHistoryReload() {
|
||||
this.collect();
|
||||
return true;
|
||||
}
|
||||
|
||||
OnHistoryReplaceEntry() {
|
||||
this.collect();
|
||||
}
|
||||
}
|
||||
SessionHistoryListener.prototype.QueryInterface = ChromeUtils.generateQI([
|
||||
Ci.nsISHistoryListener,
|
||||
Ci.nsISupportsWeakReference,
|
||||
]);
|
||||
|
||||
/**
|
||||
* A message queue that takes collected data and will take care of sending it
|
||||
* to the chrome process. It allows flushing using synchronous messages and
|
||||
|
@ -550,6 +339,9 @@ class MessageQueue extends Handler {
|
|||
*/
|
||||
const MESSAGES = [
|
||||
"SessionStore:restoreHistory",
|
||||
"SessionStore:finishRestoreHistory",
|
||||
"SessionStore:OnHistoryReload",
|
||||
"SessionStore:OnHistoryNewEntry",
|
||||
"SessionStore:restoreTabContent",
|
||||
"SessionStore:resetRestore",
|
||||
"SessionStore:flush",
|
||||
|
@ -560,23 +352,30 @@ class ContentSessionStore {
|
|||
constructor(mm) {
|
||||
this.mm = mm;
|
||||
this.messageQueue = new MessageQueue(this);
|
||||
this.stateChangeNotifier = new StateChangeNotifier(this);
|
||||
|
||||
this.epoch = 0;
|
||||
|
||||
this.contentRestoreInitialized = false;
|
||||
|
||||
this.waitRestoreSHistoryInParent = false;
|
||||
this.restoreTabContentData = null;
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "contentRestore", () => {
|
||||
this.contentRestoreInitialized = true;
|
||||
return new ContentRestore(mm);
|
||||
});
|
||||
|
||||
this.handlers = [
|
||||
new EventListener(this),
|
||||
new SessionHistoryListener(this),
|
||||
this.stateChangeNotifier,
|
||||
this.messageQueue,
|
||||
];
|
||||
this.handlers = [new EventListener(this), this.messageQueue];
|
||||
|
||||
this._shistoryInParent = Services.prefs.getBoolPref(
|
||||
"fission.sessionHistoryInParent",
|
||||
false
|
||||
);
|
||||
if (this._shistoryInParent) {
|
||||
this.mm.sendAsyncMessage("SessionStore:addSHistoryListener");
|
||||
} else {
|
||||
this.handlers.push(new SessionHistoryListener(this));
|
||||
}
|
||||
|
||||
MESSAGES.forEach(m => mm.addMessageListener(m, this));
|
||||
|
||||
|
@ -597,7 +396,7 @@ class ContentSessionStore {
|
|||
// override that to signal a new era in this tab's life. This enables it
|
||||
// to ignore async messages that were already sent but not yet received
|
||||
// and would otherwise confuse the internal tab state.
|
||||
if (data.epoch && data.epoch != this.epoch) {
|
||||
if (data && data.epoch && data.epoch != this.epoch) {
|
||||
this.epoch = data.epoch;
|
||||
}
|
||||
|
||||
|
@ -605,8 +404,45 @@ class ContentSessionStore {
|
|||
case "SessionStore:restoreHistory":
|
||||
this.restoreHistory(data);
|
||||
break;
|
||||
case "SessionStore:finishRestoreHistory":
|
||||
this.finishRestoreHistory();
|
||||
break;
|
||||
case "SessionStore:OnHistoryNewEntry":
|
||||
this.contentRestore.restoreOnNewEntry(data.uri);
|
||||
break;
|
||||
case "SessionStore:OnHistoryReload":
|
||||
// On reload, restore tab contents.
|
||||
this.contentRestore.restoreTabContent(
|
||||
null,
|
||||
false,
|
||||
() => {
|
||||
// Tell SessionStore.jsm that it may want to restore some more tabs,
|
||||
// since it restores a max of MAX_CONCURRENT_TAB_RESTORES at a time.
|
||||
this.mm.sendAsyncMessage("SessionStore:restoreTabContentComplete", {
|
||||
epoch: this.epoch,
|
||||
});
|
||||
},
|
||||
() => {
|
||||
// Tell SessionStore.jsm to remove restoreListener.
|
||||
this.mm.sendAsyncMessage("SessionStore:removeRestoreListener", {
|
||||
epoch: this.epoch,
|
||||
});
|
||||
},
|
||||
() => {
|
||||
// Tell SessionStore.jsm to reload currentEntry.
|
||||
this.mm.sendAsyncMessage("SessionStore:reloadCurrentEntry", {
|
||||
epoch: this.epoch,
|
||||
});
|
||||
}
|
||||
);
|
||||
break;
|
||||
case "SessionStore:restoreTabContent":
|
||||
this.restoreTabContent(data);
|
||||
if (this.waitRestoreSHistoryInParent) {
|
||||
// Queue the TabContentData if we haven't finished sHistoryRestore yet.
|
||||
this.restoreTabContentData = data;
|
||||
} else {
|
||||
this.restoreTabContent(data);
|
||||
}
|
||||
break;
|
||||
case "SessionStore:resetRestore":
|
||||
this.contentRestore.resetRestore();
|
||||
|
@ -615,7 +451,9 @@ class ContentSessionStore {
|
|||
this.flush(data);
|
||||
break;
|
||||
case "SessionStore:becomeActiveProcess":
|
||||
SessionHistoryListener.collect();
|
||||
if (!this._shistoryInParent) {
|
||||
SessionHistoryListener.collect();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
debug("received unknown message '" + name + "'");
|
||||
|
@ -624,27 +462,55 @@ class ContentSessionStore {
|
|||
}
|
||||
|
||||
restoreHistory({ epoch, tabData, loadArguments, isRemotenessUpdate }) {
|
||||
this.contentRestore.restoreHistory(tabData, loadArguments, {
|
||||
// Note: The callbacks passed here will only be used when a load starts
|
||||
// that was not initiated by sessionstore itself. This can happen when
|
||||
// some code calls browser.loadURI() or browser.reload() on a pending
|
||||
// browser/tab.
|
||||
this.contentRestore.restoreHistory(
|
||||
tabData,
|
||||
loadArguments,
|
||||
{
|
||||
// Note: The callbacks passed here will only be used when a load starts
|
||||
// that was not initiated by sessionstore itself. This can happen when
|
||||
// some code calls browser.loadURI() or browser.reload() on a pending
|
||||
// browser/tab.
|
||||
|
||||
onLoadStarted: () => {
|
||||
// Notify the parent that the tab is no longer pending.
|
||||
this.mm.sendAsyncMessage("SessionStore:restoreTabContentStarted", {
|
||||
epoch,
|
||||
});
|
||||
},
|
||||
onLoadStarted: () => {
|
||||
// Notify the parent that the tab is no longer pending.
|
||||
this.mm.sendAsyncMessage("SessionStore:restoreTabContentStarted", {
|
||||
epoch,
|
||||
});
|
||||
},
|
||||
|
||||
onLoadFinished: () => {
|
||||
// Tell SessionStore.jsm that it may want to restore some more tabs,
|
||||
// since it restores a max of MAX_CONCURRENT_TAB_RESTORES at a time.
|
||||
this.mm.sendAsyncMessage("SessionStore:restoreTabContentComplete", {
|
||||
epoch,
|
||||
});
|
||||
onLoadFinished: () => {
|
||||
// Tell SessionStore.jsm that it may want to restore some more tabs,
|
||||
// since it restores a max of MAX_CONCURRENT_TAB_RESTORES at a time.
|
||||
this.mm.sendAsyncMessage("SessionStore:restoreTabContentComplete", {
|
||||
epoch,
|
||||
});
|
||||
},
|
||||
|
||||
removeRestoreListener: () => {
|
||||
if (!this._shistoryInParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify the parent that the tab is no longer pending.
|
||||
this.mm.sendAsyncMessage("SessionStore:removeRestoreListener", {
|
||||
epoch,
|
||||
});
|
||||
},
|
||||
|
||||
requestRestoreSHistory: () => {
|
||||
if (!this._shistoryInParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.waitRestoreSHistoryInParent = true;
|
||||
// Send tabData to the parent process.
|
||||
this.mm.sendAsyncMessage("SessionStore:restoreSHistoryInParent", {
|
||||
epoch,
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
this._shistoryInParent
|
||||
);
|
||||
|
||||
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT) {
|
||||
// For non-remote tabs, when restoreHistory finishes, we send a synchronous
|
||||
|
@ -661,7 +527,7 @@ class ContentSessionStore {
|
|||
epoch,
|
||||
isRemotenessUpdate,
|
||||
});
|
||||
} else {
|
||||
} else if (!this._shistoryInParent) {
|
||||
this.mm.sendAsyncMessage("SessionStore:restoreHistoryComplete", {
|
||||
epoch,
|
||||
isRemotenessUpdate,
|
||||
|
@ -669,6 +535,49 @@ class ContentSessionStore {
|
|||
}
|
||||
}
|
||||
|
||||
finishRestoreHistory() {
|
||||
this.contentRestore.finishRestoreHistory({
|
||||
// Note: The callbacks passed here will only be used when a load starts
|
||||
// that was not initiated by sessionstore itself. This can happen when
|
||||
// some code calls browser.loadURI() or browser.reload() on a pending
|
||||
// browser/tab.
|
||||
onLoadStarted: () => {
|
||||
// Notify the parent that the tab is no longer pending.
|
||||
this.mm.sendAsyncMessage("SessionStore:restoreTabContentStarted", {
|
||||
epoch: this.epoch,
|
||||
});
|
||||
},
|
||||
|
||||
onLoadFinished: () => {
|
||||
// Tell SessionStore.jsm that it may want to restore some more tabs,
|
||||
// since it restores a max of MAX_CONCURRENT_TAB_RESTORES at a time.
|
||||
this.mm.sendAsyncMessage("SessionStore:restoreTabContentComplete", {
|
||||
epoch: this.epoch,
|
||||
});
|
||||
},
|
||||
|
||||
removeRestoreListener: () => {
|
||||
if (!this._shistoryInParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify the parent that the tab is no longer pending.
|
||||
this.mm.sendAsyncMessage("SessionStore:removeRestoreListener", {
|
||||
epoch: this.epoch,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
this.mm.sendAsyncMessage("SessionStore:restoreHistoryComplete", {
|
||||
epoch: this.epoch,
|
||||
});
|
||||
if (this.restoreTabContentData) {
|
||||
this.restoreTabContent(this.restoreTabContentData);
|
||||
this.restoreTabContentData = null;
|
||||
}
|
||||
this.waitRestoreSHistoryInParent = false;
|
||||
}
|
||||
|
||||
restoreTabContent({ loadArguments, isRemotenessUpdate, reason }) {
|
||||
let epoch = this.epoch;
|
||||
|
||||
|
@ -683,6 +592,17 @@ class ContentSessionStore {
|
|||
epoch,
|
||||
isRemotenessUpdate,
|
||||
});
|
||||
},
|
||||
() => {
|
||||
// Tell SessionStore.jsm to remove restore listener.
|
||||
this.mm.sendAsyncMessage("SessionStore:removeRestoreListener", {
|
||||
epoch,
|
||||
});
|
||||
},
|
||||
() => {
|
||||
this.mm.sendAsyncMessage("SessionStore:reloadCurrentEntry", {
|
||||
epoch,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["SessionHistoryListener"];
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"SessionHistory",
|
||||
"resource://gre/modules/sessionstore/SessionHistory.jsm"
|
||||
);
|
||||
|
||||
const kNoIndex = Number.MAX_SAFE_INTEGER;
|
||||
const kLastIndex = Number.MAX_SAFE_INTEGER - 1;
|
||||
|
||||
/**
|
||||
* Listens for state change notifcations from webProgress and notifies each
|
||||
* registered observer for either the start of a page load, or its completion.
|
||||
*/
|
||||
class StateChangeNotifier {
|
||||
constructor(store) {
|
||||
this.store = store;
|
||||
|
||||
this._observers = new Set();
|
||||
|
||||
let webProgress = this.mm.docShell
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebProgress);
|
||||
|
||||
webProgress.addProgressListener(
|
||||
this,
|
||||
Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT
|
||||
);
|
||||
}
|
||||
|
||||
get mm() {
|
||||
return this.store.mm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a given observer |obs| to the set of observers that will be notified
|
||||
* when when a new document starts or finishes loading.
|
||||
*
|
||||
* @param obs (object)
|
||||
*/
|
||||
addObserver(obs) {
|
||||
this._observers.add(obs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies all observers that implement the given |method|.
|
||||
*
|
||||
* @param method (string)
|
||||
*/
|
||||
notifyObservers(method) {
|
||||
for (let obs of this._observers) {
|
||||
if (typeof obs[method] == "function") {
|
||||
obs[method]();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see nsIWebProgressListener.onStateChange
|
||||
*/
|
||||
onStateChange(webProgress, request, stateFlags, status) {
|
||||
// Ignore state changes for subframes because we're only interested in the
|
||||
// top-document starting or stopping its load.
|
||||
if (!webProgress.isTopLevel || webProgress.DOMWindow != this.mm.content) {
|
||||
return;
|
||||
}
|
||||
|
||||
// onStateChange will be fired when loading the initial about:blank URI for
|
||||
// a browser, which we don't actually care about. This is particularly for
|
||||
// the case of unrestored background tabs, where the content has not yet
|
||||
// been restored: we don't want to accidentally send any updates to the
|
||||
// parent when the about:blank placeholder page has loaded.
|
||||
if (!this.mm.docShell.hasLoadedNonBlankURI) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
|
||||
this.notifyObservers("onPageLoadStarted");
|
||||
} else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
|
||||
this.notifyObservers("onPageLoadCompleted");
|
||||
}
|
||||
}
|
||||
}
|
||||
StateChangeNotifier.prototype.QueryInterface = ChromeUtils.generateQI([
|
||||
Ci.nsIWebProgressListener,
|
||||
Ci.nsISupportsWeakReference,
|
||||
]);
|
||||
|
||||
/**
|
||||
* Listens for changes to the session history. Whenever the user navigates
|
||||
* we will collect URLs and everything belonging to session history.
|
||||
*
|
||||
* Causes a SessionStore:update message to be sent that contains the current
|
||||
* session history.
|
||||
*
|
||||
* Example:
|
||||
* {entries: [{url: "about:mozilla", ...}, ...], index: 1}
|
||||
*/
|
||||
class SessionHistoryListener {
|
||||
constructor(store) {
|
||||
this.store = store;
|
||||
|
||||
this._fromIdx = kNoIndex;
|
||||
|
||||
// The state change observer is needed to handle initial subframe loads.
|
||||
// It will redundantly invalidate with the SHistoryListener in some cases
|
||||
// but these invalidations are very cheap.
|
||||
this.stateChangeNotifier = new StateChangeNotifier(this);
|
||||
this.stateChangeNotifier.addObserver(this);
|
||||
|
||||
// By adding the SHistoryListener immediately, we will unfortunately be
|
||||
// notified of every history entry as the tab is restored. We don't bother
|
||||
// waiting to add the listener later because these notifications are cheap.
|
||||
// We will likely only collect once since we are batching collection on
|
||||
// a delay.
|
||||
this.mm.docShell
|
||||
.QueryInterface(Ci.nsIWebNavigation)
|
||||
.sessionHistory.legacySHistory.addSHistoryListener(this);
|
||||
|
||||
// Collect data if we start with a non-empty shistory.
|
||||
if (!SessionHistory.isEmpty(this.mm.docShell)) {
|
||||
this.collect();
|
||||
// When a tab is detached from the window, for the new window there is a
|
||||
// new SessionHistoryListener created. Normally it is empty at this point
|
||||
// but in a test env. the initial about:blank might have a children in which
|
||||
// case we fire off a history message here with about:blank in it. If we
|
||||
// don't do it ASAP then there is going to be a browser swap and the parent
|
||||
// will be all confused by that message.
|
||||
this.store.messageQueue.send();
|
||||
}
|
||||
|
||||
// Listen for page title changes.
|
||||
this.mm.addEventListener("DOMTitleChanged", this);
|
||||
}
|
||||
|
||||
get mm() {
|
||||
return this.store.mm;
|
||||
}
|
||||
|
||||
uninit() {
|
||||
let sessionHistory = this.mm.docShell.QueryInterface(Ci.nsIWebNavigation)
|
||||
.sessionHistory;
|
||||
if (sessionHistory) {
|
||||
sessionHistory.legacySHistory.removeSHistoryListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
collect() {
|
||||
// We want to send down a historychange even for full collects in case our
|
||||
// session history is a partial session history, in which case we don't have
|
||||
// enough information for a full update. collectFrom(-1) tells the collect
|
||||
// function to collect all data avaliable in this process.
|
||||
if (this.mm.docShell) {
|
||||
this.collectFrom(-1);
|
||||
}
|
||||
}
|
||||
|
||||
// History can grow relatively big with the nested elements, so if we don't have to, we
|
||||
// don't want to send the entire history all the time. For a simple optimization
|
||||
// we keep track of the smallest index from after any change has occured and we just send
|
||||
// the elements from that index. If something more complicated happens we just clear it
|
||||
// and send the entire history. We always send the additional info like the current selected
|
||||
// index (so for going back and forth between history entries we set the index to kLastIndex
|
||||
// if nothing else changed send an empty array and the additonal info like the selected index)
|
||||
collectFrom(idx) {
|
||||
if (this._fromIdx <= idx) {
|
||||
// If we already know that we need to update history fromn index N we can ignore any changes
|
||||
// tha happened with an element with index larger than N.
|
||||
// Note: initially we use kNoIndex which is MAX_SAFE_INTEGER which means we don't ignore anything
|
||||
// here, and in case of navigation in the history back and forth we use kLastIndex which ignores
|
||||
// only the subsequent navigations, but not any new elements added.
|
||||
return;
|
||||
}
|
||||
|
||||
this._fromIdx = idx;
|
||||
this.store.messageQueue.push("historychange", () => {
|
||||
if (this._fromIdx === kNoIndex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let history = SessionHistory.collect(this.mm.docShell, this._fromIdx);
|
||||
this._fromIdx = kNoIndex;
|
||||
return history;
|
||||
});
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
this.collect();
|
||||
}
|
||||
|
||||
onPageLoadCompleted() {
|
||||
this.collect();
|
||||
}
|
||||
|
||||
onPageLoadStarted() {
|
||||
this.collect();
|
||||
}
|
||||
|
||||
OnHistoryNewEntry(newURI, oldIndex) {
|
||||
// We ought to collect the previously current entry as well, see bug 1350567.
|
||||
this.collectFrom(oldIndex);
|
||||
}
|
||||
|
||||
OnHistoryGotoIndex() {
|
||||
// We ought to collect the previously current entry as well, see bug 1350567.
|
||||
this.collectFrom(kLastIndex);
|
||||
}
|
||||
|
||||
OnHistoryPurge() {
|
||||
this.collect();
|
||||
}
|
||||
|
||||
OnHistoryReload() {
|
||||
this.collect();
|
||||
return true;
|
||||
}
|
||||
|
||||
OnHistoryReplaceEntry() {
|
||||
this.collect();
|
||||
}
|
||||
}
|
||||
SessionHistoryListener.prototype.QueryInterface = ChromeUtils.generateQI([
|
||||
Ci.nsISHistoryListener,
|
||||
Ci.nsISupportsWeakReference,
|
||||
]);
|
|
@ -97,6 +97,22 @@ const MESSAGES = [
|
|||
|
||||
// The content script encountered an error.
|
||||
"SessionStore:error",
|
||||
|
||||
// The content script asks us to add the session history listener in the
|
||||
// parent process when sessionHistory is in the parent process.
|
||||
"SessionStore:addSHistoryListener",
|
||||
|
||||
// The content script asks us to remove the session history listener which
|
||||
// is added in the restore process when sessionHistory is in the parent process.
|
||||
"SessionStore:removeRestoreListener",
|
||||
|
||||
// The content script asks us to restore session history in the parent process
|
||||
// when sessionHistory is in the parent process.
|
||||
"SessionStore:restoreSHistoryInParent",
|
||||
|
||||
// The content script asks us to reload the current session history entry when
|
||||
// sessionHistory is in the parent process.
|
||||
"SessionStore:reloadCurrentEntry",
|
||||
];
|
||||
|
||||
// The list of messages we accept from <xul:browser>s that have no tab
|
||||
|
@ -112,6 +128,9 @@ const NOTAB_MESSAGES = new Set([
|
|||
|
||||
// For a description see above.
|
||||
"SessionStore:error",
|
||||
|
||||
// For a description see above.
|
||||
"SessionStore:addSHistoryListener",
|
||||
]);
|
||||
|
||||
// The list of messages we accept without an "epoch" parameter.
|
||||
|
@ -122,6 +141,9 @@ const NOEPOCH_MESSAGES = new Set([
|
|||
|
||||
// For a description see above.
|
||||
"SessionStore:error",
|
||||
|
||||
// For a description see above.
|
||||
"SessionStore:addSHistoryListener",
|
||||
]);
|
||||
|
||||
// The list of messages we want to receive even during the short period after a
|
||||
|
@ -175,6 +197,10 @@ const RESTORE_TAB_CONTENT_REASON = {
|
|||
// 'browser.startup.page' preference value to resume the previous session.
|
||||
const BROWSER_STARTUP_RESUME_SESSION = 3;
|
||||
|
||||
// Used by SessionHistoryListener.
|
||||
const kNoIndex = Number.MAX_SAFE_INTEGER;
|
||||
const kLastIndex = Number.MAX_SAFE_INTEGER - 1;
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm", this);
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm", this);
|
||||
ChromeUtils.import("resource://gre/modules/TelemetryTimestamps.jsm", this);
|
||||
|
@ -182,6 +208,12 @@ ChromeUtils.import("resource://gre/modules/Timer.jsm", this);
|
|||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
||||
ChromeUtils.import("resource://gre/modules/osfile.jsm", this);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"SessionHistory",
|
||||
"resource://gre/modules/sessionstore/SessionHistory.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetters(this, {
|
||||
gScreenManager: ["@mozilla.org/gfx/screenmanager;1", "nsIScreenManager"],
|
||||
Telemetry: ["@mozilla.org/base/telemetry;1", "nsITelemetry"],
|
||||
|
@ -501,6 +533,15 @@ var SessionStoreInternal = {
|
|||
// windows yet to be restored
|
||||
_restoreCount: -1,
|
||||
|
||||
// For each <browser> element, records the SHistoryListener.
|
||||
_browserSHistoryListener: new WeakMap(),
|
||||
|
||||
// For each <browser> element, records the SHistoryListener.
|
||||
_browserSHistoryListenerForRestore: new WeakMap(),
|
||||
|
||||
// The history data needed to be restored in the parent.
|
||||
_shistoryToRestore: new WeakMap(),
|
||||
|
||||
// For each <browser> element, records the current epoch.
|
||||
_browserEpochs: new WeakMap(),
|
||||
|
||||
|
@ -824,6 +865,11 @@ var SessionStoreInternal = {
|
|||
"privacy.resistFingerprinting"
|
||||
);
|
||||
Services.prefs.addObserver("privacy.resistFingerprinting", this);
|
||||
|
||||
this._shistoryInParent = Services.prefs.getBoolPref(
|
||||
"fission.sessionHistoryInParent",
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -907,6 +953,205 @@ var SessionStoreInternal = {
|
|||
}
|
||||
},
|
||||
|
||||
// Create a sHistoryLister and register it.
|
||||
// We also need to save the SHistoryLister into this._browserSHistoryListener.
|
||||
addSHistoryListener(aBrowser) {
|
||||
function SHistoryListener(browser) {
|
||||
browser.frameLoader.browsingContext.sessionHistory.addSHistoryListener(
|
||||
this
|
||||
);
|
||||
|
||||
this.browser = browser;
|
||||
this.frameLoader = browser.frameLoader;
|
||||
this._fromIdx = kNoIndex;
|
||||
this._sHistoryChanges = false;
|
||||
if (this.browser.currentURI && this.browser.ownerGlobal) {
|
||||
this._lastKnownUri = browser.currentURI.displaySpec;
|
||||
this._lastKnownBody = browser.ownerGlobal.document.body;
|
||||
this._lastKnownUserContextId =
|
||||
browser.contentPrincipal.originAttributes.userContextId;
|
||||
}
|
||||
}
|
||||
SHistoryListener.prototype = {
|
||||
QueryInterface: ChromeUtils.generateQI([
|
||||
Ci.nsISHistoryListener,
|
||||
Ci.nsISupportsWeakReference,
|
||||
]),
|
||||
|
||||
notifySHistoryChanges(index) {
|
||||
if (this._fromIdx <= index) {
|
||||
// If we already know that we need to update history from index N we can ignore any changes
|
||||
// that happened with an element with index larger than N.
|
||||
// Note: initially we use kNoIndex which is MAX_SAFE_INTEGER which means we don't ignore anything
|
||||
// here, and in case of navigation in the history back and forth we use kLastIndex which ignores
|
||||
// only the subsequent navigations, but not any new elements added.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._sHistoryChanges) {
|
||||
this.frameLoader.requestSHistoryUpdate(/*aImmediately*/ false);
|
||||
this._sHistoryChanges = true;
|
||||
}
|
||||
this._fromIdx = index;
|
||||
if (this.browser.currentURI && this.browser.ownerGlobal) {
|
||||
this._lastKnownUri = this.browser.currentURI.displaySpec;
|
||||
this._lastKnownBody = this.browser.ownerGlobal.document.body;
|
||||
this._lastKnownUserContextId = this.browser.contentPrincipal.originAttributes.userContextId;
|
||||
}
|
||||
},
|
||||
|
||||
uninstall() {
|
||||
if (this.frameLoader.browsingContext) {
|
||||
let shistory = this.frameLoader.browsingContext.sessionHistory;
|
||||
if (shistory) {
|
||||
shistory.removeSHistoryListener(this);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
OnHistoryNewEntry(newURI, oldIndex) {
|
||||
this.notifySHistoryChanges(oldIndex);
|
||||
},
|
||||
|
||||
OnHistoryGotoIndex() {
|
||||
this.notifySHistoryChanges(kLastIndex);
|
||||
},
|
||||
OnHistoryPurge() {
|
||||
this.notifySHistoryChanges(-1);
|
||||
},
|
||||
|
||||
OnHistoryReload() {
|
||||
this.notifySHistoryChanges(-1);
|
||||
return true;
|
||||
},
|
||||
|
||||
OnHistoryReplaceEntry() {
|
||||
this.notifySHistoryChanges(-1);
|
||||
|
||||
let win = this.browser.ownerGlobal;
|
||||
let tab = win ? win.gBrowser.getTabForBrowser(this.browser) : null;
|
||||
if (tab) {
|
||||
let event = tab.ownerDocument.createEvent("CustomEvent");
|
||||
event.initCustomEvent("SSHistoryReplaceEntry", true, false);
|
||||
tab.dispatchEvent(event);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let spec = null;
|
||||
if (aBrowser.currentURI) {
|
||||
spec = aBrowser.currentURI.displaySpec;
|
||||
}
|
||||
|
||||
if (!aBrowser.frameLoader) {
|
||||
debug(
|
||||
"addSHistoryListener(), aBrowser.frameLoader doesn't exist" +
|
||||
",browser.currentURI.displaySpec=" +
|
||||
spec
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!aBrowser.frameLoader.browsingContext) {
|
||||
debug(
|
||||
"addSHistoryListener(), aBrowser.fl.browsingContext doesn't exists" +
|
||||
",browser.currentURI.displaySpec=" +
|
||||
spec
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!aBrowser.frameLoader.browsingContext.sessionHistory) {
|
||||
debug(
|
||||
"addSHistoryListener(), aBrowser.fl.bc.sessionHistory doesn't exists" +
|
||||
",browser.currentURI.displaySpec=" +
|
||||
spec
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let listener = new SHistoryListener(aBrowser);
|
||||
this._browserSHistoryListener.set(aBrowser.permanentKey, listener);
|
||||
|
||||
// Collect data if we start with a non-empty shistory.
|
||||
let uri = aBrowser.currentURI.displaySpec;
|
||||
let history = aBrowser.frameLoader.browsingContext.sessionHistory;
|
||||
if (uri != "about:blank" || history.count != 0) {
|
||||
aBrowser.frameLoader.requestSHistoryUpdate(/*aImmediately*/ true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
function SHistoryListener(browser) {
|
||||
browser.frameLoader.browsingContext.sessionHistory.addSHistoryListener(
|
||||
this
|
||||
);
|
||||
this.browser = browser;
|
||||
}
|
||||
SHistoryListener.prototype = {
|
||||
QueryInterface: ChromeUtils.generateQI([
|
||||
Ci.nsISHistoryListener,
|
||||
Ci.nsISupportsWeakReference,
|
||||
]),
|
||||
|
||||
uninstall() {
|
||||
let shistory = this.browser.frameLoader.browsingContext.sessionHistory;
|
||||
if (shistory) {
|
||||
shistory.removeSHistoryListener(this);
|
||||
}
|
||||
},
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Notify ContentSessionStore.jsm to restore on new entry.
|
||||
this.browser.messageManager.sendAsyncMessage(
|
||||
"SessionStore:OnHistoryNewEntry",
|
||||
{ uri: newURI.spec }
|
||||
);
|
||||
},
|
||||
|
||||
OnHistoryReload() {
|
||||
// Notify ContentSessionStore.jsm to restore tab contents.
|
||||
this.browser.messageManager.sendAsyncMessage(
|
||||
"SessionStore:OnHistoryReload"
|
||||
);
|
||||
// Cancel the load.
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
if (
|
||||
!aBrowser.frameLoader ||
|
||||
!aBrowser.frameLoader.browsingContext ||
|
||||
!aBrowser.frameLoader.browsingContext.sessionHistory
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let listener = new SHistoryListener(aBrowser);
|
||||
this._browserSHistoryListenerForRestore.set(
|
||||
aBrowser.permanentKey,
|
||||
listener
|
||||
);
|
||||
},
|
||||
|
||||
updateSessionStoreFromTablistener(aBrowser, aData) {
|
||||
if (aBrowser.permanentKey == undefined) {
|
||||
return;
|
||||
|
@ -917,6 +1162,72 @@ var SessionStoreInternal = {
|
|||
return;
|
||||
}
|
||||
|
||||
let sHistoryChangedInListener = false;
|
||||
let listener = this._browserSHistoryListener.get(aBrowser.permanentKey);
|
||||
if (listener) {
|
||||
sHistoryChangedInListener = listener._sHistoryChanges;
|
||||
}
|
||||
|
||||
if (aData.sHistoryNeeded || sHistoryChangedInListener) {
|
||||
if (!listener) {
|
||||
debug(
|
||||
"updateSessionStoreFromTablistener() with aData.sHistoryNeeded, but no SHlistener. Add again!!!"
|
||||
);
|
||||
this.addSHistoryListener(aBrowser);
|
||||
listener = this._browserSHistoryListener.get(aBrowser.permanentKey);
|
||||
}
|
||||
|
||||
if (listener) {
|
||||
if (!aData.sHistoryNeeded && listener._fromIdx == kNoIndex) {
|
||||
// No shistory changes needed.
|
||||
listener._sHistoryChanges = false;
|
||||
} else {
|
||||
// |browser.frameLoader| might be empty if the browser was already
|
||||
// destroyed and its tab removed. In that case we still have the last
|
||||
// frameLoader we know about to compare.
|
||||
let frameLoader =
|
||||
aBrowser.frameLoader ||
|
||||
this._lastKnownFrameLoader.get(aBrowser.permanentKey);
|
||||
if (
|
||||
frameLoader &&
|
||||
frameLoader.browsingContext &&
|
||||
frameLoader.browsingContext.sessionHistory
|
||||
) {
|
||||
let uri = aBrowser.currentURI
|
||||
? aBrowser.currentURI.displaySpec
|
||||
: listener._lastKnownUri;
|
||||
let body = aBrowser.ownerGlobal
|
||||
? aBrowser.ownerGlobal.document.body
|
||||
: listener._lastKnownBody;
|
||||
let userContextId = aBrowser.contentPrincipal
|
||||
? aBrowser.contentPrincipal.originAttributes.userContextId
|
||||
: listener._lastKnownUserContextId;
|
||||
aData.data.historychange = SessionHistory.collectFromParent(
|
||||
uri,
|
||||
body,
|
||||
frameLoader.browsingContext.sessionHistory,
|
||||
userContextId,
|
||||
listener._sHistoryChanges ? listener._fromIdx : -1
|
||||
);
|
||||
listener._sHistoryChanges = false;
|
||||
listener._fromIdx = kNoIndex;
|
||||
} else {
|
||||
debug(
|
||||
"updateSessionStoreFromTablistener() with sHistoryNeeded, but no fL.bC.sessionHistory."
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug(
|
||||
"updateSessionStoreFromTablistener() with sHistoryNeeded, but no sHlistener."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ("sHistoryNeeded" in aData) {
|
||||
delete aData.sHistoryNeeded;
|
||||
}
|
||||
|
||||
TabState.update(aBrowser, aData);
|
||||
let win = aBrowser.ownerGlobal;
|
||||
this.saveStateDelayed(win);
|
||||
|
@ -965,6 +1276,34 @@ var SessionStoreInternal = {
|
|||
}
|
||||
|
||||
switch (aMessage.name) {
|
||||
case "SessionStore:addSHistoryListener":
|
||||
this.addSHistoryListener(browser);
|
||||
break;
|
||||
case "SessionStore:restoreSHistoryInParent":
|
||||
if (
|
||||
browser.frameLoader &&
|
||||
browser.frameLoader.browsingContext &&
|
||||
browser.frameLoader.browsingContext.sessionHistory
|
||||
) {
|
||||
let tabData = this._shistoryToRestore.get(browser.permanentKey);
|
||||
if (tabData) {
|
||||
this._shistoryToRestore.delete(browser.permanentKey);
|
||||
SessionHistory.restoreFromParent(
|
||||
browser.frameLoader.browsingContext.sessionHistory,
|
||||
tabData
|
||||
);
|
||||
}
|
||||
this.addSHistoryListenerForRestore(browser);
|
||||
} else {
|
||||
debug(
|
||||
"receive SessionStore:restoreSHistoryInParent: but cannot find sessionHistory from bc."
|
||||
);
|
||||
}
|
||||
browser.messageManager.sendAsyncMessage(
|
||||
"SessionStore:finishRestoreHistory"
|
||||
);
|
||||
break;
|
||||
|
||||
case "SessionStore:update":
|
||||
// |browser.frameLoader| might be empty if the browser was already
|
||||
// destroyed and its tab removed. In that case we still have the last
|
||||
|
@ -984,6 +1323,13 @@ var SessionStoreInternal = {
|
|||
// late and will never respond. If they have been sent shortly after
|
||||
// switching a browser's remoteness there isn't too much data to skip.
|
||||
TabStateFlusher.resolveAll(browser);
|
||||
let listener = this._browserSHistoryListener.get(
|
||||
browser.permanentKey
|
||||
);
|
||||
if (listener) {
|
||||
listener.uninstall();
|
||||
this._browserSHistoryListener.delete(browser.permanentKey);
|
||||
}
|
||||
} else if (aMessage.data.flushID) {
|
||||
// This is an update kicked off by an async flush request. Notify the
|
||||
// TabStateFlusher so that it can finish the request and notify its
|
||||
|
@ -1073,6 +1419,39 @@ var SessionStoreInternal = {
|
|||
tab.dispatchEvent(event);
|
||||
break;
|
||||
}
|
||||
case "SessionStore:removeRestoreListener":
|
||||
let listener = this._browserSHistoryListenerForRestore.get(
|
||||
browser.permanentKey
|
||||
);
|
||||
if (listener) {
|
||||
listener.uninstall();
|
||||
this._browserSHistoryListenerForRestore.delete(browser.permanentKey);
|
||||
}
|
||||
break;
|
||||
case "SessionStore:reloadCurrentEntry":
|
||||
let fL =
|
||||
browser.frameLoader ||
|
||||
this._lastKnownFrameLoader.get(browser.permanentKey);
|
||||
if (fL) {
|
||||
if (fL.browsingContext) {
|
||||
if (fL.browsingContext.sessionHistory) {
|
||||
fL.browsingContext.sessionHistory.reloadCurrentEntry();
|
||||
} else {
|
||||
debug(
|
||||
"receive SessionStore:reloadCurrentEntry browser.fL.bC.sessionHistory is null."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
debug(
|
||||
"receive SessionStore:reloadCurrentEntry browser.fL.browsingContext is null."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
debug(
|
||||
"receive SessionStore:reloadCurrentEntry browser.frameLoader is null."
|
||||
);
|
||||
}
|
||||
break;
|
||||
case "SessionStore:restoreTabContentStarted":
|
||||
if (TAB_STATE_FOR_BROWSER.get(browser) == TAB_STATE_NEEDS_RESTORE) {
|
||||
// If a load not initiated by sessionstore was started in a
|
||||
|
@ -1213,6 +1592,11 @@ var SessionStoreInternal = {
|
|||
epoch: newEpoch,
|
||||
}
|
||||
);
|
||||
|
||||
let listener = this._browserSHistoryListener.get(target.permanentKey);
|
||||
if (listener) {
|
||||
listener.notifySHistoryChanges(-1);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(`unhandled event ${aEvent.type}?`);
|
||||
|
@ -5799,6 +6183,11 @@ var SessionStoreInternal = {
|
|||
}
|
||||
}
|
||||
|
||||
if (this._shistoryInParent) {
|
||||
// Save the history data for restoring in the parent process.
|
||||
this._shistoryToRestore.set(browser.permanentKey, options.tabData);
|
||||
}
|
||||
|
||||
browser.messageManager.sendAsyncMessage(
|
||||
"SessionStore:restoreHistory",
|
||||
options
|
||||
|
|
|
@ -17,6 +17,7 @@ EXTRA_JS_MODULES.sessionstore = [
|
|||
'RunState.jsm',
|
||||
'SessionCookies.jsm',
|
||||
'SessionFile.jsm',
|
||||
'SessionHistoryListener.jsm',
|
||||
'SessionMigration.jsm',
|
||||
'SessionSaver.jsm',
|
||||
'SessionStartup.jsm',
|
||||
|
|
|
@ -32,12 +32,26 @@ function restoreClosedTabWithValue(rval) {
|
|||
return ss.undoCloseTab(window, index);
|
||||
}
|
||||
|
||||
function promiseNewLocationAndHistoryEntryReplaced(browser, snippet) {
|
||||
function promiseNewLocationAndHistoryEntryReplaced(tab, snippet) {
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
if (Services.prefs.getBoolPref("fission.sessionHistoryInParent", false)) {
|
||||
SpecialPowers.spawn(browser, [snippet], async function(codeSnippet) {
|
||||
// Need to define 'webNavigation' for 'codeSnippet'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
// Evaluate the snippet that changes the location.
|
||||
// eslint-disable-next-line no-eval
|
||||
eval(codeSnippet);
|
||||
});
|
||||
return promiseOnHistoryReplaceEntry(tab);
|
||||
}
|
||||
|
||||
return SpecialPowers.spawn(browser, [snippet], async function(codeSnippet) {
|
||||
let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
let shistory = webNavigation.sessionHistory.legacySHistory;
|
||||
|
||||
// Evaluate the snippet that the changes the location.
|
||||
// Evaluate the snippet that changes the location.
|
||||
// eslint-disable-next-line no-eval
|
||||
eval(codeSnippet);
|
||||
|
||||
|
@ -121,7 +135,7 @@ add_task(async function save_worthy_tabs_remote_final() {
|
|||
let snippet =
|
||||
'webNavigation.loadURI("https://example.com/",\
|
||||
{triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()})';
|
||||
await promiseNewLocationAndHistoryEntryReplaced(browser, snippet);
|
||||
await promiseNewLocationAndHistoryEntryReplaced(tab, snippet);
|
||||
|
||||
// Remotness shouldn't have changed.
|
||||
ok(browser.isRemoteBrowser, "browser is still remote");
|
||||
|
@ -164,11 +178,10 @@ add_task(async function save_worthy_tabs_nonremote_final() {
|
|||
|
||||
add_task(async function dont_save_empty_tabs_final() {
|
||||
let { tab, r } = await createTabWithRandomValue("https://example.com/");
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
// Replace the current page with an about:blank entry.
|
||||
let snippet = 'content.location.replace("about:blank")';
|
||||
await promiseNewLocationAndHistoryEntryReplaced(browser, snippet);
|
||||
await promiseNewLocationAndHistoryEntryReplaced(tab, snippet);
|
||||
|
||||
// Remove the tab before the update arrives.
|
||||
let promise = promiseRemoveTabAndSessionState(tab);
|
||||
|
|
|
@ -38,8 +38,12 @@ add_task(async function test_add_interesting_window() {
|
|||
content.location = newPage;
|
||||
});
|
||||
|
||||
await promiseContentMessage(browser, "ss-test:OnHistoryReplaceEntry");
|
||||
|
||||
if (Services.prefs.getBoolPref("fission.sessionHistoryInParent", false)) {
|
||||
let tab = newWin.gBrowser.selectedTab;
|
||||
await promiseOnHistoryReplaceEntry(tab);
|
||||
} else {
|
||||
await promiseContentMessage(browser, "ss-test:OnHistoryReplaceEntry");
|
||||
}
|
||||
// Clear out the userTypedValue so that the new window looks like
|
||||
// it's really not worth restoring.
|
||||
browser.userTypedValue = null;
|
||||
|
|
|
@ -556,6 +556,10 @@ function promiseDelayedStartupFinished(aWindow) {
|
|||
return new Promise(resolve => whenDelayedStartupFinished(aWindow, resolve));
|
||||
}
|
||||
|
||||
function promiseOnHistoryReplaceEntry(tab) {
|
||||
return BrowserTestUtils.waitForEvent(tab, "SSHistoryReplaceEntry");
|
||||
}
|
||||
|
||||
function promiseTabRestored(tab) {
|
||||
return BrowserTestUtils.waitForEvent(tab, "SSTabRestored");
|
||||
}
|
||||
|
|
|
@ -3209,6 +3209,18 @@ void nsFrameLoader::RequestEpochUpdate(uint32_t aEpoch) {
|
|||
}
|
||||
}
|
||||
|
||||
void nsFrameLoader::RequestSHistoryUpdate(bool aImmediately) {
|
||||
if (mSessionStoreListener) {
|
||||
mSessionStoreListener->UpdateSHistoryChanges(aImmediately);
|
||||
return;
|
||||
}
|
||||
|
||||
// If remote browsing (e10s), handle this with the BrowserParent.
|
||||
if (auto* browserParent = GetBrowserParent()) {
|
||||
Unused << browserParent->SendUpdateSHistory(aImmediately);
|
||||
}
|
||||
}
|
||||
|
||||
void nsFrameLoader::Print(uint64_t aOuterWindowID,
|
||||
nsIPrintSettings* aPrintSettings,
|
||||
nsIWebProgressListener* aProgressListener,
|
||||
|
|
|
@ -211,6 +211,8 @@ class nsFrameLoader final : public nsStubMutationObserver,
|
|||
|
||||
void RequestEpochUpdate(uint32_t aEpoch);
|
||||
|
||||
void RequestSHistoryUpdate(bool aImmediately = false);
|
||||
|
||||
void Print(uint64_t aOuterWindowID, nsIPrintSettings* aPrintSettings,
|
||||
nsIWebProgressListener* aProgressListener,
|
||||
mozilla::ErrorResult& aRv);
|
||||
|
|
|
@ -2015,6 +2015,14 @@ mozilla::ipc::IPCResult BrowserChild::RecvUpdateEpoch(const uint32_t& aEpoch) {
|
|||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult BrowserChild::RecvUpdateSHistory(
|
||||
const bool& aImmediately) {
|
||||
if (mSessionStoreListener) {
|
||||
mSessionStoreListener->UpdateSHistoryChanges(aImmediately);
|
||||
}
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
// In case handling repeated keys takes much time, we skip firing new ones.
|
||||
bool BrowserChild::SkipRepeatedKeyEvent(const WidgetKeyboardEvent& aEvent) {
|
||||
if (mRepeatedKeyEventTime.IsNull() || !aEvent.CanSkipInRemoteProcess() ||
|
||||
|
@ -4003,8 +4011,9 @@ bool BrowserChild::UpdateSessionStore(uint32_t aFlushId, bool aIsFinal) {
|
|||
|
||||
Unused << SendSessionStoreUpdate(
|
||||
docShellCaps, privatedMode, positions, positionDescendants, inputs,
|
||||
idVals, xPathVals, origins, keys, values, isFullStorage, aFlushId,
|
||||
aIsFinal, mSessionStoreListener->GetEpoch());
|
||||
idVals, xPathVals, origins, keys, values, isFullStorage,
|
||||
store->GetAndClearSHistoryChanged(), aFlushId, aIsFinal,
|
||||
mSessionStoreListener->GetEpoch());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -381,6 +381,8 @@ class BrowserChild final : public nsMessageManagerScriptExecutor,
|
|||
|
||||
mozilla::ipc::IPCResult RecvUpdateEpoch(const uint32_t& aEpoch);
|
||||
|
||||
mozilla::ipc::IPCResult RecvUpdateSHistory(const bool& aImmediately);
|
||||
|
||||
mozilla::ipc::IPCResult RecvNativeSynthesisResponse(
|
||||
const uint64_t& aObserverId, const nsCString& aResponse);
|
||||
|
||||
|
|
|
@ -2925,7 +2925,8 @@ mozilla::ipc::IPCResult BrowserParent::RecvSessionStoreUpdate(
|
|||
const nsTArray<CollectedInputDataValue>& aXPathVals,
|
||||
nsTArray<nsCString>&& aOrigins, nsTArray<nsString>&& aKeys,
|
||||
nsTArray<nsString>&& aValues, const bool aIsFullStorage,
|
||||
const uint32_t& aFlushId, const bool& aIsFinal, const uint32_t& aEpoch) {
|
||||
const bool aNeedCollectSHistory, const uint32_t& aFlushId,
|
||||
const bool& aIsFinal, const uint32_t& aEpoch) {
|
||||
UpdateSessionStoreData data;
|
||||
if (aDocShellCaps.isSome()) {
|
||||
data.mDocShellCaps.Construct() = aDocShellCaps.value();
|
||||
|
@ -2982,8 +2983,8 @@ mozilla::ipc::IPCResult BrowserParent::RecvSessionStoreUpdate(
|
|||
bool ok = ToJSValue(jsapi.cx(), data, &dataVal);
|
||||
NS_ENSURE_TRUE(ok, IPC_OK());
|
||||
|
||||
nsresult rv = funcs->UpdateSessionStore(mFrameElement, aFlushId, aIsFinal,
|
||||
aEpoch, dataVal);
|
||||
nsresult rv = funcs->UpdateSessionStore(
|
||||
mFrameElement, aFlushId, aIsFinal, aEpoch, dataVal, aNeedCollectSHistory);
|
||||
NS_ENSURE_SUCCESS(rv, IPC_OK());
|
||||
|
||||
return IPC_OK();
|
||||
|
|
|
@ -340,7 +340,8 @@ class BrowserParent final : public PBrowserParent,
|
|||
const nsTArray<CollectedInputDataValue>& aXPathVals,
|
||||
nsTArray<nsCString>&& aOrigins, nsTArray<nsString>&& aKeys,
|
||||
nsTArray<nsString>&& aValues, const bool aIsFullStorage,
|
||||
const uint32_t& aFlushId, const bool& aIsFinal, const uint32_t& aEpoch);
|
||||
const bool aNeedCollectSHistory, const uint32_t& aFlushId,
|
||||
const bool& aIsFinal, const uint32_t& aEpoch);
|
||||
|
||||
mozilla::ipc::IPCResult RecvBrowserFrameOpenWindow(
|
||||
PBrowserParent* aOpener, const nsString& aURL, const nsString& aName,
|
||||
|
|
|
@ -616,12 +616,14 @@ parent:
|
|||
CollectedInputDataValue[] aXPathVals,
|
||||
nsCString[] aOrigins, nsString[] aKeys,
|
||||
nsString[] aValues, bool aIsFullStorage,
|
||||
uint32_t aFlushId, bool aIsFinal, uint32_t aEpoch);
|
||||
bool aNeedCollectSHistory, uint32_t aFlushId,
|
||||
bool aIsFinal, uint32_t aEpoch);
|
||||
|
||||
child:
|
||||
async NativeSynthesisResponse(uint64_t aObserverId, nsCString aResponse);
|
||||
async FlushTabState(uint32_t aFlushId, bool aIsFinal);
|
||||
async UpdateEpoch(uint32_t aEpoch);
|
||||
async UpdateSHistory(bool aImmediately);
|
||||
|
||||
parent:
|
||||
|
||||
|
|
|
@ -112,6 +112,11 @@ interface FrameLoader {
|
|||
*/
|
||||
void requestEpochUpdate(unsigned long aEpoch);
|
||||
|
||||
/**
|
||||
* Request a session history update in native sessionStoreListeners.
|
||||
*/
|
||||
void requestSHistoryUpdate(boolean aImmediately);
|
||||
|
||||
/**
|
||||
* Print the current document.
|
||||
*
|
||||
|
|
|
@ -11,5 +11,7 @@ webidl Element;
|
|||
interface nsISessionStoreFunctions : nsISupports {
|
||||
// update sessionStore from the tabListener implemented by C++
|
||||
// aData is a UpdateSessionStoreData dictionary (From SessionStoreUtils.webidl)
|
||||
void UpdateSessionStore(in Element aBrowser, in uint32_t aFlushId, in boolean aIsFinal, in uint32_t aEpoch, in jsval aData);
|
||||
void UpdateSessionStore(
|
||||
in Element aBrowser, in uint32_t aFlushId, in boolean aIsFinal,
|
||||
in uint32_t aEpoch, in jsval aData, in boolean aCollectSHistory);
|
||||
};
|
||||
|
|
|
@ -8,13 +8,21 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
SessionStore: "resource:///modules/sessionstore/SessionStore.jsm",
|
||||
});
|
||||
|
||||
function UpdateSessionStore(aBrowser, aFlushId, aIsFinal, aEpoch, aData) {
|
||||
function UpdateSessionStore(
|
||||
aBrowser,
|
||||
aFlushId,
|
||||
aIsFinal,
|
||||
aEpoch,
|
||||
aData,
|
||||
aCollectSHistory
|
||||
) {
|
||||
return SessionStoreFuncInternal.updateSessionStore(
|
||||
aBrowser,
|
||||
aFlushId,
|
||||
aIsFinal,
|
||||
aEpoch,
|
||||
aData
|
||||
aData,
|
||||
aCollectSHistory
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -382,7 +390,8 @@ var SessionStoreFuncInternal = {
|
|||
aFlushId,
|
||||
aIsFinal,
|
||||
aEpoch,
|
||||
aData
|
||||
aData,
|
||||
aCollectSHistory
|
||||
) {
|
||||
let currentData = {};
|
||||
if (aData.docShellCaps != undefined) {
|
||||
|
@ -434,6 +443,7 @@ var SessionStoreFuncInternal = {
|
|||
flushID: aFlushId,
|
||||
isFinal: aIsFinal,
|
||||
epoch: aEpoch,
|
||||
sHistoryNeeded: aCollectSHistory,
|
||||
});
|
||||
this._formDataId = [];
|
||||
this._formDataIdValue = [];
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "mozilla/dom/SessionStoreUtilsBinding.h"
|
||||
#include "mozilla/dom/StorageEvent.h"
|
||||
#include "mozilla/dom/BrowserChild.h"
|
||||
#include "mozilla/StaticPrefs_fission.h"
|
||||
#include "nsGenericHTMLElement.h"
|
||||
#include "nsDocShell.h"
|
||||
#include "nsIAppWindow.h"
|
||||
|
@ -44,7 +45,10 @@ ContentSessionStore::ContentSessionStore(nsIDocShell* aDocShell)
|
|||
mScrollChanged(NO_CHANGE),
|
||||
mFormDataChanged(NO_CHANGE),
|
||||
mStorageStatus(NO_STORAGE),
|
||||
mDocCapChanged(false) {
|
||||
mDocCapChanged(false),
|
||||
mSHistoryInParent(StaticPrefs::fission_sessionHistoryInParent()),
|
||||
mSHistoryChanged(false),
|
||||
mSHistoryChangedFromParent(false) {
|
||||
MOZ_ASSERT(mDocShell);
|
||||
// Check that value at startup as it might have
|
||||
// been set before the frame script was loaded.
|
||||
|
@ -123,11 +127,19 @@ void ContentSessionStore::OnDocumentStart() {
|
|||
}
|
||||
|
||||
SetFullStorageNeeded();
|
||||
|
||||
if (mSHistoryInParent) {
|
||||
mSHistoryChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ContentSessionStore::OnDocumentEnd() {
|
||||
mScrollChanged = WITH_CHANGE;
|
||||
SetFullStorageNeeded();
|
||||
|
||||
if (mSHistoryInParent) {
|
||||
mSHistoryChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TabListener)
|
||||
|
@ -156,7 +168,8 @@ TabListener::TabListener(nsIDocShell* aDocShell, Element* aElement)
|
|||
mUpdatedTimer(nullptr),
|
||||
mTimeoutDisabled(false),
|
||||
mUpdateInterval(15000),
|
||||
mEpoch(0) {
|
||||
mEpoch(0),
|
||||
mSHistoryInParent(StaticPrefs::fission_sessionHistoryInParent()) {
|
||||
MOZ_ASSERT(mDocShell);
|
||||
}
|
||||
|
||||
|
@ -206,6 +219,12 @@ nsresult TabListener::Init() {
|
|||
eventTarget->AddSystemEventListener(NS_LITERAL_STRING("mozvisualscroll"),
|
||||
this, false);
|
||||
eventTarget->AddSystemEventListener(NS_LITERAL_STRING("input"), this, false);
|
||||
|
||||
if (mSHistoryInParent) {
|
||||
eventTarget->AddSystemEventListener(NS_LITERAL_STRING("DOMTitleChanged"),
|
||||
this, false);
|
||||
}
|
||||
|
||||
mEventListenerRegistered = true;
|
||||
eventTarget->AddSystemEventListener(
|
||||
NS_LITERAL_STRING("MozSessionStorageChanged"), this, false);
|
||||
|
@ -347,6 +366,9 @@ TabListener::HandleEvent(Event* aEvent) {
|
|||
if (mSessionStore->AppendSessionStorageChange(event)) {
|
||||
AddTimerForUpdate();
|
||||
}
|
||||
} else if (eventType.EqualsLiteral("DOMTitleChanged")) {
|
||||
mSessionStore->SetSHistoryChanged();
|
||||
AddTimerForUpdate();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
|
@ -641,6 +663,15 @@ bool TabListener::ForceFlushFromParent(uint32_t aFlushId, bool aIsFinal) {
|
|||
return UpdateSessionStore(aFlushId, aIsFinal);
|
||||
}
|
||||
|
||||
void TabListener::UpdateSHistoryChanges(bool aImmediately) {
|
||||
mSessionStore->SetSHistoryFromParentChanged();
|
||||
if (aImmediately) {
|
||||
UpdateSessionStore();
|
||||
} else {
|
||||
AddTimerForUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
bool TabListener::UpdateSessionStore(uint32_t aFlushId, bool aIsFinal) {
|
||||
if (!aFlushId) {
|
||||
if (!mSessionStore || !mSessionStore->UpdateNeeded()) {
|
||||
|
@ -740,8 +771,9 @@ bool TabListener::UpdateSessionStore(uint32_t aFlushId, bool aIsFinal) {
|
|||
bool ok = ToJSValue(jsapi.cx(), data, &dataVal);
|
||||
NS_ENSURE_TRUE(ok, false);
|
||||
|
||||
nsresult rv = funcs->UpdateSessionStore(mOwnerContent, aFlushId, aIsFinal,
|
||||
mEpoch, dataVal);
|
||||
nsresult rv = funcs->UpdateSessionStore(
|
||||
mOwnerContent, aFlushId, aIsFinal, mEpoch, dataVal,
|
||||
mSessionStore->GetAndClearSHistoryChanged());
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
StopTimerForUpdate();
|
||||
return true;
|
||||
|
@ -791,6 +823,10 @@ void TabListener::RemoveListeners() {
|
|||
NS_LITERAL_STRING("mozvisualscroll"), this, false);
|
||||
eventTarget->RemoveSystemEventListener(NS_LITERAL_STRING("input"), this,
|
||||
false);
|
||||
if (mSHistoryInParent) {
|
||||
eventTarget->RemoveSystemEventListener(
|
||||
NS_LITERAL_STRING("DOMTitleChanged"), this, false);
|
||||
}
|
||||
mEventListenerRegistered = false;
|
||||
}
|
||||
if (mStorageChangeListenerRegistered) {
|
||||
|
|
|
@ -65,11 +65,24 @@ class ContentSessionStore {
|
|||
// Return true if there is a new storage change which is appended.
|
||||
bool AppendSessionStorageChange(StorageEvent* aEvent);
|
||||
|
||||
void SetSHistoryChanged() { mSHistoryChanged = mSHistoryInParent; }
|
||||
// request "collect sessionHistory" which is happened in the parent process
|
||||
void SetSHistoryFromParentChanged() {
|
||||
mSHistoryChangedFromParent = mSHistoryInParent;
|
||||
}
|
||||
bool GetAndClearSHistoryChanged() {
|
||||
bool ret = mSHistoryChanged;
|
||||
mSHistoryChanged = false;
|
||||
mSHistoryChangedFromParent = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void OnDocumentStart();
|
||||
void OnDocumentEnd();
|
||||
bool UpdateNeeded() {
|
||||
return mPrivateChanged || mDocCapChanged || IsScrollPositionChanged() ||
|
||||
IsFormDataChanged() || IsStorageUpdated();
|
||||
IsFormDataChanged() || IsStorageUpdated() || mSHistoryChanged ||
|
||||
mSHistoryChangedFromParent;
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -97,6 +110,17 @@ class ContentSessionStore {
|
|||
nsTArray<nsCString> mOrigins;
|
||||
nsTArray<nsString> mKeys;
|
||||
nsTArray<nsString> mValues;
|
||||
// need to collect sessionHistory
|
||||
bool mSHistoryInParent;
|
||||
// mSHistoryChanged means there are history changes which are found
|
||||
// in the child process. The flag is set when
|
||||
// 1. webProgress changes to STATE_START
|
||||
// 2. webProgress changes to STATE_STOP
|
||||
// 3. receiving "DOMTitleChanged" event
|
||||
bool mSHistoryChanged;
|
||||
// mSHistoryChangedFromParent means there are history changes which
|
||||
// are found by session history listener in the parent process.
|
||||
bool mSHistoryChangedFromParent;
|
||||
};
|
||||
|
||||
class TabListener : public nsIDOMEventListener,
|
||||
|
@ -114,6 +138,7 @@ class TabListener : public nsIDOMEventListener,
|
|||
void RemoveListeners();
|
||||
void SetEpoch(uint32_t aEpoch) { mEpoch = aEpoch; }
|
||||
uint32_t GetEpoch() { return mEpoch; }
|
||||
void UpdateSHistoryChanges(bool aImmediately);
|
||||
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(TabListener, nsIDOMEventListener)
|
||||
|
@ -145,6 +170,8 @@ class TabListener : public nsIDOMEventListener,
|
|||
bool mTimeoutDisabled;
|
||||
int32_t mUpdateInterval;
|
||||
uint32_t mEpoch;
|
||||
// sessionHistory in the parent process
|
||||
bool mSHistoryInParent;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
|
|
@ -39,8 +39,26 @@ var SessionHistory = Object.freeze({
|
|||
return SessionHistoryInternal.collect(docShell, aFromIdx);
|
||||
},
|
||||
|
||||
collectFromParent(uri, body, history, userContextId, aFromIdx = -1) {
|
||||
return SessionHistoryInternal.collectCommon(
|
||||
uri,
|
||||
body,
|
||||
history,
|
||||
userContextId,
|
||||
aFromIdx
|
||||
);
|
||||
},
|
||||
|
||||
restore(docShell, tabData) {
|
||||
return SessionHistoryInternal.restore(docShell, tabData);
|
||||
return SessionHistoryInternal.restore(
|
||||
docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory
|
||||
.legacySHistory,
|
||||
tabData
|
||||
);
|
||||
},
|
||||
|
||||
restoreFromParent(history, tabData) {
|
||||
return SessionHistoryInternal.restore(history, tabData);
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -81,12 +99,24 @@ var SessionHistoryInternal = {
|
|||
collect(docShell, aFromIdx = -1) {
|
||||
let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
|
||||
let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
let uri = webNavigation.currentURI.displaySpec;
|
||||
let body = webNavigation.document.body;
|
||||
let history = webNavigation.sessionHistory;
|
||||
let userContextId = loadContext.originAttributes.userContextId;
|
||||
return this.collectCommon(
|
||||
uri,
|
||||
body,
|
||||
history.legacySHistory,
|
||||
userContextId,
|
||||
aFromIdx
|
||||
);
|
||||
},
|
||||
|
||||
collectCommon(uri, body, shistory, userContextId, aFromIdx) {
|
||||
let data = {
|
||||
entries: [],
|
||||
userContextId: loadContext.originAttributes.userContextId,
|
||||
requestedIndex: history.legacySHistory.requestedIndex + 1,
|
||||
userContextId,
|
||||
requestedIndex: shistory.requestedIndex + 1,
|
||||
};
|
||||
|
||||
// We want to keep track how many entries we *could* have collected and
|
||||
|
@ -95,8 +125,7 @@ var SessionHistoryInternal = {
|
|||
let skippedCount = 0,
|
||||
entryCount = 0;
|
||||
|
||||
if (history && history.count > 0) {
|
||||
let shistory = history.legacySHistory.QueryInterface(Ci.nsISHistory);
|
||||
if (shistory && shistory.count > 0) {
|
||||
let count = shistory.count;
|
||||
for (; entryCount < count; entryCount++) {
|
||||
let shEntry = shistory.getEntryAtIndex(entryCount);
|
||||
|
@ -109,15 +138,13 @@ var SessionHistoryInternal = {
|
|||
}
|
||||
|
||||
// Ensure the index isn't out of bounds if an exception was thrown above.
|
||||
data.index = Math.min(history.index + 1, entryCount);
|
||||
data.index = Math.min(shistory.index + 1, entryCount);
|
||||
}
|
||||
|
||||
// If either the session history isn't available yet or doesn't have any
|
||||
// valid entries, make sure we at least include the current page,
|
||||
// unless of course we just skipped all entries because aFromIdx was big enough.
|
||||
if (!data.entries.length && (skippedCount != entryCount || aFromIdx < 0)) {
|
||||
let uri = webNavigation.currentURI.displaySpec;
|
||||
let body = webNavigation.document.body;
|
||||
// We landed here because the history is inaccessible or there are no
|
||||
// history entries. In that case we should at least record the docShell's
|
||||
// current URL as a single history entry. If the URL is not about:blank
|
||||
|
@ -328,15 +355,13 @@ var SessionHistoryInternal = {
|
|||
/**
|
||||
* Restores session history data for a given docShell.
|
||||
*
|
||||
* @param docShell
|
||||
* The docShell that owns the session history.
|
||||
* @param history
|
||||
* The session history object.
|
||||
* @param tabData
|
||||
* The tabdata including all history entries.
|
||||
* @return A reference to the docShell's nsISHistory interface.
|
||||
*/
|
||||
restore(docShell, tabData) {
|
||||
let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
let history = webNavigation.sessionHistory.legacySHistory;
|
||||
restore(history, tabData) {
|
||||
if (history.count > 0) {
|
||||
history.PurgeHistory(history.count);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче