зеркало из https://github.com/mozilla/gecko-dev.git
235 строки
8.7 KiB
JavaScript
235 строки
8.7 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";
|
|
|
|
this.EXPORTED_SYMBOLS = ["StartupPerformance"];
|
|
|
|
const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
|
"resource://gre/modules/Services.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
|
"resource://gre/modules/Console.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
|
|
"resource://gre/modules/Timer.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
|
|
"resource://gre/modules/Timer.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
|
"resource://gre/modules/Promise.jsm");
|
|
|
|
const COLLECT_RESULTS_AFTER_MS = 10000;
|
|
|
|
const OBSERVED_TOPICS = ["sessionstore-restoring-on-startup", "sessionstore-initiating-manual-restore"];
|
|
|
|
this.StartupPerformance = {
|
|
/**
|
|
* Once we have finished restoring initial tabs, we broadcast on this topic.
|
|
*/
|
|
RESTORED_TOPIC: "sessionstore-finished-restoring-initial-tabs",
|
|
|
|
// Instant at which we have started restoration (notification "sessionstore-restoring-on-startup")
|
|
_startTimeStamp: null,
|
|
|
|
// Latest instant at which we have finished restoring a tab (DOM event "SSTabRestored")
|
|
_latestRestoredTimeStamp: null,
|
|
|
|
// A promise resolved once we have finished restoring all the startup tabs.
|
|
_promiseFinished: null,
|
|
|
|
// Function `resolve()` for `_promiseFinished`.
|
|
_resolveFinished: null,
|
|
|
|
// A timer
|
|
_deadlineTimer: null,
|
|
|
|
// `true` once the timer has fired
|
|
_hasFired: false,
|
|
|
|
// `true` once we are restored
|
|
_isRestored: false,
|
|
|
|
// Statistics on the session we need to restore.
|
|
_totalNumberOfEagerTabs: 0,
|
|
_totalNumberOfTabs: 0,
|
|
_totalNumberOfWindows: 0,
|
|
|
|
init: function() {
|
|
for (let topic of OBSERVED_TOPICS) {
|
|
Services.obs.addObserver(this, topic, false);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Return the timestamp at which we finished restoring the latest tab.
|
|
*
|
|
* This information is not really interesting until we have finished restoring
|
|
* tabs.
|
|
*/
|
|
get latestRestoredTimeStamp() {
|
|
return this._latestRestoredTimeStamp;
|
|
},
|
|
|
|
/**
|
|
* `true` once we have finished restoring startup tabs.
|
|
*/
|
|
get isRestored() {
|
|
return this._isRestored;
|
|
},
|
|
|
|
// Called when restoration starts.
|
|
// Record the start timestamp, setup the timer and `this._promiseFinished`.
|
|
// Behavior is unspecified if there was already an ongoing measure.
|
|
_onRestorationStarts: function(isAutoRestore) {
|
|
this._latestRestoredTimeStamp = this._startTimeStamp = Date.now();
|
|
this._totalNumberOfEagerTabs = 0;
|
|
this._totalNumberOfTabs = 0;
|
|
this._totalNumberOfWindows = 0;
|
|
|
|
// While we may restore several sessions in a single run of the browser,
|
|
// that's a very unusual case, and not really worth measuring, so let's
|
|
// stop listening for further restorations.
|
|
|
|
for (let topic of OBSERVED_TOPICS) {
|
|
Services.obs.removeObserver(this, topic);
|
|
}
|
|
|
|
Services.obs.addObserver(this, "sessionstore-single-window-restored", false);
|
|
this._promiseFinished = new Promise(resolve => {
|
|
this._resolveFinished = resolve;
|
|
});
|
|
this._promiseFinished.then(() => {
|
|
try {
|
|
this._isRestored = true;
|
|
Services.obs.notifyObservers(null, this.RESTORED_TOPIC, "");
|
|
|
|
if (this._latestRestoredTimeStamp == this._startTimeStamp) {
|
|
// Apparently, we haven't restored any tab.
|
|
return;
|
|
}
|
|
|
|
// Once we are done restoring tabs, update Telemetry.
|
|
let histogramName = isAutoRestore ?
|
|
"FX_SESSION_RESTORE_AUTO_RESTORE_DURATION_UNTIL_EAGER_TABS_RESTORED_MS" :
|
|
"FX_SESSION_RESTORE_MANUAL_RESTORE_DURATION_UNTIL_EAGER_TABS_RESTORED_MS";
|
|
let histogram = Services.telemetry.getHistogramById(histogramName);
|
|
let delta = this._latestRestoredTimeStamp - this._startTimeStamp;
|
|
histogram.add(delta);
|
|
|
|
Services.telemetry.getHistogramById("FX_SESSION_RESTORE_NUMBER_OF_EAGER_TABS_RESTORED").add(this._totalNumberOfEagerTabs);
|
|
Services.telemetry.getHistogramById("FX_SESSION_RESTORE_NUMBER_OF_TABS_RESTORED").add(this._totalNumberOfTabs);
|
|
Services.telemetry.getHistogramById("FX_SESSION_RESTORE_NUMBER_OF_WINDOWS_RESTORED").add(this._totalNumberOfWindows);
|
|
|
|
// Reset
|
|
this._startTimeStamp = null;
|
|
} catch (ex) {
|
|
console.error("StartupPerformance: error after resolving promise", ex);
|
|
}
|
|
});
|
|
},
|
|
|
|
_startTimer: function() {
|
|
if (this._hasFired) {
|
|
return;
|
|
}
|
|
if (this._deadlineTimer) {
|
|
clearTimeout(this._deadlineTimer);
|
|
}
|
|
this._deadlineTimer = setTimeout(() => {
|
|
try {
|
|
this._resolveFinished();
|
|
} catch (ex) {
|
|
console.error("StartupPerformance: Error in timeout handler", ex);
|
|
} finally {
|
|
// Clean up.
|
|
this._deadlineTimer = null;
|
|
this._hasFired = true;
|
|
this._resolveFinished = null;
|
|
Services.obs.removeObserver(this, "sessionstore-single-window-restored");
|
|
}
|
|
}, COLLECT_RESULTS_AFTER_MS);
|
|
},
|
|
|
|
observe: function(subject, topic, details) {
|
|
try {
|
|
switch (topic) {
|
|
case "sessionstore-restoring-on-startup":
|
|
this._onRestorationStarts(true);
|
|
break;
|
|
case "sessionstore-initiating-manual-restore":
|
|
this._onRestorationStarts(false);
|
|
break;
|
|
case "sessionstore-single-window-restored": {
|
|
// Session Restore has just opened a window with (initially empty) tabs.
|
|
// Some of these tabs will be restored eagerly, while others will be
|
|
// restored on demand. The process becomes usable only when all windows
|
|
// have finished restored their eager tabs.
|
|
//
|
|
// While it would be possible to track the restoration of each tab
|
|
// from within SessionRestore to determine exactly when the process
|
|
// becomes usable, experience shows that this is too invasive. Rather,
|
|
// we employ the following heuristic:
|
|
// - we maintain a timer of `COLLECT_RESULTS_AFTER_MS` that we expect
|
|
// will be triggered only once all tabs have been restored;
|
|
// - whenever we restore a new window (hence a bunch of eager tabs),
|
|
// we postpone the timer to ensure that the new eager tabs have
|
|
// `COLLECT_RESULTS_AFTER_MS` to be restored;
|
|
// - whenever a tab is restored, we update
|
|
// `this._latestRestoredTimeStamp`;
|
|
// - after `COLLECT_RESULTS_AFTER_MS`, we collect the final version
|
|
// of `this._latestRestoredTimeStamp`, and use it to determine the
|
|
// entire duration of the collection.
|
|
//
|
|
// Note that this heuristic may be inaccurate if a user clicks
|
|
// immediately on a restore-on-demand tab before the end of
|
|
// `COLLECT_RESULTS_AFTER_MS`. We assume that this will not
|
|
// affect too much the results.
|
|
//
|
|
// Reset the delay, to give the tabs a little (more) time to restore.
|
|
this._startTimer();
|
|
|
|
this._totalNumberOfWindows += 1;
|
|
|
|
// Observe the restoration of all tabs. We assume that all tabs of this
|
|
// window will have been restored before `COLLECT_RESULTS_AFTER_MS`.
|
|
// The last call to `observer` will let us determine how long it took
|
|
// to reach that point.
|
|
let win = subject;
|
|
|
|
let observer = (event) => {
|
|
// We don't care about tab restorations that are due to
|
|
// a browser flipping from out-of-main-process to in-main-process
|
|
// or vice-versa. We only care about restorations that are due
|
|
// to the user switching to a lazily restored tab, or for tabs
|
|
// that are restoring eagerly.
|
|
if (!event.detail.isRemotenessUpdate) {
|
|
this._latestRestoredTimeStamp = Date.now();
|
|
this._totalNumberOfEagerTabs += 1;
|
|
}
|
|
};
|
|
win.gBrowser.tabContainer.addEventListener("SSTabRestored", observer);
|
|
this._totalNumberOfTabs += win.gBrowser.tabContainer.itemCount;
|
|
|
|
// Once we have finished collecting the results, clean up the observers.
|
|
this._promiseFinished.then(() => {
|
|
if (!win.gBrowser.tabContainer) {
|
|
// May be undefined during shutdown and/or some tests.
|
|
return;
|
|
}
|
|
win.gBrowser.tabContainer.removeEventListener("SSTabRestored", observer);
|
|
});
|
|
}
|
|
break;
|
|
default:
|
|
throw new Error(`Unexpected topic ${topic}`);
|
|
}
|
|
} catch (ex) {
|
|
console.error("StartupPerformance error", ex, ex.stack);
|
|
throw ex;
|
|
}
|
|
}
|
|
};
|