summaryrefslogtreecommitdiffstats
path: root/browser/components/sessionstore
diff options
context:
space:
mode:
authorwolfbeast <mcwerewolf@gmail.com>2018-06-04 13:17:38 +0200
committerwolfbeast <mcwerewolf@gmail.com>2018-06-04 13:17:38 +0200
commita1be17c1cea81ebb1e8b131a662c698d78f3f7f2 (patch)
treea92f7de513be600cc07bac458183e9af40e00c06 /browser/components/sessionstore
parentbf11fdd304898ac675e39b01b280d39550e419d0 (diff)
downloadUXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar
UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar.gz
UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar.lz
UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar.xz
UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.zip
Issue #303 Part 1: Move basilisk files from /browser to /application/basilisk
Diffstat (limited to 'browser/components/sessionstore')
-rw-r--r--browser/components/sessionstore/ContentRestore.jsm434
-rw-r--r--browser/components/sessionstore/DocShellCapabilities.jsm50
-rw-r--r--browser/components/sessionstore/FrameTree.jsm254
-rw-r--r--browser/components/sessionstore/GlobalState.jsm84
-rw-r--r--browser/components/sessionstore/PageStyle.jsm100
-rw-r--r--browser/components/sessionstore/PrivacyFilter.jsm135
-rw-r--r--browser/components/sessionstore/PrivacyLevel.jsm64
-rw-r--r--browser/components/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm214
-rw-r--r--browser/components/sessionstore/RunState.jsm96
-rw-r--r--browser/components/sessionstore/SessionCookies.jsm476
-rw-r--r--browser/components/sessionstore/SessionFile.jsm399
-rw-r--r--browser/components/sessionstore/SessionHistory.jsm431
-rw-r--r--browser/components/sessionstore/SessionMigration.jsm106
-rw-r--r--browser/components/sessionstore/SessionSaver.jsm264
-rw-r--r--browser/components/sessionstore/SessionStorage.jsm173
-rw-r--r--browser/components/sessionstore/SessionStore.jsm4746
-rw-r--r--browser/components/sessionstore/SessionWorker.js381
-rw-r--r--browser/components/sessionstore/SessionWorker.jsm25
-rw-r--r--browser/components/sessionstore/StartupPerformance.jsm234
-rw-r--r--browser/components/sessionstore/TabAttributes.jsm74
-rw-r--r--browser/components/sessionstore/TabState.jsm196
-rw-r--r--browser/components/sessionstore/TabStateCache.jsm163
-rw-r--r--browser/components/sessionstore/TabStateFlusher.jsm184
-rw-r--r--browser/components/sessionstore/content/aboutSessionRestore.js373
-rw-r--r--browser/components/sessionstore/content/aboutSessionRestore.xhtml86
-rw-r--r--browser/components/sessionstore/content/content-sessionStore.js897
-rw-r--r--browser/components/sessionstore/jar.mn8
-rw-r--r--browser/components/sessionstore/moz.build46
-rw-r--r--browser/components/sessionstore/nsISessionStartup.idl66
-rw-r--r--browser/components/sessionstore/nsISessionStore.idl220
-rw-r--r--browser/components/sessionstore/nsSessionStartup.js353
-rw-r--r--browser/components/sessionstore/nsSessionStore.js39
-rw-r--r--browser/components/sessionstore/nsSessionStore.manifest15
33 files changed, 0 insertions, 11386 deletions
diff --git a/browser/components/sessionstore/ContentRestore.jsm b/browser/components/sessionstore/ContentRestore.jsm
deleted file mode 100644
index d4972bcaf..000000000
--- a/browser/components/sessionstore/ContentRestore.jsm
+++ /dev/null
@@ -1,434 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["ContentRestore"];
-
-const Cu = Components.utils;
-const Ci = Components.interfaces;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
-
-XPCOMUtils.defineLazyModuleGetter(this, "DocShellCapabilities",
- "resource:///modules/sessionstore/DocShellCapabilities.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "FormData",
- "resource://gre/modules/FormData.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PageStyle",
- "resource:///modules/sessionstore/PageStyle.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition",
- "resource://gre/modules/ScrollPosition.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
- "resource:///modules/sessionstore/SessionHistory.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
- "resource:///modules/sessionstore/SessionStorage.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Utils",
- "resource://gre/modules/sessionstore/Utils.jsm");
-
-/**
- * This module implements the content side of session restoration. The chrome
- * side is handled by SessionStore.jsm. The functions in this module are called
- * by content-sessionStore.js based on messages received from SessionStore.jsm
- * (or, in one case, based on a "load" event). Each tab has its own
- * ContentRestore instance, constructed by content-sessionStore.js.
- *
- * In a typical restore, content-sessionStore.js will call the following based
- * on messages and events it receives:
- *
- * restoreHistory(tabData, loadArguments, callbacks)
- * Restores the tab's history and session cookies.
- * restoreTabContent(loadArguments, finishCallback)
- * Starts loading the data for the current page to restore.
- * restoreDocument()
- * Restore form and scroll data.
- *
- * When the page has been loaded from the network, we call finishCallback. It
- * should send a message to SessionStore.jsm, which may cause other tabs to be
- * restored.
- *
- * When the page has finished loading, a "load" event will trigger in
- * content-sessionStore.js, which will call restoreDocument. At that point,
- * form data is restored and the restore is complete.
- *
- * At any time, SessionStore.jsm can cancel the ongoing restore by sending a
- * reset message, which causes resetRestore to be called. At that point it's
- * legal to begin another restore.
- */
-function ContentRestore(chromeGlobal) {
- let internal = new ContentRestoreInternal(chromeGlobal);
- let external = {};
-
- let EXPORTED_METHODS = ["restoreHistory",
- "restoreTabContent",
- "restoreDocument",
- "resetRestore"
- ];
-
- for (let method of EXPORTED_METHODS) {
- external[method] = internal[method].bind(internal);
- }
-
- return Object.freeze(external);
-}
-
-function ContentRestoreInternal(chromeGlobal) {
- this.chromeGlobal = chromeGlobal;
-
- // The following fields are only valid during certain phases of the restore
- // process.
-
- // The tabData for the restore. Set in restoreHistory and removed in
- // restoreTabContent.
- this._tabData = null;
-
- // Contains {entry, pageStyle, scrollPositions, formdata}, where entry is a
- // single entry from the tabData.entries array. Set in
- // restoreTabContent and removed in restoreDocument.
- this._restoringDocument = null;
-
- // This listener is used to detect reloads on restoring tabs. Set in
- // restoreHistory and removed in restoreTabContent.
- this._historyListener = null;
-
- // This listener detects when a pending tab starts loading (when not
- // initiated by sessionstore) and when a restoring tab has finished loading
- // data from the network. Set in restoreHistory() and restoreTabContent(),
- // removed in resetRestore().
- this._progressListener = null;
-}
-
-/**
- * The API for the ContentRestore module. Methods listed in EXPORTED_METHODS are
- * public.
- */
-ContentRestoreInternal.prototype = {
-
- get docShell() {
- return this.chromeGlobal.docShell;
- },
-
- /**
- * Starts the process of restoring a tab. The tabData to be restored is passed
- * in here and used throughout the restoration. The epoch (which must be
- * non-zero) is passed through to all the callbacks. If a load in the tab
- * is started while it is pending, the appropriate callbacks are called.
- */
- restoreHistory(tabData, loadArguments, callbacks) {
- this._tabData = tabData;
-
- // In case about:blank isn't done yet.
- let webNavigation = this.docShell.QueryInterface(Ci.nsIWebNavigation);
- webNavigation.stop(Ci.nsIWebNavigation.STOP_ALL);
-
- // Make sure currentURI is set so that switch-to-tab works before the tab is
- // restored. We'll reset this to about:blank when we try to restore the tab
- // to ensure that docshell doeesn't get confused. Don't bother doing this if
- // we're restoring immediately due to a process switch. It just causes the
- // URL bar to be temporarily blank.
- let activeIndex = tabData.index - 1;
- let activePageData = tabData.entries[activeIndex] || {};
- let uri = activePageData.url || null;
- if (uri && !loadArguments) {
- webNavigation.setCurrentURI(Utils.makeURI(uri));
- }
-
- SessionHistory.restore(this.docShell, tabData);
-
- // Add a listener to watch for reloads.
- let listener = new HistoryListener(this.docShell, () => {
- // On reload, restore tab contents.
- this.restoreTabContent(null, false, callbacks.onLoadFinished);
- });
-
- webNavigation.sessionHistory.addSHistoryListener(listener);
- this._historyListener = listener;
-
- // Make sure to reset the capabilities and attributes in case this tab gets
- // reused.
- let disallow = new Set(tabData.disallow && tabData.disallow.split(","));
- DocShellCapabilities.restore(this.docShell, disallow);
-
- if (tabData.storage && this.docShell instanceof Ci.nsIDocShell) {
- SessionStorage.restore(this.docShell, tabData.storage);
- delete tabData.storage;
- }
-
- // Add a progress listener to correctly handle browser.loadURI()
- // calls from foreign code.
- this._progressListener = new ProgressListener(this.docShell, {
- onStartRequest: () => {
- // Some code called browser.loadURI() on a pending tab. It's safe to
- // assume we don't care about restoring scroll or form data.
- this._tabData = null;
-
- // Listen for the tab to finish loading.
- this.restoreTabContentStarted(callbacks.onLoadFinished);
-
- // Notify the parent.
- callbacks.onLoadStarted();
- }
- });
- },
-
- /**
- * Start loading the current page. When the data has finished loading from the
- * network, finishCallback is called. Returns true if the load was successful.
- */
- restoreTabContent: function (loadArguments, isRemotenessUpdate, finishCallback) {
- let tabData = this._tabData;
- this._tabData = null;
-
- let webNavigation = this.docShell.QueryInterface(Ci.nsIWebNavigation);
- let history = webNavigation.sessionHistory;
-
- // Listen for the tab to finish loading.
- this.restoreTabContentStarted(finishCallback);
-
- // 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
- // load happens. Don't bother doing this if we're restoring immediately
- // due to a process switch.
- if (!isRemotenessUpdate) {
- webNavigation.setCurrentURI(Utils.makeURI("about:blank"));
- }
-
- try {
- if (loadArguments) {
- // A load has been redirected to a new process so get history into the
- // same state it was before the load started then trigger the load.
- let referrer = loadArguments.referrer ?
- Utils.makeURI(loadArguments.referrer) : null;
- let referrerPolicy = ('referrerPolicy' in loadArguments
- ? loadArguments.referrerPolicy
- : Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT);
- let postData = loadArguments.postData ?
- Utils.makeInputStream(loadArguments.postData) : null;
- let triggeringPrincipal = loadArguments.triggeringPrincipal
- ? Utils.deserializePrincipal(loadArguments.triggeringPrincipal)
- : null;
-
- if (loadArguments.userContextId) {
- webNavigation.setOriginAttributesBeforeLoading({ userContextId: loadArguments.userContextId });
- }
-
- webNavigation.loadURIWithOptions(loadArguments.uri, loadArguments.flags,
- referrer, referrerPolicy, postData,
- null, null, triggeringPrincipal);
- } else if (tabData.userTypedValue && tabData.userTypedClear) {
- // If the user typed a URL into the URL bar and hit enter right before
- // we crashed, we want to start loading that page again. A non-zero
- // userTypedClear value means that the load had started.
- // Load userTypedValue and fix up the URL if it's partial/broken.
- webNavigation.loadURI(tabData.userTypedValue,
- Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP,
- null, null, null);
- } else if (tabData.entries.length) {
- // Stash away the data we need for restoreDocument.
- let activeIndex = tabData.index - 1;
- this._restoringDocument = {entry: tabData.entries[activeIndex] || {},
- formdata: tabData.formdata || {},
- pageStyle: tabData.pageStyle || {},
- scrollPositions: tabData.scroll || {}};
-
- // In order to work around certain issues in session history, we need to
- // force session history to update its internal index and call reload
- // instead of gotoIndex. See bug 597315.
- history.reloadCurrentEntry();
- } else {
- // If there's nothing to restore, we should still blank the page.
- webNavigation.loadURI("about:blank",
- Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY,
- null, null, null);
- }
-
- return true;
- } catch (ex if ex instanceof Ci.nsIException) {
- // Ignore page load errors, but return false to signal that the load never
- // happened.
- return false;
- }
- },
-
- /**
- * To be called after restoreHistory(). Removes all listeners needed for
- * pending tabs and makes sure to notify when the tab finished loading.
- */
- restoreTabContentStarted(finishCallback) {
- // The reload listener is no longer needed.
- this._historyListener.uninstall();
- this._historyListener = null;
-
- // Remove the old progress listener.
- this._progressListener.uninstall();
-
- // We're about to start a load. This listener will be called when the load
- // has finished getting everything from the network.
- this._progressListener = new ProgressListener(this.docShell, {
- onStopRequest: () => {
- // Call resetRestore() to reset the state back to normal. The data
- // needed for restoreDocument() (which hasn't happened yet) will
- // remain in _restoringDocument.
- this.resetRestore();
-
- finishCallback();
- }
- });
- },
-
- /**
- * Finish restoring the tab by filling in form data and setting the scroll
- * position. The restore is complete when this function exits. It should be
- * called when the "load" event fires for the restoring tab.
- */
- restoreDocument: function () {
- if (!this._restoringDocument) {
- return;
- }
- let {entry, pageStyle, formdata, scrollPositions} = this._restoringDocument;
- this._restoringDocument = null;
-
- let window = this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindow);
-
- PageStyle.restoreTree(this.docShell, pageStyle);
- FormData.restoreTree(window, formdata);
- ScrollPosition.restoreTree(window, scrollPositions);
- },
-
- /**
- * Cancel an ongoing restore. This function can be called any time between
- * restoreHistory and restoreDocument.
- *
- * This function is called externally (if a restore is canceled) and
- * internally (when the loads for a restore have finished). In the latter
- * case, it's called before restoreDocument, so it cannot clear
- * _restoringDocument.
- */
- resetRestore: function () {
- this._tabData = null;
-
- if (this._historyListener) {
- this._historyListener.uninstall();
- }
- this._historyListener = null;
-
- if (this._progressListener) {
- this._progressListener.uninstall();
- }
- this._progressListener = null;
- }
-};
-
-/*
- * 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.
- */
-function HistoryListener(docShell, callback) {
- let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
- webNavigation.sessionHistory.addSHistoryListener(this);
-
- this.webNavigation = webNavigation;
- this.callback = callback;
-}
-HistoryListener.prototype = {
- QueryInterface: XPCOMUtils.generateQI([
- Ci.nsISHistoryListener,
- Ci.nsISupportsWeakReference
- ]),
-
- uninstall: function () {
- let shistory = this.webNavigation.sessionHistory;
- if (shistory) {
- shistory.removeSHistoryListener(this);
- }
- },
-
- OnHistoryGoBack: function(backURI) { return true; },
- OnHistoryGoForward: function(forwardURI) { return true; },
- OnHistoryGotoIndex: function(index, gotoURI) { return true; },
- OnHistoryPurge: function(numEntries) { return true; },
- OnHistoryReplaceEntry: function(index) {},
-
- // This will be called for a pending tab when loadURI(uri) is called where
- // the given |uri| only differs in the fragment.
- OnHistoryNewEntry(newURI) {
- let currentURI = this.webNavigation.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.spec == newURI.spec)) {
- return;
- }
-
- // Reset the tab's URL to what it's actually showing. Without this loadURI()
- // would use the current document and change the displayed URL only.
- this.webNavigation.setCurrentURI(Utils.makeURI("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 flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
- this.webNavigation.loadURI(newURI.spec, flags, null, null, null);
- },
-
- OnHistoryReload(reloadURI, reloadFlags) {
- this.callback();
-
- // Cancel the load.
- return false;
- },
-}
-
-/**
- * This class informs SessionStore.jsm whenever the network requests for a
- * restoring page have completely finished. We only restore three tabs
- * simultaneously, so this is the signal for SessionStore.jsm to kick off
- * another restore (if there are more to do).
- *
- * The progress listener is also used to be notified when a load not initiated
- * by sessionstore starts. Pending tabs will then need to be marked as no
- * longer pending.
- */
-function ProgressListener(docShell, callbacks) {
- let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebProgress);
- webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
-
- this.webProgress = webProgress;
- this.callbacks = callbacks;
-}
-
-ProgressListener.prototype = {
- QueryInterface: XPCOMUtils.generateQI([
- Ci.nsIWebProgressListener,
- Ci.nsISupportsWeakReference
- ]),
-
- uninstall: function() {
- this.webProgress.removeProgressListener(this);
- },
-
- onStateChange: function(webProgress, request, stateFlags, status) {
- let {STATE_IS_WINDOW, STATE_STOP, STATE_START} = Ci.nsIWebProgressListener;
- if (!webProgress.isTopLevel || !(stateFlags & STATE_IS_WINDOW)) {
- return;
- }
-
- if (stateFlags & STATE_START && this.callbacks.onStartRequest) {
- this.callbacks.onStartRequest();
- }
-
- if (stateFlags & STATE_STOP && this.callbacks.onStopRequest) {
- this.callbacks.onStopRequest();
- }
- },
-
- onLocationChange: function() {},
- onProgressChange: function() {},
- onStatusChange: function() {},
- onSecurityChange: function() {},
-};
diff --git a/browser/components/sessionstore/DocShellCapabilities.jsm b/browser/components/sessionstore/DocShellCapabilities.jsm
deleted file mode 100644
index 098aae86f..000000000
--- a/browser/components/sessionstore/DocShellCapabilities.jsm
+++ /dev/null
@@ -1,50 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["DocShellCapabilities"];
-
-/**
- * The external API exported by this module.
- */
-this.DocShellCapabilities = Object.freeze({
- collect: function (docShell) {
- return DocShellCapabilitiesInternal.collect(docShell);
- },
-
- restore: function (docShell, disallow) {
- return DocShellCapabilitiesInternal.restore(docShell, disallow);
- },
-});
-
-/**
- * Internal functionality to save and restore the docShell.allow* properties.
- */
-var DocShellCapabilitiesInternal = {
- // List of docShell capabilities to (re)store. These are automatically
- // retrieved from a given docShell if not already collected before.
- // This is made so they're automatically in sync with all nsIDocShell.allow*
- // properties.
- caps: null,
-
- allCapabilities: function (docShell) {
- if (!this.caps) {
- let keys = Object.keys(docShell);
- this.caps = keys.filter(k => k.startsWith("allow")).map(k => k.slice(5));
- }
- return this.caps;
- },
-
- collect: function (docShell) {
- let caps = this.allCapabilities(docShell);
- return caps.filter(cap => !docShell["allow" + cap]);
- },
-
- restore: function (docShell, disallow) {
- let caps = this.allCapabilities(docShell);
- for (let cap of caps)
- docShell["allow" + cap] = !disallow.has(cap);
- },
-};
diff --git a/browser/components/sessionstore/FrameTree.jsm b/browser/components/sessionstore/FrameTree.jsm
deleted file mode 100644
index e8ed12a8f..000000000
--- a/browser/components/sessionstore/FrameTree.jsm
+++ /dev/null
@@ -1,254 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["FrameTree"];
-
-const Cu = Components.utils;
-const Ci = Components.interfaces;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
-
-const EXPORTED_METHODS = ["addObserver", "contains", "map", "forEach"];
-
-/**
- * A FrameTree represents all frames that were reachable when the document
- * was loaded. We use this information to ignore frames when collecting
- * sessionstore data as we can't currently restore anything for frames that
- * have been created dynamically after or at the load event.
- *
- * @constructor
- */
-function FrameTree(chromeGlobal) {
- let internal = new FrameTreeInternal(chromeGlobal);
- let external = {};
-
- for (let method of EXPORTED_METHODS) {
- external[method] = internal[method].bind(internal);
- }
-
- return Object.freeze(external);
-}
-
-/**
- * The internal frame tree API that the public one points to.
- *
- * @constructor
- */
-function FrameTreeInternal(chromeGlobal) {
- // A WeakMap that uses frames (DOMWindows) as keys and their initial indices
- // in their parents' child lists as values. Suppose we have a root frame with
- // three subframes i.e. a page with three iframes. The WeakMap would have
- // four entries and look as follows:
- //
- // root -> 0
- // subframe1 -> 0
- // subframe2 -> 1
- // subframe3 -> 2
- //
- // Should one of the subframes disappear we will stop collecting data for it
- // as |this._frames.has(frame) == false|. All other subframes will maintain
- // their initial indices to ensure we can restore frame data appropriately.
- this._frames = new WeakMap();
-
- // The Set of observers that will be notified when the frame changes.
- this._observers = new Set();
-
- // The chrome global we use to retrieve the current DOMWindow.
- this._chromeGlobal = chromeGlobal;
-
- // Register a web progress listener to be notified about new page loads.
- let docShell = chromeGlobal.docShell;
- let ifreq = docShell.QueryInterface(Ci.nsIInterfaceRequestor);
- let webProgress = ifreq.getInterface(Ci.nsIWebProgress);
- webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
-}
-
-FrameTreeInternal.prototype = {
-
- // Returns the docShell's current global.
- get content() {
- return this._chromeGlobal.content;
- },
-
- /**
- * Adds a given observer |obs| to the set of observers that will be notified
- * when the frame tree is reset (when a new document starts loading) or
- * recollected (when a document finishes loading).
- *
- * @param obs (object)
- */
- addObserver: function (obs) {
- this._observers.add(obs);
- },
-
- /**
- * Notifies all observers that implement the given |method|.
- *
- * @param method (string)
- */
- notifyObservers: function (method) {
- for (let obs of this._observers) {
- if (obs.hasOwnProperty(method)) {
- obs[method]();
- }
- }
- },
-
- /**
- * Checks whether a given |frame| is contained in the collected frame tree.
- * If it is not, this indicates that we should not collect data for it.
- *
- * @param frame (nsIDOMWindow)
- * @return bool
- */
- contains: function (frame) {
- return this._frames.has(frame);
- },
-
- /**
- * Recursively applies the given function |cb| to the stored frame tree. Use
- * this method to collect sessionstore data for all reachable frames stored
- * in the frame tree.
- *
- * If a given function |cb| returns a value, it must be an object. It may
- * however return "null" to indicate that there is no data to be stored for
- * the given frame.
- *
- * The object returned by |cb| cannot have any property named "children" as
- * that is used to store information about subframes in the tree returned
- * by |map()| and might be overridden.
- *
- * @param cb (function)
- * @return object
- */
- map: function (cb) {
- let frames = this._frames;
-
- function walk(frame) {
- let obj = cb(frame) || {};
-
- if (frames.has(frame)) {
- let children = [];
-
- Array.forEach(frame.frames, subframe => {
- // Don't collect any data if the frame is not contained in the
- // initial frame tree. It's a dynamic frame added later.
- if (!frames.has(subframe)) {
- return;
- }
-
- // Retrieve the frame's original position in its parent's child list.
- let index = frames.get(subframe);
-
- // Recursively collect data for the current subframe.
- let result = walk(subframe, cb);
- if (result && Object.keys(result).length) {
- children[index] = result;
- }
- });
-
- if (children.length) {
- obj.children = children;
- }
- }
-
- return Object.keys(obj).length ? obj : null;
- }
-
- return walk(this.content);
- },
-
- /**
- * Applies the given function |cb| to all frames stored in the tree. Use this
- * method if |map()| doesn't suit your needs and you want more control over
- * how data is collected.
- *
- * @param cb (function)
- * This callback receives the current frame as the only argument.
- */
- forEach: function (cb) {
- let frames = this._frames;
-
- function walk(frame) {
- cb(frame);
-
- if (!frames.has(frame)) {
- return;
- }
-
- Array.forEach(frame.frames, subframe => {
- if (frames.has(subframe)) {
- cb(subframe);
- }
- });
- }
-
- walk(this.content);
- },
-
- /**
- * Stores a given |frame| and its children in the frame tree.
- *
- * @param frame (nsIDOMWindow)
- * @param index (int)
- * The index in the given frame's parent's child list.
- */
- collect: function (frame, index = 0) {
- // Mark the given frame as contained in the frame tree.
- this._frames.set(frame, index);
-
- // Mark the given frame's subframes as contained in the tree.
- Array.forEach(frame.frames, this.collect, this);
- },
-
- /**
- * @see nsIWebProgressListener.onStateChange
- *
- * We want to be notified about:
- * - new documents that start loading to clear the current frame tree;
- * - completed document loads to recollect reachable frames.
- */
- onStateChange: function (webProgress, request, stateFlags, status) {
- // Ignore state changes for subframes because we're only interested in the
- // top-document starting or stopping its load. We thus only care about any
- // changes to the root of the frame tree, not to any of its nodes/leafs.
- if (!webProgress.isTopLevel || webProgress.DOMWindow != this.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._chromeGlobal.docShell.hasLoadedNonBlankURI) {
- return;
- }
-
- if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
- // Clear the list of frames until we can recollect it.
- this._frames = new WeakMap();
-
- // Notify observers that the frame tree has been reset.
- this.notifyObservers("onFrameTreeReset");
- } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
- // The document and its resources have finished loading.
- this.collect(webProgress.DOMWindow);
-
- // Notify observers that the frame tree has been reset.
- this.notifyObservers("onFrameTreeCollected");
- }
- },
-
- // Unused nsIWebProgressListener methods.
- onLocationChange: function () {},
- onProgressChange: function () {},
- onSecurityChange: function () {},
- onStatusChange: function () {},
-
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
- Ci.nsISupportsWeakReference])
-};
diff --git a/browser/components/sessionstore/GlobalState.jsm b/browser/components/sessionstore/GlobalState.jsm
deleted file mode 100644
index ac2d7c81b..000000000
--- a/browser/components/sessionstore/GlobalState.jsm
+++ /dev/null
@@ -1,84 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["GlobalState"];
-
-const EXPORTED_METHODS = ["getState", "clear", "get", "set", "delete", "setFromState"];
-/**
- * Module that contains global session data.
- */
-function GlobalState() {
- let internal = new GlobalStateInternal();
- let external = {};
- for (let method of EXPORTED_METHODS) {
- external[method] = internal[method].bind(internal);
- }
- return Object.freeze(external);
-}
-
-function GlobalStateInternal() {
- // Storage for global state.
- this.state = {};
-}
-
-GlobalStateInternal.prototype = {
- /**
- * Get all value from the global state.
- */
- getState: function() {
- return this.state;
- },
-
- /**
- * Clear all currently stored global state.
- */
- clear: function() {
- this.state = {};
- },
-
- /**
- * Retrieve a value from the global state.
- *
- * @param aKey
- * A key the value is stored under.
- * @return The value stored at aKey, or an empty string if no value is set.
- */
- get: function(aKey) {
- return this.state[aKey] || "";
- },
-
- /**
- * Set a global value.
- *
- * @param aKey
- * A key to store the value under.
- */
- set: function(aKey, aStringValue) {
- this.state[aKey] = aStringValue;
- },
-
- /**
- * Delete a global value.
- *
- * @param aKey
- * A key to delete the value for.
- */
- delete: function(aKey) {
- delete this.state[aKey];
- },
-
- /**
- * Set the current global state from a state object. Any previous global
- * state will be removed, even if the new state does not contain a matching
- * key.
- *
- * @param aState
- * A state object to extract global state from to be set.
- */
- setFromState: function (aState) {
- this.state = (aState && aState.global) || {};
- }
-};
diff --git a/browser/components/sessionstore/PageStyle.jsm b/browser/components/sessionstore/PageStyle.jsm
deleted file mode 100644
index 0424ef6b1..000000000
--- a/browser/components/sessionstore/PageStyle.jsm
+++ /dev/null
@@ -1,100 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["PageStyle"];
-
-const Ci = Components.interfaces;
-
-/**
- * The external API exported by this module.
- */
-this.PageStyle = Object.freeze({
- collect: function (docShell, frameTree) {
- return PageStyleInternal.collect(docShell, frameTree);
- },
-
- restoreTree: function (docShell, data) {
- PageStyleInternal.restoreTree(docShell, data);
- }
-});
-
-// Signifies that author style level is disabled for the page.
-const NO_STYLE = "_nostyle";
-
-var PageStyleInternal = {
- /**
- * Collects the selected style sheet sets for all reachable frames.
- */
- collect: function (docShell, frameTree) {
- let result = frameTree.map(({document: doc}) => {
- let style;
-
- if (doc) {
- // http://dev.w3.org/csswg/cssom/#persisting-the-selected-css-style-sheet-set
- style = doc.selectedStyleSheetSet || doc.lastStyleSheetSet;
- }
-
- return style ? {pageStyle: style} : null;
- });
-
- let markupDocumentViewer =
- docShell.contentViewer;
-
- if (markupDocumentViewer.authorStyleDisabled) {
- result = result || {};
- result.disabled = true;
- }
-
- return result && Object.keys(result).length ? result : null;
- },
-
- /**
- * Restores pageStyle data for the current frame hierarchy starting at the
- * |docShell's| current DOMWindow using the given pageStyle |data|.
- *
- * Warning: If the current frame hierarchy doesn't match that of the given
- * |data| object we will silently discard data for unreachable frames. We may
- * as well assign page styles to the wrong frames if some were reordered or
- * removed.
- *
- * @param docShell (nsIDocShell)
- * @param data (object)
- * {
- * disabled: true, // when true, author styles will be disabled
- * pageStyle: "Dusk",
- * children: [
- * null,
- * {pageStyle: "Mozilla", children: [ ... ]}
- * ]
- * }
- */
- restoreTree: function (docShell, data) {
- let disabled = data.disabled || false;
- let markupDocumentViewer =
- docShell.contentViewer;
- markupDocumentViewer.authorStyleDisabled = disabled;
-
- function restoreFrame(root, data) {
- if (data.hasOwnProperty("pageStyle")) {
- root.document.selectedStyleSheetSet = data.pageStyle;
- }
-
- if (!data.hasOwnProperty("children")) {
- return;
- }
-
- let frames = root.frames;
- data.children.forEach((child, index) => {
- if (child && index < frames.length) {
- restoreFrame(frames[index], child);
- }
- });
- }
-
- let ifreq = docShell.QueryInterface(Ci.nsIInterfaceRequestor);
- restoreFrame(ifreq.getInterface(Ci.nsIDOMWindow), data);
- }
-};
diff --git a/browser/components/sessionstore/PrivacyFilter.jsm b/browser/components/sessionstore/PrivacyFilter.jsm
deleted file mode 100644
index 88713b402..000000000
--- a/browser/components/sessionstore/PrivacyFilter.jsm
+++ /dev/null
@@ -1,135 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["PrivacyFilter"];
-
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
-
-XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
- "resource:///modules/sessionstore/PrivacyLevel.jsm");
-
-/**
- * A module that provides methods to filter various kinds of data collected
- * from a tab by the current privacy level as set by the user.
- */
-this.PrivacyFilter = Object.freeze({
- /**
- * Filters the given (serialized) session storage |data| according to the
- * current privacy level and returns a new object containing only data that
- * we're allowed to store.
- *
- * @param data The session storage data as collected from a tab.
- * @return object
- */
- filterSessionStorageData: function (data) {
- let retval = {};
-
- for (let host of Object.keys(data)) {
- if (PrivacyLevel.check(host)) {
- retval[host] = data[host];
- }
- }
-
- return Object.keys(retval).length ? retval : null;
- },
-
- /**
- * Filters the given (serialized) form |data| according to the current
- * privacy level and returns a new object containing only data that we're
- * allowed to store.
- *
- * @param data The form data as collected from a tab.
- * @return object
- */
- filterFormData: function (data) {
- // If the given form data object has an associated URL that we are not
- // allowed to store data for, bail out. We explicitly discard data for any
- // children as well even if storing data for those frames would be allowed.
- if (data.url && !PrivacyLevel.check(data.url)) {
- return;
- }
-
- let retval = {};
-
- for (let key of Object.keys(data)) {
- if (key === "children") {
- let recurse = child => this.filterFormData(child);
- let children = data.children.map(recurse).filter(child => child);
-
- if (children.length) {
- retval.children = children;
- }
- // Only copy keys other than "children" if we have a valid URL in
- // data.url and we thus passed the privacy level check.
- } else if (data.url) {
- retval[key] = data[key];
- }
- }
-
- return Object.keys(retval).length ? retval : null;
- },
-
- /**
- * Removes any private windows and tabs from a given browser state object.
- *
- * @param browserState (object)
- * The browser state for which we remove any private windows and tabs.
- * The given object will be modified.
- */
- filterPrivateWindowsAndTabs: function (browserState) {
- // Remove private opened windows.
- for (let i = browserState.windows.length - 1; i >= 0; i--) {
- let win = browserState.windows[i];
-
- if (win.isPrivate) {
- browserState.windows.splice(i, 1);
-
- if (browserState.selectedWindow >= i) {
- browserState.selectedWindow--;
- }
- } else {
- // Remove private tabs from all open non-private windows.
- this.filterPrivateTabs(win);
- }
- }
-
- // Remove private closed windows.
- browserState._closedWindows =
- browserState._closedWindows.filter(win => !win.isPrivate);
-
- // Remove private tabs from all remaining closed windows.
- browserState._closedWindows.forEach(win => this.filterPrivateTabs(win));
- },
-
- /**
- * Removes open private tabs from a given window state object.
- *
- * @param winState (object)
- * The window state for which we remove any private tabs.
- * The given object will be modified.
- */
- filterPrivateTabs: function (winState) {
- // Remove open private tabs.
- for (let i = winState.tabs.length - 1; i >= 0 ; i--) {
- let tab = winState.tabs[i];
-
- if (tab.isPrivate) {
- winState.tabs.splice(i, 1);
-
- if (winState.selected >= i) {
- winState.selected--;
- }
- }
- }
-
- // Note that closed private tabs are only stored for private windows.
- // There is no need to call this function for private windows as the
- // whole window state should just be discarded so we explicitly don't
- // try to remove closed private tabs as an optimization.
- }
-});
diff --git a/browser/components/sessionstore/PrivacyLevel.jsm b/browser/components/sessionstore/PrivacyLevel.jsm
deleted file mode 100644
index 135f1f959..000000000
--- a/browser/components/sessionstore/PrivacyLevel.jsm
+++ /dev/null
@@ -1,64 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["PrivacyLevel"];
-
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/Services.jsm");
-
-const PREF = "browser.sessionstore.privacy_level";
-
-// The following constants represent the different possible privacy levels that
-// can be set by the user and that we need to consider when collecting text
-// data, and cookies.
-//
-// Collect data from all sites (http and https).
-const PRIVACY_NONE = 0;
-// Collect data from unencrypted sites (http), only.
-const PRIVACY_ENCRYPTED = 1;
-// Collect no data.
-const PRIVACY_FULL = 2;
-
-/**
- * The external API as exposed by this module.
- */
-var PrivacyLevel = Object.freeze({
- /**
- * Returns whether the current privacy level allows saving data for the given
- * |url|.
- *
- * @param url The URL we want to save data for.
- * @return bool
- */
- check: function (url) {
- return PrivacyLevel.canSave({ isHttps: url.startsWith("https:") });
- },
-
- /**
- * Checks whether we're allowed to save data for a specific site.
- *
- * @param {isHttps: boolean}
- * An object that must have one property: 'isHttps'.
- * 'isHttps' tells whether the site us secure communication (HTTPS).
- * @return {bool} Whether we can save data for the specified site.
- */
- canSave: function ({isHttps}) {
- let level = Services.prefs.getIntPref(PREF);
-
- // Never save any data when full privacy is requested.
- if (level == PRIVACY_FULL) {
- return false;
- }
-
- // Don't save data for encrypted sites when requested.
- if (isHttps && level == PRIVACY_ENCRYPTED) {
- return false;
- }
-
- return true;
- }
-});
diff --git a/browser/components/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm b/browser/components/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm
deleted file mode 100644
index ac5731160..000000000
--- a/browser/components/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm
+++ /dev/null
@@ -1,214 +0,0 @@
-/* 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/. */
-
-this.EXPORTED_SYMBOLS = ["RecentlyClosedTabsAndWindowsMenuUtils"];
-
-const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-
-var Ci = Components.interfaces;
-var Cc = Components.classes;
-var Cr = Components.results;
-var Cu = Components.utils;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/PlacesUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
- "resource://gre/modules/PluralForm.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
- "resource:///modules/sessionstore/SessionStore.jsm");
-
-var navigatorBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
-
-this.RecentlyClosedTabsAndWindowsMenuUtils = {
-
- /**
- * Builds up a document fragment of UI items for the recently closed tabs.
- * @param aWindow
- * The window that the tabs were closed in.
- * @param aTagName
- * The tag name that will be used when creating the UI items.
- * @param aPrefixRestoreAll (defaults to false)
- * Whether the 'restore all tabs' item is suffixed or prefixed to the list.
- * If suffixed (the default) a separator will be inserted before it.
- * @param aRestoreAllLabel (defaults to "menuRestoreAllTabs.label")
- * Which localizable string to use for the 'restore all tabs' item.
- * @returns A document fragment with UI items for each recently closed tab.
- */
- getTabsFragment: function(aWindow, aTagName, aPrefixRestoreAll=false,
- aRestoreAllLabel="menuRestoreAllTabs.label") {
- let doc = aWindow.document;
- let fragment = doc.createDocumentFragment();
- if (SessionStore.getClosedTabCount(aWindow) != 0) {
- let closedTabs = SessionStore.getClosedTabData(aWindow, false);
- for (let i = 0; i < closedTabs.length; i++) {
- createEntry(aTagName, false, i, closedTabs[i], doc,
- closedTabs[i].title, fragment);
- }
-
- createRestoreAllEntry(doc, fragment, aPrefixRestoreAll, false,
- aRestoreAllLabel, closedTabs.length, aTagName)
- }
- return fragment;
- },
-
- /**
- * Builds up a document fragment of UI items for the recently closed windows.
- * @param aWindow
- * A window that can be used to create the elements and document fragment.
- * @param aTagName
- * The tag name that will be used when creating the UI items.
- * @param aPrefixRestoreAll (defaults to false)
- * Whether the 'restore all windows' item is suffixed or prefixed to the list.
- * If suffixed (the default) a separator will be inserted before it.
- * @param aRestoreAllLabel (defaults to "menuRestoreAllWindows.label")
- * Which localizable string to use for the 'restore all windows' item.
- * @returns A document fragment with UI items for each recently closed window.
- */
- getWindowsFragment: function(aWindow, aTagName, aPrefixRestoreAll=false,
- aRestoreAllLabel="menuRestoreAllWindows.label") {
- let closedWindowData = SessionStore.getClosedWindowData(false);
- let doc = aWindow.document;
- let fragment = doc.createDocumentFragment();
- if (closedWindowData.length != 0) {
- let menuLabelString = navigatorBundle.GetStringFromName("menuUndoCloseWindowLabel");
- let menuLabelStringSingleTab =
- navigatorBundle.GetStringFromName("menuUndoCloseWindowSingleTabLabel");
-
- for (let i = 0; i < closedWindowData.length; i++) {
- let undoItem = closedWindowData[i];
- let otherTabsCount = undoItem.tabs.length - 1;
- let label = (otherTabsCount == 0) ? menuLabelStringSingleTab
- : PluralForm.get(otherTabsCount, menuLabelString);
- let menuLabel = label.replace("#1", undoItem.title)
- .replace("#2", otherTabsCount);
- let selectedTab = undoItem.tabs[undoItem.selected - 1];
-
- createEntry(aTagName, true, i, selectedTab, doc, menuLabel,
- fragment);
- }
-
- createRestoreAllEntry(doc, fragment, aPrefixRestoreAll, true,
- aRestoreAllLabel, closedWindowData.length,
- aTagName);
- }
- return fragment;
- },
-
-
- /**
- * Re-open a closed tab and put it to the end of the tab strip.
- * Used for a middle click.
- * @param aEvent
- * The event when the user clicks the menu item
- */
- _undoCloseMiddleClick: function(aEvent) {
- if (aEvent.button != 1)
- return;
-
- aEvent.view.undoCloseTab(aEvent.originalTarget.getAttribute("value"));
- aEvent.view.gBrowser.moveTabToEnd();
- },
-};
-
-function setImage(aItem, aElement) {
- let iconURL = aItem.image;
- // don't initiate a connection just to fetch a favicon (see bug 467828)
- if (/^https?:/.test(iconURL))
- iconURL = "moz-anno:favicon:" + iconURL;
-
- aElement.setAttribute("image", iconURL);
-}
-
-/**
- * Create a UI entry for a recently closed tab or window.
- * @param aTagName
- * the tag name that will be used when creating the UI entry
- * @param aIsWindowsFragment
- * whether or not this entry will represent a closed window
- * @param aIndex
- * the index of the closed tab
- * @param aClosedTab
- * the closed tab
- * @param aDocument
- * a document that can be used to create the entry
- * @param aMenuLabel
- * the label the created entry will have
- * @param aFragment
- * the fragment the created entry will be in
- */
-function createEntry(aTagName, aIsWindowsFragment, aIndex, aClosedTab,
- aDocument, aMenuLabel, aFragment) {
- let element = aDocument.createElementNS(kNSXUL, aTagName);
-
- element.setAttribute("label", aMenuLabel);
- if (aClosedTab.image) {
- setImage(aClosedTab, element);
- }
- if (!aIsWindowsFragment) {
- element.setAttribute("value", aIndex);
- }
-
- if (aTagName == "menuitem") {
- element.setAttribute("class", "menuitem-iconic bookmark-item menuitem-with-favicon");
- }
-
- element.setAttribute("oncommand", "undoClose" + (aIsWindowsFragment ? "Window" : "Tab") +
- "(" + aIndex + ");");
-
- // Set the targetURI attribute so it will be shown in tooltip.
- // SessionStore uses one-based indexes, so we need to normalize them.
- let tabData;
- tabData = aIsWindowsFragment ? aClosedTab
- : aClosedTab.state;
- let activeIndex = (tabData.index || tabData.entries.length) - 1;
- if (activeIndex >= 0 && tabData.entries[activeIndex]) {
- element.setAttribute("targetURI", tabData.entries[activeIndex].url);
- }
-
- if (!aIsWindowsFragment) {
- element.addEventListener("click", RecentlyClosedTabsAndWindowsMenuUtils._undoCloseMiddleClick, false);
- }
- if (aIndex == 0) {
- element.setAttribute("key", "key_undoClose" + (aIsWindowsFragment? "Window" : "Tab"));
- }
-
- aFragment.appendChild(element);
-}
-
-/**
- * Create an entry to restore all closed windows or tabs.
- * @param aDocument
- * a document that can be used to create the entry
- * @param aFragment
- * the fragment the created entry will be in
- * @param aPrefixRestoreAll
- * whether the 'restore all windows' item is suffixed or prefixed to the list
- * If suffixed a separator will be inserted before it.
- * @param aIsWindowsFragment
- * whether or not this entry will represent a closed window
- * @param aRestoreAllLabel
- * which localizable string to use for the entry
- * @param aEntryCount
- * the number of elements to be restored by this entry
- * @param aTagName
- * the tag name that will be used when creating the UI entry
- */
-function createRestoreAllEntry(aDocument, aFragment, aPrefixRestoreAll,
- aIsWindowsFragment, aRestoreAllLabel,
- aEntryCount, aTagName) {
- let restoreAllElements = aDocument.createElementNS(kNSXUL, aTagName);
- restoreAllElements.classList.add("restoreallitem");
- restoreAllElements.setAttribute("label", navigatorBundle.GetStringFromName(aRestoreAllLabel));
- restoreAllElements.setAttribute("oncommand",
- "for (var i = 0; i < " + aEntryCount + "; i++) undoClose" +
- (aIsWindowsFragment? "Window" : "Tab") + "();");
- if (aPrefixRestoreAll) {
- aFragment.insertBefore(restoreAllElements, aFragment.firstChild);
- } else {
- aFragment.appendChild(aDocument.createElementNS(kNSXUL, "menuseparator"));
- aFragment.appendChild(restoreAllElements);
- }
-} \ No newline at end of file
diff --git a/browser/components/sessionstore/RunState.jsm b/browser/components/sessionstore/RunState.jsm
deleted file mode 100644
index 3cdf47718..000000000
--- a/browser/components/sessionstore/RunState.jsm
+++ /dev/null
@@ -1,96 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["RunState"];
-
-const STATE_STOPPED = 0;
-const STATE_RUNNING = 1;
-const STATE_QUITTING = 2;
-const STATE_CLOSING = 3;
-const STATE_CLOSED = 4;
-
-// We're initially stopped.
-var state = STATE_STOPPED;
-
-/**
- * This module keeps track of SessionStore's current run state. We will
- * always start out at STATE_STOPPED. After the session was read from disk and
- * the initial browser window has loaded we switch to STATE_RUNNING. On the
- * first notice that a browser shutdown was granted we switch to STATE_QUITTING.
- */
-this.RunState = Object.freeze({
- // If we're stopped then SessionStore hasn't been initialized yet. As soon
- // as the session is read from disk and the initial browser window has loaded
- // the run state will change to STATE_RUNNING.
- get isStopped() {
- return state == STATE_STOPPED;
- },
-
- // STATE_RUNNING is our default mode of operation that we'll spend most of
- // the time in. After the session was read from disk and the first browser
- // window has loaded we remain running until the browser quits.
- get isRunning() {
- return state == STATE_RUNNING;
- },
-
- // We will enter STATE_QUITTING as soon as we receive notice that a browser
- // shutdown was granted. SessionStore will use this information to prevent
- // us from collecting partial information while the browser is shutting down
- // as well as to allow a last single write to disk and block all writes after
- // that.
- get isQuitting() {
- return state >= STATE_QUITTING;
- },
-
- // We will enter STATE_CLOSING as soon as SessionStore is uninitialized.
- // The SessionFile module will know that a last write will happen in this
- // state and it can do some necessary cleanup.
- get isClosing() {
- return state == STATE_CLOSING;
- },
-
- // We will enter STATE_CLOSED as soon as SessionFile has written to disk for
- // the last time before shutdown and will not accept any further writes.
- get isClosed() {
- return state == STATE_CLOSED;
- },
-
- // Switch the run state to STATE_RUNNING. This must be called after the
- // session was read from, the initial browser window has loaded and we're
- // now ready to restore session data.
- setRunning() {
- if (this.isStopped) {
- state = STATE_RUNNING;
- }
- },
-
- // Switch the run state to STATE_CLOSING. This must be called *before* the
- // last SessionFile.write() call so that SessionFile knows we're closing and
- // can do some last cleanups and write a proper sessionstore.js file.
- setClosing() {
- if (this.isQuitting) {
- state = STATE_CLOSING;
- }
- },
-
- // Switch the run state to STATE_CLOSED. This must be called by SessionFile
- // after the last write to disk was accepted and no further writes will be
- // allowed. Any writes after this stage will cause exceptions.
- setClosed() {
- if (this.isClosing) {
- state = STATE_CLOSED;
- }
- },
-
- // Switch the run state to STATE_QUITTING. This should be called once we're
- // certain that the browser is going away and before we start collecting the
- // final window states to save in the session file.
- setQuitting() {
- if (this.isRunning) {
- state = STATE_QUITTING;
- }
- },
-});
diff --git a/browser/components/sessionstore/SessionCookies.jsm b/browser/components/sessionstore/SessionCookies.jsm
deleted file mode 100644
index b99ab927b..000000000
--- a/browser/components/sessionstore/SessionCookies.jsm
+++ /dev/null
@@ -1,476 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["SessionCookies"];
-
-const Cu = Components.utils;
-const Ci = Components.interfaces;
-
-Cu.import("resource://gre/modules/Services.jsm", this);
-Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
-
-XPCOMUtils.defineLazyModuleGetter(this, "Utils",
- "resource://gre/modules/sessionstore/Utils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
- "resource:///modules/sessionstore/PrivacyLevel.jsm");
-
-// MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision.
-const MAX_EXPIRY = Math.pow(2, 62);
-
-/**
- * The external API implemented by the SessionCookies module.
- */
-this.SessionCookies = Object.freeze({
- update: function (windows) {
- SessionCookiesInternal.update(windows);
- },
-
- getHostsForWindow: function (window, checkPrivacy = false) {
- return SessionCookiesInternal.getHostsForWindow(window, checkPrivacy);
- },
-
- restore(cookies) {
- SessionCookiesInternal.restore(cookies);
- }
-});
-
-/**
- * The internal API.
- */
-var SessionCookiesInternal = {
- /**
- * Stores whether we're initialized, yet.
- */
- _initialized: false,
-
- /**
- * Retrieve the list of all hosts contained in the given windows' session
- * history entries (per window) and collect the associated cookies for those
- * hosts, if any. The given state object is being modified.
- *
- * @param windows
- * Array of window state objects.
- * [{ tabs: [...], cookies: [...] }, ...]
- */
- update: function (windows) {
- this._ensureInitialized();
-
- for (let window of windows) {
- let cookies = [];
-
- // Collect all hosts for the current window.
- let hosts = this.getHostsForWindow(window, true);
-
- for (let host of Object.keys(hosts)) {
- let isPinned = hosts[host];
-
- for (let cookie of CookieStore.getCookiesForHost(host)) {
- // _getCookiesForHost() will only return hosts with the right privacy
- // rules, so there is no need to do anything special with this call
- // to PrivacyLevel.canSave().
- if (PrivacyLevel.canSave({isHttps: cookie.secure, isPinned: isPinned})) {
- cookies.push(cookie);
- }
- }
- }
-
- // Don't include/keep empty cookie sections.
- if (cookies.length) {
- window.cookies = cookies;
- } else if ("cookies" in window) {
- delete window.cookies;
- }
- }
- },
-
- /**
- * Returns a map of all hosts for a given window that we might want to
- * collect cookies for.
- *
- * @param window
- * A window state object containing tabs with history entries.
- * @param checkPrivacy (bool)
- * Whether to check the privacy level for each host.
- * @return {object} A map of hosts for a given window state object. The keys
- * will be hosts, the values are boolean and determine
- * whether we will use the deferred privacy level when
- * checking how much data to save on quitting.
- */
- getHostsForWindow: function (window, checkPrivacy = false) {
- let hosts = {};
-
- for (let tab of window.tabs) {
- for (let entry of tab.entries) {
- this._extractHostsFromEntry(entry, hosts, checkPrivacy, tab.pinned);
- }
- }
-
- return hosts;
- },
-
- /**
- * Restores a given list of session cookies.
- */
- restore(cookies) {
-
- for (let cookie of cookies) {
- let expiry = "expiry" in cookie ? cookie.expiry : MAX_EXPIRY;
- let cookieObj = {
- host: cookie.host,
- path: cookie.path || "",
- name: cookie.name || ""
- };
- if (!Services.cookies.cookieExists(cookieObj, cookie.originAttributes || {})) {
- Services.cookies.add(cookie.host, cookie.path || "", cookie.name || "",
- cookie.value, !!cookie.secure, !!cookie.httponly,
- /* isSession = */ true, expiry, cookie.originAttributes || {});
- }
- }
- },
-
- /**
- * Handles observers notifications that are sent whenever cookies are added,
- * changed, or removed. Ensures that the storage is updated accordingly.
- */
- observe: function (subject, topic, data) {
- switch (data) {
- case "added":
- case "changed":
- this._updateCookie(subject);
- break;
- case "deleted":
- this._removeCookie(subject);
- break;
- case "cleared":
- CookieStore.clear();
- break;
- case "batch-deleted":
- this._removeCookies(subject);
- break;
- case "reload":
- CookieStore.clear();
- this._reloadCookies();
- break;
- default:
- throw new Error("Unhandled cookie-changed notification.");
- }
- },
-
- /**
- * If called for the first time in a session, iterates all cookies in the
- * cookies service and puts them into the store if they're session cookies.
- */
- _ensureInitialized: function () {
- if (!this._initialized) {
- this._reloadCookies();
- this._initialized = true;
- Services.obs.addObserver(this, "cookie-changed", false);
- }
- },
-
- /**
- * Fill a given map with hosts found in the given entry's session history and
- * any child entries.
- *
- * @param entry
- * the history entry, serialized
- * @param hosts
- * the hash that will be used to store hosts eg, { hostname: true }
- * @param checkPrivacy
- * should we check the privacy level for https
- * @param isPinned
- * is the entry we're evaluating for a pinned tab; used only if
- * checkPrivacy
- */
- _extractHostsFromEntry: function (entry, hosts, checkPrivacy, isPinned) {
- let host = entry._host;
- let scheme = entry._scheme;
-
- // If host & scheme aren't defined, then we are likely here in the startup
- // process via _splitCookiesFromWindow. In that case, we'll turn entry.url
- // into an nsIURI and get host/scheme from that. This will throw for about:
- // urls in which case we don't need to do anything.
- if (!host && !scheme) {
- try {
- let uri = Utils.makeURI(entry.url);
- host = uri.host;
- scheme = uri.scheme;
- this._extractHostsFromHostScheme(host, scheme, hosts, checkPrivacy, isPinned);
- }
- catch (ex) { }
- }
-
- if (entry.children) {
- for (let child of entry.children) {
- this._extractHostsFromEntry(child, hosts, checkPrivacy, isPinned);
- }
- }
- },
-
- /**
- * Add a given host to a given map of hosts if the privacy level allows
- * saving cookie data for it.
- *
- * @param host
- * the host of a uri (usually via nsIURI.host)
- * @param scheme
- * the scheme of a uri (usually via nsIURI.scheme)
- * @param hosts
- * the hash that will be used to store hosts eg, { hostname: true }
- * @param checkPrivacy
- * should we check the privacy level for https
- * @param isPinned
- * is the entry we're evaluating for a pinned tab; used only if
- * checkPrivacy
- */
- _extractHostsFromHostScheme:
- function (host, scheme, hosts, checkPrivacy, isPinned) {
- // host and scheme may not be set (for about: urls for example), in which
- // case testing scheme will be sufficient.
- if (/https?/.test(scheme) && !hosts[host] &&
- (!checkPrivacy ||
- PrivacyLevel.canSave({isHttps: scheme == "https", isPinned: isPinned}))) {
- // By setting this to true or false, we can determine when looking at
- // the host in update() if we should check for privacy.
- hosts[host] = isPinned;
- } else if (scheme == "file") {
- hosts[host] = true;
- }
- },
-
- /**
- * Updates or adds a given cookie to the store.
- */
- _updateCookie: function (cookie) {
- cookie.QueryInterface(Ci.nsICookie2);
-
- if (cookie.isSession) {
- CookieStore.set(cookie);
- } else {
- CookieStore.delete(cookie);
- }
- },
-
- /**
- * Removes a given cookie from the store.
- */
- _removeCookie: function (cookie) {
- cookie.QueryInterface(Ci.nsICookie2);
-
- if (cookie.isSession) {
- CookieStore.delete(cookie);
- }
- },
-
- /**
- * Removes a given list of cookies from the store.
- */
- _removeCookies: function (cookies) {
- for (let i = 0; i < cookies.length; i++) {
- this._removeCookie(cookies.queryElementAt(i, Ci.nsICookie2));
- }
- },
-
- /**
- * Iterates all cookies in the cookies service and puts them into the store
- * if they're session cookies.
- */
- _reloadCookies: function () {
- let iter = Services.cookies.enumerator;
- while (iter.hasMoreElements()) {
- this._updateCookie(iter.getNext());
- }
- }
-};
-
-/**
- * Generates all possible subdomains for a given host and prepends a leading
- * dot to all variants.
- *
- * See http://tools.ietf.org/html/rfc6265#section-5.1.3
- * http://en.wikipedia.org/wiki/HTTP_cookie#Domain_and_Path
- *
- * All cookies belonging to a web page will be internally represented by a
- * nsICookie object. nsICookie.host will be the request host if no domain
- * parameter was given when setting the cookie. If a specific domain was given
- * then nsICookie.host will contain that specific domain and prepend a leading
- * dot to it.
- *
- * We thus generate all possible subdomains for a given domain and prepend a
- * leading dot to them as that is the value that was used as the map key when
- * the cookie was set.
- */
-function* getPossibleSubdomainVariants(host) {
- // Try given domain with a leading dot (.www.example.com).
- yield "." + host;
-
- // Stop if there are only two parts left (e.g. example.com was given).
- let parts = host.split(".");
- if (parts.length < 3) {
- return;
- }
-
- // Remove the first subdomain (www.example.com -> example.com).
- let rest = parts.slice(1).join(".");
-
- // Try possible parent subdomains.
- yield* getPossibleSubdomainVariants(rest);
-}
-
-/**
- * The internal cookie storage that keeps track of every active session cookie.
- * These are stored using maps per host, path, and cookie name.
- */
-var CookieStore = {
- /**
- * The internal structure holding all known cookies.
- *
- * Host =>
- * Path =>
- * Name => {path: "/", name: "sessionid", secure: true}
- *
- * Maps are used for storage but the data structure is equivalent to this:
- *
- * this._hosts = {
- * "www.mozilla.org": {
- * "/": {
- * "username": {name: "username", value: "my_name_is", etc...},
- * "sessionid": {name: "sessionid", value: "1fdb3a", etc...}
- * }
- * },
- * "tbpl.mozilla.org": {
- * "/path": {
- * "cookiename": {name: "cookiename", value: "value", etc...}
- * }
- * },
- * ".example.com": {
- * "/path": {
- * "cookiename": {name: "cookiename", value: "value", etc...}
- * }
- * }
- * };
- */
- _hosts: new Map(),
-
- /**
- * Returns the list of stored session cookies for a given host.
- *
- * @param host
- * A string containing the host name we want to get cookies for.
- */
- getCookiesForHost: function (host) {
- let cookies = [];
-
- let appendCookiesForHost = host => {
- if (!this._hosts.has(host)) {
- return;
- }
-
- for (let pathToNamesMap of this._hosts.get(host).values()) {
- for (let nameToCookiesMap of pathToNamesMap.values()) {
- cookies.push(...nameToCookiesMap.values());
- }
- }
- }
-
- // Try to find cookies for the given host, e.g. <www.example.com>.
- // The full hostname will be in the map if the Set-Cookie header did not
- // have a domain= attribute, i.e. the cookie will only be stored for the
- // request domain. Also, try to find cookies for subdomains, e.g.
- // <.example.com>. We will find those variants with a leading dot in the
- // map if the Set-Cookie header had a domain= attribute, i.e. the cookie
- // will be stored for a parent domain and we send it for any subdomain.
- for (let variant of [host, ...getPossibleSubdomainVariants(host)]) {
- appendCookiesForHost(variant);
- }
-
- return cookies;
- },
-
- /**
- * Stores a given cookie.
- *
- * @param cookie
- * The nsICookie2 object to add to the storage.
- */
- set: function (cookie) {
- let jscookie = {host: cookie.host, value: cookie.value};
-
- // Only add properties with non-default values to save a few bytes.
- if (cookie.path) {
- jscookie.path = cookie.path;
- }
-
- if (cookie.name) {
- jscookie.name = cookie.name;
- }
-
- if (cookie.isSecure) {
- jscookie.secure = true;
- }
-
- if (cookie.isHttpOnly) {
- jscookie.httponly = true;
- }
-
- if (cookie.expiry < MAX_EXPIRY) {
- jscookie.expiry = cookie.expiry;
- }
-
- if (cookie.originAttributes) {
- jscookie.originAttributes = cookie.originAttributes;
- }
-
- this._ensureMap(cookie).set(cookie.name, jscookie);
- },
-
- /**
- * Removes a given cookie.
- *
- * @param cookie
- * The nsICookie2 object to be removed from storage.
- */
- delete: function (cookie) {
- this._ensureMap(cookie).delete(cookie.name);
- },
-
- /**
- * Removes all cookies.
- */
- clear: function () {
- this._hosts.clear();
- },
-
- /**
- * Creates all maps necessary to store a given cookie.
- *
- * @param cookie
- * The nsICookie2 object to create maps for.
- *
- * @return The newly created Map instance mapping cookie names to
- * internal jscookies, in the given path of the given host.
- */
- _ensureMap: function (cookie) {
- if (!this._hosts.has(cookie.host)) {
- this._hosts.set(cookie.host, new Map());
- }
-
- let originAttributesMap = this._hosts.get(cookie.host);
- // If cookie.originAttributes is null, originAttributes will be an empty string.
- let originAttributes = ChromeUtils.originAttributesToSuffix(cookie.originAttributes);
- if (!originAttributesMap.has(originAttributes)) {
- originAttributesMap.set(originAttributes, new Map());
- }
-
- let pathToNamesMap = originAttributesMap.get(originAttributes);
-
- if (!pathToNamesMap.has(cookie.path)) {
- pathToNamesMap.set(cookie.path, new Map());
- }
-
- return pathToNamesMap.get(cookie.path);
- }
-};
diff --git a/browser/components/sessionstore/SessionFile.jsm b/browser/components/sessionstore/SessionFile.jsm
deleted file mode 100644
index 80c4e7790..000000000
--- a/browser/components/sessionstore/SessionFile.jsm
+++ /dev/null
@@ -1,399 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["SessionFile"];
-
-/**
- * Implementation of all the disk I/O required by the session store.
- * This is a private API, meant to be used only by the session store.
- * It will change. Do not use it for any other purpose.
- *
- * Note that this module implicitly depends on one of two things:
- * 1. either the asynchronous file I/O system enqueues its requests
- * and never attempts to simultaneously execute two I/O requests on
- * the files used by this module from two distinct threads; or
- * 2. the clients of this API are well-behaved and do not place
- * concurrent requests to the files used by this module.
- *
- * Otherwise, we could encounter bugs, especially under Windows,
- * e.g. if a request attempts to write sessionstore.js while
- * another attempts to copy that file.
- *
- * This implementation uses OS.File, which guarantees property 1.
- */
-
-const Cu = Components.utils;
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cr = Components.results;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/osfile.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/AsyncShutdown.jsm");
-Cu.import("resource://gre/modules/Preferences.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "console",
- "resource://gre/modules/Console.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
- "resource://gre/modules/PromiseUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RunState",
- "resource:///modules/sessionstore/RunState.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
- "resource://gre/modules/TelemetryStopwatch.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
- "resource://gre/modules/Task.jsm");
-XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
- "@mozilla.org/base/telemetry;1", "nsITelemetry");
-XPCOMUtils.defineLazyServiceGetter(this, "sessionStartup",
- "@mozilla.org/browser/sessionstartup;1", "nsISessionStartup");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionWorker",
- "resource:///modules/sessionstore/SessionWorker.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
- "resource:///modules/sessionstore/SessionStore.jsm");
-
-const PREF_UPGRADE_BACKUP = "browser.sessionstore.upgradeBackup.latestBuildID";
-const PREF_MAX_UPGRADE_BACKUPS = "browser.sessionstore.upgradeBackup.maxUpgradeBackups";
-
-const PREF_MAX_SERIALIZE_BACK = "browser.sessionstore.max_serialize_back";
-const PREF_MAX_SERIALIZE_FWD = "browser.sessionstore.max_serialize_forward";
-
-this.SessionFile = {
- /**
- * Read the contents of the session file, asynchronously.
- */
- read: function () {
- return SessionFileInternal.read();
- },
- /**
- * Write the contents of the session file, asynchronously.
- */
- write: function (aData) {
- return SessionFileInternal.write(aData);
- },
- /**
- * Wipe the contents of the session file, asynchronously.
- */
- wipe: function () {
- return SessionFileInternal.wipe();
- },
-
- /**
- * Return the paths to the files used to store, backup, etc.
- * the state of the file.
- */
- get Paths() {
- return SessionFileInternal.Paths;
- }
-};
-
-Object.freeze(SessionFile);
-
-var Path = OS.Path;
-var profileDir = OS.Constants.Path.profileDir;
-
-var SessionFileInternal = {
- Paths: Object.freeze({
- // The path to the latest version of sessionstore written during a clean
- // shutdown. After startup, it is renamed `cleanBackup`.
- clean: Path.join(profileDir, "sessionstore.js"),
-
- // The path at which we store the previous version of `clean`. Updated
- // whenever we successfully load from `clean`.
- cleanBackup: Path.join(profileDir, "sessionstore-backups", "previous.js"),
-
- // The directory containing all sessionstore backups.
- backups: Path.join(profileDir, "sessionstore-backups"),
-
- // The path to the latest version of the sessionstore written
- // during runtime. Generally, this file contains more
- // privacy-sensitive information than |clean|, and this file is
- // therefore removed during clean shutdown. This file is designed to protect
- // against crashes / sudden shutdown.
- recovery: Path.join(profileDir, "sessionstore-backups", "recovery.js"),
-
- // The path to the previous version of the sessionstore written
- // during runtime (e.g. 15 seconds before recovery). In case of a
- // clean shutdown, this file is removed. Generally, this file
- // contains more privacy-sensitive information than |clean|, and
- // this file is therefore removed during clean shutdown. This
- // file is designed to protect against crashes that are nasty
- // enough to corrupt |recovery|.
- recoveryBackup: Path.join(profileDir, "sessionstore-backups", "recovery.bak"),
-
- // The path to a backup created during an upgrade of Firefox.
- // Having this backup protects the user essentially from bugs in
- // Firefox or add-ons, especially for users of Nightly. This file
- // does not contain any information more sensitive than |clean|.
- upgradeBackupPrefix: Path.join(profileDir, "sessionstore-backups", "upgrade.js-"),
-
- // The path to the backup of the version of the session store used
- // during the latest upgrade of Firefox. During load/recovery,
- // this file should be used if both |path|, |backupPath| and
- // |latestStartPath| are absent/incorrect. May be "" if no
- // upgrade backup has ever been performed. This file does not
- // contain any information more sensitive than |clean|.
- get upgradeBackup() {
- let latestBackupID = SessionFileInternal.latestUpgradeBackupID;
- if (!latestBackupID) {
- return "";
- }
- return this.upgradeBackupPrefix + latestBackupID;
- },
-
- // The path to a backup created during an upgrade of Firefox.
- // Having this backup protects the user essentially from bugs in
- // Firefox, especially for users of Nightly.
- get nextUpgradeBackup() {
- return this.upgradeBackupPrefix + Services.appinfo.platformBuildID;
- },
-
- /**
- * The order in which to search for a valid sessionstore file.
- */
- get loadOrder() {
- // If `clean` exists and has been written without corruption during
- // the latest shutdown, we need to use it.
- //
- // Otherwise, `recovery` and `recoveryBackup` represent the most
- // recent state of the session store.
- //
- // Finally, if nothing works, fall back to the last known state
- // that can be loaded (`cleanBackup`) or, if available, to the
- // backup performed during the latest upgrade.
- let order = ["clean",
- "recovery",
- "recoveryBackup",
- "cleanBackup"];
- if (SessionFileInternal.latestUpgradeBackupID) {
- // We have an upgradeBackup
- order.push("upgradeBackup");
- }
- return order;
- },
- }),
-
- // Number of attempted calls to `write`.
- // Note that we may have _attempts > _successes + _failures,
- // if attempts never complete.
- // Used for error reporting.
- _attempts: 0,
-
- // Number of successful calls to `write`.
- // Used for error reporting.
- _successes: 0,
-
- // Number of failed calls to `write`.
- // Used for error reporting.
- _failures: 0,
-
- // Resolved once initialization is complete.
- // The promise never rejects.
- _deferredInitialized: PromiseUtils.defer(),
-
- // `true` once we have started initialization, i.e. once something
- // has been scheduled that will eventually resolve `_deferredInitialized`.
- _initializationStarted: false,
-
- // The ID of the latest version of Gecko for which we have an upgrade backup
- // or |undefined| if no upgrade backup was ever written.
- get latestUpgradeBackupID() {
- try {
- return Services.prefs.getCharPref(PREF_UPGRADE_BACKUP);
- } catch (ex) {
- return undefined;
- }
- },
-
- // Find the correct session file, read it and setup the worker.
- read: Task.async(function* () {
- this._initializationStarted = true;
-
- let result;
- let noFilesFound = true;
- // Attempt to load by order of priority from the various backups
- for (let key of this.Paths.loadOrder) {
- let corrupted = false;
- let exists = true;
- try {
- let path = this.Paths[key];
- let startMs = Date.now();
-
- let source = yield OS.File.read(path, { encoding: "utf-8" });
- let parsed = JSON.parse(source);
-
- if (!SessionStore.isFormatVersionCompatible(parsed.version || ["sessionrestore", 0] /*fallback for old versions*/)) {
- // Skip sessionstore files that we don't understand.
- Cu.reportError("Cannot extract data from Session Restore file " + path + ". Wrong format/version: " + JSON.stringify(parsed.version) + ".");
- continue;
- }
- result = {
- origin: key,
- source: source,
- parsed: parsed
- };
- Telemetry.getHistogramById("FX_SESSION_RESTORE_CORRUPT_FILE").
- add(false);
- Telemetry.getHistogramById("FX_SESSION_RESTORE_READ_FILE_MS").
- add(Date.now() - startMs);
- break;
- } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
- exists = false;
- } catch (ex if ex instanceof OS.File.Error) {
- // The file might be inaccessible due to wrong permissions
- // or similar failures. We'll just count it as "corrupted".
- console.error("Could not read session file ", ex, ex.stack);
- corrupted = true;
- } catch (ex if ex instanceof SyntaxError) {
- console.error("Corrupt session file (invalid JSON found) ", ex, ex.stack);
- // File is corrupted, try next file
- corrupted = true;
- } finally {
- if (exists) {
- noFilesFound = false;
- Telemetry.getHistogramById("FX_SESSION_RESTORE_CORRUPT_FILE").
- add(corrupted);
- }
- }
- }
-
- // All files are corrupted if files found but none could deliver a result.
- let allCorrupt = !noFilesFound && !result;
- Telemetry.getHistogramById("FX_SESSION_RESTORE_ALL_FILES_CORRUPT").
- add(allCorrupt);
-
- if (!result) {
- // If everything fails, start with an empty session.
- result = {
- origin: "empty",
- source: "",
- parsed: null
- };
- }
-
- result.noFilesFound = noFilesFound;
-
- // Initialize the worker (in the background) to let it handle backups and also
- // as a workaround for bug 964531.
- let promiseInitialized = SessionWorker.post("init", [result.origin, this.Paths, {
- maxUpgradeBackups: Preferences.get(PREF_MAX_UPGRADE_BACKUPS, 3),
- maxSerializeBack: Preferences.get(PREF_MAX_SERIALIZE_BACK, 10),
- maxSerializeForward: Preferences.get(PREF_MAX_SERIALIZE_FWD, -1)
- }]);
-
- promiseInitialized.catch(err => {
- // Ensure that we report errors but that they do not stop us.
- Promise.reject(err);
- }).then(() => this._deferredInitialized.resolve());
-
- return result;
- }),
-
- // Post a message to the worker, making sure that it has been initialized
- // first.
- _postToWorker: Task.async(function*(...args) {
- if (!this._initializationStarted) {
- // Initializing the worker is somewhat complex, as proper handling of
- // backups requires us to first read and check the session. Consequently,
- // the only way to initialize the worker is to first call `this.read()`.
-
- // The call to `this.read()` causes background initialization of the worker.
- // Initialization will be complete once `this._deferredInitialized.promise`
- // resolves.
- this.read();
- }
- yield this._deferredInitialized.promise;
- return SessionWorker.post(...args)
- }),
-
- write: function (aData) {
- if (RunState.isClosed) {
- return Promise.reject(new Error("SessionFile is closed"));
- }
-
- let isFinalWrite = false;
- if (RunState.isClosing) {
- // If shutdown has started, we will want to stop receiving
- // write instructions.
- isFinalWrite = true;
- RunState.setClosed();
- }
-
- let performShutdownCleanup = isFinalWrite &&
- !sessionStartup.isAutomaticRestoreEnabled();
-
- this._attempts++;
- let options = {isFinalWrite, performShutdownCleanup};
- let promise = this._postToWorker("write", [aData, options]);
-
- // Wait until the write is done.
- promise = promise.then(msg => {
- // Record how long the write took.
- this._recordTelemetry(msg.telemetry);
- this._successes++;
- if (msg.result.upgradeBackup) {
- // We have just completed a backup-on-upgrade, store the information
- // in preferences.
- Services.prefs.setCharPref(PREF_UPGRADE_BACKUP,
- Services.appinfo.platformBuildID);
- }
- }, err => {
- // Catch and report any errors.
- console.error("Could not write session state file ", err, err.stack);
- this._failures++;
- // By not doing anything special here we ensure that |promise| cannot
- // be rejected anymore. The shutdown/cleanup code at the end of the
- // function will thus always be executed.
- });
-
- // Ensure that we can write sessionstore.js cleanly before the profile
- // becomes unaccessible.
- AsyncShutdown.profileBeforeChange.addBlocker(
- "SessionFile: Finish writing Session Restore data",
- promise,
- {
- fetchState: () => ({
- options,
- attempts: this._attempts,
- successes: this._successes,
- failures: this._failures,
- })
- });
-
- // This code will always be executed because |promise| can't fail anymore.
- // We ensured that by having a reject handler that reports the failure but
- // doesn't forward the rejection.
- return promise.then(() => {
- // Remove the blocker, no matter if writing failed or not.
- AsyncShutdown.profileBeforeChange.removeBlocker(promise);
-
- if (isFinalWrite) {
- Services.obs.notifyObservers(null, "sessionstore-final-state-write-complete", "");
- }
- });
- },
-
- wipe: function () {
- return this._postToWorker("wipe");
- },
-
- _recordTelemetry: function(telemetry) {
- for (let id of Object.keys(telemetry)){
- let value = telemetry[id];
- let samples = [];
- if (Array.isArray(value)) {
- samples.push(...value);
- } else {
- samples.push(value);
- }
- let histogram = Telemetry.getHistogramById(id);
- for (let sample of samples) {
- histogram.add(sample);
- }
- }
- }
-};
diff --git a/browser/components/sessionstore/SessionHistory.jsm b/browser/components/sessionstore/SessionHistory.jsm
deleted file mode 100644
index 3d28d87db..000000000
--- a/browser/components/sessionstore/SessionHistory.jsm
+++ /dev/null
@@ -1,431 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["SessionHistory"];
-
-const Cu = Components.utils;
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Utils",
- "resource://gre/modules/sessionstore/Utils.jsm");
-
-function debug(msg) {
- Services.console.logStringMessage("SessionHistory: " + msg);
-}
-
-/**
- * The external API exported by this module.
- */
-this.SessionHistory = Object.freeze({
- isEmpty: function (docShell) {
- return SessionHistoryInternal.isEmpty(docShell);
- },
-
- collect: function (docShell) {
- return SessionHistoryInternal.collect(docShell);
- },
-
- restore: function (docShell, tabData) {
- SessionHistoryInternal.restore(docShell, tabData);
- }
-});
-
-/**
- * The internal API for the SessionHistory module.
- */
-var SessionHistoryInternal = {
- /**
- * Returns whether the given docShell's session history is empty.
- *
- * @param docShell
- * The docShell that owns the session history.
- */
- isEmpty: function (docShell) {
- let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
- let history = webNavigation.sessionHistory;
- if (!webNavigation.currentURI) {
- return true;
- }
- let uri = webNavigation.currentURI.spec;
- return uri == "about:blank" && history.count == 0;
- },
-
- /**
- * Collects session history data for a given docShell.
- *
- * @param docShell
- * The docShell that owns the session history.
- */
- collect: function (docShell) {
- let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
- let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
- let history = webNavigation.sessionHistory.QueryInterface(Ci.nsISHistoryInternal);
-
- let data = {entries: [], userContextId: loadContext.originAttributes.userContextId };
-
- if (history && history.count > 0) {
- // Loop over the transaction linked list directly so we can get the
- // persist property for each transaction.
- for (let txn = history.rootTransaction; txn; txn = txn.next) {
- let entry = this.serializeEntry(txn.sHEntry);
- entry.persist = txn.persist;
- data.entries.push(entry);
- }
-
- // Ensure the index isn't out of bounds if an exception was thrown above.
- data.index = Math.min(history.index + 1, data.entries.length);
- }
-
- // 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.
- if (data.entries.length == 0) {
- let uri = webNavigation.currentURI.spec;
- let body = webNavigation.document.body;
- // We landed here because the history is inaccessible or there are no
- // history entries. In that case we should at least record the docShell's
- // current URL as a single history entry. If the URL is not about:blank
- // or it's a blank tab that was modified (like a custom newtab page),
- // record it. For about:blank we explicitly want an empty array without
- // an 'index' property to denote that there are no history entries.
- if (uri != "about:blank" || (body && body.hasChildNodes())) {
- data.entries.push({
- url: uri,
- triggeringPrincipal_base64: Utils.SERIALIZED_SYSTEMPRINCIPAL
- });
- data.index = 1;
- }
- }
-
- return data;
- },
-
- /**
- * Get an object that is a serialized representation of a History entry.
- *
- * @param shEntry
- * nsISHEntry instance
- * @return object
- */
- serializeEntry: function (shEntry) {
- let entry = { url: shEntry.URI.spec };
-
- // Save some bytes and don't include the title property
- // if that's identical to the current entry's URL.
- if (shEntry.title && shEntry.title != entry.url) {
- entry.title = shEntry.title;
- }
- if (shEntry.isSubFrame) {
- entry.subframe = true;
- }
-
- entry.charset = shEntry.URI.originCharset;
-
- let cacheKey = shEntry.cacheKey;
- if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 &&
- cacheKey.data != 0) {
- // XXXbz would be better to have cache keys implement
- // nsISerializable or something.
- entry.cacheKey = cacheKey.data;
- }
- entry.ID = shEntry.ID;
- entry.docshellID = shEntry.docshellID;
-
- // We will include the property only if it's truthy to save a couple of
- // bytes when the resulting object is stringified and saved to disk.
- if (shEntry.referrerURI) {
- entry.referrer = shEntry.referrerURI.spec;
- entry.referrerPolicy = shEntry.referrerPolicy;
- }
-
- if (shEntry.originalURI) {
- entry.originalURI = shEntry.originalURI.spec;
- }
-
- if (shEntry.loadReplace) {
- entry.loadReplace = shEntry.loadReplace;
- }
-
- if (shEntry.srcdocData)
- entry.srcdocData = shEntry.srcdocData;
-
- if (shEntry.isSrcdocEntry)
- entry.isSrcdocEntry = shEntry.isSrcdocEntry;
-
- if (shEntry.baseURI)
- entry.baseURI = shEntry.baseURI.spec;
-
- if (shEntry.contentType)
- entry.contentType = shEntry.contentType;
-
- if (shEntry.scrollRestorationIsManual) {
- entry.scrollRestorationIsManual = true;
- } else {
- let x = {}, y = {};
- shEntry.getScrollPosition(x, y);
- if (x.value != 0 || y.value != 0)
- entry.scroll = x.value + "," + y.value;
- }
-
- // Collect triggeringPrincipal data for the current history entry.
- // Please note that before Bug 1297338 there was no concept of a
- // principalToInherit. To remain backward/forward compatible we
- // serialize the principalToInherit as triggeringPrincipal_b64.
- // Once principalToInherit is well established (within FF55)
- // we can update this code, remove triggeringPrincipal_b64 and
- // just keep triggeringPrincipal_base64 as well as
- // principalToInherit_base64; see Bug 1301666.
- if (shEntry.principalToInherit) {
- try {
- let principalToInherit = Utils.serializePrincipal(shEntry.principalToInherit);
- if (principalToInherit) {
- entry.triggeringPrincipal_b64 = principalToInherit;
- entry.principalToInherit_base64 = principalToInherit;
- }
- } catch (e) {
- debug(e);
- }
- }
-
- if (shEntry.triggeringPrincipal) {
- try {
- let triggeringPrincipal = Utils.serializePrincipal(shEntry.triggeringPrincipal);
- if (triggeringPrincipal) {
- entry.triggeringPrincipal_base64 = triggeringPrincipal;
- }
- } catch (e) {
- debug(e);
- }
- }
-
- entry.docIdentifier = shEntry.BFCacheEntry.ID;
-
- if (shEntry.stateData != null) {
- entry.structuredCloneState = shEntry.stateData.getDataAsBase64();
- entry.structuredCloneVersion = shEntry.stateData.formatVersion;
- }
-
- if (!(shEntry instanceof Ci.nsISHContainer)) {
- return entry;
- }
-
- if (shEntry.childCount > 0 && !shEntry.hasDynamicallyAddedChild()) {
- let children = [];
- for (let i = 0; i < shEntry.childCount; i++) {
- let child = shEntry.GetChildAt(i);
-
- if (child) {
- // Don't try to restore framesets containing wyciwyg URLs.
- // (cf. bug 424689 and bug 450595)
- if (child.URI.schemeIs("wyciwyg")) {
- children.length = 0;
- break;
- }
-
- children.push(this.serializeEntry(child));
- }
- }
-
- if (children.length) {
- entry.children = children;
- }
- }
-
- return entry;
- },
-
- /**
- * Restores session history data for a given docShell.
- *
- * @param docShell
- * The docShell that owns the session history.
- * @param tabData
- * The tabdata including all history entries.
- */
- restore: function (docShell, tabData) {
- let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
- let history = webNavigation.sessionHistory;
- if (history.count > 0) {
- history.PurgeHistory(history.count);
- }
- history.QueryInterface(Ci.nsISHistoryInternal);
-
- let idMap = { used: {} };
- let docIdentMap = {};
- for (let i = 0; i < tabData.entries.length; i++) {
- let entry = tabData.entries[i];
- //XXXzpao Wallpaper patch for bug 514751
- if (!entry.url)
- continue;
- let persist = "persist" in entry ? entry.persist : true;
- history.addEntry(this.deserializeEntry(entry, idMap, docIdentMap), persist);
- }
-
- // Select the right history entry.
- let index = tabData.index - 1;
- if (index < history.count && history.index != index) {
- history.getEntryAtIndex(index, true);
- }
- },
-
- /**
- * Expands serialized history data into a session-history-entry instance.
- *
- * @param entry
- * Object containing serialized history data for a URL
- * @param idMap
- * Hash for ensuring unique frame IDs
- * @param docIdentMap
- * Hash to ensure reuse of BFCache entries
- * @returns nsISHEntry
- */
- deserializeEntry: function (entry, idMap, docIdentMap) {
-
- var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].
- createInstance(Ci.nsISHEntry);
-
- shEntry.setURI(Utils.makeURI(entry.url, entry.charset));
- shEntry.setTitle(entry.title || entry.url);
- if (entry.subframe)
- shEntry.setIsSubFrame(entry.subframe || false);
- shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
- if (entry.contentType)
- shEntry.contentType = entry.contentType;
- if (entry.referrer) {
- shEntry.referrerURI = Utils.makeURI(entry.referrer);
- shEntry.referrerPolicy = entry.referrerPolicy;
- }
- if (entry.originalURI) {
- shEntry.originalURI = Utils.makeURI(entry.originalURI);
- }
- if (entry.loadReplace) {
- shEntry.loadReplace = entry.loadReplace;
- }
- if (entry.isSrcdocEntry)
- shEntry.srcdocData = entry.srcdocData;
- if (entry.baseURI)
- shEntry.baseURI = Utils.makeURI(entry.baseURI);
-
- if (entry.cacheKey) {
- var cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].
- createInstance(Ci.nsISupportsPRUint32);
- cacheKey.data = entry.cacheKey;
- shEntry.cacheKey = cacheKey;
- }
-
- if (entry.ID) {
- // get a new unique ID for this frame (since the one from the last
- // start might already be in use)
- var id = idMap[entry.ID] || 0;
- if (!id) {
- for (id = Date.now(); id in idMap.used; id++);
- idMap[entry.ID] = id;
- idMap.used[id] = true;
- }
- shEntry.ID = id;
- }
-
- if (entry.docshellID)
- shEntry.docshellID = entry.docshellID;
-
- if (entry.structuredCloneState && entry.structuredCloneVersion) {
- shEntry.stateData =
- Cc["@mozilla.org/docshell/structured-clone-container;1"].
- createInstance(Ci.nsIStructuredCloneContainer);
-
- shEntry.stateData.initFromBase64(entry.structuredCloneState,
- entry.structuredCloneVersion);
- }
-
- if (entry.scrollRestorationIsManual) {
- shEntry.scrollRestorationIsManual = true;
- } else if (entry.scroll) {
- var scrollPos = (entry.scroll || "0,0").split(",");
- scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
- shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);
- }
-
- let childDocIdents = {};
- if (entry.docIdentifier) {
- // If we have a serialized document identifier, try to find an SHEntry
- // which matches that doc identifier and adopt that SHEntry's
- // BFCacheEntry. If we don't find a match, insert shEntry as the match
- // for the document identifier.
- let matchingEntry = docIdentMap[entry.docIdentifier];
- if (!matchingEntry) {
- matchingEntry = {shEntry: shEntry, childDocIdents: childDocIdents};
- docIdentMap[entry.docIdentifier] = matchingEntry;
- }
- else {
- shEntry.adoptBFCacheEntry(matchingEntry.shEntry);
- childDocIdents = matchingEntry.childDocIdents;
- }
- }
-
- // The field entry.owner_b64 got renamed to entry.triggeringPricipal_b64 in
- // Bug 1286472. To remain backward compatible we still have to support that
- // field for a few cycles before we can remove it within Bug 1289785.
- if (entry.owner_b64) {
- entry.triggeringPricipal_b64 = entry.owner_b64;
- delete entry.owner_b64;
- }
-
- // Before introducing the concept of principalToInherit we only had
- // a triggeringPrincipal within every entry which basically is the
- // equivalent of the new principalToInherit. To avoid compatibility
- // issues, we first check if the entry has entries for
- // triggeringPrincipal_base64 and principalToInherit_base64. If not
- // we fall back to using the principalToInherit (which is stored
- // as triggeringPrincipal_b64) as the triggeringPrincipal and
- // the principalToInherit.
- // FF55 will remove the triggeringPrincipal_b64, see Bug 1301666.
- if (entry.triggeringPrincipal_base64 || entry.principalToInherit_base64) {
- if (entry.triggeringPrincipal_base64) {
- shEntry.triggeringPrincipal =
- Utils.deserializePrincipal(entry.triggeringPrincipal_base64);
- }
- if (entry.principalToInherit_base64) {
- shEntry.principalToInherit =
- Utils.deserializePrincipal(entry.principalToInherit_base64);
- }
- } else if (entry.triggeringPrincipal_b64) {
- shEntry.triggeringPrincipal = Utils.deserializePrincipal(entry.triggeringPrincipal_b64);
- shEntry.principalToInherit = shEntry.triggeringPrincipal;
- }
-
- if (entry.children && shEntry instanceof Ci.nsISHContainer) {
- for (var i = 0; i < entry.children.length; i++) {
- //XXXzpao Wallpaper patch for bug 514751
- if (!entry.children[i].url)
- continue;
-
- // We're getting sessionrestore.js files with a cycle in the
- // doc-identifier graph, likely due to bug 698656. (That is, we have
- // an entry where doc identifier A is an ancestor of doc identifier B,
- // and another entry where doc identifier B is an ancestor of A.)
- //
- // If we were to respect these doc identifiers, we'd create a cycle in
- // the SHEntries themselves, which causes the docshell to loop forever
- // when it looks for the root SHEntry.
- //
- // So as a hack to fix this, we restrict the scope of a doc identifier
- // to be a node's siblings and cousins, and pass childDocIdents, not
- // aDocIdents, to _deserializeHistoryEntry. That is, we say that two
- // SHEntries with the same doc identifier have the same document iff
- // they have the same parent or their parents have the same document.
-
- shEntry.AddChild(this.deserializeEntry(entry.children[i], idMap,
- childDocIdents), i);
- }
- }
-
- return shEntry;
- },
-
-};
diff --git a/browser/components/sessionstore/SessionMigration.jsm b/browser/components/sessionstore/SessionMigration.jsm
deleted file mode 100644
index 1aa22f1a9..000000000
--- a/browser/components/sessionstore/SessionMigration.jsm
+++ /dev/null
@@ -1,106 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["SessionMigration"];
-
-const Cu = Components.utils;
-Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
-Cu.import("resource://gre/modules/Task.jsm", this);
-Cu.import("resource://gre/modules/osfile.jsm", this);
-
-XPCOMUtils.defineLazyModuleGetter(this, "Utils",
- "resource://gre/modules/sessionstore/Utils.jsm");
-
-// An encoder to UTF-8.
-XPCOMUtils.defineLazyGetter(this, "gEncoder", function () {
- return new TextEncoder();
-});
-
-// A decoder.
-XPCOMUtils.defineLazyGetter(this, "gDecoder", function () {
- return new TextDecoder();
-});
-
-var SessionMigrationInternal = {
- /**
- * Convert the original session restore state into a minimal state. It will
- * only contain:
- * - open windows
- * - with tabs
- * - with history entries with only title, url, triggeringPrincipal
- * - with pinned state
- * - with tab group info (hidden + group id)
- * - with selected tab info
- * - with selected window info
- *
- * The complete state is then wrapped into the "about:welcomeback" page as
- * form field info to be restored when restoring the state.
- */
- convertState: function(aStateObj) {
- let state = {
- selectedWindow: aStateObj.selectedWindow,
- _closedWindows: []
- };
- state.windows = aStateObj.windows.map(function(oldWin) {
- var win = {extData: {}};
- win.tabs = oldWin.tabs.map(function(oldTab) {
- var tab = {};
- // Keep only titles, urls and triggeringPrincipals for history entries
- tab.entries = oldTab.entries.map(function(entry) {
- return { url: entry.url,
- triggeringPrincipal_base64: entry.triggeringPrincipal_base64,
- title: entry.title };
- });
- tab.index = oldTab.index;
- tab.hidden = oldTab.hidden;
- tab.pinned = oldTab.pinned;
- return tab;
- });
- win.selected = oldWin.selected;
- win._closedTabs = [];
- return win;
- });
- let url = "about:welcomeback";
- let formdata = {id: {sessionData: state}, url};
- let entry = { url, triggeringPrincipal_base64: Utils.SERIALIZED_SYSTEMPRINCIPAL };
- return { windows: [{ tabs: [{ entries: [ entry ], formdata}]}]};
- },
- /**
- * Asynchronously read session restore state (JSON) from a path
- */
- readState: function(aPath) {
- return Task.spawn(function() {
- let bytes = yield OS.File.read(aPath);
- let text = gDecoder.decode(bytes);
- let state = JSON.parse(text);
- throw new Task.Result(state);
- });
- },
- /**
- * Asynchronously write session restore state as JSON to a path
- */
- writeState: function(aPath, aState) {
- let bytes = gEncoder.encode(JSON.stringify(aState));
- return OS.File.writeAtomic(aPath, bytes, {tmpPath: aPath + ".tmp"});
- }
-}
-
-var SessionMigration = {
- /**
- * Migrate a limited set of session data from one path to another.
- */
- migrate: function(aFromPath, aToPath) {
- return Task.spawn(function() {
- let inState = yield SessionMigrationInternal.readState(aFromPath);
- let outState = SessionMigrationInternal.convertState(inState);
- // Unfortunately, we can't use SessionStore's own SessionFile to
- // write out the data because it has a dependency on the profile dir
- // being known. When the migration runs, there is no guarantee that
- // that's true.
- yield SessionMigrationInternal.writeState(aToPath, outState);
- });
- }
-};
diff --git a/browser/components/sessionstore/SessionSaver.jsm b/browser/components/sessionstore/SessionSaver.jsm
deleted file mode 100644
index d672f8877..000000000
--- a/browser/components/sessionstore/SessionSaver.jsm
+++ /dev/null
@@ -1,264 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["SessionSaver"];
-
-const Cu = Components.utils;
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-
-Cu.import("resource://gre/modules/Timer.jsm", this);
-Cu.import("resource://gre/modules/Services.jsm", this);
-Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
-Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
-
-XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
- "resource://gre/modules/AppConstants.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "console",
- "resource://gre/modules/Console.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
- "resource:///modules/sessionstore/PrivacyFilter.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RunState",
- "resource:///modules/sessionstore/RunState.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
- "resource:///modules/sessionstore/SessionStore.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
- "resource:///modules/sessionstore/SessionFile.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
- "resource://gre/modules/PrivateBrowsingUtils.jsm");
-
-// Minimal interval between two save operations (in milliseconds).
-XPCOMUtils.defineLazyGetter(this, "gInterval", function () {
- const PREF = "browser.sessionstore.interval";
-
- // Observer that updates the cached value when the preference changes.
- Services.prefs.addObserver(PREF, () => {
- this.gInterval = Services.prefs.getIntPref(PREF);
-
- // Cancel any pending runs and call runDelayed() with
- // zero to apply the newly configured interval.
- SessionSaverInternal.cancel();
- SessionSaverInternal.runDelayed(0);
- }, false);
-
- return Services.prefs.getIntPref(PREF);
-});
-
-// Notify observers about a given topic with a given subject.
-function notify(subject, topic) {
- Services.obs.notifyObservers(subject, topic, "");
-}
-
-// TelemetryStopwatch helper functions.
-function stopWatch(method) {
- return function (...histograms) {
- for (let hist of histograms) {
- TelemetryStopwatch[method]("FX_SESSION_RESTORE_" + hist);
- }
- };
-}
-
-var stopWatchStart = stopWatch("start");
-var stopWatchCancel = stopWatch("cancel");
-var stopWatchFinish = stopWatch("finish");
-
-/**
- * The external API implemented by the SessionSaver module.
- */
-this.SessionSaver = Object.freeze({
- /**
- * Immediately saves the current session to disk.
- */
- run: function () {
- return SessionSaverInternal.run();
- },
-
- /**
- * Saves the current session to disk delayed by a given amount of time. Should
- * another delayed run be scheduled already, we will ignore the given delay
- * and state saving may occur a little earlier.
- */
- runDelayed: function () {
- SessionSaverInternal.runDelayed();
- },
-
- /**
- * Sets the last save time to the current time. This will cause us to wait for
- * at least the configured interval when runDelayed() is called next.
- */
- updateLastSaveTime: function () {
- SessionSaverInternal.updateLastSaveTime();
- },
-
- /**
- * Cancels all pending session saves.
- */
- cancel: function () {
- SessionSaverInternal.cancel();
- }
-});
-
-/**
- * The internal API.
- */
-var SessionSaverInternal = {
- /**
- * The timeout ID referencing an active timer for a delayed save. When no
- * save is pending, this is null.
- */
- _timeoutID: null,
-
- /**
- * A timestamp that keeps track of when we saved the session last. We will
- * this to determine the correct interval between delayed saves to not deceed
- * the configured session write interval.
- */
- _lastSaveTime: 0,
-
- /**
- * Immediately saves the current session to disk.
- */
- run: function () {
- return this._saveState(true /* force-update all windows */);
- },
-
- /**
- * Saves the current session to disk delayed by a given amount of time. Should
- * another delayed run be scheduled already, we will ignore the given delay
- * and state saving may occur a little earlier.
- *
- * @param delay (optional)
- * The minimum delay in milliseconds to wait for until we collect and
- * save the current session.
- */
- runDelayed: function (delay = 2000) {
- // Bail out if there's a pending run.
- if (this._timeoutID) {
- return;
- }
-
- // Interval until the next disk operation is allowed.
- delay = Math.max(this._lastSaveTime + gInterval - Date.now(), delay, 0);
-
- // Schedule a state save.
- this._timeoutID = setTimeout(() => this._saveStateAsync(), delay);
- },
-
- /**
- * Sets the last save time to the current time. This will cause us to wait for
- * at least the configured interval when runDelayed() is called next.
- */
- updateLastSaveTime: function () {
- this._lastSaveTime = Date.now();
- },
-
- /**
- * Cancels all pending session saves.
- */
- cancel: function () {
- clearTimeout(this._timeoutID);
- this._timeoutID = null;
- },
-
- /**
- * Saves the current session state. Collects data and writes to disk.
- *
- * @param forceUpdateAllWindows (optional)
- * Forces us to recollect data for all windows and will bypass and
- * update the corresponding caches.
- */
- _saveState: function (forceUpdateAllWindows = false) {
- // Cancel any pending timeouts.
- this.cancel();
-
- if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
- // Don't save (or even collect) anything in permanent private
- // browsing mode
-
- this.updateLastSaveTime();
- return Promise.resolve();
- }
-
- stopWatchStart("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
- let state = SessionStore.getCurrentState(forceUpdateAllWindows);
- PrivacyFilter.filterPrivateWindowsAndTabs(state);
-
- // Make sure that we keep the previous session if we started with a single
- // private window and no non-private windows have been opened, yet.
- if (state.deferredInitialState) {
- state.windows = state.deferredInitialState.windows || [];
- delete state.deferredInitialState;
- }
-
- if (AppConstants.platform != "macosx") {
- // We want to restore closed windows that are marked with _shouldRestore.
- // We're doing this here because we want to control this only when saving
- // the file.
- while (state._closedWindows.length) {
- let i = state._closedWindows.length - 1;
-
- if (!state._closedWindows[i]._shouldRestore) {
- // We only need to go until _shouldRestore
- // is falsy since we're going in reverse.
- break;
- }
-
- delete state._closedWindows[i]._shouldRestore;
- state.windows.unshift(state._closedWindows.pop());
- }
- }
-
- // Clear all cookies on clean shutdown according to user preferences
- if (RunState.isClosing) {
- let expireCookies = Services.prefs.getIntPref("network.cookie.lifetimePolicy") ==
- Services.cookies.QueryInterface(Ci.nsICookieService).ACCEPT_SESSION;
- let sanitizeCookies = Services.prefs.getBoolPref("privacy.sanitize.sanitizeOnShutdown") &&
- Services.prefs.getBoolPref("privacy.clearOnShutdown.cookies");
- let restart = Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
- // Don't clear cookies when restarting
- if ((expireCookies || sanitizeCookies) && !restart) {
- for (let window of state.windows) {
- delete window.cookies;
- }
- }
- }
-
- stopWatchFinish("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
- return this._writeState(state);
- },
-
- /**
- * Saves the current session state. Collects data asynchronously and calls
- * _saveState() to collect data again (with a cache hit rate of hopefully
- * 100%) and write to disk afterwards.
- */
- _saveStateAsync: function () {
- // Allow scheduling delayed saves again.
- this._timeoutID = null;
-
- // Write to disk.
- this._saveState();
- },
-
- /**
- * Write the given state object to disk.
- */
- _writeState: function (state) {
- // We update the time stamp before writing so that we don't write again
- // too soon, if saving is requested before the write completes. Without
- // this update we may save repeatedly if actions cause a runDelayed
- // before writing has completed. See Bug 902280
- this.updateLastSaveTime();
-
- // Write (atomically) to a session file, using a tmp file. Once the session
- // file is successfully updated, save the time stamp of the last save and
- // notify the observers.
- return SessionFile.write(state).then(() => {
- this.updateLastSaveTime();
- notify(null, "sessionstore-state-write-complete");
- }, console.error);
- },
-};
diff --git a/browser/components/sessionstore/SessionStorage.jsm b/browser/components/sessionstore/SessionStorage.jsm
deleted file mode 100644
index 705139ebf..000000000
--- a/browser/components/sessionstore/SessionStorage.jsm
+++ /dev/null
@@ -1,173 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["SessionStorage"];
-
-const Cu = Components.utils;
-const Ci = Components.interfaces;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "console",
- "resource://gre/modules/Console.jsm");
-
-// Returns the principal for a given |frame| contained in a given |docShell|.
-function getPrincipalForFrame(docShell, frame) {
- let ssm = Services.scriptSecurityManager;
- let uri = frame.document.documentURIObject;
- return ssm.getDocShellCodebasePrincipal(uri, docShell);
-}
-
-this.SessionStorage = Object.freeze({
- /**
- * Updates all sessionStorage "super cookies"
- * @param docShell
- * That tab's docshell (containing the sessionStorage)
- * @param frameTree
- * The docShell's FrameTree instance.
- * @return Returns a nested object that will have hosts as keys and per-host
- * session storage data as strings. For example:
- * {"example.com": {"key": "value", "my_number": "123"}}
- */
- collect: function (docShell, frameTree) {
- return SessionStorageInternal.collect(docShell, frameTree);
- },
-
- /**
- * Restores all sessionStorage "super cookies".
- * @param aDocShell
- * A tab's docshell (containing the sessionStorage)
- * @param aStorageData
- * A nested object with storage data to be restored that has hosts as
- * keys and per-host session storage data as strings. For example:
- * {"example.com": {"key": "value", "my_number": "123"}}
- */
- restore: function (aDocShell, aStorageData) {
- SessionStorageInternal.restore(aDocShell, aStorageData);
- },
-});
-
-var SessionStorageInternal = {
- /**
- * Reads all session storage data from the given docShell.
- * @param docShell
- * A tab's docshell (containing the sessionStorage)
- * @param frameTree
- * The docShell's FrameTree instance.
- * @return Returns a nested object that will have hosts as keys and per-host
- * session storage data as strings. For example:
- * {"example.com": {"key": "value", "my_number": "123"}}
- */
- collect: function (docShell, frameTree) {
- let data = {};
- let visitedOrigins = new Set();
-
- frameTree.forEach(frame => {
- let principal = getPrincipalForFrame(docShell, frame);
- if (!principal) {
- return;
- }
-
- // Get the origin of the current history entry
- // and use that as a key for the per-principal storage data.
- let origin = principal.origin;
- if (visitedOrigins.has(origin)) {
- // Don't read a host twice.
- return;
- }
-
- // Mark the current origin as visited.
- visitedOrigins.add(origin);
-
- let originData = this._readEntry(principal, docShell);
- if (Object.keys(originData).length) {
- data[origin] = originData;
- }
- });
-
- return Object.keys(data).length ? data : null;
- },
-
- /**
- * Writes session storage data to the given tab.
- * @param aDocShell
- * A tab's docshell (containing the sessionStorage)
- * @param aStorageData
- * A nested object with storage data to be restored that has hosts as
- * keys and per-host session storage data as strings. For example:
- * {"example.com": {"key": "value", "my_number": "123"}}
- */
- restore: function (aDocShell, aStorageData) {
- for (let origin of Object.keys(aStorageData)) {
- let data = aStorageData[origin];
-
- let principal;
-
- try {
- let attrs = aDocShell.getOriginAttributes();
- let originURI = Services.io.newURI(origin, null, null);
- principal = Services.scriptSecurityManager.createCodebasePrincipal(originURI, attrs);
- } catch (e) {
- console.error(e);
- continue;
- }
-
- let storageManager = aDocShell.QueryInterface(Ci.nsIDOMStorageManager);
- let window = aDocShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
-
- // There is no need to pass documentURI, it's only used to fill documentURI property of
- // domstorage event, which in this case has no consumer. Prevention of events in case
- // of missing documentURI will be solved in a followup bug to bug 600307.
- let storage = storageManager.createStorage(window, principal, "", aDocShell.usePrivateBrowsing);
-
- for (let key of Object.keys(data)) {
- try {
- storage.setItem(key, data[key]);
- } catch (e) {
- // throws e.g. for URIs that can't have sessionStorage
- console.error(e);
- }
- }
- }
- },
-
- /**
- * Reads an entry in the session storage data contained in a tab's history.
- * @param aURI
- * That history entry uri
- * @param aDocShell
- * A tab's docshell (containing the sessionStorage)
- */
- _readEntry: function (aPrincipal, aDocShell) {
- let hostData = {};
- let storage;
-
- let window = aDocShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
-
- try {
- let storageManager = aDocShell.QueryInterface(Ci.nsIDOMStorageManager);
- storage = storageManager.getStorage(window, aPrincipal);
- storage.length; // XXX: Bug 1232955 - storage.length can throw, catch that failure
- } catch (e) {
- // sessionStorage might throw if it's turned off, see bug 458954
- storage = null;
- }
-
- if (storage && storage.length) {
- for (let i = 0; i < storage.length; i++) {
- try {
- let key = storage.key(i);
- hostData[key] = storage.getItem(key);
- } catch (e) {
- // This currently throws for secured items (cf. bug 442048).
- }
- }
- }
-
- return hostData;
- }
-};
diff --git a/browser/components/sessionstore/SessionStore.jsm b/browser/components/sessionstore/SessionStore.jsm
deleted file mode 100644
index 6b30943f3..000000000
--- a/browser/components/sessionstore/SessionStore.jsm
+++ /dev/null
@@ -1,4746 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["SessionStore"];
-
-const Cu = Components.utils;
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cr = Components.results;
-
-// Current version of the format used by Session Restore.
-const FORMAT_VERSION = 1;
-
-const TAB_STATE_NEEDS_RESTORE = 1;
-const TAB_STATE_RESTORING = 2;
-const TAB_STATE_WILL_RESTORE = 3;
-
-// A new window has just been restored. At this stage, tabs are generally
-// not restored.
-const NOTIFY_SINGLE_WINDOW_RESTORED = "sessionstore-single-window-restored";
-const NOTIFY_WINDOWS_RESTORED = "sessionstore-windows-restored";
-const NOTIFY_BROWSER_STATE_RESTORED = "sessionstore-browser-state-restored";
-const NOTIFY_LAST_SESSION_CLEARED = "sessionstore-last-session-cleared";
-const NOTIFY_RESTORING_ON_STARTUP = "sessionstore-restoring-on-startup";
-const NOTIFY_INITIATING_MANUAL_RESTORE = "sessionstore-initiating-manual-restore";
-
-const NOTIFY_TAB_RESTORED = "sessionstore-debug-tab-restored"; // WARNING: debug-only
-
-// Maximum number of tabs to restore simultaneously. Previously controlled by
-// the browser.sessionstore.max_concurrent_tabs pref.
-const MAX_CONCURRENT_TAB_RESTORES = 3;
-
-// Amount (in CSS px) by which we allow window edges to be off-screen
-// when restoring a window, before we override the saved position to
-// pull the window back within the available screen area.
-const SCREEN_EDGE_SLOP = 8;
-
-// global notifications observed
-const OBSERVING = [
- "browser-window-before-show", "domwindowclosed",
- "quit-application-granted", "browser-lastwindow-close-granted",
- "quit-application", "browser:purge-session-history",
- "browser:purge-domain-data",
- "idle-daily",
-];
-
-// XUL Window properties to (re)store
-// Restored in restoreDimensions()
-const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"];
-
-// Hideable window features to (re)store
-// Restored in restoreWindowFeatures()
-const WINDOW_HIDEABLE_FEATURES = [
- "menubar", "toolbar", "locationbar", "personalbar", "statusbar", "scrollbars"
-];
-
-// Messages that will be received via the Frame Message Manager.
-const MESSAGES = [
- // The content script sends us data that has been invalidated and needs to
- // be saved to disk.
- "SessionStore:update",
-
- // The restoreHistory code has run. This is a good time to run SSTabRestoring.
- "SessionStore:restoreHistoryComplete",
-
- // The load for the restoring tab has begun. We update the URL bar at this
- // time; if we did it before, the load would overwrite it.
- "SessionStore:restoreTabContentStarted",
-
- // All network loads for a restoring tab are done, so we should
- // consider restoring another tab in the queue. The document has
- // been restored, and forms have been filled. We trigger
- // SSTabRestored at this time.
- "SessionStore:restoreTabContentComplete",
-
- // A crashed tab was revived by navigating to a different page. Remove its
- // browser from the list of crashed browsers to stop ignoring its messages.
- "SessionStore:crashedTabRevived",
-
- // The content script encountered an error.
- "SessionStore:error",
-];
-
-// The list of messages we accept from <xul:browser>s that have no tab
-// assigned, or whose windows have gone away. Those are for example the
-// ones that preload about:newtab pages, or from browsers where the window
-// has just been closed.
-const NOTAB_MESSAGES = new Set([
- // For a description see above.
- "SessionStore:crashedTabRevived",
-
- // For a description see above.
- "SessionStore:update",
-
- // For a description see above.
- "SessionStore:error",
-]);
-
-// The list of messages we accept without an "epoch" parameter.
-// See getCurrentEpoch() and friends to find out what an "epoch" is.
-const NOEPOCH_MESSAGES = new Set([
- // For a description see above.
- "SessionStore:crashedTabRevived",
-
- // For a description see above.
- "SessionStore:error",
-]);
-
-// The list of messages we want to receive even during the short period after a
-// frame has been removed from the DOM and before its frame script has finished
-// unloading.
-const CLOSED_MESSAGES = new Set([
- // For a description see above.
- "SessionStore:crashedTabRevived",
-
- // For a description see above.
- "SessionStore:update",
-
- // For a description see above.
- "SessionStore:error",
-]);
-
-// These are tab events that we listen to.
-const TAB_EVENTS = [
- "TabOpen", "TabBrowserInserted", "TabClose", "TabSelect", "TabShow", "TabHide", "TabPinned",
- "TabUnpinned"
-];
-
-const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-
-Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm", this);
-Cu.import("resource://gre/modules/Promise.jsm", this);
-Cu.import("resource://gre/modules/Services.jsm", this);
-Cu.import("resource://gre/modules/Task.jsm", this);
-Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
-Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", this);
-Cu.import("resource://gre/modules/Timer.jsm", this);
-Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
-Cu.import("resource://gre/modules/debug.js", this);
-Cu.import("resource://gre/modules/osfile.jsm", this);
-
-XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup",
- "@mozilla.org/browser/sessionstartup;1", "nsISessionStartup");
-XPCOMUtils.defineLazyServiceGetter(this, "gScreenManager",
- "@mozilla.org/gfx/screenmanager;1", "nsIScreenManager");
-XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
- "@mozilla.org/base/telemetry;1", "nsITelemetry");
-XPCOMUtils.defineLazyModuleGetter(this, "console",
- "resource://gre/modules/Console.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
- "resource:///modules/RecentWindow.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
- "resource://gre/modules/AppConstants.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "GlobalState",
- "resource:///modules/sessionstore/GlobalState.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
- "resource:///modules/sessionstore/PrivacyFilter.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RunState",
- "resource:///modules/sessionstore/RunState.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager",
- "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionSaver",
- "resource:///modules/sessionstore/SessionSaver.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionCookies",
- "resource:///modules/sessionstore/SessionCookies.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
- "resource:///modules/sessionstore/SessionFile.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
- "resource:///modules/sessionstore/TabAttributes.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "TabCrashHandler",
- "resource:///modules/ContentCrashHandlers.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "TabState",
- "resource:///modules/sessionstore/TabState.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
- "resource:///modules/sessionstore/TabStateCache.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "TabStateFlusher",
- "resource:///modules/sessionstore/TabStateFlusher.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Utils",
- "resource://gre/modules/sessionstore/Utils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ViewSourceBrowser",
- "resource://gre/modules/ViewSourceBrowser.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
- "resource://gre/modules/AsyncShutdown.jsm");
-
-Object.defineProperty(this, "HUDService", {
- get: function HUDService_getter() {
- let devtools = Cu.import("resource://devtools/shared/Loader.jsm", {}).devtools;
- return devtools.require("devtools/client/webconsole/hudservice").HUDService;
- },
- configurable: true,
- enumerable: true
-});
-
-/**
- * |true| if we are in debug mode, |false| otherwise.
- * Debug mode is controlled by preference browser.sessionstore.debug
- */
-var gDebuggingEnabled = false;
-function debug(aMsg) {
- if (gDebuggingEnabled) {
- aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n");
- Services.console.logStringMessage(aMsg);
- }
-}
-
-this.SessionStore = {
- get promiseInitialized() {
- return SessionStoreInternal.promiseInitialized;
- },
-
- get canRestoreLastSession() {
- return SessionStoreInternal.canRestoreLastSession;
- },
-
- set canRestoreLastSession(val) {
- SessionStoreInternal.canRestoreLastSession = val;
- },
-
- get lastClosedObjectType() {
- return SessionStoreInternal.lastClosedObjectType;
- },
-
- init: function ss_init() {
- SessionStoreInternal.init();
- },
-
- getBrowserState: function ss_getBrowserState() {
- return SessionStoreInternal.getBrowserState();
- },
-
- setBrowserState: function ss_setBrowserState(aState) {
- SessionStoreInternal.setBrowserState(aState);
- },
-
- getWindowState: function ss_getWindowState(aWindow) {
- return SessionStoreInternal.getWindowState(aWindow);
- },
-
- setWindowState: function ss_setWindowState(aWindow, aState, aOverwrite) {
- SessionStoreInternal.setWindowState(aWindow, aState, aOverwrite);
- },
-
- getTabState: function ss_getTabState(aTab) {
- return SessionStoreInternal.getTabState(aTab);
- },
-
- setTabState: function ss_setTabState(aTab, aState) {
- SessionStoreInternal.setTabState(aTab, aState);
- },
-
- duplicateTab: function ss_duplicateTab(aWindow, aTab, aDelta = 0) {
- return SessionStoreInternal.duplicateTab(aWindow, aTab, aDelta);
- },
-
- getClosedTabCount: function ss_getClosedTabCount(aWindow) {
- return SessionStoreInternal.getClosedTabCount(aWindow);
- },
-
- getClosedTabData: function ss_getClosedTabData(aWindow, aAsString = true) {
- return SessionStoreInternal.getClosedTabData(aWindow, aAsString);
- },
-
- undoCloseTab: function ss_undoCloseTab(aWindow, aIndex) {
- return SessionStoreInternal.undoCloseTab(aWindow, aIndex);
- },
-
- forgetClosedTab: function ss_forgetClosedTab(aWindow, aIndex) {
- return SessionStoreInternal.forgetClosedTab(aWindow, aIndex);
- },
-
- getClosedWindowCount: function ss_getClosedWindowCount() {
- return SessionStoreInternal.getClosedWindowCount();
- },
-
- getClosedWindowData: function ss_getClosedWindowData(aAsString = true) {
- return SessionStoreInternal.getClosedWindowData(aAsString);
- },
-
- undoCloseWindow: function ss_undoCloseWindow(aIndex) {
- return SessionStoreInternal.undoCloseWindow(aIndex);
- },
-
- forgetClosedWindow: function ss_forgetClosedWindow(aIndex) {
- return SessionStoreInternal.forgetClosedWindow(aIndex);
- },
-
- getWindowValue: function ss_getWindowValue(aWindow, aKey) {
- return SessionStoreInternal.getWindowValue(aWindow, aKey);
- },
-
- setWindowValue: function ss_setWindowValue(aWindow, aKey, aStringValue) {
- SessionStoreInternal.setWindowValue(aWindow, aKey, aStringValue);
- },
-
- deleteWindowValue: function ss_deleteWindowValue(aWindow, aKey) {
- SessionStoreInternal.deleteWindowValue(aWindow, aKey);
- },
-
- getTabValue: function ss_getTabValue(aTab, aKey) {
- return SessionStoreInternal.getTabValue(aTab, aKey);
- },
-
- setTabValue: function ss_setTabValue(aTab, aKey, aStringValue) {
- SessionStoreInternal.setTabValue(aTab, aKey, aStringValue);
- },
-
- deleteTabValue: function ss_deleteTabValue(aTab, aKey) {
- SessionStoreInternal.deleteTabValue(aTab, aKey);
- },
-
- getGlobalValue: function ss_getGlobalValue(aKey) {
- return SessionStoreInternal.getGlobalValue(aKey);
- },
-
- setGlobalValue: function ss_setGlobalValue(aKey, aStringValue) {
- SessionStoreInternal.setGlobalValue(aKey, aStringValue);
- },
-
- deleteGlobalValue: function ss_deleteGlobalValue(aKey) {
- SessionStoreInternal.deleteGlobalValue(aKey);
- },
-
- persistTabAttribute: function ss_persistTabAttribute(aName) {
- SessionStoreInternal.persistTabAttribute(aName);
- },
-
- restoreLastSession: function ss_restoreLastSession() {
- SessionStoreInternal.restoreLastSession();
- },
-
- getCurrentState: function (aUpdateAll) {
- return SessionStoreInternal.getCurrentState(aUpdateAll);
- },
-
- reviveCrashedTab(aTab) {
- return SessionStoreInternal.reviveCrashedTab(aTab);
- },
-
- reviveAllCrashedTabs() {
- return SessionStoreInternal.reviveAllCrashedTabs();
- },
-
- navigateAndRestore(tab, loadArguments, historyIndex) {
- return SessionStoreInternal.navigateAndRestore(tab, loadArguments, historyIndex);
- },
-
- getSessionHistory(tab, updatedCallback) {
- return SessionStoreInternal.getSessionHistory(tab, updatedCallback);
- },
-
- undoCloseById(aClosedId) {
- return SessionStoreInternal.undoCloseById(aClosedId);
- },
-
- /**
- * Determines whether the passed version number is compatible with
- * the current version number of the SessionStore.
- *
- * @param version The format and version of the file, as an array, e.g.
- * ["sessionrestore", 1]
- */
- isFormatVersionCompatible(version) {
- if (!version) {
- return false;
- }
- if (!Array.isArray(version)) {
- // Improper format.
- return false;
- }
- if (version[0] != "sessionrestore") {
- // Not a Session Restore file.
- return false;
- }
- let number = Number.parseFloat(version[1]);
- if (Number.isNaN(number)) {
- return false;
- }
- return number <= FORMAT_VERSION;
- },
-};
-
-// Freeze the SessionStore object. We don't want anyone to modify it.
-Object.freeze(SessionStore);
-
-var SessionStoreInternal = {
- QueryInterface: XPCOMUtils.generateQI([
- Ci.nsIDOMEventListener,
- Ci.nsIObserver,
- Ci.nsISupportsWeakReference
- ]),
-
- _globalState: new GlobalState(),
-
- // A counter to be used to generate a unique ID for each closed tab or window.
- _nextClosedId: 0,
-
- // During the initial restore and setBrowserState calls tracks the number of
- // windows yet to be restored
- _restoreCount: -1,
-
- // For each <browser> element, records the current epoch.
- _browserEpochs: new WeakMap(),
-
- // Any browsers that fires the oop-browser-crashed event gets stored in
- // here - that way we know which browsers to ignore messages from (until
- // they get restored).
- _crashedBrowsers: new WeakSet(),
-
- // A map (xul:browser -> nsIFrameLoader) that maps a browser to the last
- // associated frameLoader we heard about.
- _lastKnownFrameLoader: new WeakMap(),
-
- // A map (xul:browser -> object) that maps a browser associated with a
- // recently closed tab to all its necessary state information we need to
- // properly handle final update message.
- _closedTabs: new WeakMap(),
-
- // A map (xul:browser -> object) that maps a browser associated with a
- // recently closed tab due to a window closure to the tab state information
- // that is being stored in _closedWindows for that tab.
- _closedWindowTabs: new WeakMap(),
-
- // A set of window data that has the potential to be saved in the _closedWindows
- // array for the session. We will remove window data from this set whenever
- // forgetClosedWindow is called for the window, or when session history is
- // purged, so that we don't accidentally save that data after the flush has
- // completed. Closed tabs use a more complicated mechanism for this particular
- // problem. When forgetClosedTab is called, the browser is removed from the
- // _closedTabs map, so its data is not recorded. In the purge history case,
- // the closedTabs array per window is overwritten so that once the flush is
- // complete, the tab would only ever add itself to an array that SessionStore
- // no longer cares about. Bug 1230636 has been filed to make the tab case
- // work more like the window case, which is more explicit, and easier to
- // reason about.
- _saveableClosedWindowData: new WeakSet(),
-
- // A map (xul:browser -> object) that maps a browser that is switching
- // remoteness via navigateAndRestore, to the loadArguments that were
- // most recently passed when calling navigateAndRestore.
- _remotenessChangingBrowsers: new WeakMap(),
-
- // whether a setBrowserState call is in progress
- _browserSetState: false,
-
- // time in milliseconds when the session was started (saved across sessions),
- // defaults to now if no session was restored or timestamp doesn't exist
- _sessionStartTime: Date.now(),
-
- // states for all currently opened windows
- _windows: {},
-
- // counter for creating unique window IDs
- _nextWindowID: 0,
-
- // states for all recently closed windows
- _closedWindows: [],
-
- // collection of session states yet to be restored
- _statesToRestore: {},
-
- // counts the number of crashes since the last clean start
- _recentCrashes: 0,
-
- // whether the last window was closed and should be restored
- _restoreLastWindow: false,
-
- // number of tabs currently restoring
- _tabsRestoringCount: 0,
-
- // When starting Firefox with a single private window, this is the place
- // where we keep the session we actually wanted to restore in case the user
- // decides to later open a non-private window as well.
- _deferredInitialState: null,
-
- // A promise resolved once initialization is complete
- _deferredInitialized: (function () {
- let deferred = {};
-
- deferred.promise = new Promise((resolve, reject) => {
- deferred.resolve = resolve;
- deferred.reject = reject;
- });
-
- return deferred;
- })(),
-
- // Whether session has been initialized
- _sessionInitialized: false,
-
- // Promise that is resolved when we're ready to initialize
- // and restore the session.
- _promiseReadyForInitialization: null,
-
- // Keep busy state counters per window.
- _windowBusyStates: new WeakMap(),
-
- /**
- * A promise fulfilled once initialization is complete.
- */
- get promiseInitialized() {
- return this._deferredInitialized.promise;
- },
-
- get canRestoreLastSession() {
- return LastSession.canRestore;
- },
-
- set canRestoreLastSession(val) {
- // Cheat a bit; only allow false.
- if (!val) {
- LastSession.clear();
- }
- },
-
- /**
- * Returns a string describing the last closed object, either "tab" or "window".
- *
- * This was added to support the sessions.restore WebExtensions API.
- */
- get lastClosedObjectType() {
- if (this._closedWindows.length) {
- // Since there are closed windows, we need to check if there's a closed tab
- // in one of the currently open windows that was closed after the
- // last-closed window.
- let tabTimestamps = [];
- let windowsEnum = Services.wm.getEnumerator("navigator:browser");
- while (windowsEnum.hasMoreElements()) {
- let window = windowsEnum.getNext();
- let windowState = this._windows[window.__SSi];
- if (windowState && windowState._closedTabs[0]) {
- tabTimestamps.push(windowState._closedTabs[0].closedAt);
- }
- }
- if (!tabTimestamps.length ||
- (tabTimestamps.sort((a, b) => b - a)[0] < this._closedWindows[0].closedAt)) {
- return "window";
- }
- }
- return "tab";
- },
-
- /**
- * Initialize the sessionstore service.
- */
- init: function () {
- if (this._initialized) {
- throw new Error("SessionStore.init() must only be called once!");
- }
-
- TelemetryTimestamps.add("sessionRestoreInitialized");
- OBSERVING.forEach(function(aTopic) {
- Services.obs.addObserver(this, aTopic, true);
- }, this);
-
- this._initPrefs();
- this._initialized = true;
- },
-
- /**
- * Initialize the session using the state provided by SessionStartup
- */
- initSession: function () {
- TelemetryStopwatch.start("FX_SESSION_RESTORE_STARTUP_INIT_SESSION_MS");
- let state;
- let ss = gSessionStartup;
-
- if (ss.doRestore() ||
- ss.sessionType == Ci.nsISessionStartup.DEFER_SESSION) {
- state = ss.state;
- }
-
- if (state) {
- try {
- // If we're doing a DEFERRED session, then we want to pull pinned tabs
- // out so they can be restored.
- if (ss.sessionType == Ci.nsISessionStartup.DEFER_SESSION) {
- let [iniState, remainingState] = this._prepDataForDeferredRestore(state);
- // If we have a iniState with windows, that means that we have windows
- // with app tabs to restore.
- if (iniState.windows.length)
- state = iniState;
- else
- state = null;
-
- if (remainingState.windows.length) {
- LastSession.setState(remainingState);
- }
- }
- else {
- // Get the last deferred session in case the user still wants to
- // restore it
- LastSession.setState(state.lastSessionState);
-
- if (ss.previousSessionCrashed) {
- this._recentCrashes = (state.session &&
- state.session.recentCrashes || 0) + 1;
-
- if (this._needsRestorePage(state, this._recentCrashes)) {
- // replace the crashed session with a restore-page-only session
- let url = "about:sessionrestore";
- let formdata = {id: {sessionData: state}, url};
- let entry = {url, triggeringPrincipal_base64: Utils.SERIALIZED_SYSTEMPRINCIPAL };
- state = { windows: [{ tabs: [{ entries: [entry], formdata }] }] };
- } else if (this._hasSingleTabWithURL(state.windows,
- "about:welcomeback")) {
- // On a single about:welcomeback URL that crashed, replace about:welcomeback
- // with about:sessionrestore, to make clear to the user that we crashed.
- state.windows[0].tabs[0].entries[0].url = "about:sessionrestore";
- state.windows[0].tabs[0].entries[0].triggeringPrincipal_base64 = Utils.SERIALIZED_SYSTEMPRINCIPAL;
- }
- }
-
- // Update the session start time using the restored session state.
- this._updateSessionStartTime(state);
-
- // make sure that at least the first window doesn't have anything hidden
- delete state.windows[0].hidden;
- // Since nothing is hidden in the first window, it cannot be a popup
- delete state.windows[0].isPopup;
- // We don't want to minimize and then open a window at startup.
- if (state.windows[0].sizemode == "minimized")
- state.windows[0].sizemode = "normal";
- // clear any lastSessionWindowID attributes since those don't matter
- // during normal restore
- state.windows.forEach(function(aWindow) {
- delete aWindow.__lastSessionWindowID;
- });
- }
- }
- catch (ex) { debug("The session file is invalid: " + ex); }
- }
-
- // at this point, we've as good as resumed the session, so we can
- // clear the resume_session_once flag, if it's set
- if (!RunState.isQuitting &&
- this._prefBranch.getBoolPref("sessionstore.resume_session_once"))
- this._prefBranch.setBoolPref("sessionstore.resume_session_once", false);
-
- TelemetryStopwatch.finish("FX_SESSION_RESTORE_STARTUP_INIT_SESSION_MS");
- return state;
- },
-
- _initPrefs : function() {
- this._prefBranch = Services.prefs.getBranch("browser.");
-
- gDebuggingEnabled = this._prefBranch.getBoolPref("sessionstore.debug");
-
- Services.prefs.addObserver("browser.sessionstore.debug", () => {
- gDebuggingEnabled = this._prefBranch.getBoolPref("sessionstore.debug");
- }, false);
-
- this._max_tabs_undo = this._prefBranch.getIntPref("sessionstore.max_tabs_undo");
- this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true);
-
- this._max_windows_undo = this._prefBranch.getIntPref("sessionstore.max_windows_undo");
- this._prefBranch.addObserver("sessionstore.max_windows_undo", this, true);
- },
-
- /**
- * Called on application shutdown, after notifications:
- * quit-application-granted, quit-application
- */
- _uninit: function ssi_uninit() {
- if (!this._initialized) {
- throw new Error("SessionStore is not initialized.");
- }
-
- // Prepare to close the session file and write the last state.
- RunState.setClosing();
-
- // save all data for session resuming
- if (this._sessionInitialized) {
- SessionSaver.run();
- }
-
- // clear out priority queue in case it's still holding refs
- TabRestoreQueue.reset();
-
- // Make sure to cancel pending saves.
- SessionSaver.cancel();
- },
-
- /**
- * Handle notifications
- */
- observe: function ssi_observe(aSubject, aTopic, aData) {
- switch (aTopic) {
- case "browser-window-before-show": // catch new windows
- this.onBeforeBrowserWindowShown(aSubject);
- break;
- case "domwindowclosed": // catch closed windows
- this.onClose(aSubject);
- break;
- case "quit-application-granted":
- let syncShutdown = aData == "syncShutdown";
- this.onQuitApplicationGranted(syncShutdown);
- break;
- case "browser-lastwindow-close-granted":
- this.onLastWindowCloseGranted();
- break;
- case "quit-application":
- this.onQuitApplication(aData);
- break;
- case "browser:purge-session-history": // catch sanitization
- this.onPurgeSessionHistory();
- break;
- case "browser:purge-domain-data":
- this.onPurgeDomainData(aData);
- break;
- case "nsPref:changed": // catch pref changes
- this.onPrefChange(aData);
- break;
- case "idle-daily":
- this.onIdleDaily();
- break;
- }
- },
-
- /**
- * This method handles incoming messages sent by the session store content
- * script via the Frame Message Manager or Parent Process Message Manager,
- * and thus enables communication with OOP tabs.
- */
- receiveMessage(aMessage) {
- // If we got here, that means we're dealing with a frame message
- // manager message, so the target will be a <xul:browser>.
- var browser = aMessage.target;
- let win = browser.ownerGlobal;
- let tab = win ? win.gBrowser.getTabForBrowser(browser) : null;
-
- // Ensure we receive only specific messages from <xul:browser>s that
- // have no tab or window assigned, e.g. the ones that preload
- // about:newtab pages, or windows that have closed.
- if (!tab && !NOTAB_MESSAGES.has(aMessage.name)) {
- throw new Error(`received unexpected message '${aMessage.name}' ` +
- `from a browser that has no tab or window`);
- }
-
- let data = aMessage.data || {};
- let hasEpoch = data.hasOwnProperty("epoch");
-
- // Most messages sent by frame scripts require to pass an epoch.
- if (!hasEpoch && !NOEPOCH_MESSAGES.has(aMessage.name)) {
- throw new Error(`received message '${aMessage.name}' without an epoch`);
- }
-
- // Ignore messages from previous epochs.
- if (hasEpoch && !this.isCurrentEpoch(browser, data.epoch)) {
- return;
- }
-
- switch (aMessage.name) {
- case "SessionStore:update":
- // |browser.frameLoader| might be empty if the browser was already
- // destroyed and its tab removed. In that case we still have the last
- // frameLoader we know about to compare.
- let frameLoader = browser.frameLoader ||
- this._lastKnownFrameLoader.get(browser.permanentKey);
-
- // If the message isn't targeting the latest frameLoader discard it.
- if (frameLoader != aMessage.targetFrameLoader) {
- return;
- }
-
- if (aMessage.data.isFinal) {
- // If this the final message we need to resolve all pending flush
- // requests for the given browser as they might have been sent too
- // late and will never respond. If they have been sent shortly after
- // switching a browser's remoteness there isn't too much data to skip.
- TabStateFlusher.resolveAll(browser);
- } else if (aMessage.data.flushID) {
- // This is an update kicked off by an async flush request. Notify the
- // TabStateFlusher so that it can finish the request and notify its
- // consumer that's waiting for the flush to be done.
- TabStateFlusher.resolve(browser, aMessage.data.flushID);
- }
-
- // Ignore messages from <browser> elements that have crashed
- // and not yet been revived.
- if (this._crashedBrowsers.has(browser.permanentKey)) {
- return;
- }
-
- // Record telemetry measurements done in the child and update the tab's
- // cached state. Mark the window as dirty and trigger a delayed write.
- this.recordTelemetry(aMessage.data.telemetry);
- TabState.update(browser, aMessage.data);
- this.saveStateDelayed(win);
-
- // Handle any updates sent by the child after the tab was closed. This
- // might be the final update as sent by the "unload" handler but also
- // any async update message that was sent before the child unloaded.
- if (this._closedTabs.has(browser.permanentKey)) {
- let {closedTabs, tabData} = this._closedTabs.get(browser.permanentKey);
-
- // Update the closed tab's state. This will be reflected in its
- // window's list of closed tabs as that refers to the same object.
- TabState.copyFromCache(browser, tabData.state);
-
- // Is this the tab's final message?
- if (aMessage.data.isFinal) {
- // We expect no further updates.
- this._closedTabs.delete(browser.permanentKey);
- // The tab state no longer needs this reference.
- delete tabData.permanentKey;
-
- // Determine whether the tab state is worth saving.
- let shouldSave = this._shouldSaveTabState(tabData.state);
- let index = closedTabs.indexOf(tabData);
-
- if (shouldSave && index == -1) {
- // If the tab state is worth saving and we didn't push it onto
- // the list of closed tabs when it was closed (because we deemed
- // the state not worth saving) then add it to the window's list
- // of closed tabs now.
- this.saveClosedTabData(closedTabs, tabData);
- } else if (!shouldSave && index > -1) {
- // Remove from the list of closed tabs. The update messages sent
- // after the tab was closed changed enough state so that we no
- // longer consider its data interesting enough to keep around.
- this.removeClosedTabData(closedTabs, index);
- }
- }
- }
- break;
- case "SessionStore:restoreHistoryComplete":
- // Notify the tabbrowser that the tab chrome has been restored.
- let tabData = TabState.collect(tab);
-
- // wall-paper fix for bug 439675: make sure that the URL to be loaded
- // is always visible in the address bar if no other value is present
- let activePageData = tabData.entries[tabData.index - 1] || null;
- let uri = activePageData ? activePageData.url || null : null;
- // NB: we won't set initial URIs (about:home, about:newtab, etc.) here
- // because their load will not normally trigger a location bar clearing
- // when they finish loading (to avoid race conditions where we then
- // clear user input instead), so we shouldn't set them here either.
- // They also don't fall under the issues in bug 439675 where user input
- // needs to be preserved if the load doesn't succeed.
- // We also don't do this for remoteness updates, where it should not
- // be necessary.
- if (!browser.userTypedValue && uri && !data.isRemotenessUpdate &&
- !win.gInitialPages.includes(uri)) {
- browser.userTypedValue = uri;
- }
-
- // If the page has a title, set it.
- if (activePageData) {
- if (activePageData.title) {
- tab.label = activePageData.title;
- tab.crop = "end";
- } else if (activePageData.url != "about:blank") {
- tab.label = activePageData.url;
- tab.crop = "center";
- }
- } else if (tab.hasAttribute("customizemode")) {
- win.gCustomizeMode.setTab(tab);
- }
-
- // Restore the tab icon.
- if ("image" in tabData) {
- // Use the serialized contentPrincipal with the new icon load.
- let loadingPrincipal = Utils.deserializePrincipal(tabData.iconLoadingPrincipal);
- win.gBrowser.setIcon(tab, tabData.image, loadingPrincipal);
- TabStateCache.update(browser, { image: null, iconLoadingPrincipal: null });
- }
-
- let event = win.document.createEvent("Events");
- event.initEvent("SSTabRestoring", true, false);
- tab.dispatchEvent(event);
- break;
- case "SessionStore:restoreTabContentStarted":
- if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
- // If a load not initiated by sessionstore was started in a
- // previously pending tab. Mark the tab as no longer pending.
- this.markTabAsRestoring(tab);
- } else if (!data.isRemotenessUpdate) {
- // If the user was typing into the URL bar when we crashed, but hadn't hit
- // enter yet, then we just need to write that value to the URL bar without
- // loading anything. This must happen after the load, as the load will clear
- // userTypedValue.
- let tabData = TabState.collect(tab);
- if (tabData.userTypedValue && !tabData.userTypedClear && !browser.userTypedValue) {
- browser.userTypedValue = tabData.userTypedValue;
- win.URLBarSetURI();
- }
-
- // Remove state we don't need any longer.
- TabStateCache.update(browser, {
- userTypedValue: null, userTypedClear: null
- });
- }
- break;
- case "SessionStore:restoreTabContentComplete":
- // This callback is used exclusively by tests that want to
- // monitor the progress of network loads.
- if (gDebuggingEnabled) {
- Services.obs.notifyObservers(browser, NOTIFY_TAB_RESTORED, null);
- }
-
- SessionStoreInternal._resetLocalTabRestoringState(tab);
- SessionStoreInternal.restoreNextTab();
-
- this._sendTabRestoredNotification(tab, data.isRemotenessUpdate);
- break;
- case "SessionStore:crashedTabRevived":
- // The browser was revived by navigating to a different page
- // manually, so we remove it from the ignored browser set.
- this._crashedBrowsers.delete(browser.permanentKey);
- break;
- case "SessionStore:error":
- this.reportInternalError(data);
- TabStateFlusher.resolveAll(browser, false, "Received error from the content process");
- break;
- default:
- throw new Error(`received unknown message '${aMessage.name}'`);
- break;
- }
- },
-
- /**
- * Record telemetry measurements stored in an object.
- * @param telemetry
- * {histogramID: value, ...} An object mapping histogramIDs to the
- * value to be recorded for that ID,
- */
- recordTelemetry: function (telemetry) {
- for (let histogramId in telemetry){
- Telemetry.getHistogramById(histogramId).add(telemetry[histogramId]);
- }
- },
-
- /* ........ Window Event Handlers .............. */
-
- /**
- * Implement nsIDOMEventListener for handling various window and tab events
- */
- handleEvent: function ssi_handleEvent(aEvent) {
- let win = aEvent.currentTarget.ownerGlobal;
- let target = aEvent.originalTarget;
- switch (aEvent.type) {
- case "TabOpen":
- this.onTabAdd(win);
- break;
- case "TabBrowserInserted":
- this.onTabBrowserInserted(win, target);
- break;
- case "TabClose":
- // `adoptedBy` will be set if the tab was closed because it is being
- // moved to a new window.
- if (!aEvent.detail.adoptedBy)
- this.onTabClose(win, target);
- this.onTabRemove(win, target);
- break;
- case "TabSelect":
- this.onTabSelect(win);
- break;
- case "TabShow":
- this.onTabShow(win, target);
- break;
- case "TabHide":
- this.onTabHide(win, target);
- break;
- case "TabPinned":
- case "TabUnpinned":
- case "SwapDocShells":
- this.saveStateDelayed(win);
- break;
- case "oop-browser-crashed":
- this.onBrowserCrashed(target);
- break;
- case "XULFrameLoaderCreated":
- if (target.namespaceURI == NS_XUL &&
- target.localName == "browser" &&
- target.frameLoader &&
- target.permanentKey) {
- this._lastKnownFrameLoader.set(target.permanentKey, target.frameLoader);
- this.resetEpoch(target);
- }
- break;
- default:
- throw new Error(`unhandled event ${aEvent.type}?`);
- }
- this._clearRestoringWindows();
- },
-
- /**
- * Generate a unique window identifier
- * @return string
- * A unique string to identify a window
- */
- _generateWindowID: function ssi_generateWindowID() {
- return "window" + (this._nextWindowID++);
- },
-
- /**
- * Registers and tracks a given window.
- *
- * @param aWindow
- * Window reference
- */
- onLoad(aWindow) {
- // return if window has already been initialized
- if (aWindow && aWindow.__SSi && this._windows[aWindow.__SSi])
- return;
-
- // ignore windows opened while shutting down
- if (RunState.isQuitting)
- return;
-
- // Assign the window a unique identifier we can use to reference
- // internal data about the window.
- aWindow.__SSi = this._generateWindowID();
-
- let mm = aWindow.getGroupMessageManager("browsers");
- MESSAGES.forEach(msg => {
- let listenWhenClosed = CLOSED_MESSAGES.has(msg);
- mm.addMessageListener(msg, this, listenWhenClosed);
- });
-
- // Load the frame script after registering listeners.
- mm.loadFrameScript("chrome://browser/content/content-sessionStore.js", true);
-
- // and create its data object
- this._windows[aWindow.__SSi] = { tabs: [], selected: 0, _closedTabs: [], busy: false };
-
- if (PrivateBrowsingUtils.isWindowPrivate(aWindow))
- this._windows[aWindow.__SSi].isPrivate = true;
- if (!this._isWindowLoaded(aWindow))
- this._windows[aWindow.__SSi]._restoring = true;
- if (!aWindow.toolbar.visible)
- this._windows[aWindow.__SSi].isPopup = true;
-
- let tabbrowser = aWindow.gBrowser;
-
- // add tab change listeners to all already existing tabs
- for (let i = 0; i < tabbrowser.tabs.length; i++) {
- this.onTabBrowserInserted(aWindow, tabbrowser.tabs[i]);
- }
- // notification of tab add/remove/selection/show/hide
- TAB_EVENTS.forEach(function(aEvent) {
- tabbrowser.tabContainer.addEventListener(aEvent, this, true);
- }, this);
-
- // Keep track of a browser's latest frameLoader.
- aWindow.gBrowser.addEventListener("XULFrameLoaderCreated", this);
- },
-
- /**
- * Initializes a given window.
- *
- * Windows are registered as soon as they are created but we need to wait for
- * the session file to load, and the initial window's delayed startup to
- * finish before initializing a window, i.e. restoring data into it.
- *
- * @param aWindow
- * Window reference
- * @param aInitialState
- * The initial state to be loaded after startup (optional)
- */
- initializeWindow(aWindow, aInitialState = null) {
- let isPrivateWindow = PrivateBrowsingUtils.isWindowPrivate(aWindow);
-
- // perform additional initialization when the first window is loading
- if (RunState.isStopped) {
- RunState.setRunning();
-
- // restore a crashed session resp. resume the last session if requested
- if (aInitialState) {
- // Don't write to disk right after startup. Set the last time we wrote
- // to disk to NOW() to enforce a full interval before the next write.
- SessionSaver.updateLastSaveTime();
-
- if (isPrivateWindow) {
- // We're starting with a single private window. Save the state we
- // actually wanted to restore so that we can do it later in case
- // the user opens another, non-private window.
- this._deferredInitialState = gSessionStartup.state;
-
- // Nothing to restore now, notify observers things are complete.
- Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
- } else {
- TelemetryTimestamps.add("sessionRestoreRestoring");
- this._restoreCount = aInitialState.windows ? aInitialState.windows.length : 0;
-
- // global data must be restored before restoreWindow is called so that
- // it happens before observers are notified
- this._globalState.setFromState(aInitialState);
-
- let overwrite = this._isCmdLineEmpty(aWindow, aInitialState);
- let options = {firstWindow: true, overwriteTabs: overwrite};
- this.restoreWindows(aWindow, aInitialState, options);
- }
- }
- else {
- // Nothing to restore, notify observers things are complete.
- Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
- }
- }
- // this window was opened by _openWindowWithState
- else if (!this._isWindowLoaded(aWindow)) {
- let state = this._statesToRestore[aWindow.__SS_restoreID];
- let options = {overwriteTabs: true, isFollowUp: state.windows.length == 1};
- this.restoreWindow(aWindow, state.windows[0], options);
- }
- // The user opened another, non-private window after starting up with
- // a single private one. Let's restore the session we actually wanted to
- // restore at startup.
- else if (this._deferredInitialState && !isPrivateWindow &&
- aWindow.toolbar.visible) {
-
- // global data must be restored before restoreWindow is called so that
- // it happens before observers are notified
- this._globalState.setFromState(this._deferredInitialState);
-
- this._restoreCount = this._deferredInitialState.windows ?
- this._deferredInitialState.windows.length : 0;
- this.restoreWindows(aWindow, this._deferredInitialState, {firstWindow: true});
- this._deferredInitialState = null;
- }
- else if (this._restoreLastWindow && aWindow.toolbar.visible &&
- this._closedWindows.length && !isPrivateWindow) {
-
- // default to the most-recently closed window
- // don't use popup windows
- let closedWindowState = null;
- let closedWindowIndex;
- for (let i = 0; i < this._closedWindows.length; i++) {
- // Take the first non-popup, point our object at it, and break out.
- if (!this._closedWindows[i].isPopup) {
- closedWindowState = this._closedWindows[i];
- closedWindowIndex = i;
- break;
- }
- }
-
- if (closedWindowState) {
- let newWindowState;
- if (AppConstants.platform == "macosx" || !this._doResumeSession()) {
- // We want to split the window up into pinned tabs and unpinned tabs.
- // Pinned tabs should be restored. If there are any remaining tabs,
- // they should be added back to _closedWindows.
- // We'll cheat a little bit and reuse _prepDataForDeferredRestore
- // even though it wasn't built exactly for this.
- let [appTabsState, normalTabsState] =
- this._prepDataForDeferredRestore({ windows: [closedWindowState] });
-
- // These are our pinned tabs, which we should restore
- if (appTabsState.windows.length) {
- newWindowState = appTabsState.windows[0];
- delete newWindowState.__lastSessionWindowID;
- }
-
- // In case there were no unpinned tabs, remove the window from _closedWindows
- if (!normalTabsState.windows.length) {
- this._closedWindows.splice(closedWindowIndex, 1);
- }
- // Or update _closedWindows with the modified state
- else {
- delete normalTabsState.windows[0].__lastSessionWindowID;
- this._closedWindows[closedWindowIndex] = normalTabsState.windows[0];
- }
- }
- else {
- // If we're just restoring the window, make sure it gets removed from
- // _closedWindows.
- this._closedWindows.splice(closedWindowIndex, 1);
- newWindowState = closedWindowState;
- delete newWindowState.hidden;
- }
-
- if (newWindowState) {
- // Ensure that the window state isn't hidden
- this._restoreCount = 1;
- let state = { windows: [newWindowState] };
- let options = {overwriteTabs: this._isCmdLineEmpty(aWindow, state)};
- this.restoreWindow(aWindow, newWindowState, options);
- }
- }
- // we actually restored the session just now.
- this._prefBranch.setBoolPref("sessionstore.resume_session_once", false);
- }
- if (this._restoreLastWindow && aWindow.toolbar.visible) {
- // always reset (if not a popup window)
- // we don't want to restore a window directly after, for example,
- // undoCloseWindow was executed.
- this._restoreLastWindow = false;
- }
- },
-
- /**
- * Called right before a new browser window is shown.
- * @param aWindow
- * Window reference
- */
- onBeforeBrowserWindowShown: function (aWindow) {
- // Register the window.
- this.onLoad(aWindow);
-
- // Just call initializeWindow() directly if we're initialized already.
- if (this._sessionInitialized) {
- this.initializeWindow(aWindow);
- return;
- }
-
- // The very first window that is opened creates a promise that is then
- // re-used by all subsequent windows. The promise will be used to tell
- // when we're ready for initialization.
- if (!this._promiseReadyForInitialization) {
- // Wait for the given window's delayed startup to be finished.
- let promise = new Promise(resolve => {
- Services.obs.addObserver(function obs(subject, topic) {
- if (aWindow == subject) {
- Services.obs.removeObserver(obs, topic);
- resolve();
- }
- }, "browser-delayed-startup-finished", false);
- });
-
- // We are ready for initialization as soon as the session file has been
- // read from disk and the initial window's delayed startup has finished.
- this._promiseReadyForInitialization =
- Promise.all([promise, gSessionStartup.onceInitialized]);
- }
-
- // We can't call this.onLoad since initialization
- // hasn't completed, so we'll wait until it is done.
- // Even if additional windows are opened and wait
- // for initialization as well, the first opened
- // window should execute first, and this.onLoad
- // will be called with the initialState.
- this._promiseReadyForInitialization.then(() => {
- if (aWindow.closed) {
- return;
- }
-
- if (this._sessionInitialized) {
- this.initializeWindow(aWindow);
- } else {
- let initialState = this.initSession();
- this._sessionInitialized = true;
-
- if (initialState) {
- Services.obs.notifyObservers(null, NOTIFY_RESTORING_ON_STARTUP, "");
- }
- TelemetryStopwatch.start("FX_SESSION_RESTORE_STARTUP_ONLOAD_INITIAL_WINDOW_MS");
- this.initializeWindow(aWindow, initialState);
- TelemetryStopwatch.finish("FX_SESSION_RESTORE_STARTUP_ONLOAD_INITIAL_WINDOW_MS");
-
- // Let everyone know we're done.
- this._deferredInitialized.resolve();
- }
- }, console.error);
- },
-
- /**
- * On window close...
- * - remove event listeners from tabs
- * - save all window data
- * @param aWindow
- * Window reference
- */
- onClose: function ssi_onClose(aWindow) {
- // this window was about to be restored - conserve its original data, if any
- let isFullyLoaded = this._isWindowLoaded(aWindow);
- if (!isFullyLoaded) {
- if (!aWindow.__SSi) {
- aWindow.__SSi = this._generateWindowID();
- }
-
- this._windows[aWindow.__SSi] = this._statesToRestore[aWindow.__SS_restoreID];
- delete this._statesToRestore[aWindow.__SS_restoreID];
- delete aWindow.__SS_restoreID;
- }
-
- // ignore windows not tracked by SessionStore
- if (!aWindow.__SSi || !this._windows[aWindow.__SSi]) {
- return;
- }
-
- // notify that the session store will stop tracking this window so that
- // extensions can store any data about this window in session store before
- // that's not possible anymore
- let event = aWindow.document.createEvent("Events");
- event.initEvent("SSWindowClosing", true, false);
- aWindow.dispatchEvent(event);
-
- if (this.windowToFocus && this.windowToFocus == aWindow) {
- delete this.windowToFocus;
- }
-
- var tabbrowser = aWindow.gBrowser;
-
- let browsers = Array.from(tabbrowser.browsers);
-
- TAB_EVENTS.forEach(function(aEvent) {
- tabbrowser.tabContainer.removeEventListener(aEvent, this, true);
- }, this);
-
- aWindow.gBrowser.removeEventListener("XULFrameLoaderCreated", this);
-
- let winData = this._windows[aWindow.__SSi];
-
- // Collect window data only when *not* closed during shutdown.
- if (RunState.isRunning) {
- // Grab the most recent window data. The tab data will be updated
- // once we finish flushing all of the messages from the tabs.
- let tabMap = this._collectWindowData(aWindow);
-
- for (let [tab, tabData] of tabMap) {
- let permanentKey = tab.linkedBrowser.permanentKey;
- this._closedWindowTabs.set(permanentKey, tabData);
- }
-
- if (isFullyLoaded) {
- winData.title = tabbrowser.selectedBrowser.contentTitle || tabbrowser.selectedTab.label;
- winData.title = this._replaceLoadingTitle(winData.title, tabbrowser,
- tabbrowser.selectedTab);
- SessionCookies.update([winData]);
- }
-
- if (AppConstants.platform != "macosx") {
- // Until we decide otherwise elsewhere, this window is part of a series
- // of closing windows to quit.
- winData._shouldRestore = true;
- }
-
- // Store the window's close date to figure out when each individual tab
- // was closed. This timestamp should allow re-arranging data based on how
- // recently something was closed.
- winData.closedAt = Date.now();
-
- // we don't want to save the busy state
- delete winData.busy;
-
- // When closing windows one after the other until Firefox quits, we
- // will move those closed in series back to the "open windows" bucket
- // before writing to disk. If however there is only a single window
- // with tabs we deem not worth saving then we might end up with a
- // random closed or even a pop-up window re-opened. To prevent that
- // we explicitly allow saving an "empty" window state.
- let isLastWindow =
- Object.keys(this._windows).length == 1 &&
- !this._closedWindows.some(win => win._shouldRestore || false);
-
- // clear this window from the list, since it has definitely been closed.
- delete this._windows[aWindow.__SSi];
-
- // This window has the potential to be saved in the _closedWindows
- // array (maybeSaveClosedWindows gets the final call on that).
- this._saveableClosedWindowData.add(winData);
-
- // Now we have to figure out if this window is worth saving in the _closedWindows
- // Object.
- //
- // We're about to flush the tabs from this window, but it's possible that we
- // might never hear back from the content process(es) in time before the user
- // chooses to restore the closed window. So we do the following:
- //
- // 1) Use the tab state cache to determine synchronously if the window is
- // worth stashing in _closedWindows.
- // 2) Flush the window.
- // 3) When the flush is complete, revisit our decision to store the window
- // in _closedWindows, and add/remove as necessary.
- if (!winData.isPrivate) {
- // Remove any open private tabs the window may contain.
- PrivacyFilter.filterPrivateTabs(winData);
- this.maybeSaveClosedWindow(winData, isLastWindow);
- }
-
- TabStateFlusher.flushWindow(aWindow).then(() => {
- // At this point, aWindow is closed! You should probably not try to
- // access any DOM elements from aWindow within this callback unless
- // you're holding on to them in the closure.
-
- for (let browser of browsers) {
- if (this._closedWindowTabs.has(browser.permanentKey)) {
- let tabData = this._closedWindowTabs.get(browser.permanentKey);
- TabState.copyFromCache(browser, tabData);
- this._closedWindowTabs.delete(browser.permanentKey);
- }
- }
-
- // Save non-private windows if they have at
- // least one saveable tab or are the last window.
- if (!winData.isPrivate) {
- // It's possible that a tab switched its privacy state at some point
- // before our flush, so we need to filter again.
- PrivacyFilter.filterPrivateTabs(winData);
- this.maybeSaveClosedWindow(winData, isLastWindow);
- }
-
- // Update the tabs data now that we've got the most
- // recent information.
- this.cleanUpWindow(aWindow, winData, browsers);
-
- // save the state without this window to disk
- this.saveStateDelayed();
- });
- } else {
- this.cleanUpWindow(aWindow, winData, browsers);
- }
-
- for (let i = 0; i < tabbrowser.tabs.length; i++) {
- this.onTabRemove(aWindow, tabbrowser.tabs[i], true);
- }
- },
-
- /**
- * Clean up the message listeners on a window that has finally
- * gone away. Call this once you're sure you don't want to hear
- * from any of this windows tabs from here forward.
- *
- * @param aWindow
- * The browser window we're cleaning up.
- * @param winData
- * The data for the window that we should hold in the
- * DyingWindowCache in case anybody is still holding a
- * reference to it.
- */
- cleanUpWindow(aWindow, winData, browsers) {
- // Any leftover TabStateFlusher Promises need to be resolved now,
- // since we're about to remove the message listeners.
- for (let browser of browsers) {
- TabStateFlusher.resolveAll(browser);
- }
-
- // Cache the window state until it is completely gone.
- DyingWindowCache.set(aWindow, winData);
-
- let mm = aWindow.getGroupMessageManager("browsers");
- MESSAGES.forEach(msg => mm.removeMessageListener(msg, this));
-
- this._saveableClosedWindowData.delete(winData);
- delete aWindow.__SSi;
- },
-
- /**
- * Decides whether or not a closed window should be put into the
- * _closedWindows Object. This might be called multiple times per
- * window, and will do the right thing of moving the window data
- * in or out of _closedWindows if the winData indicates that our
- * need for saving it has changed.
- *
- * @param winData
- * The data for the closed window that we might save.
- * @param isLastWindow
- * Whether or not the window being closed is the last
- * browser window. Callers of this function should pass
- * in the value of SessionStoreInternal.atLastWindow for
- * this argument, and pass in the same value if they happen
- * to call this method again asynchronously (for example, after
- * a window flush).
- */
- maybeSaveClosedWindow(winData, isLastWindow) {
- // Make sure SessionStore is still running, and make sure that we
- // haven't chosen to forget this window.
- if (RunState.isRunning && this._saveableClosedWindowData.has(winData)) {
- // Determine whether the window has any tabs worth saving.
- let hasSaveableTabs = winData.tabs.some(this._shouldSaveTabState);
-
- // Note that we might already have this window stored in
- // _closedWindows from a previous call to this function.
- let winIndex = this._closedWindows.indexOf(winData);
- let alreadyStored = (winIndex != -1);
- let shouldStore = (hasSaveableTabs || isLastWindow);
-
- if (shouldStore && !alreadyStored) {
- let index = this._closedWindows.findIndex(win => {
- return win.closedAt < winData.closedAt;
- });
-
- // If we found no tab closed before our
- // tab then just append it to the list.
- if (index == -1) {
- index = this._closedWindows.length;
- }
-
- // About to save the closed window, add a unique ID.
- winData.closedId = this._nextClosedId++;
-
- // Insert tabData at the right position.
- this._closedWindows.splice(index, 0, winData);
- this._capClosedWindows();
- } else if (!shouldStore && alreadyStored) {
- this._closedWindows.splice(winIndex, 1);
- }
- }
- },
-
- /**
- * On quit application granted
- */
- onQuitApplicationGranted: function ssi_onQuitApplicationGranted(syncShutdown=false) {
- // Collect an initial snapshot of window data before we do the flush
- this._forEachBrowserWindow((win) => {
- this._collectWindowData(win);
- });
-
- // Now add an AsyncShutdown blocker that'll spin the event loop
- // until the windows have all been flushed.
-
- // This progress object will track the state of async window flushing
- // and will help us debug things that go wrong with our AsyncShutdown
- // blocker.
- let progress = { total: -1, current: -1 };
-
- // We're going down! Switch state so that we treat closing windows and
- // tabs correctly.
- RunState.setQuitting();
-
- if (!syncShutdown) {
- // We've got some time to shut down, so let's do this properly.
- // To prevent blocker from breaking the 60 sec limit(which will cause a
- // crash) of async shutdown during flushing all windows, we resolve the
- // promise passed to blocker once:
- // 1. the flushing exceed 50 sec, or
- // 2. 'oop-frameloader-crashed' or 'ipc:content-shutdown' is observed.
- // Thus, Firefox still can open the last session on next startup.
- AsyncShutdown.quitApplicationGranted.addBlocker(
- "SessionStore: flushing all windows",
- () => {
- var promises = [];
- promises.push(this.flushAllWindowsAsync(progress));
- promises.push(this.looseTimer(50000));
-
- var promiseOFC = new Promise(resolve => {
- Services.obs.addObserver(function obs(subject, topic) {
- Services.obs.removeObserver(obs, topic);
- resolve();
- }, "oop-frameloader-crashed", false);
- });
- promises.push(promiseOFC);
-
- var promiseICS = new Promise(resolve => {
- Services.obs.addObserver(function obs(subject, topic) {
- Services.obs.removeObserver(obs, topic);
- resolve();
- }, "ipc:content-shutdown", false);
- });
- promises.push(promiseICS);
-
- return Promise.race(promises);
- },
- () => progress);
- } else {
- // We have to shut down NOW, which means we only get to save whatever
- // we already had cached.
- }
- },
-
- /**
- * An async Task that iterates all open browser windows and flushes
- * any outstanding messages from their tabs. This will also close
- * all of the currently open windows while we wait for the flushes
- * to complete.
- *
- * @param progress (Object)
- * Optional progress object that will be updated as async
- * window flushing progresses. flushAllWindowsSync will
- * write to the following properties:
- *
- * total (int):
- * The total number of windows to be flushed.
- * current (int):
- * The current window that we're waiting for a flush on.
- *
- * @return Promise
- */
- flushAllWindowsAsync: Task.async(function*(progress={}) {
- let windowPromises = new Map();
- // We collect flush promises and close each window immediately so that
- // the user can't start changing any window state while we're waiting
- // for the flushes to finish.
- this._forEachBrowserWindow((win) => {
- windowPromises.set(win, TabStateFlusher.flushWindow(win));
-
- // We have to wait for these messages to come up from
- // each window and each browser. In the meantime, hide
- // the windows to improve perceived shutdown speed.
- let baseWin = win.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDocShell)
- .QueryInterface(Ci.nsIDocShellTreeItem)
- .treeOwner
- .QueryInterface(Ci.nsIBaseWindow);
- baseWin.visibility = false;
- });
-
- progress.total = windowPromises.size;
- progress.current = 0;
-
- // We'll iterate through the Promise array, yielding each one, so as to
- // provide useful progress information to AsyncShutdown.
- for (let [win, promise] of windowPromises) {
- yield promise;
- this._collectWindowData(win);
- progress.current++;
- };
-
- // We must cache this because _getMostRecentBrowserWindow will always
- // return null by the time quit-application occurs.
- var activeWindow = this._getMostRecentBrowserWindow();
- if (activeWindow)
- this.activeWindowSSiCache = activeWindow.__SSi || "";
- DirtyWindows.clear();
- }),
-
- /**
- * On last browser window close
- */
- onLastWindowCloseGranted: function ssi_onLastWindowCloseGranted() {
- // last browser window is quitting.
- // remember to restore the last window when another browser window is opened
- // do not account for pref(resume_session_once) at this point, as it might be
- // set by another observer getting this notice after us
- this._restoreLastWindow = true;
- },
-
- /**
- * On quitting application
- * @param aData
- * String type of quitting
- */
- onQuitApplication: function ssi_onQuitApplication(aData) {
- if (aData == "restart") {
- this._prefBranch.setBoolPref("sessionstore.resume_session_once", true);
- // The browser:purge-session-history notification fires after the
- // quit-application notification so unregister the
- // browser:purge-session-history notification to prevent clearing
- // session data on disk on a restart. It is also unnecessary to
- // perform any other sanitization processing on a restart as the
- // browser is about to exit anyway.
- Services.obs.removeObserver(this, "browser:purge-session-history");
- }
-
- if (aData != "restart") {
- // Throw away the previous session on shutdown
- LastSession.clear();
- }
-
- this._uninit();
- },
-
- /**
- * On purge of session history
- */
- onPurgeSessionHistory: function ssi_onPurgeSessionHistory() {
- SessionFile.wipe();
- // If the browser is shutting down, simply return after clearing the
- // session data on disk as this notification fires after the
- // quit-application notification so the browser is about to exit.
- if (RunState.isQuitting)
- return;
- LastSession.clear();
-
- let openWindows = {};
- // Collect open windows.
- this._forEachBrowserWindow(({__SSi: id}) => openWindows[id] = true);
-
- // also clear all data about closed tabs and windows
- for (let ix in this._windows) {
- if (ix in openWindows) {
- this._windows[ix]._closedTabs = [];
- } else {
- delete this._windows[ix];
- }
- }
- // also clear all data about closed windows
- this._closedWindows = [];
- // give the tabbrowsers a chance to clear their histories first
- var win = this._getMostRecentBrowserWindow();
- if (win) {
- win.setTimeout(() => SessionSaver.run(), 0);
- } else if (RunState.isRunning) {
- SessionSaver.run();
- }
-
- this._clearRestoringWindows();
- this._saveableClosedWindowData = new WeakSet();
- },
-
- /**
- * On purge of domain data
- * @param aData
- * String domain data
- */
- onPurgeDomainData: function ssi_onPurgeDomainData(aData) {
- // does a session history entry contain a url for the given domain?
- function containsDomain(aEntry) {
- if (Utils.hasRootDomain(aEntry.url, aData)) {
- return true;
- }
- return aEntry.children && aEntry.children.some(containsDomain, this);
- }
- // remove all closed tabs containing a reference to the given domain
- for (let ix in this._windows) {
- let closedTabs = this._windows[ix]._closedTabs;
- for (let i = closedTabs.length - 1; i >= 0; i--) {
- if (closedTabs[i].state.entries.some(containsDomain, this))
- closedTabs.splice(i, 1);
- }
- }
- // remove all open & closed tabs containing a reference to the given
- // domain in closed windows
- for (let ix = this._closedWindows.length - 1; ix >= 0; ix--) {
- let closedTabs = this._closedWindows[ix]._closedTabs;
- let openTabs = this._closedWindows[ix].tabs;
- let openTabCount = openTabs.length;
- for (let i = closedTabs.length - 1; i >= 0; i--)
- if (closedTabs[i].state.entries.some(containsDomain, this))
- closedTabs.splice(i, 1);
- for (let j = openTabs.length - 1; j >= 0; j--) {
- if (openTabs[j].entries.some(containsDomain, this)) {
- openTabs.splice(j, 1);
- if (this._closedWindows[ix].selected > j)
- this._closedWindows[ix].selected--;
- }
- }
- if (openTabs.length == 0) {
- this._closedWindows.splice(ix, 1);
- }
- else if (openTabs.length != openTabCount) {
- // Adjust the window's title if we removed an open tab
- let selectedTab = openTabs[this._closedWindows[ix].selected - 1];
- // some duplication from restoreHistory - make sure we get the correct title
- let activeIndex = (selectedTab.index || selectedTab.entries.length) - 1;
- if (activeIndex >= selectedTab.entries.length)
- activeIndex = selectedTab.entries.length - 1;
- this._closedWindows[ix].title = selectedTab.entries[activeIndex].title;
- }
- }
-
- if (RunState.isRunning) {
- SessionSaver.run();
- }
-
- this._clearRestoringWindows();
- },
-
- /**
- * On preference change
- * @param aData
- * String preference changed
- */
- onPrefChange: function ssi_onPrefChange(aData) {
- switch (aData) {
- // if the user decreases the max number of closed tabs they want
- // preserved update our internal states to match that max
- case "sessionstore.max_tabs_undo":
- this._max_tabs_undo = this._prefBranch.getIntPref("sessionstore.max_tabs_undo");
- for (let ix in this._windows) {
- this._windows[ix]._closedTabs.splice(this._max_tabs_undo, this._windows[ix]._closedTabs.length);
- }
- break;
- case "sessionstore.max_windows_undo":
- this._max_windows_undo = this._prefBranch.getIntPref("sessionstore.max_windows_undo");
- this._capClosedWindows();
- break;
- }
- },
-
- /**
- * save state when new tab is added
- * @param aWindow
- * Window reference
- */
- onTabAdd: function ssi_onTabAdd(aWindow) {
- this.saveStateDelayed(aWindow);
- },
-
- /**
- * set up listeners for a new tab
- * @param aWindow
- * Window reference
- * @param aTab
- * Tab reference
- */
- onTabBrowserInserted: function ssi_onTabBrowserInserted(aWindow, aTab) {
- let browser = aTab.linkedBrowser;
- browser.addEventListener("SwapDocShells", this);
- browser.addEventListener("oop-browser-crashed", this);
-
- if (browser.frameLoader) {
- this._lastKnownFrameLoader.set(browser.permanentKey, browser.frameLoader);
- }
- },
-
- /**
- * remove listeners for a tab
- * @param aWindow
- * Window reference
- * @param aTab
- * Tab reference
- * @param aNoNotification
- * bool Do not save state if we're updating an existing tab
- */
- onTabRemove: function ssi_onTabRemove(aWindow, aTab, aNoNotification) {
- let browser = aTab.linkedBrowser;
- browser.removeEventListener("SwapDocShells", this);
- browser.removeEventListener("oop-browser-crashed", this);
-
- // If this tab was in the middle of restoring or still needs to be restored,
- // we need to reset that state. If the tab was restoring, we will attempt to
- // restore the next tab.
- let previousState = browser.__SS_restoreState;
- if (previousState) {
- this._resetTabRestoringState(aTab);
- if (previousState == TAB_STATE_RESTORING)
- this.restoreNextTab();
- }
-
- if (!aNoNotification) {
- this.saveStateDelayed(aWindow);
- }
- },
-
- /**
- * When a tab closes, collect its properties
- * @param aWindow
- * Window reference
- * @param aTab
- * Tab reference
- */
- onTabClose: function ssi_onTabClose(aWindow, aTab) {
- // notify the tabbrowser that the tab state will be retrieved for the last time
- // (so that extension authors can easily set data on soon-to-be-closed tabs)
- var event = aWindow.document.createEvent("Events");
- event.initEvent("SSTabClosing", true, false);
- aTab.dispatchEvent(event);
-
- // don't update our internal state if we don't have to
- if (this._max_tabs_undo == 0) {
- return;
- }
-
- // Get the latest data for this tab (generally, from the cache)
- let tabState = TabState.collect(aTab);
-
- // Don't save private tabs
- let isPrivateWindow = PrivateBrowsingUtils.isWindowPrivate(aWindow);
- if (!isPrivateWindow && tabState.isPrivate) {
- return;
- }
-
- // Store closed-tab data for undo.
- let tabbrowser = aWindow.gBrowser;
- let tabTitle = this._replaceLoadingTitle(aTab.label, tabbrowser, aTab);
- let {permanentKey} = aTab.linkedBrowser;
-
- let tabData = {
- permanentKey,
- state: tabState,
- title: tabTitle,
- image: tabbrowser.getIcon(aTab),
- iconLoadingPrincipal: Utils.serializePrincipal(aTab.linkedBrowser.contentPrincipal),
- pos: aTab._tPos,
- closedAt: Date.now()
- };
-
- let closedTabs = this._windows[aWindow.__SSi]._closedTabs;
-
- // Determine whether the tab contains any information worth saving. Note
- // that there might be pending state changes queued in the child that
- // didn't reach the parent yet. If a tab is emptied before closing then we
- // might still remove it from the list of closed tabs later.
- if (this._shouldSaveTabState(tabState)) {
- // Save the tab state, for now. We might push a valid tab out
- // of the list but those cases should be extremely rare and
- // do probably never occur when using the browser normally.
- // (Tests or add-ons might do weird things though.)
- this.saveClosedTabData(closedTabs, tabData);
- }
-
- // Remember the closed tab to properly handle any last updates included in
- // the final "update" message sent by the frame script's unload handler.
- this._closedTabs.set(permanentKey, {closedTabs, tabData});
- },
-
- /**
- * Insert a given |tabData| object into the list of |closedTabs|. We will
- * determine the right insertion point based on the .closedAt properties of
- * all tabs already in the list. The list will be truncated to contain a
- * maximum of |this._max_tabs_undo| entries.
- *
- * @param closedTabs (array)
- * The list of closed tabs for a window.
- * @param tabData (object)
- * The tabData to be inserted.
- */
- saveClosedTabData(closedTabs, tabData) {
- // Find the index of the first tab in the list
- // of closed tabs that was closed before our tab.
- let index = closedTabs.findIndex(tab => {
- return tab.closedAt < tabData.closedAt;
- });
-
- // If we found no tab closed before our
- // tab then just append it to the list.
- if (index == -1) {
- index = closedTabs.length;
- }
-
- // About to save the closed tab, add a unique ID.
- tabData.closedId = this._nextClosedId++;
-
- // Insert tabData at the right position.
- closedTabs.splice(index, 0, tabData);
-
- // Truncate the list of closed tabs, if needed.
- if (closedTabs.length > this._max_tabs_undo) {
- closedTabs.splice(this._max_tabs_undo, closedTabs.length);
- }
- },
-
- /**
- * Remove the closed tab data at |index| from the list of |closedTabs|. If
- * the tab's final message is still pending we will simply discard it when
- * it arrives so that the tab doesn't reappear in the list.
- *
- * @param closedTabs (array)
- * The list of closed tabs for a window.
- * @param index (uint)
- * The index of the tab to remove.
- */
- removeClosedTabData(closedTabs, index) {
- // Remove the given index from the list.
- let [closedTab] = closedTabs.splice(index, 1);
-
- // If the closed tab's state still has a .permanentKey property then we
- // haven't seen its final update message yet. Remove it from the map of
- // closed tabs so that we will simply discard its last messages and will
- // not add it back to the list of closed tabs again.
- if (closedTab.permanentKey) {
- this._closedTabs.delete(closedTab.permanentKey);
- this._closedWindowTabs.delete(closedTab.permanentKey);
- delete closedTab.permanentKey;
- }
-
- return closedTab;
- },
-
- /**
- * When a tab is selected, save session data
- * @param aWindow
- * Window reference
- */
- onTabSelect: function ssi_onTabSelect(aWindow) {
- if (RunState.isRunning) {
- this._windows[aWindow.__SSi].selected = aWindow.gBrowser.tabContainer.selectedIndex;
-
- let tab = aWindow.gBrowser.selectedTab;
- let browser = tab.linkedBrowser;
-
- if (browser.__SS_restoreState &&
- browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
- // If __SS_restoreState is still on the browser and it is
- // TAB_STATE_NEEDS_RESTORE, then then we haven't restored
- // this tab yet.
- //
- // It's possible that this tab was recently revived, and that
- // we've deferred showing the tab crashed page for it (if the
- // tab crashed in the background). If so, we need to re-enter
- // the crashed state, since we'll be showing the tab crashed
- // page.
- if (TabCrashHandler.willShowCrashedTab(browser)) {
- this.enterCrashedState(browser);
- } else {
- this.restoreTabContent(tab);
- }
- }
- }
- },
-
- onTabShow: function ssi_onTabShow(aWindow, aTab) {
- // If the tab hasn't been restored yet, move it into the right bucket
- if (aTab.linkedBrowser.__SS_restoreState &&
- aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
- TabRestoreQueue.hiddenToVisible(aTab);
-
- // let's kick off tab restoration again to ensure this tab gets restored
- // with "restore_hidden_tabs" == false (now that it has become visible)
- this.restoreNextTab();
- }
-
- // Default delay of 2 seconds gives enough time to catch multiple TabShow
- // events. This used to be due to changing groups in 'tab groups'. We
- // might be able to get rid of this now?
- this.saveStateDelayed(aWindow);
- },
-
- onTabHide: function ssi_onTabHide(aWindow, aTab) {
- // If the tab hasn't been restored yet, move it into the right bucket
- if (aTab.linkedBrowser.__SS_restoreState &&
- aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
- TabRestoreQueue.visibleToHidden(aTab);
- }
-
- // Default delay of 2 seconds gives enough time to catch multiple TabHide
- // events. This used to be due to changing groups in 'tab groups'. We
- // might be able to get rid of this now?
- this.saveStateDelayed(aWindow);
- },
-
- /**
- * Handler for the event that is fired when a <xul:browser> crashes.
- *
- * @param aWindow
- * The window that the crashed browser belongs to.
- * @param aBrowser
- * The <xul:browser> that is now in the crashed state.
- */
- onBrowserCrashed: function(aBrowser) {
- NS_ASSERT(aBrowser.isRemoteBrowser,
- "Only remote browsers should be able to crash");
-
- this.enterCrashedState(aBrowser);
- // The browser crashed so we might never receive flush responses.
- // Resolve all pending flush requests for the crashed browser.
- TabStateFlusher.resolveAll(aBrowser);
- },
-
- /**
- * Called when a browser is showing or is about to show the tab
- * crashed page. This method causes SessionStore to ignore the
- * tab until it's restored.
- *
- * @param browser
- * The <xul:browser> that is about to show the crashed page.
- */
- enterCrashedState(browser) {
- this._crashedBrowsers.add(browser.permanentKey);
-
- let win = browser.ownerGlobal;
-
- // If we hadn't yet restored, or were still in the midst of
- // restoring this browser at the time of the crash, we need
- // to reset its state so that we can try to restore it again
- // when the user revives the tab from the crash.
- if (browser.__SS_restoreState) {
- let tab = win.gBrowser.getTabForBrowser(browser);
- this._resetLocalTabRestoringState(tab);
- }
- },
-
- // Clean up data that has been closed a long time ago.
- // Do not reschedule a save. This will wait for the next regular
- // save.
- onIdleDaily: function() {
- // Remove old closed windows
- this._cleanupOldData([this._closedWindows]);
-
- // Remove closed tabs of closed windows
- this._cleanupOldData(this._closedWindows.map((winData) => winData._closedTabs));
-
- // Remove closed tabs of open windows
- this._cleanupOldData(Object.keys(this._windows).map((key) => this._windows[key]._closedTabs));
- },
-
- // Remove "old" data from an array
- _cleanupOldData: function(targets) {
- const TIME_TO_LIVE = this._prefBranch.getIntPref("sessionstore.cleanup.forget_closed_after");
- const now = Date.now();
-
- for (let array of targets) {
- for (let i = array.length - 1; i >= 0; --i) {
- let data = array[i];
- // Make sure that we have a timestamp to tell us when the target
- // has been closed. If we don't have a timestamp, default to a
- // safe timestamp: just now.
- data.closedAt = data.closedAt || now;
- if (now - data.closedAt > TIME_TO_LIVE) {
- array.splice(i, 1);
- }
- }
- }
- },
-
- /* ........ nsISessionStore API .............. */
-
- getBrowserState: function ssi_getBrowserState() {
- let state = this.getCurrentState();
-
- // Don't include the last session state in getBrowserState().
- delete state.lastSessionState;
-
- // Don't include any deferred initial state.
- delete state.deferredInitialState;
-
- return JSON.stringify(state);
- },
-
- setBrowserState: function ssi_setBrowserState(aState) {
- this._handleClosedWindows();
-
- try {
- var state = JSON.parse(aState);
- }
- catch (ex) { /* invalid state object - don't restore anything */ }
- if (!state) {
- throw Components.Exception("Invalid state string: not JSON", Cr.NS_ERROR_INVALID_ARG);
- }
- if (!state.windows) {
- throw Components.Exception("No windows", Cr.NS_ERROR_INVALID_ARG);
- }
-
- this._browserSetState = true;
-
- // Make sure the priority queue is emptied out
- this._resetRestoringState();
-
- var window = this._getMostRecentBrowserWindow();
- if (!window) {
- this._restoreCount = 1;
- this._openWindowWithState(state);
- return;
- }
-
- // close all other browser windows
- this._forEachBrowserWindow(function(aWindow) {
- if (aWindow != window) {
- aWindow.close();
- this.onClose(aWindow);
- }
- });
-
- // make sure closed window data isn't kept
- this._closedWindows = [];
-
- // determine how many windows are meant to be restored
- this._restoreCount = state.windows ? state.windows.length : 0;
-
- // global data must be restored before restoreWindow is called so that
- // it happens before observers are notified
- this._globalState.setFromState(state);
-
- // restore to the given state
- this.restoreWindows(window, state, {overwriteTabs: true});
- },
-
- getWindowState: function ssi_getWindowState(aWindow) {
- if ("__SSi" in aWindow) {
- return JSON.stringify(this._getWindowState(aWindow));
- }
-
- if (DyingWindowCache.has(aWindow)) {
- let data = DyingWindowCache.get(aWindow);
- return JSON.stringify({ windows: [data] });
- }
-
- throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
- },
-
- setWindowState: function ssi_setWindowState(aWindow, aState, aOverwrite) {
- if (!aWindow.__SSi) {
- throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
- }
-
- this.restoreWindows(aWindow, aState, {overwriteTabs: aOverwrite});
- },
-
- getTabState: function ssi_getTabState(aTab) {
- if (!aTab.ownerGlobal.__SSi) {
- throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
- }
-
- let tabState = TabState.collect(aTab);
-
- return JSON.stringify(tabState);
- },
-
- setTabState(aTab, aState) {
- // Remove the tab state from the cache.
- // Note that we cannot simply replace the contents of the cache
- // as |aState| can be an incomplete state that will be completed
- // by |restoreTabs|.
- let tabState = JSON.parse(aState);
- if (!tabState) {
- throw Components.Exception("Invalid state string: not JSON", Cr.NS_ERROR_INVALID_ARG);
- }
- if (typeof tabState != "object") {
- throw Components.Exception("Not an object", Cr.NS_ERROR_INVALID_ARG);
- }
- if (!("entries" in tabState)) {
- throw Components.Exception("Invalid state object: no entries", Cr.NS_ERROR_INVALID_ARG);
- }
-
- let window = aTab.ownerGlobal;
- if (!("__SSi" in window)) {
- throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
- }
-
- if (aTab.linkedBrowser.__SS_restoreState) {
- this._resetTabRestoringState(aTab);
- }
-
- this.restoreTab(aTab, tabState);
- },
-
- duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta = 0, aRestoreImmediately = true) {
- if (!aTab.ownerGlobal.__SSi) {
- throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
- }
- if (!aWindow.gBrowser) {
- throw Components.Exception("Invalid window object: no gBrowser", Cr.NS_ERROR_INVALID_ARG);
- }
-
- // Create a new tab.
- let userContextId = aTab.getAttribute("usercontextid");
- let newTab = aTab == aWindow.gBrowser.selectedTab ?
- aWindow.gBrowser.addTab(null, {relatedToCurrent: true, ownerTab: aTab, userContextId}) :
- aWindow.gBrowser.addTab(null, {userContextId});
-
- // Set tab title to "Connecting..." and start the throbber to pretend we're
- // doing something while actually waiting for data from the frame script.
- aWindow.gBrowser.setTabTitleLoading(newTab);
- newTab.setAttribute("busy", "true");
-
- // Collect state before flushing.
- let tabState = TabState.clone(aTab);
-
- // Flush to get the latest tab state to duplicate.
- let browser = aTab.linkedBrowser;
- TabStateFlusher.flush(browser).then(() => {
- // The new tab might have been closed in the meantime.
- if (newTab.closing || !newTab.linkedBrowser) {
- return;
- }
-
- let window = newTab.ownerGlobal;
-
- // The tab or its window might be gone.
- if (!window || !window.__SSi) {
- return;
- }
-
- // Update state with flushed data. We can't use TabState.clone() here as
- // the tab to duplicate may have already been closed. In that case we
- // only have access to the <xul:browser>.
- let options = {includePrivateData: true};
- TabState.copyFromCache(browser, tabState, options);
-
- tabState.index += aDelta;
- tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
- tabState.pinned = false;
-
- // Restore the state into the new tab.
- this.restoreTab(newTab, tabState, {
- restoreImmediately: aRestoreImmediately
- });
- });
-
- return newTab;
- },
-
- getClosedTabCount: function ssi_getClosedTabCount(aWindow) {
- if ("__SSi" in aWindow) {
- return this._windows[aWindow.__SSi]._closedTabs.length;
- }
-
- if (!DyingWindowCache.has(aWindow)) {
- throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
- }
-
- return DyingWindowCache.get(aWindow)._closedTabs.length;
- },
-
- getClosedTabData: function ssi_getClosedTabData(aWindow, aAsString = true) {
- if ("__SSi" in aWindow) {
- return aAsString ?
- JSON.stringify(this._windows[aWindow.__SSi]._closedTabs) :
- Cu.cloneInto(this._windows[aWindow.__SSi]._closedTabs, {});
- }
-
- if (!DyingWindowCache.has(aWindow)) {
- throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
- }
-
- let data = DyingWindowCache.get(aWindow);
- return aAsString ? JSON.stringify(data._closedTabs) : Cu.cloneInto(data._closedTabs, {});
- },
-
- undoCloseTab: function ssi_undoCloseTab(aWindow, aIndex) {
- if (!aWindow.__SSi) {
- throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
- }
-
- var closedTabs = this._windows[aWindow.__SSi]._closedTabs;
-
- // default to the most-recently closed tab
- aIndex = aIndex || 0;
- if (!(aIndex in closedTabs)) {
- throw Components.Exception("Invalid index: not in the closed tabs", Cr.NS_ERROR_INVALID_ARG);
- }
-
- // fetch the data of closed tab, while removing it from the array
- let {state, pos} = this.removeClosedTabData(closedTabs, aIndex);
-
- // create a new tab
- let tabbrowser = aWindow.gBrowser;
- let tab = tabbrowser.selectedTab = tabbrowser.addTab(null, state);
-
- // restore tab content
- this.restoreTab(tab, state);
-
- // restore the tab's position
- tabbrowser.moveTabTo(tab, pos);
-
- // focus the tab's content area (bug 342432)
- tab.linkedBrowser.focus();
-
- return tab;
- },
-
- forgetClosedTab: function ssi_forgetClosedTab(aWindow, aIndex) {
- if (!aWindow.__SSi) {
- throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
- }
-
- var closedTabs = this._windows[aWindow.__SSi]._closedTabs;
-
- // default to the most-recently closed tab
- aIndex = aIndex || 0;
- if (!(aIndex in closedTabs)) {
- throw Components.Exception("Invalid index: not in the closed tabs", Cr.NS_ERROR_INVALID_ARG);
- }
-
- // remove closed tab from the array
- this.removeClosedTabData(closedTabs, aIndex);
- },
-
- getClosedWindowCount: function ssi_getClosedWindowCount() {
- return this._closedWindows.length;
- },
-
- getClosedWindowData: function ssi_getClosedWindowData(aAsString = true) {
- return aAsString ? JSON.stringify(this._closedWindows) : Cu.cloneInto(this._closedWindows, {});
- },
-
- undoCloseWindow: function ssi_undoCloseWindow(aIndex) {
- if (!(aIndex in this._closedWindows)) {
- throw Components.Exception("Invalid index: not in the closed windows", Cr.NS_ERROR_INVALID_ARG);
- }
-
- // reopen the window
- let state = { windows: this._closedWindows.splice(aIndex, 1) };
- delete state.windows[0].closedAt; // Window is now open.
-
- let window = this._openWindowWithState(state);
- this.windowToFocus = window;
- return window;
- },
-
- forgetClosedWindow: function ssi_forgetClosedWindow(aIndex) {
- // default to the most-recently closed window
- aIndex = aIndex || 0;
- if (!(aIndex in this._closedWindows)) {
- throw Components.Exception("Invalid index: not in the closed windows", Cr.NS_ERROR_INVALID_ARG);
- }
-
- // remove closed window from the array
- let winData = this._closedWindows[aIndex];
- this._closedWindows.splice(aIndex, 1);
- this._saveableClosedWindowData.delete(winData);
- },
-
- getWindowValue: function ssi_getWindowValue(aWindow, aKey) {
- if ("__SSi" in aWindow) {
- var data = this._windows[aWindow.__SSi].extData || {};
- return data[aKey] || "";
- }
-
- if (DyingWindowCache.has(aWindow)) {
- let data = DyingWindowCache.get(aWindow).extData || {};
- return data[aKey] || "";
- }
-
- throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
- },
-
- setWindowValue: function ssi_setWindowValue(aWindow, aKey, aStringValue) {
- if (typeof aStringValue != "string") {
- throw new TypeError("setWindowValue only accepts string values");
- }
-
- if (!("__SSi" in aWindow)) {
- throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
- }
- if (!this._windows[aWindow.__SSi].extData) {
- this._windows[aWindow.__SSi].extData = {};
- }
- this._windows[aWindow.__SSi].extData[aKey] = aStringValue;
- this.saveStateDelayed(aWindow);
- },
-
- deleteWindowValue: function ssi_deleteWindowValue(aWindow, aKey) {
- if (aWindow.__SSi && this._windows[aWindow.__SSi].extData &&
- this._windows[aWindow.__SSi].extData[aKey])
- delete this._windows[aWindow.__SSi].extData[aKey];
- this.saveStateDelayed(aWindow);
- },
-
- getTabValue: function ssi_getTabValue(aTab, aKey) {
- return (aTab.__SS_extdata || {})[aKey] || "";
- },
-
- setTabValue: function ssi_setTabValue(aTab, aKey, aStringValue) {
- if (typeof aStringValue != "string") {
- throw new TypeError("setTabValue only accepts string values");
- }
-
- // If the tab hasn't been restored, then set the data there, otherwise we
- // could lose newly added data.
- if (!aTab.__SS_extdata) {
- aTab.__SS_extdata = {};
- }
-
- aTab.__SS_extdata[aKey] = aStringValue;
- this.saveStateDelayed(aTab.ownerGlobal);
- },
-
- deleteTabValue: function ssi_deleteTabValue(aTab, aKey) {
- if (aTab.__SS_extdata && aKey in aTab.__SS_extdata) {
- delete aTab.__SS_extdata[aKey];
- this.saveStateDelayed(aTab.ownerGlobal);
- }
- },
-
- getGlobalValue: function ssi_getGlobalValue(aKey) {
- return this._globalState.get(aKey);
- },
-
- setGlobalValue: function ssi_setGlobalValue(aKey, aStringValue) {
- if (typeof aStringValue != "string") {
- throw new TypeError("setGlobalValue only accepts string values");
- }
-
- this._globalState.set(aKey, aStringValue);
- this.saveStateDelayed();
- },
-
- deleteGlobalValue: function ssi_deleteGlobalValue(aKey) {
- this._globalState.delete(aKey);
- this.saveStateDelayed();
- },
-
- persistTabAttribute: function ssi_persistTabAttribute(aName) {
- if (TabAttributes.persist(aName)) {
- this.saveStateDelayed();
- }
- },
-
-
- /**
- * Undoes the closing of a tab or window which corresponds
- * to the closedId passed in.
- *
- * @param aClosedId
- * The closedId of the tab or window
- *
- * @returns a tab or window object
- */
- undoCloseById(aClosedId) {
- // Check for a window first.
- for (let i = 0, l = this._closedWindows.length; i < l; i++) {
- if (this._closedWindows[i].closedId == aClosedId) {
- return this.undoCloseWindow(i);
- }
- }
-
- // Check for a tab.
- let windowsEnum = Services.wm.getEnumerator("navigator:browser");
- while (windowsEnum.hasMoreElements()) {
- let window = windowsEnum.getNext();
- let windowState = this._windows[window.__SSi];
- if (windowState) {
- for (let j = 0, l = windowState._closedTabs.length; j < l; j++) {
- if (windowState._closedTabs[j].closedId == aClosedId) {
- return this.undoCloseTab(window, j);
- }
- }
- }
- }
-
- // Neither a tab nor a window was found, return undefined and let the caller decide what to do about it.
- return undefined;
- },
-
- /**
- * Restores the session state stored in LastSession. This will attempt
- * to merge data into the current session. If a window was opened at startup
- * with pinned tab(s), then the remaining data from the previous session for
- * that window will be opened into that window. Otherwise new windows will
- * be opened.
- */
- restoreLastSession: function ssi_restoreLastSession() {
- // Use the public getter since it also checks PB mode
- if (!this.canRestoreLastSession) {
- throw Components.Exception("Last session can not be restored");
- }
-
- Services.obs.notifyObservers(null, NOTIFY_INITIATING_MANUAL_RESTORE, "");
-
- // First collect each window with its id...
- let windows = {};
- this._forEachBrowserWindow(function(aWindow) {
- if (aWindow.__SS_lastSessionWindowID)
- windows[aWindow.__SS_lastSessionWindowID] = aWindow;
- });
-
- let lastSessionState = LastSession.getState();
-
- // This shouldn't ever be the case...
- if (!lastSessionState.windows.length) {
- throw Components.Exception("lastSessionState has no windows", Cr.NS_ERROR_UNEXPECTED);
- }
-
- // We're technically doing a restore, so set things up so we send the
- // notification when we're done. We want to send "sessionstore-browser-state-restored".
- this._restoreCount = lastSessionState.windows.length;
- this._browserSetState = true;
-
- // We want to re-use the last opened window instead of opening a new one in
- // the case where it's "empty" and not associated with a window in the session.
- // We will do more processing via _prepWindowToRestoreInto if we need to use
- // the lastWindow.
- let lastWindow = this._getMostRecentBrowserWindow();
- let canUseLastWindow = lastWindow &&
- !lastWindow.__SS_lastSessionWindowID;
-
- // global data must be restored before restoreWindow is called so that
- // it happens before observers are notified
- this._globalState.setFromState(lastSessionState);
-
- // Restore into windows or open new ones as needed.
- for (let i = 0; i < lastSessionState.windows.length; i++) {
- let winState = lastSessionState.windows[i];
- let lastSessionWindowID = winState.__lastSessionWindowID;
- // delete lastSessionWindowID so we don't add that to the window again
- delete winState.__lastSessionWindowID;
-
- // See if we can use an open window. First try one that is associated with
- // the state we're trying to restore and then fallback to the last selected
- // window.
- let windowToUse = windows[lastSessionWindowID];
- if (!windowToUse && canUseLastWindow) {
- windowToUse = lastWindow;
- canUseLastWindow = false;
- }
-
- let [canUseWindow, canOverwriteTabs] = this._prepWindowToRestoreInto(windowToUse);
-
- // If there's a window already open that we can restore into, use that
- if (canUseWindow) {
- // Since we're not overwriting existing tabs, we want to merge _closedTabs,
- // putting existing ones first. Then make sure we're respecting the max pref.
- if (winState._closedTabs && winState._closedTabs.length) {
- let curWinState = this._windows[windowToUse.__SSi];
- curWinState._closedTabs = curWinState._closedTabs.concat(winState._closedTabs);
- curWinState._closedTabs.splice(this._max_tabs_undo, curWinState._closedTabs.length);
- }
-
- // Restore into that window - pretend it's a followup since we'll already
- // have a focused window.
- //XXXzpao This is going to merge extData together (taking what was in
- // winState over what is in the window already.
- let options = {overwriteTabs: canOverwriteTabs, isFollowUp: true};
- this.restoreWindow(windowToUse, winState, options);
- }
- else {
- this._openWindowWithState({ windows: [winState] });
- }
- }
-
- // Merge closed windows from this session with ones from last session
- if (lastSessionState._closedWindows) {
- this._closedWindows = this._closedWindows.concat(lastSessionState._closedWindows);
- this._capClosedWindows();
- }
-
- // Scratchpad
- if (lastSessionState.scratchpads) {
- ScratchpadManager.restoreSession(lastSessionState.scratchpads);
- }
-
- // The Browser Console
- if (lastSessionState.browserConsole) {
- HUDService.restoreBrowserConsoleSession();
- }
-
- // Set data that persists between sessions
- this._recentCrashes = lastSessionState.session &&
- lastSessionState.session.recentCrashes || 0;
-
- // Update the session start time using the restored session state.
- this._updateSessionStartTime(lastSessionState);
-
- LastSession.clear();
- },
-
- /**
- * Revive a crashed tab and restore its state from before it crashed.
- *
- * @param aTab
- * A <xul:tab> linked to a crashed browser. This is a no-op if the
- * browser hasn't actually crashed, or is not associated with a tab.
- * This function will also throw if the browser happens to be remote.
- */
- reviveCrashedTab(aTab) {
- if (!aTab) {
- throw new Error("SessionStore.reviveCrashedTab expected a tab, but got null.");
- }
-
- let browser = aTab.linkedBrowser;
- if (!this._crashedBrowsers.has(browser.permanentKey)) {
- return;
- }
-
- // Sanity check - the browser to be revived should not be remote
- // at this point.
- if (browser.isRemoteBrowser) {
- throw new Error("SessionStore.reviveCrashedTab: " +
- "Somehow a crashed browser is still remote.")
- }
-
- // We put the browser at about:blank in case the user is
- // restoring tabs on demand. This way, the user won't see
- // a flash of the about:tabcrashed page after selecting
- // the revived tab.
- aTab.removeAttribute("crashed");
- browser.loadURI("about:blank", null, null);
-
- let data = TabState.collect(aTab);
- this.restoreTab(aTab, data, {
- forceOnDemand: true,
- });
- },
-
- /**
- * Revive all crashed tabs and reset the crashed tabs count to 0.
- */
- reviveAllCrashedTabs() {
- let windowsEnum = Services.wm.getEnumerator("navigator:browser");
- while (windowsEnum.hasMoreElements()) {
- let window = windowsEnum.getNext();
- for (let tab of window.gBrowser.tabs) {
- this.reviveCrashedTab(tab);
- }
- }
- },
-
- /**
- * Navigate the given |tab| by first collecting its current state and then
- * either changing only the index of the currently shown history entry,
- * or restoring the exact same state again and passing the new URL to load
- * in |loadArguments|. Use this method to seamlessly switch between pages
- * loaded in the parent and pages loaded in the child process.
- *
- * This method might be called multiple times before it has finished
- * flushing the browser tab. If that occurs, the loadArguments from
- * the most recent call to navigateAndRestore will be used once the
- * flush has finished.
- */
- navigateAndRestore(tab, loadArguments, historyIndex) {
- let window = tab.ownerGlobal;
- NS_ASSERT(window.__SSi, "tab's window must be tracked");
- let browser = tab.linkedBrowser;
-
- // Were we already waiting for a flush from a previous call to
- // navigateAndRestore on this tab?
- let alreadyRestoring =
- this._remotenessChangingBrowsers.has(browser.permanentKey);
-
- // Stash the most recent loadArguments in this WeakMap so that
- // we know to use it when the TabStateFlusher.flush resolves.
- this._remotenessChangingBrowsers.set(browser.permanentKey, loadArguments);
-
- if (alreadyRestoring) {
- // This tab was already being restored to run in the
- // correct process. We're done here.
- return;
- }
-
- // Set tab title to "Connecting..." and start the throbber to pretend we're
- // doing something while actually waiting for data from the frame script.
- window.gBrowser.setTabTitleLoading(tab);
- tab.setAttribute("busy", "true");
-
- // Flush to get the latest tab state.
- TabStateFlusher.flush(browser).then(() => {
- // loadArguments might have been overwritten by multiple calls
- // to navigateAndRestore while we waited for the tab to flush,
- // so we use the most recently stored one.
- let recentLoadArguments =
- this._remotenessChangingBrowsers.get(browser.permanentKey);
- this._remotenessChangingBrowsers.delete(browser.permanentKey);
-
- // The tab might have been closed/gone in the meantime.
- if (tab.closing || !tab.linkedBrowser) {
- return;
- }
-
- let window = tab.ownerGlobal;
-
- // The tab or its window might be gone.
- if (!window || !window.__SSi || window.closed) {
- return;
- }
-
- let tabState = TabState.clone(tab);
- let options = {
- restoreImmediately: true,
- // We want to make sure that this information is passed to restoreTab
- // whether or not a historyIndex is passed in. Thus, we extract it from
- // the loadArguments.
- reloadInFreshProcess: !!recentLoadArguments.reloadInFreshProcess,
- };
-
- if (historyIndex >= 0) {
- tabState.index = historyIndex + 1;
- tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
- } else {
- options.loadArguments = recentLoadArguments;
- }
-
- // Need to reset restoring tabs.
- if (tab.linkedBrowser.__SS_restoreState) {
- this._resetLocalTabRestoringState(tab);
- }
-
- // Restore the state into the tab.
- this.restoreTab(tab, tabState, options);
- });
-
- tab.linkedBrowser.__SS_restoreState = TAB_STATE_WILL_RESTORE;
- },
-
- /**
- * Retrieves the latest session history information for a tab. The cached data
- * is returned immediately, but a callback may be provided that supplies
- * up-to-date data when or if it is available. The callback is passed a single
- * argument with data in the same format as the return value.
- *
- * @param tab tab to retrieve the session history for
- * @param updatedCallback function to call with updated data as the single argument
- * @returns a object containing 'index' specifying the current index, and an
- * array 'entries' containing an object for each history item.
- */
- getSessionHistory(tab, updatedCallback) {
- if (updatedCallback) {
- TabStateFlusher.flush(tab.linkedBrowser).then(() => {
- let sessionHistory = this.getSessionHistory(tab);
- if (sessionHistory) {
- updatedCallback(sessionHistory);
- }
- });
- }
-
- // Don't continue if the tab was closed before TabStateFlusher.flush resolves.
- if (tab.linkedBrowser) {
- let tabState = TabState.collect(tab);
- return { index: tabState.index - 1, entries: tabState.entries }
- }
- },
-
- /**
- * See if aWindow is usable for use when restoring a previous session via
- * restoreLastSession. If usable, prepare it for use.
- *
- * @param aWindow
- * the window to inspect & prepare
- * @returns [canUseWindow, canOverwriteTabs]
- * canUseWindow: can the window be used to restore into
- * canOverwriteTabs: all of the current tabs are home pages and we
- * can overwrite them
- */
- _prepWindowToRestoreInto: function ssi_prepWindowToRestoreInto(aWindow) {
- if (!aWindow)
- return [false, false];
-
- // We might be able to overwrite the existing tabs instead of just adding
- // the previous session's tabs to the end. This will be set if possible.
- let canOverwriteTabs = false;
-
- // Look at the open tabs in comparison to home pages. If all the tabs are
- // home pages then we'll end up overwriting all of them. Otherwise we'll
- // just close the tabs that match home pages. Tabs with the about:blank
- // URI will always be overwritten.
- let homePages = ["about:blank"];
- let removableTabs = [];
- let tabbrowser = aWindow.gBrowser;
- let normalTabsLen = tabbrowser.tabs.length - tabbrowser._numPinnedTabs;
- let startupPref = this._prefBranch.getIntPref("startup.page");
- if (startupPref == 1)
- homePages = homePages.concat(aWindow.gHomeButton.getHomePage().split("|"));
-
- for (let i = tabbrowser._numPinnedTabs; i < tabbrowser.tabs.length; i++) {
- let tab = tabbrowser.tabs[i];
- if (homePages.indexOf(tab.linkedBrowser.currentURI.spec) != -1) {
- removableTabs.push(tab);
- }
- }
-
- if (tabbrowser.tabs.length == removableTabs.length) {
- canOverwriteTabs = true;
- }
- else {
- // If we're not overwriting all of the tabs, then close the home tabs.
- for (let i = removableTabs.length - 1; i >= 0; i--) {
- tabbrowser.removeTab(removableTabs.pop(), { animate: false });
- }
- }
-
- return [true, canOverwriteTabs];
- },
-
- /* ........ Saving Functionality .............. */
-
- /**
- * Store window dimensions, visibility, sidebar
- * @param aWindow
- * Window reference
- */
- _updateWindowFeatures: function ssi_updateWindowFeatures(aWindow) {
- var winData = this._windows[aWindow.__SSi];
-
- WINDOW_ATTRIBUTES.forEach(function(aAttr) {
- winData[aAttr] = this._getWindowDimension(aWindow, aAttr);
- }, this);
-
- var hidden = WINDOW_HIDEABLE_FEATURES.filter(function(aItem) {
- return aWindow[aItem] && !aWindow[aItem].visible;
- });
- if (hidden.length != 0)
- winData.hidden = hidden.join(",");
- else if (winData.hidden)
- delete winData.hidden;
-
- var sidebar = aWindow.document.getElementById("sidebar-box").getAttribute("sidebarcommand");
- if (sidebar)
- winData.sidebar = sidebar;
- else if (winData.sidebar)
- delete winData.sidebar;
- },
-
- /**
- * gather session data as object
- * @param aUpdateAll
- * Bool update all windows
- * @returns object
- */
- getCurrentState: function (aUpdateAll) {
- this._handleClosedWindows();
-
- var activeWindow = this._getMostRecentBrowserWindow();
-
- TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_ALL_WINDOWS_DATA_MS");
- if (RunState.isRunning) {
- // update the data for all windows with activities since the last save operation
- this._forEachBrowserWindow(function(aWindow) {
- if (!this._isWindowLoaded(aWindow)) // window data is still in _statesToRestore
- return;
- if (aUpdateAll || DirtyWindows.has(aWindow) || aWindow == activeWindow) {
- this._collectWindowData(aWindow);
- }
- else { // always update the window features (whose change alone never triggers a save operation)
- this._updateWindowFeatures(aWindow);
- }
- });
- DirtyWindows.clear();
- }
- TelemetryStopwatch.finish("FX_SESSION_RESTORE_COLLECT_ALL_WINDOWS_DATA_MS");
-
- // An array that at the end will hold all current window data.
- var total = [];
- // The ids of all windows contained in 'total' in the same order.
- var ids = [];
- // The number of window that are _not_ popups.
- var nonPopupCount = 0;
- var ix;
-
- // collect the data for all windows
- for (ix in this._windows) {
- if (this._windows[ix]._restoring) // window data is still in _statesToRestore
- continue;
- total.push(this._windows[ix]);
- ids.push(ix);
- if (!this._windows[ix].isPopup)
- nonPopupCount++;
- }
-
- TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_COOKIES_MS");
- SessionCookies.update(total);
- TelemetryStopwatch.finish("FX_SESSION_RESTORE_COLLECT_COOKIES_MS");
-
- // collect the data for all windows yet to be restored
- for (ix in this._statesToRestore) {
- for (let winData of this._statesToRestore[ix].windows) {
- total.push(winData);
- if (!winData.isPopup)
- nonPopupCount++;
- }
- }
-
- // shallow copy this._closedWindows to preserve current state
- let lastClosedWindowsCopy = this._closedWindows.slice();
-
- if (AppConstants.platform != "macosx") {
- // If no non-popup browser window remains open, return the state of the last
- // closed window(s). We only want to do this when we're actually "ending"
- // the session.
- //XXXzpao We should do this for _restoreLastWindow == true, but that has
- // its own check for popups. c.f. bug 597619
- if (nonPopupCount == 0 && lastClosedWindowsCopy.length > 0 &&
- RunState.isQuitting) {
- // prepend the last non-popup browser window, so that if the user loads more tabs
- // at startup we don't accidentally add them to a popup window
- do {
- total.unshift(lastClosedWindowsCopy.shift())
- } while (total[0].isPopup && lastClosedWindowsCopy.length > 0)
- }
- }
-
- if (activeWindow) {
- this.activeWindowSSiCache = activeWindow.__SSi || "";
- }
- ix = ids.indexOf(this.activeWindowSSiCache);
- // We don't want to restore focus to a minimized window or a window which had all its
- // tabs stripped out (doesn't exist).
- if (ix != -1 && total[ix] && total[ix].sizemode == "minimized")
- ix = -1;
-
- let session = {
- lastUpdate: Date.now(),
- startTime: this._sessionStartTime,
- recentCrashes: this._recentCrashes
- };
-
- let state = {
- version: ["sessionrestore", FORMAT_VERSION],
- windows: total,
- selectedWindow: ix + 1,
- _closedWindows: lastClosedWindowsCopy,
- session: session,
- global: this._globalState.getState()
- };
-
- // Scratchpad
- if (Cu.isModuleLoaded("resource://devtools/client/scratchpad/scratchpad-manager.jsm")) {
- // get open Scratchpad window states too
- let scratchpads = ScratchpadManager.getSessionState();
- if (scratchpads && scratchpads.length) {
- state.scratchpads = scratchpads;
- }
- }
-
- // The Browser Console
- state.browserConsole = HUDService.getBrowserConsoleSessionState();
-
- // Persist the last session if we deferred restoring it
- if (LastSession.canRestore) {
- state.lastSessionState = LastSession.getState();
- }
-
- // If we were called by the SessionSaver and started with only a private
- // window we want to pass the deferred initial state to not lose the
- // previous session.
- if (this._deferredInitialState) {
- state.deferredInitialState = this._deferredInitialState;
- }
-
- return state;
- },
-
- /**
- * serialize session data for a window
- * @param aWindow
- * Window reference
- * @returns string
- */
- _getWindowState: function ssi_getWindowState(aWindow) {
- if (!this._isWindowLoaded(aWindow))
- return this._statesToRestore[aWindow.__SS_restoreID];
-
- if (RunState.isRunning) {
- this._collectWindowData(aWindow);
- }
-
- let windows = [this._windows[aWindow.__SSi]];
- SessionCookies.update(windows);
-
- return { windows: windows };
- },
-
- /**
- * Gathers data about a window and its tabs, and updates its
- * entry in this._windows.
- *
- * @param aWindow
- * Window references.
- * @returns a Map mapping the browser tabs from aWindow to the tab
- * entry that was put into the window data in this._windows.
- */
- _collectWindowData: function ssi_collectWindowData(aWindow) {
- let tabMap = new Map();
-
- if (!this._isWindowLoaded(aWindow))
- return tabMap;
-
- let tabbrowser = aWindow.gBrowser;
- let tabs = tabbrowser.tabs;
- let winData = this._windows[aWindow.__SSi];
- let tabsData = winData.tabs = [];
-
- // update the internal state data for this window
- for (let tab of tabs) {
- let tabData = TabState.collect(tab);
- tabMap.set(tab, tabData);
- tabsData.push(tabData);
- }
- winData.selected = tabbrowser.mTabBox.selectedIndex + 1;
-
- this._updateWindowFeatures(aWindow);
-
- // Make sure we keep __SS_lastSessionWindowID around for cases like entering
- // or leaving PB mode.
- if (aWindow.__SS_lastSessionWindowID)
- this._windows[aWindow.__SSi].__lastSessionWindowID =
- aWindow.__SS_lastSessionWindowID;
-
- DirtyWindows.remove(aWindow);
- return tabMap;
- },
-
- /* ........ Restoring Functionality .............. */
-
- /**
- * restore features to a single window
- * @param aWindow
- * Window reference to the window to use for restoration
- * @param winData
- * JS object
- * @param aOptions
- * {overwriteTabs: true} to overwrite existing tabs w/ new ones
- * {isFollowUp: true} if this is not the restoration of the 1st window
- * {firstWindow: true} if this is the first non-private window we're
- * restoring in this session, that might open an
- * external link as well
- */
- restoreWindow: function ssi_restoreWindow(aWindow, winData, aOptions = {}) {
- let overwriteTabs = aOptions && aOptions.overwriteTabs;
- let isFollowUp = aOptions && aOptions.isFollowUp;
- let firstWindow = aOptions && aOptions.firstWindow;
-
- if (isFollowUp) {
- this.windowToFocus = aWindow;
- }
-
- // initialize window if necessary
- if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
- this.onLoad(aWindow);
-
- TelemetryStopwatch.start("FX_SESSION_RESTORE_RESTORE_WINDOW_MS");
-
- // We're not returning from this before we end up calling restoreTabs
- // for this window, so make sure we send the SSWindowStateBusy event.
- this._setWindowStateBusy(aWindow);
-
- if (!winData.tabs) {
- winData.tabs = [];
- }
-
- // don't restore a single blank tab when we've had an external
- // URL passed in for loading at startup (cf. bug 357419)
- else if (firstWindow && !overwriteTabs && winData.tabs.length == 1 &&
- (!winData.tabs[0].entries || winData.tabs[0].entries.length == 0)) {
- winData.tabs = [];
- }
-
- var tabbrowser = aWindow.gBrowser;
- var openTabCount = overwriteTabs ? tabbrowser.browsers.length : -1;
- var newTabCount = winData.tabs.length;
- var tabs = [];
-
- // disable smooth scrolling while adding, moving, removing and selecting tabs
- var tabstrip = tabbrowser.tabContainer.mTabstrip;
- var smoothScroll = tabstrip.smoothScroll;
- tabstrip.smoothScroll = false;
-
- // unpin all tabs to ensure they are not reordered in the next loop
- if (overwriteTabs) {
- for (let t = tabbrowser._numPinnedTabs - 1; t > -1; t--)
- tabbrowser.unpinTab(tabbrowser.tabs[t]);
- }
-
- // We need to keep track of the initially open tabs so that they
- // can be moved to the end of the restored tabs.
- let initialTabs = [];
- if (!overwriteTabs && firstWindow) {
- initialTabs = Array.slice(tabbrowser.tabs);
- }
-
- // make sure that the selected tab won't be closed in order to
- // prevent unnecessary flickering
- if (overwriteTabs && tabbrowser.selectedTab._tPos >= newTabCount)
- tabbrowser.moveTabTo(tabbrowser.selectedTab, newTabCount - 1);
-
- let numVisibleTabs = 0;
-
- for (var t = 0; t < newTabCount; t++) {
- // When trying to restore into existing tab, we also take the userContextId
- // into account if present.
- let userContextId = winData.tabs[t].userContextId;
- let reuseExisting = t < openTabCount &&
- (tabbrowser.tabs[t].getAttribute("usercontextid") == (userContextId || ""));
- // If the tab is pinned, then we'll be loading it right away, and
- // there's no need to cause a remoteness flip by loading it initially
- // non-remote.
- let forceNotRemote = !winData.tabs[t].pinned;
- let tab = reuseExisting ? tabbrowser.tabs[t] :
- tabbrowser.addTab("about:blank",
- {skipAnimation: true,
- forceNotRemote,
- userContextId});
-
- // If we inserted a new tab because the userContextId didn't match with the
- // open tab, even though `t < openTabCount`, we need to remove that open tab
- // and put the newly added tab in its place.
- if (!reuseExisting && t < openTabCount) {
- tabbrowser.removeTab(tabbrowser.tabs[t]);
- tabbrowser.moveTabTo(tab, t);
- }
-
- tabs.push(tab);
-
- if (winData.tabs[t].pinned)
- tabbrowser.pinTab(tabs[t]);
-
- if (winData.tabs[t].hidden) {
- tabbrowser.hideTab(tabs[t]);
- }
- else {
- tabbrowser.showTab(tabs[t]);
- numVisibleTabs++;
- }
-
- if (!!winData.tabs[t].muted != tabs[t].linkedBrowser.audioMuted) {
- tabs[t].toggleMuteAudio(winData.tabs[t].muteReason);
- }
- }
-
- if (!overwriteTabs && firstWindow) {
- // Move the originally open tabs to the end
- let endPosition = tabbrowser.tabs.length - 1;
- for (let i = 0; i < initialTabs.length; i++) {
- tabbrowser.moveTabTo(initialTabs[i], endPosition);
- }
- }
-
- // if all tabs to be restored are hidden, make the first one visible
- if (!numVisibleTabs && winData.tabs.length) {
- winData.tabs[0].hidden = false;
- tabbrowser.showTab(tabs[0]);
- }
-
- // If overwriting tabs, we want to reset each tab's "restoring" state. Since
- // we're overwriting those tabs, they should no longer be restoring. The
- // tabs will be rebuilt and marked if they need to be restored after loading
- // state (in restoreTabs).
- if (overwriteTabs) {
- for (let i = 0; i < tabbrowser.tabs.length; i++) {
- let tab = tabbrowser.tabs[i];
- if (tabbrowser.browsers[i].__SS_restoreState)
- this._resetTabRestoringState(tab);
- }
- }
-
- // We want to correlate the window with data from the last session, so
- // assign another id if we have one. Otherwise clear so we don't do
- // anything with it.
- delete aWindow.__SS_lastSessionWindowID;
- if (winData.__lastSessionWindowID)
- aWindow.__SS_lastSessionWindowID = winData.__lastSessionWindowID;
-
- // when overwriting tabs, remove all superflous ones
- if (overwriteTabs && newTabCount < openTabCount) {
- Array.slice(tabbrowser.tabs, newTabCount, openTabCount)
- .forEach(tabbrowser.removeTab, tabbrowser);
- }
-
- if (overwriteTabs) {
- this.restoreWindowFeatures(aWindow, winData);
- delete this._windows[aWindow.__SSi].extData;
- }
- if (winData.cookies) {
- SessionCookies.restore(winData.cookies);
- }
- if (winData.extData) {
- if (!this._windows[aWindow.__SSi].extData) {
- this._windows[aWindow.__SSi].extData = {};
- }
- for (var key in winData.extData) {
- this._windows[aWindow.__SSi].extData[key] = winData.extData[key];
- }
- }
-
- let newClosedTabsData = winData._closedTabs || [];
-
- if (overwriteTabs || firstWindow) {
- // Overwrite existing closed tabs data when overwriteTabs=true
- // or we're the first window to be restored.
- this._windows[aWindow.__SSi]._closedTabs = newClosedTabsData;
- } else if (this._max_tabs_undo > 0) {
- // If we merge tabs, we also want to merge closed tabs data. We'll assume
- // the restored tabs were closed more recently and append the current list
- // of closed tabs to the new one...
- newClosedTabsData =
- newClosedTabsData.concat(this._windows[aWindow.__SSi]._closedTabs);
-
- // ... and make sure that we don't exceed the max number of closed tabs
- // we can restore.
- this._windows[aWindow.__SSi]._closedTabs =
- newClosedTabsData.slice(0, this._max_tabs_undo);
- }
-
- // Restore tabs, if any.
- if (winData.tabs.length) {
- this.restoreTabs(aWindow, tabs, winData.tabs,
- (overwriteTabs ? (parseInt(winData.selected || "1")) : 0));
- }
-
- // set smoothScroll back to the original value
- tabstrip.smoothScroll = smoothScroll;
-
- TelemetryStopwatch.finish("FX_SESSION_RESTORE_RESTORE_WINDOW_MS");
-
- this._setWindowStateReady(aWindow);
-
- this._sendWindowRestoredNotification(aWindow);
-
- Services.obs.notifyObservers(aWindow, NOTIFY_SINGLE_WINDOW_RESTORED, "");
-
- this._sendRestoreCompletedNotifications();
- },
-
- /**
- * Restore multiple windows using the provided state.
- * @param aWindow
- * Window reference to the first window to use for restoration.
- * Additionally required windows will be opened.
- * @param aState
- * JS object or JSON string
- * @param aOptions
- * {overwriteTabs: true} to overwrite existing tabs w/ new ones
- * {isFollowUp: true} if this is not the restoration of the 1st window
- * {firstWindow: true} if this is the first non-private window we're
- * restoring in this session, that might open an
- * external link as well
- */
- restoreWindows: function ssi_restoreWindows(aWindow, aState, aOptions = {}) {
- let isFollowUp = aOptions && aOptions.isFollowUp;
-
- if (isFollowUp) {
- this.windowToFocus = aWindow;
- }
-
- // initialize window if necessary
- if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
- this.onLoad(aWindow);
-
- let root;
- try {
- root = (typeof aState == "string") ? JSON.parse(aState) : aState;
- }
- catch (ex) { // invalid state object - don't restore anything
- debug(ex);
- this._sendRestoreCompletedNotifications();
- return;
- }
-
- // Restore closed windows if any.
- if (root._closedWindows) {
- this._closedWindows = root._closedWindows;
- }
-
- // We're done here if there are no windows.
- if (!root.windows || !root.windows.length) {
- this._sendRestoreCompletedNotifications();
- return;
- }
-
- if (!root.selectedWindow || root.selectedWindow > root.windows.length) {
- root.selectedWindow = 0;
- }
-
- // open new windows for all further window entries of a multi-window session
- // (unless they don't contain any tab data)
- let winData;
- for (var w = 1; w < root.windows.length; w++) {
- winData = root.windows[w];
- if (winData && winData.tabs && winData.tabs[0]) {
- var window = this._openWindowWithState({ windows: [winData] });
- if (w == root.selectedWindow - 1) {
- this.windowToFocus = window;
- }
- }
- }
-
- this.restoreWindow(aWindow, root.windows[0], aOptions);
-
- // Scratchpad
- if (aState.scratchpads) {
- ScratchpadManager.restoreSession(aState.scratchpads);
- }
-
- // The Browser Console
- if (aState.browserConsole) {
- HUDService.restoreBrowserConsoleSession();
- }
- },
-
- /**
- * Manage history restoration for a window
- * @param aWindow
- * Window to restore the tabs into
- * @param aTabs
- * Array of tab references
- * @param aTabData
- * Array of tab data
- * @param aSelectTab
- * Index of the tab to select. This is a 1-based index where "1"
- * indicates the first tab should be selected, and "0" indicates that
- * the currently selected tab will not be changed.
- */
- restoreTabs(aWindow, aTabs, aTabData, aSelectTab) {
- var tabbrowser = aWindow.gBrowser;
-
- if (!this._isWindowLoaded(aWindow)) {
- // from now on, the data will come from the actual window
- delete this._statesToRestore[aWindow.__SS_restoreID];
- delete aWindow.__SS_restoreID;
- delete this._windows[aWindow.__SSi]._restoring;
- }
-
- let numTabsToRestore = aTabs.length;
- let numTabsInWindow = tabbrowser.tabs.length;
- let tabsDataArray = this._windows[aWindow.__SSi].tabs;
-
- // Update the window state in case we shut down without being notified.
- // Individual tab states will be taken care of by restoreTab() below.
- if (numTabsInWindow == numTabsToRestore) {
- // Remove all previous tab data.
- tabsDataArray.length = 0;
- } else {
- // Remove all previous tab data except tabs that should not be overriden.
- tabsDataArray.splice(numTabsInWindow - numTabsToRestore);
- }
-
- // Let the tab data array have the right number of slots.
- tabsDataArray.length = numTabsInWindow;
-
- // If provided, set the selected tab.
- if (aSelectTab > 0 && aSelectTab <= aTabs.length) {
- tabbrowser.selectedTab = aTabs[aSelectTab - 1];
-
- // Update the window state in case we shut down without being notified.
- this._windows[aWindow.__SSi].selected = aSelectTab;
- }
-
- // Restore all tabs.
- for (let t = 0; t < aTabs.length; t++) {
- this.restoreTab(aTabs[t], aTabData[t]);
- }
- },
-
- // Restores the given tab state for a given tab.
- restoreTab(tab, tabData, options = {}) {
- NS_ASSERT(!tab.linkedBrowser.__SS_restoreState,
- "must reset tab before calling restoreTab()");
-
- let restoreImmediately = options.restoreImmediately;
- let loadArguments = options.loadArguments;
- let browser = tab.linkedBrowser;
- let window = tab.ownerGlobal;
- let tabbrowser = window.gBrowser;
- let forceOnDemand = options.forceOnDemand;
- let reloadInFreshProcess = options.reloadInFreshProcess;
-
- let willRestoreImmediately = restoreImmediately ||
- tabbrowser.selectedBrowser == browser ||
- loadArguments;
-
- if (!willRestoreImmediately && !forceOnDemand) {
- TabRestoreQueue.add(tab);
- }
-
- this._maybeUpdateBrowserRemoteness({ tabbrowser, tab,
- willRestoreImmediately });
-
- // Increase the busy state counter before modifying the tab.
- this._setWindowStateBusy(window);
-
- // It's important to set the window state to dirty so that
- // we collect their data for the first time when saving state.
- DirtyWindows.add(window);
-
- // In case we didn't collect/receive data for any tabs yet we'll have to
- // fill the array with at least empty tabData objects until |_tPos| or
- // we'll end up with |null| entries.
- for (let otherTab of Array.slice(tabbrowser.tabs, 0, tab._tPos)) {
- let emptyState = {entries: [], lastAccessed: otherTab.lastAccessed};
- this._windows[window.__SSi].tabs.push(emptyState);
- }
-
- // Update the tab state in case we shut down without being notified.
- this._windows[window.__SSi].tabs[tab._tPos] = tabData;
-
- // Prepare the tab so that it can be properly restored. We'll pin/unpin
- // and show/hide tabs as necessary. We'll also attach a copy of the tab's
- // data in case we close it before it's been restored.
- if (tabData.pinned) {
- tabbrowser.pinTab(tab);
- } else {
- tabbrowser.unpinTab(tab);
- }
-
- if (tabData.hidden) {
- tabbrowser.hideTab(tab);
- } else {
- tabbrowser.showTab(tab);
- }
-
- if (!!tabData.muted != browser.audioMuted) {
- tab.toggleMuteAudio(tabData.muteReason);
- }
-
- if (tabData.lastAccessed) {
- tab.updateLastAccessed(tabData.lastAccessed);
- }
-
- if ("attributes" in tabData) {
- // Ensure that we persist tab attributes restored from previous sessions.
- Object.keys(tabData.attributes).forEach(a => TabAttributes.persist(a));
- }
-
- if (!tabData.entries) {
- tabData.entries = [];
- }
- if (tabData.extData) {
- tab.__SS_extdata = Cu.cloneInto(tabData.extData, {});
- } else {
- delete tab.__SS_extdata;
- }
-
- // Tab is now open.
- delete tabData.closedAt;
-
- // Ensure the index is in bounds.
- let activeIndex = (tabData.index || tabData.entries.length) - 1;
- activeIndex = Math.min(activeIndex, tabData.entries.length - 1);
- activeIndex = Math.max(activeIndex, 0);
-
- // Save the index in case we updated it above.
- tabData.index = activeIndex + 1;
-
- // Start a new epoch to discard all frame script messages relating to a
- // previous epoch. All async messages that are still on their way to chrome
- // will be ignored and don't override any tab data set when restoring.
- let epoch = this.startNextEpoch(browser);
-
- // keep the data around to prevent dataloss in case
- // a tab gets closed before it's been properly restored
- browser.__SS_restoreState = TAB_STATE_NEEDS_RESTORE;
- browser.setAttribute("pending", "true");
- tab.setAttribute("pending", "true");
-
- // If we're restoring this tab, it certainly shouldn't be in
- // the ignored set anymore.
- this._crashedBrowsers.delete(browser.permanentKey);
-
- // Update the persistent tab state cache with |tabData| information.
- TabStateCache.update(browser, {
- history: {entries: tabData.entries, index: tabData.index},
- scroll: tabData.scroll || null,
- storage: tabData.storage || null,
- formdata: tabData.formdata || null,
- disallow: tabData.disallow || null,
- pageStyle: tabData.pageStyle || null,
-
- // This information is only needed until the tab has finished restoring.
- // When that's done it will be removed from the cache and we always
- // collect it in TabState._collectBaseTabData().
- image: tabData.image || "",
- iconLoadingPrincipal: tabData.iconLoadingPrincipal || null,
- userTypedValue: tabData.userTypedValue || "",
- userTypedClear: tabData.userTypedClear || 0
- });
-
- browser.messageManager.sendAsyncMessage("SessionStore:restoreHistory",
- {tabData: tabData, epoch: epoch, loadArguments});
-
- // Restore tab attributes.
- if ("attributes" in tabData) {
- TabAttributes.set(tab, tabData.attributes);
- }
-
- // This could cause us to ignore MAX_CONCURRENT_TAB_RESTORES a bit, but
- // it ensures each window will have its selected tab loaded.
- if (willRestoreImmediately) {
- this.restoreTabContent(tab, loadArguments, reloadInFreshProcess);
- } else if (!forceOnDemand) {
- this.restoreNextTab();
- }
-
- // Decrease the busy state counter after we're done.
- this._setWindowStateReady(window);
- },
-
- /**
- * Kicks off restoring the given tab.
- *
- * @param aTab
- * the tab to restore
- * @param aLoadArguments
- * optional load arguments used for loadURI()
- * @param aReloadInFreshProcess
- * true if we want to reload into a fresh process
- */
- restoreTabContent: function (aTab, aLoadArguments = null, aReloadInFreshProcess = false) {
- if (aTab.hasAttribute("customizemode") && !aLoadArguments) {
- return;
- }
-
- let browser = aTab.linkedBrowser;
- let window = aTab.ownerGlobal;
- let tabbrowser = window.gBrowser;
- let tabData = TabState.clone(aTab);
- let activeIndex = tabData.index - 1;
- let activePageData = tabData.entries[activeIndex] || null;
- let uri = activePageData ? activePageData.url || null : null;
- if (aLoadArguments) {
- uri = aLoadArguments.uri;
- if (aLoadArguments.userContextId) {
- browser.setAttribute("usercontextid", aLoadArguments.userContextId);
- }
- }
-
- // We have to mark this tab as restoring first, otherwise
- // the "pending" attribute will be applied to the linked
- // browser, which removes it from the display list. We cannot
- // flip the remoteness of any browser that is not being displayed.
- this.markTabAsRestoring(aTab);
-
- let isRemotenessUpdate = false;
- if (aReloadInFreshProcess) {
- isRemotenessUpdate = tabbrowser.switchBrowserIntoFreshProcess(browser);
- } else {
- isRemotenessUpdate = tabbrowser.updateBrowserRemotenessByURL(browser, uri);
- }
-
- if (isRemotenessUpdate) {
- // We updated the remoteness, so we need to send the history down again.
- //
- // Start a new epoch to discard all frame script messages relating to a
- // previous epoch. All async messages that are still on their way to chrome
- // will be ignored and don't override any tab data set when restoring.
- let epoch = this.startNextEpoch(browser);
-
- browser.messageManager.sendAsyncMessage("SessionStore:restoreHistory", {
- tabData: tabData,
- epoch: epoch,
- loadArguments: aLoadArguments,
- isRemotenessUpdate,
- });
-
- }
-
- // If the restored browser wants to show view source content, start up a
- // view source browser that will load the required frame script.
- if (uri && ViewSourceBrowser.isViewSource(uri)) {
- new ViewSourceBrowser(browser);
- }
-
- browser.messageManager.sendAsyncMessage("SessionStore:restoreTabContent",
- {loadArguments: aLoadArguments, isRemotenessUpdate});
- },
-
- /**
- * Marks a given pending tab as restoring.
- *
- * @param aTab
- * the pending tab to mark as restoring
- */
- markTabAsRestoring(aTab) {
- let browser = aTab.linkedBrowser;
- if (browser.__SS_restoreState != TAB_STATE_NEEDS_RESTORE) {
- throw new Error("Given tab is not pending.");
- }
-
- // Make sure that this tab is removed from the priority queue.
- TabRestoreQueue.remove(aTab);
-
- // Increase our internal count.
- this._tabsRestoringCount++;
-
- // Set this tab's state to restoring
- browser.__SS_restoreState = TAB_STATE_RESTORING;
- browser.removeAttribute("pending");
- aTab.removeAttribute("pending");
- },
-
- /**
- * This _attempts_ to restore the next available tab. If the restore fails,
- * then we will attempt the next one.
- * There are conditions where this won't do anything:
- * if we're in the process of quitting
- * if there are no tabs to restore
- * if we have already reached the limit for number of tabs to restore
- */
- restoreNextTab: function ssi_restoreNextTab() {
- // If we call in here while quitting, we don't actually want to do anything
- if (RunState.isQuitting)
- return;
-
- // Don't exceed the maximum number of concurrent tab restores.
- if (this._tabsRestoringCount >= MAX_CONCURRENT_TAB_RESTORES)
- return;
-
- let tab = TabRestoreQueue.shift();
- if (tab) {
- this.restoreTabContent(tab);
- }
- },
-
- /**
- * Restore visibility and dimension features to a window
- * @param aWindow
- * Window reference
- * @param aWinData
- * Object containing session data for the window
- */
- restoreWindowFeatures: function ssi_restoreWindowFeatures(aWindow, aWinData) {
- var hidden = (aWinData.hidden)?aWinData.hidden.split(","):[];
- WINDOW_HIDEABLE_FEATURES.forEach(function(aItem) {
- aWindow[aItem].visible = hidden.indexOf(aItem) == -1;
- });
-
- if (aWinData.isPopup) {
- this._windows[aWindow.__SSi].isPopup = true;
- if (aWindow.gURLBar) {
- aWindow.gURLBar.readOnly = true;
- aWindow.gURLBar.setAttribute("enablehistory", "false");
- }
- }
- else {
- delete this._windows[aWindow.__SSi].isPopup;
- if (aWindow.gURLBar) {
- aWindow.gURLBar.readOnly = false;
- aWindow.gURLBar.setAttribute("enablehistory", "true");
- }
- }
-
- var _this = this;
- aWindow.setTimeout(function() {
- _this.restoreDimensions.apply(_this, [aWindow,
- +(aWinData.width || 0),
- +(aWinData.height || 0),
- "screenX" in aWinData ? +aWinData.screenX : NaN,
- "screenY" in aWinData ? +aWinData.screenY : NaN,
- aWinData.sizemode || "", aWinData.sidebar || ""]);
- }, 0);
- },
-
- /**
- * Restore a window's dimensions
- * @param aWidth
- * Window width
- * @param aHeight
- * Window height
- * @param aLeft
- * Window left
- * @param aTop
- * Window top
- * @param aSizeMode
- * Window size mode (eg: maximized)
- * @param aSidebar
- * Sidebar command
- */
- restoreDimensions: function ssi_restoreDimensions(aWindow, aWidth, aHeight, aLeft, aTop, aSizeMode, aSidebar) {
- var win = aWindow;
- var _this = this;
- function win_(aName) { return _this._getWindowDimension(win, aName); }
-
- // find available space on the screen where this window is being placed
- let screen = gScreenManager.screenForRect(aLeft, aTop, aWidth, aHeight);
- if (screen) {
- let screenLeft = {}, screenTop = {}, screenWidth = {}, screenHeight = {};
- screen.GetAvailRectDisplayPix(screenLeft, screenTop, screenWidth, screenHeight);
- // screenX/Y are based on the origin of the screen's desktop-pixel coordinate space
- let screenLeftCss = screenLeft.value;
- let screenTopCss = screenTop.value;
- // convert screen's device pixel dimensions to CSS px dimensions
- screen.GetAvailRect(screenLeft, screenTop, screenWidth, screenHeight);
- let cssToDevScale = screen.defaultCSSScaleFactor;
- let screenRightCss = screenLeftCss + screenWidth.value / cssToDevScale;
- let screenBottomCss = screenTopCss + screenHeight.value / cssToDevScale;
-
- // Pull the window within the screen's bounds (allowing a little slop
- // for windows that may be deliberately placed with their border off-screen
- // as when Win10 "snaps" a window to the left/right edge -- bug 1276516).
- // First, ensure the left edge is large enough...
- if (aLeft < screenLeftCss - SCREEN_EDGE_SLOP) {
- aLeft = screenLeftCss;
- }
- // Then check the resulting right edge, and reduce it if necessary.
- let right = aLeft + aWidth;
- if (right > screenRightCss + SCREEN_EDGE_SLOP) {
- right = screenRightCss;
- // See if we can move the left edge leftwards to maintain width.
- if (aLeft > screenLeftCss) {
- aLeft = Math.max(right - aWidth, screenLeftCss);
- }
- }
- // Finally, update aWidth to account for the adjusted left and right edges.
- aWidth = right - aLeft;
-
- // And do the same in the vertical dimension.
- if (aTop < screenTopCss - SCREEN_EDGE_SLOP) {
- aTop = screenTopCss;
- }
- let bottom = aTop + aHeight;
- if (bottom > screenBottomCss + SCREEN_EDGE_SLOP) {
- bottom = screenBottomCss;
- if (aTop > screenTopCss) {
- aTop = Math.max(bottom - aHeight, screenTopCss);
- }
- }
- aHeight = bottom - aTop;
- }
-
- // only modify those aspects which aren't correct yet
- if (!isNaN(aLeft) && !isNaN(aTop) && (aLeft != win_("screenX") || aTop != win_("screenY"))) {
- aWindow.moveTo(aLeft, aTop);
- }
- if (aWidth && aHeight && (aWidth != win_("width") || aHeight != win_("height"))) {
- // Don't resize the window if it's currently maximized and we would
- // maximize it again shortly after.
- if (aSizeMode != "maximized" || win_("sizemode") != "maximized") {
- aWindow.resizeTo(aWidth, aHeight);
- }
- }
- if (aSizeMode && win_("sizemode") != aSizeMode)
- {
- switch (aSizeMode)
- {
- case "maximized":
- aWindow.maximize();
- break;
- case "minimized":
- aWindow.minimize();
- break;
- case "normal":
- aWindow.restore();
- break;
- }
- }
- var sidebar = aWindow.document.getElementById("sidebar-box");
- if (sidebar.getAttribute("sidebarcommand") != aSidebar) {
- aWindow.SidebarUI.show(aSidebar);
- }
- // since resizing/moving a window brings it to the foreground,
- // we might want to re-focus the last focused window
- if (this.windowToFocus) {
- this.windowToFocus.focus();
- }
- },
-
- /* ........ Disk Access .............. */
-
- /**
- * Save the current session state to disk, after a delay.
- *
- * @param aWindow (optional)
- * Will mark the given window as dirty so that we will recollect its
- * data before we start writing.
- */
- saveStateDelayed: function (aWindow = null) {
- if (aWindow) {
- DirtyWindows.add(aWindow);
- }
-
- SessionSaver.runDelayed();
- },
-
- /* ........ Auxiliary Functions .............. */
-
- /**
- * Determines whether or not a tab that is being restored needs
- * to have its remoteness flipped first.
- *
- * @param (object) with the following properties:
- *
- * tabbrowser (<xul:tabbrowser>):
- * The tabbrowser that the browser belongs to.
- *
- * tab (<xul:tab>):
- * The tab being restored
- *
- * willRestoreImmediately (bool):
- * true if the tab is going to have its content
- * restored immediately by the caller.
- *
- */
- _maybeUpdateBrowserRemoteness({ tabbrowser, tab,
- willRestoreImmediately }) {
- // If the browser we're attempting to restore happens to be
- // remote, we need to flip it back to non-remote if it's going
- // to go into the pending background tab state. This is to make
- // sure that a background tab can't crash if it hasn't yet
- // been restored.
- //
- // Normally, when a window is restored, the tabs that SessionStore
- // inserts are non-remote - but the initial browser is, by default,
- // remote, so this check and flip covers this case. The other case
- // is when window state is overwriting the state of an existing
- // window with some remote tabs.
- let browser = tab.linkedBrowser;
-
- // There are two ways that a tab might start restoring its content
- // very soon - either the caller is going to restore the content
- // immediately, or the TabRestoreQueue is set up so that the tab
- // content is going to be restored in the very near future. In
- // either case, we don't want to flip remoteness, since the browser
- // will soon be loading content.
- let willRestore = willRestoreImmediately ||
- TabRestoreQueue.willRestoreSoon(tab);
-
- if (browser.isRemoteBrowser && !willRestore) {
- tabbrowser.updateBrowserRemoteness(browser, false);
- }
- },
-
- /**
- * Update the session start time and send a telemetry measurement
- * for the number of days elapsed since the session was started.
- *
- * @param state
- * The session state.
- */
- _updateSessionStartTime: function ssi_updateSessionStartTime(state) {
- // Attempt to load the session start time from the session state
- if (state.session && state.session.startTime) {
- this._sessionStartTime = state.session.startTime;
- }
- },
-
- /**
- * call a callback for all currently opened browser windows
- * (might miss the most recent one)
- * @param aFunc
- * Callback each window is passed to
- */
- _forEachBrowserWindow: function ssi_forEachBrowserWindow(aFunc) {
- var windowsEnum = Services.wm.getEnumerator("navigator:browser");
-
- while (windowsEnum.hasMoreElements()) {
- var window = windowsEnum.getNext();
- if (window.__SSi && !window.closed) {
- aFunc.call(this, window);
- }
- }
- },
-
- /**
- * Returns most recent window
- * @returns Window reference
- */
- _getMostRecentBrowserWindow: function ssi_getMostRecentBrowserWindow() {
- return RecentWindow.getMostRecentBrowserWindow({ allowPopups: true });
- },
-
- /**
- * Calls onClose for windows that are determined to be closed but aren't
- * destroyed yet, which would otherwise cause getBrowserState and
- * setBrowserState to treat them as open windows.
- */
- _handleClosedWindows: function ssi_handleClosedWindows() {
- var windowsEnum = Services.wm.getEnumerator("navigator:browser");
-
- while (windowsEnum.hasMoreElements()) {
- var window = windowsEnum.getNext();
- if (window.closed) {
- this.onClose(window);
- }
- }
- },
-
- /**
- * open a new browser window for a given session state
- * called when restoring a multi-window session
- * @param aState
- * Object containing session data
- */
- _openWindowWithState: function ssi_openWindowWithState(aState) {
- var argString = Cc["@mozilla.org/supports-string;1"].
- createInstance(Ci.nsISupportsString);
- argString.data = "";
-
- // Build feature string
- let features = "chrome,dialog=no,macsuppressanimation,all";
- let winState = aState.windows[0];
- WINDOW_ATTRIBUTES.forEach(function(aFeature) {
- // Use !isNaN as an easy way to ignore sizemode and check for numbers
- if (aFeature in winState && !isNaN(winState[aFeature]))
- features += "," + aFeature + "=" + winState[aFeature];
- });
-
- if (winState.isPrivate) {
- features += ",private";
- }
-
- var window =
- Services.ww.openWindow(null, this._prefBranch.getCharPref("chromeURL"),
- "_blank", features, argString);
-
- do {
- var ID = "window" + Math.random();
- } while (ID in this._statesToRestore);
- this._statesToRestore[(window.__SS_restoreID = ID)] = aState;
-
- return window;
- },
-
- /**
- * Whether or not to resume session, if not recovering from a crash.
- * @returns bool
- */
- _doResumeSession: function ssi_doResumeSession() {
- return this._prefBranch.getIntPref("startup.page") == 3 ||
- this._prefBranch.getBoolPref("sessionstore.resume_session_once");
- },
-
- /**
- * whether the user wants to load any other page at startup
- * (except the homepage) - needed for determining whether to overwrite the current tabs
- * C.f.: nsBrowserContentHandler's defaultArgs implementation.
- * @returns bool
- */
- _isCmdLineEmpty: function ssi_isCmdLineEmpty(aWindow, aState) {
- var pinnedOnly = aState.windows &&
- aState.windows.every(win =>
- win.tabs.every(tab => tab.pinned));
-
- let hasFirstArgument = aWindow.arguments && aWindow.arguments[0];
- if (!pinnedOnly) {
- let defaultArgs = Cc["@mozilla.org/browser/clh;1"].
- getService(Ci.nsIBrowserHandler).defaultArgs;
- if (aWindow.arguments &&
- aWindow.arguments[0] &&
- aWindow.arguments[0] == defaultArgs)
- hasFirstArgument = false;
- }
-
- return !hasFirstArgument;
- },
-
- /**
- * on popup windows, the XULWindow's attributes seem not to be set correctly
- * we use thus JSDOMWindow attributes for sizemode and normal window attributes
- * (and hope for reasonable values when maximized/minimized - since then
- * outerWidth/outerHeight aren't the dimensions of the restored window)
- * @param aWindow
- * Window reference
- * @param aAttribute
- * String sizemode | width | height | other window attribute
- * @returns string
- */
- _getWindowDimension: function ssi_getWindowDimension(aWindow, aAttribute) {
- if (aAttribute == "sizemode") {
- switch (aWindow.windowState) {
- case aWindow.STATE_FULLSCREEN:
- case aWindow.STATE_MAXIMIZED:
- return "maximized";
- case aWindow.STATE_MINIMIZED:
- return "minimized";
- default:
- return "normal";
- }
- }
-
- var dimension;
- switch (aAttribute) {
- case "width":
- dimension = aWindow.outerWidth;
- break;
- case "height":
- dimension = aWindow.outerHeight;
- break;
- default:
- dimension = aAttribute in aWindow ? aWindow[aAttribute] : "";
- break;
- }
-
- if (aWindow.windowState == aWindow.STATE_NORMAL) {
- return dimension;
- }
- return aWindow.document.documentElement.getAttribute(aAttribute) || dimension;
- },
-
- /**
- * @param aState is a session state
- * @param aRecentCrashes is the number of consecutive crashes
- * @returns whether a restore page will be needed for the session state
- */
- _needsRestorePage: function ssi_needsRestorePage(aState, aRecentCrashes) {
- const SIX_HOURS_IN_MS = 6 * 60 * 60 * 1000;
-
- // don't display the page when there's nothing to restore
- let winData = aState.windows || null;
- if (!winData || winData.length == 0)
- return false;
-
- // don't wrap a single about:sessionrestore page
- if (this._hasSingleTabWithURL(winData, "about:sessionrestore") ||
- this._hasSingleTabWithURL(winData, "about:welcomeback")) {
- return false;
- }
-
- // don't automatically restore in Safe Mode
- if (Services.appinfo.inSafeMode)
- return true;
-
- let max_resumed_crashes =
- this._prefBranch.getIntPref("sessionstore.max_resumed_crashes");
- let sessionAge = aState.session && aState.session.lastUpdate &&
- (Date.now() - aState.session.lastUpdate);
-
- return max_resumed_crashes != -1 &&
- (aRecentCrashes > max_resumed_crashes ||
- sessionAge && sessionAge >= SIX_HOURS_IN_MS);
- },
-
- /**
- * @param aWinData is the set of windows in session state
- * @param aURL is the single URL we're looking for
- * @returns whether the window data contains only the single URL passed
- */
- _hasSingleTabWithURL: function(aWinData, aURL) {
- if (aWinData &&
- aWinData.length == 1 &&
- aWinData[0].tabs &&
- aWinData[0].tabs.length == 1 &&
- aWinData[0].tabs[0].entries &&
- aWinData[0].tabs[0].entries.length == 1) {
- return aURL == aWinData[0].tabs[0].entries[0].url;
- }
- return false;
- },
-
- /**
- * Determine if the tab state we're passed is something we should save. This
- * is used when closing a tab or closing a window with a single tab
- *
- * @param aTabState
- * The current tab state
- * @returns boolean
- */
- _shouldSaveTabState: function ssi_shouldSaveTabState(aTabState) {
- // If the tab has only a transient about: history entry, no other
- // session history, and no userTypedValue, then we don't actually want to
- // store this tab's data.
- return aTabState.entries.length &&
- !(aTabState.entries.length == 1 &&
- (aTabState.entries[0].url == "about:blank" ||
- aTabState.entries[0].url == "about:newtab" ||
- aTabState.entries[0].url == "about:privatebrowsing") &&
- !aTabState.userTypedValue);
- },
-
- /**
- * This is going to take a state as provided at startup (via
- * nsISessionStartup.state) and split it into 2 parts. The first part
- * (defaultState) will be a state that should still be restored at startup,
- * while the second part (state) is a state that should be saved for later.
- * defaultState will be comprised of windows with only pinned tabs, extracted
- * from state. It will contain the cookies that go along with the history
- * entries in those tabs. It will also contain window position information.
- *
- * defaultState will be restored at startup. state will be passed into
- * LastSession and will be kept in case the user explicitly wants
- * to restore the previous session (publicly exposed as restoreLastSession).
- *
- * @param state
- * The state, presumably from nsISessionStartup.state
- * @returns [defaultState, state]
- */
- _prepDataForDeferredRestore: function ssi_prepDataForDeferredRestore(state) {
- // Make sure that we don't modify the global state as provided by
- // nsSessionStartup.state.
- state = Cu.cloneInto(state, {});
-
- let defaultState = { windows: [], selectedWindow: 1 };
-
- state.selectedWindow = state.selectedWindow || 1;
-
- // Look at each window, remove pinned tabs, adjust selectedindex,
- // remove window if necessary.
- for (let wIndex = 0; wIndex < state.windows.length;) {
- let window = state.windows[wIndex];
- window.selected = window.selected || 1;
- // We're going to put the state of the window into this object
- let pinnedWindowState = { tabs: [], cookies: []};
- for (let tIndex = 0; tIndex < window.tabs.length;) {
- if (window.tabs[tIndex].pinned) {
- // Adjust window.selected
- if (tIndex + 1 < window.selected)
- window.selected -= 1;
- else if (tIndex + 1 == window.selected)
- pinnedWindowState.selected = pinnedWindowState.tabs.length + 2;
- // + 2 because the tab isn't actually in the array yet
-
- // Now add the pinned tab to our window
- pinnedWindowState.tabs =
- pinnedWindowState.tabs.concat(window.tabs.splice(tIndex, 1));
- // We don't want to increment tIndex here.
- continue;
- }
- tIndex++;
- }
-
- // At this point the window in the state object has been modified (or not)
- // We want to build the rest of this new window object if we have pinnedTabs.
- if (pinnedWindowState.tabs.length) {
- // First get the other attributes off the window
- WINDOW_ATTRIBUTES.forEach(function(attr) {
- if (attr in window) {
- pinnedWindowState[attr] = window[attr];
- delete window[attr];
- }
- });
- // We're just copying position data into the pinned window.
- // Not copying over:
- // - _closedTabs
- // - extData
- // - isPopup
- // - hidden
-
- // Assign a unique ID to correlate the window to be opened with the
- // remaining data
- window.__lastSessionWindowID = pinnedWindowState.__lastSessionWindowID
- = "" + Date.now() + Math.random();
-
- // Extract the cookies that belong with each pinned tab
- this._splitCookiesFromWindow(window, pinnedWindowState);
-
- // Actually add this window to our defaultState
- defaultState.windows.push(pinnedWindowState);
- // Remove the window from the state if it doesn't have any tabs
- if (!window.tabs.length) {
- if (wIndex + 1 <= state.selectedWindow)
- state.selectedWindow -= 1;
- else if (wIndex + 1 == state.selectedWindow)
- defaultState.selectedIndex = defaultState.windows.length + 1;
-
- state.windows.splice(wIndex, 1);
- // We don't want to increment wIndex here.
- continue;
- }
-
-
- }
- wIndex++;
- }
-
- return [defaultState, state];
- },
-
- /**
- * Splits out the cookies from aWinState into aTargetWinState based on the
- * tabs that are in aTargetWinState.
- * This alters the state of aWinState and aTargetWinState.
- */
- _splitCookiesFromWindow:
- function ssi_splitCookiesFromWindow(aWinState, aTargetWinState) {
- if (!aWinState.cookies || !aWinState.cookies.length)
- return;
-
- // Get the hosts for history entries in aTargetWinState
- let cookieHosts = SessionCookies.getHostsForWindow(aTargetWinState);
-
- // By creating a regex we reduce overhead and there is only one loop pass
- // through either array (cookieHosts and aWinState.cookies).
- let hosts = Object.keys(cookieHosts).join("|").replace(/\./g, "\\.");
- // If we don't actually have any hosts, then we don't want to do anything.
- if (!hosts.length)
- return;
- let cookieRegex = new RegExp(".*(" + hosts + ")");
- for (let cIndex = 0; cIndex < aWinState.cookies.length;) {
- if (cookieRegex.test(aWinState.cookies[cIndex].host)) {
- aTargetWinState.cookies =
- aTargetWinState.cookies.concat(aWinState.cookies.splice(cIndex, 1));
- continue;
- }
- cIndex++;
- }
- },
-
- _sendRestoreCompletedNotifications: function ssi_sendRestoreCompletedNotifications() {
- // not all windows restored, yet
- if (this._restoreCount > 1) {
- this._restoreCount--;
- return;
- }
-
- // observers were already notified
- if (this._restoreCount == -1)
- return;
-
- // This was the last window restored at startup, notify observers.
- Services.obs.notifyObservers(null,
- this._browserSetState ? NOTIFY_BROWSER_STATE_RESTORED : NOTIFY_WINDOWS_RESTORED,
- "");
-
- this._browserSetState = false;
- this._restoreCount = -1;
- },
-
- /**
- * Set the given window's busy state
- * @param aWindow the window
- * @param aValue the window's busy state
- */
- _setWindowStateBusyValue:
- function ssi_changeWindowStateBusyValue(aWindow, aValue) {
-
- this._windows[aWindow.__SSi].busy = aValue;
-
- // Keep the to-be-restored state in sync because that is returned by
- // getWindowState() as long as the window isn't loaded, yet.
- if (!this._isWindowLoaded(aWindow)) {
- let stateToRestore = this._statesToRestore[aWindow.__SS_restoreID].windows[0];
- stateToRestore.busy = aValue;
- }
- },
-
- /**
- * Set the given window's state to 'not busy'.
- * @param aWindow the window
- */
- _setWindowStateReady: function ssi_setWindowStateReady(aWindow) {
- let newCount = (this._windowBusyStates.get(aWindow) || 0) - 1;
- if (newCount < 0) {
- throw new Error("Invalid window busy state (less than zero).");
- }
- this._windowBusyStates.set(aWindow, newCount);
-
- if (newCount == 0) {
- this._setWindowStateBusyValue(aWindow, false);
- this._sendWindowStateEvent(aWindow, "Ready");
- }
- },
-
- /**
- * Set the given window's state to 'busy'.
- * @param aWindow the window
- */
- _setWindowStateBusy: function ssi_setWindowStateBusy(aWindow) {
- let newCount = (this._windowBusyStates.get(aWindow) || 0) + 1;
- this._windowBusyStates.set(aWindow, newCount);
-
- if (newCount == 1) {
- this._setWindowStateBusyValue(aWindow, true);
- this._sendWindowStateEvent(aWindow, "Busy");
- }
- },
-
- /**
- * Dispatch an SSWindowState_____ event for the given window.
- * @param aWindow the window
- * @param aType the type of event, SSWindowState will be prepended to this string
- */
- _sendWindowStateEvent: function ssi_sendWindowStateEvent(aWindow, aType) {
- let event = aWindow.document.createEvent("Events");
- event.initEvent("SSWindowState" + aType, true, false);
- aWindow.dispatchEvent(event);
- },
-
- /**
- * Dispatch the SSWindowRestored event for the given window.
- * @param aWindow
- * The window which has been restored
- */
- _sendWindowRestoredNotification(aWindow) {
- let event = aWindow.document.createEvent("Events");
- event.initEvent("SSWindowRestored", true, false);
- aWindow.dispatchEvent(event);
- },
-
- /**
- * Dispatch the SSTabRestored event for the given tab.
- * @param aTab
- * The tab which has been restored
- * @param aIsRemotenessUpdate
- * True if this tab was restored due to flip from running from
- * out-of-main-process to in-main-process or vice-versa.
- */
- _sendTabRestoredNotification(aTab, aIsRemotenessUpdate) {
- let event = aTab.ownerDocument.createEvent("CustomEvent");
- event.initCustomEvent("SSTabRestored", true, false, {
- isRemotenessUpdate: aIsRemotenessUpdate,
- });
- aTab.dispatchEvent(event);
- },
-
- /**
- * @param aWindow
- * Window reference
- * @returns whether this window's data is still cached in _statesToRestore
- * because it's not fully loaded yet
- */
- _isWindowLoaded: function ssi_isWindowLoaded(aWindow) {
- return !aWindow.__SS_restoreID;
- },
-
- /**
- * Replace "Loading..." with the tab label (with minimal side-effects)
- * @param aString is the string the title is stored in
- * @param aTabbrowser is a tabbrowser object, containing aTab
- * @param aTab is the tab whose title we're updating & using
- *
- * @returns aString that has been updated with the new title
- */
- _replaceLoadingTitle : function ssi_replaceLoadingTitle(aString, aTabbrowser, aTab) {
- if (aString == aTabbrowser.mStringBundle.getString("tabs.connecting")) {
- aTabbrowser.setTabTitle(aTab);
- [aString, aTab.label] = [aTab.label, aString];
- }
- return aString;
- },
-
- /**
- * Resize this._closedWindows to the value of the pref, except in the case
- * where we don't have any non-popup windows on Windows and Linux. Then we must
- * resize such that we have at least one non-popup window.
- */
- _capClosedWindows : function ssi_capClosedWindows() {
- if (this._closedWindows.length <= this._max_windows_undo)
- return;
- let spliceTo = this._max_windows_undo;
- if (AppConstants.platform != "macosx") {
- let normalWindowIndex = 0;
- // try to find a non-popup window in this._closedWindows
- while (normalWindowIndex < this._closedWindows.length &&
- !!this._closedWindows[normalWindowIndex].isPopup)
- normalWindowIndex++;
- if (normalWindowIndex >= this._max_windows_undo)
- spliceTo = normalWindowIndex + 1;
- }
- this._closedWindows.splice(spliceTo, this._closedWindows.length);
- },
-
- /**
- * Clears the set of windows that are "resurrected" before writing to disk to
- * make closing windows one after the other until shutdown work as expected.
- *
- * This function should only be called when we are sure that there has been
- * a user action that indicates the browser is actively being used and all
- * windows that have been closed before are not part of a series of closing
- * windows.
- */
- _clearRestoringWindows: function ssi_clearRestoringWindows() {
- for (let i = 0; i < this._closedWindows.length; i++) {
- delete this._closedWindows[i]._shouldRestore;
- }
- },
-
- /**
- * Reset state to prepare for a new session state to be restored.
- */
- _resetRestoringState: function ssi_initRestoringState() {
- TabRestoreQueue.reset();
- this._tabsRestoringCount = 0;
- },
-
- /**
- * Reset the restoring state for a particular tab. This will be called when
- * removing a tab or when a tab needs to be reset (it's being overwritten).
- *
- * @param aTab
- * The tab that will be "reset"
- */
- _resetLocalTabRestoringState: function (aTab) {
- NS_ASSERT(aTab.linkedBrowser.__SS_restoreState,
- "given tab is not restoring");
-
- let browser = aTab.linkedBrowser;
-
- // Keep the tab's previous state for later in this method
- let previousState = browser.__SS_restoreState;
-
- // The browser is no longer in any sort of restoring state.
- delete browser.__SS_restoreState;
-
- aTab.removeAttribute("pending");
- browser.removeAttribute("pending");
-
- if (previousState == TAB_STATE_RESTORING) {
- if (this._tabsRestoringCount)
- this._tabsRestoringCount--;
- } else if (previousState == TAB_STATE_NEEDS_RESTORE) {
- // Make sure that the tab is removed from the list of tabs to restore.
- // Again, this is normally done in restoreTabContent, but that isn't being called
- // for this tab.
- TabRestoreQueue.remove(aTab);
- }
- },
-
- _resetTabRestoringState: function (tab) {
- NS_ASSERT(tab.linkedBrowser.__SS_restoreState,
- "given tab is not restoring");
-
- let browser = tab.linkedBrowser;
- browser.messageManager.sendAsyncMessage("SessionStore:resetRestore", {});
- this._resetLocalTabRestoringState(tab);
- },
-
- /**
- * Each fresh tab starts out with epoch=0. This function can be used to
- * start a next epoch by incrementing the current value. It will enables us
- * to ignore stale messages sent from previous epochs. The function returns
- * the new epoch ID for the given |browser|.
- */
- startNextEpoch(browser) {
- let next = this.getCurrentEpoch(browser) + 1;
- this._browserEpochs.set(browser.permanentKey, next);
- return next;
- },
-
- /**
- * Returns the current epoch for the given <browser>. If we haven't assigned
- * a new epoch this will default to zero for new tabs.
- */
- getCurrentEpoch(browser) {
- return this._browserEpochs.get(browser.permanentKey) || 0;
- },
-
- /**
- * Each time a <browser> element is restored, we increment its "epoch". To
- * check if a message from content-sessionStore.js is out of date, we can
- * compare the epoch received with the message to the <browser> element's
- * epoch. This function does that, and returns true if |epoch| is up-to-date
- * with respect to |browser|.
- */
- isCurrentEpoch: function (browser, epoch) {
- return this.getCurrentEpoch(browser) == epoch;
- },
-
- /**
- * Resets the epoch for a given <browser>. We need to this every time we
- * receive a hint that a new docShell has been loaded into the browser as
- * the frame script starts out with epoch=0.
- */
- resetEpoch(browser) {
- this._browserEpochs.delete(browser.permanentKey);
- },
-
- /**
- * Handle an error report from a content process.
- */
- reportInternalError(data) {
- // For the moment, we only report errors through Telemetry.
- if (data.telemetry) {
- for (let key of Object.keys(data.telemetry)) {
- let histogram = Telemetry.getHistogramById(key);
- histogram.add(data.telemetry[key]);
- }
- }
- },
-
- /**
- * Countdown for a given duration, skipping beats if the computer is too busy,
- * sleeping or otherwise unavailable.
- *
- * @param {number} delay An approximate delay to wait in milliseconds (rounded
- * up to the closest second).
- *
- * @return Promise
- */
- looseTimer(delay) {
- let DELAY_BEAT = 1000;
- let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
- let beats = Math.ceil(delay / DELAY_BEAT);
- let promise = new Promise(resolve => {
- timer.initWithCallback(function() {
- if (beats <= 0) {
- resolve();
- }
- --beats;
- }, DELAY_BEAT, Ci.nsITimer.TYPE_REPEATING_PRECISE_CAN_SKIP);
- });
- // Ensure that the timer is both canceled once we are done with it
- // and not garbage-collected until then.
- promise.then(() => timer.cancel(), () => timer.cancel());
- return promise;
- }
-};
-
-/**
- * Priority queue that keeps track of a list of tabs to restore and returns
- * the tab we should restore next, based on priority rules. We decide between
- * pinned, visible and hidden tabs in that and FIFO order. Hidden tabs are only
- * restored with restore_hidden_tabs=true.
- */
-var TabRestoreQueue = {
- // The separate buckets used to store tabs.
- tabs: {priority: [], visible: [], hidden: []},
-
- // Preferences used by the TabRestoreQueue to determine which tabs
- // are restored automatically and which tabs will be on-demand.
- prefs: {
- // Lazy getter that returns whether tabs are restored on demand.
- get restoreOnDemand() {
- let updateValue = () => {
- let value = Services.prefs.getBoolPref(PREF);
- let definition = {value: value, configurable: true};
- Object.defineProperty(this, "restoreOnDemand", definition);
- return value;
- }
-
- const PREF = "browser.sessionstore.restore_on_demand";
- Services.prefs.addObserver(PREF, updateValue, false);
- return updateValue();
- },
-
- // Lazy getter that returns whether pinned tabs are restored on demand.
- get restorePinnedTabsOnDemand() {
- let updateValue = () => {
- let value = Services.prefs.getBoolPref(PREF);
- let definition = {value: value, configurable: true};
- Object.defineProperty(this, "restorePinnedTabsOnDemand", definition);
- return value;
- }
-
- const PREF = "browser.sessionstore.restore_pinned_tabs_on_demand";
- Services.prefs.addObserver(PREF, updateValue, false);
- return updateValue();
- },
-
- // Lazy getter that returns whether we should restore hidden tabs.
- get restoreHiddenTabs() {
- let updateValue = () => {
- let value = Services.prefs.getBoolPref(PREF);
- let definition = {value: value, configurable: true};
- Object.defineProperty(this, "restoreHiddenTabs", definition);
- return value;
- }
-
- const PREF = "browser.sessionstore.restore_hidden_tabs";
- Services.prefs.addObserver(PREF, updateValue, false);
- return updateValue();
- }
- },
-
- // Resets the queue and removes all tabs.
- reset: function () {
- this.tabs = {priority: [], visible: [], hidden: []};
- },
-
- // Adds a tab to the queue and determines its priority bucket.
- add: function (tab) {
- let {priority, hidden, visible} = this.tabs;
-
- if (tab.pinned) {
- priority.push(tab);
- } else if (tab.hidden) {
- hidden.push(tab);
- } else {
- visible.push(tab);
- }
- },
-
- // Removes a given tab from the queue, if it's in there.
- remove: function (tab) {
- let {priority, hidden, visible} = this.tabs;
-
- // We'll always check priority first since we don't
- // have an indicator if a tab will be there or not.
- let set = priority;
- let index = set.indexOf(tab);
-
- if (index == -1) {
- set = tab.hidden ? hidden : visible;
- index = set.indexOf(tab);
- }
-
- if (index > -1) {
- set.splice(index, 1);
- }
- },
-
- // Returns and removes the tab with the highest priority.
- shift: function () {
- let set;
- let {priority, hidden, visible} = this.tabs;
-
- let {restoreOnDemand, restorePinnedTabsOnDemand} = this.prefs;
- let restorePinned = !(restoreOnDemand && restorePinnedTabsOnDemand);
- if (restorePinned && priority.length) {
- set = priority;
- } else if (!restoreOnDemand) {
- if (visible.length) {
- set = visible;
- } else if (this.prefs.restoreHiddenTabs && hidden.length) {
- set = hidden;
- }
- }
-
- return set && set.shift();
- },
-
- // Moves a given tab from the 'hidden' to the 'visible' bucket.
- hiddenToVisible: function (tab) {
- let {hidden, visible} = this.tabs;
- let index = hidden.indexOf(tab);
-
- if (index > -1) {
- hidden.splice(index, 1);
- visible.push(tab);
- }
- },
-
- // Moves a given tab from the 'visible' to the 'hidden' bucket.
- visibleToHidden: function (tab) {
- let {visible, hidden} = this.tabs;
- let index = visible.indexOf(tab);
-
- if (index > -1) {
- visible.splice(index, 1);
- hidden.push(tab);
- }
- },
-
- /**
- * Returns true if the passed tab is in one of the sets that we're
- * restoring content in automatically.
- *
- * @param tab (<xul:tab>)
- * The tab to check
- * @returns bool
- */
- willRestoreSoon: function (tab) {
- let { priority, hidden, visible } = this.tabs;
- let { restoreOnDemand, restorePinnedTabsOnDemand,
- restoreHiddenTabs } = this.prefs;
- let restorePinned = !(restoreOnDemand && restorePinnedTabsOnDemand);
- let candidateSet = [];
-
- if (restorePinned && priority.length)
- candidateSet.push(...priority);
-
- if (!restoreOnDemand) {
- if (visible.length)
- candidateSet.push(...visible);
-
- if (restoreHiddenTabs && hidden.length)
- candidateSet.push(...hidden);
- }
-
- return candidateSet.indexOf(tab) > -1;
- },
-};
-
-// A map storing a closed window's state data until it goes aways (is GC'ed).
-// This ensures that API clients can still read (but not write) states of
-// windows they still hold a reference to but we don't.
-var DyingWindowCache = {
- _data: new WeakMap(),
-
- has: function (window) {
- return this._data.has(window);
- },
-
- get: function (window) {
- return this._data.get(window);
- },
-
- set: function (window, data) {
- this._data.set(window, data);
- },
-
- remove: function (window) {
- this._data.delete(window);
- }
-};
-
-// A weak set of dirty windows. We use it to determine which windows we need to
-// recollect data for when getCurrentState() is called.
-var DirtyWindows = {
- _data: new WeakMap(),
-
- has: function (window) {
- return this._data.has(window);
- },
-
- add: function (window) {
- return this._data.set(window, true);
- },
-
- remove: function (window) {
- this._data.delete(window);
- },
-
- clear: function (window) {
- this._data = new WeakMap();
- }
-};
-
-// The state from the previous session (after restoring pinned tabs). This
-// state is persisted and passed through to the next session during an app
-// restart to make the third party add-on warning not trash the deferred
-// session
-var LastSession = {
- _state: null,
-
- get canRestore() {
- return !!this._state;
- },
-
- getState: function () {
- return this._state;
- },
-
- setState: function (state) {
- this._state = state;
- },
-
- clear: function () {
- if (this._state) {
- this._state = null;
- Services.obs.notifyObservers(null, NOTIFY_LAST_SESSION_CLEARED, null);
- }
- }
-};
diff --git a/browser/components/sessionstore/SessionWorker.js b/browser/components/sessionstore/SessionWorker.js
deleted file mode 100644
index 7d802a7df..000000000
--- a/browser/components/sessionstore/SessionWorker.js
+++ /dev/null
@@ -1,381 +0,0 @@
-/* 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/. */
-
-/**
- * A worker dedicated to handle I/O for Session Store.
- */
-
-"use strict";
-
-importScripts("resource://gre/modules/osfile.jsm");
-
-var PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js");
-
-var File = OS.File;
-var Encoder = new TextEncoder();
-var Decoder = new TextDecoder();
-
-var worker = new PromiseWorker.AbstractWorker();
-worker.dispatch = function(method, args = []) {
- return Agent[method](...args);
-};
-worker.postMessage = function(result, ...transfers) {
- self.postMessage(result, ...transfers);
-};
-worker.close = function() {
- self.close();
-};
-
-self.addEventListener("message", msg => worker.handleMessage(msg));
-
-// The various possible states
-
-/**
- * We just started (we haven't written anything to disk yet) from
- * `Paths.clean`. The backup directory may not exist.
- */
-const STATE_CLEAN = "clean";
-/**
- * We know that `Paths.recovery` is good, either because we just read
- * it (we haven't written anything to disk yet) or because have
- * already written once to `Paths.recovery` during this session.
- * `Paths.clean` is absent or invalid. The backup directory exists.
- */
-const STATE_RECOVERY = "recovery";
-/**
- * We just started from `Paths.recoverBackupy` (we haven't written
- * anything to disk yet). Both `Paths.clean` and `Paths.recovery` are
- * absent or invalid. The backup directory exists.
- */
-const STATE_RECOVERY_BACKUP = "recoveryBackup";
-/**
- * We just started from `Paths.upgradeBackup` (we haven't written
- * anything to disk yet). Both `Paths.clean`, `Paths.recovery` and
- * `Paths.recoveryBackup` are absent or invalid. The backup directory
- * exists.
- */
-const STATE_UPGRADE_BACKUP = "upgradeBackup";
-/**
- * We just started without a valid session store file (we haven't
- * written anything to disk yet). The backup directory may not exist.
- */
-const STATE_EMPTY = "empty";
-
-var Agent = {
- // Path to the files used by the SessionWorker
- Paths: null,
-
- /**
- * The current state of the worker, as one of the following strings:
- * - "permanent", once the first write has been completed;
- * - "empty", before the first write has been completed,
- * if we have started without any sessionstore;
- * - one of "clean", "recovery", "recoveryBackup", "cleanBackup",
- * "upgradeBackup", before the first write has been completed, if
- * we have started by loading the corresponding file.
- */
- state: null,
-
- /**
- * Number of old upgrade backups that are being kept
- */
- maxUpgradeBackups: null,
-
- /**
- * Initialize (or reinitialize) the worker
- *
- * @param {string} origin Which of sessionstore.js or its backups
- * was used. One of the `STATE_*` constants defined above.
- * @param {object} paths The paths at which to find the various files.
- * @param {object} prefs The preferences the worker needs to known.
- */
- init(origin, paths, prefs = {}) {
- if (!(origin in paths || origin == STATE_EMPTY)) {
- throw new TypeError("Invalid origin: " + origin);
- }
-
- // Check that all required preference values were passed.
- for (let pref of ["maxUpgradeBackups", "maxSerializeBack", "maxSerializeForward"]) {
- if (!prefs.hasOwnProperty(pref)) {
- throw new TypeError(`Missing preference value for ${pref}`);
- }
- }
-
- this.state = origin;
- this.Paths = paths;
- this.maxUpgradeBackups = prefs.maxUpgradeBackups;
- this.maxSerializeBack = prefs.maxSerializeBack;
- this.maxSerializeForward = prefs.maxSerializeForward;
- this.upgradeBackupNeeded = paths.nextUpgradeBackup != paths.upgradeBackup;
- return {result: true};
- },
-
- /**
- * Write the session to disk.
- * Write the session to disk, performing any necessary backup
- * along the way.
- *
- * @param {object} state The state to write to disk.
- * @param {object} options
- * - performShutdownCleanup If |true|, we should
- * perform shutdown-time cleanup to ensure that private data
- * is not left lying around;
- * - isFinalWrite If |true|, write to Paths.clean instead of
- * Paths.recovery
- */
- write: function (state, options = {}) {
- let exn;
- let telemetry = {};
-
- // Cap the number of backward and forward shistory entries on shutdown.
- if (options.isFinalWrite) {
- for (let window of state.windows) {
- for (let tab of window.tabs) {
- let lower = 0;
- let upper = tab.entries.length;
-
- if (this.maxSerializeBack > -1) {
- lower = Math.max(lower, tab.index - this.maxSerializeBack - 1);
- }
- if (this.maxSerializeForward > -1) {
- upper = Math.min(upper, tab.index + this.maxSerializeForward);
- }
-
- tab.entries = tab.entries.slice(lower, upper);
- tab.index -= lower;
- }
- }
- }
-
- let stateString = JSON.stringify(state);
- let data = Encoder.encode(stateString);
-
- try {
-
- if (this.state == STATE_CLEAN || this.state == STATE_EMPTY) {
- // The backups directory may not exist yet. In all other cases,
- // we have either already read from or already written to this
- // directory, so we are satisfied that it exists.
- File.makeDir(this.Paths.backups);
- }
-
- if (this.state == STATE_CLEAN) {
- // Move $Path.clean out of the way, to avoid any ambiguity as
- // to which file is more recent.
- File.move(this.Paths.clean, this.Paths.cleanBackup);
- }
-
- let startWriteMs = Date.now();
-
- if (options.isFinalWrite) {
- // We are shutting down. At this stage, we know that
- // $Paths.clean is either absent or corrupted. If it was
- // originally present and valid, it has been moved to
- // $Paths.cleanBackup a long time ago. We can therefore write
- // with the guarantees that we erase no important data.
- File.writeAtomic(this.Paths.clean, data, {
- tmpPath: this.Paths.clean + ".tmp"
- });
- } else if (this.state == STATE_RECOVERY) {
- // At this stage, either $Paths.recovery was written >= 15
- // seconds ago during this session or we have just started
- // from $Paths.recovery left from the previous session. Either
- // way, $Paths.recovery is good. We can move $Path.backup to
- // $Path.recoveryBackup without erasing a good file with a bad
- // file.
- File.writeAtomic(this.Paths.recovery, data, {
- tmpPath: this.Paths.recovery + ".tmp",
- backupTo: this.Paths.recoveryBackup
- });
- } else {
- // In other cases, either $Path.recovery is not necessary, or
- // it doesn't exist or it has been corrupted. Regardless,
- // don't backup $Path.recovery.
- File.writeAtomic(this.Paths.recovery, data, {
- tmpPath: this.Paths.recovery + ".tmp"
- });
- }
-
- telemetry.FX_SESSION_RESTORE_WRITE_FILE_MS = Date.now() - startWriteMs;
- telemetry.FX_SESSION_RESTORE_FILE_SIZE_BYTES = data.byteLength;
-
- } catch (ex) {
- // Don't throw immediately
- exn = exn || ex;
- }
-
- // If necessary, perform an upgrade backup
- let upgradeBackupComplete = false;
- if (this.upgradeBackupNeeded
- && (this.state == STATE_CLEAN || this.state == STATE_UPGRADE_BACKUP)) {
- try {
- // If we loaded from `clean`, the file has since then been renamed to `cleanBackup`.
- let path = this.state == STATE_CLEAN ? this.Paths.cleanBackup : this.Paths.upgradeBackup;
- File.copy(path, this.Paths.nextUpgradeBackup);
- this.upgradeBackupNeeded = false;
- upgradeBackupComplete = true;
- } catch (ex) {
- // Don't throw immediately
- exn = exn || ex;
- }
-
- // Find all backups
- let iterator;
- let backups = []; // array that will contain the paths to all upgrade backup
- let upgradeBackupPrefix = this.Paths.upgradeBackupPrefix; // access for forEach callback
-
- try {
- iterator = new File.DirectoryIterator(this.Paths.backups);
- iterator.forEach(function (file) {
- if (file.path.startsWith(upgradeBackupPrefix)) {
- backups.push(file.path);
- }
- }, this);
- } catch (ex) {
- // Don't throw immediately
- exn = exn || ex;
- } finally {
- if (iterator) {
- iterator.close();
- }
- }
-
- // If too many backups exist, delete them
- if (backups.length > this.maxUpgradeBackups) {
- // Use alphanumerical sort since dates are in YYYYMMDDHHMMSS format
- backups.sort().forEach((file, i) => {
- // remove backup file if it is among the first (n-maxUpgradeBackups) files
- if (i < backups.length - this.maxUpgradeBackups) {
- File.remove(file);
- }
- });
- }
- }
-
- if (options.performShutdownCleanup && !exn) {
-
- // During shutdown, if auto-restore is disabled, we need to
- // remove possibly sensitive data that has been stored purely
- // for crash recovery. Note that this slightly decreases our
- // ability to recover from OS-level/hardware-level issue.
-
- // If an exception was raised, we assume that we still need
- // these files.
- File.remove(this.Paths.recoveryBackup);
- File.remove(this.Paths.recovery);
- }
-
- this.state = STATE_RECOVERY;
-
- if (exn) {
- throw exn;
- }
-
- return {
- result: {
- upgradeBackup: upgradeBackupComplete
- },
- telemetry: telemetry,
- };
- },
-
- /**
- * Wipes all files holding session data from disk.
- */
- wipe: function () {
-
- // Don't stop immediately in case of error.
- let exn = null;
-
- // Erase main session state file
- try {
- File.remove(this.Paths.clean);
- } catch (ex) {
- // Don't stop immediately.
- exn = exn || ex;
- }
-
- // Wipe the Session Restore directory
- try {
- this._wipeFromDir(this.Paths.backups, null);
- } catch (ex) {
- exn = exn || ex;
- }
-
- try {
- File.removeDir(this.Paths.backups);
- } catch (ex) {
- exn = exn || ex;
- }
-
- // Wipe legacy Ression Restore files from the profile directory
- try {
- this._wipeFromDir(OS.Constants.Path.profileDir, "sessionstore.bak");
- } catch (ex) {
- exn = exn || ex;
- }
-
-
- this.state = STATE_EMPTY;
- if (exn) {
- throw exn;
- }
-
- return { result: true };
- },
-
- /**
- * Wipe a number of files from a directory.
- *
- * @param {string} path The directory.
- * @param {string|null} prefix If provided, only remove files whose
- * name starts with a specific prefix.
- */
- _wipeFromDir: function(path, prefix) {
- // Sanity check
- if (typeof prefix == "undefined" || prefix == "") {
- throw new TypeError();
- }
-
- let exn = null;
-
- let iterator = new File.DirectoryIterator(path);
- try {
- if (!iterator.exists()) {
- return;
- }
- for (let entry in iterator) {
- if (entry.isDir) {
- continue;
- }
- if (!prefix || entry.name.startsWith(prefix)) {
- try {
- File.remove(entry.path);
- } catch (ex) {
- // Don't stop immediately
- exn = exn || ex;
- }
- }
- }
-
- if (exn) {
- throw exn;
- }
- } finally {
- iterator.close();
- }
- },
-};
-
-function isNoSuchFileEx(aReason) {
- return aReason instanceof OS.File.Error && aReason.becauseNoSuchFile;
-}
-
-/**
- * Estimate the number of bytes that a data structure will use on disk
- * once serialized.
- */
-function getByteLength(str) {
- return Encoder.encode(JSON.stringify(str)).byteLength;
-}
diff --git a/browser/components/sessionstore/SessionWorker.jsm b/browser/components/sessionstore/SessionWorker.jsm
deleted file mode 100644
index b26e531ac..000000000
--- a/browser/components/sessionstore/SessionWorker.jsm
+++ /dev/null
@@ -1,25 +0,0 @@
-/* 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";
-
-/**
- * Interface to a dedicated thread handling I/O
- */
-
-const Cu = Components.utils;
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cr = Components.results;
-
-Cu.import("resource://gre/modules/PromiseWorker.jsm", this);
-Cu.import("resource://gre/modules/osfile.jsm", this);
-
-this.EXPORTED_SYMBOLS = ["SessionWorker"];
-
-this.SessionWorker = new BasePromiseWorker("resource:///modules/sessionstore/SessionWorker.js");
-// As the Session Worker performs I/O, we can receive instances of
-// OS.File.Error, so we need to install a decoder.
-this.SessionWorker.ExceptionHandlers["OS.File.Error"] = OS.File.Error.fromMsg;
-
diff --git a/browser/components/sessionstore/StartupPerformance.jsm b/browser/components/sessionstore/StartupPerformance.jsm
deleted file mode 100644
index d1b77a237..000000000
--- a/browser/components/sessionstore/StartupPerformance.jsm
+++ /dev/null
@@ -1,234 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["StartupPerformance"];
-
-const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
-
-XPCOMUtils.defineLazyModuleGetter(this, "Services",
- "resource://gre/modules/Services.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "console",
- "resource://gre/modules/Console.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
- "resource://gre/modules/Timer.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
- "resource://gre/modules/Timer.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Promise",
- "resource://gre/modules/Promise.jsm");
-
-const COLLECT_RESULTS_AFTER_MS = 10000;
-
-const OBSERVED_TOPICS = ["sessionstore-restoring-on-startup", "sessionstore-initiating-manual-restore"];
-
-this.StartupPerformance = {
- /**
- * Once we have finished restoring initial tabs, we broadcast on this topic.
- */
- RESTORED_TOPIC: "sessionstore-finished-restoring-initial-tabs",
-
- // Instant at which we have started restoration (notification "sessionstore-restoring-on-startup")
- _startTimeStamp: null,
-
- // Latest instant at which we have finished restoring a tab (DOM event "SSTabRestored")
- _latestRestoredTimeStamp: null,
-
- // A promise resolved once we have finished restoring all the startup tabs.
- _promiseFinished: null,
-
- // Function `resolve()` for `_promiseFinished`.
- _resolveFinished: null,
-
- // A timer
- _deadlineTimer: null,
-
- // `true` once the timer has fired
- _hasFired: false,
-
- // `true` once we are restored
- _isRestored: false,
-
- // Statistics on the session we need to restore.
- _totalNumberOfEagerTabs: 0,
- _totalNumberOfTabs: 0,
- _totalNumberOfWindows: 0,
-
- init: function() {
- for (let topic of OBSERVED_TOPICS) {
- Services.obs.addObserver(this, topic, false);
- }
- },
-
- /**
- * Return the timestamp at which we finished restoring the latest tab.
- *
- * This information is not really interesting until we have finished restoring
- * tabs.
- */
- get latestRestoredTimeStamp() {
- return this._latestRestoredTimeStamp;
- },
-
- /**
- * `true` once we have finished restoring startup tabs.
- */
- get isRestored() {
- return this._isRestored;
- },
-
- // Called when restoration starts.
- // Record the start timestamp, setup the timer and `this._promiseFinished`.
- // Behavior is unspecified if there was already an ongoing measure.
- _onRestorationStarts: function(isAutoRestore) {
- this._latestRestoredTimeStamp = this._startTimeStamp = Date.now();
- this._totalNumberOfEagerTabs = 0;
- this._totalNumberOfTabs = 0;
- this._totalNumberOfWindows = 0;
-
- // While we may restore several sessions in a single run of the browser,
- // that's a very unusual case, and not really worth measuring, so let's
- // stop listening for further restorations.
-
- for (let topic of OBSERVED_TOPICS) {
- Services.obs.removeObserver(this, topic);
- }
-
- Services.obs.addObserver(this, "sessionstore-single-window-restored", false);
- this._promiseFinished = new Promise(resolve => {
- this._resolveFinished = resolve;
- });
- this._promiseFinished.then(() => {
- try {
- this._isRestored = true;
- Services.obs.notifyObservers(null, this.RESTORED_TOPIC, "");
-
- if (this._latestRestoredTimeStamp == this._startTimeStamp) {
- // Apparently, we haven't restored any tab.
- return;
- }
-
- // Once we are done restoring tabs, update Telemetry.
- let histogramName = isAutoRestore ?
- "FX_SESSION_RESTORE_AUTO_RESTORE_DURATION_UNTIL_EAGER_TABS_RESTORED_MS" :
- "FX_SESSION_RESTORE_MANUAL_RESTORE_DURATION_UNTIL_EAGER_TABS_RESTORED_MS";
- let histogram = Services.telemetry.getHistogramById(histogramName);
- let delta = this._latestRestoredTimeStamp - this._startTimeStamp;
- histogram.add(delta);
-
- Services.telemetry.getHistogramById("FX_SESSION_RESTORE_NUMBER_OF_EAGER_TABS_RESTORED").add(this._totalNumberOfEagerTabs);
- Services.telemetry.getHistogramById("FX_SESSION_RESTORE_NUMBER_OF_TABS_RESTORED").add(this._totalNumberOfTabs);
- Services.telemetry.getHistogramById("FX_SESSION_RESTORE_NUMBER_OF_WINDOWS_RESTORED").add(this._totalNumberOfWindows);
-
- // Reset
- this._startTimeStamp = null;
- } catch (ex) {
- console.error("StartupPerformance: error after resolving promise", ex);
- }
- });
- },
-
- _startTimer: function() {
- if (this._hasFired) {
- return;
- }
- if (this._deadlineTimer) {
- clearTimeout(this._deadlineTimer);
- }
- this._deadlineTimer = setTimeout(() => {
- try {
- this._resolveFinished();
- } catch (ex) {
- console.error("StartupPerformance: Error in timeout handler", ex);
- } finally {
- // Clean up.
- this._deadlineTimer = null;
- this._hasFired = true;
- this._resolveFinished = null;
- Services.obs.removeObserver(this, "sessionstore-single-window-restored");
- }
- }, COLLECT_RESULTS_AFTER_MS);
- },
-
- observe: function(subject, topic, details) {
- try {
- switch (topic) {
- case "sessionstore-restoring-on-startup":
- this._onRestorationStarts(true);
- break;
- case "sessionstore-initiating-manual-restore":
- this._onRestorationStarts(false);
- break;
- case "sessionstore-single-window-restored": {
- // Session Restore has just opened a window with (initially empty) tabs.
- // Some of these tabs will be restored eagerly, while others will be
- // restored on demand. The process becomes usable only when all windows
- // have finished restored their eager tabs.
- //
- // While it would be possible to track the restoration of each tab
- // from within SessionRestore to determine exactly when the process
- // becomes usable, experience shows that this is too invasive. Rather,
- // we employ the following heuristic:
- // - we maintain a timer of `COLLECT_RESULTS_AFTER_MS` that we expect
- // will be triggered only once all tabs have been restored;
- // - whenever we restore a new window (hence a bunch of eager tabs),
- // we postpone the timer to ensure that the new eager tabs have
- // `COLLECT_RESULTS_AFTER_MS` to be restored;
- // - whenever a tab is restored, we update
- // `this._latestRestoredTimeStamp`;
- // - after `COLLECT_RESULTS_AFTER_MS`, we collect the final version
- // of `this._latestRestoredTimeStamp`, and use it to determine the
- // entire duration of the collection.
- //
- // Note that this heuristic may be inaccurate if a user clicks
- // immediately on a restore-on-demand tab before the end of
- // `COLLECT_RESULTS_AFTER_MS`. We assume that this will not
- // affect too much the results.
- //
- // Reset the delay, to give the tabs a little (more) time to restore.
- this._startTimer();
-
- this._totalNumberOfWindows += 1;
-
- // Observe the restoration of all tabs. We assume that all tabs of this
- // window will have been restored before `COLLECT_RESULTS_AFTER_MS`.
- // The last call to `observer` will let us determine how long it took
- // to reach that point.
- let win = subject;
-
- let observer = (event) => {
- // We don't care about tab restorations that are due to
- // a browser flipping from out-of-main-process to in-main-process
- // or vice-versa. We only care about restorations that are due
- // to the user switching to a lazily restored tab, or for tabs
- // that are restoring eagerly.
- if (!event.detail.isRemotenessUpdate) {
- this._latestRestoredTimeStamp = Date.now();
- this._totalNumberOfEagerTabs += 1;
- }
- };
- win.gBrowser.tabContainer.addEventListener("SSTabRestored", observer);
- this._totalNumberOfTabs += win.gBrowser.tabContainer.itemCount;
-
- // Once we have finished collecting the results, clean up the observers.
- this._promiseFinished.then(() => {
- if (!win.gBrowser.tabContainer) {
- // May be undefined during shutdown and/or some tests.
- return;
- }
- win.gBrowser.tabContainer.removeEventListener("SSTabRestored", observer);
- });
- }
- break;
- default:
- throw new Error(`Unexpected topic ${topic}`);
- }
- } catch (ex) {
- console.error("StartupPerformance error", ex, ex.stack);
- throw ex;
- }
- }
-};
diff --git a/browser/components/sessionstore/TabAttributes.jsm b/browser/components/sessionstore/TabAttributes.jsm
deleted file mode 100644
index 8a29680f4..000000000
--- a/browser/components/sessionstore/TabAttributes.jsm
+++ /dev/null
@@ -1,74 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["TabAttributes"];
-
-// We never want to directly read or write these attributes.
-// 'image' should not be accessed directly but handled by using the
-// gBrowser.getIcon()/setIcon() methods.
-// 'muted' should not be accessed directly but handled by using the
-// tab.linkedBrowser.audioMuted/toggleMuteAudio methods.
-// 'pending' is used internal by sessionstore and managed accordingly.
-// 'iconLoadingPrincipal' is same as 'image' that it should be handled by
-// using the gBrowser.getIcon()/setIcon() methods.
-const ATTRIBUTES_TO_SKIP = new Set(["image", "muted", "pending", "iconLoadingPrincipal"]);
-
-// A set of tab attributes to persist. We will read a given list of tab
-// attributes when collecting tab data and will re-set those attributes when
-// the given tab data is restored to a new tab.
-this.TabAttributes = Object.freeze({
- persist: function (name) {
- return TabAttributesInternal.persist(name);
- },
-
- get: function (tab) {
- return TabAttributesInternal.get(tab);
- },
-
- set: function (tab, data = {}) {
- TabAttributesInternal.set(tab, data);
- }
-});
-
-var TabAttributesInternal = {
- _attrs: new Set(),
-
- persist: function (name) {
- if (this._attrs.has(name) || ATTRIBUTES_TO_SKIP.has(name)) {
- return false;
- }
-
- this._attrs.add(name);
- return true;
- },
-
- get: function (tab) {
- let data = {};
-
- for (let name of this._attrs) {
- if (tab.hasAttribute(name)) {
- data[name] = tab.getAttribute(name);
- }
- }
-
- return data;
- },
-
- set: function (tab, data = {}) {
- // Clear attributes.
- for (let name of this._attrs) {
- tab.removeAttribute(name);
- }
-
- // Set attributes.
- for (let name in data) {
- if (!ATTRIBUTES_TO_SKIP.has(name)) {
- tab.setAttribute(name, data[name]);
- }
- }
- }
-};
-
diff --git a/browser/components/sessionstore/TabState.jsm b/browser/components/sessionstore/TabState.jsm
deleted file mode 100644
index f22c52fe3..000000000
--- a/browser/components/sessionstore/TabState.jsm
+++ /dev/null
@@ -1,196 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["TabState"];
-
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
-
-XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
- "resource:///modules/sessionstore/PrivacyFilter.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
- "resource:///modules/sessionstore/TabStateCache.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
- "resource:///modules/sessionstore/TabAttributes.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Utils",
- "resource://gre/modules/sessionstore/Utils.jsm");
-
-/**
- * Module that contains tab state collection methods.
- */
-this.TabState = Object.freeze({
- update: function (browser, data) {
- TabStateInternal.update(browser, data);
- },
-
- collect: function (tab) {
- return TabStateInternal.collect(tab);
- },
-
- clone: function (tab) {
- return TabStateInternal.clone(tab);
- },
-
- copyFromCache(browser, tabData, options) {
- TabStateInternal.copyFromCache(browser, tabData, options);
- },
-});
-
-var TabStateInternal = {
- /**
- * Processes a data update sent by the content script.
- */
- update: function (browser, {data}) {
- TabStateCache.update(browser, data);
- },
-
- /**
- * Collect data related to a single tab, synchronously.
- *
- * @param tab
- * tabbrowser tab
- *
- * @returns {TabData} An object with the data for this tab. If the
- * tab has not been invalidated since the last call to
- * collect(aTab), the same object is returned.
- */
- collect: function (tab) {
- return this._collectBaseTabData(tab);
- },
-
- /**
- * Collect data related to a single tab, including private data.
- * Use with caution.
- *
- * @param tab
- * tabbrowser tab
- *
- * @returns {object} An object with the data for this tab. This data is never
- * cached, it will always be read from the tab and thus be
- * up-to-date.
- */
- clone: function (tab) {
- return this._collectBaseTabData(tab, {includePrivateData: true});
- },
-
- /**
- * Collects basic tab data for a given tab.
- *
- * @param tab
- * tabbrowser tab
- * @param options (object)
- * {includePrivateData: true} to always include private data
- *
- * @returns {object} An object with the basic data for this tab.
- */
- _collectBaseTabData: function (tab, options) {
- let tabData = { entries: [], lastAccessed: tab.lastAccessed };
- let browser = tab.linkedBrowser;
-
- if (tab.pinned) {
- tabData.pinned = true;
- }
-
- tabData.hidden = tab.hidden;
-
- if (browser.audioMuted) {
- tabData.muted = true;
- tabData.muteReason = tab.muteReason;
- }
-
- // Save tab attributes.
- tabData.attributes = TabAttributes.get(tab);
-
- if (tab.__SS_extdata) {
- tabData.extData = tab.__SS_extdata;
- }
-
- // Copy data from the tab state cache only if the tab has fully finished
- // restoring. We don't want to overwrite data contained in __SS_data.
- this.copyFromCache(browser, tabData, options);
-
- // After copyFromCache() was called we check for properties that are kept
- // in the cache only while the tab is pending or restoring. Once that
- // happened those properties will be removed from the cache and will
- // be read from the tab/browser every time we collect data.
-
- // Store the tab icon.
- if (!("image" in tabData)) {
- let tabbrowser = tab.ownerGlobal.gBrowser;
- tabData.image = tabbrowser.getIcon(tab);
- }
-
- // Store the serialized contentPrincipal of this tab to use for the icon.
- if (!("iconLoadingPrincipal" in tabData)) {
- tabData.iconLoadingPrincipal = Utils.serializePrincipal(browser.contentPrincipal);
- }
-
- // If there is a userTypedValue set, then either the user has typed something
- // in the URL bar, or a new tab was opened with a URI to load.
- // If so, we also track whether we were still in the process of loading something.
- if (!("userTypedValue" in tabData) && browser.userTypedValue) {
- tabData.userTypedValue = browser.userTypedValue;
- // We always used to keep track of the loading state as an integer, where
- // '0' indicated the user had typed since the last load (or no load was
- // ongoing), and any positive value indicated we had started a load since
- // the last time the user typed in the URL bar. Mimic this to keep the
- // session store representation in sync, even though we now represent this
- // more explicitly:
- tabData.userTypedClear = browser.didStartLoadSinceLastUserTyping() ? 1 : 0;
- }
-
- return tabData;
- },
-
- /**
- * Copy data for the given |browser| from the cache to |tabData|.
- *
- * @param browser (xul:browser)
- * The browser belonging to the given |tabData| object.
- * @param tabData (object)
- * The tab data belonging to the given |tab|.
- * @param options (object)
- * {includePrivateData: true} to always include private data
- */
- copyFromCache(browser, tabData, options = {}) {
- let data = TabStateCache.get(browser);
- if (!data) {
- return;
- }
-
- // The caller may explicitly request to omit privacy checks.
- let includePrivateData = options && options.includePrivateData;
- let isPinned = !!tabData.pinned;
-
- for (let key of Object.keys(data)) {
- let value = data[key];
-
- // Filter sensitive data according to the current privacy level.
- if (!includePrivateData) {
- if (key === "storage") {
- value = PrivacyFilter.filterSessionStorageData(value);
- } else if (key === "formdata") {
- value = PrivacyFilter.filterFormData(value);
- }
- }
-
- if (key === "history") {
- tabData.entries = value.entries;
-
- if (value.hasOwnProperty("userContextId")) {
- tabData.userContextId = value.userContextId;
- }
-
- if (value.hasOwnProperty("index")) {
- tabData.index = value.index;
- }
- } else {
- tabData[key] = value;
- }
- }
- }
-};
diff --git a/browser/components/sessionstore/TabStateCache.jsm b/browser/components/sessionstore/TabStateCache.jsm
deleted file mode 100644
index 9bed315a0..000000000
--- a/browser/components/sessionstore/TabStateCache.jsm
+++ /dev/null
@@ -1,163 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["TabStateCache"];
-
-/**
- * A cache for tabs data.
- *
- * This cache implements a weak map from tabs (as XUL elements)
- * to tab data (as objects).
- *
- * Note that we should never cache private data, as:
- * - that data is used very seldom by SessionStore;
- * - caching private data in addition to public data is memory consuming.
- */
-this.TabStateCache = Object.freeze({
- /**
- * Retrieves cached data for a given |tab| or associated |browser|.
- *
- * @param browserOrTab (xul:tab or xul:browser)
- * The tab or browser to retrieve cached data for.
- * @return (object)
- * The cached data stored for the given |tab|
- * or associated |browser|.
- */
- get: function (browserOrTab) {
- return TabStateCacheInternal.get(browserOrTab);
- },
-
- /**
- * Updates cached data for a given |tab| or associated |browser|.
- *
- * @param browserOrTab (xul:tab or xul:browser)
- * The tab or browser belonging to the given tab data.
- * @param newData (object)
- * The new data to be stored for the given |tab|
- * or associated |browser|.
- */
- update: function (browserOrTab, newData) {
- TabStateCacheInternal.update(browserOrTab, newData);
- }
-});
-
-var TabStateCacheInternal = {
- _data: new WeakMap(),
-
- /**
- * Retrieves cached data for a given |tab| or associated |browser|.
- *
- * @param browserOrTab (xul:tab or xul:browser)
- * The tab or browser to retrieve cached data for.
- * @return (object)
- * The cached data stored for the given |tab|
- * or associated |browser|.
- */
- get: function (browserOrTab) {
- return this._data.get(browserOrTab.permanentKey);
- },
-
- /**
- * Helper function used by update (see below). For message size
- * optimization sometimes we don't update the whole session storage
- * only the values that have been changed.
- *
- * @param data (object)
- * The cached data where we want to update the changes.
- * @param change (object)
- * The actual changed values per domain.
- */
- updatePartialStorageChange: function (data, change) {
- if (!data.storage) {
- data.storage = {};
- }
-
- let storage = data.storage;
- for (let domain of Object.keys(change)) {
- for (let key of Object.keys(change[domain])) {
- let value = change[domain][key];
- if (value === null) {
- if (storage[domain] && storage[domain][key]) {
- delete storage[domain][key];
- }
- } else {
- if (!storage[domain]) {
- storage[domain] = {};
- }
- storage[domain][key] = value;
- }
- }
- }
- },
-
- /**
- * Helper function used by update (see below). For message size
- * optimization sometimes we don't update the whole browser history
- * only the current index and the tail of the history from a certain
- * index (specified by change.fromIdx)
- *
- * @param data (object)
- * The cached data where we want to update the changes.
- * @param change (object)
- * Object containing the tail of the history array, and
- * some additional metadata.
- */
- updatePartialHistoryChange: function (data, change) {
- const kLastIndex = Number.MAX_SAFE_INTEGER - 1;
-
- if (!data.history) {
- data.history = { entries: [] };
- }
-
- let history = data.history;
- for (let key of Object.keys(change)) {
- if (key == "entries") {
- if (change.fromIdx != kLastIndex) {
- history.entries.splice(change.fromIdx + 1);
- while (change.entries.length) {
- history.entries.push(change.entries.shift());
- }
- }
- } else if (key != "fromIndex") {
- history[key] = change[key];
- }
- }
- },
-
- /**
- * Updates cached data for a given |tab| or associated |browser|.
- *
- * @param browserOrTab (xul:tab or xul:browser)
- * The tab or browser belonging to the given tab data.
- * @param newData (object)
- * The new data to be stored for the given |tab|
- * or associated |browser|.
- */
- update: function (browserOrTab, newData) {
- let data = this._data.get(browserOrTab.permanentKey) || {};
-
- for (let key of Object.keys(newData)) {
- if (key == "storagechange") {
- this.updatePartialStorageChange(data, newData.storagechange);
- continue;
- }
-
- if (key == "historychange") {
- this.updatePartialHistoryChange(data, newData.historychange);
- continue;
- }
-
- let value = newData[key];
- if (value === null) {
- delete data[key];
- } else {
- data[key] = value;
- }
- }
-
- this._data.set(browserOrTab.permanentKey, data);
- }
-};
diff --git a/browser/components/sessionstore/TabStateFlusher.jsm b/browser/components/sessionstore/TabStateFlusher.jsm
deleted file mode 100644
index 6397efe9d..000000000
--- a/browser/components/sessionstore/TabStateFlusher.jsm
+++ /dev/null
@@ -1,184 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["TabStateFlusher"];
-
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/Promise.jsm", this);
-
-/**
- * A module that enables async flushes. Updates from frame scripts are
- * throttled to be sent only once per second. If an action wants a tab's latest
- * state without waiting for a second then it can request an async flush and
- * wait until the frame scripts reported back. At this point the parent has the
- * latest data and the action can continue.
- */
-this.TabStateFlusher = Object.freeze({
- /**
- * Requests an async flush for the given browser. Returns a promise that will
- * resolve when we heard back from the content process and the parent has
- * all the latest data.
- */
- flush(browser) {
- return TabStateFlusherInternal.flush(browser);
- },
-
- /**
- * Requests an async flush for all browsers of a given window. Returns a Promise
- * that will resolve when we've heard back from all browsers.
- */
- flushWindow(window) {
- return TabStateFlusherInternal.flushWindow(window);
- },
-
- /**
- * Resolves the flush request with the given flush ID.
- *
- * @param browser (<xul:browser>)
- * The browser for which the flush is being resolved.
- * @param flushID (int)
- * The ID of the flush that was sent to the browser.
- * @param success (bool, optional)
- * Whether or not the flush succeeded.
- * @param message (string, optional)
- * An error message that will be sent to the Console in the
- * event that a flush failed.
- */
- resolve(browser, flushID, success=true, message="") {
- TabStateFlusherInternal.resolve(browser, flushID, success, message);
- },
-
- /**
- * Resolves all active flush requests for a given browser. This should be
- * used when the content process crashed or the final update message was
- * seen. In those cases we can't guarantee to ever hear back from the frame
- * script so we just resolve all requests instead of discarding them.
- *
- * @param browser (<xul:browser>)
- * The browser for which all flushes are being resolved.
- * @param success (bool, optional)
- * Whether or not the flushes succeeded.
- * @param message (string, optional)
- * An error message that will be sent to the Console in the
- * event that the flushes failed.
- */
- resolveAll(browser, success=true, message="") {
- TabStateFlusherInternal.resolveAll(browser, success, message);
- }
-});
-
-var TabStateFlusherInternal = {
- // Stores the last request ID.
- _lastRequestID: 0,
-
- // A map storing all active requests per browser.
- _requests: new WeakMap(),
-
- /**
- * Requests an async flush for the given browser. Returns a promise that will
- * resolve when we heard back from the content process and the parent has
- * all the latest data.
- */
- flush(browser) {
- let id = ++this._lastRequestID;
- let mm = browser.messageManager;
- mm.sendAsyncMessage("SessionStore:flush", {id});
-
- // Retrieve active requests for given browser.
- let permanentKey = browser.permanentKey;
- let perBrowserRequests = this._requests.get(permanentKey) || new Map();
-
- return new Promise(resolve => {
- // Store resolve() so that we can resolve the promise later.
- perBrowserRequests.set(id, resolve);
-
- // Update the flush requests stored per browser.
- this._requests.set(permanentKey, perBrowserRequests);
- });
- },
-
- /**
- * Requests an async flush for all browsers of a given window. Returns a Promise
- * that will resolve when we've heard back from all browsers.
- */
- flushWindow(window) {
- let browsers = window.gBrowser.browsers;
- let promises = browsers.map((browser) => this.flush(browser));
- return Promise.all(promises);
- },
-
- /**
- * Resolves the flush request with the given flush ID.
- *
- * @param browser (<xul:browser>)
- * The browser for which the flush is being resolved.
- * @param flushID (int)
- * The ID of the flush that was sent to the browser.
- * @param success (bool, optional)
- * Whether or not the flush succeeded.
- * @param message (string, optional)
- * An error message that will be sent to the Console in the
- * event that a flush failed.
- */
- resolve(browser, flushID, success=true, message="") {
- // Nothing to do if there are no pending flushes for the given browser.
- if (!this._requests.has(browser.permanentKey)) {
- return;
- }
-
- // Retrieve active requests for given browser.
- let perBrowserRequests = this._requests.get(browser.permanentKey);
- if (!perBrowserRequests.has(flushID)) {
- return;
- }
-
- if (!success) {
- Cu.reportError("Failed to flush browser: " + message);
- }
-
- // Resolve the request with the given id.
- let resolve = perBrowserRequests.get(flushID);
- perBrowserRequests.delete(flushID);
- resolve(success);
- },
-
- /**
- * Resolves all active flush requests for a given browser. This should be
- * used when the content process crashed or the final update message was
- * seen. In those cases we can't guarantee to ever hear back from the frame
- * script so we just resolve all requests instead of discarding them.
- *
- * @param browser (<xul:browser>)
- * The browser for which all flushes are being resolved.
- * @param success (bool, optional)
- * Whether or not the flushes succeeded.
- * @param message (string, optional)
- * An error message that will be sent to the Console in the
- * event that the flushes failed.
- */
- resolveAll(browser, success=true, message="") {
- // Nothing to do if there are no pending flushes for the given browser.
- if (!this._requests.has(browser.permanentKey)) {
- return;
- }
-
- // Retrieve active requests for given browser.
- let perBrowserRequests = this._requests.get(browser.permanentKey);
-
- if (!success) {
- Cu.reportError("Failed to flush browser: " + message);
- }
-
- // Resolve all requests.
- for (let resolve of perBrowserRequests.values()) {
- resolve(success);
- }
-
- // Clear active requests.
- perBrowserRequests.clear();
- }
-};
diff --git a/browser/components/sessionstore/content/aboutSessionRestore.js b/browser/components/sessionstore/content/aboutSessionRestore.js
deleted file mode 100644
index 8f265235d..000000000
--- a/browser/components/sessionstore/content/aboutSessionRestore.js
+++ /dev/null
@@ -1,373 +0,0 @@
-/* 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 {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
- "resource://gre/modules/AppConstants.jsm");
-
-var gStateObject;
-var gTreeData;
-
-// Page initialization
-
-window.onload = function() {
- // pages used by this script may have a link that needs to be updated to
- // the in-product link.
- let anchor = document.getElementById("linkMoreTroubleshooting");
- if (anchor) {
- let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
- anchor.setAttribute("href", baseURL + "troubleshooting");
- }
-
- // wire up click handlers for the radio buttons if they exist.
- for (let radioId of ["radioRestoreAll", "radioRestoreChoose"]) {
- let button = document.getElementById(radioId);
- if (button) {
- button.addEventListener("click", updateTabListVisibility);
- }
- }
-
- // the crashed session state is kept inside a textbox so that SessionStore picks it up
- // (for when the tab is closed or the session crashes right again)
- var sessionData = document.getElementById("sessionData");
- if (!sessionData.value) {
- document.getElementById("errorTryAgain").disabled = true;
- return;
- }
-
- try {
- gStateObject = JSON.parse(sessionData.value);
- } catch (e) {
- Cu.reportError(e);
- }
-
- // make sure the data is tracked to be restored in case of a subsequent crash
- var event = document.createEvent("UIEvents");
- event.initUIEvent("input", true, true, window, 0);
- sessionData.dispatchEvent(event);
-
- initTreeView();
-
- document.getElementById("errorTryAgain").focus();
-};
-
-function isTreeViewVisible() {
- let tabList = document.querySelector(".tree-container");
- return tabList.hasAttribute("available");
-}
-
-function initTreeView() {
- // If we aren't visible we initialize as we are made visible (and it's OK
- // to initialize multiple times)
- if (!isTreeViewVisible()) {
- return;
- }
- var tabList = document.getElementById("tabList");
- var winLabel = tabList.getAttribute("_window_label");
-
- gTreeData = [];
- if (gStateObject) {
- gStateObject.windows.forEach(function(aWinData, aIx) {
- var winState = {
- label: winLabel.replace("%S", (aIx + 1)),
- open: true,
- checked: true,
- ix: aIx
- };
- winState.tabs = aWinData.tabs.map(function(aTabData) {
- var entry = aTabData.entries[aTabData.index - 1] || { url: "about:blank" };
- var iconURL = aTabData.image || null;
- // don't initiate a connection just to fetch a favicon (see bug 462863)
- if (/^https?:/.test(iconURL))
- iconURL = "moz-anno:favicon:" + iconURL;
- return {
- label: entry.title || entry.url,
- checked: true,
- src: iconURL,
- parent: winState
- };
- });
- gTreeData.push(winState);
- for (let tab of winState.tabs)
- gTreeData.push(tab);
- }, this);
- }
-
- tabList.view = treeView;
- tabList.view.selection.select(0);
-}
-
-// User actions
-function updateTabListVisibility() {
- let tabList = document.querySelector(".tree-container");
- let container = document.querySelector(".container");
- if (document.getElementById("radioRestoreChoose").checked) {
- tabList.setAttribute("available", "true");
- container.classList.add("restore-chosen");
- } else {
- tabList.removeAttribute("available");
- container.classList.remove("restore-chosen");
- }
- initTreeView();
-}
-
-function restoreSession() {
- Services.obs.notifyObservers(null, "sessionstore-initiating-manual-restore", "");
- document.getElementById("errorTryAgain").disabled = true;
-
- if (isTreeViewVisible()) {
- if (!gTreeData.some(aItem => aItem.checked)) {
- // This should only be possible when we have no "cancel" button, and thus
- // the "Restore session" button always remains enabled. In that case and
- // when nothing is selected, we just want a new session.
- startNewSession();
- return;
- }
-
- // remove all unselected tabs from the state before restoring it
- var ix = gStateObject.windows.length - 1;
- for (var t = gTreeData.length - 1; t >= 0; t--) {
- if (treeView.isContainer(t)) {
- if (gTreeData[t].checked === 0)
- // this window will be restored partially
- gStateObject.windows[ix].tabs =
- gStateObject.windows[ix].tabs.filter((aTabData, aIx) =>
- gTreeData[t].tabs[aIx].checked);
- else if (!gTreeData[t].checked)
- // this window won't be restored at all
- gStateObject.windows.splice(ix, 1);
- ix--;
- }
- }
- }
- var stateString = JSON.stringify(gStateObject);
-
- var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
- var top = getBrowserWindow();
-
- // if there's only this page open, reuse the window for restoring the session
- if (top.gBrowser.tabs.length == 1) {
- ss.setWindowState(top, stateString, true);
- return;
- }
-
- // restore the session into a new window and close the current tab
- var newWindow = top.openDialog(top.location, "_blank", "chrome,dialog=no,all");
-
- var obs = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
- obs.addObserver(function observe(win, topic) {
- if (win != newWindow) {
- return;
- }
-
- obs.removeObserver(observe, topic);
- ss.setWindowState(newWindow, stateString, true);
-
- var tabbrowser = top.gBrowser;
- var tabIndex = tabbrowser.getBrowserIndexForDocument(document);
- tabbrowser.removeTab(tabbrowser.tabs[tabIndex]);
- }, "browser-delayed-startup-finished", false);
-}
-
-function startNewSession() {
- var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
- if (prefBranch.getIntPref("browser.startup.page") == 0)
- getBrowserWindow().gBrowser.loadURI("about:blank");
- else
- getBrowserWindow().BrowserHome();
-}
-
-function onListClick(aEvent) {
- // don't react to right-clicks
- if (aEvent.button == 2)
- return;
-
- if (!treeView.treeBox) {
- return;
- }
- var cell = treeView.treeBox.getCellAt(aEvent.clientX, aEvent.clientY);
- if (cell.col) {
- // Restore this specific tab in the same window for middle/double/accel clicking
- // on a tab's title.
- let accelKey = AppConstants.platform == "macosx" ?
- aEvent.metaKey :
- aEvent.ctrlKey;
- if ((aEvent.button == 1 || aEvent.button == 0 && aEvent.detail == 2 || accelKey) &&
- cell.col.id == "title" &&
- !treeView.isContainer(cell.row)) {
- restoreSingleTab(cell.row, aEvent.shiftKey);
- aEvent.stopPropagation();
- }
- else if (cell.col.id == "restore")
- toggleRowChecked(cell.row);
- }
-}
-
-function onListKeyDown(aEvent) {
- switch (aEvent.keyCode)
- {
- case KeyEvent.DOM_VK_SPACE:
- toggleRowChecked(document.getElementById("tabList").currentIndex);
- // Prevent page from scrolling on the space key.
- aEvent.preventDefault();
- break;
- case KeyEvent.DOM_VK_RETURN:
- var ix = document.getElementById("tabList").currentIndex;
- if (aEvent.ctrlKey && !treeView.isContainer(ix))
- restoreSingleTab(ix, aEvent.shiftKey);
- break;
- }
-}
-
-// Helper functions
-
-function getBrowserWindow() {
- return window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem
- .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
-}
-
-function toggleRowChecked(aIx) {
- function isChecked(aItem) {
- return aItem.checked;
- }
-
- var item = gTreeData[aIx];
- item.checked = !item.checked;
- treeView.treeBox.invalidateRow(aIx);
-
- if (treeView.isContainer(aIx)) {
- // (un)check all tabs of this window as well
- for (let tab of item.tabs) {
- tab.checked = item.checked;
- treeView.treeBox.invalidateRow(gTreeData.indexOf(tab));
- }
- }
- else {
- // update the window's checkmark as well (0 means "partially checked")
- item.parent.checked = item.parent.tabs.every(isChecked) ? true :
- item.parent.tabs.some(isChecked) ? 0 : false;
- treeView.treeBox.invalidateRow(gTreeData.indexOf(item.parent));
- }
-
- // we only disable the button when there's no cancel button.
- if (document.getElementById("errorCancel")) {
- document.getElementById("errorTryAgain").disabled = !gTreeData.some(isChecked);
- }
-}
-
-function restoreSingleTab(aIx, aShifted) {
- var tabbrowser = getBrowserWindow().gBrowser;
- var newTab = tabbrowser.addTab();
- var item = gTreeData[aIx];
-
- var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
- var tabState = gStateObject.windows[item.parent.ix]
- .tabs[aIx - gTreeData.indexOf(item.parent) - 1];
- // ensure tab would be visible on the tabstrip.
- tabState.hidden = false;
- ss.setTabState(newTab, JSON.stringify(tabState));
-
- // respect the preference as to whether to select the tab (the Shift key inverses)
- var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
- if (prefBranch.getBoolPref("browser.tabs.loadInBackground") != !aShifted)
- tabbrowser.selectedTab = newTab;
-}
-
-// Tree controller
-
-var treeView = {
- treeBox: null,
- selection: null,
-
- get rowCount() { return gTreeData.length; },
- setTree: function(treeBox) { this.treeBox = treeBox; },
- getCellText: function(idx, column) { return gTreeData[idx].label; },
- isContainer: function(idx) {
- return gTreeData[idx] ? "open" in gTreeData[idx] : false;
- },
- getCellValue: function(idx, column){ return gTreeData[idx].checked; },
- isContainerOpen: function(idx) { return gTreeData[idx].open; },
- isContainerEmpty: function(idx) { return false; },
- isSeparator: function(idx) { return false; },
- isSorted: function() { return false; },
- isEditable: function(idx, column) { return false; },
- canDrop: function(idx, orientation, dt) { return false; },
- getLevel: function(idx) { return this.isContainer(idx) ? 0 : 1; },
-
- getParentIndex: function(idx) {
- if (!this.isContainer(idx))
- for (var t = idx - 1; t >= 0 ; t--)
- if (this.isContainer(t))
- return t;
- return -1;
- },
-
- hasNextSibling: function(idx, after) {
- var thisLevel = this.getLevel(idx);
- for (var t = after + 1; t < gTreeData.length; t++)
- if (this.getLevel(t) <= thisLevel)
- return this.getLevel(t) == thisLevel;
- return false;
- },
-
- toggleOpenState: function(idx) {
- if (!this.isContainer(idx))
- return;
- var item = gTreeData[idx];
- if (item.open) {
- // remove this window's tab rows from the view
- var thisLevel = this.getLevel(idx);
- for (var t = idx + 1; t < gTreeData.length && this.getLevel(t) > thisLevel; t++);
- var deletecount = t - idx - 1;
- gTreeData.splice(idx + 1, deletecount);
- this.treeBox.rowCountChanged(idx + 1, -deletecount);
- }
- else {
- // add this window's tab rows to the view
- var toinsert = gTreeData[idx].tabs;
- for (var i = 0; i < toinsert.length; i++)
- gTreeData.splice(idx + i + 1, 0, toinsert[i]);
- this.treeBox.rowCountChanged(idx + 1, toinsert.length);
- }
- item.open = !item.open;
- this.treeBox.invalidateRow(idx);
- },
-
- getCellProperties: function(idx, column) {
- if (column.id == "restore" && this.isContainer(idx) && gTreeData[idx].checked === 0)
- return "partial";
- if (column.id == "title")
- return this.getImageSrc(idx, column) ? "icon" : "noicon";
-
- return "";
- },
-
- getRowProperties: function(idx) {
- var winState = gTreeData[idx].parent || gTreeData[idx];
- if (winState.ix % 2 != 0)
- return "alternate";
-
- return "";
- },
-
- getImageSrc: function(idx, column) {
- if (column.id == "title")
- return gTreeData[idx].src || null;
- return null;
- },
-
- getProgressMode : function(idx, column) { },
- cycleHeader: function(column) { },
- cycleCell: function(idx, column) { },
- selectionChanged: function() { },
- performAction: function(action) { },
- performActionOnCell: function(action, index, column) { },
- getColumnProperties: function(column) { return ""; }
-};
diff --git a/browser/components/sessionstore/content/aboutSessionRestore.xhtml b/browser/components/sessionstore/content/aboutSessionRestore.xhtml
deleted file mode 100644
index bcd9084e7..000000000
--- a/browser/components/sessionstore/content/aboutSessionRestore.xhtml
+++ /dev/null
@@ -1,86 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-# 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/.
--->
-<!DOCTYPE html [
- <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
- %htmlDTD;
- <!ENTITY % netErrorDTD SYSTEM "chrome://global/locale/netError.dtd">
- %netErrorDTD;
- <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
- %globalDTD;
- <!ENTITY % restorepageDTD SYSTEM "chrome://browser/locale/aboutSessionRestore.dtd">
- %restorepageDTD;
-]>
-
-<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
- <head>
- <title>&restorepage.tabtitle;</title>
- <link rel="stylesheet" href="chrome://global/skin/in-content/info-pages.css" type="text/css" media="all"/>
- <link rel="stylesheet" href="chrome://browser/skin/aboutSessionRestore.css" type="text/css" media="all"/>
- <link rel="icon" type="image/png" href="chrome://global/skin/icons/warning-16.png"/>
-
- <script type="application/javascript;version=1.8" src="chrome://browser/content/aboutSessionRestore.js"/>
- </head>
-
- <body dir="&locale.dir;">
-
- <div class="container restore-chosen">
-
- <div class="title">
- <h1 class="title-text">&restorepage.errorTitle;</h1>
- </div>
- <div class="description">
- <p>&restorepage.problemDesc;</p>
-
- <div id="errorLongDesc">
- <p>&restorepage.tryThis;</p>
- <ul>
- <li>&restorepage.restoreSome;</li>
- <li>&restorepage.startNew;</li>
- </ul>
- </div>
- </div>
- <div class="tree-container" available="true">
- <xul:tree id="tabList" seltype="single" hidecolumnpicker="true"
- onclick="onListClick(event);" onkeydown="onListKeyDown(event);"
- _window_label="&restorepage.windowLabel;">
- <xul:treecols>
- <xul:treecol cycler="true" id="restore" type="checkbox" label="&restorepage.restoreHeader;"/>
- <xul:splitter class="tree-splitter"/>
- <xul:treecol primary="true" id="title" label="&restorepage.listHeader;" flex="1"/>
- </xul:treecols>
- <xul:treechildren flex="1"/>
- </xul:tree>
- </div>
- <div class="button-container">
-#ifdef XP_UNIX
- <xul:button id="errorCancel"
- label="&restorepage.closeButton;"
- accesskey="&restorepage.close.access;"
- oncommand="startNewSession();"/>
- <xul:button class="primary"
- id="errorTryAgain"
- label="&restorepage.tryagainButton;"
- accesskey="&restorepage.restore.access;"
- oncommand="restoreSession();"/>
-#else
- <xul:button class="primary"
- id="errorTryAgain"
- label="&restorepage.tryagainButton;"
- accesskey="&restorepage.restore.access;"
- oncommand="restoreSession();"/>
- <xul:button id="errorCancel"
- label="&restorepage.closeButton;"
- accesskey="&restorepage.close.access;"
- oncommand="startNewSession();"/>
-#endif
- </div>
- <!-- holds the session data for when the tab is closed -->
- <input type="text" id="sessionData" style="display: none;"/>
- </div>
-
- </body>
-</html>
diff --git a/browser/components/sessionstore/content/content-sessionStore.js b/browser/components/sessionstore/content/content-sessionStore.js
deleted file mode 100644
index 858e35750..000000000
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ /dev/null
@@ -1,897 +0,0 @@
-/* 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";
-
-function debug(msg) {
- Services.console.logStringMessage("SessionStoreContent: " + msg);
-}
-
-var Cu = Components.utils;
-var Cc = Components.classes;
-var Ci = Components.interfaces;
-var Cr = Components.results;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
-Cu.import("resource://gre/modules/Timer.jsm", this);
-
-XPCOMUtils.defineLazyModuleGetter(this, "FormData",
- "resource://gre/modules/FormData.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
- "resource://gre/modules/Preferences.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "DocShellCapabilities",
- "resource:///modules/sessionstore/DocShellCapabilities.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PageStyle",
- "resource:///modules/sessionstore/PageStyle.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition",
- "resource://gre/modules/ScrollPosition.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
- "resource:///modules/sessionstore/SessionHistory.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
- "resource:///modules/sessionstore/SessionStorage.jsm");
-
-Cu.import("resource:///modules/sessionstore/FrameTree.jsm", this);
-var gFrameTree = new FrameTree(this);
-
-Cu.import("resource:///modules/sessionstore/ContentRestore.jsm", this);
-XPCOMUtils.defineLazyGetter(this, 'gContentRestore',
- () => { return new ContentRestore(this) });
-
-// The current epoch.
-var gCurrentEpoch = 0;
-
-// A bound to the size of data to store for DOM Storage.
-const DOM_STORAGE_MAX_CHARS = 10000000; // 10M characters
-
-// This pref controls whether or not we send updates to the parent on a timeout
-// or not, and should only be used for tests or debugging.
-const TIMEOUT_DISABLED_PREF = "browser.sessionstore.debug.no_auto_updates";
-
-const kNoIndex = Number.MAX_SAFE_INTEGER;
-const kLastIndex = Number.MAX_SAFE_INTEGER - 1;
-
-/**
- * Returns a lazy function that will evaluate the given
- * function |fn| only once and cache its return value.
- */
-function createLazy(fn) {
- let cached = false;
- let cachedValue = null;
-
- return function lazy() {
- if (!cached) {
- cachedValue = fn();
- cached = true;
- }
-
- return cachedValue;
- };
-}
-
-/**
- * Listens for and handles content events that we need for the
- * session store service to be notified of state changes in content.
- */
-var EventListener = {
-
- init: function () {
- addEventListener("load", this, true);
- },
-
- handleEvent: function (event) {
- // Ignore load events from subframes.
- if (event.target != content.document) {
- return;
- }
-
- if (content.document.documentURI.startsWith("about:reader")) {
- if (event.type == "load" &&
- !content.document.body.classList.contains("loaded")) {
- // Don't restore the scroll position of an about:reader page at this
- // point; listen for the custom event dispatched from AboutReader.jsm.
- content.addEventListener("AboutReaderContentReady", this);
- return;
- }
-
- content.removeEventListener("AboutReaderContentReady", this);
- }
-
- // Restore the form data and scroll position. If we're not currently
- // restoring a tab state then this call will simply be a noop.
- gContentRestore.restoreDocument();
- }
-};
-
-/**
- * Listens for and handles messages sent by the session store service.
- */
-var MessageListener = {
-
- MESSAGES: [
- "SessionStore:restoreHistory",
- "SessionStore:restoreTabContent",
- "SessionStore:resetRestore",
- "SessionStore:flush",
- ],
-
- init: function () {
- this.MESSAGES.forEach(m => addMessageListener(m, this));
- },
-
- receiveMessage: function ({name, data}) {
- // The docShell might be gone. Don't process messages,
- // that will just lead to errors anyway.
- if (!docShell) {
- return;
- }
-
- // A fresh tab always starts with epoch=0. The parent has the ability to
- // override that to signal a new era in this tab's life. This enables it
- // to ignore async messages that were already sent but not yet received
- // and would otherwise confuse the internal tab state.
- if (data.epoch && data.epoch != gCurrentEpoch) {
- gCurrentEpoch = data.epoch;
- }
-
- switch (name) {
- case "SessionStore:restoreHistory":
- this.restoreHistory(data);
- break;
- case "SessionStore:restoreTabContent":
- this.restoreTabContent(data);
- break;
- case "SessionStore:resetRestore":
- gContentRestore.resetRestore();
- break;
- case "SessionStore:flush":
- this.flush(data);
- break;
- default:
- debug("received unknown message '" + name + "'");
- break;
- }
- },
-
- restoreHistory({epoch, tabData, loadArguments, isRemotenessUpdate}) {
- gContentRestore.restoreHistory(tabData, loadArguments, {
- // Note: The callbacks passed here will only be used when a load starts
- // that was not initiated by sessionstore itself. This can happen when
- // some code calls browser.loadURI() or browser.reload() on a pending
- // browser/tab.
-
- onLoadStarted() {
- // Notify the parent that the tab is no longer pending.
- sendSyncMessage("SessionStore:restoreTabContentStarted", {epoch});
- },
-
- onLoadFinished() {
- // Tell SessionStore.jsm that it may want to restore some more tabs,
- // since it restores a max of MAX_CONCURRENT_TAB_RESTORES at a time.
- sendAsyncMessage("SessionStore:restoreTabContentComplete", {epoch});
- }
- });
-
- // When restoreHistory finishes, we send a synchronous message to
- // SessionStore.jsm so that it can run SSTabRestoring. Users of
- // SSTabRestoring seem to get confused if chrome and content are out of
- // sync about the state of the restore (particularly regarding
- // docShell.currentURI). Using a synchronous message is the easiest way
- // to temporarily synchronize them.
- sendSyncMessage("SessionStore:restoreHistoryComplete", {epoch, isRemotenessUpdate});
- },
-
- restoreTabContent({loadArguments, isRemotenessUpdate}) {
- let epoch = gCurrentEpoch;
-
- // We need to pass the value of didStartLoad back to SessionStore.jsm.
- let didStartLoad = gContentRestore.restoreTabContent(loadArguments, isRemotenessUpdate, () => {
- // 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.
- sendAsyncMessage("SessionStore:restoreTabContentComplete", {epoch, isRemotenessUpdate});
- });
-
- sendAsyncMessage("SessionStore:restoreTabContentStarted", {epoch, isRemotenessUpdate});
-
- if (!didStartLoad) {
- // Pretend that the load succeeded so that event handlers fire correctly.
- sendAsyncMessage("SessionStore:restoreTabContentComplete", {epoch, isRemotenessUpdate});
- }
- },
-
- flush({id}) {
- // Flush the message queue, send the latest updates.
- MessageQueue.send({flushID: id});
- }
-};
-
-/**
- * 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}
- */
-var SessionHistoryListener = {
- init: function () {
- // The frame tree observer is needed to handle initial subframe loads.
- // It will redundantly invalidate with the SHistoryListener in some cases
- // but these invalidations are very cheap.
- gFrameTree.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.
- docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory.
- addSHistoryListener(this);
-
- // Collect data if we start with a non-empty shistory.
- if (!SessionHistory.isEmpty(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.
- MessageQueue.send();
- }
-
- // Listen for page title changes.
- addEventListener("DOMTitleChanged", this);
- },
-
- uninit: function () {
- let sessionHistory = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
- if (sessionHistory) {
- sessionHistory.removeSHistoryListener(this);
- }
- },
-
- collect: function () {
- this._fromIdx = kNoIndex;
- if (docShell) {
- MessageQueue.push("history", () => SessionHistory.collect(docShell));
- }
- },
-
- _fromIdx: kNoIndex,
-
- // 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: function (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;
- MessageQueue.push("historychange", () => {
- if (this._fromIdx === kNoIndex) {
- return null;
- }
-
- let history = SessionHistory.collect(docShell);
- if (kLastIndex == idx) {
- history.entries = [];
- } else {
- history.entries.splice(0, this._fromIdx + 1);
- }
-
- history.fromIdx = this._fromIdx;
-
- this._fromIdx = kNoIndex;
- return history;
- });
- },
-
- handleEvent(event) {
- this.collect();
- },
-
- onFrameTreeCollected: function () {
- this.collect();
- },
-
- onFrameTreeReset: function () {
- this.collect();
- },
-
- OnHistoryNewEntry: function (newURI, oldIndex) {
- this.collectFrom(oldIndex);
- },
-
- OnHistoryGoBack: function (backURI) {
- this.collectFrom(kLastIndex);
- return true;
- },
-
- OnHistoryGoForward: function (forwardURI) {
- this.collectFrom(kLastIndex);
- return true;
- },
-
- OnHistoryGotoIndex: function (index, gotoURI) {
- this.collectFrom(kLastIndex);
- return true;
- },
-
- OnHistoryPurge: function (numEntries) {
- this.collect();
- return true;
- },
-
- OnHistoryReload: function (reloadURI, reloadFlags) {
- this.collect();
- return true;
- },
-
- OnHistoryReplaceEntry: function (index) {
- this.collect();
- },
-
- QueryInterface: XPCOMUtils.generateQI([
- Ci.nsISHistoryListener,
- Ci.nsISupportsWeakReference
- ])
-};
-
-/**
- * Listens for scroll position changes. Whenever the user scrolls the top-most
- * frame we update the scroll position and will restore it when requested.
- *
- * Causes a SessionStore:update message to be sent that contains the current
- * scroll positions as a tree of strings. If no frame of the whole frame tree
- * is scrolled this will return null so that we don't tack a property onto
- * the tabData object in the parent process.
- *
- * Example:
- * {scroll: "100,100", children: [null, null, {scroll: "200,200"}]}
- */
-var ScrollPositionListener = {
- init: function () {
- addEventListener("scroll", this);
- gFrameTree.addObserver(this);
- },
-
- handleEvent: function (event) {
- let frame = event.target.defaultView;
-
- // Don't collect scroll data for frames created at or after the load event
- // as SessionStore can't restore scroll data for those.
- if (gFrameTree.contains(frame)) {
- MessageQueue.push("scroll", () => this.collect());
- }
- },
-
- onFrameTreeCollected: function () {
- MessageQueue.push("scroll", () => this.collect());
- },
-
- onFrameTreeReset: function () {
- MessageQueue.push("scroll", () => null);
- },
-
- collect: function () {
- return gFrameTree.map(ScrollPosition.collect);
- }
-};
-
-/**
- * Listens for changes to input elements. Whenever the value of an input
- * element changes we will re-collect data for the current frame tree and send
- * a message to the parent process.
- *
- * Causes a SessionStore:update message to be sent that contains the form data
- * for all reachable frames.
- *
- * Example:
- * {
- * formdata: {url: "http://mozilla.org/", id: {input_id: "input value"}},
- * children: [
- * null,
- * {url: "http://sub.mozilla.org/", id: {input_id: "input value 2"}}
- * ]
- * }
- */
-var FormDataListener = {
- init: function () {
- addEventListener("input", this, true);
- addEventListener("change", this, true);
- gFrameTree.addObserver(this);
- },
-
- handleEvent: function (event) {
- let frame = event.target.ownerGlobal;
-
- // Don't collect form data for frames created at or after the load event
- // as SessionStore can't restore form data for those.
- if (gFrameTree.contains(frame)) {
- MessageQueue.push("formdata", () => this.collect());
- }
- },
-
- onFrameTreeReset: function () {
- MessageQueue.push("formdata", () => null);
- },
-
- collect: function () {
- return gFrameTree.map(FormData.collect);
- }
-};
-
-/**
- * Listens for changes to the page style. Whenever a different page style is
- * selected or author styles are enabled/disabled we send a message with the
- * currently applied style to the chrome process.
- *
- * Causes a SessionStore:update message to be sent that contains the currently
- * selected pageStyle for all reachable frames.
- *
- * Example:
- * {pageStyle: "Dusk", children: [null, {pageStyle: "Mozilla"}]}
- */
-var PageStyleListener = {
- init: function () {
- Services.obs.addObserver(this, "author-style-disabled-changed", false);
- Services.obs.addObserver(this, "style-sheet-applicable-state-changed", false);
- gFrameTree.addObserver(this);
- },
-
- uninit: function () {
- Services.obs.removeObserver(this, "author-style-disabled-changed");
- Services.obs.removeObserver(this, "style-sheet-applicable-state-changed");
- },
-
- observe: function (subject, topic) {
- let frame = subject.defaultView;
-
- if (frame && gFrameTree.contains(frame)) {
- MessageQueue.push("pageStyle", () => this.collect());
- }
- },
-
- collect: function () {
- return PageStyle.collect(docShell, gFrameTree);
- },
-
- onFrameTreeCollected: function () {
- MessageQueue.push("pageStyle", () => this.collect());
- },
-
- onFrameTreeReset: function () {
- MessageQueue.push("pageStyle", () => null);
- }
-};
-
-/**
- * Listens for changes to docShell capabilities. Whenever a new load is started
- * we need to re-check the list of capabilities and send message when it has
- * changed.
- *
- * Causes a SessionStore:update message to be sent that contains the currently
- * disabled docShell capabilities (all nsIDocShell.allow* properties set to
- * false) as a string - i.e. capability names separate by commas.
- */
-var DocShellCapabilitiesListener = {
- /**
- * This field is used to compare the last docShell capabilities to the ones
- * that have just been collected. If nothing changed we won't send a message.
- */
- _latestCapabilities: "",
-
- init: function () {
- gFrameTree.addObserver(this);
- },
-
- /**
- * onFrameTreeReset() is called as soon as we start loading a page.
- */
- onFrameTreeReset: function() {
- // The order of docShell capabilities cannot change while we're running
- // so calling join() without sorting before is totally sufficient.
- let caps = DocShellCapabilities.collect(docShell).join(",");
-
- // Send new data only when the capability list changes.
- if (caps != this._latestCapabilities) {
- this._latestCapabilities = caps;
- MessageQueue.push("disallow", () => caps || null);
- }
- }
-};
-
-/**
- * Listens for changes to the DOMSessionStorage. Whenever new keys are added,
- * existing ones removed or changed, or the storage is cleared we will send a
- * message to the parent process containing up-to-date sessionStorage data.
- *
- * Causes a SessionStore:update message to be sent that contains the current
- * DOMSessionStorage contents. The data is a nested object using host names
- * as keys and per-host DOMSessionStorage data as values.
- */
-var SessionStorageListener = {
- init: function () {
- addEventListener("MozSessionStorageChanged", this, true);
- Services.obs.addObserver(this, "browser:purge-domain-data", false);
- gFrameTree.addObserver(this);
- },
-
- uninit: function () {
- Services.obs.removeObserver(this, "browser:purge-domain-data");
- },
-
- handleEvent: function (event) {
- if (gFrameTree.contains(event.target)) {
- this.collectFromEvent(event);
- }
- },
-
- observe: function () {
- // Collect data on the next tick so that any other observer
- // that needs to purge data can do its work first.
- setTimeout(() => this.collect(), 0);
- },
-
- // Before DOM Storage can be written to disk, it needs to be serialized
- // for sending across frames/processes, then again to be sent across
- // threads, then again to be put in a buffer for the disk. Each of these
- // serializations is an opportunity to OOM and (depending on the site of
- // the OOM), either crash, lose all data for the frame or lose all data
- // for the application.
- //
- // In order to avoid this, compute an estimate of the size of the
- // object, and block SessionStorage items that are too large. As
- // we also don't want to cause an OOM here, we use a quick and memory-
- // efficient approximation: we compute the total sum of string lengths
- // involved in this object.
- estimateStorageSize: function(collected) {
- if (!collected) {
- return 0;
- }
-
- let size = 0;
- for (let host of Object.keys(collected)) {
- size += host.length;
- let perHost = collected[host];
- for (let key of Object.keys(perHost)) {
- size += key.length;
- let perKey = perHost[key];
- size += perKey.length;
- }
- }
-
- return size;
- },
-
- // We don't want to send all the session storage data for all the frames
- // for every change. So if only a few value changed we send them over as
- // a "storagechange" event. If however for some reason before we send these
- // changes we have to send over the entire sessions storage data, we just
- // reset these changes.
- _changes: undefined,
-
- resetChanges: function () {
- this._changes = undefined;
- },
-
- collectFromEvent: function (event) {
- // TODO: we should take browser.sessionstore.dom_storage_limit into an account here.
- if (docShell) {
- let {url, key, newValue} = event;
- let uri = Services.io.newURI(url, null, null);
- let domain = uri.prePath;
- if (!this._changes) {
- this._changes = {};
- }
- if (!this._changes[domain]) {
- this._changes[domain] = {};
- }
- this._changes[domain][key] = newValue;
-
- MessageQueue.push("storagechange", () => {
- let tmp = this._changes;
- // If there were multiple changes we send them merged.
- // First one will collect all the changes the rest of
- // these messages will be ignored.
- this.resetChanges();
- return tmp;
- });
- }
- },
-
- collect: function () {
- if (docShell) {
- // We need the entire session storage, let's reset the pending individual change
- // messages.
- this.resetChanges();
- MessageQueue.push("storage", () => {
- let collected = SessionStorage.collect(docShell, gFrameTree);
-
- if (collected == null) {
- return collected;
- }
-
- let size = this.estimateStorageSize(collected);
-
- MessageQueue.push("telemetry", () => ({ FX_SESSION_RESTORE_DOM_STORAGE_SIZE_ESTIMATE_CHARS: size }));
- if (size > Preferences.get("browser.sessionstore.dom_storage_limit", DOM_STORAGE_MAX_CHARS)) {
- // Rather than keeping the old storage, which wouldn't match the rest
- // of the state of the page, empty the storage. DOM storage will be
- // recollected the next time and stored if it is now small enough.
- return {};
- }
-
- return collected;
- });
- }
- },
-
- onFrameTreeCollected: function () {
- this.collect();
- },
-
- onFrameTreeReset: function () {
- this.collect();
- }
-};
-
-/**
- * Listen for changes to the privacy status of the tab.
- * By definition, tabs start in non-private mode.
- *
- * Causes a SessionStore:update message to be sent for
- * field "isPrivate". This message contains
- * |true| if the tab is now private
- * |null| if the tab is now public - the field is therefore
- * not saved.
- */
-var PrivacyListener = {
- init: function() {
- docShell.addWeakPrivacyTransitionObserver(this);
-
- // Check that value at startup as it might have
- // been set before the frame script was loaded.
- if (docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing) {
- MessageQueue.push("isPrivate", () => true);
- }
- },
-
- // Ci.nsIPrivacyTransitionObserver
- privateModeChanged: function(enabled) {
- MessageQueue.push("isPrivate", () => enabled || null);
- },
-
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrivacyTransitionObserver,
- Ci.nsISupportsWeakReference])
-};
-
-/**
- * A message queue that takes collected data and will take care of sending it
- * to the chrome process. It allows flushing using synchronous messages and
- * takes care of any race conditions that might occur because of that. Changes
- * will be batched if they're pushed in quick succession to avoid a message
- * flood.
- */
-var MessageQueue = {
- /**
- * A map (string -> lazy fn) holding lazy closures of all queued data
- * collection routines. These functions will return data collected from the
- * docShell.
- */
- _data: new Map(),
-
- /**
- * The delay (in ms) used to delay sending changes after data has been
- * invalidated.
- */
- BATCH_DELAY_MS: 1000,
-
- /**
- * The current timeout ID, null if there is no queue data. We use timeouts
- * to damp a flood of data changes and send lots of changes as one batch.
- */
- _timeout: null,
-
- /**
- * Whether or not sending batched messages on a timer is disabled. This should
- * only be used for debugging or testing. If you need to access this value,
- * you should probably use the timeoutDisabled getter.
- */
- _timeoutDisabled: false,
-
- /**
- * True if batched messages are not being fired on a timer. This should only
- * ever be true when debugging or during tests.
- */
- get timeoutDisabled() {
- return this._timeoutDisabled;
- },
-
- /**
- * Disables sending batched messages on a timer. Also cancels any pending
- * timers.
- */
- set timeoutDisabled(val) {
- this._timeoutDisabled = val;
-
- if (val && this._timeout) {
- clearTimeout(this._timeout);
- this._timeout = null;
- }
-
- return val;
- },
-
- init() {
- this.timeoutDisabled =
- Services.prefs.getBoolPref(TIMEOUT_DISABLED_PREF);
-
- Services.prefs.addObserver(TIMEOUT_DISABLED_PREF, this, false);
- },
-
- uninit() {
- Services.prefs.removeObserver(TIMEOUT_DISABLED_PREF, this);
- },
-
- observe(subject, topic, data) {
- if (topic == "nsPref:changed" && data == TIMEOUT_DISABLED_PREF) {
- this.timeoutDisabled =
- Services.prefs.getBoolPref(TIMEOUT_DISABLED_PREF);
- }
- },
-
- /**
- * Pushes a given |value| onto the queue. The given |key| represents the type
- * of data that is stored and can override data that has been queued before
- * but has not been sent to the parent process, yet.
- *
- * @param key (string)
- * A unique identifier specific to the type of data this is passed.
- * @param fn (function)
- * A function that returns the value that will be sent to the parent
- * process.
- */
- push: function (key, fn) {
- this._data.set(key, createLazy(fn));
-
- if (!this._timeout && !this._timeoutDisabled) {
- // Wait a little before sending the message to batch multiple changes.
- this._timeout = setTimeout(() => this.send(), this.BATCH_DELAY_MS);
- }
- },
-
- /**
- * Sends queued data to the chrome process.
- *
- * @param options (object)
- * {flushID: 123} to specify that this is a flush
- * {isFinal: true} to signal this is the final message sent on unload
- */
- send: function (options = {}) {
- // Looks like we have been called off a timeout after the tab has been
- // closed. The docShell is gone now and we can just return here as there
- // is nothing to do.
- if (!docShell) {
- return;
- }
-
- if (this._timeout) {
- clearTimeout(this._timeout);
- this._timeout = null;
- }
-
- let flushID = (options && options.flushID) || 0;
-
- let durationMs = Date.now();
-
- let data = {};
- let telemetry = {};
- for (let [key, func] of this._data) {
- let value = func();
- if (key == "telemetry") {
- for (let histogramId of Object.keys(value)) {
- telemetry[histogramId] = value[histogramId];
- }
- } else if (value || (key != "storagechange" && key != "historychange")) {
- data[key] = value;
- }
- }
-
- this._data.clear();
-
- durationMs = Date.now() - durationMs;
- telemetry.FX_SESSION_RESTORE_CONTENT_COLLECT_DATA_LONGEST_OP_MS = durationMs;
-
- try {
- // Send all data to the parent process.
- sendAsyncMessage("SessionStore:update", {
- data, telemetry, flushID,
- isFinal: options.isFinal || false,
- epoch: gCurrentEpoch
- });
- } catch (ex if ex && ex.result == Cr.NS_ERROR_OUT_OF_MEMORY) {
- let telemetry = {
- FX_SESSION_RESTORE_SEND_UPDATE_CAUSED_OOM: 1
- };
- sendAsyncMessage("SessionStore:error", {
- telemetry
- });
- }
- },
-};
-
-EventListener.init();
-MessageListener.init();
-FormDataListener.init();
-PageStyleListener.init();
-SessionHistoryListener.init();
-SessionStorageListener.init();
-ScrollPositionListener.init();
-DocShellCapabilitiesListener.init();
-PrivacyListener.init();
-MessageQueue.init();
-
-function handleRevivedTab() {
- if (!content) {
- removeEventListener("pagehide", handleRevivedTab);
- return;
- }
-
- if (content.document.documentURI.startsWith("about:tabcrashed")) {
- if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT) {
- // Sanity check - we'd better be loading this in a non-remote browser.
- throw new Error("We seem to be navigating away from about:tabcrashed in " +
- "a non-remote browser. This should really never happen.");
- }
-
- removeEventListener("pagehide", handleRevivedTab);
-
- // Notify the parent.
- sendAsyncMessage("SessionStore:crashedTabRevived");
- }
-}
-
-// If we're browsing from the tab crashed UI to a blacklisted URI that keeps
-// this browser non-remote, we'll handle that in a pagehide event.
-addEventListener("pagehide", handleRevivedTab);
-
-addEventListener("unload", () => {
- // Upon frameLoader destruction, send a final update message to
- // the parent and flush all data currently held in the child.
- MessageQueue.send({isFinal: true});
-
- // If we're browsing from the tab crashed UI to a URI that causes the tab
- // to go remote again, we catch this in the unload event handler, because
- // swapping out the non-remote browser for a remote one in
- // tabbrowser.xml's updateBrowserRemoteness doesn't cause the pagehide
- // event to be fired.
- handleRevivedTab();
-
- // Remove all registered nsIObservers.
- PageStyleListener.uninit();
- SessionStorageListener.uninit();
- SessionHistoryListener.uninit();
- MessageQueue.uninit();
-
- // Remove progress listeners.
- gContentRestore.resetRestore();
-
- // We don't need to take care of any gFrameTree observers as the gFrameTree
- // will die with the content script. The same goes for the privacy transition
- // observer that will die with the docShell when the tab is closed.
-});
diff --git a/browser/components/sessionstore/jar.mn b/browser/components/sessionstore/jar.mn
deleted file mode 100644
index 7e5bc07dc..000000000
--- a/browser/components/sessionstore/jar.mn
+++ /dev/null
@@ -1,8 +0,0 @@
-# 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/.
-
-browser.jar:
-* content/browser/aboutSessionRestore.xhtml (content/aboutSessionRestore.xhtml)
- content/browser/aboutSessionRestore.js (content/aboutSessionRestore.js)
- content/browser/content-sessionStore.js (content/content-sessionStore.js)
diff --git a/browser/components/sessionstore/moz.build b/browser/components/sessionstore/moz.build
deleted file mode 100644
index 3117f02c7..000000000
--- a/browser/components/sessionstore/moz.build
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# 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/.
-
-JAR_MANIFESTS += ['jar.mn']
-
-XPIDL_SOURCES += [
- 'nsISessionStartup.idl',
- 'nsISessionStore.idl',
-]
-
-XPIDL_MODULE = 'sessionstore'
-
-EXTRA_COMPONENTS += [
- 'nsSessionStartup.js',
- 'nsSessionStore.js',
- 'nsSessionStore.manifest',
-]
-
-EXTRA_JS_MODULES.sessionstore = [
- 'ContentRestore.jsm',
- 'DocShellCapabilities.jsm',
- 'FrameTree.jsm',
- 'GlobalState.jsm',
- 'PageStyle.jsm',
- 'PrivacyFilter.jsm',
- 'PrivacyLevel.jsm',
- 'RecentlyClosedTabsAndWindowsMenuUtils.jsm',
- 'RunState.jsm',
- 'SessionCookies.jsm',
- 'SessionFile.jsm',
- 'SessionHistory.jsm',
- 'SessionMigration.jsm',
- 'SessionSaver.jsm',
- 'SessionStorage.jsm',
- 'SessionStore.jsm',
- 'SessionWorker.js',
- 'SessionWorker.jsm',
- 'StartupPerformance.jsm',
- 'TabAttributes.jsm',
- 'TabState.jsm',
- 'TabStateCache.jsm',
- 'TabStateFlusher.jsm',
-]
diff --git a/browser/components/sessionstore/nsISessionStartup.idl b/browser/components/sessionstore/nsISessionStartup.idl
deleted file mode 100644
index 2321ac310..000000000
--- a/browser/components/sessionstore/nsISessionStartup.idl
+++ /dev/null
@@ -1,66 +0,0 @@
-/* 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/. */
-
-#include "nsISupports.idl"
-
-/**
- * nsISessionStore keeps track of the current browsing state - i.e.
- * tab history, cookies, scroll state, form data, and window features
- * - and allows to restore everything into one window.
- */
-
-[scriptable, uuid(934697e4-3807-47f8-b6c9-6caa8d83ccd1)]
-interface nsISessionStartup: nsISupports
-{
- /**
- * Return a promise that is resolved once initialization
- * is complete.
- */
- readonly attribute jsval onceInitialized;
-
- // Get session state
- readonly attribute jsval state;
-
- /**
- * Determines whether there is a pending session restore. Should only be
- * called after initialization has completed.
- */
- boolean doRestore();
-
- /**
- * Determines whether automatic session restoration is enabled for this
- * launch of the browser. This does not include crash restoration, and will
- * return false if restoration will only be caused by a crash.
- */
- boolean isAutomaticRestoreEnabled();
-
- /**
- * Returns whether we will restore a session that ends up replacing the
- * homepage. The browser uses this to not start loading the homepage if
- * we're going to stop its load anyway shortly after.
- *
- * This is meant to be an optimization for the average case that loading the
- * session file finishes before we may want to start loading the default
- * homepage. Should this be called before the session file has been read it
- * will just return false.
- */
- readonly attribute bool willOverrideHomepage;
-
- /**
- * What type of session we're restoring.
- * NO_SESSION There is no data available from the previous session
- * RECOVER_SESSION The last session crashed. It will either be restored or
- * about:sessionrestore will be shown.
- * RESUME_SESSION The previous session should be restored at startup
- * DEFER_SESSION The previous session is fine, but it shouldn't be restored
- * without explicit action (with the exception of pinned tabs)
- */
- const unsigned long NO_SESSION = 0;
- const unsigned long RECOVER_SESSION = 1;
- const unsigned long RESUME_SESSION = 2;
- const unsigned long DEFER_SESSION = 3;
-
- readonly attribute unsigned long sessionType;
- readonly attribute bool previousSessionCrashed;
-};
diff --git a/browser/components/sessionstore/nsISessionStore.idl b/browser/components/sessionstore/nsISessionStore.idl
deleted file mode 100644
index 0d2500ef7..000000000
--- a/browser/components/sessionstore/nsISessionStore.idl
+++ /dev/null
@@ -1,220 +0,0 @@
-/* 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/. */
-
-#include "nsISupports.idl"
-
-interface nsIDOMWindow;
-interface nsIDOMNode;
-
-/**
- * nsISessionStore keeps track of the current browsing state - i.e.
- * tab history, cookies, scroll state, form data, and window features
- * - and allows to restore everything into one browser window.
- *
- * The nsISessionStore API operates mostly on browser windows and the tabbrowser
- * tabs contained in them:
- *
- * * "Browser windows" are those DOM windows having loaded
- * chrome://browser/content/browser.xul . From overlays you can just pass the
- * global |window| object to the API, though (or |top| from a sidebar).
- * From elsewhere you can get browser windows through the nsIWindowMediator
- * by looking for "navigator:browser" windows.
- *
- * * "Tabbrowser tabs" are all the child nodes of a browser window's
- * |gBrowser.tabContainer| such as e.g. |gBrowser.selectedTab|.
- */
-
-[scriptable, uuid(4580f5eb-693d-423d-b0ce-2cb20a962e4d)]
-interface nsISessionStore : nsISupports
-{
- /**
- * Is it possible to restore the previous session. Will always be false when
- * in Private Browsing mode.
- */
- attribute boolean canRestoreLastSession;
-
- /**
- * Restore the previous session if possible. This will not overwrite the
- * current session. Instead the previous session will be merged into the
- * current session. Current windows will be reused if they were windows that
- * pinned tabs were previously restored into. New windows will be opened as
- * needed.
- *
- * Note: This will throw if there is no previous state to restore. Check with
- * canRestoreLastSession first to avoid thrown errors.
- */
- void restoreLastSession();
-
- /**
- * Get the current browsing state.
- * @returns a JSON string representing the session state.
- */
- AString getBrowserState();
-
- /**
- * Set the browsing state.
- * This will immediately restore the state of the whole application to the state
- * passed in, *replacing* the current session.
- *
- * @param aState is a JSON string representing the session state.
- */
- void setBrowserState(in AString aState);
-
- /**
- * @param aWindow is the browser window whose state is to be returned.
- *
- * @returns a JSON string representing a session state with only one window.
- */
- AString getWindowState(in nsIDOMWindow aWindow);
-
- /**
- * @param aWindow is the browser window whose state is to be set.
- * @param aState is a JSON string representing a session state.
- * @param aOverwrite boolean overwrite existing tabs
- */
- void setWindowState(in nsIDOMWindow aWindow, in AString aState, in boolean aOverwrite);
-
- /**
- * @param aTab is the tabbrowser tab whose state is to be returned.
- *
- * @returns a JSON string representing the state of the tab
- * (note: doesn't contain cookies - if you need them, use getWindowState instead).
- */
- AString getTabState(in nsIDOMNode aTab);
-
- /**
- * @param aTab is the tabbrowser tab whose state is to be set.
- * @param aState is a JSON string representing a session state.
- */
- void setTabState(in nsIDOMNode aTab, in AString aState);
-
- /**
- * Duplicates a given tab as thoroughly as possible.
- *
- * @param aWindow is the browser window into which the tab will be duplicated.
- * @param aTab is the tabbrowser tab to duplicate (can be from a different window).
- * @param aDelta is the offset to the history entry to load in the duplicated tab.
- * @returns a reference to the newly created tab.
- */
- nsIDOMNode duplicateTab(in nsIDOMWindow aWindow, in nsIDOMNode aTab,
- [optional] in long aDelta);
-
- /**
- * Get the number of restore-able tabs for a browser window
- */
- unsigned long getClosedTabCount(in nsIDOMWindow aWindow);
-
- /**
- * Get closed tab data
- *
- * @param aWindow is the browser window for which to get closed tab data
- * @returns a JSON string representing the list of closed tabs.
- */
- AString getClosedTabData(in nsIDOMWindow aWindow);
-
- /**
- * @param aWindow is the browser window to reopen a closed tab in.
- * @param aIndex is the index of the tab to be restored (FIFO ordered).
- * @returns a reference to the reopened tab.
- */
- nsIDOMNode undoCloseTab(in nsIDOMWindow aWindow, in unsigned long aIndex);
-
- /**
- * @param aWindow is the browser window associated with the closed tab.
- * @param aIndex is the index of the closed tab to be removed (FIFO ordered).
- */
- nsIDOMNode forgetClosedTab(in nsIDOMWindow aWindow, in unsigned long aIndex);
-
- /**
- * Get the number of restore-able windows
- */
- unsigned long getClosedWindowCount();
-
- /**
- * Get closed windows data
- *
- * @returns a JSON string representing the list of closed windows.
- */
- AString getClosedWindowData();
-
- /**
- * @param aIndex is the index of the windows to be restored (FIFO ordered).
- * @returns the nsIDOMWindow object of the reopened window
- */
- nsIDOMWindow undoCloseWindow(in unsigned long aIndex);
-
- /**
- * @param aIndex is the index of the closed window to be removed (FIFO ordered).
- *
- * @throws NS_ERROR_INVALID_ARG
- * when aIndex does not map to a closed window
- */
- nsIDOMNode forgetClosedWindow(in unsigned long aIndex);
-
- /**
- * @param aWindow is the window to get the value for.
- * @param aKey is the value's name.
- *
- * @returns A string value or an empty string if none is set.
- */
- AString getWindowValue(in nsIDOMWindow aWindow, in AString aKey);
-
- /**
- * @param aWindow is the browser window to set the value for.
- * @param aKey is the value's name.
- * @param aStringValue is the value itself (use JSON.stringify/parse before setting JS objects).
- */
- void setWindowValue(in nsIDOMWindow aWindow, in AString aKey, in jsval aStringValue);
-
- /**
- * @param aWindow is the browser window to get the value for.
- * @param aKey is the value's name.
- */
- void deleteWindowValue(in nsIDOMWindow aWindow, in AString aKey);
-
- /**
- * @param aTab is the tabbrowser tab to get the value for.
- * @param aKey is the value's name.
- *
- * @returns A string value or an empty string if none is set.
- */
- AString getTabValue(in nsIDOMNode aTab, in AString aKey);
-
- /**
- * @param aTab is the tabbrowser tab to set the value for.
- * @param aKey is the value's name.
- * @param aStringValue is the value itself (use JSON.stringify/parse before setting JS objects).
- */
- void setTabValue(in nsIDOMNode aTab, in AString aKey, in jsval aStringValue);
-
- /**
- * @param aTab is the tabbrowser tab to get the value for.
- * @param aKey is the value's name.
- */
- void deleteTabValue(in nsIDOMNode aTab, in AString aKey);
-
- /**
- * @param aKey is the value's name.
- *
- * @returns A string value or an empty string if none is set.
- */
- AString getGlobalValue(in AString aKey);
-
- /**
- * @param aKey is the value's name.
- * @param aStringValue is the value itself (use JSON.stringify/parse before setting JS objects).
- */
- void setGlobalValue(in AString aKey, in jsval aStringValue);
-
- /**
- * @param aTab is the browser tab to get the value for.
- * @param aKey is the value's name.
- */
- void deleteGlobalValue(in AString aKey);
-
- /**
- * @param aName is the name of the attribute to save/restore for all tabbrowser tabs.
- */
- void persistTabAttribute(in AString aName);
-};
diff --git a/browser/components/sessionstore/nsSessionStartup.js b/browser/components/sessionstore/nsSessionStartup.js
deleted file mode 100644
index 7593c48ec..000000000
--- a/browser/components/sessionstore/nsSessionStartup.js
+++ /dev/null
@@ -1,353 +0,0 @@
-/* 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";
-
-/**
- * Session Storage and Restoration
- *
- * Overview
- * This service reads user's session file at startup, and makes a determination
- * as to whether the session should be restored. It will restore the session
- * under the circumstances described below. If the auto-start Private Browsing
- * mode is active, however, the session is never restored.
- *
- * Crash Detection
- * The CrashMonitor is used to check if the final session state was successfully
- * written at shutdown of the last session. If we did not reach
- * 'sessionstore-final-state-write-complete', then it's assumed that the browser
- * has previously crashed and we should restore the session.
- *
- * Forced Restarts
- * In the event that a restart is required due to application update or extension
- * installation, set the browser.sessionstore.resume_session_once pref to true,
- * and the session will be restored the next time the browser starts.
- *
- * Always Resume
- * This service will always resume the session if the integer pref
- * browser.startup.page is set to 3.
- */
-
-/* :::::::: Constants and Helpers ::::::::::::::: */
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cr = Components.results;
-const Cu = Components.utils;
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/TelemetryStopwatch.jsm");
-Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "console",
- "resource://gre/modules/Console.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
- "resource:///modules/sessionstore/SessionFile.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "StartupPerformance",
- "resource:///modules/sessionstore/StartupPerformance.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "CrashMonitor",
- "resource://gre/modules/CrashMonitor.jsm");
-
-const STATE_RUNNING_STR = "running";
-
-// 'browser.startup.page' preference value to resume the previous session.
-const BROWSER_STARTUP_RESUME_SESSION = 3;
-
-function debug(aMsg) {
- aMsg = ("SessionStartup: " + aMsg).replace(/\S{80}/g, "$&\n");
- Services.console.logStringMessage(aMsg);
-}
-function warning(aMsg, aException) {
- let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
-consoleMsg.init(aMsg, aException.fileName, null, aException.lineNumber, 0, Ci.nsIScriptError.warningFlag, "component javascript");
- Services.console.logMessage(consoleMsg);
-}
-
-var gOnceInitializedDeferred = (function () {
- let deferred = {};
-
- deferred.promise = new Promise((resolve, reject) => {
- deferred.resolve = resolve;
- deferred.reject = reject;
- });
-
- return deferred;
-})();
-
-/* :::::::: The Service ::::::::::::::: */
-
-function SessionStartup() {
-}
-
-SessionStartup.prototype = {
-
- // the state to restore at startup
- _initialState: null,
- _sessionType: Ci.nsISessionStartup.NO_SESSION,
- _initialized: false,
-
- // Stores whether the previous session crashed.
- _previousSessionCrashed: null,
-
-/* ........ Global Event Handlers .............. */
-
- /**
- * Initialize the component
- */
- init: function sss_init() {
- Services.obs.notifyObservers(null, "sessionstore-init-started", null);
- StartupPerformance.init();
-
- // do not need to initialize anything in auto-started private browsing sessions
- if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
- this._initialized = true;
- gOnceInitializedDeferred.resolve();
- return;
- }
-
- SessionFile.read().then(
- this._onSessionFileRead.bind(this),
- console.error
- );
- },
-
- // Wrap a string as a nsISupports
- _createSupportsString: function ssfi_createSupportsString(aData) {
- let string = Cc["@mozilla.org/supports-string;1"]
- .createInstance(Ci.nsISupportsString);
- string.data = aData;
- return string;
- },
-
- /**
- * Complete initialization once the Session File has been read
- *
- * @param source The Session State string read from disk.
- * @param parsed The object obtained by parsing |source| as JSON.
- */
- _onSessionFileRead: function ({source, parsed, noFilesFound}) {
- this._initialized = true;
-
- // Let observers modify the state before it is used
- let supportsStateString = this._createSupportsString(source);
- Services.obs.notifyObservers(supportsStateString, "sessionstore-state-read", "");
- let stateString = supportsStateString.data;
-
- if (stateString != source) {
- // The session has been modified by an add-on, reparse.
- try {
- this._initialState = JSON.parse(stateString);
- } catch (ex) {
- // That's not very good, an add-on has rewritten the initial
- // state to something that won't parse.
- warning("Observer rewrote the state to something that won't parse", ex);
- }
- } else {
- // No need to reparse
- this._initialState = parsed;
- }
-
- if (this._initialState == null) {
- // No valid session found.
- this._sessionType = Ci.nsISessionStartup.NO_SESSION;
- Services.obs.notifyObservers(null, "sessionstore-state-finalized", "");
- gOnceInitializedDeferred.resolve();
- return;
- }
-
- let shouldResumeSessionOnce = Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
- let shouldResumeSession = shouldResumeSessionOnce ||
- Services.prefs.getIntPref("browser.startup.page") == BROWSER_STARTUP_RESUME_SESSION;
-
- // If this is a normal restore then throw away any previous session
- if (!shouldResumeSessionOnce && this._initialState) {
- delete this._initialState.lastSessionState;
- }
-
- let resumeFromCrash = Services.prefs.getBoolPref("browser.sessionstore.resume_from_crash");
-
- CrashMonitor.previousCheckpoints.then(checkpoints => {
- if (checkpoints) {
- // If the previous session finished writing the final state, we'll
- // assume there was no crash.
- this._previousSessionCrashed = !checkpoints["sessionstore-final-state-write-complete"];
-
- } else {
- // If the Crash Monitor could not load a checkpoints file it will
- // provide null. This could occur on the first run after updating to
- // a version including the Crash Monitor, or if the checkpoints file
- // was removed, or on first startup with this profile, or after Firefox Reset.
-
- if (noFilesFound) {
- // There was no checkpoints file and no sessionstore.js or its backups
- // so we will assume that this was a fresh profile.
- this._previousSessionCrashed = false;
-
- } else {
- // If this is the first run after an update, sessionstore.js should
- // still contain the session.state flag to indicate if the session
- // crashed. If it is not present, we will assume this was not the first
- // run after update and the checkpoints file was somehow corrupted or
- // removed by a crash.
- //
- // If the session.state flag is present, we will fallback to using it
- // for crash detection - If the last write of sessionstore.js had it
- // set to "running", we crashed.
- let stateFlagPresent = (this._initialState.session &&
- this._initialState.session.state);
-
-
- this._previousSessionCrashed = !stateFlagPresent ||
- (this._initialState.session.state == STATE_RUNNING_STR);
- }
- }
-
- // Report shutdown success via telemetry. Shortcoming here are
- // being-killed-by-OS-shutdown-logic, shutdown freezing after
- // session restore was written, etc.
- Services.telemetry.getHistogramById("SHUTDOWN_OK").add(!this._previousSessionCrashed);
-
- // set the startup type
- if (this._previousSessionCrashed && resumeFromCrash)
- this._sessionType = Ci.nsISessionStartup.RECOVER_SESSION;
- else if (!this._previousSessionCrashed && shouldResumeSession)
- this._sessionType = Ci.nsISessionStartup.RESUME_SESSION;
- else if (this._initialState)
- this._sessionType = Ci.nsISessionStartup.DEFER_SESSION;
- else
- this._initialState = null; // reset the state
-
- Services.obs.addObserver(this, "sessionstore-windows-restored", true);
-
- if (this._sessionType != Ci.nsISessionStartup.NO_SESSION)
- Services.obs.addObserver(this, "browser:purge-session-history", true);
-
- // We're ready. Notify everyone else.
- Services.obs.notifyObservers(null, "sessionstore-state-finalized", "");
- gOnceInitializedDeferred.resolve();
- });
- },
-
- /**
- * Handle notifications
- */
- observe: function sss_observe(aSubject, aTopic, aData) {
- switch (aTopic) {
- case "app-startup":
- Services.obs.addObserver(this, "final-ui-startup", true);
- Services.obs.addObserver(this, "quit-application", true);
- break;
- case "final-ui-startup":
- Services.obs.removeObserver(this, "final-ui-startup");
- Services.obs.removeObserver(this, "quit-application");
- this.init();
- break;
- case "quit-application":
- // no reason for initializing at this point (cf. bug 409115)
- Services.obs.removeObserver(this, "final-ui-startup");
- Services.obs.removeObserver(this, "quit-application");
- if (this._sessionType != Ci.nsISessionStartup.NO_SESSION)
- Services.obs.removeObserver(this, "browser:purge-session-history");
- break;
- case "sessionstore-windows-restored":
- Services.obs.removeObserver(this, "sessionstore-windows-restored");
- // free _initialState after nsSessionStore is done with it
- this._initialState = null;
- break;
- case "browser:purge-session-history":
- Services.obs.removeObserver(this, "browser:purge-session-history");
- // reset all state on sanitization
- this._sessionType = Ci.nsISessionStartup.NO_SESSION;
- break;
- }
- },
-
-/* ........ Public API ................*/
-
- get onceInitialized() {
- return gOnceInitializedDeferred.promise;
- },
-
- /**
- * Get the session state as a jsval
- */
- get state() {
- return this._initialState;
- },
-
- /**
- * Determines whether there is a pending session restore. Should only be
- * called after initialization has completed.
- * @returns bool
- */
- doRestore: function sss_doRestore() {
- return this._willRestore();
- },
-
- /**
- * Determines whether automatic session restoration is enabled for this
- * launch of the browser. This does not include crash restoration. In
- * particular, if session restore is configured to restore only in case of
- * crash, this method returns false.
- * @returns bool
- */
- isAutomaticRestoreEnabled: function () {
- return Services.prefs.getBoolPref("browser.sessionstore.resume_session_once") ||
- Services.prefs.getIntPref("browser.startup.page") == BROWSER_STARTUP_RESUME_SESSION;
- },
-
- /**
- * Determines whether there is a pending session restore.
- * @returns bool
- */
- _willRestore: function () {
- return this._sessionType == Ci.nsISessionStartup.RECOVER_SESSION ||
- this._sessionType == Ci.nsISessionStartup.RESUME_SESSION;
- },
-
- /**
- * Returns whether we will restore a session that ends up replacing the
- * homepage. The browser uses this to not start loading the homepage if
- * we're going to stop its load anyway shortly after.
- *
- * This is meant to be an optimization for the average case that loading the
- * session file finishes before we may want to start loading the default
- * homepage. Should this be called before the session file has been read it
- * will just return false.
- *
- * @returns bool
- */
- get willOverrideHomepage() {
- if (this._initialState && this._willRestore()) {
- let windows = this._initialState.windows || null;
- // If there are valid windows with not only pinned tabs, signal that we
- // will override the default homepage by restoring a session.
- return windows && windows.some(w => w.tabs.some(t => !t.pinned));
- }
- return false;
- },
-
- /**
- * Get the type of pending session store, if any.
- */
- get sessionType() {
- return this._sessionType;
- },
-
- /**
- * Get whether the previous session crashed.
- */
- get previousSessionCrashed() {
- return this._previousSessionCrashed;
- },
-
- /* ........ QueryInterface .............. */
- QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
- Ci.nsISupportsWeakReference,
- Ci.nsISessionStartup]),
- classID: Components.ID("{ec7a6c20-e081-11da-8ad9-0800200c9a66}")
-};
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SessionStartup]);
diff --git a/browser/components/sessionstore/nsSessionStore.js b/browser/components/sessionstore/nsSessionStore.js
deleted file mode 100644
index 8d96178ce..000000000
--- a/browser/components/sessionstore/nsSessionStore.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/* 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";
-
-/**
- * Session Storage and Restoration
- *
- * Overview
- * This service keeps track of a user's session, storing the various bits
- * required to return the browser to its current state. The relevant data is
- * stored in memory, and is periodically saved to disk in a file in the
- * profile directory. The service is started at first window load, in
- * delayedStartup, and will restore the session from the data received from
- * the nsSessionStartup service.
- */
-
-const Cu = Components.utils;
-const Ci = Components.interfaces;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource:///modules/sessionstore/SessionStore.jsm");
-
-function SessionStoreService() {}
-
-// The SessionStore module's object is frozen. We need to modify our prototype
-// and add some properties so let's just copy the SessionStore object.
-Object.keys(SessionStore).forEach(function (aName) {
- let desc = Object.getOwnPropertyDescriptor(SessionStore, aName);
- Object.defineProperty(SessionStoreService.prototype, aName, desc);
-});
-
-SessionStoreService.prototype.classID =
- Components.ID("{5280606b-2510-4fe0-97ef-9b5a22eafe6b}");
-SessionStoreService.prototype.QueryInterface =
- XPCOMUtils.generateQI([Ci.nsISessionStore]);
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SessionStoreService]);
diff --git a/browser/components/sessionstore/nsSessionStore.manifest b/browser/components/sessionstore/nsSessionStore.manifest
deleted file mode 100644
index 9b5819c6a..000000000
--- a/browser/components/sessionstore/nsSessionStore.manifest
+++ /dev/null
@@ -1,15 +0,0 @@
-# This component must restrict its registration for the app-startup category
-# to the specific list of apps that use it so it doesn't get loaded in xpcshell.
-# Thus we restrict it to these apps:
-#
-# b2g: {3c2e2abc-06d4-11e1-ac3b-374f68613e61}
-# browser: {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
-# mobile/android: {aa3c5121-dab2-40e2-81ca-7ea25febc110}
-# mobile/xul: {a23983c0-fd0e-11dc-95ff-0800200c9a66}
-# graphene: {d1bfe7d9-c01e-4237-998b-7b5f960a4314}
-
-component {5280606b-2510-4fe0-97ef-9b5a22eafe6b} nsSessionStore.js
-contract @mozilla.org/browser/sessionstore;1 {5280606b-2510-4fe0-97ef-9b5a22eafe6b}
-component {ec7a6c20-e081-11da-8ad9-0800200c9a66} nsSessionStartup.js
-contract @mozilla.org/browser/sessionstartup;1 {ec7a6c20-e081-11da-8ad9-0800200c9a66}
-category app-startup nsSessionStartup service,@mozilla.org/browser/sessionstartup;1 application={3c2e2abc-06d4-11e1-ac3b-374f68613e61} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384} application={aa3c5121-dab2-40e2-81ca-7ea25febc110} application={a23983c0-fd0e-11dc-95ff-0800200c9a66} application={d1bfe7d9-c01e-4237-998b-7b5f960a4314}