forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			350 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			350 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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/devtools/Console.jsm");
 | |
| XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
 | |
|   "resource:///modules/sessionstore/SessionFile.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);
 | |
| }
 | |
| 
 | |
| let 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);
 | |
| 
 | |
|     // 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]);
 | 
