/* 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 . */ // Tests the Javascript Tracing feature via the Web Console :trace command. "use strict"; const TEST_URI = `data:text/html;charset=utf-8,

Testing trace command

`; add_task(async function testBasicRecord() { await pushPref("devtools.debugger.features.javascript-tracing", true); const hud = await openNewTabAndConsole(TEST_URI); ok(hud, "web console opened"); info("Test unsupported param error message"); let msg = await evaluateExpressionInConsole( hud, ":trace --unsupported-param", "console-api" ); is( msg.textContent.trim(), ":trace command doesn't support 'unsupported-param' argument." ); info("Test the help argument"); msg = await evaluateExpressionInConsole(hud, ":trace --help", "console-api"); ok(msg.textContent.includes("Toggles the JavaScript tracer")); info("Test toggling the tracer ON"); // Pass `console-api` specific classname as the command results don't log anything. // Instead a JSTRACER_STATE resource logs a console-api message. msg = await evaluateExpressionInConsole( hud, ":trace --logMethod console --prefix foo --returns --values --on-next-interaction", "console-api" ); is( msg.textContent.trim(), "Waiting for next user interaction before tracing (next mousedown or keydown event)" ); info("Trigger some code before the user interaction"); await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { content.wrappedJSObject.someNoise(); }); info("Simulate a user interaction by trigerring a key event on the page"); await BrowserTestUtils.synthesizeKey("a", {}, gBrowser.selectedBrowser); info("Trigger some code to log some traces"); await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { content.wrappedJSObject.main("arg", 2); }); info("Ensure a message notified about the tracer actual start"); await waitFor( () => !!findConsoleAPIMessage(hud, `Started tracing to Web Console`) ); // Assert that we also see the custom prefix, as well as function arguments await waitFor( () => !!findTracerMessages(hud, `foo: ⟶ interpreter λ main("arg", 2)`).length ); is( findTracerMessages(hud, `someNoise`).length, 0, "The code running before the key press should not be traced" ); await waitFor( () => !!findTracerMessages(hud, `foo: ⟵ λ main return 42`).length, "Got the function returns being logged, with the returned value" ); // But now that the tracer is active, we will be able to log this call to someNoise await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { content.wrappedJSObject.someNoise(); }); await waitFor( () => !!findTracerMessages(hud, `foo: ⟶ interpreter λ someNoise()`).length ); info("Test toggling the tracer OFF"); msg = await evaluateExpressionInConsole(hud, ":trace", "console-api"); is(msg.textContent.trim(), "Stopped tracing"); info("Clear past traces"); hud.ui.clearOutput(); await waitFor( () => !findTracerMessages(hud, `foo: ⟶ interpreter λ main("arg", 2)`).length ); ok("Console was cleared"); info("Trigger some code again"); await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { content.wrappedJSObject.main(); }); // Let some time for traces to appear await wait(500); ok( !findTracerMessages(hud, `foo: ⟶ interpreter λ main("arg", 2)`).length, "We really stopped recording traces, and no trace appear in the console" ); }); add_task(async function testLimitedRecord() { await pushPref("devtools.debugger.features.javascript-tracing", true); const jsCode = `function foo1() {foo2()}; function foo2() {foo3()}; function foo3() {}; function bar() {}`; const hud = await openNewTabAndConsole( "data:text/html," + encodeURIComponent(``) ); ok(hud, "web console opened"); info("Test toggling the tracer ON"); // Pass `console-api` specific classname as the command results aren't logged as "result". // Instead the frontend log a message as a console API message. await evaluateExpressionInConsole( hud, ":trace --logMethod console --max-depth 1", "console-api" ); info("Execute some code trigerring the tracer"); await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { content.wrappedJSObject.foo1(); }); await waitFor(() => !!findTracerMessages(hud, `λ foo1`).length); ok(true, "foo1 trace was printed to console"); is( findTracerMessages(hud, `λ foo2`).length, 0, "We only see the first function thanks to the depth limit" ); info("Test toggling the tracer OFF"); await evaluateExpressionInConsole(hud, ":trace", "console-api"); info("Clear past traces"); hud.ui.clearOutput(); await waitFor(() => !findTracerMessages(hud, `λ foo1`).length); ok("Console was cleared"); info("Re-enable the tracing, but with a max record limit"); await evaluateExpressionInConsole( hud, ":trace --logMethod console --max-records 1", "console-api" ); info("Trigger some code again"); await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { content.wrappedJSObject.foo1(); content.wrappedJSObject.bar(); }); await waitFor(() => !!findTracerMessages(hud, `λ foo3`).length); await waitFor(() => !!findConsoleAPIMessage(hud, `Stopped tracing`)); is( findTracerMessages(hud, `λ foo1`).length, 1, "Found the whole depth for the first event loop 1/3" ); is( findTracerMessages(hud, `λ foo2`).length, 1, "Found the whole depth for the first event loop 2/3" ); is( findTracerMessages(hud, `λ foo3`).length, 1, "Found the whole depth for the first event loop 3/3" ); is( findTracerMessages(hud, `λ bar`).length, 0, "But the second event loop was ignored" ); ok( !!findConsoleAPIMessage( hud, `Stopped tracing (reason: max-records)`, ".console-api" ), "And the tracer was automatically stopped" ); info("Enable tracing one last time without any restriction"); await evaluateExpressionInConsole( hud, ":trace --logMethod console", "console-api" ); info("Trigger various code again"); await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { content.wrappedJSObject.foo1(); content.wrappedJSObject.bar(); }); await waitFor(() => !!findTracerMessages(hud, `λ foo1`).length); await waitFor(() => !!findTracerMessages(hud, `λ foo2`).length); await waitFor(() => !!findTracerMessages(hud, `λ foo3`).length); await waitFor(() => !!findTracerMessages(hud, `λ bar`).length); ok(true, "All traces were printed to console"); }); add_task(async function testDOMMutations() { await pushPref("devtools.debugger.features.javascript-tracing", true); const testScript = `/* this will be line 1 */ function add() { const element = document.createElement("hr"); document.body.appendChild(element); } function attributes() { document.querySelector("hr").setAttribute("hidden", "true"); } function remove() { document.querySelector("hr").remove(); } /* Fake a real file name for this inline script */ //# sourceURL=fake.js `; const hud = await openNewTabAndConsole( `data:text/html,test-page` ); ok(hud, "web console opened"); let msg = await evaluateExpressionInConsole( hud, ":trace --dom-mutations foo", "console-api" ); is( msg.textContent.trim(), ":trace --dom-mutations only accept a list of strings whose values can be: add,attributes,remove" ); msg = await evaluateExpressionInConsole( hud, ":trace --dom-mutations 42", "console-api" ); is( msg.textContent.trim(), ":trace --dom-mutations accept only no arguments, or a list mutation type strings (add,attributes,remove)" ); info("Test toggling the tracer ON"); // Pass `console-api` specific classname as the command results aren't logged as "result". // Instead the frontend log a message as a console API message. await evaluateExpressionInConsole( hud, ":trace --logMethod console --dom-mutations", "console-api" ); info("Trigger some code to add a DOM Element"); await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { content.wrappedJSObject.add(); }); let traceNode = await waitFor( () => findTracerMessages(hud, `DOM Mutation | add
`)[0], "Wait for the DOM Mutation trace for DOM element creation" ); is( traceNode.querySelector(".message-location").textContent, "fake.js:4:19", "Add Mutation location is correct" ); info("Trigger some code to modify attributes of a DOM Element"); await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { content.wrappedJSObject.attributes(); }); traceNode = await waitFor( () => findTracerMessages( hud, `DOM Mutation | attributes ` )[0], "Wait for the DOM Mutation trace for DOM attributes modification" ); is( traceNode.querySelector(".message-location").textContent, "fake.js:7:34", "Attributes Mutation location is correct" ); info("Trigger some code to remove a DOM Element"); await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { content.wrappedJSObject.remove(); }); traceNode = await waitFor( () => findTracerMessages(hud, `DOM Mutation | remove `)[0], "Wait for the DOM Mutation trace for DOM Element removal" ); is( traceNode.querySelector(".message-location").textContent, "fake.js:10:34", "Remove Mutation location is correct" ); info("Stop tracing all mutations"); await evaluateExpressionInConsole(hud, ":trace", "console-api"); info("Clear past traces"); hud.ui.clearOutput(); await waitFor( () => !findTracerMessages(hud, `remove()`).length ); ok("Console was cleared"); info("Re-enable the tracing, but only with a subset of mutations"); await evaluateExpressionInConsole( hud, ":trace --logMethod console --dom-mutations attributes,remove", "console-api" ); info("Trigger all types of mutations"); await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { const element = content.document.createElement("hr"); content.document.body.appendChild(element); element.setAttribute("hidden", "true"); element.remove(); }); await waitFor( () => !!findTracerMessages(hud, `DOM Mutation | attributes `) .length, "Wait for the DOM Mutation trace for DOM attributes modification" ); await waitFor( () => !!findTracerMessages(hud, `DOM Mutation | remove `) .length, "Wait for the DOM Mutation trace for DOM Element removal" ); is( findTracerMessages(hud, `add