diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
commit | d8bbc7858622b6d9c278469aab701ca0b609cddf (patch) | |
tree | eff41dc61d9f714852212739e6b3738b82a2af87 /devtools/server/tracer | |
parent | Releasing progress-linux version 125.0.3-1~progress7.99u1. (diff) | |
download | firefox-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.build | 2 | ||||
-rw-r--r-- | devtools/server/tracer/tests/browser/WorkerDebugger.tracer.js | 17 | ||||
-rw-r--r-- | devtools/server/tracer/tests/browser/browser_document_tracer.js | 18 | ||||
-rw-r--r-- | devtools/server/tracer/tests/xpcshell/test_tracer.js | 175 | ||||
-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, +}; |