diff options
author | Thomas Groman <tgroman@nuegia.net> | 2020-04-20 20:49:37 -0700 |
---|---|---|
committer | Thomas Groman <tgroman@nuegia.net> | 2020-04-20 20:49:37 -0700 |
commit | f9cab004186edb425a9b88ad649726605080a17c (patch) | |
tree | e2dae51d3144e83d097a12e7a1499e3ea93f90be /components/sessionstore | |
parent | f428692de8b59ab89a66502c079e1823dfda8aeb (diff) | |
download | webbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar webbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar.gz webbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar.lz webbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar.xz webbrowser-f9cab004186edb425a9b88ad649726605080a17c.zip |
move browser to webbrowser/
Diffstat (limited to 'components/sessionstore')
-rw-r--r-- | components/sessionstore/DocumentUtils.jsm | 230 | ||||
-rw-r--r-- | components/sessionstore/SessionStorage.jsm | 165 | ||||
-rw-r--r-- | components/sessionstore/SessionStore.jsm | 4786 | ||||
-rw-r--r-- | components/sessionstore/XPathGenerator.jsm | 97 | ||||
-rw-r--r-- | components/sessionstore/_SessionFile.jsm | 314 | ||||
-rw-r--r-- | components/sessionstore/content/aboutSessionRestore.js | 320 | ||||
-rw-r--r-- | components/sessionstore/content/aboutSessionRestore.xhtml | 94 | ||||
-rw-r--r-- | components/sessionstore/content/content-sessionStore.js | 40 | ||||
-rw-r--r-- | components/sessionstore/jar.mn | 8 | ||||
-rw-r--r-- | components/sessionstore/moz.build | 29 | ||||
-rw-r--r-- | components/sessionstore/nsISessionStartup.idl | 59 | ||||
-rw-r--r-- | components/sessionstore/nsISessionStore.idl | 206 | ||||
-rw-r--r-- | components/sessionstore/nsSessionStartup.js | 296 | ||||
-rw-r--r-- | components/sessionstore/nsSessionStore.js | 37 | ||||
-rw-r--r-- | components/sessionstore/nsSessionStore.manifest | 18 |
15 files changed, 0 insertions, 6699 deletions
diff --git a/components/sessionstore/DocumentUtils.jsm b/components/sessionstore/DocumentUtils.jsm deleted file mode 100644 index 6b3f729..0000000 --- a/components/sessionstore/DocumentUtils.jsm +++ /dev/null @@ -1,230 +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 = [ "DocumentUtils" ]; - -const Cu = Components.utils; -const Ci = Components.interfaces; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource:///modules/sessionstore/XPathGenerator.jsm"); - -this.DocumentUtils = { - /** - * Obtain form data for a DOMDocument instance. - * - * The returned object has 2 keys, "id" and "xpath". Each key holds an object - * which further defines form data. - * - * The "id" object maps element IDs to values. The "xpath" object maps the - * XPath of an element to its value. - * - * @param aDocument - * DOMDocument instance to obtain form data for. - * @return object - * Form data encoded in an object. - */ - getFormData: function DocumentUtils_getFormData(aDocument) { - let formNodes = aDocument.evaluate( - XPathGenerator.restorableFormNodes, - aDocument, - XPathGenerator.resolveNS, - Ci.nsIDOMXPathResult.UNORDERED_NODE_ITERATOR_TYPE, null - ); - - let node; - let ret = {id: {}, xpath: {}}; - - // Limit the number of XPath expressions for performance reasons. See - // bug 477564. - const MAX_TRAVERSED_XPATHS = 100; - let generatedCount = 0; - - while (node = formNodes.iterateNext()) { - let nId = node.id; - let hasDefaultValue = true; - let value; - - // Only generate a limited number of XPath expressions for perf reasons - // (cf. bug 477564) - if (!nId && generatedCount > MAX_TRAVERSED_XPATHS) { - continue; - } - - if (node instanceof Ci.nsIDOMHTMLInputElement || - node instanceof Ci.nsIDOMHTMLTextAreaElement) { - switch (node.type) { - case "checkbox": - case "radio": - value = node.checked; - hasDefaultValue = value == node.defaultChecked; - break; - case "file": - value = { type: "file", fileList: node.mozGetFileNameArray() }; - hasDefaultValue = !value.fileList.length; - break; - default: // text, textarea - value = node.value; - hasDefaultValue = value == node.defaultValue; - break; - } - } else if (!node.multiple) { - // <select>s without the multiple attribute are hard to determine the - // default value, so assume we don't have the default. - hasDefaultValue = false; - value = { selectedIndex: node.selectedIndex, value: node.value }; - } else { - // <select>s with the multiple attribute are easier to determine the - // default value since each <option> has a defaultSelected - let options = Array.map(node.options, function(aOpt, aIx) { - let oSelected = aOpt.selected; - hasDefaultValue = hasDefaultValue && (oSelected == aOpt.defaultSelected); - return oSelected ? aOpt.value : -1; - }); - value = options.filter(function(aIx) aIx !== -1); - } - - // In order to reduce XPath generation (which is slow), we only save data - // for form fields that have been changed. (cf. bug 537289) - if (!hasDefaultValue) { - if (nId) { - ret.id[nId] = value; - } else { - generatedCount++; - ret.xpath[XPathGenerator.generate(node)] = value; - } - } - } - - return ret; - }, - - /** - * Merges form data on a document from previously obtained data. - * - * This is the inverse of getFormData(). The data argument is the same object - * type which is returned by getFormData(): an object containing the keys - * "id" and "xpath" which are each objects mapping element identifiers to - * form values. - * - * Where the document has existing form data for an element, the value - * will be replaced. Where the document has a form element but no matching - * data in the passed object, the element is untouched. - * - * @param aDocument - * DOMDocument instance to which to restore form data. - * @param aData - * Object defining form data. - */ - mergeFormData: function DocumentUtils_mergeFormData(aDocument, aData) { - if ("xpath" in aData) { - for each (let [xpath, value] in Iterator(aData.xpath)) { - let node = XPathGenerator.resolve(aDocument, xpath); - - if (node) { - this.restoreFormValue(node, value, aDocument); - } - } - } - - if ("id" in aData) { - for each (let [id, value] in Iterator(aData.id)) { - let node = aDocument.getElementById(id); - - if (node) { - this.restoreFormValue(node, value, aDocument); - } - } - } - }, - - /** - * Low-level function to restore a form value to a DOMNode. - * - * If you want a higher-level interface, see mergeFormData(). - * - * When the value is changed, the function will fire the appropriate DOM - * events. - * - * @param aNode - * DOMNode to set form value on. - * @param aValue - * Value to set form element to. - * @param aDocument [optional] - * DOMDocument node belongs to. If not defined, node.ownerDocument - * is used. - */ - restoreFormValue: function DocumentUtils_restoreFormValue(aNode, aValue, aDocument) { - aDocument = aDocument || aNode.ownerDocument; - - let eventType; - - if (typeof aValue == "string" && aNode.type != "file") { - // Don't dispatch an input event if there is no change. - if (aNode.value == aValue) { - return; - } - - aNode.value = aValue; - eventType = "input"; - } else if (typeof aValue == "boolean") { - // Don't dispatch a change event for no change. - if (aNode.checked == aValue) { - return; - } - - aNode.checked = aValue; - eventType = "change"; - } else if (typeof aValue == "number") { - // handle select backwards compatibility, example { "#id" : index } - // We saved the value blindly since selects take more work to determine - // default values. So now we should check to avoid unnecessary events. - if (aNode.selectedIndex == aValue) { - return; - } - - if (aValue < aNode.options.length) { - aNode.selectedIndex = aValue; - eventType = "change"; - } - } else if (aValue && aValue.selectedIndex >= 0 && aValue.value) { - // handle select new format - - // Don't dispatch a change event for no change - if (aNode.options[aNode.selectedIndex].value == aValue.value) { - return; - } - - // find first option with matching aValue if possible - for (let i = 0; i < aNode.options.length; i++) { - if (aNode.options[i].value == aValue.value) { - aNode.selectedIndex = i; - break; - } - } - eventType = "change"; - } else if (aValue && aValue.fileList && aValue.type == "file" && - aNode.type == "file") { - aNode.mozSetFileNameArray(aValue.fileList, aValue.fileList.length); - eventType = "input"; - } else if (aValue && typeof aValue.indexOf == "function" && aNode.options) { - Array.forEach(aNode.options, function(opt, index) { - // don't worry about malformed options with same values - opt.selected = aValue.indexOf(opt.value) > -1; - - // Only fire the event here if this wasn't selected by default - if (!opt.defaultSelected) { - eventType = "change"; - } - }); - } - - // Fire events for this node if applicable - if (eventType) { - let event = aDocument.createEvent("UIEvents"); - event.initUIEvent(eventType, true, true, aDocument.defaultView, 0); - aNode.dispatchEvent(event); - } - } -}; diff --git a/components/sessionstore/SessionStorage.jsm b/components/sessionstore/SessionStorage.jsm deleted file mode 100644 index 64aef35..0000000 --- a/components/sessionstore/SessionStorage.jsm +++ /dev/null @@ -1,165 +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 = ["SessionStorage"]; - -const Cu = Components.utils; - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "SessionStore", - "resource:///modules/sessionstore/SessionStore.jsm"); - -this.SessionStorage = { - /** - * Updates all sessionStorage "super cookies" - * @param aDocShell - * That tab's docshell (containing the sessionStorage) - * @param aFullData - * always return privacy sensitive data (use with care) - */ - serialize: function ssto_serialize(aDocShell, aFullData) { - return DomStorage.read(aDocShell, aFullData); - }, - - /** - * Restores all sessionStorage "super cookies". - * @param aDocShell - * A tab's docshell (containing the sessionStorage) - * @param aStorageData - * Storage data to be restored - */ - deserialize: function ssto_deserialize(aDocShell, aStorageData) { - DomStorage.write(aDocShell, aStorageData); - } -}; - -Object.freeze(SessionStorage); - -var DomStorage = { - /** - * Reads all session storage data from the given docShell. - * @param aDocShell - * A tab's docshell (containing the sessionStorage) - * @param aFullData - * Always return privacy sensitive data (use with care) - */ - read: function DomStorage_read(aDocShell, aFullData) { - let data = {}; - let isPinned = aDocShell.isAppTab; - let shistory = aDocShell.sessionHistory; - - for (let i = 0; i < shistory.count; i++) { - let principal = History.getPrincipalForEntry(shistory, i, aDocShell); - if (!principal) - continue; - - // Check if we're allowed to store sessionStorage data. - let isHTTPS = principal.URI && principal.URI.schemeIs("https"); - if (aFullData || SessionStore.checkPrivacyLevel(isHTTPS, isPinned)) { - let origin = principal.extendedOrigin; - - // Don't read a host twice. - if (!(origin in data)) { - let originData = this._readEntry(principal, aDocShell); - if (Object.keys(originData).length) { - data[origin] = originData; - } - } - } - } - - return data; - }, - - /** - * Writes session storage data to the given tab. - * @param aDocShell - * A tab's docshell (containing the sessionStorage) - * @param aStorageData - * Storage data to be restored - */ - write: function DomStorage_write(aDocShell, aStorageData) { - for (let [host, data] in Iterator(aStorageData)) { - let uri = Services.io.newURI(host, null, null); - let principal = Services.scriptSecurityManager.getDocShellCodebasePrincipal(uri, aDocShell); - let storageManager = aDocShell.QueryInterface(Components.interfaces.nsIDOMStorageManager); - let window = aDocShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor) - .getInterface(Components.interfaces.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. - try { - let storage = storageManager.createStorage(window, principal, "", aDocShell.usePrivateBrowsing); - } catch(e) { - Cu.reportError(e); - } - - for (let [key, value] in Iterator(data)) { - try { - storage.setItem(key, value); - } catch (e) { - // throws e.g. for URIs that can't have sessionStorage - Cu.reportError(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 DomStorage_readEntry(aPrincipal, aDocShell) { - let hostData = {}; - let storage; - - try { - let storageManager = aDocShell.QueryInterface(Components.interfaces.nsIDOMStorageManager); - storage = storageManager.getStorage(aPrincipal); - } catch (e) { - // sessionStorage might throw if it's turned off, see bug 458954 - } - - 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; - } -}; - -var History = { - /** - * Returns a given history entry's URI. - * @param aHistory - * That tab's session history - * @param aIndex - * The history entry's index - * @param aDocShell - * That tab's docshell - */ - getPrincipalForEntry: function History_getPrincipalForEntry(aHistory, - aIndex, - aDocShell) { - try { - return Services.scriptSecurityManager.getDocShellCodebasePrincipal( - aHistory.getEntryAtIndex(aIndex, false).URI, aDocShell); - } catch (e) { - // This might throw for some reason. - } - }, -}; diff --git a/components/sessionstore/SessionStore.jsm b/components/sessionstore/SessionStore.jsm deleted file mode 100644 index e19a578..0000000 --- a/components/sessionstore/SessionStore.jsm +++ /dev/null @@ -1,4786 +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 = ["SessionStore"]; - -const Cu = Components.utils; -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cr = Components.results; - -const STATE_STOPPED = 0; -const STATE_RUNNING = 1; -const STATE_QUITTING = -1; - -const STATE_STOPPED_STR = "stopped"; -const STATE_RUNNING_STR = "running"; - -const TAB_STATE_NEEDS_RESTORE = 1; -const TAB_STATE_RESTORING = 2; - -const PRIVACY_NONE = 0; -const PRIVACY_ENCRYPTED = 1; -const PRIVACY_FULL = 2; - -const NOTIFY_WINDOWS_RESTORED = "sessionstore-windows-restored"; -const NOTIFY_BROWSER_STATE_RESTORED = "sessionstore-browser-state-restored"; - -// Default maximum number of tabs to restore simultaneously. Controlled by -// the browser.sessionstore.max_concurrent_tabs pref. -const DEFAULT_MAX_CONCURRENT_TAB_RESTORES = 3; - -// global notifications observed -const OBSERVING = [ - "domwindowopened", "domwindowclosed", - "quit-application-requested", "quit-application-granted", - "browser-lastwindow-close-granted", - "quit-application", "browser:purge-session-history", - "browser:purge-domain-data" -]; - -// 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" -]; - -const MESSAGES = [ - // The content script tells us that its form data (or that of one of its - // subframes) might have changed. This can be the contents or values of - // standard form fields or of ContentEditables. - "SessionStore:input", - - // The content script has received a pageshow event. This happens when a - // page is loaded from bfcache without any network activity, i.e. when - // clicking the back or forward button. - "SessionStore:pageshow" -]; - -// These are tab events that we listen to. -const TAB_EVENTS = [ - "TabOpen", "TabClose", "TabSelect", "TabShow", "TabHide", "TabPinned", - "TabUnpinned" -]; - -#ifndef XP_WIN -#define BROKEN_WM_Z_ORDER -#endif - -Cu.import("resource://gre/modules/Services.jsm", this); -Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); -// debug.js adds NS_ASSERT. cf. bug 669196 -Cu.import("resource://gre/modules/debug.js", this); -Cu.import("resource://gre/modules/osfile.jsm", this); -Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm", this); -Cu.import("resource://gre/modules/Promise.jsm", this); - -XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup", - "@mozilla.org/browser/sessionstartup;1", "nsISessionStartup"); -XPCOMUtils.defineLazyServiceGetter(this, "gScreenManager", - "@mozilla.org/gfx/screenmanager;1", "nsIScreenManager"); - -// 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. -var gDocShellCapabilities = (function () { - let caps; - - return docShell => { - if (!caps) { - let keys = Object.keys(docShell); - caps = keys.filter(k => k.startsWith("allow")).map(k => k.slice(5)); - } - - return caps; - }; -})(); - -XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", - "resource://gre/modules/NetUtil.jsm"); - -#ifdef MOZ_DEVTOOLS -XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager", - "resource://devtools/client/scratchpad/scratchpad-manager.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 -}); -#endif - -XPCOMUtils.defineLazyModuleGetter(this, "DocumentUtils", - "resource:///modules/sessionstore/DocumentUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage", - "resource:///modules/sessionstore/SessionStorage.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "_SessionFile", - "resource:///modules/sessionstore/_SessionFile.jsm"); - -function debug(aMsg) { - aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n"); - Services.console.logStringMessage(aMsg); -} - -this.SessionStore = { - get promiseInitialized() { - return SessionStoreInternal.promiseInitialized.promise; - }, - - get canRestoreLastSession() { - return SessionStoreInternal.canRestoreLastSession; - }, - - set canRestoreLastSession(val) { - SessionStoreInternal.canRestoreLastSession = val; - }, - - init: function ss_init(aWindow) { - return SessionStoreInternal.init(aWindow); - }, - - 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) { - return SessionStoreInternal.duplicateTab(aWindow, aTab, aDelta); - }, - - getClosedTabCount: function ss_getClosedTabCount(aWindow) { - return SessionStoreInternal.getClosedTabCount(aWindow); - }, - - getClosedTabData: function ss_getClosedTabDataAt(aWindow) { - return SessionStoreInternal.getClosedTabData(aWindow); - }, - - 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() { - return SessionStoreInternal.getClosedWindowData(); - }, - - 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); - }, - - persistTabAttribute: function ss_persistTabAttribute(aName) { - SessionStoreInternal.persistTabAttribute(aName); - }, - - restoreLastSession: function ss_restoreLastSession() { - SessionStoreInternal.restoreLastSession(); - }, - - checkPrivacyLevel: function ss_checkPrivacyLevel(aIsHTTPS, aUseDefaultPref) { - return SessionStoreInternal.checkPrivacyLevel(aIsHTTPS, aUseDefaultPref); - } -}; - -// 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 - ]), - - // set default load state - _loadState: STATE_STOPPED, - - // During the initial restore and setBrowserState calls tracks the number of - // windows yet to be restored - _restoreCount: -1, - - // whether a setBrowserState call is in progress - _browserSetState: false, - - // time in milliseconds (Date.now()) when the session was last written to file - _lastSaveTime: 0, - - // 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: {}, - - // internal states for all open windows (data we need to associate, - // but not write to disk) - _internalWindows: {}, - - // states for all recently closed windows - _closedWindows: [], - - // not-"dirty" windows usually don't need to have their data updated - _dirtyWindows: {}, - - // 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, - - // max number of tabs to restore concurrently - _maxConcurrentTabRestores: DEFAULT_MAX_CONCURRENT_TAB_RESTORES, - - // whether restored tabs load cached versions or force a reload - _cacheBehavior: 0, - - // 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 - _lastSessionState: null, - - // 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 - _promiseInitialization: Promise.defer(), - - // Whether session has been initialized - _sessionInitialized: false, - - // True if session store is disabled by multi-process browsing. - // See bug 516755. - _disabledForMultiProcess: false, - - // The original "sessionstore.resume_session_once" preference value before it - // was modified by saveState. saveState will set the - // "sessionstore.resume_session_once" to true when the - // the "sessionstore.resume_from_crash" preference is false (crash recovery - // is disabled) so that pinned tabs will be restored in the case of a - // crash. This variable is used to restore the original value so the - // previous session is not always restored when - // "sessionstore.resume_from_crash" is true. - _resume_session_once_on_shutdown: null, - - /** - * A promise fulfilled once initialization is complete. - */ - get promiseInitialized() { - return this._promiseInitialization; - }, - - /* ........ Public Getters .............. */ - get canRestoreLastSession() { - return this._lastSessionState; - }, - - set canRestoreLastSession(val) { - this._lastSessionState = null; - }, - - /* ........ Global Event Handlers .............. */ - - /** - * Initialize the component - */ - initService: function ssi_initService() { - if (this._sessionInitialized) { - return; - } - OBSERVING.forEach(function(aTopic) { - Services.obs.addObserver(this, aTopic, true); - }, this); - - this._initPrefs(); - - this._disabledForMultiProcess = false; - - // this pref is only read at startup, so no need to observe it - this._sessionhistory_max_entries = - this._prefBranch.getIntPref("sessionhistory.max_entries"); - - gSessionStartup.onceInitialized.then( - this.initSession.bind(this) - ); - }, - - initSession: function ssi_initSession() { - let ss = gSessionStartup; - try { - if (ss.doRestore() || - ss.sessionType == Ci.nsISessionStartup.DEFER_SESSION) - this._initialState = ss.state; - } - catch(ex) { dump(ex + "\n"); } // no state to restore, which is ok - - if (this._initialState) { - 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(this._initialState); - // If we have a iniState with windows, that means that we have windows - // with app tabs to restore. - if (iniState.windows.length) - this._initialState = iniState; - else - this._initialState = null; - if (remainingState.windows.length) - this._lastSessionState = remainingState; - } - else { - // Get the last deferred session in case the user still wants to - // restore it - this._lastSessionState = this._initialState.lastSessionState; - - let lastSessionCrashed = - this._initialState.session && this._initialState.session.state && - this._initialState.session.state == STATE_RUNNING_STR; - if (lastSessionCrashed) { - this._recentCrashes = (this._initialState.session && - this._initialState.session.recentCrashes || 0) + 1; - - if (this._needsRestorePage(this._initialState, this._recentCrashes)) { - // replace the crashed session with a restore-page-only session - let pageData = { - url: "about:sessionrestore", - formdata: { - id: { "sessionData": this._initialState }, - xpath: {} - } - }; - this._initialState = { windows: [{ tabs: [{ entries: [pageData] }] }] }; - } - } - - // Load the session start time from the previous state - this._sessionStartTime = this._initialState.session && - this._initialState.session.startTime || - this._sessionStartTime; - - // make sure that at least the first window doesn't have anything hidden - delete this._initialState.windows[0].hidden; - // Since nothing is hidden in the first window, it cannot be a popup - delete this._initialState.windows[0].isPopup; - // We don't want to minimize and then open a window at startup. - if (this._initialState.windows[0].sizemode == "minimized") - this._initialState.windows[0].sizemode = "normal"; - // clear any lastSessionWindowID attributes since those don't matter - // during normal restore - this._initialState.windows.forEach(function(aWindow) { - delete aWindow.__lastSessionWindowID; - }); - } - } - catch (ex) { debug("The session file is invalid: " + ex); } - } - - // A Lazy getter for the sessionstore.js backup promise. - XPCOMUtils.defineLazyGetter(this, "_backupSessionFileOnce", function () { - return _SessionFile.createBackupCopy(); - }); - - // 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 (this._loadState != STATE_QUITTING && - this._prefBranch.getBoolPref("sessionstore.resume_session_once")) - this._prefBranch.setBoolPref("sessionstore.resume_session_once", false); - - this._initEncoding(); - - // Session is ready. - this._sessionInitialized = true; - this._promiseInitialization.resolve(); - }, - - _initEncoding : function ssi_initEncoding() { - // The (UTF-8) encoder used to write to files. - XPCOMUtils.defineLazyGetter(this, "_writeFileEncoder", function () { - return new TextEncoder(); - }); - }, - - _initPrefs : function() { - XPCOMUtils.defineLazyGetter(this, "_prefBranch", function () { - return Services.prefs.getBranch("browser."); - }); - - // minimal interval between two save operations (in milliseconds) - XPCOMUtils.defineLazyGetter(this, "_interval", function () { - // used often, so caching/observing instead of fetching on-demand - this._prefBranch.addObserver("sessionstore.interval", this, true); - return this._prefBranch.getIntPref("sessionstore.interval"); - }); - - // when crash recovery is disabled, session data is not written to disk - XPCOMUtils.defineLazyGetter(this, "_resume_from_crash", function () { - // get crash recovery state from prefs and allow for proper reaction to state changes - this._prefBranch.addObserver("sessionstore.resume_from_crash", this, true); - return this._prefBranch.getBoolPref("sessionstore.resume_from_crash"); - }); - - 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); - - // Straight-up collect the following one-time prefs - this._maxConcurrentTabRestores = - Services.prefs.getIntPref("browser.sessionstore.max_concurrent_tabs"); - // ensure a sane value for concurrency, ignore and set default otherwise - if (this._maxConcurrentTabRestores < 1 || this._maxConcurrentTabRestores > 10) { - this._maxConcurrentTabRestores = DEFAULT_MAX_CONCURRENT_TAB_RESTORES; - } - this._cacheBehavior = - Services.prefs.getIntPref("browser.sessionstore.cache_behavior"); - - }, - - _initWindow: function ssi_initWindow(aWindow) { - if (aWindow) { - this.onLoad(aWindow); - } else if (this._loadState == STATE_STOPPED) { - // If init is being called with a null window, it's possible that we - // just want to tell sessionstore that a session is live (as is the case - // with starting Firefox with -private, for example; see bug 568816), - // so we should mark the load state as running to make sure that - // things like setBrowserState calls will succeed in restoring the session. - this._loadState = STATE_RUNNING; - } - }, - - /** - * Start tracking a window. - * - * This function also initializes the component if it is not - * initialized yet. - */ - init: function ssi_init(aWindow) { - let self = this; - this.initService(); - return this._promiseInitialization.promise.then( - function onSuccess() { - self._initWindow(aWindow); - } - ); - }, - - /** - * Called on application shutdown, after notifications: - * quit-application-granted, quit-application - */ - _uninit: function ssi_uninit() { - // save all data for session resuming - if (this._sessionInitialized) - this.saveState(true); - - // clear out priority queue in case it's still holding refs - TabRestoreQueue.reset(); - - // Make sure to break our cycle with the save timer - if (this._saveTimer) { - this._saveTimer.cancel(); - this._saveTimer = null; - } - }, - - /** - * Handle notifications - */ - observe: function ssi_observe(aSubject, aTopic, aData) { - if (this._disabledForMultiProcess) - return; - - switch (aTopic) { - case "domwindowopened": // catch new windows - this.onOpen(aSubject); - break; - case "domwindowclosed": // catch closed windows - this.onClose(aSubject); - break; - case "quit-application-requested": - this.onQuitApplicationRequested(); - break; - case "quit-application-granted": - this.onQuitApplicationGranted(); - 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 "timer-callback": // timer call back for delayed saving - this.onTimerCallback(); - break; - } - }, - - /** - * This method handles incoming messages sent by the session store content - * script and thus enables communication with OOP tabs. - */ - receiveMessage: function ssi_receiveMessage(aMessage) { - var browser = aMessage.target; - var win = browser.ownerDocument.defaultView; - - switch (aMessage.name) { - case "SessionStore:pageshow": - this.onTabLoad(win, browser); - break; - case "SessionStore:input": - this.onTabInput(win, browser); - break; - default: - debug("received unknown message '" + aMessage.name + "'"); - break; - } - - this._clearRestoringWindows(); - }, - - /* ........ Window Event Handlers .............. */ - - /** - * Implement nsIDOMEventListener for handling various window and tab events - */ - handleEvent: function ssi_handleEvent(aEvent) { - if (this._disabledForMultiProcess) - return; - - var win = aEvent.currentTarget.ownerDocument.defaultView; - switch (aEvent.type) { - case "load": - // If __SS_restore_data is set, then we need to restore the document - // (form data, scrolling, etc.). This will only happen when a tab is - // first restored. - let browser = aEvent.currentTarget; - if (browser.__SS_restore_data) - this.restoreDocument(win, browser, aEvent); - this.onTabLoad(win, browser); - break; - case "TabOpen": - this.onTabAdd(win, aEvent.originalTarget); - break; - case "TabClose": - // aEvent.detail determines if the tab was closed by moving to a different window - if (!aEvent.detail) - this.onTabClose(win, aEvent.originalTarget); - this.onTabRemove(win, aEvent.originalTarget); - break; - case "TabSelect": - this.onTabSelect(win); - break; - case "TabShow": - this.onTabShow(win, aEvent.originalTarget); - break; - case "TabHide": - this.onTabHide(win, aEvent.originalTarget); - break; - case "TabPinned": - case "TabUnpinned": - this.saveStateDelayed(win); - break; - } - - this._clearRestoringWindows(); - }, - - /** - * If it's the first window load since app start... - * - determine if we're reloading after a crash or a forced-restart - * - restore window state - * - restart downloads - * Set up event listeners for this window's tabs - * @param aWindow - * Window reference - */ - onLoad: function ssi_onLoad(aWindow) { - // return if window has already been initialized - if (aWindow && aWindow.__SSi && this._windows[aWindow.__SSi]) - return; - - // ignore non-browser windows and windows opened while shutting down - if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser" || - this._loadState == STATE_QUITTING) - return; - - // assign it a unique identifier (timestamp) - aWindow.__SSi = "window" + Date.now(); - - // and create its data object - this._windows[aWindow.__SSi] = { tabs: [], selected: 0, _closedTabs: [], busy: false }; - - // and create its internal data object - this._internalWindows[aWindow.__SSi] = { hosts: {} } - - let isPrivateWindow = false; - if (PrivateBrowsingUtils.isWindowPrivate(aWindow)) - this._windows[aWindow.__SSi].isPrivate = isPrivateWindow = true; - if (!this._isWindowLoaded(aWindow)) - this._windows[aWindow.__SSi]._restoring = true; - if (!aWindow.toolbar.visible) - this._windows[aWindow.__SSi].isPopup = true; - - // perform additional initialization when the first window is loading - if (this._loadState == STATE_STOPPED) { - this._loadState = STATE_RUNNING; - this._lastSaveTime = Date.now(); - - // restore a crashed session resp. resume the last session if requested - if (this._initialState) { - 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; - delete this._initialState; - - // Nothing to restore now, notify observers things are complete. - Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, ""); - } else { - // make sure that the restored tabs are first in the window - this._initialState._firstTabs = true; - this._restoreCount = this._initialState.windows ? this._initialState.windows.length : 0; - this.restoreWindow(aWindow, this._initialState, - this._isCmdLineEmpty(aWindow, this._initialState)); - delete this._initialState; - - // _loadState changed from "stopped" to "running" - // force a save operation so that crashes happening during startup are correctly counted - this.saveState(true); - } - } - else { - // Nothing to restore, notify observers things are complete. - Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, ""); - - // the next delayed save request should execute immediately - this._lastSaveTime -= this._interval; - } - } - // this window was opened by _openWindowWithState - else if (!this._isWindowLoaded(aWindow)) { - let followUp = this._statesToRestore[aWindow.__SS_restoreID].windows.length == 1; - this.restoreWindow(aWindow, this._statesToRestore[aWindow.__SS_restoreID], true, followUp); - } - // 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) { - - this._deferredInitialState._firstTabs = true; - this._restoreCount = this._deferredInitialState.windows ? - this._deferredInitialState.windows.length : 0; - this.restoreWindow(aWindow, this._deferredInitialState, false); - 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; -#ifndef XP_MACOSX - if (!this._doResumeSession()) { -#endif - // 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]; - } -#ifndef XP_MACOSX - } - 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; - } -#endif - if (newWindowState) { - // Ensure that the window state isn't hidden - this._restoreCount = 1; - let state = { windows: [newWindowState] }; - this.restoreWindow(aWindow, state, this._isCmdLineEmpty(aWindow, state)); - } - } - // 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; - } - - var tabbrowser = aWindow.gBrowser; - - // add tab change listeners to all already existing tabs - for (let i = 0; i < tabbrowser.tabs.length; i++) { - this.onTabAdd(aWindow, tabbrowser.tabs[i], true); - } - // notification of tab add/remove/selection/show/hide - TAB_EVENTS.forEach(function(aEvent) { - tabbrowser.tabContainer.addEventListener(aEvent, this, true); - }, this); - }, - - /** - * On window open - * @param aWindow - * Window reference - */ - onOpen: function ssi_onOpen(aWindow) { - var _this = this; - aWindow.addEventListener("load", function(aEvent) { - aEvent.currentTarget.removeEventListener("load", arguments.callee, false); - _this.onLoad(aEvent.currentTarget); - }, false); - return; - }, - - /** - * 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 = "window" + Date.now(); - 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; - - TAB_EVENTS.forEach(function(aEvent) { - tabbrowser.tabContainer.removeEventListener(aEvent, this, true); - }, this); - - // remove the progress listener for this window - tabbrowser.removeTabsProgressListener(gRestoreTabsProgressListener); - - let winData = this._windows[aWindow.__SSi]; - if (this._loadState == STATE_RUNNING) { // window not closed during a regular shut-down - // update all window data for a last time - this._collectWindowData(aWindow); - - if (isFullyLoaded) { - winData.title = aWindow.content.document.title || tabbrowser.selectedTab.label; - winData.title = this._replaceLoadingTitle(winData.title, tabbrowser, - tabbrowser.selectedTab); - let windows = {}; - windows[aWindow.__SSi] = winData; - this._updateCookies(windows); - } - -#ifndef XP_MACOSX - // Until we decide otherwise elsewhere, this window is part of a series - // of closing windows to quit. - winData._shouldRestore = true; -#endif - - // Save the window if it has multiple tabs or a single saveable tab and - // it's not private. - if (!winData.isPrivate && (winData.tabs.length > 1 || - (winData.tabs.length == 1 && this._shouldSaveTabState(winData.tabs[0])))) { - // we don't want to save the busy state - delete winData.busy; - - this._closedWindows.unshift(winData); - this._capClosedWindows(); - } - - // clear this window from the list - delete this._windows[aWindow.__SSi]; - delete this._internalWindows[aWindow.__SSi]; - - // save the state without this window to disk - this.saveStateDelayed(); - } - - for (let i = 0; i < tabbrowser.tabs.length; i++) { - this.onTabRemove(aWindow, tabbrowser.tabs[i], true); - } - - // Cache the window state until it is completely gone. - DyingWindowCache.set(aWindow, winData); - - delete aWindow.__SSi; - }, - - /** - * On quit application requested - */ - onQuitApplicationRequested: function ssi_onQuitApplicationRequested() { - // get a current snapshot of all windows - this._forEachBrowserWindow(function(aWindow) { - this._collectWindowData(aWindow); - }); - // 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 || ""; - this._dirtyWindows = []; - }, - - /** - * On quit application granted - */ - onQuitApplicationGranted: function ssi_onQuitApplicationGranted() { - // freeze the data at what we've got (ignoring closing windows) - this._loadState = STATE_QUITTING; - }, - - /** - * 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"); - } - else if (this._resume_session_once_on_shutdown != null) { - // if the sessionstore.resume_session_once preference was changed by - // saveState because crash recovery is disabled then restore the - // preference back to the value it was prior to that. This will prevent - // SessionStore from always restoring the session when crash recovery is - // disabled. - this._prefBranch.setBoolPref("sessionstore.resume_session_once", - this._resume_session_once_on_shutdown); - } - - if (aData != "restart") { - // Throw away the previous session on shutdown - this._lastSessionState = null; - } - - this._loadState = STATE_QUITTING; // just to be sure - this._uninit(); - }, - - /** - * On purge of session history - */ - onPurgeSessionHistory: function ssi_onPurgeSessionHistory() { - var _this = this; - _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 (this._loadState == STATE_QUITTING) - return; - this._lastSessionState = null; - let openWindows = {}; - this._forEachBrowserWindow(function(aWindow) { - Array.forEach(aWindow.gBrowser.tabs, function(aTab) { - delete aTab.linkedBrowser.__SS_data; - delete aTab.linkedBrowser.__SS_tabStillLoading; - delete aTab.linkedBrowser.__SS_formDataSaved; - delete aTab.linkedBrowser.__SS_hostSchemeData; - if (aTab.linkedBrowser.__SS_restoreState) - this._resetTabRestoringState(aTab); - }, this); - openWindows[aWindow.__SSi] = 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]; - delete this._internalWindows[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(function() { _this.saveState(true); }, 0); - else if (this._loadState == STATE_RUNNING) - this.saveState(true); - // Delete the private browsing backed up state, if any - if ("_stateBackup" in this) - delete this._stateBackup; - - this._clearRestoringWindows(); - }, - - /** - * 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) { - try { - if (this._getURIFromString(aEntry.url).host.hasRootDomain(aData)) - return true; - } - catch (ex) { /* url had no host at all */ } - 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 (this._loadState == STATE_RUNNING) - this.saveState(true); - - 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; - case "sessionstore.interval": - this._interval = this._prefBranch.getIntPref("sessionstore.interval"); - // reset timer and save - if (this._saveTimer) { - this._saveTimer.cancel(); - this._saveTimer = null; - } - this.saveStateDelayed(null, -1); - break; - case "sessionstore.resume_from_crash": - this._resume_from_crash = this._prefBranch.getBoolPref("sessionstore.resume_from_crash"); - // restore original resume_session_once preference if set in saveState - if (this._resume_session_once_on_shutdown != null) { - this._prefBranch.setBoolPref("sessionstore.resume_session_once", - this._resume_session_once_on_shutdown); - this._resume_session_once_on_shutdown = null; - } - // either create the file with crash recovery information or remove it - // (when _loadState is not STATE_RUNNING, that file is used for session resuming instead) - if (!this._resume_from_crash) - _SessionFile.wipe(); - this.saveState(true); - break; - } - }, - - /** - * On timer callback - */ - onTimerCallback: function ssi_onTimerCallback() { - this._saveTimer = null; - this.saveState(); - }, - - /** - * set up listeners for a new tab - * @param aWindow - * Window reference - * @param aTab - * Tab reference - * @param aNoNotification - * bool Do not save state if we're updating an existing tab - */ - onTabAdd: function ssi_onTabAdd(aWindow, aTab, aNoNotification) { - let browser = aTab.linkedBrowser; - browser.addEventListener("load", this, true); - - let mm = browser.messageManager; - MESSAGES.forEach(msg => mm.addMessageListener(msg, this)); - - if (!aNoNotification) { - this.saveStateDelayed(aWindow); - } - }, - - /** - * 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("load", this, true); - - let mm = browser.messageManager; - MESSAGES.forEach(msg => mm.removeMessageListener(msg, this)); - - delete browser.__SS_data; - delete browser.__SS_tabStillLoading; - delete browser.__SS_formDataSaved; - delete browser.__SS_hostSchemeData; - - // 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; - } - - // make sure that the tab related data is up-to-date - var tabState = this._collectTabData(aTab); - this._updateTextAndScrollDataForTab(aWindow, aTab.linkedBrowser, tabState); - - // store closed-tab data for undo - if (this._shouldSaveTabState(tabState)) { - let tabTitle = aTab.label; - let tabbrowser = aWindow.gBrowser; - tabTitle = this._replaceLoadingTitle(tabTitle, tabbrowser, aTab); - - this._windows[aWindow.__SSi]._closedTabs.unshift({ - state: tabState, - title: tabTitle, - image: tabbrowser.getIcon(aTab), - pos: aTab._tPos - }); - var length = this._windows[aWindow.__SSi]._closedTabs.length; - if (length > this._max_tabs_undo) - this._windows[aWindow.__SSi]._closedTabs.splice(this._max_tabs_undo, length - this._max_tabs_undo); - } - }, - - /** - * When a tab loads, save state. - * @param aWindow - * Window reference - * @param aBrowser - * Browser reference - */ - onTabLoad: function ssi_onTabLoad(aWindow, aBrowser) { - // react on "load" and solitary "pageshow" events (the first "pageshow" - // following "load" is too late for deleting the data caches) - // It's possible to get a load event after calling stop on a browser (when - // overwriting tabs). We want to return early if the tab hasn't been restored yet. - if (aBrowser.__SS_restoreState && - aBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) { - return; - } - - delete aBrowser.__SS_data; - delete aBrowser.__SS_tabStillLoading; - delete aBrowser.__SS_formDataSaved; - this.saveStateDelayed(aWindow); - - }, - - /** - * Called when a browser sends the "input" notification - * @param aWindow - * Window reference - * @param aBrowser - * Browser reference - */ - onTabInput: function ssi_onTabInput(aWindow, aBrowser) { - // deleting __SS_formDataSaved will cause us to recollect form data - delete aBrowser.__SS_formDataSaved; - - this.saveStateDelayed(aWindow, 3000); - }, - - /** - * When a tab is selected, save session data - * @param aWindow - * Window reference - */ - onTabSelect: function ssi_onTabSelect(aWindow) { - if (this._loadState == STATE_RUNNING) { - this._windows[aWindow.__SSi].selected = aWindow.gBrowser.tabContainer.selectedIndex; - - let tab = aWindow.gBrowser.selectedTab; - // If __SS_restoreState is still on the browser and it is - // TAB_STATE_NEEDS_RESTORE, then then we haven't restored - // this tab yet. Explicitly call restoreTab to kick off the restore. - if (tab.linkedBrowser.__SS_restoreState && - tab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) - this.restoreTab(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 due to changing groups in Panorama. - 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 due to changing groups in Panorama. - this.saveStateDelayed(aWindow); - }, - - /* ........ nsISessionStore API .............. */ - - getBrowserState: function ssi_getBrowserState() { - return this._toJSONString(this._getCurrentState()); - }, - - setBrowserState: function ssi_setBrowserState(aState) { - this._handleClosedWindows(); - - try { - var state = JSON.parse(aState); - } - catch (ex) { /* invalid state object - don't restore anything */ } - if (!state || !state.windows) - throw (Components.returnCode = 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; - - // restore to the given state - this.restoreWindow(window, state, true); - }, - - getWindowState: function ssi_getWindowState(aWindow) { - if ("__SSi" in aWindow) { - return this._toJSONString(this._getWindowState(aWindow)); - } - - if (DyingWindowCache.has(aWindow)) { - let data = DyingWindowCache.get(aWindow); - return this._toJSONString({ windows: [data] }); - } - - throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); - }, - - setWindowState: function ssi_setWindowState(aWindow, aState, aOverwrite) { - if (!aWindow.__SSi) - throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); - - this.restoreWindow(aWindow, aState, aOverwrite); - }, - - getTabState: function ssi_getTabState(aTab) { - if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi) - throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); - - var tabState = this._collectTabData(aTab); - - var window = aTab.ownerDocument.defaultView; - this._updateTextAndScrollDataForTab(window, aTab.linkedBrowser, tabState); - - return this._toJSONString(tabState); - }, - - setTabState: function ssi_setTabState(aTab, aState) { - var tabState = JSON.parse(aState); - if (!tabState.entries || !aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi) - throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); - - var window = aTab.ownerDocument.defaultView; - this._setWindowStateBusy(window); - this.restoreHistoryPrecursor(window, [aTab], [tabState], 0, 0, 0); - }, - - duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta) { - if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi || - !aWindow.getBrowser) - throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); - - var tabState = this._collectTabData(aTab, true); - var sourceWindow = aTab.ownerDocument.defaultView; - this._updateTextAndScrollDataForTab(sourceWindow, aTab.linkedBrowser, tabState, true); - tabState.index += aDelta; - tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length)); - tabState.pinned = false; - - this._setWindowStateBusy(aWindow); - let newTab = aTab == aWindow.gBrowser.selectedTab ? - aWindow.gBrowser.addTab(null, {relatedToCurrent: true, ownerTab: aTab}) : - aWindow.gBrowser.addTab(); - - this.restoreHistoryPrecursor(aWindow, [newTab], [tabState], 0, 0, 0, - true /* Load this tab right away. */); - - return newTab; - }, - - getClosedTabCount: function ssi_getClosedTabCount(aWindow) { - if ("__SSi" in aWindow) { - return this._windows[aWindow.__SSi]._closedTabs.length; - } - - if (DyingWindowCache.has(aWindow)) { - return DyingWindowCache.get(aWindow)._closedTabs.length; - } - - throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); - }, - - getClosedTabData: function ssi_getClosedTabDataAt(aWindow) { - if ("__SSi" in aWindow) { - return this._toJSONString(this._windows[aWindow.__SSi]._closedTabs); - } - - if (DyingWindowCache.has(aWindow)) { - let data = DyingWindowCache.get(aWindow); - return this._toJSONString(data._closedTabs); - } - - throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); - }, - - undoCloseTab: function ssi_undoCloseTab(aWindow, aIndex) { - if (!aWindow.__SSi) - throw (Components.returnCode = 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.returnCode = Cr.NS_ERROR_INVALID_ARG); - - // fetch the data of closed tab, while removing it from the array - let closedTab = closedTabs.splice(aIndex, 1).shift(); - let closedTabState = closedTab.state; - - this._setWindowStateBusy(aWindow); - // create a new tab - let tabbrowser = aWindow.gBrowser; - let tab = tabbrowser.addTab(); - - // restore tab content - this.restoreHistoryPrecursor(aWindow, [tab], [closedTabState], 1, 0, 0); - - // restore the tab's position - tabbrowser.moveTabTo(tab, closedTab.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.returnCode = 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.returnCode = Cr.NS_ERROR_INVALID_ARG); - - // remove closed tab from the array - closedTabs.splice(aIndex, 1); - }, - - getClosedWindowCount: function ssi_getClosedWindowCount() { - return this._closedWindows.length; - }, - - getClosedWindowData: function ssi_getClosedWindowData() { - return this._toJSONString(this._closedWindows); - }, - - undoCloseWindow: function ssi_undoCloseWindow(aIndex) { - if (!(aIndex in this._closedWindows)) - throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); - - // reopen the window - let state = { windows: this._closedWindows.splice(aIndex, 1) }; - 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.returnCode = Cr.NS_ERROR_INVALID_ARG); - - // remove closed window from the array - this._closedWindows.splice(aIndex, 1); - }, - - 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.returnCode = Cr.NS_ERROR_INVALID_ARG); - }, - - setWindowValue: function ssi_setWindowValue(aWindow, aKey, aStringValue) { - if (aWindow.__SSi) { - if (!this._windows[aWindow.__SSi].extData) { - this._windows[aWindow.__SSi].extData = {}; - } - this._windows[aWindow.__SSi].extData[aKey] = aStringValue; - this.saveStateDelayed(aWindow); - } - else { - throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); - } - }, - - 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]; - }, - - getTabValue: function ssi_getTabValue(aTab, aKey) { - let data = {}; - if (aTab.__SS_extdata) { - data = aTab.__SS_extdata; - } - else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) { - // If the tab hasn't been fully restored, get the data from the to-be-restored data - data = aTab.linkedBrowser.__SS_data.extData; - } - return data[aKey] || ""; - }, - - setTabValue: function ssi_setTabValue(aTab, aKey, aStringValue) { - // If the tab hasn't been restored, then set the data there, otherwise we - // could lose newly added data. - let saveTo; - if (aTab.__SS_extdata) { - saveTo = aTab.__SS_extdata; - } - else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) { - saveTo = aTab.linkedBrowser.__SS_data.extData; - } - else { - aTab.__SS_extdata = {}; - saveTo = aTab.__SS_extdata; - } - saveTo[aKey] = aStringValue; - this.saveStateDelayed(aTab.ownerDocument.defaultView); - }, - - deleteTabValue: function ssi_deleteTabValue(aTab, aKey) { - // We want to make sure that if data is accessed early, we attempt to delete - // that data from __SS_data as well. Otherwise we'll throw in cases where - // data can be set or read. - let deleteFrom; - if (aTab.__SS_extdata) { - deleteFrom = aTab.__SS_extdata; - } - else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) { - deleteFrom = aTab.linkedBrowser.__SS_data.extData; - } - - if (deleteFrom && deleteFrom[aKey]) - delete deleteFrom[aKey]; - }, - - persistTabAttribute: function ssi_persistTabAttribute(aName) { - if (TabAttributes.persist(aName)) { - this.saveStateDelayed(); - } - }, - - /** - * Restores the session state stored in _lastSessionState. 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 winddow. 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.returnCode = Cr.NS_ERROR_FAILURE); - - // First collect each window with its id... - let windows = {}; - this._forEachBrowserWindow(function(aWindow) { - if (aWindow.__SS_lastSessionWindowID) - windows[aWindow.__SS_lastSessionWindowID] = aWindow; - }); - - let lastSessionState = this._lastSessionState; - - // This shouldn't ever be the case... - if (!lastSessionState.windows.length) - throw (Components.returnCode = 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; - - // 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._prefBranch.getIntPref("sessionstore.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. The hack we have - // in _preWindowToRestoreInto will prevent most (all?) Panorama - // weirdness but we will still merge other extData. - // Bug 588217 should make this go away by merging the group data. - this.restoreWindow(windowToUse, { windows: [winState] }, canOverwriteTabs, true); - } - 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(); - } - -#ifdef MOZ_DEVTOOLS - // Scratchpad - if (lastSessionState.scratchpads) { - ScratchpadManager.restoreSession(lastSessionState.scratchpads); - } - - // The Browser Console - if (lastSessionState.browserConsole) { - HUDService.restoreBrowserConsoleSession(); - } -#endif - - // Set data that persists between sessions - this._recentCrashes = lastSessionState.session && - lastSessionState.session.recentCrashes || 0; - this._sessionStartTime = lastSessionState.session && - lastSessionState.session.startTime || - this._sessionStartTime; - - this._lastSessionState = null; - }, - - /** - * 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]; - - let event = aWindow.document.createEvent("Events"); - event.initEvent("SSRestoreIntoWindow", true, true); - - // Check if we can use the window. - if (!aWindow.dispatchEvent(event)) - 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 all session data for a window - * @param aWindow - * Window reference - */ - _saveWindowHistory: function ssi_saveWindowHistory(aWindow) { - var tabbrowser = aWindow.gBrowser; - var tabs = tabbrowser.tabs; - var tabsData = this._windows[aWindow.__SSi].tabs = []; - - for (var i = 0; i < tabs.length; i++) - tabsData.push(this._collectTabData(tabs[i])); - - this._windows[aWindow.__SSi].selected = tabbrowser.mTabBox.selectedIndex + 1; - }, - - /** - * Collect data related to a single tab - * @param aTab - * tabbrowser tab - * @param aFullData - * always return privacy sensitive data (use with care) - * @returns object - */ - _collectTabData: function ssi_collectTabData(aTab, aFullData) { - var tabData = { entries: [], lastAccessed: aTab.lastAccessed }; - var browser = aTab.linkedBrowser; - - if (!browser || !browser.currentURI) - // can happen when calling this function right after .addTab() - return tabData; - else if (browser.__SS_data && browser.__SS_tabStillLoading) { - // use the data to be restored when the tab hasn't been completely loaded - tabData = browser.__SS_data; - if (aTab.pinned) - tabData.pinned = true; - else - delete tabData.pinned; - tabData.hidden = aTab.hidden; - - // If __SS_extdata is set then we'll use that since it might be newer. - if (aTab.__SS_extdata) - tabData.extData = aTab.__SS_extdata; - // If it exists but is empty then a key was likely deleted. In that case just - // delete extData. - if (tabData.extData && !Object.keys(tabData.extData).length) - delete tabData.extData; - return tabData; - } - - var history = null; - try { - history = browser.sessionHistory; - } - catch (ex) { } // this could happen if we catch a tab during (de)initialization - - // Limit number of back/forward button history entries to save - let oldest, newest; - let maxSerializeBack = this._prefBranch.getIntPref("sessionstore.max_serialize_back"); - if (maxSerializeBack >= 0) { - oldest = Math.max(0, history.index - maxSerializeBack); - } else { // History.getEntryAtIndex(0, ...) is the oldest. - oldest = 0; - } - let maxSerializeFwd = this._prefBranch.getIntPref("sessionstore.max_serialize_forward"); - if (maxSerializeFwd >= 0) { - newest = Math.min(history.count - 1, history.index + maxSerializeFwd); - } else { // History.getEntryAtIndex(history.count - 1, ...) is the newest. - newest = history.count - 1; - } - - // XXXzeniko anchor navigation doesn't reset __SS_data, so we could reuse - // data even when we shouldn't (e.g. Back, different anchor) - // Warning: this is required to save form data and scrolling position! - if (history && browser.__SS_data && - browser.__SS_data.entries[history.index] && - browser.__SS_data.entries[history.index].url == browser.currentURI.spec && - history.index < this._sessionhistory_max_entries - 1 && !aFullData) { - try { - tabData.entries = browser.__SS_data.entries.slice(oldest, newest + 1); - } - catch (ex) { - // No errors are expected above, but we use try-catch to keep sessionstore.js safe - NS_ASSERT(false, "SessionStore failed to slice history from browser.__SS_data"); - } - - // Set the one-based index of the currently active tab, ensuring it isn't out of bounds - tabData.index = Math.min(history.index - oldest + 1, tabData.entries.length); - } - else if (history && history.count > 0) { - browser.__SS_hostSchemeData = []; - try { - for (var j = oldest; j <= newest; j++) { - let entry = this._serializeHistoryEntry(history.getEntryAtIndex(j, false), - aFullData, aTab.pinned, browser.__SS_hostSchemeData); - tabData.entries.push(entry); - } - } - catch (ex) { - // In some cases, getEntryAtIndex will throw. This seems to be due to - // history.count being higher than it should be. By doing this in a - // try-catch, we'll update history to where it breaks, assert for - // non-release builds, and still save sessionstore.js. - NS_ASSERT(false, "SessionStore failed gathering complete history " + - "for the focused window/tab. See bug 669196."); - } - - // Set the one-based index of the currently active tab, ensuring it isn't out of bounds - tabData.index = Math.min(history.index - oldest + 1, tabData.entries.length); - - // make sure not to cache privacy sensitive data which shouldn't get out - if (!aFullData) - browser.__SS_data = tabData; - } - else if (browser.currentURI.spec != "about:blank" || - browser.contentDocument.body.hasChildNodes()) { - tabData.entries[0] = { url: browser.currentURI.spec }; - tabData.index = 1; - } - - // 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. userTypedClear - // is used to indicate whether the tab was in some sort of loading state with - // userTypedValue. - if (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; - } else { - delete tabData.userTypedValue; - delete tabData.userTypedClear; - } - - if (aTab.pinned) - tabData.pinned = true; - else - delete tabData.pinned; - tabData.hidden = aTab.hidden; - - var disallow = []; - for (let cap of gDocShellCapabilities(browser.docShell)) - if (!browser.docShell["allow" + cap]) - disallow.push(cap); - if (disallow.length > 0) - tabData.disallow = disallow.join(","); - else if (tabData.disallow) - delete tabData.disallow; - - // Save tab attributes. - tabData.attributes = TabAttributes.get(aTab); - - // Store the tab icon. - let tabbrowser = aTab.ownerDocument.defaultView.gBrowser; - tabData.image = tabbrowser.getIcon(aTab); - - if (aTab.__SS_extdata) - tabData.extData = aTab.__SS_extdata; - else if (tabData.extData) - delete tabData.extData; - - if (history && browser.docShell instanceof Ci.nsIDocShell) { - let storageData = SessionStorage.serialize(browser.docShell, aFullData) - if (Object.keys(storageData).length) - tabData.storage = storageData; - } - - return tabData; - }, - - /** - * Get an object that is a serialized representation of a History entry - * Used for data storage - * @param aEntry - * nsISHEntry instance - * @param aFullData - * always return privacy sensitive data (use with care) - * @param aIsPinned - * the tab is pinned and should be treated differently for privacy - * @param aHostSchemeData - * an array of objects with host & scheme keys - * @returns object - */ - _serializeHistoryEntry: - function ssi_serializeHistoryEntry(aEntry, aFullData, aIsPinned, aHostSchemeData) { - var entry = { url: aEntry.URI.spec }; - - try { - // throwing is expensive, we know that about: pages will throw - if (entry.url.indexOf("about:") != 0) - aHostSchemeData.push({ host: aEntry.URI.host, scheme: aEntry.URI.scheme }); - } - catch (ex) { - // We just won't attempt to get cookies for this entry. - } - - if (aEntry.title && aEntry.title != entry.url) { - entry.title = aEntry.title; - } - if (aEntry.isSubFrame) { - entry.subframe = true; - } - if (!(aEntry instanceof Ci.nsISHEntry)) { - return entry; - } - - var cacheKey = aEntry.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 = aEntry.ID; - entry.docshellID = aEntry.docshellID; - - if (aEntry.referrerURI) - entry.referrer = aEntry.referrerURI.spec; - - if (aEntry.srcdocData) - entry.srcdocData = aEntry.srcdocData; - - if (aEntry.isSrcdocEntry) - entry.isSrcdocEntry = aEntry.isSrcdocEntry; - - if (aEntry.contentType) - entry.contentType = aEntry.contentType; - - var x = {}, y = {}; - aEntry.getScrollPosition(x, y); - if (x.value != 0 || y.value != 0) - entry.scroll = x.value + "," + y.value; - - try { - var prefPostdata = this._prefBranch.getIntPref("sessionstore.postdata"); - if (aEntry.postData && (aFullData || prefPostdata && - this.checkPrivacyLevel(aEntry.URI.schemeIs("https"), aIsPinned))) { - aEntry.postData.QueryInterface(Ci.nsISeekableStream). - seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); - var stream = Cc["@mozilla.org/binaryinputstream;1"]. - createInstance(Ci.nsIBinaryInputStream); - stream.setInputStream(aEntry.postData); - var postBytes = stream.readByteArray(stream.available()); - var postdata = String.fromCharCode.apply(null, postBytes); - if (aFullData || prefPostdata == -1 || - postdata.replace(/^(Content-.*\r\n)+(\r\n)*/, "").length <= - prefPostdata) { - // We can stop doing base64 encoding once our serialization into JSON - // is guaranteed to handle all chars in strings, including embedded - // nulls. - entry.postdata_b64 = btoa(postdata); - } - } - } - catch (ex) { debug(ex); } // POSTDATA is tricky - especially since some extensions don't get it right - - if (aEntry.triggeringPrincipal) { - // Not catching anything specific here, just possible errors - // from writeCompoundObject and the like. - try { - var binaryStream = Cc["@mozilla.org/binaryoutputstream;1"]. - createInstance(Ci.nsIObjectOutputStream); - var pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe); - pipe.init(false, false, 0, 0xffffffff, null); - binaryStream.setOutputStream(pipe.outputStream); - binaryStream.writeCompoundObject(aEntry.triggeringPrincipal, Ci.nsIPrincipal, true); - binaryStream.close(); - - // Now we want to read the data from the pipe's input end and encode it. - var scriptableStream = Cc["@mozilla.org/binaryinputstream;1"]. - createInstance(Ci.nsIBinaryInputStream); - scriptableStream.setInputStream(pipe.inputStream); - var triggeringPrincipalBytes = - scriptableStream.readByteArray(scriptableStream.available()); - // We can stop doing base64 encoding once our serialization into JSON - // is guaranteed to handle all chars in strings, including embedded - // nulls. - entry.triggeringPrincipal_b64 = btoa(String.fromCharCode.apply(null, triggeringPrincipalBytes)); - } - catch (ex) { debug(ex); } - } - - entry.docIdentifier = aEntry.BFCacheEntry.ID; - - if (aEntry.stateData != null) { - entry.structuredCloneState = aEntry.stateData.getDataAsBase64(); - entry.structuredCloneVersion = aEntry.stateData.formatVersion; - } - - if (!(aEntry instanceof Ci.nsISHContainer)) { - return entry; - } - - if (aEntry.childCount > 0) { - let children = []; - for (var i = 0; i < aEntry.childCount; i++) { - var child = aEntry.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 = []; - break; - } - - children.push(this._serializeHistoryEntry(child, aFullData, - aIsPinned, aHostSchemeData)); - } - } - - if (children.length) - entry.children = children; - } - - return entry; - }, - - /** - * go through all tabs and store the current scroll positions - * and innerHTML content of WYSIWYG editors - * @param aWindow - * Window reference - */ - _updateTextAndScrollData: function ssi_updateTextAndScrollData(aWindow) { - var browsers = aWindow.gBrowser.browsers; - this._windows[aWindow.__SSi].tabs.forEach(function (tabData, i) { - try { - this._updateTextAndScrollDataForTab(aWindow, browsers[i], tabData); - } - catch (ex) { debug(ex); } // get as much data as possible, ignore failures (might succeed the next time) - }, this); - }, - - /** - * go through all frames and store the current scroll positions - * and innerHTML content of WYSIWYG editors - * @param aWindow - * Window reference - * @param aBrowser - * single browser reference - * @param aTabData - * tabData object to add the information to - * @param aFullData - * always return privacy sensitive data (use with care) - */ - _updateTextAndScrollDataForTab: - function ssi_updateTextAndScrollDataForTab(aWindow, aBrowser, aTabData, aFullData) { - // we shouldn't update data for incompletely initialized tabs - if (aBrowser.__SS_data && aBrowser.__SS_tabStillLoading) - return; - - var tabIndex = (aTabData.index || aTabData.entries.length) - 1; - // entry data needn't exist for tabs just initialized with an incomplete session state - if (!aTabData.entries[tabIndex]) - return; - - let selectedPageStyle = aBrowser.markupDocumentViewer.authorStyleDisabled ? "_nostyle" : - this._getSelectedPageStyle(aBrowser.contentWindow); - if (selectedPageStyle) - aTabData.pageStyle = selectedPageStyle; - else if (aTabData.pageStyle) - delete aTabData.pageStyle; - - this._updateTextAndScrollDataForFrame(aWindow, aBrowser.contentWindow, - aTabData.entries[tabIndex], - !aBrowser.__SS_formDataSaved, aFullData, - !!aTabData.pinned); - aBrowser.__SS_formDataSaved = true; - if (aBrowser.currentURI.spec == "about:config") - aTabData.entries[tabIndex].formdata = { - id: { - "textbox": aBrowser.contentDocument.getElementById("textbox").value - }, - xpath: {} - }; - }, - - /** - * go through all subframes and store all form data, the current - * scroll positions and innerHTML content of WYSIWYG editors - * @param aWindow - * Window reference - * @param aContent - * frame reference - * @param aData - * part of a tabData object to add the information to - * @param aUpdateFormData - * update all form data for this tab - * @param aFullData - * always return privacy sensitive data (use with care) - * @param aIsPinned - * the tab is pinned and should be treated differently for privacy - */ - _updateTextAndScrollDataForFrame: - function ssi_updateTextAndScrollDataForFrame(aWindow, aContent, aData, - aUpdateFormData, aFullData, aIsPinned) { - for (var i = 0; i < aContent.frames.length; i++) { - if (aData.children && aData.children[i]) - this._updateTextAndScrollDataForFrame(aWindow, aContent.frames[i], - aData.children[i], aUpdateFormData, - aFullData, aIsPinned); - } - var isHTTPS = this._getURIFromString((aContent.parent || aContent). - document.location.href).schemeIs("https"); - let isAboutSR = aContent.top.document.location.href == "about:sessionrestore"; - if (aFullData || this.checkPrivacyLevel(isHTTPS, aIsPinned) || isAboutSR) { - if (aFullData || aUpdateFormData) { - let formData = DocumentUtils.getFormData(aContent.document); - - // We want to avoid saving data for about:sessionrestore as a string. - // Since it's stored in the form as stringified JSON, stringifying further - // causes an explosion of escape characters. cf. bug 467409 - if (formData && isAboutSR) { - formData.id["sessionData"] = JSON.parse(formData.id["sessionData"]); - } - - if (Object.keys(formData.id).length || - Object.keys(formData.xpath).length) { - aData.formdata = formData; - } else if (aData.formdata) { - delete aData.formdata; - } - } - - // designMode is undefined e.g. for XUL documents (as about:config) - if ((aContent.document.designMode || "") == "on" && aContent.document.body) - aData.innerHTML = aContent.document.body.innerHTML; - } - - // get scroll position from nsIDOMWindowUtils, since it allows avoiding a - // flush of layout - let domWindowUtils = aContent.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - let scrollX = {}, scrollY = {}; - domWindowUtils.getScrollXY(false, scrollX, scrollY); - aData.scroll = scrollX.value + "," + scrollY.value; - }, - - /** - * determine the title of the currently enabled style sheet (if any) - * and recurse through the frameset if necessary - * @param aContent is a frame reference - * @returns the title style sheet determined to be enabled (empty string if none) - */ - _getSelectedPageStyle: function ssi_getSelectedPageStyle(aContent) { - const forScreen = /(?:^|,)\s*(?:all|screen)\s*(?:,|$)/i; - for (let i = 0; i < aContent.document.styleSheets.length; i++) { - let ss = aContent.document.styleSheets[i]; - let media = ss.media.mediaText; - if (!ss.disabled && ss.title && (!media || forScreen.test(media))) - return ss.title - } - for (let i = 0; i < aContent.frames.length; i++) { - let selectedPageStyle = this._getSelectedPageStyle(aContent.frames[i]); - if (selectedPageStyle) - return selectedPageStyle; - } - return ""; - }, - - /** - * extract the base domain from a history entry and its children - * @param aEntry - * the history entry, serialized - * @param aHosts - * the hash that will be used to store hosts eg, { hostname: true } - * @param aCheckPrivacy - * should we check the privacy level for https - * @param aIsPinned - * is the entry we're evaluating for a pinned tab; used only if - * aCheckPrivacy - */ - _extractHostsForCookiesFromEntry: - function ssi_extractHostsForCookiesFromEntry(aEntry, aHosts, aCheckPrivacy, aIsPinned) { - - let host = aEntry._host, - scheme = aEntry._scheme; - - // If host & scheme aren't defined, then we are likely here in the startup - // process via _splitCookiesFromWindow. In that case, we'll turn aEntry.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 = this._getURIFromString(aEntry.url); - host = uri.host; - scheme = uri.scheme; - this._extractHostsForCookiesFromHostScheme(host, scheme, aHosts, aCheckPrivacy, aIsPinned); - } - catch(ex) { } - } - - if (aEntry.children) { - aEntry.children.forEach(function(entry) { - this._extractHostsForCookiesFromEntry(entry, aHosts, aCheckPrivacy, aIsPinned); - }, this); - } - }, - - /** - * extract the base domain from a host & scheme - * @param aHost - * the host of a uri (usually via nsIURI.host) - * @param aScheme - * the scheme of a uri (usually via nsIURI.scheme) - * @param aHosts - * the hash that will be used to store hosts eg, { hostname: true } - * @param aCheckPrivacy - * should we check the privacy level for https - * @param aIsPinned - * is the entry we're evaluating for a pinned tab; used only if - * aCheckPrivacy - */ - _extractHostsForCookiesFromHostScheme: - function ssi_extractHostsForCookiesFromHostScheme(aHost, aScheme, aHosts, aCheckPrivacy, aIsPinned) { - // host and scheme may not be set (for about: urls for example), in which - // case testing scheme will be sufficient. - if (/https?/.test(aScheme) && !aHosts[aHost] && - (!aCheckPrivacy || - this.checkPrivacyLevel(aScheme == "https", aIsPinned))) { - // By setting this to true or false, we can determine when looking at - // the host in _updateCookies if we should check for privacy. - aHosts[aHost] = aIsPinned; - } - else if (aScheme == "file") { - aHosts[aHost] = true; - } - }, - - /** - * store all hosts for a URL - * @param aWindow - * Window reference - */ - _updateCookieHosts: function ssi_updateCookieHosts(aWindow) { - var hosts = this._internalWindows[aWindow.__SSi].hosts = {}; - - // Since _updateCookiesHosts is only ever called for open windows during a - // session, we can call into _extractHostsForCookiesFromHostScheme directly - // using data that is attached to each browser. - for (let i = 0; i < aWindow.gBrowser.tabs.length; i++) { - let tab = aWindow.gBrowser.tabs[i]; - let hostSchemeData = tab.linkedBrowser.__SS_hostSchemeData || []; - for (let j = 0; j < hostSchemeData.length; j++) { - this._extractHostsForCookiesFromHostScheme(hostSchemeData[j].host, - hostSchemeData[j].scheme, - hosts, true, tab.pinned); - } - } - }, - - /** - * Serialize cookie data - * @param aWindows - * JS object containing window data references - * { id: winData, etc. } - */ - _updateCookies: function ssi_updateCookies(aWindows) { - function addCookieToHash(aHash, aHost, aPath, aName, aCookie) { - // lazily build up a 3-dimensional hash, with - // aHost, aPath, and aName as keys - if (!aHash[aHost]) - aHash[aHost] = {}; - if (!aHash[aHost][aPath]) - aHash[aHost][aPath] = {}; - aHash[aHost][aPath][aName] = aCookie; - } - - var jscookies = {}; - var _this = this; - // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision - var MAX_EXPIRY = Math.pow(2, 62); - - for (let [id, window] in Iterator(aWindows)) { - window.cookies = []; - let internalWindow = this._internalWindows[id]; - if (!internalWindow.hosts) - return; - for (var [host, isPinned] in Iterator(internalWindow.hosts)) { - let list; - try { - list = Services.cookies.getCookiesFromHost(host, {}); - } - catch (ex) { - debug("getCookiesFromHost failed. Host: " + host); - } - while (list && list.hasMoreElements()) { - var cookie = list.getNext().QueryInterface(Ci.nsICookie2); - // window._hosts will only have hosts with the right privacy rules, - // so there is no need to do anything special with this call to - // checkPrivacyLevel. - if (cookie.isSession && _this.checkPrivacyLevel(cookie.isSecure, isPinned)) { - // use the cookie's host, path, and name as keys into a hash, - // to make sure we serialize each cookie only once - if (!(cookie.host in jscookies && - cookie.path in jscookies[cookie.host] && - cookie.name in jscookies[cookie.host][cookie.path])) { - var jscookie = { "host": cookie.host, "value": cookie.value }; - // only add attributes with non-default values (saving a few bits) - 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; - - addCookieToHash(jscookies, cookie.host, cookie.path, cookie.name, jscookie); - } - window.cookies.push(jscookies[cookie.host][cookie.path][cookie.name]); - } - } - } - - // don't include empty cookie sections - if (!window.cookies.length) - delete window.cookies; - } - }, - - /** - * 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 - * @param aPinnedOnly - * Bool collect pinned tabs only - * @returns object - */ - _getCurrentState: function ssi_getCurrentState(aUpdateAll, aPinnedOnly) { - this._handleClosedWindows(); - - var activeWindow = this._getMostRecentBrowserWindow(); - - if (this._loadState == STATE_RUNNING) { - // 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 || this._dirtyWindows[aWindow.__SSi] || aWindow == activeWindow) { - this._collectWindowData(aWindow); - } - else { // always update the window features (whose change alone never triggers a save operation) - this._updateWindowFeatures(aWindow); - } - }); - this._dirtyWindows = []; - } - - // collect the data for all windows - var total = [], windows = {}, ids = []; - var nonPopupCount = 0; - var ix; - 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); - windows[ix] = this._windows[ix]; - if (!this._windows[ix].isPopup) - nonPopupCount++; - } - this._updateCookies(windows); - - // collect the data for all windows yet to be restored - for (ix in this._statesToRestore) { - for each (let winData in this._statesToRestore[ix].windows) { - total.push(winData); - if (!winData.isPopup) - nonPopupCount++; - } - } - - // shallow copy this._closedWindows to preserve current state - let lastClosedWindowsCopy = this._closedWindows.slice(); - -#ifndef XP_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 && - this._loadState == STATE_QUITTING) { - // 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) - } -#endif - - if (aPinnedOnly) { - // perform a deep copy so that existing session variables are not changed. - total = JSON.parse(this._toJSONString(total)); - total = total.filter(function (win) { - win.tabs = win.tabs.filter(function (tab) tab.pinned); - // remove closed tabs - win._closedTabs = []; - // correct selected tab index if it was stripped out - if (win.selected > win.tabs.length) - win.selected = 1; - return win.tabs.length > 0; - }); - if (total.length == 0) - return null; - - lastClosedWindowsCopy = []; - } - - 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 = { - state: this._loadState == STATE_RUNNING ? STATE_RUNNING_STR : STATE_STOPPED_STR, - lastUpdate: Date.now(), - startTime: this._sessionStartTime, - recentCrashes: this._recentCrashes - }; - - var scratchpads = null; - var browserConsole = null; -#ifdef MOZ_DEVTOOLS - // Scratchpad - // get open Scratchpad window states too - scratchpads = ScratchpadManager.getSessionState(); - - // The Browser Console - browserConsole = HUDService.getBrowserConsoleSessionState(); -#endif - - return { - windows: total, - selectedWindow: ix + 1, - _closedWindows: lastClosedWindowsCopy, -#ifdef MOZ_DEVTOOLS - session: session, - scratchpads: scratchpads, - browserConsole: browserConsole -#else - session: session -#endif - }; - }, - - /** - * 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 (this._loadState == STATE_RUNNING) { - this._collectWindowData(aWindow); - } - - var winData = this._windows[aWindow.__SSi]; - let windows = {}; - windows[aWindow.__SSi] = winData; - this._updateCookies(windows); - - return { windows: [winData] }; - }, - - _collectWindowData: function ssi_collectWindowData(aWindow) { - if (!this._isWindowLoaded(aWindow)) - return; - - // update the internal state data for this window - this._saveWindowHistory(aWindow); - this._updateTextAndScrollData(aWindow); - this._updateCookieHosts(aWindow); - 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; - - this._dirtyWindows[aWindow.__SSi] = false; - }, - - /* ........ Restoring Functionality .............. */ - - /** - * restore features to a single window - * @param aWindow - * Window reference - * @param aState - * JS object or its eval'able source - * @param aOverwriteTabs - * bool overwrite existing tabs w/ new ones - * @param aFollowUp - * bool this isn't the restoration of the first window - */ - restoreWindow: function ssi_restoreWindow(aWindow, aState, aOverwriteTabs, aFollowUp) { - if (!aFollowUp) { - this.windowToFocus = aWindow; - } - // initialize window if necessary - if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi])) - this.onLoad(aWindow); - - try { - var root = typeof aState == "string" ? JSON.parse(aState) : aState; - if (!root.windows[0]) { - this._sendRestoreCompletedNotifications(); - return; // nothing to restore - } - } - catch (ex) { // invalid state object - don't restore anything - debug(ex); - this._sendRestoreCompletedNotifications(); - return; - } - - // We're not returning from this before we end up calling restoreHistoryPrecursor - // for this window, so make sure we send the SSWindowStateBusy event. - this._setWindowStateBusy(aWindow); - - if (root._closedWindows) - this._closedWindows = root._closedWindows; - - var winData; - 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) - 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; - } - } - } - winData = root.windows[0]; - 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 (root._firstTabs && !aOverwriteTabs && winData.tabs.length == 1 && - (!winData.tabs[0].entries || winData.tabs[0].entries.length == 0)) { - winData.tabs = []; - } - - var tabbrowser = aWindow.gBrowser; - var openTabCount = aOverwriteTabs ? 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 (aOverwriteTabs) { - for (let t = tabbrowser._numPinnedTabs - 1; t > -1; t--) - tabbrowser.unpinTab(tabbrowser.tabs[t]); - } - - // make sure that the selected tab won't be closed in order to - // prevent unnecessary flickering - if (aOverwriteTabs && tabbrowser.selectedTab._tPos >= newTabCount) - tabbrowser.moveTabTo(tabbrowser.selectedTab, newTabCount - 1); - - let numVisibleTabs = 0; - - for (var t = 0; t < newTabCount; t++) { - tabs.push(t < openTabCount ? - tabbrowser.tabs[t] : - tabbrowser.addTab("about:blank", - {skipAnimation: true, - skipBackgroundNotify: true})); - // when resuming at startup: add additionally requested pages to the end - if (!aOverwriteTabs && root._firstTabs) { - tabbrowser.moveTabTo(tabs[t], t); - } - - 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 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 restoreHistoryPrecursor). - if (aOverwriteTabs) { - for (let i = 0; i < tabbrowser.tabs.length; i++) { - if (tabbrowser.browsers[i].__SS_restoreState) - this._resetTabRestoringState(tabbrowser.tabs[i]); - } - } - - // We want to set up a counter on the window that indicates how many tabs - // in this window are unrestored. This will be used in restoreNextTab to - // determine if gRestoreTabsProgressListener should be removed from the window. - // If we aren't overwriting existing tabs, then we want to add to the existing - // count in case there are still tabs restoring. - if (!aWindow.__SS_tabsToRestore) - aWindow.__SS_tabsToRestore = 0; - if (aOverwriteTabs) - aWindow.__SS_tabsToRestore = newTabCount; - else - aWindow.__SS_tabsToRestore += newTabCount; - - // 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 (aOverwriteTabs && newTabCount < openTabCount) { - Array.slice(tabbrowser.tabs, newTabCount, openTabCount) - .forEach(tabbrowser.removeTab, tabbrowser); - } - - if (aOverwriteTabs) { - this.restoreWindowFeatures(aWindow, winData); - delete this._windows[aWindow.__SSi].extData; - } - if (winData.cookies) { - this.restoreCookies(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]; - } - } - if (aOverwriteTabs || root._firstTabs) { - this._windows[aWindow.__SSi]._closedTabs = winData._closedTabs || []; - } - - this.restoreHistoryPrecursor(aWindow, tabs, winData.tabs, - (aOverwriteTabs ? (parseInt(winData.selected) || 1) : 0), 0, 0); - -#ifdef MOZ_DEVTOOLS - if (aState.scratchpads) { - ScratchpadManager.restoreSession(aState.scratchpads); - } - - // The Browser Console - if (aState.browserConsole) { - HUDService.restoreBrowserConsoleSession(); - } - -#endif - // set smoothScroll back to the original value - tabstrip.smoothScroll = smoothScroll; - - this._sendRestoreCompletedNotifications(); - }, - - /** - * Sets the tabs restoring order with the following priority: - * Selected tab, pinned tabs, optimized visible tabs, other visible tabs and - * hidden tabs. - * @param aTabBrowser - * Tab browser object - * @param aTabs - * Array of tab references - * @param aTabData - * Array of tab data - * @param aSelectedTab - * Index of selected tab (1 is first tab, 0 no selected tab) - */ - _setTabsRestoringOrder : function ssi__setTabsRestoringOrder( - aTabBrowser, aTabs, aTabData, aSelectedTab) { - - // Store the selected tab. Need to substract one to get the index in aTabs. - let selectedTab; - if (aSelectedTab > 0 && aTabs[aSelectedTab - 1]) { - selectedTab = aTabs[aSelectedTab - 1]; - } - - // Store the pinned tabs and hidden tabs. - let pinnedTabs = []; - let pinnedTabsData = []; - let hiddenTabs = []; - let hiddenTabsData = []; - if (aTabs.length > 1) { - for (let t = aTabs.length - 1; t >= 0; t--) { - if (aTabData[t].pinned) { - pinnedTabs.unshift(aTabs.splice(t, 1)[0]); - pinnedTabsData.unshift(aTabData.splice(t, 1)[0]); - } else if (aTabData[t].hidden) { - hiddenTabs.unshift(aTabs.splice(t, 1)[0]); - hiddenTabsData.unshift(aTabData.splice(t, 1)[0]); - } - } - } - - // Optimize the visible tabs only if there is a selected tab. - if (selectedTab) { - let selectedTabIndex = aTabs.indexOf(selectedTab); - if (selectedTabIndex > 0) { - let scrollSize = aTabBrowser.tabContainer.mTabstrip.scrollClientSize; - let tabWidth = aTabs[0].getBoundingClientRect().width; - let maxVisibleTabs = Math.ceil(scrollSize / tabWidth); - if (maxVisibleTabs < aTabs.length) { - let firstVisibleTab = 0; - let nonVisibleTabsCount = aTabs.length - maxVisibleTabs; - if (nonVisibleTabsCount >= selectedTabIndex) { - // Selected tab is leftmost since we scroll to it when possible. - firstVisibleTab = selectedTabIndex; - } else { - // Selected tab is rightmost or no more room to scroll right. - firstVisibleTab = nonVisibleTabsCount; - } - aTabs = aTabs.splice(firstVisibleTab, maxVisibleTabs).concat(aTabs); - aTabData = - aTabData.splice(firstVisibleTab, maxVisibleTabs).concat(aTabData); - } - } - } - - // Merge the stored tabs in order. - aTabs = pinnedTabs.concat(aTabs, hiddenTabs); - aTabData = pinnedTabsData.concat(aTabData, hiddenTabsData); - - // Load the selected tab to the first position and select it. - if (selectedTab) { - let selectedTabIndex = aTabs.indexOf(selectedTab); - if (selectedTabIndex > 0) { - aTabs = aTabs.splice(selectedTabIndex, 1).concat(aTabs); - aTabData = aTabData.splice(selectedTabIndex, 1).concat(aTabData); - } - aTabBrowser.selectedTab = selectedTab; - } - - return [aTabs, aTabData]; - }, - - /** - * 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 selected tab - * @param aIx - * Index of the next tab to check readyness for - * @param aCount - * Counter for number of times delaying b/c browser or history aren't ready - * @param aRestoreImmediately - * Flag to indicate whether the given set of tabs aTabs should be - * restored/loaded immediately even if restore_on_demand = true - */ - restoreHistoryPrecursor: - function ssi_restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab, - aIx, aCount, aRestoreImmediately = false) { - var tabbrowser = aWindow.gBrowser; - - // make sure that all browsers and their histories are available - // - if one's not, resume this check in 100ms (repeat at most 10 times) - for (var t = aIx; t < aTabs.length; t++) { - try { - if (!tabbrowser.getBrowserForTab(aTabs[t]).webNavigation.sessionHistory) { - throw new Error(); - } - } - catch (ex) { // in case browser or history aren't ready yet - if (aCount < 10) { - var restoreHistoryFunc = function(self) { - self.restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab, - aIx, aCount + 1, aRestoreImmediately); - } - aWindow.setTimeout(restoreHistoryFunc, 100, this); - return; - } - } - } - - 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; - - // It's important to set the window state to dirty so that - // we collect their data for the first time when saving state. - this._dirtyWindows[aWindow.__SSi] = true; - } - - if (aTabs.length == 0) { - // this is normally done in restoreHistory() but as we're returning early - // here we need to take care of it. - this._setWindowStateReady(aWindow); - return; - } - - // Sets the tabs restoring order. - [aTabs, aTabData] = - this._setTabsRestoringOrder(tabbrowser, aTabs, aTabData, aSelectTab); - - // Prepare the tabs so that they can be properly restored. We'll pin/unpin - // and show/hide tabs as necessary. We'll also set the labels, user typed - // value, and attach a copy of the tab's data in case we close it before - // it's been restored. - for (t = 0; t < aTabs.length; t++) { - let tab = aTabs[t]; - let browser = tabbrowser.getBrowserForTab(tab); - let tabData = aTabData[t]; - - if (tabData.pinned) - tabbrowser.pinTab(tab); - else - tabbrowser.unpinTab(tab); - - if (tabData.hidden) - tabbrowser.hideTab(tab); - else - tabbrowser.showTab(tab); - - if ("attributes" in tabData) { - // Ensure that we persist tab attributes restored from previous sessions. - Object.keys(tabData.attributes).forEach(a => TabAttributes.persist(a)); - } - - browser.__SS_tabStillLoading = true; - - // keep the data around to prevent dataloss in case - // a tab gets closed before it's been properly restored - browser.__SS_data = tabData; - browser.__SS_restoreState = TAB_STATE_NEEDS_RESTORE; - browser.setAttribute("pending", "true"); - tab.setAttribute("pending", "true"); - - // Make sure that set/getTabValue will set/read the correct data by - // wiping out any current value in tab.__SS_extdata. - delete tab.__SS_extdata; - - if (!tabData.entries || tabData.entries.length == 0) { - // make sure to blank out this tab's content - // (just purging the tab's history won't be enough) - browser.contentDocument.location = "about:blank"; - continue; - } - - browser.stop(); // in case about:blank isn't done yet - - // wall-paper fix for bug 439675: make sure that the URL to be loaded - // is always visible in the address bar - let activeIndex = (tabData.index || tabData.entries.length) - 1; - let activePageData = tabData.entries[activeIndex] || null; - let uri = activePageData ? activePageData.url || null : null; - browser.userTypedValue = uri; - - // Also 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. - if (uri) - browser.docShell.setCurrentURI(this._getURIFromString(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"; - } - } - } - - // helper hashes for ensuring unique frame IDs and unique document - // identifiers. - var idMap = { used: {} }; - var docIdentMap = {}; - this.restoreHistory(aWindow, aTabs, aTabData, idMap, docIdentMap, - aRestoreImmediately); - }, - - /** - * Restore history for a window - * @param aWindow - * Window reference - * @param aTabs - * Array of tab references - * @param aTabData - * Array of tab data - * @param aIdMap - * Hash for ensuring unique frame IDs - * @param aRestoreImmediately - * Flag to indicate whether the given set of tabs aTabs should be - * restored/loaded immediately even if restore_on_demand = true - */ - restoreHistory: - function ssi_restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap, - aRestoreImmediately) { - var _this = this; - // if the tab got removed before being completely restored, then skip it - while (aTabs.length > 0 && !(this._canRestoreTabHistory(aTabs[0]))) { - aTabs.shift(); - aTabData.shift(); - } - if (aTabs.length == 0) { - // At this point we're essentially ready for consumers to read/write data - // via the sessionstore API so we'll send the SSWindowStateReady event. - this._setWindowStateReady(aWindow); - return; // no more tabs to restore - } - - var tab = aTabs.shift(); - var tabData = aTabData.shift(); - - var browser = aWindow.gBrowser.getBrowserForTab(tab); - var history = browser.webNavigation.sessionHistory; - - if (history.count > 0) { - history.PurgeHistory(history.count); - } - history.QueryInterface(Ci.nsISHistoryInternal); - - browser.__SS_shistoryListener = new SessionStoreSHistoryListener(tab); - history.addSHistoryListener(browser.__SS_shistoryListener); - - if (!tabData.entries) { - tabData.entries = []; - } - if (tabData.extData) { - tab.__SS_extdata = {}; - for (let key in tabData.extData) - tab.__SS_extdata[key] = tabData.extData[key]; - } - else - delete tab.__SS_extdata; - - for (var i = 0; i < tabData.entries.length; i++) { - //XXXzpao Wallpaper patch for bug 514751 - if (!tabData.entries[i].url) - continue; - history.addEntry(this._deserializeHistoryEntry(tabData.entries[i], - aIdMap, aDocIdentMap), true); - } - - // make sure to reset the capabilities and attributes, in case this tab gets reused - let disallow = new Set(tabData.disallow && tabData.disallow.split(",")); - for (let cap of gDocShellCapabilities(browser.docShell)) - browser.docShell["allow" + cap] = !disallow.has(cap); - - // Restore tab attributes. - if ("attributes" in tabData) { - TabAttributes.set(tab, tabData.attributes); - } - - // Restore the tab icon. - if ("image" in tabData) { - // Using null as the loadingPrincipal because serializing - // the principal would be overkill. Within SetIcon we - // default to the systemPrincipal if aLoadingPrincipal is - // null which will allow the favicon to load. - aWindow.gBrowser.setIcon(tab, tabData.image, null); - } - - if (tabData.storage && browser.docShell instanceof Ci.nsIDocShell) - SessionStorage.deserialize(browser.docShell, tabData.storage); - - // notify the tabbrowser that the tab chrome has been restored - var event = aWindow.document.createEvent("Events"); - event.initEvent("SSTabRestoring", true, false); - tab.dispatchEvent(event); - - // Restore the history in the next tab - aWindow.setTimeout(function(){ - _this.restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap, - aRestoreImmediately); - }, 0); - - // This could cause us to ignore max_concurrent_tabs pref a bit, but - // it ensures each window will have its selected tab loaded. - if (aRestoreImmediately || aWindow.gBrowser.selectedBrowser == browser) { - this.restoreTab(tab); - } - else { - TabRestoreQueue.add(tab); - this.restoreNextTab(); - } - }, - - /** - * Restores the specified tab. If the tab can't be restored (eg, no history or - * calling gotoIndex fails), then state changes will be rolled back. - * This method will check if gTabsProgressListener is attached to the tab's - * window, ensuring that we don't get caught without one. - * This method removes the session history listener right before starting to - * attempt a load. This will prevent cases of "stuck" listeners. - * If this method returns false, then it is up to the caller to decide what to - * do. In the common case (restoreNextTab), we will want to then attempt to - * restore the next tab. In the other case (selecting the tab, reloading the - * tab), the caller doesn't actually want to do anything if no page is loaded. - * - * @param aTab - * the tab to restore - * - * @returns true/false indicating whether or not a load actually happened - */ - restoreTab: function ssi_restoreTab(aTab) { - let window = aTab.ownerDocument.defaultView; - let browser = aTab.linkedBrowser; - let tabData = browser.__SS_data; - - // There are cases within where we haven't actually started a load. In that - // that case we'll reset state changes we made and return false to the caller - // can handle appropriately. - let didStartLoad = false; - - // Make sure that the tabs progress listener is attached to this window - this._ensureTabsProgressListener(window); - - // 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"); - - // Remove the history listener, since we no longer need it once we start restoring - this._removeSHistoryListener(aTab); - - let activeIndex = (tabData.index || tabData.entries.length) - 1; - if (activeIndex >= tabData.entries.length) - activeIndex = tabData.entries.length - 1; - // Reset currentURI. This creates a new session history entry with a new - // doc identifier, so we need to explicitly save and restore the old doc - // identifier (corresponding to the SHEntry at activeIndex) below. - browser.webNavigation.setCurrentURI(this._getURIFromString("about:blank")); - // Attach data that will be restored on "load" event, after tab is restored. - if (activeIndex > -1) { - // restore those aspects of the currently active documents which are not - // preserved in the plain history entries (mainly scroll state and text data) - browser.__SS_restore_data = tabData.entries[activeIndex] || {}; - browser.__SS_restore_pageStyle = tabData.pageStyle || ""; - browser.__SS_restore_tab = aTab; - didStartLoad = true; - try { - // 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. - browser.webNavigation.sessionHistory.getEntryAtIndex(activeIndex, true); - browser.webNavigation.sessionHistory.reloadCurrentEntry(); - // If the user prefers it, bypass cache and always load from the network. - let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; - switch (this._cacheBehavior) { - case 2: // hard refresh - flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | - Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE; - browser.webNavigation.reload(flags); - break; - case 1: // soft refresh - browser.webNavigation.reload(flags); - break; - default: // 0 or other: use cache, so do nothing. - break; - } - } - catch (ex) { - // ignore page load errors - aTab.removeAttribute("busy"); - didStartLoad = false; - } - } - - // Handle userTypedValue. Setting userTypedValue seems to update gURLbar - // as needed. Calling loadURI will cancel form filling in restoreDocument - if (tabData.userTypedValue) { - browser.userTypedValue = tabData.userTypedValue; - if (tabData.userTypedClear) { - // Make it so that we'll enter restoreDocument on page load. We will - // fire SSTabRestored from there. We don't have any form data to restore - // so we can just set the URL to null. - browser.__SS_restore_data = { url: null }; - browser.__SS_restore_tab = aTab; - if (didStartLoad) - browser.stop(); - didStartLoad = true; - browser.loadURIWithFlags(tabData.userTypedValue, - Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP); - } - } - - // If we didn't start a load, then we won't reset this tab through the usual - // channel (via the progress listener), so reset the tab ourselves. We will - // also send SSTabRestored since this tab has technically been restored. - if (!didStartLoad) { - this._sendTabRestoredNotification(aTab); - this._resetTabRestoringState(aTab); - } - - return didStartLoad; - }, - - /** - * 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 (this._loadState == STATE_QUITTING) - return; - - // Don't exceed the maximum number of concurrent tab restores. - if (this._tabsRestoringCount >= this._maxConcurrentTabRestores) - return; - - let tab = TabRestoreQueue.shift(); - if (tab) { - let didStartLoad = this.restoreTab(tab); - // If we don't start a load in the restored tab (eg, no entries) then we - // want to attempt to restore the next tab. - if (!didStartLoad) - this.restoreNextTab(); - } - }, - - /** - * expands serialized history data into a session-history-entry instance - * @param aEntry - * Object containing serialized history data for a URL - * @param aIdMap - * Hash for ensuring unique frame IDs - * @returns nsISHEntry - */ - _deserializeHistoryEntry: - function ssi_deserializeHistoryEntry(aEntry, aIdMap, aDocIdentMap) { - - var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"]. - createInstance(Ci.nsISHEntry); - - shEntry.setURI(this._getURIFromString(aEntry.url)); - shEntry.setTitle(aEntry.title || aEntry.url); - if (aEntry.subframe) - shEntry.setIsSubFrame(aEntry.subframe || false); - shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory; - if (aEntry.contentType) - shEntry.contentType = aEntry.contentType; - if (aEntry.referrer) - shEntry.referrerURI = this._getURIFromString(aEntry.referrer); - if (aEntry.isSrcdocEntry) - shEntry.srcdocData = aEntry.srcdocData; - - if (aEntry.cacheKey) { - var cacheKey = Cc["@mozilla.org/supports-PRUint32;1"]. - createInstance(Ci.nsISupportsPRUint32); - cacheKey.data = aEntry.cacheKey; - shEntry.cacheKey = cacheKey; - } - - if (aEntry.ID) { - // get a new unique ID for this frame (since the one from the last - // start might already be in use) - var id = aIdMap[aEntry.ID] || 0; - if (!id) { - for (id = Date.now(); id in aIdMap.used; id++); - aIdMap[aEntry.ID] = id; - aIdMap.used[id] = true; - } - shEntry.ID = id; - } - - if (aEntry.docshellID) - shEntry.docshellID = aEntry.docshellID; - - if (aEntry.structuredCloneState && aEntry.structuredCloneVersion) { - shEntry.stateData = - Cc["@mozilla.org/docshell/structured-clone-container;1"]. - createInstance(Ci.nsIStructuredCloneContainer); - - shEntry.stateData.initFromBase64(aEntry.structuredCloneState, - aEntry.structuredCloneVersion); - } - - if (aEntry.scroll) { - var scrollPos = (aEntry.scroll || "0,0").split(","); - scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0]; - shEntry.setScrollPosition(scrollPos[0], scrollPos[1]); - } - - if (aEntry.postdata_b64) { - var postdata = atob(aEntry.postdata_b64); - var stream = Cc["@mozilla.org/io/string-input-stream;1"]. - createInstance(Ci.nsIStringInputStream); - stream.setData(postdata, postdata.length); - shEntry.postData = stream; - } - - let childDocIdents = {}; - if (aEntry.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 = aDocIdentMap[aEntry.docIdentifier]; - if (!matchingEntry) { - matchingEntry = {shEntry: shEntry, childDocIdents: childDocIdents}; - aDocIdentMap[aEntry.docIdentifier] = matchingEntry; - } - else { - shEntry.adoptBFCacheEntry(matchingEntry.shEntry); - childDocIdents = matchingEntry.childDocIdents; - } - } - - // The field aEntry.owner_b64 got renamed to aEntry.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 (aEntry.owner_b64) { - aEntry.triggeringPrincipal_b64 = aEntry.owner_b64; - delete aEntry.owner_b64; - } - - if (aEntry.triggeringPrincipal_b64) { - var triggeringPrincipalInput = Cc["@mozilla.org/io/string-input-stream;1"]. - createInstance(Ci.nsIStringInputStream); - var binaryData = atob(aEntry.triggeringPrincipal_b64); - triggeringPrincipalInput.setData(binaryData, binaryData.length); - var binaryStream = Cc["@mozilla.org/binaryinputstream;1"]. - createInstance(Ci.nsIObjectInputStream); - binaryStream.setInputStream(triggeringPrincipalInput); - try { // Catch possible deserialization exceptions - shEntry.triggeringPrincipal = binaryStream.readObject(true); - } catch (ex) { debug(ex); } - } - - if (aEntry.children && shEntry instanceof Ci.nsISHContainer) { - for (var i = 0; i < aEntry.children.length; i++) { - //XXXzpao Wallpaper patch for bug 514751 - if (!aEntry.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._deserializeHistoryEntry(aEntry.children[i], aIdMap, - childDocIdents), i); - } - } - - return shEntry; - }, - - /** - * Restore properties to a loaded document - */ - restoreDocument: function ssi_restoreDocument(aWindow, aBrowser, aEvent) { - // wait for the top frame to be loaded completely - if (!aEvent || !aEvent.originalTarget || !aEvent.originalTarget.defaultView || - aEvent.originalTarget.defaultView != aEvent.originalTarget.defaultView.top) { - return; - } - - // always call this before injecting content into a document! - function hasExpectedURL(aDocument, aURL) - !aURL || aURL.replace(/#.*/, "") == aDocument.location.href.replace(/#.*/, ""); - - let selectedPageStyle = aBrowser.__SS_restore_pageStyle; - function restoreTextDataAndScrolling(aContent, aData, aPrefix) { - if (aData.formdata && hasExpectedURL(aContent.document, aData.url)) { - let formdata = aData.formdata; - - // handle backwards compatibility - // this is a migration from pre-firefox 15. cf. bug 742051 - if (!("xpath" in formdata || "id" in formdata)) { - formdata = { xpath: {}, id: {} }; - - for each (let [key, value] in Iterator(aData.formdata)) { - if (key.charAt(0) == "#") { - formdata.id[key.slice(1)] = value; - } else { - formdata.xpath[key] = value; - } - } - } - - // for about:sessionrestore we saved the field as JSON to avoid - // nested instances causing humongous sessionstore.js files. - // cf. bug 467409 - if (aData.url == "about:sessionrestore" && - "sessionData" in formdata.id && - typeof formdata.id["sessionData"] == "object") { - formdata.id["sessionData"] = - JSON.stringify(formdata.id["sessionData"]); - } - - // update the formdata - aData.formdata = formdata; - // merge the formdata - DocumentUtils.mergeFormData(aContent.document, formdata); - } - - if (aData.innerHTML) { - aWindow.setTimeout(function() { - if (aContent.document.designMode == "on" && - hasExpectedURL(aContent.document, aData.url) && - aContent.document.body) { - aContent.document.body.innerHTML = aData.innerHTML; - } - }, 0); - } - var match; - if (aData.scroll && (match = /(\d+),(\d+)/.exec(aData.scroll)) != null) { - aContent.scrollTo(match[1], match[2]); - } - Array.forEach(aContent.document.styleSheets, function(aSS) { - aSS.disabled = aSS.title && aSS.title != selectedPageStyle; - }); - for (var i = 0; i < aContent.frames.length; i++) { - if (aData.children && aData.children[i] && - hasExpectedURL(aContent.document, aData.url)) { - restoreTextDataAndScrolling(aContent.frames[i], aData.children[i], aPrefix + i + "|"); - } - } - } - - // don't restore text data and scrolling state if the user has navigated - // away before the loading completed (except for in-page navigation) - if (hasExpectedURL(aEvent.originalTarget, aBrowser.__SS_restore_data.url)) { - var content = aEvent.originalTarget.defaultView; - restoreTextDataAndScrolling(content, aBrowser.__SS_restore_data, ""); - aBrowser.markupDocumentViewer.authorStyleDisabled = selectedPageStyle == "_nostyle"; - } - - // notify the tabbrowser that this document has been completely restored - this._sendTabRestoredNotification(aBrowser.__SS_restore_tab); - - delete aBrowser.__SS_restore_data; - delete aBrowser.__SS_restore_pageStyle; - delete aBrowser.__SS_restore_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 && !this._prefBranch.getBoolPref("sessionstore.exactPos")) { - let screenLeft = {}, screenTop = {}, screenWidth = {}, screenHeight = {}; - screen.GetAvailRectDisplayPix(screenLeft, screenTop, screenWidth, screenHeight); - - // Screen X/Y are based on the origin of the screen's desktop-pixel coordinate space - let screenLeftCss = screenLeft.value; - let screenTopCss = screenTop.value; - - // Convert the 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. - // First, ensure the left edge is on-screen - if (aLeft < screenLeftCss) { - aLeft = screenLeftCss; - } - // Then check the resulting right edge, and reduce it if necessary. - let right = aLeft + aWidth; - if (right > screenRightCss) { - 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; - - // Do the same in the vertical dimension. - // First, ensure the top edge is on-screen - if (aTop < screenTopCss) { - aTop = screenTopCss; - } - // Then check the resulting right edge, and reduce it if necessary. - let bottom = aTop + aHeight; - if (bottom > screenBottomCss) { - bottom = screenBottomCss; - // See if we can move the top edge upwards to maintain height. - if (aTop > screenTopCss) { - aTop = Math.max(bottom - aHeight, screenTopCss); - } - } - // Finally, update aHeight to account for the adjusted top and bottom edges. - 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); - } - } - - // Restore window state - 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.toggleSidebar(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(); - } - }, - - /** - * Restores cookies - * @param aCookies - * Array of cookie objects - */ - restoreCookies: function ssi_restoreCookies(aCookies) { - // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision - var MAX_EXPIRY = Math.pow(2, 62); - for (let i = 0; i < aCookies.length; i++) { - var cookie = aCookies[i]; - try { - Services.cookies.add(cookie.host, cookie.path || "", cookie.name || "", - cookie.value, !!cookie.secure, !!cookie.httponly, true, - "expiry" in cookie ? cookie.expiry : MAX_EXPIRY, {}); - } - catch (ex) { Cu.reportError(ex); } // don't let a single cookie stop recovering - } - }, - - /* ........ Disk Access .............. */ - - /** - * save state delayed by N ms - * marks window as dirty (i.e. data update can't be skipped) - * @param aWindow - * Window reference - * @param aDelay - * Milliseconds to delay - */ - saveStateDelayed: function ssi_saveStateDelayed(aWindow, aDelay) { - if (aWindow) { - this._dirtyWindows[aWindow.__SSi] = true; - } - - if (!this._saveTimer) { - // interval until the next disk operation is allowed - var minimalDelay = this._lastSaveTime + this._interval - Date.now(); - - // if we have to wait, set a timer, otherwise saveState directly - aDelay = Math.max(minimalDelay, aDelay || 2000); - if (aDelay > 0) { - this._saveTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - this._saveTimer.init(this, aDelay, Ci.nsITimer.TYPE_ONE_SHOT); - } - else { - this.saveState(); - } - } - }, - - /** - * save state to disk - * @param aUpdateAll - * Bool update all windows - */ - saveState: function ssi_saveState(aUpdateAll) { - // If crash recovery is disabled, we only want to resume with pinned tabs - // if we crash. - let pinnedOnly = this._loadState == STATE_RUNNING && !this._resume_from_crash; - - var oState = this._getCurrentState(aUpdateAll, pinnedOnly); - if (!oState) { - return; - } - - // Forget about private windows. - for (let i = oState.windows.length - 1; i >= 0; i--) { - if (oState.windows[i].isPrivate) { - oState.windows.splice(i, 1); - if (oState.selectedWindow >= i) { - oState.selectedWindow--; - } - } - } - - for (let i = oState._closedWindows.length - 1; i >= 0; i--) { - if (oState._closedWindows[i].isPrivate) { - oState._closedWindows.splice(i, 1); - } - } - -#ifndef XP_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 (oState._closedWindows.length) { - let i = oState._closedWindows.length - 1; - if (oState._closedWindows[i]._shouldRestore) { - delete oState._closedWindows[i]._shouldRestore; - oState.windows.unshift(oState._closedWindows.pop()); - } - else { - // We only need to go until we hit !needsRestore since we're going in reverse - break; - } - } -#endif - - if (pinnedOnly) { - // Save original resume_session_once preference for when quiting browser, - // otherwise session will be restored next time browser starts and we - // only want it to be restored in the case of a crash. - if (this._resume_session_once_on_shutdown == null) { - this._resume_session_once_on_shutdown = - this._prefBranch.getBoolPref("sessionstore.resume_session_once"); - this._prefBranch.setBoolPref("sessionstore.resume_session_once", true); - // flush the preference file so preference will be saved in case of a crash - Services.prefs.savePrefFile(null); - } - } - - // Persist the last session if we deferred restoring it - if (this._lastSessionState) - oState.lastSessionState = this._lastSessionState; - - // 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 (this._deferredInitialState) { - oState.windows = this._deferredInitialState.windows || []; - } - - this._saveStateObject(oState); - }, - - /** - * write a state object to disk - */ - _saveStateObject: function ssi_saveStateObject(aStateObj) { - let data = this._toJSONString(aStateObj); - - let stateString = this._createSupportsString(data); - Services.obs.notifyObservers(stateString, "sessionstore-state-write", ""); - data = stateString.data; - - // Don't touch the file if an observer has deleted all state data. - if (!data) { - return; - } - - let promise; - // If "sessionstore.resume_from_crash" is true, attempt to backup the - // session file first, before writing to it. - if (this._resume_from_crash) { - // Note that we do not have race conditions here as _SessionFile - // guarantees that any I/O operation is completed before proceeding to - // the next I/O operation. - // Note backup happens only once, on initial save. - promise = this._backupSessionFileOnce; - } else { - promise = Promise.resolve(); - } - - // Attempt to write to the session file (potentially, depending on - // "sessionstore.resume_from_crash" preference, after successful backup). - promise = promise.then(function onSuccess() { - // Write (atomically) to a session file, using a tmp file. - return _SessionFile.write(data); - }); - - // Once the session file is successfully updated, save the time stamp of the - // last save and notify the observers. - promise = promise.then(() => { - this._lastSaveTime = Date.now(); - Services.obs.notifyObservers(null, "sessionstore-state-write-complete", - ""); - }); - }, - - /* ........ Auxiliary Functions .............. */ - - // Wrap a string as a nsISupports - _createSupportsString: function ssi_createSupportsString(aData) { - let string = Cc["@mozilla.org/supports-string;1"] - .createInstance(Ci.nsISupportsString); - string.data = aData; - return string; - }, - - /** - * 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() { - var win = Services.wm.getMostRecentWindow("navigator:browser"); - if (!win) - return null; - if (!win.closed) - return win; - -#ifdef BROKEN_WM_Z_ORDER - win = null; - var windowsEnum = Services.wm.getEnumerator("navigator:browser"); - // this is oldest to newest, so this gets a bit ugly - while (windowsEnum.hasMoreElements()) { - let nextWin = windowsEnum.getNext(); - if (!nextWin.closed) - win = nextWin; - } - return win; -#else - var windowsEnum = - Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", true); - while (windowsEnum.hasMoreElements()) { - win = windowsEnum.getNext(); - if (!win.closed) - return win; - } - return null; -#endif - }, - - /** - * 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(function (win) - win.tabs.every(function (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; - }, - - /** - * don't save sensitive data if the user doesn't want to - * (distinguishes between encrypted and non-encrypted sites) - * @param aIsHTTPS - * Bool is encrypted - * @param aUseDefaultPref - * don't do normal check for deferred - * @returns bool - */ - checkPrivacyLevel: function ssi_checkPrivacyLevel(aIsHTTPS, aUseDefaultPref) { - let pref = "sessionstore.privacy_level"; - // If we're in the process of quitting and we're not autoresuming the session - // then we should treat it as a deferred session. We have a different privacy - // pref for that case. - if (!aUseDefaultPref && this._loadState == STATE_QUITTING && !this._doResumeSession()) - pref = "sessionstore.privacy_level_deferred"; - return this._prefBranch.getIntPref(pref) < (aIsHTTPS ? PRIVACY_ENCRYPTED : PRIVACY_FULL); - }, - - /** - * 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; - }, - - /** - * Get nsIURI from string - * @param string - * @returns nsIURI - */ - _getURIFromString: function ssi_getURIFromString(aString) { - return Services.io.newURI(aString, null, null); - }, - - /** - * @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 (winData.length == 1 && winData[0].tabs && - winData[0].tabs.length == 1 && winData[0].tabs[0].entries && - winData[0].tabs[0].entries.length == 1 && - winData[0].tabs[0].entries[0].url == "about:sessionrestore") - 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); - }, - - /** - * 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.userTypedValue); - }, - - /** - * Determine if we can restore history into this tab. - * This will be false when a tab has been removed (usually between - * restoreHistoryPrecursor && restoreHistory) or if the tab is still marked - * as loading. - * - * @param aTab - * @returns boolean - */ - _canRestoreTabHistory: function ssi_canRestoreTabHistory(aTab) { - return aTab.parentNode && aTab.linkedBrowser && - aTab.linkedBrowser.__SS_tabStillLoading; - }, - - /** - * 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 placed into - * this._lastSessionState 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. Converting the object to a JSON string and - // parsing it again is the easiest way to do that, although not the most - // efficient one. Deferred sessions that don't have automatic session - // restore enabled tend to be a lot smaller though so that this shouldn't - // be a big perf hit. - state = JSON.parse(JSON.stringify(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 = {}; - aTargetWinState.tabs.forEach(function(tab) { - tab.entries.forEach(function(entry) { - this._extractHostsForCookiesFromEntry(entry, cookieHosts, false); - }, this); - }, this); - - // 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++; - } - }, - - /** - * Converts a JavaScript object into a JSON string - * (see http://www.json.org/ for more information). - * - * The inverse operation consists of JSON.parse(JSON_string). - * - * @param aJSObject is the object to be converted - * @returns the object's JSON representation - */ - _toJSONString: function ssi_toJSONString(aJSObject) { - return JSON.stringify(aJSObject); - }, - - _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) { - 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) { - 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 SSTabRestored event for the given tab. - * @param aTab the which has been restored - */ - _sendTabRestoredNotification: function ssi_sendTabRestoredNotification(aTab) { - let event = aTab.ownerDocument.createEvent("Events"); - event.initEvent("SSTabRestored", true, false); - 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; -#ifndef XP_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; -#endif - this._closedWindows.splice(spliceTo, this._closedWindows.length); - }, - - _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" - */ - _resetTabRestoringState: function ssi_resetTabRestoringState(aTab) { - let window = aTab.ownerDocument.defaultView; - 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"); - - // We want to decrement window.__SS_tabsToRestore here so that we always - // decrement it AFTER a tab is done restoring or when a tab gets "reset". - window.__SS_tabsToRestore--; - - // Remove the progress listener if we should. - this._removeTabsProgressListener(window); - - if (previousState == TAB_STATE_RESTORING) { - if (this._tabsRestoringCount) - this._tabsRestoringCount--; - } - else if (previousState == TAB_STATE_NEEDS_RESTORE) { - // Make sure the session history listener is removed. This is normally - // done in restoreTab, but this tab is being removed before that gets called. - this._removeSHistoryListener(aTab); - - // Make sure that the tab is removed from the list of tabs to restore. - // Again, this is normally done in restoreTab, but that isn't being called - // for this tab. - TabRestoreQueue.remove(aTab); - } - }, - - /** - * Add the tabs progress listener to the window if it isn't already - * - * @param aWindow - * The window to add our progress listener to - */ - _ensureTabsProgressListener: function ssi_ensureTabsProgressListener(aWindow) { - let tabbrowser = aWindow.gBrowser; - if (tabbrowser.mTabsProgressListeners.indexOf(gRestoreTabsProgressListener) == -1) - tabbrowser.addTabsProgressListener(gRestoreTabsProgressListener); - }, - - /** - * Attempt to remove the tabs progress listener from the window. - * - * @param aWindow - * The window from which to remove our progress listener from - */ - _removeTabsProgressListener: function ssi_removeTabsProgressListener(aWindow) { - // If there are no tabs left to restore (or restoring) in this window, then - // we can safely remove the progress listener from this window. - if (!aWindow.__SS_tabsToRestore) - aWindow.gBrowser.removeTabsProgressListener(gRestoreTabsProgressListener); - }, - - /** - * Remove the session history listener from the tab's browser if there is one. - * - * @param aTab - * The tab who's browser to remove the listener - */ - _removeSHistoryListener: function ssi_removeSHistoryListener(aTab) { - let browser = aTab.linkedBrowser; - if (browser.__SS_shistoryListener) { - browser.webNavigation.sessionHistory. - removeSHistoryListener(browser.__SS_shistoryListener); - delete browser.__SS_shistoryListener; - } - } -}; - -/** - * 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); - } else { - throw new Error("restore queue: hidden tab not found"); - } - }, - - // 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); - } else { - throw new Error("restore queue: visible tab not found"); - } - } -}; - -// 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 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. -var TabAttributes = { - _attrs: new Set(), - - // We never want to directly read or write those attributes. - // 'image' should not be accessed directly but handled by using the - // gBrowser.getIcon()/setIcon() methods. - // 'pending' is used internal by sessionstore and managed accordingly. - // 'skipbackgroundnotify' is used internal by tabbrowser.xml. - _skipAttrs: new Set(["image", "pending", "skipbackgroundnotify"]), - - persist: function (name) { - if (this._attrs.has(name) || this._skipAttrs.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) { - tab.setAttribute(name, data[name]); - } - } -}; - -// This is used to help meter the number of restoring tabs. This is the control -// point for telling the next tab to restore. It gets attached to each gBrowser -// via gBrowser.addTabsProgressListener -var gRestoreTabsProgressListener = { - onStateChange: function(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { - // Ignore state changes on browsers that we've already restored and state - // changes that aren't applicable. - if (aBrowser.__SS_restoreState && - aBrowser.__SS_restoreState == TAB_STATE_RESTORING && - aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && - aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK && - aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) { - // We need to reset the tab before starting the next restore. - let win = aBrowser.ownerDocument.defaultView; - let tab = win.gBrowser.getTabForBrowser(aBrowser); - SessionStoreInternal._resetTabRestoringState(tab); - SessionStoreInternal.restoreNextTab(); - } - } -}; - -// A SessionStoreSHistoryListener will be attached to each browser before it is -// restored. We need to catch reloads that occur before the tab is restored -// because otherwise, docShell will reload an old URI (usually about:blank). -function SessionStoreSHistoryListener(aTab) { - this.tab = aTab; -} -SessionStoreSHistoryListener.prototype = { - QueryInterface: XPCOMUtils.generateQI([ - Ci.nsISHistoryListener, - Ci.nsISupportsWeakReference - ]), - browser: null, - OnHistoryNewEntry: function(aNewURI) { }, - OnHistoryGoBack: function(aBackURI) { return true; }, - OnHistoryGoForward: function(aForwardURI) { return true; }, - OnHistoryGotoIndex: function(aIndex, aGotoURI) { return true; }, - OnHistoryPurge: function(aNumEntries) { return true; }, - OnHistoryReload: function(aReloadURI, aReloadFlags) { - // On reload, we want to make sure that session history loads the right - // URI. In order to do that, we will juet call restoreTab. That will remove - // the history listener and load the right URI. - SessionStoreInternal.restoreTab(this.tab); - // Returning false will stop the load that docshell is attempting. - return false; - } -} - -// See toolkit/forgetaboutsite/ForgetAboutSite.jsm -String.prototype.hasRootDomain = function hasRootDomain(aDomain) { - let index = this.indexOf(aDomain); - if (index == -1) - return false; - - if (this == aDomain) - return true; - - let prevChar = this[index - 1]; - return (index == (this.length - aDomain.length)) && - (prevChar == "." || prevChar == "/"); -} diff --git a/components/sessionstore/XPathGenerator.jsm b/components/sessionstore/XPathGenerator.jsm deleted file mode 100644 index 83ff2b8..0000000 --- a/components/sessionstore/XPathGenerator.jsm +++ /dev/null @@ -1,97 +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 = ["XPathGenerator"]; - -this.XPathGenerator = { - // these two hashes should be kept in sync - namespaceURIs: { "xhtml": "http://www.w3.org/1999/xhtml" }, - namespacePrefixes: { "http://www.w3.org/1999/xhtml": "xhtml" }, - - /** - * Generates an approximate XPath query to an (X)HTML node - */ - generate: function sss_xph_generate(aNode) { - // have we reached the document node already? - if (!aNode.parentNode) - return ""; - - // Access localName, namespaceURI just once per node since it's expensive. - let nNamespaceURI = aNode.namespaceURI; - let nLocalName = aNode.localName; - - let prefix = this.namespacePrefixes[nNamespaceURI] || null; - let tag = (prefix ? prefix + ":" : "") + this.escapeName(nLocalName); - - // stop once we've found a tag with an ID - if (aNode.id) - return "//" + tag + "[@id=" + this.quoteArgument(aNode.id) + "]"; - - // count the number of previous sibling nodes of the same tag - // (and possible also the same name) - let count = 0; - let nName = aNode.name || null; - for (let n = aNode; (n = n.previousSibling); ) - if (n.localName == nLocalName && n.namespaceURI == nNamespaceURI && - (!nName || n.name == nName)) - count++; - - // recurse until hitting either the document node or an ID'd node - return this.generate(aNode.parentNode) + "/" + tag + - (nName ? "[@name=" + this.quoteArgument(nName) + "]" : "") + - (count ? "[" + (count + 1) + "]" : ""); - }, - - /** - * Resolves an XPath query generated by XPathGenerator.generate - */ - resolve: function sss_xph_resolve(aDocument, aQuery) { - let xptype = Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE; - return aDocument.evaluate(aQuery, aDocument, this.resolveNS, xptype, null).singleNodeValue; - }, - - /** - * Namespace resolver for the above XPath resolver - */ - resolveNS: function sss_xph_resolveNS(aPrefix) { - return XPathGenerator.namespaceURIs[aPrefix] || null; - }, - - /** - * @returns valid XPath for the given node (usually just the local name itself) - */ - escapeName: function sss_xph_escapeName(aName) { - // we can't just use the node's local name, if it contains - // special characters (cf. bug 485482) - return /^\w+$/.test(aName) ? aName : - "*[local-name()=" + this.quoteArgument(aName) + "]"; - }, - - /** - * @returns a properly quoted string to insert into an XPath query - */ - quoteArgument: function sss_xph_quoteArgument(aArg) { - return !/'/.test(aArg) ? "'" + aArg + "'" : - !/"/.test(aArg) ? '"' + aArg + '"' : - "concat('" + aArg.replace(/'+/g, "',\"$&\",'") + "')"; - }, - - /** - * @returns an XPath query to all savable form field nodes - */ - get restorableFormNodes() { - // for a comprehensive list of all available <INPUT> types see - // http://mxr.mozilla.org/mozilla-central/search?string=kInputTypeTable - let ignoreTypes = ["password", "hidden", "button", "image", "submit", "reset"]; - // XXXzeniko work-around until lower-case has been implemented (bug 398389) - let toLowerCase = '"ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"'; - let ignore = "not(translate(@type, " + toLowerCase + ")='" + - ignoreTypes.join("' or translate(@type, " + toLowerCase + ")='") + "')"; - let formNodesXPath = "//textarea|//select|//xhtml:textarea|//xhtml:select|" + - "//input[" + ignore + "]|//xhtml:input[" + ignore + "]"; - - delete this.restorableFormNodes; - return (this.restorableFormNodes = formNodesXPath); - } -}; diff --git a/components/sessionstore/_SessionFile.jsm b/components/sessionstore/_SessionFile.jsm deleted file mode 100644 index 62b4d16..0000000 --- a/components/sessionstore/_SessionFile.jsm +++ /dev/null @@ -1,314 +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; - -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"); - -XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", - "resource://gre/modules/NetUtil.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "console", - "resource://gre/modules/Console.jsm"); - -// An encoder to UTF-8. -XPCOMUtils.defineLazyGetter(this, "gEncoder", function () { - return new TextEncoder(); -}); -// A decoder. -XPCOMUtils.defineLazyGetter(this, "gDecoder", function () { - return new TextDecoder(); -}); - -this._SessionFile = { - /** - * A promise fulfilled once initialization (either synchronous or - * asynchronous) is complete. - */ - promiseInitialized: function SessionFile_initialized() { - return SessionFileInternal.promiseInitialized; - }, - /** - * Read the contents of the session file, asynchronously. - */ - read: function SessionFile_read() { - return SessionFileInternal.read(); - }, - /** - * Read the contents of the session file, synchronously. - */ - syncRead: function SessionFile_syncRead() { - return SessionFileInternal.syncRead(); - }, - /** - * Write the contents of the session file, asynchronously. - */ - write: function SessionFile_write(aData) { - return SessionFileInternal.write(aData); - }, - /** - * Create a backup copy, asynchronously. - */ - createBackupCopy: function SessionFile_createBackupCopy() { - return SessionFileInternal.createBackupCopy(); - }, - /** - * Wipe the contents of the session file, asynchronously. - */ - wipe: function SessionFile_wipe() { - return SessionFileInternal.wipe(); - } -}; - -Object.freeze(_SessionFile); - -/** - * Utilities for dealing with promises and Task.jsm - */ -const TaskUtils = { - /** - * Add logging to a promise. - * - * @param {Promise} promise - * @return {Promise} A promise behaving as |promise|, but with additional - * logging in case of uncaught error. - */ - captureErrors: function captureErrors(promise) { - return promise.then( - null, - function onError(reason) { - console.error("Uncaught asynchronous error:", reason); - throw reason; - } - ); - }, - /** - * Spawn a new Task from a generator. - * - * This function behaves as |Task.spawn|, with the exception that it - * adds logging in case of uncaught error. For more information, see - * the documentation of |Task.jsm|. - * - * @param {generator} gen Some generator. - * @return {Promise} A promise built from |gen|, with the same semantics - * as |Task.spawn(gen)|. - */ - spawn: function spawn(gen) { - return this.captureErrors(Task.spawn(gen)); - } -}; - -var SessionFileInternal = { - /** - * A promise fulfilled once initialization is complete - */ - promiseInitialized: Promise.defer(), - - /** - * The path to sessionstore.js - */ - path: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"), - - /** - * The path to sessionstore.bak - */ - backupPath: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak"), - - /** - * Utility function to safely read a file synchronously. - * @param aPath - * A path to read the file from. - * @returns string if successful, undefined otherwise. - */ - readAuxSync: function ssfi_readAuxSync(aPath) { - let text; - try { - let file = new FileUtils.File(aPath); - let chan = NetUtil.newChannel({ - uri: NetUtil.newURI(file), - loadUsingSystemPrincipal: true - }); - let stream = chan.open(); - text = NetUtil.readInputStreamToString(stream, stream.available(), - {charset: "utf-8"}); - } catch (e if e.result == Components.results.NS_ERROR_FILE_NOT_FOUND) { - // Ignore exceptions about non-existent files. - } catch (ex) { - // Any other error. - console.error("Uncaught error:", ex); - } finally { - return text; - } - }, - - /** - * Read the sessionstore file synchronously. - * - * This function is meant to serve as a fallback in case of race - * between a synchronous usage of the API and asynchronous - * initialization. - * - * In case if sessionstore.js file does not exist or is corrupted (something - * happened between backup and write), attempt to read the sessionstore.bak - * instead. - */ - syncRead: function ssfi_syncRead() { - // First read the sessionstore.js. - let text = this.readAuxSync(this.path); - if (typeof text === "undefined") { - // If sessionstore.js does not exist or is corrupted, read sessionstore.bak. - text = this.readAuxSync(this.backupPath); - } - return text || ""; - }, - - /** - * Utility function to safely read a file asynchronously. - * @param aPath - * A path to read the file from. - * @param aReadOptions - * Read operation options. - * |outExecutionDuration| option will be reused and can be - * incrementally updated by the worker process. - * @returns string if successful, undefined otherwise. - */ - readAux: function ssfi_readAux(aPath, aReadOptions) { - let self = this; - return TaskUtils.spawn(function () { - let text; - try { - let bytes = yield OS.File.read(aPath, undefined, aReadOptions); - text = gDecoder.decode(bytes); - } catch (ex if self._isNoSuchFile(ex)) { - // Ignore exceptions about non-existent files. - } catch (ex) { - // Any other error. - console.error("Uncaught error - with the file: " + self.path, ex); - } - throw new Task.Result(text); - }); - }, - - /** - * Read the sessionstore file asynchronously. - * - * In case sessionstore.js file does not exist or is corrupted (something - * happened between backup and write), attempt to read the sessionstore.bak - * instead. - */ - read: function ssfi_read() { - let self = this; - return TaskUtils.spawn(function task() { - // Specify |outExecutionDuration| option to hold the combined duration of - // the asynchronous reads off the main thread (of both sessionstore.js and - // sessionstore.bak, if necessary). If sessionstore.js does not exist or - // is corrupted, |outExecutionDuration| will register the time it took to - // attempt to read the file. It will then be subsequently incremented by - // the read time of sessionsore.bak. - let readOptions = { - outExecutionDuration: null - }; - // First read the sessionstore.js. - let text = yield self.readAux(self.path, readOptions); - if (typeof text === "undefined") { - // If sessionstore.js does not exist or is corrupted, read the - // sessionstore.bak. - text = yield self.readAux(self.backupPath, readOptions); - } - // Return either the content of the sessionstore.bak if it was read - // successfully or an empty string otherwise. - throw new Task.Result(text || ""); - }); - }, - - write: function ssfi_write(aData) { - let refObj = {}; - let self = this; - return TaskUtils.spawn(function task() { - let bytes = gEncoder.encode(aData); - - try { - let promise = OS.File.writeAtomic(self.path, bytes, {tmpPath: self.path + ".tmp"}); - yield promise; - } catch (ex) { - console.error("Could not write session state file: " + self.path, ex); - } - }); - }, - - createBackupCopy: function ssfi_createBackupCopy() { - let backupCopyOptions = { - outExecutionDuration: null - }; - let self = this; - return TaskUtils.spawn(function task() { - try { - yield OS.File.move(self.path, self.backupPath, backupCopyOptions); - } catch (ex if self._isNoSuchFile(ex)) { - // Ignore exceptions about non-existent files. - } catch (ex) { - console.error("Could not backup session state file: " + self.path, ex); - throw ex; - } - }); - }, - - wipe: function ssfi_wipe() { - let self = this; - return TaskUtils.spawn(function task() { - try { - yield OS.File.remove(self.path); - } catch (ex if self._isNoSuchFile(ex)) { - // Ignore exceptions about non-existent files. - } catch (ex) { - console.error("Could not remove session state file: " + self.path, ex); - throw ex; - } - - try { - yield OS.File.remove(self.backupPath); - } catch (ex if self._isNoSuchFile(ex)) { - // Ignore exceptions about non-existent files. - } catch (ex) { - console.error("Could not remove session state backup file: " + self.path, ex); - throw ex; - } - }); - }, - - _isNoSuchFile: function ssfi_isNoSuchFile(aReason) { - return aReason instanceof OS.File.Error && aReason.becauseNoSuchFile; - } -}; diff --git a/components/sessionstore/content/aboutSessionRestore.js b/components/sessionstore/content/aboutSessionRestore.js deleted file mode 100644 index 2b6f9ea..0000000 --- a/components/sessionstore/content/aboutSessionRestore.js +++ /dev/null @@ -1,320 +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/. */ - -var Cc = Components.classes; -var Ci = Components.interfaces; -var Cu = Components.utils; - -var gStateObject; -var gTreeData; - -// Page initialization - -window.onload = function() { - // 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; - } - - // remove unneeded braces (added for compatibility with Firefox 2.0 and 3.0) - if (sessionData.value.charAt(0) == '(') - sessionData.value = sessionData.value.slice(1, -1); - try { - gStateObject = JSON.parse(sessionData.value); - } - catch (exJSON) { - var s = new Cu.Sandbox("about:blank", {sandboxName: 'aboutSessionRestore'}); - gStateObject = Cu.evalInSandbox("(" + sessionData.value + ")", s); - // If we couldn't parse the string with JSON.parse originally, make sure - // that the value in the textbox will be parsable. - sessionData.value = JSON.stringify(gStateObject); - } - - // 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 initTreeView() { - var tabList = document.getElementById("tabList"); - var winLabel = tabList.getAttribute("_window_label"); - - gTreeData = []; - 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.attributes && aTabData.attributes.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 restoreSession() { - document.getElementById("errorTryAgain").disabled = true; - - // 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(function(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"); - newWindow.addEventListener("load", function() { - newWindow.removeEventListener("load", arguments.callee, true); - ss.setWindowState(newWindow, stateString, true); - - var tabbrowser = top.gBrowser; - var tabIndex = tabbrowser.getBrowserIndexForDocument(document); - tabbrowser.removeTab(tabbrowser.tabs[tabIndex]); - }, true); -} - -function startNewSession() { - var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); - if (prefBranch.getIntPref("browser.startup.page") == 0) - getBrowserWindow().gBrowser.loadURI("about:logopage"); - 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. -#ifdef XP_MACOSX - let accelKey = aEvent.metaKey; -#else - let accelKey = aEvent.ctrlKey; -#endif - 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); - break; - case KeyEvent.DOM_VK_RETURN: - var ix = document.getElementById("tabList").currentIndex; - if (aEvent.ctrlKey && !treeView.isContainer(ix)) - restoreSingleTab(ix, aEvent.shiftKey); - break; - case KeyEvent.DOM_VK_UP: - case KeyEvent.DOM_VK_DOWN: - case KeyEvent.DOM_VK_PAGE_UP: - case KeyEvent.DOM_VK_PAGE_DOWN: - case KeyEvent.DOM_VK_HOME: - case KeyEvent.DOM_VK_END: - aEvent.preventDefault(); // else the page scrolls unwantedly - 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) { - var item = gTreeData[aIx]; - item.checked = !item.checked; - treeView.treeBox.invalidateRow(aIx); - - function isChecked(aItem) aItem.checked; - - 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)); - } - - 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 "open" in gTreeData[idx]; }, - 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/components/sessionstore/content/aboutSessionRestore.xhtml b/components/sessionstore/content/aboutSessionRestore.xhtml deleted file mode 100644 index 6b22250..0000000 --- a/components/sessionstore/content/aboutSessionRestore.xhtml +++ /dev/null @@ -1,94 +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"> - <head> - <title>&restorepage.tabtitle;</title> - <link rel="stylesheet" href="chrome://global/skin/netError.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;"> - - <!-- PAGE CONTAINER (for styling purposes only) --> - <div id="errorPageContainer"> - - <!-- Error Title --> - <div id="errorTitle"> - <h1 id="errorTitleText">&restorepage.errorTitle;</h1> - </div> - - <!-- LONG CONTENT (the section most likely to require scrolling) --> - <div id="errorLongContent"> - - <!-- Short Description --> - <div id="errorShortDesc"> - <p id="errorShortDescText">&restorepage.problemDesc;</p> - </div> - - <!-- Long Description (Note: See netError.dtd for used XHTML tags) --> - <div id="errorLongDesc"> - <p>&restorepage.tryThis;</p> - <ul> - <li>&restorepage.restoreSome;</li> - <li>&restorepage.startNew;</li> - </ul> - </div> - - <!-- Short Description --> - <div id="errorTrailerDesc"> - <tree xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" - id="tabList" flex="1" seltype="single" hidecolumnpicker="true" - onclick="onListClick(event);" onkeydown="onListKeyDown(event);" - _window_label="&restorepage.windowLabel;"> - <treecols> - <treecol cycler="true" id="restore" type="checkbox" label="&restorepage.restoreHeader;"/> - <splitter class="tree-splitter"/> - <treecol primary="true" id="title" label="&restorepage.listHeader;" flex="1"/> - </treecols> - <treechildren flex="1"/> - </tree> - </div> - </div> - - <!-- Buttons --> - <hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="buttons"> -#ifdef XP_UNIX - <button id="errorCancel" label="&restorepage.closeButton;" - accesskey="&restorepage.close.access;" - oncommand="startNewSession();"/> - <button id="errorTryAgain" label="&restorepage.tryagainButton;" - accesskey="&restorepage.restore.access;" - oncommand="restoreSession();"/> -#else - <button id="errorTryAgain" label="&restorepage.tryagainButton;" - accesskey="&restorepage.restore.access;" - oncommand="restoreSession();"/> - <button id="errorCancel" label="&restorepage.closeButton;" - accesskey="&restorepage.close.access;" - oncommand="startNewSession();"/> -#endif - </hbox> - <!-- holds the session data for when the tab is closed --> - <input type="text" id="sessionData" style="display: none;"/> - </div> - - </body> -</html> diff --git a/components/sessionstore/content/content-sessionStore.js b/components/sessionstore/content/content-sessionStore.js deleted file mode 100644 index e3e956e..0000000 --- a/components/sessionstore/content/content-sessionStore.js +++ /dev/null @@ -1,40 +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/. */ - -function debug(msg) { - Services.console.logStringMessage("SessionStoreContent: " + msg); -} - -/** - * Listens for and handles content events that we need for the - * session store service to be notified of state changes in content. - */ -var EventListener = { - - DOM_EVENTS: [ - "pageshow", "change", "input" - ], - - init: function () { - this.DOM_EVENTS.forEach(e => addEventListener(e, this, true)); - }, - - handleEvent: function (event) { - switch (event.type) { - case "pageshow": - if (event.persisted) - sendAsyncMessage("SessionStore:pageshow"); - break; - case "input": - case "change": - sendAsyncMessage("SessionStore:input"); - break; - default: - debug("received unknown event '" + event.type + "'"); - break; - } - } -}; - -EventListener.init(); diff --git a/components/sessionstore/jar.mn b/components/sessionstore/jar.mn deleted file mode 100644 index 825b00f..0000000 --- a/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/components/sessionstore/moz.build b/components/sessionstore/moz.build deleted file mode 100644 index 84278da..0000000 --- a/components/sessionstore/moz.build +++ /dev/null @@ -1,29 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; 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 = [ - '_SessionFile.jsm', - 'DocumentUtils.jsm', - 'SessionStorage.jsm', - 'XPathGenerator.jsm', -] - -EXTRA_PP_JS_MODULES.sessionstore += ['SessionStore.jsm']
\ No newline at end of file diff --git a/components/sessionstore/nsISessionStartup.idl b/components/sessionstore/nsISessionStartup.idl deleted file mode 100644 index a8e786d..0000000 --- a/components/sessionstore/nsISessionStartup.idl +++ /dev/null @@ -1,59 +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, POSTDATA and window features - * - and allows to restore everything into one window. - */ - -[scriptable, uuid(51f4b9f0-f3d2-11e2-bb62-2c24dd830245)] -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 and makes sure that - * we're initialized before returning. If we're not yet this will read the - * session file synchronously. - */ - boolean doRestore(); - - /** - * 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; -}; diff --git a/components/sessionstore/nsISessionStore.idl b/components/sessionstore/nsISessionStore.idl deleted file mode 100644 index 0490772..0000000 --- a/components/sessionstore/nsISessionStore.idl +++ /dev/null @@ -1,206 +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, POSTDATA 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(43ec216b-f002-4424-bfc5-fc555c87dbc4)] -interface nsISessionStore : nsISupports -{ - /** - * Initialize the service - */ - jsval init(in nsIDOMWindow aWindow); - - /** - * 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 AString 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 AString 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 aName is the name of the attribute to save/restore for all tabbrowser tabs. - */ - void persistTabAttribute(in AString aName); -}; diff --git a/components/sessionstore/nsSessionStartup.js b/components/sessionstore/nsSessionStartup.js deleted file mode 100644 index 04037c1..0000000 --- a/components/sessionstore/nsSessionStartup.js +++ /dev/null @@ -1,296 +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/. */ - -/** - * 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 session file stores a session.state property, that - * indicates whether the browser is currently running. When the browser shuts - * down, the field is changed to "stopped". At startup, this field is read, and - * if its value is "running", then it's assumed that the browser had previously - * crashed, or at the very least that something bad happened, and that 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/PrivateBrowsingUtils.jsm"); -Cu.import("resource://gre/modules/Promise.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "_SessionFile", - "resource:///modules/sessionstore/_SessionFile.jsm"); - -const STATE_RUNNING_STR = "running"; - -function debug(aMsg) { - aMsg = ("SessionStartup: " + aMsg).replace(/\S{80}/g, "$&\n"); - Services.console.logStringMessage(aMsg); -} - -var gOnceInitializedDeferred = Promise.defer(); - -/* :::::::: The Service ::::::::::::::: */ - -function SessionStartup() { -} - -SessionStartup.prototype = { - - // the state to restore at startup - _initialState: null, - _sessionType: Ci.nsISessionStartup.NO_SESSION, - _initialized: false, - -/* ........ Global Event Handlers .............. */ - - /** - * Initialize the component - */ - init: function sss_init() { - // do not need to initialize anything in auto-started private browsing sessions - if (PrivateBrowsingUtils.permanentPrivateBrowsing) { - this._initialized = true; - gOnceInitializedDeferred.resolve(); - return; - } - - if (Services.prefs.getBoolPref("browser.sessionstore.resume_session_once") || - Services.prefs.getIntPref("browser.startup.page") == 3) { - this._ensureInitialized(); - } else { - _SessionFile.read().then( - this._onSessionFileRead.bind(this) - ); - } - }, - - // 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; - }, - - _onSessionFileRead: function sss_onSessionFileRead(aStateString) { - if (this._initialized) { - // Initialization is complete, nothing else to do - return; - } - try { - this._initialized = true; - - // Let observers modify the state before it is used - let supportsStateString = this._createSupportsString(aStateString); - Services.obs.notifyObservers(supportsStateString, "sessionstore-state-read", ""); - aStateString = supportsStateString.data; - - // No valid session found. - if (!aStateString) { - this._sessionType = Ci.nsISessionStartup.NO_SESSION; - return; - } - - // parse the session state into a JS object - // remove unneeded braces (added for compatibility with Firefox 2.0 and 3.0) - if (aStateString.charAt(0) == '(') - aStateString = aStateString.slice(1, -1); - let corruptFile = false; - try { - this._initialState = JSON.parse(aStateString); - } - catch (ex) { - debug("The session file contained un-parse-able JSON: " + ex); - // This is not valid JSON, but this might still be valid JavaScript, - // as used in FF2/FF3, so we need to eval. - // evalInSandbox will throw if aStateString is not parse-able. - try { - var s = new Cu.Sandbox("about:blank", {sandboxName: 'nsSessionStartup'}); - this._initialState = Cu.evalInSandbox("(" + aStateString + ")", s); - } catch(ex) { - debug("The session file contained un-eval-able JSON: " + ex); - corruptFile = true; - } - } - let doResumeSessionOnce = Services.prefs.getBoolPref("browser.sessionstore.resume_session_once"); - let doResumeSession = doResumeSessionOnce || - Services.prefs.getIntPref("browser.startup.page") == 3; - - // If this is a normal restore then throw away any previous session - if (!doResumeSessionOnce) - delete this._initialState.lastSessionState; - - let resumeFromCrash = Services.prefs.getBoolPref("browser.sessionstore.resume_from_crash"); - let lastSessionCrashed = - this._initialState && this._initialState.session && - this._initialState.session.state && - this._initialState.session.state == STATE_RUNNING_STR; - - // set the startup type - if (lastSessionCrashed && resumeFromCrash) - this._sessionType = Ci.nsISessionStartup.RECOVER_SESSION; - else if (!lastSessionCrashed && doResumeSession) - 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); - - } finally { - // 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() { - this._ensureInitialized(); - return this._initialState; - }, - - /** - * Determines whether there is a pending session restore and makes sure that - * we're initialized before returning. If we're not yet this will read the - * session file synchronously. - * @returns bool - */ - doRestore: function sss_doRestore() { - this._ensureInitialized(); - return this._willRestore(); - }, - - /** - * 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() { - this._ensureInitialized(); - return this._sessionType; - }, - - // Ensure that initialization is complete. - // If initialization is not complete yet, fall back to a synchronous - // initialization and kill ongoing asynchronous initialization - _ensureInitialized: function sss__ensureInitialized() { - try { - if (this._initialized) { - // Initialization is complete, nothing else to do - return; - } - let contents = _SessionFile.syncRead(); - this._onSessionFileRead(contents); - } catch(ex) { - debug("ensureInitialized: could not read session " + ex + ", " + ex.stack); - throw ex; - } - }, - - /* ........ 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/components/sessionstore/nsSessionStore.js b/components/sessionstore/nsSessionStore.js deleted file mode 100644 index 38713d5..0000000 --- a/components/sessionstore/nsSessionStore.js +++ /dev/null @@ -1,37 +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/. */ - -/** - * 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/components/sessionstore/nsSessionStore.manifest b/components/sessionstore/nsSessionStore.manifest deleted file mode 100644 index b136b41..0000000 --- a/components/sessionstore/nsSessionStore.manifest +++ /dev/null @@ -1,18 +0,0 @@ -# WebappRT doesn't need these instructions, and they don't necessarily work -# with it, but it does use a GRE directory that the GRE shares with Firefox, -# so in order to prevent the instructions from being processed for WebappRT, -# we need to restrict them to the applications that depend on them, i.e.: -# -# b2g: {3c2e2abc-06d4-11e1-ac3b-374f68613e61} -# browser: {8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4} -# mobile/android: {aa3c5121-dab2-40e2-81ca-7ea25febc110} -# mobile/xul: {a23983c0-fd0e-11dc-95ff-0800200c9a66} -# -# In theory we should do this for all these instructions, but in practice it is -# sufficient to do it for the app-startup one, and the file is simpler that way. - -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={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4} application={aa3c5121-dab2-40e2-81ca-7ea25febc110} application={a23983c0-fd0e-11dc-95ff-0800200c9a66} |