2017-06-01 00:00:43 +03:00
|
|
|
/* 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/. */
|
|
|
|
|
2017-06-13 09:39:39 +03:00
|
|
|
const {classes: Cc, utils: Cu, interfaces: Ci, manager: Cm} = Components;
|
|
|
|
Cm.QueryInterface(Ci.nsIServiceManager);
|
2017-06-01 00:00:43 +03:00
|
|
|
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
2017-06-09 16:11:03 +03:00
|
|
|
Cu.import("resource://gre/modules/AppConstants.jsm");
|
|
|
|
|
|
|
|
let firstPaintNotification = "widget-first-paint";
|
|
|
|
// widget-first-paint fires much later than expected on Linux.
|
|
|
|
if (AppConstants.platform == "linux")
|
|
|
|
firstPaintNotification = "xul-window-visible";
|
2017-06-01 00:00:43 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The startupRecorder component observes notifications at various stages of
|
|
|
|
* startup and records the set of JS components and modules that were already
|
|
|
|
* loaded at each of these points.
|
|
|
|
* The records are meant to be used by startup tests in
|
|
|
|
* browser/base/content/test/performance
|
|
|
|
* This component only exists in nightly and debug builds, it doesn't ship in
|
|
|
|
* our release builds.
|
|
|
|
*/
|
|
|
|
function startupRecorder() {
|
|
|
|
this.wrappedJSObject = this;
|
|
|
|
this.loader = Cc["@mozilla.org/moz/jsloader;1"].getService(Ci.xpcIJSModuleLoader);
|
2017-06-15 01:11:48 +03:00
|
|
|
this.data = {
|
|
|
|
images: {
|
|
|
|
"image-drawing": new Set(),
|
|
|
|
"image-loading": new Set(),
|
|
|
|
},
|
|
|
|
code: {}
|
|
|
|
};
|
2017-07-13 00:09:42 +03:00
|
|
|
this.done = new Promise(resolve => { this._resolve = resolve });
|
2017-06-01 00:00:43 +03:00
|
|
|
}
|
|
|
|
startupRecorder.prototype = {
|
|
|
|
classID: Components.ID("{11c095b2-e42e-4bdf-9dd0-aed87595f6a4}"),
|
|
|
|
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
|
|
|
|
|
|
|
|
record(name) {
|
2017-06-15 01:11:48 +03:00
|
|
|
this.data.code[name] = {
|
2017-06-01 00:00:43 +03:00
|
|
|
components: this.loader.loadedComponents(),
|
2017-06-13 09:39:39 +03:00
|
|
|
modules: this.loader.loadedModules(),
|
|
|
|
services: Object.keys(Cc).filter(c => {
|
|
|
|
try {
|
|
|
|
Cm.isServiceInstantiatedByContractID(c, Ci.nsISupports);
|
|
|
|
return true;
|
|
|
|
} catch (e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
})
|
2017-06-01 00:00:43 +03:00
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
observe(subject, topic, data) {
|
|
|
|
|
|
|
|
if (topic == "app-startup") {
|
|
|
|
// We can't ensure our observer will be called first or last, so the list of
|
|
|
|
// topics we observe here should avoid the topics used to trigger things
|
|
|
|
// during startup (eg. the topics observed by nsBrowserGlue.js).
|
|
|
|
let topics = [
|
|
|
|
"profile-do-change", // This catches stuff loaded during app-startup
|
|
|
|
"toplevel-window-ready", // Catches stuff from final-ui-startup
|
2017-06-15 01:11:48 +03:00
|
|
|
"image-loading",
|
|
|
|
"image-drawing",
|
2017-06-09 16:11:03 +03:00
|
|
|
firstPaintNotification,
|
2017-06-01 00:00:43 +03:00
|
|
|
"sessionstore-windows-restored",
|
|
|
|
];
|
|
|
|
for (let t of topics)
|
|
|
|
Services.obs.addObserver(this, t);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-06-15 01:11:48 +03:00
|
|
|
if (topic == "image-drawing" || topic == "image-loading") {
|
|
|
|
this.data.images[topic].add(data);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-06-01 00:00:43 +03:00
|
|
|
Services.obs.removeObserver(this, topic);
|
|
|
|
|
|
|
|
if (topic == "sessionstore-windows-restored") {
|
2017-06-14 12:34:46 +03:00
|
|
|
// We use idleDispatchToMainThread here to record the set of
|
|
|
|
// loaded scripts after we are fully done with startup and ready
|
|
|
|
// to react to user events.
|
2017-07-13 00:09:42 +03:00
|
|
|
Services.tm.dispatchToMainThread(
|
2017-06-01 00:00:43 +03:00
|
|
|
this.record.bind(this, "before handling user events"));
|
2017-06-15 01:11:48 +03:00
|
|
|
|
2017-07-13 00:09:42 +03:00
|
|
|
// 10 is an arbitrary value here, it needs to be at least 2 to avoid
|
|
|
|
// races with code initializing itself using idle callbacks.
|
|
|
|
(function waitForIdle(callback, count = 10) {
|
|
|
|
if (count)
|
|
|
|
Services.tm.idleDispatchToMainThread(() => waitForIdle(callback, count - 1));
|
|
|
|
else
|
|
|
|
callback();
|
|
|
|
})(() => {
|
|
|
|
this.record("before becoming idle");
|
|
|
|
Services.obs.removeObserver(this, "image-drawing");
|
|
|
|
Services.obs.removeObserver(this, "image-loading");
|
|
|
|
this._resolve();
|
|
|
|
this._resolve = null;
|
|
|
|
});
|
2017-06-01 00:00:43 +03:00
|
|
|
} else {
|
|
|
|
const topicsToNames = {
|
|
|
|
"profile-do-change": "before profile selection",
|
|
|
|
"toplevel-window-ready": "before opening first browser window",
|
|
|
|
};
|
2017-06-09 16:11:03 +03:00
|
|
|
topicsToNames[firstPaintNotification] = "before first paint";
|
2017-06-01 00:00:43 +03:00
|
|
|
this.record(topicsToNames[topic]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([startupRecorder]);
|