Source code

Revision control

Copy as Markdown

Other Tools

/* 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/. */
/**
* Helper code to play with the javascript thread
**/
function getSandboxWithDebuggerSymbol() {
// Bug 1835268 - Changing this to an ES module import currently throws an
// assertion in test_javascript_tracer.js in debug builds.
const { addDebuggerToGlobal } = ChromeUtils.import(
);
const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
const debuggerSandbox = Cu.Sandbox(systemPrincipal, {
// This sandbox is also reused for ChromeDebugger implementation.
// As we want to load the `Debugger` API for debugging chrome contexts,
// we have to ensure loading it in a distinct compartment from its debuggee.
freshCompartment: true,
invisibleToDebugger: true,
});
addDebuggerToGlobal(debuggerSandbox);
return debuggerSandbox;
}
/**
* Implementation of a Javascript tracer logging traces to stdout.
*
* To be used like this:
const { traceAllJSCalls } = ChromeUtils.importESModule(
);
const jsTracer = traceAllJSCalls();
[... execute some code to tracer ...]
jsTracer.stop();
* @param prefix String
* Optional, if passed, this will be displayed in front of each
* line reporting a new frame execution.
* @param pause Number
* Optional, if passed, hold off each frame for `pause` ms,
* by letting the other event loops run in between.
* Be careful that it can introduce unexpected race conditions
* that can't necessarily be reproduced without this.
*/
export function traceAllJSCalls({ prefix = "", pause } = {}) {
const debuggerSandbox = getSandboxWithDebuggerSymbol();
debuggerSandbox.Services = Services;
const f = Cu.evalInSandbox(
"(" +
function (pauseInMs, prefixString) {
const dbg = new Debugger();
// Add absolutely all the globals...
dbg.addAllGlobalsAsDebuggees();
// ...but avoid tracing this sandbox code
const global = Cu.getGlobalForObject(this);
dbg.removeDebuggee(global);
// Add all globals created later on
dbg.onNewGlobalObject = g => dbg.addDebuggee(g);
function formatDisplayName(frame) {
if (frame.type === "call") {
const callee = frame.callee;
return callee.name || callee.userDisplayName || callee.displayName;
}
return `(${frame.type})`;
}
function stop() {
dbg.onEnterFrame = undefined;
dbg.removeAllDebuggees();
}
global.stop = stop;
let depth = 0;
dbg.onEnterFrame = frame => {
if (depth == 100) {
dump(
"Looks like an infinite loop? We stop the js tracer, but code may still be running!\n"
);
stop();
return;
}
const { script } = frame;
const { lineNumber, columnNumber } = script.getOffsetMetadata(
frame.offset
);
const padding = new Array(depth).join(" ");
dump(
`${prefixString}${padding}--[${frame.implementation}]--> ${
script.source.url
} @ ${lineNumber}:${columnNumber} - ${formatDisplayName(frame)}\n`
);
depth++;
frame.onPop = () => {
depth--;
};
// Optionaly pause the frame execute by letting the other event loop to run in between.
if (typeof pauseInMs == "number") {
let freeze = true;
const timer = Cc["@mozilla.org/timer;1"].createInstance(
Ci.nsITimer
);
timer.initWithCallback(
() => {
freeze = false;
},
pauseInMs,
Ci.nsITimer.TYPE_ONE_SHOT
);
Services.tm.spinEventLoopUntil("debugger-slow-motion", function () {
return !freeze;
});
}
};
return { stop };
} +
")",
debuggerSandbox,
undefined,
"debugger-javascript-tracer",
1,
/* enforceFilenameRestrictions */ false
);
f(pause, prefix);
return {
stop() {
debuggerSandbox.stop();
},
};
}