Bug 1380011 - Make startupRecorder.js' 'before handling user event' phase more reliable, r=mconley.
--- a/browser/base/content/test/performance/browser_startup.js
+++ b/browser/base/content/test/performance/browser_startup.js
@@ -56,65 +56,81 @@ const startupPhases = {
])
}},
// We reach this phase right after showing the first browser window.
// This means that anything already loaded at this point has been loaded
// before first paint and delayed it.
"before first paint": {blacklist: {
components: new Set([
- "PageIconProtocolHandler.js",
- "PlacesCategoriesStarter.js",
"UnifiedComplete.js",
- "nsPlacesExpiration.js",
"nsSearchService.js",
]),
modules: new Set([
- "chrome://webcompat-reporter/content/TabListener.jsm",
"resource:///modules/AboutNewTab.jsm",
"resource:///modules/DirectoryLinksProvider.jsm",
- "resource:///modules/RecentWindow.jsm",
- "resource://gre/modules/BookmarkHTMLUtils.jsm",
- "resource://gre/modules/Bookmarks.jsm",
- "resource://gre/modules/ContextualIdentityService.jsm",
"resource://gre/modules/NewTabUtils.jsm",
"resource://gre/modules/PageThumbs.jsm",
- "resource://gre/modules/PlacesSyncUtils.jsm",
- "resource://gre/modules/Promise.jsm",
- "resource://gre/modules/Sqlite.jsm",
+ "resource://gre/modules/Promise.jsm", // imported by devtools during _delayedStartup
]),
services: new Set([
- "@mozilla.org/browser/annotation-service;1",
- "@mozilla.org/browser/favicon-service;1",
- "@mozilla.org/browser/nav-bookmarks-service;1",
"@mozilla.org/browser/search-service;1",
])
}},
// We are at this phase once we are ready to handle user events.
// Anything loaded at this phase or before gets in the way of the user
// interacting with the first browser window.
"before handling user events": {blacklist: {
+ components: new Set([
+ "PageIconProtocolHandler.js",
+ "PlacesCategoriesStarter.js",
+ "nsPlacesExpiration.js",
+ ]),
modules: new Set([
+ "chrome://webcompat-reporter/content/TabListener.jsm",
+ "chrome://webcompat-reporter/content/WebCompatReporter.jsm",
+ "resource:///modules/RecentWindow.jsm",
+ "resource://gre/modules/BookmarkHTMLUtils.jsm",
+ "resource://gre/modules/Bookmarks.jsm",
+ "resource://gre/modules/ContextualIdentityService.jsm",
"resource://gre/modules/FxAccounts.jsm",
"resource://gre/modules/FxAccountsStorage.jsm",
+ "resource://gre/modules/PlacesSyncUtils.jsm",
+ "resource://gre/modules/Sqlite.jsm",
+ ]),
+ services: new Set([
+ "@mozilla.org/browser/annotation-service;1",
+ "@mozilla.org/browser/favicon-service;1",
+ "@mozilla.org/browser/nav-bookmarks-service;1",
+ ])
+ }},
+
+ // Things that are expected to be completely out of the startup path
+ // and loaded lazily when used for the first time by the user should
+ // be blacklisted here.
+ "before becoming idle": {blacklist: {
+ modules: new Set([
"resource://gre/modules/LoginManagerContextMenu.jsm",
"resource://gre/modules/Task.jsm",
]),
}},
};
-function test() {
+add_task(async function() {
if (!AppConstants.NIGHTLY_BUILD && !AppConstants.DEBUG) {
ok(!("@mozilla.org/test/startuprecorder;1" in Cc),
"the startup recorder component shouldn't exist in this non-nightly non-debug build.");
return;
}
- let data = Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject.data.code;
+ let startupRecorder = Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject;
+ await startupRecorder.done;
+
+ let data = startupRecorder.data.code;
// Keep only the file name for components, as the path is an absolute file
// URL rather than a resource:// URL like for modules.
for (let phase in data) {
data[phase].components =
data[phase].components.map(f => f.replace(/.*\//, ""))
.filter(c => c != "startupRecorder.js");
}
@@ -162,9 +178,9 @@ function test() {
if (blacklist) {
for (let scriptType in blacklist) {
for (let file of blacklist[scriptType]) {
ok(!loadedList[scriptType].includes(file), `${file} is not allowed ${phase}`);
}
}
}
}
-}
+});
--- a/browser/base/content/test/performance/browser_startup_images.js
+++ b/browser/base/content/test/performance/browser_startup_images.js
@@ -210,18 +210,21 @@ const whitelist = [
},
{
file: "chrome://browser/skin/tabbrowser/tab-arrow-right.png",
hidpi: "chrome://browser/skin/tabbrowser/tab-arrow-right@2x.png",
platforms: ["macosx"],
},
];
-function test() {
- let data = Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject.data.images;
+add_task(async function() {
+ let startupRecorder = Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject;
+ await startupRecorder.done;
+
+ let data = startupRecorder.data.images;
let filteredWhitelist = whitelist.filter(el => {
return el.platforms.includes(AppConstants.platform) &&
(el.photon === undefined || el.photon == AppConstants.MOZ_PHOTON_THEME);
});
let loadedImages = data["image-loading"];
let shownImages = data["image-drawing"];
@@ -250,9 +253,9 @@ function test() {
if (item.hidpi != "<not loaded>") {
ok(loadedImages.has(item.hidpi), `Whitelisted image ${item.hidpi} should have been loaded.`);
}
} else {
ok(loadedImages.has(item.file), `Whitelisted image ${item.file} should have been loaded.`);
}
}
}
-}
+});
--- a/browser/components/tests/startupRecorder.js
+++ b/browser/components/tests/startupRecorder.js
@@ -28,16 +28,17 @@ function startupRecorder() {
this.loader = Cc["@mozilla.org/moz/jsloader;1"].getService(Ci.xpcIJSModuleLoader);
this.data = {
images: {
"image-drawing": new Set(),
"image-loading": new Set(),
},
code: {}
};
+ this.done = new Promise(resolve => { this._resolve = resolve });
}
startupRecorder.prototype = {
classID: Components.ID("{11c095b2-e42e-4bdf-9dd0-aed87595f6a4}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
record(name) {
this.data.code[name] = {
@@ -79,21 +80,33 @@ startupRecorder.prototype = {
}
Services.obs.removeObserver(this, topic);
if (topic == "sessionstore-windows-restored") {
// 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.
- Services.tm.idleDispatchToMainThread(
+ Services.tm.dispatchToMainThread(
this.record.bind(this, "before handling user events"));
- Services.obs.removeObserver(this, "image-drawing");
- Services.obs.removeObserver(this, "image-loading");
+ // 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;
+ });
} else {
const topicsToNames = {
"profile-do-change": "before profile selection",
"toplevel-window-ready": "before opening first browser window",
};
topicsToNames[firstPaintNotification] = "before first paint";
this.record(topicsToNames[topic]);
}
--- a/testing/marionette/components/marionette.js
+++ b/testing/marionette/components/marionette.js
@@ -257,17 +257,20 @@ MarionetteComponent.prototype.suppressSa
MarionetteComponent.prototype.init = function() {
if (this.running || !this.enabled || !this.finalUIStartup) {
return;
}
// Delay initialization until we are done with delayed startup...
Services.tm.idleDispatchToMainThread(() => {
// ... and with startup tests.
- Services.tm.idleDispatchToMainThread(() => {
+ let promise = Promise.resolve();
+ if ("@mozilla.org/test/startuprecorder;1" in Cc)
+ promise = Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject.done;
+ promise.then(() => {
let s;
try {
Cu.import("chrome://marionette/content/server.js");
s = new server.TCPListener(prefs.port);
s.start();
this.logger.info(`Listening on port ${s.port}`);
} finally {
if (s) {