summaryrefslogtreecommitdiffstats
path: root/browser/components/sessionstore/nsSessionStartup.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/sessionstore/nsSessionStartup.js')
-rw-r--r--browser/components/sessionstore/nsSessionStartup.js353
1 files changed, 353 insertions, 0 deletions
diff --git a/browser/components/sessionstore/nsSessionStartup.js b/browser/components/sessionstore/nsSessionStartup.js
new file mode 100644
index 000000000..7593c48ec
--- /dev/null
+++ b/browser/components/sessionstore/nsSessionStartup.js
@@ -0,0 +1,353 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Session Storage and Restoration
+ *
+ * Overview
+ * This service reads user's session file at startup, and makes a determination
+ * as to whether the session should be restored. It will restore the session
+ * under the circumstances described below. If the auto-start Private Browsing
+ * mode is active, however, the session is never restored.
+ *
+ * Crash Detection
+ * The CrashMonitor is used to check if the final session state was successfully
+ * written at shutdown of the last session. If we did not reach
+ * 'sessionstore-final-state-write-complete', then it's assumed that the browser
+ * has previously crashed and we should restore the session.
+ *
+ * Forced Restarts
+ * In the event that a restart is required due to application update or extension
+ * installation, set the browser.sessionstore.resume_session_once pref to true,
+ * and the session will be restored the next time the browser starts.
+ *
+ * Always Resume
+ * This service will always resume the session if the integer pref
+ * browser.startup.page is set to 3.
+ */
+
+/* :::::::: Constants and Helpers ::::::::::::::: */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/TelemetryStopwatch.jsm");
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+ "resource://gre/modules/Console.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
+ "resource:///modules/sessionstore/SessionFile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "StartupPerformance",
+ "resource:///modules/sessionstore/StartupPerformance.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CrashMonitor",
+ "resource://gre/modules/CrashMonitor.jsm");
+
+const STATE_RUNNING_STR = "running";
+
+// 'browser.startup.page' preference value to resume the previous session.
+const BROWSER_STARTUP_RESUME_SESSION = 3;
+
+function debug(aMsg) {
+ aMsg = ("SessionStartup: " + aMsg).replace(/\S{80}/g, "$&\n");
+ Services.console.logStringMessage(aMsg);
+}
+function warning(aMsg, aException) {
+ let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
+consoleMsg.init(aMsg, aException.fileName, null, aException.lineNumber, 0, Ci.nsIScriptError.warningFlag, "component javascript");
+ Services.console.logMessage(consoleMsg);
+}
+
+var gOnceInitializedDeferred = (function () {
+ let deferred = {};
+
+ deferred.promise = new Promise((resolve, reject) => {
+ deferred.resolve = resolve;
+ deferred.reject = reject;
+ });
+
+ return deferred;
+})();
+
+/* :::::::: The Service ::::::::::::::: */
+
+function SessionStartup() {
+}
+
+SessionStartup.prototype = {
+
+ // the state to restore at startup
+ _initialState: null,
+ _sessionType: Ci.nsISessionStartup.NO_SESSION,
+ _initialized: false,
+
+ // Stores whether the previous session crashed.
+ _previousSessionCrashed: null,
+
+/* ........ Global Event Handlers .............. */
+
+ /**
+ * Initialize the component
+ */
+ init: function sss_init() {
+ Services.obs.notifyObservers(null, "sessionstore-init-started", null);
+ StartupPerformance.init();
+
+ // do not need to initialize anything in auto-started private browsing sessions
+ if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ this._initialized = true;
+ gOnceInitializedDeferred.resolve();
+ return;
+ }
+
+ SessionFile.read().then(
+ this._onSessionFileRead.bind(this),
+ console.error
+ );
+ },
+
+ // Wrap a string as a nsISupports
+ _createSupportsString: function ssfi_createSupportsString(aData) {
+ let string = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ string.data = aData;
+ return string;
+ },
+
+ /**
+ * Complete initialization once the Session File has been read
+ *
+ * @param source The Session State string read from disk.
+ * @param parsed The object obtained by parsing |source| as JSON.
+ */
+ _onSessionFileRead: function ({source, parsed, noFilesFound}) {
+ this._initialized = true;
+
+ // Let observers modify the state before it is used
+ let supportsStateString = this._createSupportsString(source);
+ Services.obs.notifyObservers(supportsStateString, "sessionstore-state-read", "");
+ let stateString = supportsStateString.data;
+
+ if (stateString != source) {
+ // The session has been modified by an add-on, reparse.
+ try {
+ this._initialState = JSON.parse(stateString);
+ } catch (ex) {
+ // That's not very good, an add-on has rewritten the initial
+ // state to something that won't parse.
+ warning("Observer rewrote the state to something that won't parse", ex);
+ }
+ } else {
+ // No need to reparse
+ this._initialState = parsed;
+ }
+
+ if (this._initialState == null) {
+ // No valid session found.
+ this._sessionType = Ci.nsISessionStartup.NO_SESSION;
+ Services.obs.notifyObservers(null, "sessionstore-state-finalized", "");
+ gOnceInitializedDeferred.resolve();
+ return;
+ }
+
+ let shouldResumeSessionOnce = Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
+ let shouldResumeSession = shouldResumeSessionOnce ||
+ Services.prefs.getIntPref("browser.startup.page") == BROWSER_STARTUP_RESUME_SESSION;
+
+ // If this is a normal restore then throw away any previous session
+ if (!shouldResumeSessionOnce && this._initialState) {
+ delete this._initialState.lastSessionState;
+ }
+
+ let resumeFromCrash = Services.prefs.getBoolPref("browser.sessionstore.resume_from_crash");
+
+ CrashMonitor.previousCheckpoints.then(checkpoints => {
+ if (checkpoints) {
+ // If the previous session finished writing the final state, we'll
+ // assume there was no crash.
+ this._previousSessionCrashed = !checkpoints["sessionstore-final-state-write-complete"];
+
+ } else {
+ // If the Crash Monitor could not load a checkpoints file it will
+ // provide null. This could occur on the first run after updating to
+ // a version including the Crash Monitor, or if the checkpoints file
+ // was removed, or on first startup with this profile, or after Firefox Reset.
+
+ if (noFilesFound) {
+ // There was no checkpoints file and no sessionstore.js or its backups
+ // so we will assume that this was a fresh profile.
+ this._previousSessionCrashed = false;
+
+ } else {
+ // If this is the first run after an update, sessionstore.js should
+ // still contain the session.state flag to indicate if the session
+ // crashed. If it is not present, we will assume this was not the first
+ // run after update and the checkpoints file was somehow corrupted or
+ // removed by a crash.
+ //
+ // If the session.state flag is present, we will fallback to using it
+ // for crash detection - If the last write of sessionstore.js had it
+ // set to "running", we crashed.
+ let stateFlagPresent = (this._initialState.session &&
+ this._initialState.session.state);
+
+
+ this._previousSessionCrashed = !stateFlagPresent ||
+ (this._initialState.session.state == STATE_RUNNING_STR);
+ }
+ }
+
+ // Report shutdown success via telemetry. Shortcoming here are
+ // being-killed-by-OS-shutdown-logic, shutdown freezing after
+ // session restore was written, etc.
+ Services.telemetry.getHistogramById("SHUTDOWN_OK").add(!this._previousSessionCrashed);
+
+ // set the startup type
+ if (this._previousSessionCrashed && resumeFromCrash)
+ this._sessionType = Ci.nsISessionStartup.RECOVER_SESSION;
+ else if (!this._previousSessionCrashed && shouldResumeSession)
+ this._sessionType = Ci.nsISessionStartup.RESUME_SESSION;
+ else if (this._initialState)
+ this._sessionType = Ci.nsISessionStartup.DEFER_SESSION;
+ else
+ this._initialState = null; // reset the state
+
+ Services.obs.addObserver(this, "sessionstore-windows-restored", true);
+
+ if (this._sessionType != Ci.nsISessionStartup.NO_SESSION)
+ Services.obs.addObserver(this, "browser:purge-session-history", true);
+
+ // We're ready. Notify everyone else.
+ Services.obs.notifyObservers(null, "sessionstore-state-finalized", "");
+ gOnceInitializedDeferred.resolve();
+ });
+ },
+
+ /**
+ * Handle notifications
+ */
+ observe: function sss_observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "app-startup":
+ Services.obs.addObserver(this, "final-ui-startup", true);
+ Services.obs.addObserver(this, "quit-application", true);
+ break;
+ case "final-ui-startup":
+ Services.obs.removeObserver(this, "final-ui-startup");
+ Services.obs.removeObserver(this, "quit-application");
+ this.init();
+ break;
+ case "quit-application":
+ // no reason for initializing at this point (cf. bug 409115)
+ Services.obs.removeObserver(this, "final-ui-startup");
+ Services.obs.removeObserver(this, "quit-application");
+ if (this._sessionType != Ci.nsISessionStartup.NO_SESSION)
+ Services.obs.removeObserver(this, "browser:purge-session-history");
+ break;
+ case "sessionstore-windows-restored":
+ Services.obs.removeObserver(this, "sessionstore-windows-restored");
+ // free _initialState after nsSessionStore is done with it
+ this._initialState = null;
+ break;
+ case "browser:purge-session-history":
+ Services.obs.removeObserver(this, "browser:purge-session-history");
+ // reset all state on sanitization
+ this._sessionType = Ci.nsISessionStartup.NO_SESSION;
+ break;
+ }
+ },
+
+/* ........ Public API ................*/
+
+ get onceInitialized() {
+ return gOnceInitializedDeferred.promise;
+ },
+
+ /**
+ * Get the session state as a jsval
+ */
+ get state() {
+ return this._initialState;
+ },
+
+ /**
+ * Determines whether there is a pending session restore. Should only be
+ * called after initialization has completed.
+ * @returns bool
+ */
+ doRestore: function sss_doRestore() {
+ return this._willRestore();
+ },
+
+ /**
+ * Determines whether automatic session restoration is enabled for this
+ * launch of the browser. This does not include crash restoration. In
+ * particular, if session restore is configured to restore only in case of
+ * crash, this method returns false.
+ * @returns bool
+ */
+ isAutomaticRestoreEnabled: function () {
+ return Services.prefs.getBoolPref("browser.sessionstore.resume_session_once") ||
+ Services.prefs.getIntPref("browser.startup.page") == BROWSER_STARTUP_RESUME_SESSION;
+ },
+
+ /**
+ * Determines whether there is a pending session restore.
+ * @returns bool
+ */
+ _willRestore: function () {
+ return this._sessionType == Ci.nsISessionStartup.RECOVER_SESSION ||
+ this._sessionType == Ci.nsISessionStartup.RESUME_SESSION;
+ },
+
+ /**
+ * Returns whether we will restore a session that ends up replacing the
+ * homepage. The browser uses this to not start loading the homepage if
+ * we're going to stop its load anyway shortly after.
+ *
+ * This is meant to be an optimization for the average case that loading the
+ * session file finishes before we may want to start loading the default
+ * homepage. Should this be called before the session file has been read it
+ * will just return false.
+ *
+ * @returns bool
+ */
+ get willOverrideHomepage() {
+ if (this._initialState && this._willRestore()) {
+ let windows = this._initialState.windows || null;
+ // If there are valid windows with not only pinned tabs, signal that we
+ // will override the default homepage by restoring a session.
+ return windows && windows.some(w => w.tabs.some(t => !t.pinned));
+ }
+ return false;
+ },
+
+ /**
+ * Get the type of pending session store, if any.
+ */
+ get sessionType() {
+ return this._sessionType;
+ },
+
+ /**
+ * Get whether the previous session crashed.
+ */
+ get previousSessionCrashed() {
+ return this._previousSessionCrashed;
+ },
+
+ /* ........ QueryInterface .............. */
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference,
+ Ci.nsISessionStartup]),
+ classID: Components.ID("{ec7a6c20-e081-11da-8ad9-0800200c9a66}")
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SessionStartup]);