browser/components/sessionstore/src/nsSessionStartup.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/components/sessionstore/src/nsSessionStartup.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,354 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +/**
    1.11 + * Session Storage and Restoration
    1.12 + *
    1.13 + * Overview
    1.14 + * This service reads user's session file at startup, and makes a determination
    1.15 + * as to whether the session should be restored. It will restore the session
    1.16 + * under the circumstances described below.  If the auto-start Private Browsing
    1.17 + * mode is active, however, the session is never restored.
    1.18 + *
    1.19 + * Crash Detection
    1.20 + * The CrashMonitor is used to check if the final session state was successfully
    1.21 + * written at shutdown of the last session. If we did not reach
    1.22 + * 'sessionstore-final-state-write-complete', then it's assumed that the browser
    1.23 + * has previously crashed and we should restore the session.
    1.24 + *
    1.25 + * Forced Restarts
    1.26 + * In the event that a restart is required due to application update or extension
    1.27 + * installation, set the browser.sessionstore.resume_session_once pref to true,
    1.28 + * and the session will be restored the next time the browser starts.
    1.29 + *
    1.30 + * Always Resume
    1.31 + * This service will always resume the session if the integer pref
    1.32 + * browser.startup.page is set to 3.
    1.33 + */
    1.34 +
    1.35 +/* :::::::: Constants and Helpers ::::::::::::::: */
    1.36 +
    1.37 +const Cc = Components.classes;
    1.38 +const Ci = Components.interfaces;
    1.39 +const Cr = Components.results;
    1.40 +const Cu = Components.utils;
    1.41 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.42 +Cu.import("resource://gre/modules/Services.jsm");
    1.43 +Cu.import("resource://gre/modules/TelemetryStopwatch.jsm");
    1.44 +Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
    1.45 +Cu.import("resource://gre/modules/Promise.jsm");
    1.46 +
    1.47 +XPCOMUtils.defineLazyModuleGetter(this, "console",
    1.48 +  "resource://gre/modules/devtools/Console.jsm");
    1.49 +XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
    1.50 +  "resource:///modules/sessionstore/SessionFile.jsm");
    1.51 +XPCOMUtils.defineLazyModuleGetter(this, "CrashMonitor",
    1.52 +  "resource://gre/modules/CrashMonitor.jsm");
    1.53 +
    1.54 +const STATE_RUNNING_STR = "running";
    1.55 +
    1.56 +// 'browser.startup.page' preference value to resume the previous session.
    1.57 +const BROWSER_STARTUP_RESUME_SESSION = 3;
    1.58 +
    1.59 +function debug(aMsg) {
    1.60 +  aMsg = ("SessionStartup: " + aMsg).replace(/\S{80}/g, "$&\n");
    1.61 +  Services.console.logStringMessage(aMsg);
    1.62 +}
    1.63 +
    1.64 +let gOnceInitializedDeferred = Promise.defer();
    1.65 +
    1.66 +/* :::::::: The Service ::::::::::::::: */
    1.67 +
    1.68 +function SessionStartup() {
    1.69 +}
    1.70 +
    1.71 +SessionStartup.prototype = {
    1.72 +
    1.73 +  // the state to restore at startup
    1.74 +  _initialState: null,
    1.75 +  _sessionType: Ci.nsISessionStartup.NO_SESSION,
    1.76 +  _initialized: false,
    1.77 +
    1.78 +  // Stores whether the previous session crashed.
    1.79 +  _previousSessionCrashed: null,
    1.80 +
    1.81 +/* ........ Global Event Handlers .............. */
    1.82 +
    1.83 +  /**
    1.84 +   * Initialize the component
    1.85 +   */
    1.86 +  init: function sss_init() {
    1.87 +    Services.obs.notifyObservers(null, "sessionstore-init-started", null);
    1.88 +
    1.89 +    // do not need to initialize anything in auto-started private browsing sessions
    1.90 +    if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
    1.91 +      this._initialized = true;
    1.92 +      gOnceInitializedDeferred.resolve();
    1.93 +      return;
    1.94 +    }
    1.95 +
    1.96 +    SessionFile.read().then(
    1.97 +      this._onSessionFileRead.bind(this),
    1.98 +      console.error
    1.99 +    );
   1.100 +  },
   1.101 +
   1.102 +  // Wrap a string as a nsISupports
   1.103 +  _createSupportsString: function ssfi_createSupportsString(aData) {
   1.104 +    let string = Cc["@mozilla.org/supports-string;1"]
   1.105 +                   .createInstance(Ci.nsISupportsString);
   1.106 +    string.data = aData;
   1.107 +    return string;
   1.108 +  },
   1.109 +
   1.110 +  /**
   1.111 +   * Complete initialization once the Session File has been read
   1.112 +   *
   1.113 +   * @param stateString
   1.114 +   *        string The Session State string read from disk
   1.115 +   */
   1.116 +  _onSessionFileRead: function (stateString) {
   1.117 +    this._initialized = true;
   1.118 +
   1.119 +    // Let observers modify the state before it is used
   1.120 +    let supportsStateString = this._createSupportsString(stateString);
   1.121 +    Services.obs.notifyObservers(supportsStateString, "sessionstore-state-read", "");
   1.122 +    stateString = supportsStateString.data;
   1.123 +
   1.124 +    // No valid session found.
   1.125 +    if (!stateString) {
   1.126 +      this._sessionType = Ci.nsISessionStartup.NO_SESSION;
   1.127 +      Services.obs.notifyObservers(null, "sessionstore-state-finalized", "");
   1.128 +      gOnceInitializedDeferred.resolve();
   1.129 +      return;
   1.130 +    }
   1.131 +
   1.132 +    this._initialState = this._parseStateString(stateString);
   1.133 +
   1.134 +    let shouldResumeSessionOnce = Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
   1.135 +    let shouldResumeSession = shouldResumeSessionOnce ||
   1.136 +          Services.prefs.getIntPref("browser.startup.page") == BROWSER_STARTUP_RESUME_SESSION;
   1.137 +
   1.138 +    // If this is a normal restore then throw away any previous session
   1.139 +    if (!shouldResumeSessionOnce && this._initialState) {
   1.140 +      delete this._initialState.lastSessionState;
   1.141 +    }
   1.142 +
   1.143 +    let resumeFromCrash = Services.prefs.getBoolPref("browser.sessionstore.resume_from_crash");
   1.144 +
   1.145 +    CrashMonitor.previousCheckpoints.then(checkpoints => {
   1.146 +      if (checkpoints) {
   1.147 +        // If the previous session finished writing the final state, we'll
   1.148 +        // assume there was no crash.
   1.149 +        this._previousSessionCrashed = !checkpoints["sessionstore-final-state-write-complete"];
   1.150 +      } else {
   1.151 +        // If the Crash Monitor could not load a checkpoints file it will
   1.152 +        // provide null. This could occur on the first run after updating to
   1.153 +        // a version including the Crash Monitor, or if the checkpoints file
   1.154 +        // was removed.
   1.155 +        //
   1.156 +        // If this is the first run after an update, sessionstore.js should
   1.157 +        // still contain the session.state flag to indicate if the session
   1.158 +        // crashed. If it is not present, we will assume this was not the first
   1.159 +        // run after update and the checkpoints file was somehow corrupted or
   1.160 +        // removed by a crash.
   1.161 +        //
   1.162 +        // If the session.state flag is present, we will fallback to using it
   1.163 +        // for crash detection - If the last write of sessionstore.js had it
   1.164 +        // set to "running", we crashed.
   1.165 +        let stateFlagPresent = (this._initialState &&
   1.166 +                                this._initialState.session &&
   1.167 +                                this._initialState.session.state);
   1.168 +
   1.169 +
   1.170 +        this._previousSessionCrashed = !stateFlagPresent ||
   1.171 +                                       (this._initialState.session.state == STATE_RUNNING_STR);
   1.172 +      }
   1.173 +
   1.174 +      // Report shutdown success via telemetry. Shortcoming here are
   1.175 +      // being-killed-by-OS-shutdown-logic, shutdown freezing after
   1.176 +      // session restore was written, etc.
   1.177 +      Services.telemetry.getHistogramById("SHUTDOWN_OK").add(!this._previousSessionCrashed);
   1.178 +
   1.179 +      // set the startup type
   1.180 +      if (this._previousSessionCrashed && resumeFromCrash)
   1.181 +        this._sessionType = Ci.nsISessionStartup.RECOVER_SESSION;
   1.182 +      else if (!this._previousSessionCrashed && shouldResumeSession)
   1.183 +        this._sessionType = Ci.nsISessionStartup.RESUME_SESSION;
   1.184 +      else if (this._initialState)
   1.185 +        this._sessionType = Ci.nsISessionStartup.DEFER_SESSION;
   1.186 +      else
   1.187 +        this._initialState = null; // reset the state
   1.188 +
   1.189 +      Services.obs.addObserver(this, "sessionstore-windows-restored", true);
   1.190 +
   1.191 +      if (this._sessionType != Ci.nsISessionStartup.NO_SESSION)
   1.192 +        Services.obs.addObserver(this, "browser:purge-session-history", true);
   1.193 +
   1.194 +      // We're ready. Notify everyone else.
   1.195 +      Services.obs.notifyObservers(null, "sessionstore-state-finalized", "");
   1.196 +      gOnceInitializedDeferred.resolve();
   1.197 +    });
   1.198 +  },
   1.199 +
   1.200 +
   1.201 +  /**
   1.202 +   * Convert the Session State string into a state object
   1.203 +   *
   1.204 +   * @param stateString
   1.205 +   *        string The Session State string read from disk
   1.206 +   * @returns {State} a Session State object
   1.207 +   */
   1.208 +  _parseStateString: function (stateString) {
   1.209 +    let state = null;
   1.210 +    let corruptFile = false;
   1.211 +
   1.212 +    try {
   1.213 +      state = JSON.parse(stateString);
   1.214 +    } catch (ex) {
   1.215 +      debug("The session file contained un-parse-able JSON: " + ex);
   1.216 +      corruptFile = true;
   1.217 +    }
   1.218 +    Services.telemetry.getHistogramById("FX_SESSION_RESTORE_CORRUPT_FILE").add(corruptFile);
   1.219 +
   1.220 +    return state;
   1.221 +  },
   1.222 +
   1.223 +  /**
   1.224 +   * Handle notifications
   1.225 +   */
   1.226 +  observe: function sss_observe(aSubject, aTopic, aData) {
   1.227 +    switch (aTopic) {
   1.228 +    case "app-startup":
   1.229 +      Services.obs.addObserver(this, "final-ui-startup", true);
   1.230 +      Services.obs.addObserver(this, "quit-application", true);
   1.231 +      break;
   1.232 +    case "final-ui-startup":
   1.233 +      Services.obs.removeObserver(this, "final-ui-startup");
   1.234 +      Services.obs.removeObserver(this, "quit-application");
   1.235 +      this.init();
   1.236 +      break;
   1.237 +    case "quit-application":
   1.238 +      // no reason for initializing at this point (cf. bug 409115)
   1.239 +      Services.obs.removeObserver(this, "final-ui-startup");
   1.240 +      Services.obs.removeObserver(this, "quit-application");
   1.241 +      if (this._sessionType != Ci.nsISessionStartup.NO_SESSION)
   1.242 +        Services.obs.removeObserver(this, "browser:purge-session-history");
   1.243 +      break;
   1.244 +    case "sessionstore-windows-restored":
   1.245 +      Services.obs.removeObserver(this, "sessionstore-windows-restored");
   1.246 +      // free _initialState after nsSessionStore is done with it
   1.247 +      this._initialState = null;
   1.248 +      break;
   1.249 +    case "browser:purge-session-history":
   1.250 +      Services.obs.removeObserver(this, "browser:purge-session-history");
   1.251 +      // reset all state on sanitization
   1.252 +      this._sessionType = Ci.nsISessionStartup.NO_SESSION;
   1.253 +      break;
   1.254 +    }
   1.255 +  },
   1.256 +
   1.257 +/* ........ Public API ................*/
   1.258 +
   1.259 +  get onceInitialized() {
   1.260 +    return gOnceInitializedDeferred.promise;
   1.261 +  },
   1.262 +
   1.263 +  /**
   1.264 +   * Get the session state as a jsval
   1.265 +   */
   1.266 +  get state() {
   1.267 +    this._ensureInitialized();
   1.268 +    return this._initialState;
   1.269 +  },
   1.270 +
   1.271 +  /**
   1.272 +   * Determines whether there is a pending session restore. Should only be
   1.273 +   * called after initialization has completed.
   1.274 +   * @throws Error if initialization is not complete yet.
   1.275 +   * @returns bool
   1.276 +   */
   1.277 +  doRestore: function sss_doRestore() {
   1.278 +    this._ensureInitialized();
   1.279 +    return this._willRestore();
   1.280 +  },
   1.281 +
   1.282 +  /**
   1.283 +   * Determines whether automatic session restoration is enabled for this
   1.284 +   * launch of the browser. This does not include crash restoration. In
   1.285 +   * particular, if session restore is configured to restore only in case of
   1.286 +   * crash, this method returns false.
   1.287 +   * @returns bool
   1.288 +   */
   1.289 +  isAutomaticRestoreEnabled: function () {
   1.290 +    return Services.prefs.getBoolPref("browser.sessionstore.resume_session_once") ||
   1.291 +           Services.prefs.getIntPref("browser.startup.page") == BROWSER_STARTUP_RESUME_SESSION;
   1.292 +  },
   1.293 +
   1.294 +  /**
   1.295 +   * Determines whether there is a pending session restore.
   1.296 +   * @returns bool
   1.297 +   */
   1.298 +  _willRestore: function () {
   1.299 +    return this._sessionType == Ci.nsISessionStartup.RECOVER_SESSION ||
   1.300 +           this._sessionType == Ci.nsISessionStartup.RESUME_SESSION;
   1.301 +  },
   1.302 +
   1.303 +  /**
   1.304 +   * Returns whether we will restore a session that ends up replacing the
   1.305 +   * homepage. The browser uses this to not start loading the homepage if
   1.306 +   * we're going to stop its load anyway shortly after.
   1.307 +   *
   1.308 +   * This is meant to be an optimization for the average case that loading the
   1.309 +   * session file finishes before we may want to start loading the default
   1.310 +   * homepage. Should this be called before the session file has been read it
   1.311 +   * will just return false.
   1.312 +   *
   1.313 +   * @returns bool
   1.314 +   */
   1.315 +  get willOverrideHomepage() {
   1.316 +    if (this._initialState && this._willRestore()) {
   1.317 +      let windows = this._initialState.windows || null;
   1.318 +      // If there are valid windows with not only pinned tabs, signal that we
   1.319 +      // will override the default homepage by restoring a session.
   1.320 +      return windows && windows.some(w => w.tabs.some(t => !t.pinned));
   1.321 +    }
   1.322 +    return false;
   1.323 +  },
   1.324 +
   1.325 +  /**
   1.326 +   * Get the type of pending session store, if any.
   1.327 +   */
   1.328 +  get sessionType() {
   1.329 +    this._ensureInitialized();
   1.330 +    return this._sessionType;
   1.331 +  },
   1.332 +
   1.333 +  /**
   1.334 +   * Get whether the previous session crashed.
   1.335 +   */
   1.336 +  get previousSessionCrashed() {
   1.337 +    this._ensureInitialized();
   1.338 +    return this._previousSessionCrashed;
   1.339 +  },
   1.340 +
   1.341 +  // Ensure that initialization is complete. If initialization is not complete
   1.342 +  // yet, something is attempting to use the old synchronous initialization,
   1.343 +  // throw an error.
   1.344 +  _ensureInitialized: function sss__ensureInitialized() {
   1.345 +    if (!this._initialized) {
   1.346 +      throw new Error("Session Store is not initialized.");
   1.347 +    }
   1.348 +  },
   1.349 +
   1.350 +  /* ........ QueryInterface .............. */
   1.351 +  QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
   1.352 +                                          Ci.nsISupportsWeakReference,
   1.353 +                                          Ci.nsISessionStartup]),
   1.354 +  classID:          Components.ID("{ec7a6c20-e081-11da-8ad9-0800200c9a66}")
   1.355 +};
   1.356 +
   1.357 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SessionStartup]);

mercurial