summaryrefslogtreecommitdiffstats
path: root/devtools/server/tracer
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
commitd8bbc7858622b6d9c278469aab701ca0b609cddf (patch)
treeeff41dc61d9f714852212739e6b3738b82a2af87 /devtools/server/tracer
parentReleasing progress-linux version 125.0.3-1~progress7.99u1. (diff)
downloadfirefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz
firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/server/tracer')
-rw-r--r--devtools/server/tracer/moz.build2
-rw-r--r--devtools/server/tracer/tests/browser/WorkerDebugger.tracer.js17
-rw-r--r--devtools/server/tracer/tests/browser/browser_document_tracer.js18
-rw-r--r--devtools/server/tracer/tests/xpcshell/test_tracer.js175
-rw-r--r--devtools/server/tracer/tracer.sys.mjs (renamed from devtools/server/tracer/tracer.jsm)98
5 files changed, 205 insertions, 105 deletions
diff --git a/devtools/server/tracer/moz.build b/devtools/server/tracer/moz.build
index 26f7665018..40ded9ba8a 100644
--- a/devtools/server/tracer/moz.build
+++ b/devtools/server/tracer/moz.build
@@ -4,7 +4,7 @@
# 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/.
-DevToolsModules("tracer.jsm")
+DevToolsModules("tracer.sys.mjs")
XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell/xpcshell.toml"]
if CONFIG["MOZ_BUILD_APP"] != "mobile/android":
diff --git a/devtools/server/tracer/tests/browser/WorkerDebugger.tracer.js b/devtools/server/tracer/tests/browser/WorkerDebugger.tracer.js
index bd6e646b3b..ef15fd5cfb 100644
--- a/devtools/server/tracer/tests/browser/WorkerDebugger.tracer.js
+++ b/devtools/server/tracer/tests/browser/WorkerDebugger.tracer.js
@@ -1,6 +1,6 @@
"use strict";
-/* global global, loadSubScript */
+/* global global */
try {
// For some reason WorkerDebuggerGlobalScope.global doesn't expose JS variables
@@ -8,8 +8,11 @@ try {
const dbg = new Debugger(global);
const [debuggee] = dbg.getDebuggees();
- /* global startTracing, stopTracing, addTracingListener, removeTracingListener */
- loadSubScript("resource://devtools/server/tracer/tracer.jsm");
+ const { JSTracer } = ChromeUtils.importESModule(
+ "resource://devtools/server/tracer/tracer.sys.mjs",
+ { global: "contextual" }
+ );
+
const frames = [];
const listener = {
onTracingFrame(args) {
@@ -19,13 +22,13 @@ try {
return true;
},
};
- addTracingListener(listener);
- startTracing({ global, prefix: "testWorkerPrefix" });
+ JSTracer.addTracingListener(listener);
+ JSTracer.startTracing({ global, prefix: "testWorkerPrefix" });
debuggee.executeInGlobal("foo()");
- stopTracing();
- removeTracingListener(listener);
+ JSTracer.stopTracing();
+ JSTracer.removeTracingListener(listener);
// Send the frames to the main thread to do the assertions there.
postMessage(JSON.stringify(frames));
diff --git a/devtools/server/tracer/tests/browser/browser_document_tracer.js b/devtools/server/tracer/tests/browser/browser_document_tracer.js
index dcf2c9eb4d..d7d351d6dd 100644
--- a/devtools/server/tracer/tests/browser/browser_document_tracer.js
+++ b/devtools/server/tracer/tests/browser/browser_document_tracer.js
@@ -17,12 +17,10 @@ add_task(async function testTracingWorker() {
const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
- const {
- addTracingListener,
- removeTracingListener,
- startTracing,
- stopTracing,
- } = ChromeUtils.import("resource://devtools/server/tracer/tracer.jsm");
+ const { JSTracer } = ChromeUtils.importESModule(
+ "resource://devtools/server/tracer/tracer.sys.mjs",
+ { global: "shared" }
+ );
// We have to fake opening DevTools otherwise DebuggerNotificationObserver wouldn't work
// and the tracer wouldn't be able to trace the DOM events.
@@ -35,10 +33,10 @@ add_task(async function testTracingWorker() {
},
};
info("Register a tracing listener");
- addTracingListener(listener);
+ JSTracer.addTracingListener(listener);
info("Start tracing the iframe");
- startTracing({ global: content, traceDOMEvents: true });
+ JSTracer.startTracing({ global: content, traceDOMEvents: true });
info("Dispatch a click event on the iframe");
EventUtils.synthesizeMouseAtCenter(
@@ -58,8 +56,8 @@ add_task(async function testTracingWorker() {
is(lastFrame.formatedDisplayName, "λ bar");
is(lastFrame.currentDOMEvent, "setTimeoutCallback");
- stopTracing();
- removeTracingListener(listener);
+ JSTracer.stopTracing();
+ JSTracer.removeTracingListener(listener);
ChromeUtils.notifyDevToolsClosed();
});
diff --git a/devtools/server/tracer/tests/xpcshell/test_tracer.js b/devtools/server/tracer/tests/xpcshell/test_tracer.js
index 0f38052ba5..8435cb8691 100644
--- a/devtools/server/tracer/tests/xpcshell/test_tracer.js
+++ b/devtools/server/tracer/tests/xpcshell/test_tracer.js
@@ -3,8 +3,10 @@
"use strict";
-const { addTracingListener, removeTracingListener, startTracing, stopTracing } =
- ChromeUtils.import("resource://devtools/server/tracer/tracer.jsm");
+const { JSTracer } = ChromeUtils.importESModule(
+ "resource://devtools/server/tracer/tracer.sys.mjs",
+ { global: "shared" }
+);
add_task(async function () {
// Because this test uses evalInSandbox, we need to tweak the following prefs
@@ -30,13 +32,13 @@ add_task(async function testTracingContentGlobal() {
};
info("Register a tracing listener");
- addTracingListener(listener);
+ JSTracer.addTracingListener(listener);
const sandbox = Cu.Sandbox("https://example.com");
Cu.evalInSandbox("function bar() {}; function foo() {bar()};", sandbox);
info("Start tracing");
- startTracing({ global: sandbox, prefix: "testContentPrefix" });
+ JSTracer.startTracing({ global: sandbox, prefix: "testContentPrefix" });
Assert.equal(toggles.length, 1);
Assert.equal(toggles[0], true);
@@ -56,7 +58,7 @@ add_task(async function testTracingContentGlobal() {
Assert.ok(lastFrame.frame);
info("Stop tracing");
- stopTracing();
+ JSTracer.stopTracing();
Assert.equal(toggles.length, 2);
Assert.equal(toggles[1], false);
@@ -65,19 +67,19 @@ add_task(async function testTracingContentGlobal() {
Assert.equal(frames.length, 0);
info("Start tracing again, and recall code");
- startTracing({ global: sandbox, prefix: "testContentPrefix" });
+ JSTracer.startTracing({ global: sandbox, prefix: "testContentPrefix" });
sandbox.foo();
info("New traces are logged");
Assert.equal(frames.length, 2);
info("Unregister the listener and recall code");
- removeTracingListener(listener);
+ JSTracer.removeTracingListener(listener);
sandbox.foo();
info("No more traces are logged");
Assert.equal(frames.length, 2);
info("Stop tracing");
- stopTracing();
+ JSTracer.stopTracing();
});
add_task(async function testTracingJSMGlobal() {
@@ -103,10 +105,10 @@ add_task(async function testTracingJSMGlobal() {
);
info("Register a tracing listener");
- addTracingListener(listenerSandbox.listener);
+ JSTracer.addTracingListener(listenerSandbox.listener);
info("Start tracing");
- startTracing({ global: null, prefix: "testPrefix" });
+ JSTracer.startTracing({ global: null, prefix: "testPrefix" });
Assert.equal(listenerSandbox.toggles.length, 1);
Assert.equal(listenerSandbox.toggles[0], true);
@@ -131,11 +133,11 @@ add_task(async function testTracingJSMGlobal() {
Assert.ok(lastFrame.frame);
info("Stop tracing");
- stopTracing();
+ JSTracer.stopTracing();
Assert.equal(listenerSandbox.toggles.length, 2);
Assert.equal(listenerSandbox.toggles[1], false);
- removeTracingListener(listenerSandbox.listener);
+ JSTracer.removeTracingListener(listenerSandbox.listener);
});
add_task(async function testTracingValues() {
@@ -153,7 +155,7 @@ add_task(async function testTracingValues() {
}
info("Start tracing");
- startTracing({ global: sandbox, traceValues: true, loggingMethod });
+ JSTracer.startTracing({ global: sandbox, traceValues: true, loggingMethod });
info("Call some code");
sandbox.foo();
@@ -167,7 +169,7 @@ add_task(async function testTracingValues() {
);
info("Stop tracing");
- stopTracing();
+ JSTracer.stopTracing();
});
add_task(async function testTracingFunctionReturn() {
@@ -185,7 +187,11 @@ add_task(async function testTracingFunctionReturn() {
}
info("Start tracing");
- startTracing({ global: sandbox, traceFunctionReturn: true, loggingMethod });
+ JSTracer.startTracing({
+ global: sandbox,
+ traceFunctionReturn: true,
+ loggingMethod,
+ });
info("Call some code");
sandbox.foo();
@@ -198,7 +204,7 @@ add_task(async function testTracingFunctionReturn() {
Assert.stringContains(logs[4], "λ foo return");
info("Stop tracing");
- stopTracing();
+ JSTracer.stopTracing();
});
add_task(async function testTracingFunctionReturnAndValues() {
@@ -216,7 +222,7 @@ add_task(async function testTracingFunctionReturnAndValues() {
}
info("Start tracing");
- startTracing({
+ JSTracer.startTracing({
global: sandbox,
traceFunctionReturn: true,
traceValues: true,
@@ -236,7 +242,7 @@ add_task(async function testTracingFunctionReturnAndValues() {
Assert.stringContains(logs[6], "λ foo return undefined");
info("Stop tracing");
- stopTracing();
+ JSTracer.stopTracing();
});
add_task(async function testTracingStep() {
@@ -246,22 +252,23 @@ add_task(async function testTracingStep() {
function foo() {
bar(); /* line 3 */
second(); /* line 4 */
+ dump("foo\\n");
}
function bar() {
- let res; /* line 7 */
- if (1 === 1) { /* line 8 */
- res = "string"; /* line 9 */
+ let res; /* line 8 */
+ if (1 === 1) { /* line 9 */
+ res = "string"; /* line 10 */
} else {
res = "nope"
}
- return res; /* line 13 */
+ return res; /* line 14 */
};
function second() {
- let x = 0; /* line 16 */
- for (let i = 0; i < 2; i++) { /* line 17 */
- x++; /* line 18 */
+ let x = 0; /* line 17 */
+ for (let i = 0; i < 2; i++) { /* line 18 */
+ x++; /* line 19 */
}
- return null; /* line 20 */
+ return null; /* line 21 */
};
foo();`;
Cu.evalInSandbox(source, sandbox, null, "file.js", 1);
@@ -273,7 +280,7 @@ foo();`;
}
info("Start tracing");
- startTracing({
+ JSTracer.startTracing({
global: sandbox,
traceSteps: true,
loggingMethod,
@@ -287,47 +294,46 @@ foo();`;
Assert.stringContains(logs[1], "λ foo");
Assert.stringContains(logs[1], "file.js:3:3");
- // Each "step" only prints the location and nothing more
- Assert.stringContains(logs[2], "file.js:3:3");
-
- Assert.stringContains(logs[3], "λ bar");
- Assert.stringContains(logs[3], "file.js:6:16");
+ Assert.stringContains(logs[2], "λ bar");
+ Assert.stringContains(logs[2], "file.js:7:16");
- Assert.stringContains(logs[4], "file.js:8:7");
+ // Each "step" only prints the location and nothing more
+ Assert.stringContains(logs[3], "file.js:9:7");
- Assert.stringContains(logs[5], "file.js:9:5");
+ Assert.stringContains(logs[4], "file.js:10:5");
- Assert.stringContains(logs[6], "file.js:13:3");
+ Assert.stringContains(logs[5], "file.js:14:3");
- Assert.stringContains(logs[7], "file.js:4:3");
+ Assert.stringContains(logs[6], "file.js:4:3");
- Assert.stringContains(logs[8], "λ second");
- Assert.stringContains(logs[8], "file.js:15:19");
+ Assert.stringContains(logs[7], "λ second");
+ Assert.stringContains(logs[7], "file.js:16:19");
- Assert.stringContains(logs[9], "file.js:16:11");
+ Assert.stringContains(logs[8], "file.js:17:11");
// For loop
- Assert.stringContains(logs[10], "file.js:17:16");
+ Assert.stringContains(logs[9], "file.js:18:16");
- Assert.stringContains(logs[11], "file.js:17:19");
+ Assert.stringContains(logs[10], "file.js:18:19");
- Assert.stringContains(logs[12], "file.js:18:5");
+ Assert.stringContains(logs[11], "file.js:19:5");
- Assert.stringContains(logs[13], "file.js:17:26");
+ Assert.stringContains(logs[12], "file.js:18:26");
- Assert.stringContains(logs[14], "file.js:17:19");
+ Assert.stringContains(logs[13], "file.js:18:19");
- Assert.stringContains(logs[15], "file.js:18:5");
+ Assert.stringContains(logs[14], "file.js:19:5");
- Assert.stringContains(logs[16], "file.js:17:26");
+ Assert.stringContains(logs[15], "file.js:18:26");
- Assert.stringContains(logs[17], "file.js:17:19");
+ Assert.stringContains(logs[16], "file.js:18:19");
// End of for loop
- Assert.stringContains(logs[18], "file.js:20:3");
+ Assert.stringContains(logs[17], "file.js:21:3");
+ Assert.stringContains(logs[18], "file.js:5:3");
info("Stop tracing");
- stopTracing();
+ JSTracer.stopTracing();
});
add_task(async function testTracingPauseOnStep() {
@@ -348,7 +354,7 @@ add_task(async function testTracingPauseOnStep() {
}
info("Start tracing without pause");
- startTracing({
+ JSTracer.startTracing({
global: sandbox,
loggingMethod,
});
@@ -366,13 +372,13 @@ add_task(async function testTracingPauseOnStep() {
Assert.equal(sandbox.counter, 1);
info("Stop tracing");
- stopTracing();
+ JSTracer.stopTracing();
logs.length = 0;
sandbox.counter = 0;
info("Start tracing with 0ms pause");
- startTracing({
+ JSTracer.startTracing({
global: sandbox,
pauseOnStep: 0,
loggingMethod,
@@ -415,13 +421,13 @@ add_task(async function testTracingPauseOnStep() {
Assert.equal(sandbox.counter, 1);
info("Stop tracing");
- stopTracing();
+ JSTracer.stopTracing();
logs.length = 0;
sandbox.counter = 0;
info("Start tracing with 250ms pause");
- startTracing({
+ JSTracer.startTracing({
global: sandbox,
pauseOnStep: 250,
loggingMethod,
@@ -463,7 +469,7 @@ add_task(async function testTracingPauseOnStep() {
Assert.greater(Cu.now() - startTimestamp, 250);
info("Stop tracing");
- stopTracing();
+ JSTracer.stopTracing();
});
add_task(async function testTracingFilterSourceUrl() {
@@ -485,7 +491,7 @@ add_task(async function testTracingFilterSourceUrl() {
}
info("Start tracing");
- startTracing({
+ JSTracer.startTracing({
global: sandbox,
filterFrameSourceUrl: "second",
loggingMethod,
@@ -500,5 +506,60 @@ add_task(async function testTracingFilterSourceUrl() {
Assert.stringContains(logs[1], "second.js:1:18");
info("Stop tracing");
- stopTracing();
+ JSTracer.stopTracing();
+});
+
+add_task(async function testTracingAllGlobals() {
+ // Test the `traceAllGlobals` flag
+
+ // Create two distinct globals in order to verify that both are traced
+ const sandbox1 = Cu.Sandbox("https://example.com");
+ const sandbox2 = Cu.Sandbox("https://example.com");
+
+ const source1 = `function foo() { bar(); }`;
+ Cu.evalInSandbox(source1, sandbox1, null, "sandbox1.js", 1);
+
+ const source2 = `function bar() { }`;
+ Cu.evalInSandbox(source2, sandbox2, null, "sandbox2.js", 1);
+ // Expose `bar` from sandbox2 as global in sandbox1, so that `foo` from sandbox1 can call it.
+ sandbox1.bar = sandbox2.bar;
+
+ // Pass an override method to catch all strings tentatively logged to stdout
+ //
+ // But in this test, we have to evaluate it in a special sandbox which will be ignored by the tracer.
+ // Otherwise, the tracer would do an infinite loop on this loggingMethod.
+ const ignoredGlobal = new Cu.Sandbox(null, { invisibleToDebugger: true });
+ const loggingMethodString = `
+ var logs = [];
+ function loggingMethod(str) {
+ logs.push(str);
+ };
+ `;
+ Cu.evalInSandbox(
+ loggingMethodString,
+ ignoredGlobal,
+ null,
+ "loggin-method.js",
+ 1
+ );
+ const { loggingMethod, logs } = ignoredGlobal;
+
+ info("Start tracing on all globals");
+ JSTracer.startTracing({
+ traceAllGlobals: true,
+ loggingMethod,
+ });
+
+ // Call some code while being careful to not call anything else which may be traced
+ sandbox1.foo();
+
+ JSTracer.stopTracing();
+
+ Assert.equal(logs.length, 4);
+ Assert.equal(logs[0], "Start tracing JavaScript\n");
+ Assert.stringContains(logs[1], "λ foo");
+ Assert.stringContains(logs[1], "sandbox1.js:1:18");
+ Assert.stringContains(logs[2], "λ bar");
+ Assert.stringContains(logs[2], "sandbox2.js:1:18");
+ Assert.equal(logs[3], "Stop tracing JavaScript\n");
});
diff --git a/devtools/server/tracer/tracer.jsm b/devtools/server/tracer/tracer.sys.mjs
index 955b25fe3a..6fe1334f2b 100644
--- a/devtools/server/tracer/tracer.jsm
+++ b/devtools/server/tracer/tracer.sys.mjs
@@ -17,17 +17,6 @@
* `JavaScriptTracer.onEnterFrame` method is hot codepath and should be reviewed accordingly.
*/
-"use strict";
-
-const EXPORTED_SYMBOLS = [
- "startTracing",
- "stopTracing",
- "addTracingListener",
- "removeTracingListener",
- "NEXT_INTERACTION_MESSAGE",
- "DOM_MUTATIONS",
-];
-
const NEXT_INTERACTION_MESSAGE =
"Waiting for next user interaction before tracing (next mousedown or keydown event)";
@@ -93,7 +82,8 @@ const customLazy = {
get DistinctCompartmentDebugger() {
const { addDebuggerToGlobal } = ChromeUtils.importESModule(
- "resource://gre/modules/jsdebugger.sys.mjs"
+ "resource://gre/modules/jsdebugger.sys.mjs",
+ { global: "contextual" }
);
const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
const debuggerSandbox = Cu.Sandbox(systemPrincipal, {
@@ -117,12 +107,10 @@ const customLazy = {
* @param {Object} options.global
* The tracer only log traces related to the code executed within this global.
* When omitted, it will default to the options object's global.
+ * @param {Boolean} options.traceAllGlobals
+ * When set to true, this will trace all the globals running in the current thread.
* @param {String} options.prefix
* Optional string logged as a prefix to all traces.
- * @param {Debugger} options.dbg
- * Optional spidermonkey's Debugger instance.
- * This allows devtools to pass a custom instance and ease worker support
- * where we can't load jsdebugger.sys.mjs.
* @param {Boolean} options.loggingMethod
* Optional setting to use something else than `dump()` to log traces to stdout.
* This is mostly used by tests.
@@ -167,10 +155,29 @@ class JavaScriptTracer {
this.abortController = new AbortController();
}
- // By default, we would trace only JavaScript related to caller's global.
- // As there is no way to compute the caller's global default to the global of the
- // mandatory options argument.
- this.tracedGlobal = options.global || Cu.getGlobalForObject(options);
+ if (options.traceAllGlobals) {
+ this.traceAllGlobals = true;
+ if (options.traceOnNextInteraction) {
+ throw new Error(
+ "Tracing all globals and waiting for next user interaction are not yet compatible"
+ );
+ }
+ if (this.traceDOMEvents) {
+ throw new Error(
+ "Tracing all globals and DOM Events are not yet compatible"
+ );
+ }
+ if (options.global) {
+ throw new Error(
+ "'global' option should be omitted when using 'traceAllGlobals'"
+ );
+ }
+ } else {
+ // By default, we would trace only JavaScript related to caller's global.
+ // As there is no way to compute the caller's global default to the global of the
+ // mandatory options argument.
+ this.tracedGlobal = options.global || Cu.getGlobalForObject(options);
+ }
// Instantiate a brand new Debugger API so that we can trace independently
// of all other DevTools operations. i.e. we can pause while tracing without any interference.
@@ -495,6 +502,20 @@ class JavaScriptTracer {
* This allows to implement tracing independently of DevTools.
*/
makeDebugger() {
+ if (this.traceAllGlobals) {
+ const dbg = new customLazy.DistinctCompartmentDebugger();
+ dbg.addAllGlobalsAsDebuggees();
+
+ // addAllGlobalAsAdebuggees will also add the global for this module...
+ // which we have to prevent tracing!
+ // eslint-disable-next-line mozilla/reject-globalThis-modification
+ dbg.removeDebuggee(globalThis);
+
+ // Add any future global being created later
+ dbg.onNewGlobalObject = g => dbg.addDebuggee(g);
+ return dbg;
+ }
+
// When this code runs in the worker thread, Cu isn't available
// and we don't have system principal anyway in this context.
const { isSystemPrincipal } =
@@ -637,6 +658,11 @@ class JavaScriptTracer {
currentDOMEvent: this.currentDOMEvent,
});
}
+ // Bail out early if any listener stopped tracing as the Frame object
+ // will be no longer usable by any other code.
+ if (!this.isTracing) {
+ return;
+ }
}
}
@@ -647,13 +673,26 @@ class JavaScriptTracer {
}
if (this.traceSteps) {
+ // Collect the location notified via onTracingFrame to also avoid redundancy between similar location
+ // between onEnterFrame and onStep notifications.
+ let { lineNumber: lastLine, columnNumber: lastColumn } =
+ frame.script.getOffsetMetadata(frame.offset);
+
frame.onStep = () => {
// Spidermonkey steps on many intermediate positions which don't make sense to the user.
// `isStepStart` is close to each statement start, which is meaningful to the user.
- const { isStepStart } = frame.script.getOffsetMetadata(frame.offset);
+ const { isStepStart, lineNumber, columnNumber } =
+ frame.script.getOffsetMetadata(frame.offset);
if (!isStepStart) {
return;
}
+ // onStep may be called on many instructions related to the same line and colunm.
+ // Avoid notifying duplicated steps if we stepped on the exact same location.
+ if (lastLine == lineNumber && lastColumn == columnNumber) {
+ return;
+ }
+ lastLine = lineNumber;
+ lastColumn = columnNumber;
shouldLogToStdout = true;
if (listeners.size > 0) {
@@ -1039,12 +1078,11 @@ function syncPause(duration) {
});
}
-// This JSM may be execute as CommonJS when loaded in the worker thread
-if (typeof module == "object") {
- module.exports = {
- startTracing,
- stopTracing,
- addTracingListener,
- removeTracingListener,
- };
-}
+export const JSTracer = {
+ startTracing,
+ stopTracing,
+ addTracingListener,
+ removeTracingListener,
+ NEXT_INTERACTION_MESSAGE,
+ DOM_MUTATIONS,
+};