gecko/browser/components/sessionstore/src/nsSessionStartup.js

310 lines
11 KiB
JavaScript
Raw Normal View History

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/**
* Session Storage and Restoration
*
* Overview
* This service reads user's session file at startup, and makes a determination
* as to whether the session should be restored. It will restore the session
* under the circumstances described below. If the auto-start Private Browsing
* mode is active, however, the session is never restored.
*
* Crash Detection
* The 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/TelemetryStopwatch.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";
// 'browser.startup.page' preference value to resume the previous session.
const BROWSER_STARTUP_RESUME_SESSION = 3;
function debug(aMsg) {
aMsg = ("SessionStartup: " + aMsg).replace(/\S{80}/g, "$&\n");
Services.console.logStringMessage(aMsg);
}
let 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;
}
SessionFile.read().then(
this._onSessionFileRead.bind(this),
Cu.reportError
);
},
// 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;
}
}
Services.telemetry.getHistogramById("FX_SESSION_RESTORE_CORRUPT_FILE").add(corruptFile);
let doResumeSessionOnce = Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
let doResumeSession = doResumeSessionOnce ||
Services.prefs.getIntPref("browser.startup.page") == BROWSER_STARTUP_RESUME_SESSION;
// 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;
// Report shutdown success via telemetry. Shortcoming here are
// being-killed-by-OS-shutdown-logic, shutdown freezing after
// session restore was written, etc.
Services.telemetry.getHistogramById("SHUTDOWN_OK").add(!lastSessionCrashed);
// 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. Should only be
* called after initialization has completed.
* @throws Error if initialization is not complete yet.
* @returns bool
*/
doRestore: function sss_doRestore() {
this._ensureInitialized();
return this._willRestore();
},
/**
* Determines whether automatic session restoration is enabled for this
* launch of the browser. This does not include crash restoration. In
* particular, if session restore is configured to restore only in case of
* crash, this method returns false.
* @returns bool
*/
isAutomaticRestoreEnabled: function () {
return Services.prefs.getBoolPref("browser.sessionstore.resume_session_once") ||
Services.prefs.getIntPref("browser.startup.page") == BROWSER_STARTUP_RESUME_SESSION;
},
/**
* Determines whether there is a pending session restore.
* @returns bool
*/
_willRestore: function () {
return this._sessionType == Ci.nsISessionStartup.RECOVER_SESSION ||
this._sessionType == Ci.nsISessionStartup.RESUME_SESSION;
},
/**
* Returns whether we will restore a session that ends up replacing the
* homepage. The browser uses this to not start loading the homepage if
* we're going to stop its load anyway shortly after.
*
* This is meant to be an optimization for the average case that loading the
* session file finishes before we may want to start loading the default
* homepage. Should this be called before the session file has been read it
* will just return false.
*
* @returns bool
*/
get willOverrideHomepage() {
if (this._initialState && this._willRestore()) {
let windows = this._initialState.windows || null;
// If there are valid windows with not only pinned tabs, signal that we
// will override the default homepage by restoring a session.
return windows && windows.some(w => w.tabs.some(t => !t.pinned));
}
return false;
},
/**
* Get the type of pending session store, if any.
*/
get sessionType() {
this._ensureInitialized();
return this._sessionType;
},
// Ensure that initialization is complete. If initialization is not complete
// yet, something is attempting to use the old synchronous initialization,
// throw an error.
_ensureInitialized: function sss__ensureInitialized() {
if (!this._initialized) {
throw new Error("Session Store is not initialized.");
}
},
/* ........ QueryInterface .............. */
QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference,
Ci.nsISessionStartup]),
classID: Components.ID("{ec7a6c20-e081-11da-8ad9-0800200c9a66}")
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SessionStartup]);