2012-08-10 13:20:25 -07:00
|
|
|
/* 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/. */
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2013-08-09 20:33:07 -07:00
|
|
|
"use strict";
|
|
|
|
|
2007-03-22 10:30:00 -07:00
|
|
|
/**
|
2012-08-10 13:20:25 -07:00
|
|
|
* 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
|
2014-01-17 02:40:18 -08:00
|
|
|
* 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.
|
2012-08-10 13:20:25 -07:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
/* :::::::: Constants and Helpers ::::::::::::::: */
|
|
|
|
|
|
|
|
const Cc = Components.classes;
|
|
|
|
const Ci = Components.interfaces;
|
|
|
|
const Cr = Components.results;
|
2010-01-22 12:21:41 -08:00
|
|
|
const Cu = Components.utils;
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
2010-03-19 15:43:01 -07:00
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
2012-03-16 09:21:19 -07:00
|
|
|
Cu.import("resource://gre/modules/TelemetryStopwatch.jsm");
|
2012-10-24 13:21:33 -07:00
|
|
|
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
|
2013-07-25 12:50:15 -07:00
|
|
|
Cu.import("resource://gre/modules/Promise.jsm");
|
2012-12-19 17:04:26 -08:00
|
|
|
|
2014-01-06 12:27:25 -08:00
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
|
|
|
"resource://gre/modules/devtools/Console.jsm");
|
2013-10-25 02:52:42 -07:00
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
|
|
|
|
"resource:///modules/sessionstore/SessionFile.jsm");
|
2014-01-17 02:40:18 -08:00
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "CrashMonitor",
|
|
|
|
"resource://gre/modules/CrashMonitor.jsm");
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
const STATE_RUNNING_STR = "running";
|
|
|
|
|
2013-11-19 11:57:46 -08:00
|
|
|
// 'browser.startup.page' preference value to resume the previous session.
|
|
|
|
const BROWSER_STARTUP_RESUME_SESSION = 3;
|
|
|
|
|
2007-03-22 10:30:00 -07:00
|
|
|
function debug(aMsg) {
|
|
|
|
aMsg = ("SessionStartup: " + aMsg).replace(/\S{80}/g, "$&\n");
|
2010-03-19 15:43:01 -07:00
|
|
|
Services.console.logStringMessage(aMsg);
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
|
2012-12-19 17:04:26 -08:00
|
|
|
let gOnceInitializedDeferred = Promise.defer();
|
|
|
|
|
2007-03-22 10:30:00 -07:00
|
|
|
/* :::::::: The Service ::::::::::::::: */
|
|
|
|
|
|
|
|
function SessionStartup() {
|
|
|
|
}
|
|
|
|
|
|
|
|
SessionStartup.prototype = {
|
|
|
|
|
|
|
|
// the state to restore at startup
|
2011-07-15 09:42:21 -07:00
|
|
|
_initialState: null,
|
2008-02-14 13:53:33 -08:00
|
|
|
_sessionType: Ci.nsISessionStartup.NO_SESSION,
|
2012-12-19 17:04:26 -08:00
|
|
|
_initialized: false,
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2014-01-17 02:40:18 -08:00
|
|
|
// Stores whether the previous session crashed.
|
|
|
|
_previousSessionCrashed: null,
|
|
|
|
|
2007-03-22 10:30:00 -07:00
|
|
|
/* ........ Global Event Handlers .............. */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize the component
|
|
|
|
*/
|
|
|
|
init: function sss_init() {
|
2014-04-04 05:34:24 -07:00
|
|
|
Services.obs.notifyObservers(null, "sessionstore-init-started", null);
|
|
|
|
|
2009-05-01 22:17:22 -07:00
|
|
|
// do not need to initialize anything in auto-started private browsing sessions
|
2013-02-22 22:45:37 -08:00
|
|
|
if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
|
|
|
|
this._initialized = true;
|
|
|
|
gOnceInitializedDeferred.resolve();
|
2012-12-20 14:50:35 -08:00
|
|
|
return;
|
2013-02-22 22:45:37 -08:00
|
|
|
}
|
2012-12-20 14:50:35 -08:00
|
|
|
|
2013-10-25 02:52:42 -07:00
|
|
|
SessionFile.read().then(
|
2013-09-17 08:37:28 -07:00
|
|
|
this._onSessionFileRead.bind(this),
|
2014-01-06 12:27:25 -08:00
|
|
|
console.error
|
2012-12-19 17:04:26 -08:00
|
|
|
);
|
|
|
|
},
|
2009-05-01 22:17:22 -07:00
|
|
|
|
2012-12-19 17:04:26 -08:00
|
|
|
// 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;
|
|
|
|
},
|
2010-06-09 13:55:45 -07:00
|
|
|
|
2014-01-17 02:40:18 -08:00
|
|
|
/**
|
|
|
|
* Complete initialization once the Session File has been read
|
|
|
|
*
|
|
|
|
* @param stateString
|
|
|
|
* string The Session State string read from disk
|
|
|
|
*/
|
|
|
|
_onSessionFileRead: function (stateString) {
|
|
|
|
this._initialized = true;
|
2012-12-15 09:08:04 -08:00
|
|
|
|
2014-01-17 02:40:18 -08:00
|
|
|
// Let observers modify the state before it is used
|
|
|
|
let supportsStateString = this._createSupportsString(stateString);
|
|
|
|
Services.obs.notifyObservers(supportsStateString, "sessionstore-state-read", "");
|
|
|
|
stateString = supportsStateString.data;
|
2012-12-15 07:44:07 -08:00
|
|
|
|
2014-01-17 02:40:18 -08:00
|
|
|
// No valid session found.
|
|
|
|
if (!stateString) {
|
|
|
|
this._sessionType = Ci.nsISessionStartup.NO_SESSION;
|
|
|
|
Services.obs.notifyObservers(null, "sessionstore-state-finalized", "");
|
|
|
|
gOnceInitializedDeferred.resolve();
|
|
|
|
return;
|
|
|
|
}
|
2012-12-19 17:04:26 -08:00
|
|
|
|
2014-01-17 02:40:18 -08:00
|
|
|
this._initialState = this._parseStateString(stateString);
|
|
|
|
|
|
|
|
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)
|
|
|
|
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.
|
|
|
|
//
|
|
|
|
// 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 &&
|
|
|
|
this._initialState.session &&
|
|
|
|
this._initialState.session.state);
|
|
|
|
|
|
|
|
|
|
|
|
this._previousSessionCrashed = !stateFlagPresent ||
|
|
|
|
(this._initialState.session.state == STATE_RUNNING_STR);
|
2012-12-19 17:04:26 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Report shutdown success via telemetry. Shortcoming here are
|
|
|
|
// being-killed-by-OS-shutdown-logic, shutdown freezing after
|
|
|
|
// session restore was written, etc.
|
2014-01-17 02:40:18 -08:00
|
|
|
Services.telemetry.getHistogramById("SHUTDOWN_OK").add(!this._previousSessionCrashed);
|
2012-12-19 17:04:26 -08:00
|
|
|
|
|
|
|
// set the startup type
|
2014-01-17 02:40:18 -08:00
|
|
|
if (this._previousSessionCrashed && resumeFromCrash)
|
2012-12-19 17:04:26 -08:00
|
|
|
this._sessionType = Ci.nsISessionStartup.RECOVER_SESSION;
|
2014-01-17 02:40:18 -08:00
|
|
|
else if (!this._previousSessionCrashed && shouldResumeSession)
|
2012-12-19 17:04:26 -08:00
|
|
|
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();
|
2014-01-17 02:40:18 -08:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert the Session State string into a state object
|
|
|
|
*
|
|
|
|
* @param stateString
|
|
|
|
* string The Session State string read from disk
|
|
|
|
* @returns {State} a Session State object
|
|
|
|
*/
|
|
|
|
_parseStateString: function (stateString) {
|
|
|
|
let state = null;
|
|
|
|
let corruptFile = false;
|
|
|
|
|
|
|
|
try {
|
|
|
|
state = JSON.parse(stateString);
|
|
|
|
} catch (ex) {
|
|
|
|
debug("The session file contained un-parse-able JSON: " + ex);
|
|
|
|
corruptFile = true;
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
2014-01-17 02:40:18 -08:00
|
|
|
Services.telemetry.getHistogramById("FX_SESSION_RESTORE_CORRUPT_FILE").add(corruptFile);
|
|
|
|
|
|
|
|
return state;
|
2007-03-22 10:30:00 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle notifications
|
|
|
|
*/
|
|
|
|
observe: function sss_observe(aSubject, aTopic, aData) {
|
|
|
|
switch (aTopic) {
|
2012-05-25 14:39:22 -07:00
|
|
|
case "app-startup":
|
2010-03-19 15:43:01 -07:00
|
|
|
Services.obs.addObserver(this, "final-ui-startup", true);
|
|
|
|
Services.obs.addObserver(this, "quit-application", true);
|
2007-03-22 10:30:00 -07:00
|
|
|
break;
|
2012-05-25 14:39:22 -07:00
|
|
|
case "final-ui-startup":
|
2010-03-19 15:43:01 -07:00
|
|
|
Services.obs.removeObserver(this, "final-ui-startup");
|
|
|
|
Services.obs.removeObserver(this, "quit-application");
|
2007-03-22 10:30:00 -07:00
|
|
|
this.init();
|
|
|
|
break;
|
2008-08-19 10:08:30 -07:00
|
|
|
case "quit-application":
|
2008-09-05 03:05:34 -07:00
|
|
|
// no reason for initializing at this point (cf. bug 409115)
|
2010-03-19 15:43:01 -07:00
|
|
|
Services.obs.removeObserver(this, "final-ui-startup");
|
|
|
|
Services.obs.removeObserver(this, "quit-application");
|
2011-10-25 10:19:28 -07:00
|
|
|
if (this._sessionType != Ci.nsISessionStartup.NO_SESSION)
|
|
|
|
Services.obs.removeObserver(this, "browser:purge-session-history");
|
2008-08-19 10:08:30 -07:00
|
|
|
break;
|
2011-01-20 13:43:32 -08:00
|
|
|
case "sessionstore-windows-restored":
|
|
|
|
Services.obs.removeObserver(this, "sessionstore-windows-restored");
|
2011-07-15 09:42:21 -07:00
|
|
|
// free _initialState after nsSessionStore is done with it
|
|
|
|
this._initialState = null;
|
2011-10-25 10:19:28 -07:00
|
|
|
break;
|
|
|
|
case "browser:purge-session-history":
|
|
|
|
Services.obs.removeObserver(this, "browser:purge-session-history");
|
|
|
|
// reset all state on sanitization
|
2008-10-11 11:58:21 -07:00
|
|
|
this._sessionType = Ci.nsISessionStartup.NO_SESSION;
|
|
|
|
break;
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/* ........ Public API ................*/
|
|
|
|
|
2012-12-19 17:04:26 -08:00
|
|
|
get onceInitialized() {
|
|
|
|
return gOnceInitializedDeferred.promise;
|
|
|
|
},
|
|
|
|
|
2007-03-22 10:30:00 -07:00
|
|
|
/**
|
2011-07-25 16:29:57 -07:00
|
|
|
* Get the session state as a jsval
|
2007-03-22 10:30:00 -07:00
|
|
|
*/
|
|
|
|
get state() {
|
2012-12-19 17:04:26 -08:00
|
|
|
this._ensureInitialized();
|
2011-07-15 09:42:21 -07:00
|
|
|
return this._initialState;
|
2007-03-22 10:30:00 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2013-11-19 11:57:46 -08:00
|
|
|
* Determines whether there is a pending session restore. Should only be
|
|
|
|
* called after initialization has completed.
|
|
|
|
* @throws Error if initialization is not complete yet.
|
2007-03-22 10:30:00 -07:00
|
|
|
* @returns bool
|
|
|
|
*/
|
|
|
|
doRestore: function sss_doRestore() {
|
2012-12-19 17:04:26 -08:00
|
|
|
this._ensureInitialized();
|
2013-07-26 04:16:29 -07:00
|
|
|
return this._willRestore();
|
|
|
|
},
|
|
|
|
|
2013-11-19 11:57:46 -08:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
},
|
|
|
|
|
2013-07-26 04:16:29 -07:00
|
|
|
/**
|
|
|
|
* Determines whether there is a pending session restore.
|
|
|
|
* @returns bool
|
|
|
|
*/
|
|
|
|
_willRestore: function () {
|
2010-09-10 10:57:28 -07:00
|
|
|
return this._sessionType == Ci.nsISessionStartup.RECOVER_SESSION ||
|
|
|
|
this._sessionType == Ci.nsISessionStartup.RESUME_SESSION;
|
2008-02-14 13:53:33 -08:00
|
|
|
},
|
|
|
|
|
2013-07-26 04:16:29 -07:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
},
|
|
|
|
|
2008-02-14 13:53:33 -08:00
|
|
|
/**
|
|
|
|
* Get the type of pending session store, if any.
|
|
|
|
*/
|
|
|
|
get sessionType() {
|
2012-12-19 17:04:26 -08:00
|
|
|
this._ensureInitialized();
|
2008-02-14 13:53:33 -08:00
|
|
|
return this._sessionType;
|
2007-03-22 10:30:00 -07:00
|
|
|
},
|
|
|
|
|
2014-01-17 02:40:18 -08:00
|
|
|
/**
|
|
|
|
* Get whether the previous session crashed.
|
|
|
|
*/
|
|
|
|
get previousSessionCrashed() {
|
|
|
|
this._ensureInitialized();
|
|
|
|
return this._previousSessionCrashed;
|
|
|
|
},
|
|
|
|
|
2013-11-19 11:57:46 -08:00
|
|
|
// Ensure that initialization is complete. If initialization is not complete
|
|
|
|
// yet, something is attempting to use the old synchronous initialization,
|
|
|
|
// throw an error.
|
2012-12-19 17:04:26 -08:00
|
|
|
_ensureInitialized: function sss__ensureInitialized() {
|
2013-11-19 11:57:46 -08:00
|
|
|
if (!this._initialized) {
|
|
|
|
throw new Error("Session Store is not initialized.");
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2007-09-18 09:36:17 -07:00
|
|
|
/* ........ QueryInterface .............. */
|
|
|
|
QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
|
|
|
|
Ci.nsISupportsWeakReference,
|
|
|
|
Ci.nsISessionStartup]),
|
2013-02-10 14:08:32 -08:00
|
|
|
classID: Components.ID("{ec7a6c20-e081-11da-8ad9-0800200c9a66}")
|
2007-03-22 10:30:00 -07:00
|
|
|
};
|
|
|
|
|
2012-10-31 09:13:28 -07:00
|
|
|
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SessionStartup]);
|