browser/components/sessionstore/src/nsSessionStartup.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 /**
michael@0 8 * Session Storage and Restoration
michael@0 9 *
michael@0 10 * Overview
michael@0 11 * This service reads user's session file at startup, and makes a determination
michael@0 12 * as to whether the session should be restored. It will restore the session
michael@0 13 * under the circumstances described below. If the auto-start Private Browsing
michael@0 14 * mode is active, however, the session is never restored.
michael@0 15 *
michael@0 16 * Crash Detection
michael@0 17 * The CrashMonitor is used to check if the final session state was successfully
michael@0 18 * written at shutdown of the last session. If we did not reach
michael@0 19 * 'sessionstore-final-state-write-complete', then it's assumed that the browser
michael@0 20 * has previously crashed and we should restore the session.
michael@0 21 *
michael@0 22 * Forced Restarts
michael@0 23 * In the event that a restart is required due to application update or extension
michael@0 24 * installation, set the browser.sessionstore.resume_session_once pref to true,
michael@0 25 * and the session will be restored the next time the browser starts.
michael@0 26 *
michael@0 27 * Always Resume
michael@0 28 * This service will always resume the session if the integer pref
michael@0 29 * browser.startup.page is set to 3.
michael@0 30 */
michael@0 31
michael@0 32 /* :::::::: Constants and Helpers ::::::::::::::: */
michael@0 33
michael@0 34 const Cc = Components.classes;
michael@0 35 const Ci = Components.interfaces;
michael@0 36 const Cr = Components.results;
michael@0 37 const Cu = Components.utils;
michael@0 38 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 39 Cu.import("resource://gre/modules/Services.jsm");
michael@0 40 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm");
michael@0 41 Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
michael@0 42 Cu.import("resource://gre/modules/Promise.jsm");
michael@0 43
michael@0 44 XPCOMUtils.defineLazyModuleGetter(this, "console",
michael@0 45 "resource://gre/modules/devtools/Console.jsm");
michael@0 46 XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
michael@0 47 "resource:///modules/sessionstore/SessionFile.jsm");
michael@0 48 XPCOMUtils.defineLazyModuleGetter(this, "CrashMonitor",
michael@0 49 "resource://gre/modules/CrashMonitor.jsm");
michael@0 50
michael@0 51 const STATE_RUNNING_STR = "running";
michael@0 52
michael@0 53 // 'browser.startup.page' preference value to resume the previous session.
michael@0 54 const BROWSER_STARTUP_RESUME_SESSION = 3;
michael@0 55
michael@0 56 function debug(aMsg) {
michael@0 57 aMsg = ("SessionStartup: " + aMsg).replace(/\S{80}/g, "$&\n");
michael@0 58 Services.console.logStringMessage(aMsg);
michael@0 59 }
michael@0 60
michael@0 61 let gOnceInitializedDeferred = Promise.defer();
michael@0 62
michael@0 63 /* :::::::: The Service ::::::::::::::: */
michael@0 64
michael@0 65 function SessionStartup() {
michael@0 66 }
michael@0 67
michael@0 68 SessionStartup.prototype = {
michael@0 69
michael@0 70 // the state to restore at startup
michael@0 71 _initialState: null,
michael@0 72 _sessionType: Ci.nsISessionStartup.NO_SESSION,
michael@0 73 _initialized: false,
michael@0 74
michael@0 75 // Stores whether the previous session crashed.
michael@0 76 _previousSessionCrashed: null,
michael@0 77
michael@0 78 /* ........ Global Event Handlers .............. */
michael@0 79
michael@0 80 /**
michael@0 81 * Initialize the component
michael@0 82 */
michael@0 83 init: function sss_init() {
michael@0 84 Services.obs.notifyObservers(null, "sessionstore-init-started", null);
michael@0 85
michael@0 86 // do not need to initialize anything in auto-started private browsing sessions
michael@0 87 if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
michael@0 88 this._initialized = true;
michael@0 89 gOnceInitializedDeferred.resolve();
michael@0 90 return;
michael@0 91 }
michael@0 92
michael@0 93 SessionFile.read().then(
michael@0 94 this._onSessionFileRead.bind(this),
michael@0 95 console.error
michael@0 96 );
michael@0 97 },
michael@0 98
michael@0 99 // Wrap a string as a nsISupports
michael@0 100 _createSupportsString: function ssfi_createSupportsString(aData) {
michael@0 101 let string = Cc["@mozilla.org/supports-string;1"]
michael@0 102 .createInstance(Ci.nsISupportsString);
michael@0 103 string.data = aData;
michael@0 104 return string;
michael@0 105 },
michael@0 106
michael@0 107 /**
michael@0 108 * Complete initialization once the Session File has been read
michael@0 109 *
michael@0 110 * @param stateString
michael@0 111 * string The Session State string read from disk
michael@0 112 */
michael@0 113 _onSessionFileRead: function (stateString) {
michael@0 114 this._initialized = true;
michael@0 115
michael@0 116 // Let observers modify the state before it is used
michael@0 117 let supportsStateString = this._createSupportsString(stateString);
michael@0 118 Services.obs.notifyObservers(supportsStateString, "sessionstore-state-read", "");
michael@0 119 stateString = supportsStateString.data;
michael@0 120
michael@0 121 // No valid session found.
michael@0 122 if (!stateString) {
michael@0 123 this._sessionType = Ci.nsISessionStartup.NO_SESSION;
michael@0 124 Services.obs.notifyObservers(null, "sessionstore-state-finalized", "");
michael@0 125 gOnceInitializedDeferred.resolve();
michael@0 126 return;
michael@0 127 }
michael@0 128
michael@0 129 this._initialState = this._parseStateString(stateString);
michael@0 130
michael@0 131 let shouldResumeSessionOnce = Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
michael@0 132 let shouldResumeSession = shouldResumeSessionOnce ||
michael@0 133 Services.prefs.getIntPref("browser.startup.page") == BROWSER_STARTUP_RESUME_SESSION;
michael@0 134
michael@0 135 // If this is a normal restore then throw away any previous session
michael@0 136 if (!shouldResumeSessionOnce && this._initialState) {
michael@0 137 delete this._initialState.lastSessionState;
michael@0 138 }
michael@0 139
michael@0 140 let resumeFromCrash = Services.prefs.getBoolPref("browser.sessionstore.resume_from_crash");
michael@0 141
michael@0 142 CrashMonitor.previousCheckpoints.then(checkpoints => {
michael@0 143 if (checkpoints) {
michael@0 144 // If the previous session finished writing the final state, we'll
michael@0 145 // assume there was no crash.
michael@0 146 this._previousSessionCrashed = !checkpoints["sessionstore-final-state-write-complete"];
michael@0 147 } else {
michael@0 148 // If the Crash Monitor could not load a checkpoints file it will
michael@0 149 // provide null. This could occur on the first run after updating to
michael@0 150 // a version including the Crash Monitor, or if the checkpoints file
michael@0 151 // was removed.
michael@0 152 //
michael@0 153 // If this is the first run after an update, sessionstore.js should
michael@0 154 // still contain the session.state flag to indicate if the session
michael@0 155 // crashed. If it is not present, we will assume this was not the first
michael@0 156 // run after update and the checkpoints file was somehow corrupted or
michael@0 157 // removed by a crash.
michael@0 158 //
michael@0 159 // If the session.state flag is present, we will fallback to using it
michael@0 160 // for crash detection - If the last write of sessionstore.js had it
michael@0 161 // set to "running", we crashed.
michael@0 162 let stateFlagPresent = (this._initialState &&
michael@0 163 this._initialState.session &&
michael@0 164 this._initialState.session.state);
michael@0 165
michael@0 166
michael@0 167 this._previousSessionCrashed = !stateFlagPresent ||
michael@0 168 (this._initialState.session.state == STATE_RUNNING_STR);
michael@0 169 }
michael@0 170
michael@0 171 // Report shutdown success via telemetry. Shortcoming here are
michael@0 172 // being-killed-by-OS-shutdown-logic, shutdown freezing after
michael@0 173 // session restore was written, etc.
michael@0 174 Services.telemetry.getHistogramById("SHUTDOWN_OK").add(!this._previousSessionCrashed);
michael@0 175
michael@0 176 // set the startup type
michael@0 177 if (this._previousSessionCrashed && resumeFromCrash)
michael@0 178 this._sessionType = Ci.nsISessionStartup.RECOVER_SESSION;
michael@0 179 else if (!this._previousSessionCrashed && shouldResumeSession)
michael@0 180 this._sessionType = Ci.nsISessionStartup.RESUME_SESSION;
michael@0 181 else if (this._initialState)
michael@0 182 this._sessionType = Ci.nsISessionStartup.DEFER_SESSION;
michael@0 183 else
michael@0 184 this._initialState = null; // reset the state
michael@0 185
michael@0 186 Services.obs.addObserver(this, "sessionstore-windows-restored", true);
michael@0 187
michael@0 188 if (this._sessionType != Ci.nsISessionStartup.NO_SESSION)
michael@0 189 Services.obs.addObserver(this, "browser:purge-session-history", true);
michael@0 190
michael@0 191 // We're ready. Notify everyone else.
michael@0 192 Services.obs.notifyObservers(null, "sessionstore-state-finalized", "");
michael@0 193 gOnceInitializedDeferred.resolve();
michael@0 194 });
michael@0 195 },
michael@0 196
michael@0 197
michael@0 198 /**
michael@0 199 * Convert the Session State string into a state object
michael@0 200 *
michael@0 201 * @param stateString
michael@0 202 * string The Session State string read from disk
michael@0 203 * @returns {State} a Session State object
michael@0 204 */
michael@0 205 _parseStateString: function (stateString) {
michael@0 206 let state = null;
michael@0 207 let corruptFile = false;
michael@0 208
michael@0 209 try {
michael@0 210 state = JSON.parse(stateString);
michael@0 211 } catch (ex) {
michael@0 212 debug("The session file contained un-parse-able JSON: " + ex);
michael@0 213 corruptFile = true;
michael@0 214 }
michael@0 215 Services.telemetry.getHistogramById("FX_SESSION_RESTORE_CORRUPT_FILE").add(corruptFile);
michael@0 216
michael@0 217 return state;
michael@0 218 },
michael@0 219
michael@0 220 /**
michael@0 221 * Handle notifications
michael@0 222 */
michael@0 223 observe: function sss_observe(aSubject, aTopic, aData) {
michael@0 224 switch (aTopic) {
michael@0 225 case "app-startup":
michael@0 226 Services.obs.addObserver(this, "final-ui-startup", true);
michael@0 227 Services.obs.addObserver(this, "quit-application", true);
michael@0 228 break;
michael@0 229 case "final-ui-startup":
michael@0 230 Services.obs.removeObserver(this, "final-ui-startup");
michael@0 231 Services.obs.removeObserver(this, "quit-application");
michael@0 232 this.init();
michael@0 233 break;
michael@0 234 case "quit-application":
michael@0 235 // no reason for initializing at this point (cf. bug 409115)
michael@0 236 Services.obs.removeObserver(this, "final-ui-startup");
michael@0 237 Services.obs.removeObserver(this, "quit-application");
michael@0 238 if (this._sessionType != Ci.nsISessionStartup.NO_SESSION)
michael@0 239 Services.obs.removeObserver(this, "browser:purge-session-history");
michael@0 240 break;
michael@0 241 case "sessionstore-windows-restored":
michael@0 242 Services.obs.removeObserver(this, "sessionstore-windows-restored");
michael@0 243 // free _initialState after nsSessionStore is done with it
michael@0 244 this._initialState = null;
michael@0 245 break;
michael@0 246 case "browser:purge-session-history":
michael@0 247 Services.obs.removeObserver(this, "browser:purge-session-history");
michael@0 248 // reset all state on sanitization
michael@0 249 this._sessionType = Ci.nsISessionStartup.NO_SESSION;
michael@0 250 break;
michael@0 251 }
michael@0 252 },
michael@0 253
michael@0 254 /* ........ Public API ................*/
michael@0 255
michael@0 256 get onceInitialized() {
michael@0 257 return gOnceInitializedDeferred.promise;
michael@0 258 },
michael@0 259
michael@0 260 /**
michael@0 261 * Get the session state as a jsval
michael@0 262 */
michael@0 263 get state() {
michael@0 264 this._ensureInitialized();
michael@0 265 return this._initialState;
michael@0 266 },
michael@0 267
michael@0 268 /**
michael@0 269 * Determines whether there is a pending session restore. Should only be
michael@0 270 * called after initialization has completed.
michael@0 271 * @throws Error if initialization is not complete yet.
michael@0 272 * @returns bool
michael@0 273 */
michael@0 274 doRestore: function sss_doRestore() {
michael@0 275 this._ensureInitialized();
michael@0 276 return this._willRestore();
michael@0 277 },
michael@0 278
michael@0 279 /**
michael@0 280 * Determines whether automatic session restoration is enabled for this
michael@0 281 * launch of the browser. This does not include crash restoration. In
michael@0 282 * particular, if session restore is configured to restore only in case of
michael@0 283 * crash, this method returns false.
michael@0 284 * @returns bool
michael@0 285 */
michael@0 286 isAutomaticRestoreEnabled: function () {
michael@0 287 return Services.prefs.getBoolPref("browser.sessionstore.resume_session_once") ||
michael@0 288 Services.prefs.getIntPref("browser.startup.page") == BROWSER_STARTUP_RESUME_SESSION;
michael@0 289 },
michael@0 290
michael@0 291 /**
michael@0 292 * Determines whether there is a pending session restore.
michael@0 293 * @returns bool
michael@0 294 */
michael@0 295 _willRestore: function () {
michael@0 296 return this._sessionType == Ci.nsISessionStartup.RECOVER_SESSION ||
michael@0 297 this._sessionType == Ci.nsISessionStartup.RESUME_SESSION;
michael@0 298 },
michael@0 299
michael@0 300 /**
michael@0 301 * Returns whether we will restore a session that ends up replacing the
michael@0 302 * homepage. The browser uses this to not start loading the homepage if
michael@0 303 * we're going to stop its load anyway shortly after.
michael@0 304 *
michael@0 305 * This is meant to be an optimization for the average case that loading the
michael@0 306 * session file finishes before we may want to start loading the default
michael@0 307 * homepage. Should this be called before the session file has been read it
michael@0 308 * will just return false.
michael@0 309 *
michael@0 310 * @returns bool
michael@0 311 */
michael@0 312 get willOverrideHomepage() {
michael@0 313 if (this._initialState && this._willRestore()) {
michael@0 314 let windows = this._initialState.windows || null;
michael@0 315 // If there are valid windows with not only pinned tabs, signal that we
michael@0 316 // will override the default homepage by restoring a session.
michael@0 317 return windows && windows.some(w => w.tabs.some(t => !t.pinned));
michael@0 318 }
michael@0 319 return false;
michael@0 320 },
michael@0 321
michael@0 322 /**
michael@0 323 * Get the type of pending session store, if any.
michael@0 324 */
michael@0 325 get sessionType() {
michael@0 326 this._ensureInitialized();
michael@0 327 return this._sessionType;
michael@0 328 },
michael@0 329
michael@0 330 /**
michael@0 331 * Get whether the previous session crashed.
michael@0 332 */
michael@0 333 get previousSessionCrashed() {
michael@0 334 this._ensureInitialized();
michael@0 335 return this._previousSessionCrashed;
michael@0 336 },
michael@0 337
michael@0 338 // Ensure that initialization is complete. If initialization is not complete
michael@0 339 // yet, something is attempting to use the old synchronous initialization,
michael@0 340 // throw an error.
michael@0 341 _ensureInitialized: function sss__ensureInitialized() {
michael@0 342 if (!this._initialized) {
michael@0 343 throw new Error("Session Store is not initialized.");
michael@0 344 }
michael@0 345 },
michael@0 346
michael@0 347 /* ........ QueryInterface .............. */
michael@0 348 QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
michael@0 349 Ci.nsISupportsWeakReference,
michael@0 350 Ci.nsISessionStartup]),
michael@0 351 classID: Components.ID("{ec7a6c20-e081-11da-8ad9-0800200c9a66}")
michael@0 352 };
michael@0 353
michael@0 354 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SessionStartup]);

mercurial