forked from mirrors/gecko-dev
The current shutdown handling code is susceptible to deadlocks, since it spins the event loop while it holds mMonitor, and other main thread methods which try to acquire mMonitor can be called from code that runs while the event loop is spinning. My initial solution was just to release mMonitor before spinning the event loop, but at this point I think it makes more sense to switch to the standardized AsyncShutdown routines, which provide better diagnostics and allow us to avoid one more nested event loop during shutdown. MozReview-Commit-ID: 1RtFN585IR7 --HG-- extra : rebase_source : 978f3bf7cef73e56b3e1c1c838c2bc6efcefb0c0 extra : amend_source : 2b7c9422004b58ad1d38d7dd705ad446bc47cb23 extra : histedit_source : 7a4e80de7d5aa48e074ea03821bb78e5e287842e%2C92c1119a131adaa33f5691c0e534bb243115817b
232 lines
8.8 KiB
JavaScript
232 lines
8.8 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
/* This test records at which phase of startup the JS components and modules
|
|
* are first loaded.
|
|
* If you made changes that cause this test to fail, it's likely because you
|
|
* are loading more JS code during startup.
|
|
* Most code has no reason to run off of the app-startup notification
|
|
* (this is very early, before we have selected the user profile, so
|
|
* preferences aren't accessible yet).
|
|
* If your code isn't strictly required to show the first browser window,
|
|
* it shouldn't be loaded before we are done with first paint.
|
|
* Finally, if your code isn't really needed during startup, it should not be
|
|
* loaded before we have started handling user events.
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
/* Set this to true only for debugging purpose; it makes the output noisy. */
|
|
const kDumpAllStacks = false;
|
|
|
|
const startupPhases = {
|
|
// For app-startup, we have a whitelist of acceptable JS files.
|
|
// Anything loaded during app-startup must have a compelling reason
|
|
// to run before we have even selected the user profile.
|
|
// Consider loading your code after first paint instead,
|
|
// eg. from nsBrowserGlue.js' _onFirstWindowLoaded method).
|
|
"before profile selection": {whitelist: {
|
|
components: new Set([
|
|
"nsBrowserGlue.js",
|
|
"MainProcessSingleton.js",
|
|
|
|
// Bugs to fix: The following components shouldn't be initialized that early.
|
|
"WebContentConverter.js", // bug 1369443
|
|
"nsSessionStartup.js", // bug 1369456
|
|
"PushComponents.js", // bug 1369436
|
|
]),
|
|
modules: new Set([
|
|
"resource://gre/modules/AppConstants.jsm",
|
|
"resource://gre/modules/XPCOMUtils.jsm",
|
|
"resource://gre/modules/Services.jsm",
|
|
|
|
// Bugs to fix: Probably loaded too early, needs investigation.
|
|
"resource://gre/modules/RemotePageManager.jsm", // bug 1369466
|
|
])
|
|
}},
|
|
|
|
// For the following phases of startup we have only a black list for now
|
|
|
|
// We are at this phase after creating the first browser window (ie. after final-ui-startup).
|
|
"before opening first browser window": {blacklist: {
|
|
modules: new Set([
|
|
"resource://gre/modules/PlacesBackups.jsm",
|
|
"resource://gre/modules/PlacesUtils.jsm",
|
|
])
|
|
}},
|
|
|
|
// 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([
|
|
"UnifiedComplete.js",
|
|
"nsSearchService.js",
|
|
]),
|
|
modules: new Set([
|
|
"chrome://webcompat-reporter/content/WebCompatReporter.jsm",
|
|
"chrome://webcompat/content/data/ua_overrides.jsm",
|
|
"chrome://webcompat/content/lib/ua_overrider.jsm",
|
|
"resource:///modules/AboutNewTab.jsm",
|
|
"resource:///modules/BrowserUITelemetry.jsm",
|
|
"resource:///modules/BrowserUsageTelemetry.jsm",
|
|
"resource:///modules/ContentCrashHandlers.jsm",
|
|
"resource:///modules/DirectoryLinksProvider.jsm",
|
|
"resource://gre/modules/NewTabUtils.jsm",
|
|
"resource://gre/modules/PageThumbs.jsm",
|
|
"resource://gre/modules/Promise.jsm", // imported by devtools during _delayedStartup
|
|
"resource://gre/modules/Preferences.jsm",
|
|
]),
|
|
services: new Set([
|
|
"@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([
|
|
// Bug 1391495 - RecentWindow.jsm is intermittently used.
|
|
// "resource:///modules/RecentWindow.jsm",
|
|
"resource://gre/modules/BookmarkHTMLUtils.jsm",
|
|
"resource://gre/modules/Bookmarks.jsm",
|
|
"resource://gre/modules/ContextualIdentityService.jsm",
|
|
"resource://gre/modules/CrashSubmit.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/AsyncPrefs.jsm",
|
|
"resource://gre/modules/LoginManagerContextMenu.jsm",
|
|
"resource://gre/modules/Task.jsm",
|
|
]),
|
|
}},
|
|
};
|
|
|
|
if (!gBrowser.selectedBrowser.isRemoteBrowser) {
|
|
// With e10s disabled, Places and RecentWindow.jsm (from a
|
|
// SessionSaver.jsm timer) intermittently get loaded earlier. Likely
|
|
// due to messages from the 'content' process arriving synchronously
|
|
// instead of crossing a process boundary.
|
|
info("merging the 'before handling user events' blacklist into the " +
|
|
"'before first paint' one when e10s is disabled.");
|
|
let from = startupPhases["before handling user events"].blacklist;
|
|
let to = startupPhases["before first paint"].blacklist;
|
|
for (let scriptType in from) {
|
|
if (!(scriptType in to)) {
|
|
to[scriptType] = from[scriptType];
|
|
} else {
|
|
for (let item of from[scriptType])
|
|
to[scriptType].add(item);
|
|
}
|
|
}
|
|
startupPhases["before handling user events"].blacklist = null;
|
|
}
|
|
|
|
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 startupRecorder = Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject;
|
|
await startupRecorder.done;
|
|
|
|
let loader = Cc["@mozilla.org/moz/jsloader;1"].getService(Ci.xpcIJSModuleLoader);
|
|
let componentStacks = new Map();
|
|
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(uri => {
|
|
let fileName = uri.replace(/.*\//, "");
|
|
componentStacks.set(fileName, loader.getComponentLoadStack(uri));
|
|
return fileName;
|
|
}).filter(c => c != "startupRecorder.js");
|
|
}
|
|
|
|
function printStack(scriptType, name) {
|
|
if (scriptType == "modules")
|
|
info(loader.getModuleImportStack(name));
|
|
else if (scriptType == "components")
|
|
info(componentStacks.get(name));
|
|
}
|
|
|
|
// This block only adds debug output to help find the next bugs to file,
|
|
// it doesn't contribute to the actual test.
|
|
SimpleTest.requestCompleteLog();
|
|
let previous;
|
|
for (let phase in data) {
|
|
for (let scriptType in data[phase]) {
|
|
for (let f of data[phase][scriptType]) {
|
|
// phases are ordered, so if a script wasn't loaded yet at the immediate
|
|
// previous phase, it wasn't loaded during any of the previous phases
|
|
// either, and is new in the current phase.
|
|
if (!previous || !data[previous][scriptType].includes(f)) {
|
|
info(`${scriptType} loaded ${phase}: ${f}`);
|
|
if (kDumpAllStacks)
|
|
printStack(scriptType, f);
|
|
}
|
|
}
|
|
}
|
|
previous = phase;
|
|
}
|
|
|
|
for (let phase in startupPhases) {
|
|
let loadedList = data[phase];
|
|
let whitelist = startupPhases[phase].whitelist || null;
|
|
if (whitelist) {
|
|
for (let scriptType in whitelist) {
|
|
loadedList[scriptType] = loadedList[scriptType].filter(c => {
|
|
if (!whitelist[scriptType].has(c))
|
|
return true;
|
|
whitelist[scriptType].delete(c);
|
|
return false;
|
|
});
|
|
is(loadedList[scriptType].length, 0,
|
|
`should have no unexpected ${scriptType} loaded ${phase}`);
|
|
for (let script of loadedList[scriptType]) {
|
|
ok(false, `unexpected ${scriptType}: ${script}`);
|
|
printStack(scriptType, script);
|
|
}
|
|
is(whitelist[scriptType].size, 0,
|
|
`all ${scriptType} whitelist entries should have been used`);
|
|
for (let script of whitelist[scriptType]) {
|
|
ok(false, `unused ${scriptType} whitelist entry: ${script}`);
|
|
}
|
|
}
|
|
}
|
|
let blacklist = startupPhases[phase].blacklist || null;
|
|
if (blacklist) {
|
|
for (let scriptType in blacklist) {
|
|
for (let file of blacklist[scriptType]) {
|
|
let loaded = loadedList[scriptType].includes(file);
|
|
ok(!loaded, `${file} is not allowed ${phase}`);
|
|
if (loaded)
|
|
printStack(scriptType, file);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|