зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1507287 - Make sessionRestore work with session history living in the parent process. r=peterv
Differential Revision: https://phabricator.services.mozilla.com/D46281 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
cbf7696246
Коммит
9ead74bd10
|
@ -37,7 +37,6 @@ const whitelist = {
|
||||||
|
|
||||||
// Session store
|
// Session store
|
||||||
"resource:///modules/sessionstore/ContentSessionStore.jsm",
|
"resource:///modules/sessionstore/ContentSessionStore.jsm",
|
||||||
"resource://gre/modules/sessionstore/SessionHistory.jsm",
|
|
||||||
|
|
||||||
// Browser front-end
|
// Browser front-end
|
||||||
"resource:///actors/AboutReaderChild.jsm",
|
"resource:///actors/AboutReaderChild.jsm",
|
||||||
|
@ -86,6 +85,10 @@ const intermittently_loaded_whitelist = {
|
||||||
"resource://gre/modules/nsAsyncShutdown.jsm",
|
"resource://gre/modules/nsAsyncShutdown.jsm",
|
||||||
"resource://gre/modules/sessionstore/Utils.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/SpecialPowersChild.jsm",
|
||||||
"resource://specialpowers/WrapPrivileged.jsm",
|
"resource://specialpowers/WrapPrivileged.jsm",
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,8 @@ function ContentRestore(chromeGlobal) {
|
||||||
|
|
||||||
let EXPORTED_METHODS = [
|
let EXPORTED_METHODS = [
|
||||||
"restoreHistory",
|
"restoreHistory",
|
||||||
|
"finishRestoreHistory",
|
||||||
|
"restoreOnNewEntry",
|
||||||
"restoreTabContent",
|
"restoreTabContent",
|
||||||
"restoreDocument",
|
"restoreDocument",
|
||||||
"resetRestore",
|
"resetRestore",
|
||||||
|
@ -94,6 +96,26 @@ function ContentRestoreInternal(chromeGlobal) {
|
||||||
// data from the network. Set in restoreHistory() and restoreTabContent(),
|
// data from the network. Set in restoreHistory() and restoreTabContent(),
|
||||||
// removed in resetRestore().
|
// removed in resetRestore().
|
||||||
this._progressListener = null;
|
this._progressListener = null;
|
||||||
|
|
||||||
|
this._shistoryInParent = false;
|
||||||
|
this._callbacksForBottomHalf = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,8 +133,9 @@ ContentRestoreInternal.prototype = {
|
||||||
* non-zero) is passed through to all the callbacks. If a load in the tab
|
* 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.
|
* is started while it is pending, the appropriate callbacks are called.
|
||||||
*/
|
*/
|
||||||
restoreHistory(tabData, loadArguments, callbacks) {
|
restoreHistory(tabData, loadArguments, callbacks, shistoryInParent) {
|
||||||
this._tabData = tabData;
|
this._tabData = tabData;
|
||||||
|
this._callbacksForBottomHalf = callbacks;
|
||||||
|
|
||||||
// In case about:blank isn't done yet.
|
// In case about:blank isn't done yet.
|
||||||
let webNavigation = this.docShell.QueryInterface(Ci.nsIWebNavigation);
|
let webNavigation = this.docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||||
|
@ -130,27 +153,46 @@ ContentRestoreInternal.prototype = {
|
||||||
webNavigation.setCurrentURI(Services.io.newURI(uri));
|
webNavigation.setCurrentURI(Services.io.newURI(uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
SessionHistory.restore(this.docShell, tabData);
|
this._shistoryInParent = shistoryInParent;
|
||||||
|
|
||||||
// Add a listener to watch for reloads.
|
if (this._shistoryInParent) {
|
||||||
let listener = new HistoryListener(this.docShell, () => {
|
callbacks.requestRestoreSHistory();
|
||||||
// On reload, restore tab contents.
|
} else {
|
||||||
this.restoreTabContent(null, false, callbacks.onLoadFinished);
|
SessionHistory.restore(this.docShell, tabData);
|
||||||
});
|
|
||||||
|
|
||||||
webNavigation.sessionHistory.legacySHistory.addSHistoryListener(listener);
|
// Add a listener to watch for reloads.
|
||||||
this._historyListener = listener;
|
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();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
finishRestoreHistory() {
|
||||||
// Make sure to reset the capabilities and attributes in case this tab gets
|
// Make sure to reset the capabilities and attributes in case this tab gets
|
||||||
// reused.
|
// reused.
|
||||||
SessionStoreUtils.restoreDocShellCapabilities(
|
SessionStoreUtils.restoreDocShellCapabilities(
|
||||||
this.docShell,
|
this.docShell,
|
||||||
tabData.disallow
|
this._tabData.disallow
|
||||||
);
|
);
|
||||||
|
|
||||||
if (tabData.storage && this.docShell instanceof Ci.nsIDocShell) {
|
if (this._tabData.storage && this.docShell instanceof Ci.nsIDocShell) {
|
||||||
SessionStoreUtils.restoreSessionStorage(this.docShell, tabData.storage);
|
SessionStoreUtils.restoreSessionStorage(
|
||||||
delete tabData.storage;
|
this.docShell,
|
||||||
|
this._tabData.storage
|
||||||
|
);
|
||||||
|
delete this._tabData.storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a progress listener to correctly handle browser.loadURI()
|
// Add a progress listener to correctly handle browser.loadURI()
|
||||||
|
@ -162,19 +204,33 @@ ContentRestoreInternal.prototype = {
|
||||||
this._tabData = null;
|
this._tabData = null;
|
||||||
|
|
||||||
// Listen for the tab to finish loading.
|
// Listen for the tab to finish loading.
|
||||||
this.restoreTabContentStarted(callbacks.onLoadFinished);
|
this.restoreTabContentStarted(
|
||||||
|
this._callbacksForBottomHalf.onLoadFinished,
|
||||||
|
this._callbacksForBottomHalf.removeRestoreListener
|
||||||
|
);
|
||||||
|
|
||||||
// Notify the parent.
|
// Notify the parent.
|
||||||
callbacks.onLoadStarted();
|
this._callbacksForBottomHalf.onLoadStarted();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
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
|
* Start loading the current page. When the data has finished loading from the
|
||||||
* network, finishCallback is called. Returns true if the load was successful.
|
* 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;
|
let tabData = this._tabData;
|
||||||
this._tabData = null;
|
this._tabData = null;
|
||||||
|
|
||||||
|
@ -182,7 +238,7 @@ ContentRestoreInternal.prototype = {
|
||||||
let history = webNavigation.sessionHistory.legacySHistory;
|
let history = webNavigation.sessionHistory.legacySHistory;
|
||||||
|
|
||||||
// Listen for the tab to finish loading.
|
// 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
|
// 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
|
// switch-to-tab, but now it must go back to the correct value before the
|
||||||
|
@ -273,7 +329,11 @@ ContentRestoreInternal.prototype = {
|
||||||
// In order to work around certain issues in session history, we need to
|
// In order to work around certain issues in session history, we need to
|
||||||
// force session history to update its internal index and call reload
|
// force session history to update its internal index and call reload
|
||||||
// instead of gotoIndex. See bug 597315.
|
// instead of gotoIndex. See bug 597315.
|
||||||
history.reloadCurrentEntry();
|
if (this._shistoryInParent) {
|
||||||
|
reloadSHistoryCallback();
|
||||||
|
} else {
|
||||||
|
history.reloadCurrentEntry();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// If there's nothing to restore, we should still blank the page.
|
// If there's nothing to restore, we should still blank the page.
|
||||||
let loadURIOptions = {
|
let loadURIOptions = {
|
||||||
|
@ -298,10 +358,14 @@ ContentRestoreInternal.prototype = {
|
||||||
* To be called after restoreHistory(). Removes all listeners needed for
|
* To be called after restoreHistory(). Removes all listeners needed for
|
||||||
* pending tabs and makes sure to notify when the tab finished loading.
|
* pending tabs and makes sure to notify when the tab finished loading.
|
||||||
*/
|
*/
|
||||||
restoreTabContentStarted(finishCallback) {
|
restoreTabContentStarted(finishCallback, removeListenerCallback) {
|
||||||
// The reload listener is no longer needed.
|
// The reload listener is no longer needed.
|
||||||
this._historyListener.uninstall();
|
if (!this._shistoryInParent) {
|
||||||
this._historyListener = null;
|
this._historyListener.uninstall();
|
||||||
|
this._historyListener = null;
|
||||||
|
} else {
|
||||||
|
removeListenerCallback();
|
||||||
|
}
|
||||||
|
|
||||||
// Remove the old progress listener.
|
// Remove the old progress listener.
|
||||||
this._progressListener.uninstall();
|
this._progressListener.uninstall();
|
||||||
|
@ -371,6 +435,8 @@ ContentRestoreInternal.prototype = {
|
||||||
this._progressListener.uninstall();
|
this._progressListener.uninstall();
|
||||||
}
|
}
|
||||||
this._progressListener = null;
|
this._progressListener = null;
|
||||||
|
|
||||||
|
this._callbacksForBottomHalf = null;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -415,20 +481,7 @@ HistoryListener.prototype = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the tab's URL to what it's actually showing. Without this loadURI()
|
kickOffNewLoadFromBlankPage(this.webNavigation, newURI);
|
||||||
// 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);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
OnHistoryReload() {
|
OnHistoryReload() {
|
||||||
|
|
|
@ -21,8 +21,8 @@ ChromeUtils.defineModuleGetter(
|
||||||
);
|
);
|
||||||
ChromeUtils.defineModuleGetter(
|
ChromeUtils.defineModuleGetter(
|
||||||
this,
|
this,
|
||||||
"SessionHistory",
|
"SessionHistoryListener",
|
||||||
"resource://gre/modules/sessionstore/SessionHistory.jsm"
|
"resource:///modules/sessionstore/SessionHistoryListener.jsm"
|
||||||
);
|
);
|
||||||
|
|
||||||
// This pref controls whether or not we send updates to the parent on a timeout
|
// 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 PREF_INTERVAL = "browser.sessionstore.interval";
|
||||||
|
|
||||||
const kNoIndex = Number.MAX_SAFE_INTEGER;
|
|
||||||
const kLastIndex = Number.MAX_SAFE_INTEGER - 1;
|
|
||||||
|
|
||||||
class Handler {
|
class Handler {
|
||||||
constructor(store) {
|
constructor(store) {
|
||||||
this.store = store;
|
this.store = store;
|
||||||
|
@ -54,83 +51,8 @@ class Handler {
|
||||||
get messageQueue() {
|
get messageQueue() {
|
||||||
return this.store.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
|
* Listens for and handles content events that we need for the
|
||||||
* session store service to be notified of state changes in content.
|
* 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
|
* 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
|
* to the chrome process. It allows flushing using synchronous messages and
|
||||||
|
@ -550,6 +339,9 @@ class MessageQueue extends Handler {
|
||||||
*/
|
*/
|
||||||
const MESSAGES = [
|
const MESSAGES = [
|
||||||
"SessionStore:restoreHistory",
|
"SessionStore:restoreHistory",
|
||||||
|
"SessionStore:finishRestoreHistory",
|
||||||
|
"SessionStore:OnHistoryReload",
|
||||||
|
"SessionStore:OnHistoryNewEntry",
|
||||||
"SessionStore:restoreTabContent",
|
"SessionStore:restoreTabContent",
|
||||||
"SessionStore:resetRestore",
|
"SessionStore:resetRestore",
|
||||||
"SessionStore:flush",
|
"SessionStore:flush",
|
||||||
|
@ -560,23 +352,30 @@ class ContentSessionStore {
|
||||||
constructor(mm) {
|
constructor(mm) {
|
||||||
this.mm = mm;
|
this.mm = mm;
|
||||||
this.messageQueue = new MessageQueue(this);
|
this.messageQueue = new MessageQueue(this);
|
||||||
this.stateChangeNotifier = new StateChangeNotifier(this);
|
|
||||||
|
|
||||||
this.epoch = 0;
|
this.epoch = 0;
|
||||||
|
|
||||||
this.contentRestoreInitialized = false;
|
this.contentRestoreInitialized = false;
|
||||||
|
|
||||||
|
this.waitRestoreSHistoryInParent = false;
|
||||||
|
this.restoreTabContentData = null;
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGetter(this, "contentRestore", () => {
|
XPCOMUtils.defineLazyGetter(this, "contentRestore", () => {
|
||||||
this.contentRestoreInitialized = true;
|
this.contentRestoreInitialized = true;
|
||||||
return new ContentRestore(mm);
|
return new ContentRestore(mm);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.handlers = [
|
this.handlers = [new EventListener(this), this.messageQueue];
|
||||||
new EventListener(this),
|
|
||||||
new SessionHistoryListener(this),
|
this._shistoryInParent = Services.prefs.getBoolPref(
|
||||||
this.stateChangeNotifier,
|
"fission.sessionHistoryInParent",
|
||||||
this.messageQueue,
|
false
|
||||||
];
|
);
|
||||||
|
if (this._shistoryInParent) {
|
||||||
|
this.mm.sendAsyncMessage("SessionStore:addSHistoryListener");
|
||||||
|
} else {
|
||||||
|
this.handlers.push(new SessionHistoryListener(this));
|
||||||
|
}
|
||||||
|
|
||||||
MESSAGES.forEach(m => mm.addMessageListener(m, 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
|
// 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
|
// to ignore async messages that were already sent but not yet received
|
||||||
// and would otherwise confuse the internal tab state.
|
// 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;
|
this.epoch = data.epoch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -605,8 +404,53 @@ class ContentSessionStore {
|
||||||
case "SessionStore:restoreHistory":
|
case "SessionStore:restoreHistory":
|
||||||
this.restoreHistory(data);
|
this.restoreHistory(data);
|
||||||
break;
|
break;
|
||||||
|
case "SessionStore:finishRestoreHistory":
|
||||||
|
this.contentRestore.finishRestoreHistory();
|
||||||
|
this.mm.sendAsyncMessage("SessionStore:restoreHistoryComplete", {
|
||||||
|
epoch: this.epoch,
|
||||||
|
});
|
||||||
|
if (this.restoreTabContentData) {
|
||||||
|
this.restoreTabContent(this.restoreTabContentData);
|
||||||
|
this.restoreTabContentData = null;
|
||||||
|
}
|
||||||
|
this.waitRestoreSHistoryInParent = false;
|
||||||
|
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":
|
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;
|
break;
|
||||||
case "SessionStore:resetRestore":
|
case "SessionStore:resetRestore":
|
||||||
this.contentRestore.resetRestore();
|
this.contentRestore.resetRestore();
|
||||||
|
@ -615,7 +459,9 @@ class ContentSessionStore {
|
||||||
this.flush(data);
|
this.flush(data);
|
||||||
break;
|
break;
|
||||||
case "SessionStore:becomeActiveProcess":
|
case "SessionStore:becomeActiveProcess":
|
||||||
SessionHistoryListener.collect();
|
if (!this._shistoryInParent) {
|
||||||
|
SessionHistoryListener.collect();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
debug("received unknown message '" + name + "'");
|
debug("received unknown message '" + name + "'");
|
||||||
|
@ -624,27 +470,55 @@ class ContentSessionStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreHistory({ epoch, tabData, loadArguments, isRemotenessUpdate }) {
|
restoreHistory({ epoch, tabData, loadArguments, isRemotenessUpdate }) {
|
||||||
this.contentRestore.restoreHistory(tabData, loadArguments, {
|
this.contentRestore.restoreHistory(
|
||||||
// Note: The callbacks passed here will only be used when a load starts
|
tabData,
|
||||||
// that was not initiated by sessionstore itself. This can happen when
|
loadArguments,
|
||||||
// some code calls browser.loadURI() or browser.reload() on a pending
|
{
|
||||||
// browser/tab.
|
// 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: () => {
|
onLoadStarted: () => {
|
||||||
// Notify the parent that the tab is no longer pending.
|
// Notify the parent that the tab is no longer pending.
|
||||||
this.mm.sendAsyncMessage("SessionStore:restoreTabContentStarted", {
|
this.mm.sendAsyncMessage("SessionStore:restoreTabContentStarted", {
|
||||||
epoch,
|
epoch,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onLoadFinished: () => {
|
onLoadFinished: () => {
|
||||||
// Tell SessionStore.jsm that it may want to restore some more tabs,
|
// 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.
|
// since it restores a max of MAX_CONCURRENT_TAB_RESTORES at a time.
|
||||||
this.mm.sendAsyncMessage("SessionStore:restoreTabContentComplete", {
|
this.mm.sendAsyncMessage("SessionStore:restoreTabContentComplete", {
|
||||||
epoch,
|
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) {
|
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT) {
|
||||||
// For non-remote tabs, when restoreHistory finishes, we send a synchronous
|
// For non-remote tabs, when restoreHistory finishes, we send a synchronous
|
||||||
|
@ -661,7 +535,7 @@ class ContentSessionStore {
|
||||||
epoch,
|
epoch,
|
||||||
isRemotenessUpdate,
|
isRemotenessUpdate,
|
||||||
});
|
});
|
||||||
} else {
|
} else if (!this._shistoryInParent) {
|
||||||
this.mm.sendAsyncMessage("SessionStore:restoreHistoryComplete", {
|
this.mm.sendAsyncMessage("SessionStore:restoreHistoryComplete", {
|
||||||
epoch,
|
epoch,
|
||||||
isRemotenessUpdate,
|
isRemotenessUpdate,
|
||||||
|
@ -683,6 +557,17 @@ class ContentSessionStore {
|
||||||
epoch,
|
epoch,
|
||||||
isRemotenessUpdate,
|
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) {
|
||||||
|
// super(store);
|
||||||
|
this.store = store;
|
||||||
|
|
||||||
|
this._observers = new Set();
|
||||||
|
// let ifreq = this.mm.docShell.QueryInterface(Ci.nsIInterfaceRequestor);
|
||||||
|
let ifreq = this.store.mm.docShell.QueryInterface(Ci.nsIInterfaceRequestor);
|
||||||
|
let webProgress = ifreq.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) {
|
||||||
|
// super(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.store.mm.docShell
|
||||||
|
.QueryInterface(Ci.nsIWebNavigation)
|
||||||
|
.sessionHistory.legacySHistory.addSHistoryListener(this);
|
||||||
|
|
||||||
|
// Collect data if we start with a non-empty shistory.
|
||||||
|
if (!SessionHistory.isEmpty(this.store.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.store.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.
|
// The content script encountered an error.
|
||||||
"SessionStore: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
|
// 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.
|
// For a description see above.
|
||||||
"SessionStore:error",
|
"SessionStore:error",
|
||||||
|
|
||||||
|
// For a description see above.
|
||||||
|
"SessionStore:addSHistoryListener",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// The list of messages we accept without an "epoch" parameter.
|
// The list of messages we accept without an "epoch" parameter.
|
||||||
|
@ -122,6 +141,9 @@ const NOEPOCH_MESSAGES = new Set([
|
||||||
|
|
||||||
// For a description see above.
|
// For a description see above.
|
||||||
"SessionStore:error",
|
"SessionStore:error",
|
||||||
|
|
||||||
|
// For a description see above.
|
||||||
|
"SessionStore:addSHistoryListener",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// The list of messages we want to receive even during the short period after a
|
// 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.
|
// 'browser.startup.page' preference value to resume the previous session.
|
||||||
const BROWSER_STARTUP_RESUME_SESSION = 3;
|
const BROWSER_STARTUP_RESUME_SESSION = 3;
|
||||||
|
|
||||||
|
// for session history listener
|
||||||
|
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/PrivateBrowsingUtils.jsm", this);
|
||||||
ChromeUtils.import("resource://gre/modules/Services.jsm", this);
|
ChromeUtils.import("resource://gre/modules/Services.jsm", this);
|
||||||
ChromeUtils.import("resource://gre/modules/TelemetryTimestamps.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/XPCOMUtils.jsm", this);
|
||||||
ChromeUtils.import("resource://gre/modules/osfile.jsm", this);
|
ChromeUtils.import("resource://gre/modules/osfile.jsm", this);
|
||||||
|
|
||||||
|
ChromeUtils.defineModuleGetter(
|
||||||
|
this,
|
||||||
|
"SessionHistory",
|
||||||
|
"resource://gre/modules/sessionstore/SessionHistory.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
XPCOMUtils.defineLazyServiceGetters(this, {
|
XPCOMUtils.defineLazyServiceGetters(this, {
|
||||||
gScreenManager: ["@mozilla.org/gfx/screenmanager;1", "nsIScreenManager"],
|
gScreenManager: ["@mozilla.org/gfx/screenmanager;1", "nsIScreenManager"],
|
||||||
Telemetry: ["@mozilla.org/base/telemetry;1", "nsITelemetry"],
|
Telemetry: ["@mozilla.org/base/telemetry;1", "nsITelemetry"],
|
||||||
|
@ -501,6 +533,15 @@ var SessionStoreInternal = {
|
||||||
// windows yet to be restored
|
// windows yet to be restored
|
||||||
_restoreCount: -1,
|
_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.
|
// For each <browser> element, records the current epoch.
|
||||||
_browserEpochs: new WeakMap(),
|
_browserEpochs: new WeakMap(),
|
||||||
|
|
||||||
|
@ -824,6 +865,11 @@ var SessionStoreInternal = {
|
||||||
"privacy.resistFingerprinting"
|
"privacy.resistFingerprinting"
|
||||||
);
|
);
|
||||||
Services.prefs.addObserver("privacy.resistFingerprinting", this);
|
Services.prefs.addObserver("privacy.resistFingerprinting", this);
|
||||||
|
|
||||||
|
this._shistoryInParent = Services.prefs.getBoolPref(
|
||||||
|
"fission.sessionHistoryInParent",
|
||||||
|
false
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -907,6 +953,211 @@ var SessionStoreInternal = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Create a sHistoryLister and register it.
|
||||||
|
// 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) {
|
||||||
|
dump(
|
||||||
|
"====DEBUG==== addSHistoryListener(), aBrowser.frameLoader not exists" +
|
||||||
|
",browser.currentURI.displaySpec=" +
|
||||||
|
spec +
|
||||||
|
"\n"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!aBrowser.frameLoader.browsingContext) {
|
||||||
|
dump(
|
||||||
|
"====DEBUG==== addSHistoryListener(), aBrowser.fl.browsingContext not exists" +
|
||||||
|
",browser.currentURI.displaySpec=" +
|
||||||
|
spec +
|
||||||
|
"\n"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!aBrowser.frameLoader.browsingContext.sessionHistory) {
|
||||||
|
dump(
|
||||||
|
"====DEBUG==== addSHistoryListener(), aBrowser.fl.bc.sessionHistory not exists" +
|
||||||
|
",browser.currentURI.displaySpec=" +
|
||||||
|
spec +
|
||||||
|
"\n"
|
||||||
|
);
|
||||||
|
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) {
|
||||||
|
// Need to do something
|
||||||
|
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 tab contents.
|
||||||
|
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) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!aBrowser.frameLoader.browsingContext) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!aBrowser.frameLoader.browsingContext.sessionHistory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let listener = new SHistoryListener(aBrowser);
|
||||||
|
this._browserSHistoryListenerForRestore.set(
|
||||||
|
aBrowser.permanentKey,
|
||||||
|
listener
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
updateSessionStoreFromTablistener(aBrowser, aData) {
|
updateSessionStoreFromTablistener(aBrowser, aData) {
|
||||||
if (aBrowser.permanentKey == undefined) {
|
if (aBrowser.permanentKey == undefined) {
|
||||||
return;
|
return;
|
||||||
|
@ -917,6 +1168,72 @@ var SessionStoreInternal = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sHistoryChangedInListener = false;
|
||||||
|
let listener = this._browserSHistoryListener.get(aBrowser.permanentKey);
|
||||||
|
if (listener) {
|
||||||
|
sHistoryChangedInListener = listener._sHistoryChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aData.sHistoryNeeded || sHistoryChangedInListener) {
|
||||||
|
if (!listener) {
|
||||||
|
dump(
|
||||||
|
"====DEBUG==== @ SessionStore.jsm updateSessionStoreFromTablistener() with aData.sHistoryNeeded, but no SHlistener. Add again!!!\n"
|
||||||
|
);
|
||||||
|
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 {
|
||||||
|
dump(
|
||||||
|
"====DEBUG==== @SessionStore.jsm:updateSessionStoreFromTablistener() with sHistoryNeeded, but no fL.bC.sessionHistory\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dump(
|
||||||
|
"====DEBUG==== @ SessionStore.jsm updateSessionStoreFromTablistener() with sHistoryNeeded, but no sHlistener!!!\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("sHistoryNeeded" in aData) {
|
||||||
|
delete aData.sHistoryNeeded;
|
||||||
|
}
|
||||||
|
|
||||||
TabState.update(aBrowser, aData);
|
TabState.update(aBrowser, aData);
|
||||||
let win = aBrowser.ownerGlobal;
|
let win = aBrowser.ownerGlobal;
|
||||||
this.saveStateDelayed(win);
|
this.saveStateDelayed(win);
|
||||||
|
@ -965,6 +1282,34 @@ var SessionStoreInternal = {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (aMessage.name) {
|
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 {
|
||||||
|
dump(
|
||||||
|
"====DEBUG==== @SessionStore.jsm receive SessionStore:restoreSHistoryInParent: but cannot find sessionHistory from bc\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
browser.messageManager.sendAsyncMessage(
|
||||||
|
"SessionStore:finishRestoreHistory"
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
case "SessionStore:update":
|
case "SessionStore:update":
|
||||||
// |browser.frameLoader| might be empty if the browser was already
|
// |browser.frameLoader| might be empty if the browser was already
|
||||||
// destroyed and its tab removed. In that case we still have the last
|
// destroyed and its tab removed. In that case we still have the last
|
||||||
|
@ -984,6 +1329,13 @@ var SessionStoreInternal = {
|
||||||
// late and will never respond. If they have been sent shortly after
|
// 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.
|
// switching a browser's remoteness there isn't too much data to skip.
|
||||||
TabStateFlusher.resolveAll(browser);
|
TabStateFlusher.resolveAll(browser);
|
||||||
|
let listener = this._browserSHistoryListener.get(
|
||||||
|
browser.permanentKey
|
||||||
|
);
|
||||||
|
if (listener) {
|
||||||
|
listener.uninstall();
|
||||||
|
this._browserSHistoryListener.delete(browser.permanentKey);
|
||||||
|
}
|
||||||
} else if (aMessage.data.flushID) {
|
} else if (aMessage.data.flushID) {
|
||||||
// This is an update kicked off by an async flush request. Notify the
|
// This is an update kicked off by an async flush request. Notify the
|
||||||
// TabStateFlusher so that it can finish the request and notify its
|
// TabStateFlusher so that it can finish the request and notify its
|
||||||
|
@ -1073,6 +1425,39 @@ var SessionStoreInternal = {
|
||||||
tab.dispatchEvent(event);
|
tab.dispatchEvent(event);
|
||||||
break;
|
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 {
|
||||||
|
dump(
|
||||||
|
"====DEBUG==== receive SessionStore:reloadCurrentEntry browser.fL.bC.sessionHistory is null\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dump(
|
||||||
|
"====DEBUG==== receive SessionStore:reloadCurrentEntry browser.fL.browsingContext is null\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dump(
|
||||||
|
"====DEBUG==== receive SessionStore:reloadCurrentEntry browser.frameLoader is null\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "SessionStore:restoreTabContentStarted":
|
case "SessionStore:restoreTabContentStarted":
|
||||||
if (TAB_STATE_FOR_BROWSER.get(browser) == TAB_STATE_NEEDS_RESTORE) {
|
if (TAB_STATE_FOR_BROWSER.get(browser) == TAB_STATE_NEEDS_RESTORE) {
|
||||||
// If a load not initiated by sessionstore was started in a
|
// If a load not initiated by sessionstore was started in a
|
||||||
|
@ -1115,9 +1500,7 @@ var SessionStoreInternal = {
|
||||||
|
|
||||||
SessionStoreInternal._resetLocalTabRestoringState(tab);
|
SessionStoreInternal._resetLocalTabRestoringState(tab);
|
||||||
SessionStoreInternal.restoreNextTab();
|
SessionStoreInternal.restoreNextTab();
|
||||||
|
|
||||||
this._sendTabRestoredNotification(tab, data.isRemotenessUpdate);
|
this._sendTabRestoredNotification(tab, data.isRemotenessUpdate);
|
||||||
|
|
||||||
Services.obs.notifyObservers(
|
Services.obs.notifyObservers(
|
||||||
null,
|
null,
|
||||||
"sessionstore-one-or-no-tab-restored"
|
"sessionstore-one-or-no-tab-restored"
|
||||||
|
@ -1211,6 +1594,11 @@ var SessionStoreInternal = {
|
||||||
epoch: newEpoch,
|
epoch: newEpoch,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let listener = this._browserSHistoryListener.get(target.permanentKey);
|
||||||
|
if (listener) {
|
||||||
|
listener.notifySHistoryChanges(-1);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`unhandled event ${aEvent.type}?`);
|
throw new Error(`unhandled event ${aEvent.type}?`);
|
||||||
|
@ -5796,6 +6184,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(
|
browser.messageManager.sendAsyncMessage(
|
||||||
"SessionStore:restoreHistory",
|
"SessionStore:restoreHistory",
|
||||||
options
|
options
|
||||||
|
|
|
@ -17,6 +17,7 @@ EXTRA_JS_MODULES.sessionstore = [
|
||||||
'RunState.jsm',
|
'RunState.jsm',
|
||||||
'SessionCookies.jsm',
|
'SessionCookies.jsm',
|
||||||
'SessionFile.jsm',
|
'SessionFile.jsm',
|
||||||
|
'SessionHistoryListener.jsm',
|
||||||
'SessionMigration.jsm',
|
'SessionMigration.jsm',
|
||||||
'SessionSaver.jsm',
|
'SessionSaver.jsm',
|
||||||
'SessionStartup.jsm',
|
'SessionStartup.jsm',
|
||||||
|
|
|
@ -32,12 +32,26 @@ function restoreClosedTabWithValue(rval) {
|
||||||
return ss.undoCloseTab(window, index);
|
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) {
|
return SpecialPowers.spawn(browser, [snippet], async function(codeSnippet) {
|
||||||
let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
|
let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||||
let shistory = webNavigation.sessionHistory.legacySHistory;
|
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
|
// eslint-disable-next-line no-eval
|
||||||
eval(codeSnippet);
|
eval(codeSnippet);
|
||||||
|
|
||||||
|
@ -121,7 +135,7 @@ add_task(async function save_worthy_tabs_remote_final() {
|
||||||
let snippet =
|
let snippet =
|
||||||
'webNavigation.loadURI("https://example.com/",\
|
'webNavigation.loadURI("https://example.com/",\
|
||||||
{triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()})';
|
{triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()})';
|
||||||
await promiseNewLocationAndHistoryEntryReplaced(browser, snippet);
|
await promiseNewLocationAndHistoryEntryReplaced(tab, snippet);
|
||||||
|
|
||||||
// Remotness shouldn't have changed.
|
// Remotness shouldn't have changed.
|
||||||
ok(browser.isRemoteBrowser, "browser is still remote");
|
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() {
|
add_task(async function dont_save_empty_tabs_final() {
|
||||||
let { tab, r } = await createTabWithRandomValue("https://example.com/");
|
let { tab, r } = await createTabWithRandomValue("https://example.com/");
|
||||||
let browser = tab.linkedBrowser;
|
|
||||||
|
|
||||||
// Replace the current page with an about:blank entry.
|
// Replace the current page with an about:blank entry.
|
||||||
let snippet = 'content.location.replace("about:blank")';
|
let snippet = 'content.location.replace("about:blank")';
|
||||||
await promiseNewLocationAndHistoryEntryReplaced(browser, snippet);
|
await promiseNewLocationAndHistoryEntryReplaced(tab, snippet);
|
||||||
|
|
||||||
// Remove the tab before the update arrives.
|
// Remove the tab before the update arrives.
|
||||||
let promise = promiseRemoveTabAndSessionState(tab);
|
let promise = promiseRemoveTabAndSessionState(tab);
|
||||||
|
|
|
@ -38,8 +38,12 @@ add_task(async function test_add_interesting_window() {
|
||||||
content.location = newPage;
|
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
|
// Clear out the userTypedValue so that the new window looks like
|
||||||
// it's really not worth restoring.
|
// it's really not worth restoring.
|
||||||
browser.userTypedValue = null;
|
browser.userTypedValue = null;
|
||||||
|
|
|
@ -556,6 +556,10 @@ function promiseDelayedStartupFinished(aWindow) {
|
||||||
return new Promise(resolve => whenDelayedStartupFinished(aWindow, resolve));
|
return new Promise(resolve => whenDelayedStartupFinished(aWindow, resolve));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function promiseOnHistoryReplaceEntry(tab) {
|
||||||
|
return BrowserTestUtils.waitForEvent(tab, "SSHistoryReplaceEntry");
|
||||||
|
}
|
||||||
|
|
||||||
function promiseTabRestored(tab) {
|
function promiseTabRestored(tab) {
|
||||||
return BrowserTestUtils.waitForEvent(tab, "SSTabRestored");
|
return BrowserTestUtils.waitForEvent(tab, "SSTabRestored");
|
||||||
}
|
}
|
||||||
|
|
|
@ -3199,6 +3199,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,
|
void nsFrameLoader::Print(uint64_t aOuterWindowID,
|
||||||
nsIPrintSettings* aPrintSettings,
|
nsIPrintSettings* aPrintSettings,
|
||||||
nsIWebProgressListener* aProgressListener,
|
nsIWebProgressListener* aProgressListener,
|
||||||
|
|
|
@ -211,6 +211,8 @@ class nsFrameLoader final : public nsStubMutationObserver,
|
||||||
|
|
||||||
void RequestEpochUpdate(uint32_t aEpoch);
|
void RequestEpochUpdate(uint32_t aEpoch);
|
||||||
|
|
||||||
|
void RequestSHistoryUpdate(bool aImmediately = false);
|
||||||
|
|
||||||
void Print(uint64_t aOuterWindowID, nsIPrintSettings* aPrintSettings,
|
void Print(uint64_t aOuterWindowID, nsIPrintSettings* aPrintSettings,
|
||||||
nsIWebProgressListener* aProgressListener,
|
nsIWebProgressListener* aProgressListener,
|
||||||
mozilla::ErrorResult& aRv);
|
mozilla::ErrorResult& aRv);
|
||||||
|
|
|
@ -2013,6 +2013,14 @@ mozilla::ipc::IPCResult BrowserChild::RecvUpdateEpoch(const uint32_t& aEpoch) {
|
||||||
return IPC_OK();
|
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.
|
// In case handling repeated keys takes much time, we skip firing new ones.
|
||||||
bool BrowserChild::SkipRepeatedKeyEvent(const WidgetKeyboardEvent& aEvent) {
|
bool BrowserChild::SkipRepeatedKeyEvent(const WidgetKeyboardEvent& aEvent) {
|
||||||
if (mRepeatedKeyEventTime.IsNull() || !aEvent.CanSkipInRemoteProcess() ||
|
if (mRepeatedKeyEventTime.IsNull() || !aEvent.CanSkipInRemoteProcess() ||
|
||||||
|
@ -3938,8 +3946,9 @@ bool BrowserChild::UpdateSessionStore(uint32_t aFlushId, bool aIsFinal) {
|
||||||
|
|
||||||
Unused << SendSessionStoreUpdate(
|
Unused << SendSessionStoreUpdate(
|
||||||
docShellCaps, privatedMode, positions, positionDescendants, inputs,
|
docShellCaps, privatedMode, positions, positionDescendants, inputs,
|
||||||
idVals, xPathVals, origins, keys, values, isFullStorage, aFlushId,
|
idVals, xPathVals, origins, keys, values, isFullStorage,
|
||||||
aIsFinal, mSessionStoreListener->GetEpoch());
|
store->GetAndClearSHistoryChanged(), aFlushId, aIsFinal,
|
||||||
|
mSessionStoreListener->GetEpoch());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -368,6 +368,8 @@ class BrowserChild final : public nsMessageManagerScriptExecutor,
|
||||||
|
|
||||||
mozilla::ipc::IPCResult RecvUpdateEpoch(const uint32_t& aEpoch);
|
mozilla::ipc::IPCResult RecvUpdateEpoch(const uint32_t& aEpoch);
|
||||||
|
|
||||||
|
mozilla::ipc::IPCResult RecvUpdateSHistory(const bool& aImmediately);
|
||||||
|
|
||||||
mozilla::ipc::IPCResult RecvNativeSynthesisResponse(
|
mozilla::ipc::IPCResult RecvNativeSynthesisResponse(
|
||||||
const uint64_t& aObserverId, const nsCString& aResponse);
|
const uint64_t& aObserverId, const nsCString& aResponse);
|
||||||
|
|
||||||
|
|
|
@ -2805,7 +2805,8 @@ mozilla::ipc::IPCResult BrowserParent::RecvSessionStoreUpdate(
|
||||||
const nsTArray<CollectedInputDataValue>& aXPathVals,
|
const nsTArray<CollectedInputDataValue>& aXPathVals,
|
||||||
nsTArray<nsCString>&& aOrigins, nsTArray<nsString>&& aKeys,
|
nsTArray<nsCString>&& aOrigins, nsTArray<nsString>&& aKeys,
|
||||||
nsTArray<nsString>&& aValues, const bool aIsFullStorage,
|
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;
|
UpdateSessionStoreData data;
|
||||||
if (aDocShellCaps.isSome()) {
|
if (aDocShellCaps.isSome()) {
|
||||||
data.mDocShellCaps.Construct() = aDocShellCaps.value();
|
data.mDocShellCaps.Construct() = aDocShellCaps.value();
|
||||||
|
@ -2862,8 +2863,8 @@ mozilla::ipc::IPCResult BrowserParent::RecvSessionStoreUpdate(
|
||||||
bool ok = ToJSValue(jsapi.cx(), data, &dataVal);
|
bool ok = ToJSValue(jsapi.cx(), data, &dataVal);
|
||||||
NS_ENSURE_TRUE(ok, IPC_OK());
|
NS_ENSURE_TRUE(ok, IPC_OK());
|
||||||
|
|
||||||
nsresult rv = funcs->UpdateSessionStore(mFrameElement, aFlushId, aIsFinal,
|
nsresult rv = funcs->UpdateSessionStore(
|
||||||
aEpoch, dataVal);
|
mFrameElement, aFlushId, aIsFinal, aEpoch, dataVal, aNeedCollectSHistory);
|
||||||
NS_ENSURE_SUCCESS(rv, IPC_OK());
|
NS_ENSURE_SUCCESS(rv, IPC_OK());
|
||||||
|
|
||||||
return IPC_OK();
|
return IPC_OK();
|
||||||
|
|
|
@ -340,7 +340,8 @@ class BrowserParent final : public PBrowserParent,
|
||||||
const nsTArray<CollectedInputDataValue>& aXPathVals,
|
const nsTArray<CollectedInputDataValue>& aXPathVals,
|
||||||
nsTArray<nsCString>&& aOrigins, nsTArray<nsString>&& aKeys,
|
nsTArray<nsCString>&& aOrigins, nsTArray<nsString>&& aKeys,
|
||||||
nsTArray<nsString>&& aValues, const bool aIsFullStorage,
|
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(
|
mozilla::ipc::IPCResult RecvBrowserFrameOpenWindow(
|
||||||
PBrowserParent* aOpener, const nsString& aURL, const nsString& aName,
|
PBrowserParent* aOpener, const nsString& aURL, const nsString& aName,
|
||||||
|
|
|
@ -614,12 +614,14 @@ parent:
|
||||||
CollectedInputDataValue[] aXPathVals,
|
CollectedInputDataValue[] aXPathVals,
|
||||||
nsCString[] aOrigins, nsString[] aKeys,
|
nsCString[] aOrigins, nsString[] aKeys,
|
||||||
nsString[] aValues, bool aIsFullStorage,
|
nsString[] aValues, bool aIsFullStorage,
|
||||||
uint32_t aFlushId, bool aIsFinal, uint32_t aEpoch);
|
bool aNeedCollectSHistory, uint32_t aFlushId,
|
||||||
|
bool aIsFinal, uint32_t aEpoch);
|
||||||
|
|
||||||
child:
|
child:
|
||||||
async NativeSynthesisResponse(uint64_t aObserverId, nsCString aResponse);
|
async NativeSynthesisResponse(uint64_t aObserverId, nsCString aResponse);
|
||||||
async FlushTabState(uint32_t aFlushId, bool aIsFinal);
|
async FlushTabState(uint32_t aFlushId, bool aIsFinal);
|
||||||
async UpdateEpoch(uint32_t aEpoch);
|
async UpdateEpoch(uint32_t aEpoch);
|
||||||
|
async UpdateSHistory(bool aImmediately);
|
||||||
|
|
||||||
parent:
|
parent:
|
||||||
|
|
||||||
|
|
|
@ -112,6 +112,11 @@ interface FrameLoader {
|
||||||
*/
|
*/
|
||||||
void requestEpochUpdate(unsigned long aEpoch);
|
void requestEpochUpdate(unsigned long aEpoch);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a session history update in native sessionStoreListeners.
|
||||||
|
*/
|
||||||
|
void requestSHistoryUpdate(boolean aImmediately);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print the current document.
|
* Print the current document.
|
||||||
*
|
*
|
||||||
|
|
|
@ -11,5 +11,7 @@ webidl Element;
|
||||||
interface nsISessionStoreFunctions : nsISupports {
|
interface nsISessionStoreFunctions : nsISupports {
|
||||||
// update sessionStore from the tabListener implemented by C++
|
// update sessionStore from the tabListener implemented by C++
|
||||||
// aData is a UpdateSessionStoreData dictionary (From SessionStoreUtils.webidl)
|
// 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",
|
SessionStore: "resource:///modules/sessionstore/SessionStore.jsm",
|
||||||
});
|
});
|
||||||
|
|
||||||
function UpdateSessionStore(aBrowser, aFlushId, aIsFinal, aEpoch, aData) {
|
function UpdateSessionStore(
|
||||||
|
aBrowser,
|
||||||
|
aFlushId,
|
||||||
|
aIsFinal,
|
||||||
|
aEpoch,
|
||||||
|
aData,
|
||||||
|
aCollectSHistory
|
||||||
|
) {
|
||||||
return SessionStoreFuncInternal.updateSessionStore(
|
return SessionStoreFuncInternal.updateSessionStore(
|
||||||
aBrowser,
|
aBrowser,
|
||||||
aFlushId,
|
aFlushId,
|
||||||
aIsFinal,
|
aIsFinal,
|
||||||
aEpoch,
|
aEpoch,
|
||||||
aData
|
aData,
|
||||||
|
aCollectSHistory
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,7 +390,8 @@ var SessionStoreFuncInternal = {
|
||||||
aFlushId,
|
aFlushId,
|
||||||
aIsFinal,
|
aIsFinal,
|
||||||
aEpoch,
|
aEpoch,
|
||||||
aData
|
aData,
|
||||||
|
aCollectSHistory
|
||||||
) {
|
) {
|
||||||
let currentData = {};
|
let currentData = {};
|
||||||
if (aData.docShellCaps != undefined) {
|
if (aData.docShellCaps != undefined) {
|
||||||
|
@ -434,6 +443,7 @@ var SessionStoreFuncInternal = {
|
||||||
flushID: aFlushId,
|
flushID: aFlushId,
|
||||||
isFinal: aIsFinal,
|
isFinal: aIsFinal,
|
||||||
epoch: aEpoch,
|
epoch: aEpoch,
|
||||||
|
sHistoryNeeded: aCollectSHistory,
|
||||||
});
|
});
|
||||||
this._formDataId = [];
|
this._formDataId = [];
|
||||||
this._formDataIdValue = [];
|
this._formDataIdValue = [];
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "mozilla/dom/SessionStoreUtilsBinding.h"
|
#include "mozilla/dom/SessionStoreUtilsBinding.h"
|
||||||
#include "mozilla/dom/StorageEvent.h"
|
#include "mozilla/dom/StorageEvent.h"
|
||||||
#include "mozilla/dom/BrowserChild.h"
|
#include "mozilla/dom/BrowserChild.h"
|
||||||
|
#include "mozilla/StaticPrefs_fission.h"
|
||||||
#include "nsGenericHTMLElement.h"
|
#include "nsGenericHTMLElement.h"
|
||||||
#include "nsDocShell.h"
|
#include "nsDocShell.h"
|
||||||
#include "nsIAppWindow.h"
|
#include "nsIAppWindow.h"
|
||||||
|
@ -44,7 +45,10 @@ ContentSessionStore::ContentSessionStore(nsIDocShell* aDocShell)
|
||||||
mScrollChanged(NO_CHANGE),
|
mScrollChanged(NO_CHANGE),
|
||||||
mFormDataChanged(NO_CHANGE),
|
mFormDataChanged(NO_CHANGE),
|
||||||
mStorageStatus(NO_STORAGE),
|
mStorageStatus(NO_STORAGE),
|
||||||
mDocCapChanged(false) {
|
mDocCapChanged(false),
|
||||||
|
mSHistoryInParent(StaticPrefs::fission_sessionHistoryInParent()),
|
||||||
|
mSHistoryChanged(false),
|
||||||
|
mSHistoryChangedFromParent(false) {
|
||||||
MOZ_ASSERT(mDocShell);
|
MOZ_ASSERT(mDocShell);
|
||||||
// Check that value at startup as it might have
|
// Check that value at startup as it might have
|
||||||
// been set before the frame script was loaded.
|
// been set before the frame script was loaded.
|
||||||
|
@ -123,11 +127,19 @@ void ContentSessionStore::OnDocumentStart() {
|
||||||
}
|
}
|
||||||
|
|
||||||
SetFullStorageNeeded();
|
SetFullStorageNeeded();
|
||||||
|
|
||||||
|
if (mSHistoryInParent) {
|
||||||
|
mSHistoryChanged = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentSessionStore::OnDocumentEnd() {
|
void ContentSessionStore::OnDocumentEnd() {
|
||||||
mScrollChanged = WITH_CHANGE;
|
mScrollChanged = WITH_CHANGE;
|
||||||
SetFullStorageNeeded();
|
SetFullStorageNeeded();
|
||||||
|
|
||||||
|
if (mSHistoryInParent) {
|
||||||
|
mSHistoryChanged = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TabListener)
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TabListener)
|
||||||
|
@ -156,7 +168,8 @@ TabListener::TabListener(nsIDocShell* aDocShell, Element* aElement)
|
||||||
mUpdatedTimer(nullptr),
|
mUpdatedTimer(nullptr),
|
||||||
mTimeoutDisabled(false),
|
mTimeoutDisabled(false),
|
||||||
mUpdateInterval(15000),
|
mUpdateInterval(15000),
|
||||||
mEpoch(0) {
|
mEpoch(0),
|
||||||
|
mSHistoryInParent(StaticPrefs::fission_sessionHistoryInParent()) {
|
||||||
MOZ_ASSERT(mDocShell);
|
MOZ_ASSERT(mDocShell);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,6 +219,12 @@ nsresult TabListener::Init() {
|
||||||
eventTarget->AddSystemEventListener(NS_LITERAL_STRING("mozvisualscroll"),
|
eventTarget->AddSystemEventListener(NS_LITERAL_STRING("mozvisualscroll"),
|
||||||
this, false);
|
this, false);
|
||||||
eventTarget->AddSystemEventListener(NS_LITERAL_STRING("input"), this, false);
|
eventTarget->AddSystemEventListener(NS_LITERAL_STRING("input"), this, false);
|
||||||
|
|
||||||
|
if (mSHistoryInParent) {
|
||||||
|
eventTarget->AddSystemEventListener(NS_LITERAL_STRING("DOMTitleChanged"),
|
||||||
|
this, false);
|
||||||
|
}
|
||||||
|
|
||||||
mEventListenerRegistered = true;
|
mEventListenerRegistered = true;
|
||||||
eventTarget->AddSystemEventListener(
|
eventTarget->AddSystemEventListener(
|
||||||
NS_LITERAL_STRING("MozSessionStorageChanged"), this, false);
|
NS_LITERAL_STRING("MozSessionStorageChanged"), this, false);
|
||||||
|
@ -347,6 +366,9 @@ TabListener::HandleEvent(Event* aEvent) {
|
||||||
if (mSessionStore->AppendSessionStorageChange(event)) {
|
if (mSessionStore->AppendSessionStorageChange(event)) {
|
||||||
AddTimerForUpdate();
|
AddTimerForUpdate();
|
||||||
}
|
}
|
||||||
|
} else if (eventType.EqualsLiteral("DOMTitleChanged")) {
|
||||||
|
mSessionStore->SetSHistoryChanged();
|
||||||
|
AddTimerForUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
|
@ -641,6 +663,15 @@ bool TabListener::ForceFlushFromParent(uint32_t aFlushId, bool aIsFinal) {
|
||||||
return UpdateSessionStore(aFlushId, 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) {
|
bool TabListener::UpdateSessionStore(uint32_t aFlushId, bool aIsFinal) {
|
||||||
if (!aFlushId) {
|
if (!aFlushId) {
|
||||||
if (!mSessionStore || !mSessionStore->UpdateNeeded()) {
|
if (!mSessionStore || !mSessionStore->UpdateNeeded()) {
|
||||||
|
@ -740,8 +771,9 @@ bool TabListener::UpdateSessionStore(uint32_t aFlushId, bool aIsFinal) {
|
||||||
bool ok = ToJSValue(jsapi.cx(), data, &dataVal);
|
bool ok = ToJSValue(jsapi.cx(), data, &dataVal);
|
||||||
NS_ENSURE_TRUE(ok, false);
|
NS_ENSURE_TRUE(ok, false);
|
||||||
|
|
||||||
nsresult rv = funcs->UpdateSessionStore(mOwnerContent, aFlushId, aIsFinal,
|
nsresult rv = funcs->UpdateSessionStore(
|
||||||
mEpoch, dataVal);
|
mOwnerContent, aFlushId, aIsFinal, mEpoch, dataVal,
|
||||||
|
mSessionStore->GetAndClearSHistoryChanged());
|
||||||
NS_ENSURE_SUCCESS(rv, false);
|
NS_ENSURE_SUCCESS(rv, false);
|
||||||
StopTimerForUpdate();
|
StopTimerForUpdate();
|
||||||
return true;
|
return true;
|
||||||
|
@ -791,6 +823,10 @@ void TabListener::RemoveListeners() {
|
||||||
NS_LITERAL_STRING("mozvisualscroll"), this, false);
|
NS_LITERAL_STRING("mozvisualscroll"), this, false);
|
||||||
eventTarget->RemoveSystemEventListener(NS_LITERAL_STRING("input"), this,
|
eventTarget->RemoveSystemEventListener(NS_LITERAL_STRING("input"), this,
|
||||||
false);
|
false);
|
||||||
|
if (mSHistoryInParent) {
|
||||||
|
eventTarget->RemoveSystemEventListener(
|
||||||
|
NS_LITERAL_STRING("DOMTitleChanged"), this, false);
|
||||||
|
}
|
||||||
mEventListenerRegistered = false;
|
mEventListenerRegistered = false;
|
||||||
}
|
}
|
||||||
if (mStorageChangeListenerRegistered) {
|
if (mStorageChangeListenerRegistered) {
|
||||||
|
|
|
@ -65,11 +65,24 @@ class ContentSessionStore {
|
||||||
// Return true if there is a new storage change which is appended.
|
// Return true if there is a new storage change which is appended.
|
||||||
bool AppendSessionStorageChange(StorageEvent* aEvent);
|
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 OnDocumentStart();
|
||||||
void OnDocumentEnd();
|
void OnDocumentEnd();
|
||||||
bool UpdateNeeded() {
|
bool UpdateNeeded() {
|
||||||
return mPrivateChanged || mDocCapChanged || IsScrollPositionChanged() ||
|
return mPrivateChanged || mDocCapChanged || IsScrollPositionChanged() ||
|
||||||
IsFormDataChanged() || IsStorageUpdated();
|
IsFormDataChanged() || IsStorageUpdated() || mSHistoryChanged ||
|
||||||
|
mSHistoryChangedFromParent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -97,6 +110,17 @@ class ContentSessionStore {
|
||||||
nsTArray<nsCString> mOrigins;
|
nsTArray<nsCString> mOrigins;
|
||||||
nsTArray<nsString> mKeys;
|
nsTArray<nsString> mKeys;
|
||||||
nsTArray<nsString> mValues;
|
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,
|
class TabListener : public nsIDOMEventListener,
|
||||||
|
@ -114,6 +138,7 @@ class TabListener : public nsIDOMEventListener,
|
||||||
void RemoveListeners();
|
void RemoveListeners();
|
||||||
void SetEpoch(uint32_t aEpoch) { mEpoch = aEpoch; }
|
void SetEpoch(uint32_t aEpoch) { mEpoch = aEpoch; }
|
||||||
uint32_t GetEpoch() { return mEpoch; }
|
uint32_t GetEpoch() { return mEpoch; }
|
||||||
|
void UpdateSHistoryChanges(bool aImmediately);
|
||||||
|
|
||||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||||
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(TabListener, nsIDOMEventListener)
|
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(TabListener, nsIDOMEventListener)
|
||||||
|
@ -145,6 +170,8 @@ class TabListener : public nsIDOMEventListener,
|
||||||
bool mTimeoutDisabled;
|
bool mTimeoutDisabled;
|
||||||
int32_t mUpdateInterval;
|
int32_t mUpdateInterval;
|
||||||
uint32_t mEpoch;
|
uint32_t mEpoch;
|
||||||
|
// sessionHistory in the parent process
|
||||||
|
bool mSHistoryInParent;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace dom
|
} // namespace dom
|
||||||
|
|
|
@ -39,9 +39,23 @@ var SessionHistory = Object.freeze({
|
||||||
return SessionHistoryInternal.collect(docShell, aFromIdx);
|
return SessionHistoryInternal.collect(docShell, aFromIdx);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
collectFromParent(uri, body, history, userContextId, aFromIdx = -1) {
|
||||||
|
return SessionHistoryInternal.collectCommon(
|
||||||
|
uri,
|
||||||
|
body,
|
||||||
|
history,
|
||||||
|
userContextId,
|
||||||
|
aFromIdx
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
restore(docShell, tabData) {
|
restore(docShell, tabData) {
|
||||||
return SessionHistoryInternal.restore(docShell, tabData);
|
return SessionHistoryInternal.restore(docShell, tabData);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
restoreFromParent(history, tabData) {
|
||||||
|
return SessionHistoryInternal.restoreCommon(history, tabData);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -81,12 +95,24 @@ var SessionHistoryInternal = {
|
||||||
collect(docShell, aFromIdx = -1) {
|
collect(docShell, aFromIdx = -1) {
|
||||||
let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
|
let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
|
||||||
let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
|
let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||||
|
let uri = webNavigation.currentURI.displaySpec;
|
||||||
|
let body = webNavigation.document.body;
|
||||||
let history = webNavigation.sessionHistory;
|
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 = {
|
let data = {
|
||||||
entries: [],
|
entries: [],
|
||||||
userContextId: loadContext.originAttributes.userContextId,
|
userContextId,
|
||||||
requestedIndex: history.legacySHistory.requestedIndex + 1,
|
requestedIndex: shistory.requestedIndex + 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
// We want to keep track how many entries we *could* have collected and
|
// We want to keep track how many entries we *could* have collected and
|
||||||
|
@ -95,8 +121,7 @@ var SessionHistoryInternal = {
|
||||||
let skippedCount = 0,
|
let skippedCount = 0,
|
||||||
entryCount = 0;
|
entryCount = 0;
|
||||||
|
|
||||||
if (history && history.count > 0) {
|
if (shistory && shistory.count > 0) {
|
||||||
let shistory = history.legacySHistory.QueryInterface(Ci.nsISHistory);
|
|
||||||
let count = shistory.count;
|
let count = shistory.count;
|
||||||
for (; entryCount < count; entryCount++) {
|
for (; entryCount < count; entryCount++) {
|
||||||
let shEntry = shistory.getEntryAtIndex(entryCount);
|
let shEntry = shistory.getEntryAtIndex(entryCount);
|
||||||
|
@ -109,15 +134,13 @@ var SessionHistoryInternal = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the index isn't out of bounds if an exception was thrown above.
|
// 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
|
// 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,
|
// valid entries, make sure we at least include the current page,
|
||||||
// unless of course we just skipped all entries because aFromIdx was big enough.
|
// unless of course we just skipped all entries because aFromIdx was big enough.
|
||||||
if (!data.entries.length && (skippedCount != entryCount || aFromIdx < 0)) {
|
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
|
// 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
|
// 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
|
// current URL as a single history entry. If the URL is not about:blank
|
||||||
|
@ -337,6 +360,10 @@ var SessionHistoryInternal = {
|
||||||
restore(docShell, tabData) {
|
restore(docShell, tabData) {
|
||||||
let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
|
let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||||
let history = webNavigation.sessionHistory.legacySHistory;
|
let history = webNavigation.sessionHistory.legacySHistory;
|
||||||
|
this.restoreCommon(history, tabData);
|
||||||
|
},
|
||||||
|
|
||||||
|
restoreCommon(history, tabData) {
|
||||||
if (history.count > 0) {
|
if (history.count > 0) {
|
||||||
history.PurgeHistory(history.count);
|
history.PurgeHistory(history.count);
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче