Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 this.EXPORTED_SYMBOLS = ["SessionSaver"];
9 const Cu = Components.utils;
10 const Cc = Components.classes;
11 const Ci = Components.interfaces;
13 Cu.import("resource://gre/modules/Timer.jsm", this);
14 Cu.import("resource://gre/modules/Services.jsm", this);
15 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
16 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
18 XPCOMUtils.defineLazyModuleGetter(this, "console",
19 "resource://gre/modules/devtools/Console.jsm");
20 XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
21 "resource:///modules/sessionstore/PrivacyFilter.jsm");
22 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
23 "resource:///modules/sessionstore/SessionStore.jsm");
24 XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
25 "resource:///modules/sessionstore/SessionFile.jsm");
26 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
27 "resource://gre/modules/PrivateBrowsingUtils.jsm");
29 // Minimal interval between two save operations (in milliseconds).
30 XPCOMUtils.defineLazyGetter(this, "gInterval", function () {
31 const PREF = "browser.sessionstore.interval";
33 // Observer that updates the cached value when the preference changes.
34 Services.prefs.addObserver(PREF, () => {
35 this.gInterval = Services.prefs.getIntPref(PREF);
37 // Cancel any pending runs and call runDelayed() with
38 // zero to apply the newly configured interval.
39 SessionSaverInternal.cancel();
40 SessionSaverInternal.runDelayed(0);
41 }, false);
43 return Services.prefs.getIntPref(PREF);
44 });
46 // Notify observers about a given topic with a given subject.
47 function notify(subject, topic) {
48 Services.obs.notifyObservers(subject, topic, "");
49 }
51 // TelemetryStopwatch helper functions.
52 function stopWatch(method) {
53 return function (...histograms) {
54 for (let hist of histograms) {
55 TelemetryStopwatch[method]("FX_SESSION_RESTORE_" + hist);
56 }
57 };
58 }
60 let stopWatchStart = stopWatch("start");
61 let stopWatchCancel = stopWatch("cancel");
62 let stopWatchFinish = stopWatch("finish");
64 /**
65 * The external API implemented by the SessionSaver module.
66 */
67 this.SessionSaver = Object.freeze({
68 /**
69 * Immediately saves the current session to disk.
70 */
71 run: function () {
72 return SessionSaverInternal.run();
73 },
75 /**
76 * Saves the current session to disk delayed by a given amount of time. Should
77 * another delayed run be scheduled already, we will ignore the given delay
78 * and state saving may occur a little earlier.
79 */
80 runDelayed: function () {
81 SessionSaverInternal.runDelayed();
82 },
84 /**
85 * Sets the last save time to the current time. This will cause us to wait for
86 * at least the configured interval when runDelayed() is called next.
87 */
88 updateLastSaveTime: function () {
89 SessionSaverInternal.updateLastSaveTime();
90 },
92 /**
93 * Sets the last save time to zero. This will cause us to
94 * immediately save the next time runDelayed() is called.
95 */
96 clearLastSaveTime: function () {
97 SessionSaverInternal.clearLastSaveTime();
98 },
100 /**
101 * Cancels all pending session saves.
102 */
103 cancel: function () {
104 SessionSaverInternal.cancel();
105 }
106 });
108 /**
109 * The internal API.
110 */
111 let SessionSaverInternal = {
112 /**
113 * The timeout ID referencing an active timer for a delayed save. When no
114 * save is pending, this is null.
115 */
116 _timeoutID: null,
118 /**
119 * A timestamp that keeps track of when we saved the session last. We will
120 * this to determine the correct interval between delayed saves to not deceed
121 * the configured session write interval.
122 */
123 _lastSaveTime: 0,
125 /**
126 * Immediately saves the current session to disk.
127 */
128 run: function () {
129 return this._saveState(true /* force-update all windows */);
130 },
132 /**
133 * Saves the current session to disk delayed by a given amount of time. Should
134 * another delayed run be scheduled already, we will ignore the given delay
135 * and state saving may occur a little earlier.
136 *
137 * @param delay (optional)
138 * The minimum delay in milliseconds to wait for until we collect and
139 * save the current session.
140 */
141 runDelayed: function (delay = 2000) {
142 // Bail out if there's a pending run.
143 if (this._timeoutID) {
144 return;
145 }
147 // Interval until the next disk operation is allowed.
148 delay = Math.max(this._lastSaveTime + gInterval - Date.now(), delay, 0);
150 // Schedule a state save.
151 this._timeoutID = setTimeout(() => this._saveStateAsync(), delay);
152 },
154 /**
155 * Sets the last save time to the current time. This will cause us to wait for
156 * at least the configured interval when runDelayed() is called next.
157 */
158 updateLastSaveTime: function () {
159 this._lastSaveTime = Date.now();
160 },
162 /**
163 * Sets the last save time to zero. This will cause us to
164 * immediately save the next time runDelayed() is called.
165 */
166 clearLastSaveTime: function () {
167 this._lastSaveTime = 0;
168 },
170 /**
171 * Cancels all pending session saves.
172 */
173 cancel: function () {
174 clearTimeout(this._timeoutID);
175 this._timeoutID = null;
176 },
178 /**
179 * Saves the current session state. Collects data and writes to disk.
180 *
181 * @param forceUpdateAllWindows (optional)
182 * Forces us to recollect data for all windows and will bypass and
183 * update the corresponding caches.
184 */
185 _saveState: function (forceUpdateAllWindows = false) {
186 // Cancel any pending timeouts.
187 this.cancel();
189 if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
190 // Don't save (or even collect) anything in permanent private
191 // browsing mode
193 this.updateLastSaveTime();
194 return Promise.resolve();
195 }
197 stopWatchStart("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
198 let state = SessionStore.getCurrentState(forceUpdateAllWindows);
199 PrivacyFilter.filterPrivateWindowsAndTabs(state);
201 // Make sure that we keep the previous session if we started with a single
202 // private window and no non-private windows have been opened, yet.
203 if (state.deferredInitialState) {
204 state.windows = state.deferredInitialState.windows || [];
205 delete state.deferredInitialState;
206 }
208 #ifndef XP_MACOSX
209 // We want to restore closed windows that are marked with _shouldRestore.
210 // We're doing this here because we want to control this only when saving
211 // the file.
212 while (state._closedWindows.length) {
213 let i = state._closedWindows.length - 1;
215 if (!state._closedWindows[i]._shouldRestore) {
216 // We only need to go until _shouldRestore
217 // is falsy since we're going in reverse.
218 break;
219 }
221 delete state._closedWindows[i]._shouldRestore;
222 state.windows.unshift(state._closedWindows.pop());
223 }
224 #endif
226 stopWatchFinish("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
227 return this._writeState(state);
228 },
230 /**
231 * Saves the current session state. Collects data asynchronously and calls
232 * _saveState() to collect data again (with a cache hit rate of hopefully
233 * 100%) and write to disk afterwards.
234 */
235 _saveStateAsync: function () {
236 // Allow scheduling delayed saves again.
237 this._timeoutID = null;
239 // Write to disk.
240 this._saveState();
241 },
243 /**
244 * Write the given state object to disk.
245 */
246 _writeState: function (state) {
247 // Inform observers
248 notify(null, "sessionstore-state-write");
250 stopWatchStart("SERIALIZE_DATA_MS", "SERIALIZE_DATA_LONGEST_OP_MS", "WRITE_STATE_LONGEST_OP_MS");
251 let data = JSON.stringify(state);
252 stopWatchFinish("SERIALIZE_DATA_MS", "SERIALIZE_DATA_LONGEST_OP_MS");
254 // We update the time stamp before writing so that we don't write again
255 // too soon, if saving is requested before the write completes. Without
256 // this update we may save repeatedly if actions cause a runDelayed
257 // before writing has completed. See Bug 902280
258 this.updateLastSaveTime();
260 // Write (atomically) to a session file, using a tmp file. Once the session
261 // file is successfully updated, save the time stamp of the last save and
262 // notify the observers.
263 stopWatchStart("SEND_SERIALIZED_STATE_LONGEST_OP_MS");
264 let promise = SessionFile.write(data);
265 stopWatchFinish("WRITE_STATE_LONGEST_OP_MS",
266 "SEND_SERIALIZED_STATE_LONGEST_OP_MS");
267 promise = promise.then(() => {
268 this.updateLastSaveTime();
269 notify(null, "sessionstore-state-write-complete");
270 }, console.error);
272 return promise;
273 },
274 };