browser/components/sessionstore/src/nsSessionStartup.js

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

mercurial