diff options
Diffstat (limited to 'browser/components/sessionstore/nsSessionStartup.js')
-rw-r--r-- | browser/components/sessionstore/nsSessionStartup.js | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/browser/components/sessionstore/nsSessionStartup.js b/browser/components/sessionstore/nsSessionStartup.js new file mode 100644 index 000000000..13e13ecdb --- /dev/null +++ b/browser/components/sessionstore/nsSessionStartup.js @@ -0,0 +1,296 @@ +/* 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() { + // 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(aData) { + let string = Cc["@mozilla.org/supports-string;1"] + .createInstance(Ci.nsISupportsString); + string.data = aData; + return string; + }, + + _onSessionFileRead: function(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(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() { + 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() { + 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]); |