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/. */
6 ChromeUtils.defineESModuleGetters(lazy, {
7 clearTimeout: "resource://gre/modules/Timer.sys.mjs",
8 setTimeout: "resource://gre/modules/Timer.sys.mjs",
11 const COLLECT_RESULTS_AFTER_MS = 10000;
13 const OBSERVED_TOPICS = [
14 "sessionstore-restoring-on-startup",
15 "sessionstore-initiating-manual-restore",
18 export var StartupPerformance = {
20 * Once we have finished restoring initial tabs, we broadcast on this topic.
22 RESTORED_TOPIC: "sessionstore-finished-restoring-initial-tabs",
24 // Instant at which we have started restoration (notification "sessionstore-restoring-on-startup")
25 _startTimeStamp: null,
27 // Latest instant at which we have finished restoring a tab (DOM event "SSTabRestored")
28 _latestRestoredTimeStamp: null,
30 // A promise resolved once we have finished restoring all the startup tabs.
31 _promiseFinished: null,
33 // Function `resolve()` for `_promiseFinished`.
34 _resolveFinished: null,
39 // `true` once the timer has fired
42 // `true` once we are restored
45 // Statistics on the session we need to restore.
46 _totalNumberOfEagerTabs: 0,
47 _totalNumberOfTabs: 0,
48 _totalNumberOfWindows: 0,
51 for (let topic of OBSERVED_TOPICS) {
52 Services.obs.addObserver(this, topic);
57 * Return the timestamp at which we finished restoring the latest tab.
59 * This information is not really interesting until we have finished restoring
62 get latestRestoredTimeStamp() {
63 return this._latestRestoredTimeStamp;
67 * `true` once we have finished restoring startup tabs.
70 return this._isRestored;
73 // Called when restoration starts.
74 // Record the start timestamp, setup the timer and `this._promiseFinished`.
75 // Behavior is unspecified if there was already an ongoing measure.
76 _onRestorationStarts(isAutoRestore) {
77 ChromeUtils.addProfilerMarker("_onRestorationStarts");
78 this._latestRestoredTimeStamp = this._startTimeStamp = Date.now();
79 this._totalNumberOfEagerTabs = 0;
80 this._totalNumberOfTabs = 0;
81 this._totalNumberOfWindows = 0;
83 // While we may restore several sessions in a single run of the browser,
84 // that's a very unusual case, and not really worth measuring, so let's
85 // stop listening for further restorations.
87 for (let topic of OBSERVED_TOPICS) {
88 Services.obs.removeObserver(this, topic);
91 Services.obs.addObserver(this, "sessionstore-single-window-restored");
92 this._promiseFinished = new Promise(resolve => {
93 this._resolveFinished = resolve;
95 this._promiseFinished.then(() => {
97 this._isRestored = true;
98 Services.obs.notifyObservers(null, this.RESTORED_TOPIC);
100 if (this._latestRestoredTimeStamp == this._startTimeStamp) {
101 // Apparently, we haven't restored any tab.
105 // Once we are done restoring tabs, update Telemetry.
106 let delta = this._latestRestoredTimeStamp - this._startTimeStamp;
110 "FX_SESSION_RESTORE_AUTO_RESTORE_DURATION_UNTIL_EAGER_TABS_RESTORED_MS"
116 "FX_SESSION_RESTORE_MANUAL_RESTORE_DURATION_UNTIL_EAGER_TABS_RESTORED_MS"
121 .getHistogramById("FX_SESSION_RESTORE_NUMBER_OF_EAGER_TABS_RESTORED")
122 .add(this._totalNumberOfEagerTabs);
124 .getHistogramById("FX_SESSION_RESTORE_NUMBER_OF_TABS_RESTORED")
125 .add(this._totalNumberOfTabs);
127 .getHistogramById("FX_SESSION_RESTORE_NUMBER_OF_WINDOWS_RESTORED")
128 .add(this._totalNumberOfWindows);
131 this._startTimeStamp = null;
133 console.error("StartupPerformance: error after resolving promise", ex);
139 if (this._hasFired) {
142 if (this._deadlineTimer) {
143 lazy.clearTimeout(this._deadlineTimer);
145 this._deadlineTimer = lazy.setTimeout(() => {
147 this._resolveFinished();
149 console.error("StartupPerformance: Error in timeout handler", ex);
152 this._deadlineTimer = null;
153 this._hasFired = true;
154 this._resolveFinished = null;
155 Services.obs.removeObserver(
157 "sessionstore-single-window-restored"
160 }, COLLECT_RESULTS_AFTER_MS);
163 observe(subject, topic) {
166 case "sessionstore-restoring-on-startup":
167 this._onRestorationStarts(true);
169 case "sessionstore-initiating-manual-restore":
170 this._onRestorationStarts(false);
172 case "sessionstore-single-window-restored":
174 // Session Restore has just opened a window with (initially empty) tabs.
175 // Some of these tabs will be restored eagerly, while others will be
176 // restored on demand. The process becomes usable only when all windows
177 // have finished restored their eager tabs.
179 // While it would be possible to track the restoration of each tab
180 // from within SessionRestore to determine exactly when the process
181 // becomes usable, experience shows that this is too invasive. Rather,
182 // we employ the following heuristic:
183 // - we maintain a timer of `COLLECT_RESULTS_AFTER_MS` that we expect
184 // will be triggered only once all tabs have been restored;
185 // - whenever we restore a new window (hence a bunch of eager tabs),
186 // we postpone the timer to ensure that the new eager tabs have
187 // `COLLECT_RESULTS_AFTER_MS` to be restored;
188 // - whenever a tab is restored, we update
189 // `this._latestRestoredTimeStamp`;
190 // - after `COLLECT_RESULTS_AFTER_MS`, we collect the final version
191 // of `this._latestRestoredTimeStamp`, and use it to determine the
192 // entire duration of the collection.
194 // Note that this heuristic may be inaccurate if a user clicks
195 // immediately on a restore-on-demand tab before the end of
196 // `COLLECT_RESULTS_AFTER_MS`. We assume that this will not
197 // affect too much the results.
199 // Reset the delay, to give the tabs a little (more) time to restore.
202 this._totalNumberOfWindows += 1;
204 // Observe the restoration of all tabs. We assume that all tabs of this
205 // window will have been restored before `COLLECT_RESULTS_AFTER_MS`.
206 // The last call to `observer` will let us determine how long it took
207 // to reach that point.
210 let observer = event => {
211 // We don't care about tab restorations that are due to
212 // a browser flipping from out-of-main-process to in-main-process
213 // or vice-versa. We only care about restorations that are due
214 // to the user switching to a lazily restored tab, or for tabs
215 // that are restoring eagerly.
216 if (!event.detail.isRemotenessUpdate) {
217 ChromeUtils.addProfilerMarker("SSTabRestored");
218 this._latestRestoredTimeStamp = Date.now();
219 this._totalNumberOfEagerTabs += 1;
222 win.gBrowser.tabContainer.addEventListener(
226 this._totalNumberOfTabs += win.gBrowser.tabContainer.itemCount;
228 // Once we have finished collecting the results, clean up the observers.
229 this._promiseFinished.then(() => {
230 if (!win.gBrowser.tabContainer) {
231 // May be undefined during shutdown and/or some tests.
234 win.gBrowser.tabContainer.removeEventListener(
242 throw new Error(`Unexpected topic ${topic}`);
245 console.error("StartupPerformance error", ex, ex.stack);