180 lines
5.7 KiB
JavaScript
180 lines
5.7 KiB
JavaScript
/* 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/. */
|
|
|
|
"use strict";
|
|
|
|
const { JSTracer } = ChromeUtils.importESModule(
|
|
"resource://devtools/server/tracer/tracer.sys.mjs",
|
|
{ global: "shared" }
|
|
);
|
|
|
|
let lineToTrace;
|
|
|
|
const fileContents = new Map();
|
|
|
|
function getFileContent(url) {
|
|
let content = fileContents.get(url);
|
|
if (content) {
|
|
return content;
|
|
}
|
|
content = readURI(url).split("\n");
|
|
fileContents.set(url, content);
|
|
return content;
|
|
}
|
|
|
|
function isNestedFrame(frame, topFrame) {
|
|
if (frame.older) {
|
|
// older will be a Debugger.Frame
|
|
while ((frame = frame.older)) {
|
|
if (frame == topFrame) {
|
|
return true;
|
|
}
|
|
}
|
|
} else if (frame.olderSavedFrame) {
|
|
// olderSavedFrame will be a SavedStack object
|
|
frame = frame.olderSavedFrame;
|
|
const { lineNumber, columnNumber } = topFrame.script.getOffsetMetadata(
|
|
top.offset
|
|
);
|
|
while ((frame = frame.parent || frame.asyncParent)) {
|
|
if (
|
|
frame.source == topFrame.script.source.url &&
|
|
frame.line == lineNumber &&
|
|
frame.column == columnNumber
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Store the top most frame running at `lineToTrace` line.
|
|
// We will then log all frames which are children of this top one.
|
|
let initialFrame = null;
|
|
let previousSourceUrl = null;
|
|
|
|
function traceFrame({ frame }) {
|
|
const { script } = frame;
|
|
const { lineNumber, columnNumber } = script.getOffsetMetadata(frame.offset);
|
|
if (lineToTrace) {
|
|
if (lineNumber == lineToTrace) {
|
|
// Stop the first tracer started from `exports.start()` which was only waiting for the particular test script line to run
|
|
JSTracer.stopTracing();
|
|
|
|
const { url } = script.source;
|
|
const filename = url.substr(url.lastIndexOf("/") + 1);
|
|
const line = getFileContent(url)[lineNumber - 1];
|
|
logStep(`Start tracing ${filename} @ ${lineNumber} :: ${line}`);
|
|
previousSourceUrl = url;
|
|
// Restart a new tracer which would go track all the globals and not restrict to the test script.
|
|
const tracerOptions = {
|
|
// Ensure tracing all globals in this thread
|
|
traceAllGlobals: true,
|
|
// Ensure tracing each execution within functions (and not only function calls)
|
|
traceSteps: true,
|
|
};
|
|
lineToTrace = null;
|
|
JSTracer.startTracing(tracerOptions);
|
|
}
|
|
return false;
|
|
}
|
|
// We executed the test line we wanted to trace and now log all frames via a second tracer instance
|
|
|
|
// First pick up the very first executed frame, so that we can trace all nested frame from this one.
|
|
if (!initialFrame) {
|
|
initialFrame = frame;
|
|
} else if (initialFrame.terminated) {
|
|
// If the traced top frame completed its execution, stop tracing.
|
|
// Note that terminated will only be true once any possibly asynchronous work of the traced function
|
|
// is done executing.
|
|
logStep("End of execution");
|
|
exports.stop();
|
|
return false;
|
|
} else if (!initialFrame.onStack) {
|
|
// If we are done executing the traced Frame, it will be declared out of the stack.
|
|
// By we should keep tracing as, if the traced Frame involves async work, it may be later restored onto the stack.
|
|
return false;
|
|
} else if (frame != initialFrame && !isNestedFrame(frame, initialFrame)) {
|
|
// Then, only log frame which ultimately related to this first frame we picked.
|
|
// Because of asynchronous calls and concurrent event loops, we may have in-between frames
|
|
// that we ignore which relates to another event loop and another top frame.
|
|
//
|
|
// Note that the tracer may notify us about the exact same Frame object multiple times.
|
|
// (its offset/location will change, but the object will be the same)
|
|
return false;
|
|
}
|
|
|
|
const { url } = script.source;
|
|
|
|
// Print the full source URL each time we start tracing a new source
|
|
if (previousSourceUrl && previousSourceUrl !== url) {
|
|
logStep("");
|
|
logStep(url);
|
|
// Log a grey line separator
|
|
logStep(`\x1b[2m` + `\u2500`.repeat(url.length) + `\x1b[0m`);
|
|
previousSourceUrl = url;
|
|
}
|
|
|
|
const line = getFileContent(url)[lineNumber - 1];
|
|
// Grey out the beginning of the line, before frame's column,
|
|
// and display an arrow before displaying the rest of the line.
|
|
const code =
|
|
"\x1b[2m" +
|
|
line.substr(0, columnNumber - 1) +
|
|
"\x1b[0m" +
|
|
"\u21A6 " +
|
|
line.substr(columnNumber - 1);
|
|
|
|
const position = (lineNumber + ":" + columnNumber).padEnd(7);
|
|
logStep(`${position} \u007C ${code}`);
|
|
|
|
// Disable builtin tracer logging
|
|
return false;
|
|
}
|
|
|
|
function logStep(message) {
|
|
dump(` \x1b[2m[STEP]\x1b[0m ${message}\n`);
|
|
}
|
|
|
|
const tracingListener = {
|
|
onTracingFrame: traceFrame,
|
|
onTracingFrameStep: traceFrame,
|
|
};
|
|
|
|
exports.start = function (testGlobal, testUrl, line) {
|
|
lineToTrace = line;
|
|
const tracerOptions = {
|
|
global: testGlobal,
|
|
// Ensure tracing each execution within functions (and not only function calls)
|
|
traceSteps: true,
|
|
// Only trace the running test and nothing else
|
|
filterFrameSourceUrl: testUrl,
|
|
};
|
|
JSTracer.startTracing(tracerOptions);
|
|
JSTracer.addTracingListener(tracingListener);
|
|
};
|
|
|
|
exports.stop = function () {
|
|
JSTracer.stopTracing();
|
|
JSTracer.removeTracingListener(tracingListener);
|
|
};
|
|
|
|
function readURI(uri) {
|
|
const { NetUtil } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/NetUtil.sys.mjs",
|
|
{ global: "contextual" }
|
|
);
|
|
const stream = NetUtil.newChannel({
|
|
uri: NetUtil.newURI(uri, "UTF-8"),
|
|
loadUsingSystemPrincipal: true,
|
|
}).open();
|
|
const count = stream.available();
|
|
const data = NetUtil.readInputStreamToString(stream, count, {
|
|
charset: "UTF-8",
|
|
});
|
|
|
|
stream.close();
|
|
return data;
|
|
}
|