Bug 1398198 - browser_startup.js should show the stack when a JS file was loaded earlier than expected, r=felipe,mccr8.
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
|
prefs =
|
||||||
|
browser.startup.record=true
|
||||||
support-files =
|
support-files =
|
||||||
head.js
|
head.js
|
||||||
[browser_appmenu_reflows.js]
|
[browser_appmenu_reflows.js]
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* Set this to true only for debugging purpose; it makes the output noisy. */
|
||||||
|
const kDumpAllStacks = false;
|
||||||
|
|
||||||
const startupPhases = {
|
const startupPhases = {
|
||||||
// For app-startup, we have a whitelist of acceptable JS files.
|
// For app-startup, we have a whitelist of acceptable JS files.
|
||||||
// Anything loaded during app-startup must have a compelling reason
|
// 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;
|
let startupRecorder = Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject;
|
||||||
await startupRecorder.done;
|
await startupRecorder.done;
|
||||||
|
|
||||||
|
let loader = Cc["@mozilla.org/moz/jsloader;1"].getService(Ci.xpcIJSModuleLoader);
|
||||||
|
let componentStacks = new Map();
|
||||||
let data = startupRecorder.data.code;
|
let data = startupRecorder.data.code;
|
||||||
// Keep only the file name for components, as the path is an absolute file
|
// Keep only the file name for components, as the path is an absolute file
|
||||||
// URL rather than a resource:// URL like for modules.
|
// URL rather than a resource:// URL like for modules.
|
||||||
for (let phase in data) {
|
for (let phase in data) {
|
||||||
data[phase].components =
|
data[phase].components =
|
||||||
data[phase].components.map(f => f.replace(/.*\//, ""))
|
data[phase].components.map(uri => {
|
||||||
.filter(c => c != "startupRecorder.js");
|
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,
|
// 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
|
// 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
|
// previous phase, it wasn't loaded during any of the previous phases
|
||||||
// either, and is new in the current phase.
|
// 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}`);
|
info(`${scriptType} loaded ${phase}: ${f}`);
|
||||||
|
if (kDumpAllStacks)
|
||||||
|
printStack(scriptType, f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
previous = phase;
|
previous = phase;
|
||||||
@@ -193,6 +211,7 @@ add_task(async function() {
|
|||||||
`should have no unexpected ${scriptType} loaded ${phase}`);
|
`should have no unexpected ${scriptType} loaded ${phase}`);
|
||||||
for (let script of loadedList[scriptType]) {
|
for (let script of loadedList[scriptType]) {
|
||||||
ok(false, `unexpected ${scriptType}: ${script}`);
|
ok(false, `unexpected ${scriptType}: ${script}`);
|
||||||
|
printStack(scriptType, script);
|
||||||
}
|
}
|
||||||
is(whitelist[scriptType].size, 0,
|
is(whitelist[scriptType].size, 0,
|
||||||
`all ${scriptType} whitelist entries should have been used`);
|
`all ${scriptType} whitelist entries should have been used`);
|
||||||
@@ -205,7 +224,10 @@ add_task(async function() {
|
|||||||
if (blacklist) {
|
if (blacklist) {
|
||||||
for (let scriptType in blacklist) {
|
for (let scriptType in blacklist) {
|
||||||
for (let file of blacklist[scriptType]) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ startupRecorder.prototype = {
|
|||||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
|
||||||
|
|
||||||
record(name) {
|
record(name) {
|
||||||
|
if (!Services.prefs.getBoolPref("browser.startup.record", false))
|
||||||
|
return;
|
||||||
|
|
||||||
this.data.code[name] = {
|
this.data.code[name] = {
|
||||||
components: this.loader.loadedComponents(),
|
components: this.loader.loadedComponents(),
|
||||||
modules: this.loader.loadedModules(),
|
modules: this.loader.loadedModules(),
|
||||||
@@ -81,6 +84,12 @@ startupRecorder.prototype = {
|
|||||||
Services.obs.removeObserver(this, topic);
|
Services.obs.removeObserver(this, topic);
|
||||||
|
|
||||||
if (topic == "sessionstore-windows-restored") {
|
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
|
// We use idleDispatchToMainThread here to record the set of
|
||||||
// loaded scripts after we are fully done with startup and ready
|
// loaded scripts after we are fully done with startup and ready
|
||||||
// to react to user events.
|
// to react to user events.
|
||||||
|
|||||||
@@ -8,11 +8,17 @@
|
|||||||
[scriptable, builtinclass, uuid(4f94b21f-2920-4bd9-8251-5fb60fb054b2)]
|
[scriptable, builtinclass, uuid(4f94b21f-2920-4bd9-8251-5fb60fb054b2)]
|
||||||
interface xpcIJSModuleLoader : nsISupports
|
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.
|
// to be used for production code.
|
||||||
void loadedModules([optional] out unsigned long length,
|
void loadedModules([optional] out unsigned long length,
|
||||||
[retval, array, size_is(length)] out string aModules);
|
[retval, array, size_is(length)] out string aModules);
|
||||||
|
|
||||||
void loadedComponents([optional] out unsigned long length,
|
void loadedComponents([optional] out unsigned long length,
|
||||||
[retval, array, size_is(length)] out string aComponents);
|
[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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -433,6 +433,12 @@ mozJSComponentLoader::LoadModule(FileLocation& aFile)
|
|||||||
return nullptr;
|
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
|
// Cache this module for later
|
||||||
mModules.Put(spec, entry);
|
mModules.Put(spec, entry);
|
||||||
|
|
||||||
@@ -1000,6 +1006,52 @@ NS_IMETHODIMP mozJSComponentLoader::LoadedComponents(uint32_t* length,
|
|||||||
return NS_OK;
|
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*
|
static JSObject*
|
||||||
ResolveModuleObjectPropertyById(JSContext* aCx, HandleObject aModObj, HandleId id)
|
ResolveModuleObjectPropertyById(JSContext* aCx, HandleObject aModObj, HandleId id)
|
||||||
{
|
{
|
||||||
@@ -1100,6 +1152,13 @@ mozJSComponentLoader::ImportInto(const nsACString& aLocation,
|
|||||||
return NS_ERROR_FILE_NOT_FOUND;
|
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;
|
mod = newEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -163,6 +163,9 @@ class mozJSComponentLoader final : public mozilla::ModuleLoader,
|
|||||||
obj = nullptr;
|
obj = nullptr;
|
||||||
thisObjectKey = nullptr;
|
thisObjectKey = nullptr;
|
||||||
location = nullptr;
|
location = nullptr;
|
||||||
|
#if defined(NIGHTLY_BUILD) || defined(DEBUG)
|
||||||
|
importStack.Truncate();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
|
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
|
||||||
@@ -175,6 +178,9 @@ class mozJSComponentLoader final : public mozilla::ModuleLoader,
|
|||||||
JS::PersistentRootedScript thisObjectKey;
|
JS::PersistentRootedScript thisObjectKey;
|
||||||
char* location;
|
char* location;
|
||||||
nsCString resolvedURL;
|
nsCString resolvedURL;
|
||||||
|
#if defined(NIGHTLY_BUILD) || defined(DEBUG)
|
||||||
|
nsCString importStack;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
static size_t DataEntrySizeOfExcludingThis(const nsACString& aKey, ModuleEntry* const& aData,
|
static size_t DataEntrySizeOfExcludingThis(const nsACString& aKey, ModuleEntry* const& aData,
|
||||||
|
|||||||
Reference in New Issue
Block a user