From 9278da562f0fbfa22f4b51c1b82ee5a70d160687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Qu=C3=A8ze?= Date: Wed, 13 Sep 2017 21:19:53 +0200 Subject: [PATCH] Bug 1398198 - browser_startup.js should show the stack when a JS file was loaded earlier than expected, r=felipe,mccr8. --- .../base/content/test/performance/browser.ini | 2 + .../test/performance/browser_startup.js | 30 ++++++++-- browser/components/tests/startupRecorder.js | 9 +++ js/xpconnect/idl/xpcIJSModuleLoader.idl | 8 ++- js/xpconnect/loader/mozJSComponentLoader.cpp | 59 +++++++++++++++++++ js/xpconnect/loader/mozJSComponentLoader.h | 6 ++ 6 files changed, 109 insertions(+), 5 deletions(-) diff --git a/browser/base/content/test/performance/browser.ini b/browser/base/content/test/performance/browser.ini index d801852ddb12..b2546281e033 100644 --- a/browser/base/content/test/performance/browser.ini +++ b/browser/base/content/test/performance/browser.ini @@ -1,4 +1,6 @@ [DEFAULT] +prefs = + browser.startup.record=true support-files = head.js [browser_appmenu_reflows.js] diff --git a/browser/base/content/test/performance/browser_startup.js b/browser/base/content/test/performance/browser_startup.js index 13a25cebe618..05a106933074 100644 --- a/browser/base/content/test/performance/browser_startup.js +++ b/browser/base/content/test/performance/browser_startup.js @@ -16,6 +16,9 @@ "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 @@ -152,13 +155,25 @@ add_task(async function() { 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(f => f.replace(/.*\//, "")) - .filter(c => c != "startupRecorder.js"); + 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, @@ -171,8 +186,11 @@ add_task(async function() { // 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)) + if (!previous || !data[previous][scriptType].includes(f)) { info(`${scriptType} loaded ${phase}: ${f}`); + if (kDumpAllStacks) + printStack(scriptType, f); + } } } previous = phase; @@ -193,6 +211,7 @@ add_task(async function() { `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`); @@ -205,7 +224,10 @@ add_task(async function() { if (blacklist) { for (let scriptType in blacklist) { for (let file of blacklist[scriptType]) { - ok(!loadedList[scriptType].includes(file), `${file} is not allowed ${phase}`); + let loaded = loadedList[scriptType].includes(file); + ok(!loaded, `${file} is not allowed ${phase}`); + if (loaded) + printStack(scriptType, file); } } } diff --git a/browser/components/tests/startupRecorder.js b/browser/components/tests/startupRecorder.js index 6ffd90f06778..caea02923179 100644 --- a/browser/components/tests/startupRecorder.js +++ b/browser/components/tests/startupRecorder.js @@ -41,6 +41,9 @@ startupRecorder.prototype = { QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]), record(name) { + if (!Services.prefs.getBoolPref("browser.startup.record", false)) + return; + this.data.code[name] = { components: this.loader.loadedComponents(), modules: this.loader.loadedModules(), @@ -81,6 +84,12 @@ startupRecorder.prototype = { Services.obs.removeObserver(this, topic); if (topic == "sessionstore-windows-restored") { + if (!Services.prefs.getBoolPref("browser.startup.record", false)) { + this._resolve(); + this._resolve = null; + return; + } + // 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. diff --git a/js/xpconnect/idl/xpcIJSModuleLoader.idl b/js/xpconnect/idl/xpcIJSModuleLoader.idl index 37d2069b149c..6bff90547e97 100644 --- a/js/xpconnect/idl/xpcIJSModuleLoader.idl +++ b/js/xpconnect/idl/xpcIJSModuleLoader.idl @@ -8,11 +8,17 @@ [scriptable, builtinclass, uuid(4f94b21f-2920-4bd9-8251-5fb60fb054b2)] interface xpcIJSModuleLoader : nsISupports { - // These 2 functions are for startup testing purposes. They are not expected + // These functions are for startup testing purposes. They are not expected // to be used for production code. void loadedModules([optional] out unsigned long length, [retval, array, size_is(length)] out string aModules); void loadedComponents([optional] out unsigned long length, [retval, array, size_is(length)] out string aComponents); + + // These 2 functions will only return useful values if the + // "browser.startup.record" preference was true at the time the JS file + // was loaded. + ACString getModuleImportStack(in AUTF8String aLocation); + ACString getComponentLoadStack(in AUTF8String aLocation); }; diff --git a/js/xpconnect/loader/mozJSComponentLoader.cpp b/js/xpconnect/loader/mozJSComponentLoader.cpp index 6b1cb307016a..c9c8e5a40600 100644 --- a/js/xpconnect/loader/mozJSComponentLoader.cpp +++ b/js/xpconnect/loader/mozJSComponentLoader.cpp @@ -433,6 +433,12 @@ mozJSComponentLoader::LoadModule(FileLocation& aFile) return nullptr; } +#if defined(NIGHTLY_BUILD) || defined(DEBUG) + if (Preferences::GetBool("browser.startup.record", false)) { + entry->importStack = xpc_PrintJSStack(cx, false, false, false).get(); + } +#endif + // Cache this module for later mModules.Put(spec, entry); @@ -1000,6 +1006,52 @@ NS_IMETHODIMP mozJSComponentLoader::LoadedComponents(uint32_t* length, return NS_OK; } +NS_IMETHODIMP +mozJSComponentLoader::GetModuleImportStack(const nsACString& aLocation, + nsACString& retval) +{ +#if defined(NIGHTLY_BUILD) || defined(DEBUG) + MOZ_ASSERT(nsContentUtils::IsCallerChrome()); + MOZ_ASSERT(mInitialized); + + ComponentLoaderInfo info(aLocation); + nsresult rv = info.EnsureKey(); + NS_ENSURE_SUCCESS(rv, rv); + + ModuleEntry* mod; + if (!mImports.Get(info.Key(), &mod)) + return NS_ERROR_FAILURE; + + retval = mod->importStack; + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +NS_IMETHODIMP +mozJSComponentLoader::GetComponentLoadStack(const nsACString& aLocation, + nsACString& retval) +{ +#if defined(NIGHTLY_BUILD) || defined(DEBUG) + MOZ_ASSERT(nsContentUtils::IsCallerChrome()); + MOZ_ASSERT(mInitialized); + + ComponentLoaderInfo info(aLocation); + nsresult rv = info.EnsureURI(); + NS_ENSURE_SUCCESS(rv, rv); + + ModuleEntry* mod; + if (!mModules.Get(info.Key(), &mod)) + return NS_ERROR_FAILURE; + + retval = mod->importStack; + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + static JSObject* ResolveModuleObjectPropertyById(JSContext* aCx, HandleObject aModObj, HandleId id) { @@ -1100,6 +1152,13 @@ mozJSComponentLoader::ImportInto(const nsACString& aLocation, return NS_ERROR_FILE_NOT_FOUND; } +#if defined(NIGHTLY_BUILD) || defined(DEBUG) + if (Preferences::GetBool("browser.startup.record", false)) { + newEntry->importStack = + xpc_PrintJSStack(callercx, false, false, false).get(); + } +#endif + mod = newEntry; } diff --git a/js/xpconnect/loader/mozJSComponentLoader.h b/js/xpconnect/loader/mozJSComponentLoader.h index adfcb7a7888a..15b530e2f039 100644 --- a/js/xpconnect/loader/mozJSComponentLoader.h +++ b/js/xpconnect/loader/mozJSComponentLoader.h @@ -163,6 +163,9 @@ class mozJSComponentLoader final : public mozilla::ModuleLoader, obj = nullptr; thisObjectKey = nullptr; location = nullptr; +#if defined(NIGHTLY_BUILD) || defined(DEBUG) + importStack.Truncate(); +#endif } size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; @@ -175,6 +178,9 @@ class mozJSComponentLoader final : public mozilla::ModuleLoader, JS::PersistentRootedScript thisObjectKey; char* location; nsCString resolvedURL; +#if defined(NIGHTLY_BUILD) || defined(DEBUG) + nsCString importStack; +#endif }; static size_t DataEntrySizeOfExcludingThis(const nsACString& aKey, ModuleEntry* const& aData,