browser_jsterm_trace_command.js (11450B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ 4 5 // Tests the Javascript Tracing feature via the Web Console :trace command. 6 7 "use strict"; 8 9 const TEST_URI = `data:text/html;charset=utf-8,<!DOCTYPE html> 10 <body> 11 <div> 12 <h1>Testing trace command</h1> 13 <script> 14 function main() { return 42; } 15 function someNoise() {} 16 </script> 17 </div> 18 <div><p></p></div> 19 </body>`; 20 21 add_task(async function testBasicRecord() { 22 await pushPref("devtools.debugger.features.javascript-tracing", true); 23 24 // The console output is no longer the default one 25 await pushPref("devtools.debugger.javascript-tracing-log-method", "console"); 26 27 const hud = await openNewTabAndConsole(TEST_URI); 28 ok(hud, "web console opened"); 29 30 info("Test unsupported param error message"); 31 let msg = await evaluateExpressionInConsole( 32 hud, 33 ":trace --unsupported-param", 34 "console-api" 35 ); 36 is( 37 msg.textContent.trim(), 38 ":trace command doesn't support 'unsupported-param' argument." 39 ); 40 41 info("Test the help argument"); 42 msg = await evaluateExpressionInConsole(hud, ":trace --help", "console-api"); 43 ok(msg.textContent.includes("Toggles the JavaScript tracer")); 44 45 info("Test toggling the tracer ON"); 46 // Pass `console-api` specific classname as the command results don't log anything. 47 // Instead a JSTRACER_STATE resource logs a console-api message. 48 msg = await evaluateExpressionInConsole( 49 hud, 50 ":trace --logMethod console --prefix foo --returns --values --on-next-interaction", 51 "console-api" 52 ); 53 is( 54 msg.textContent.trim(), 55 "Waiting for next user interaction before tracing (next mousedown or keydown event)" 56 ); 57 58 info("Trigger some code before the user interaction"); 59 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 60 content.wrappedJSObject.someNoise(); 61 }); 62 63 info("Simulate a user interaction by trigerring a key event on the page"); 64 await BrowserTestUtils.synthesizeKey("a", {}, gBrowser.selectedBrowser); 65 66 info("Trigger some code to log some traces"); 67 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 68 content.wrappedJSObject.main("arg", 2); 69 }); 70 71 info("Ensure a message notified about the tracer actual start"); 72 await waitFor( 73 () => !!findConsoleAPIMessage(hud, `Started tracing to Web Console`) 74 ); 75 76 // Assert that we also see the custom prefix, as well as function arguments 77 await waitFor( 78 () => 79 !!findTracerMessages(hud, `foo: ⟶ interpreter λ main("arg", 2)`).length 80 ); 81 is( 82 findTracerMessages(hud, `someNoise`).length, 83 0, 84 "The code running before the key press should not be traced" 85 ); 86 await waitFor( 87 () => !!findTracerMessages(hud, `foo: ⟵ λ main return 42`).length, 88 89 "Got the function returns being logged, with the returned value" 90 ); 91 92 // But now that the tracer is active, we will be able to log this call to someNoise 93 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 94 content.wrappedJSObject.someNoise(); 95 }); 96 await waitFor( 97 () => !!findTracerMessages(hud, `foo: ⟶ interpreter λ someNoise()`).length 98 ); 99 100 info("Test toggling the tracer OFF"); 101 msg = await evaluateExpressionInConsole(hud, ":trace", "console-api"); 102 is(msg.textContent.trim(), "Stopped tracing"); 103 104 info("Clear past traces"); 105 hud.ui.clearOutput(); 106 await waitFor( 107 () => !findTracerMessages(hud, `foo: ⟶ interpreter λ main("arg", 2)`).length 108 ); 109 ok("Console was cleared"); 110 111 info("Trigger some code again"); 112 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 113 content.wrappedJSObject.main(); 114 }); 115 116 // Let some time for traces to appear 117 await wait(500); 118 119 ok( 120 !findTracerMessages(hud, `foo: ⟶ interpreter λ main("arg", 2)`).length, 121 "We really stopped recording traces, and no trace appear in the console" 122 ); 123 }); 124 125 add_task(async function testLimitedRecord() { 126 await pushPref("devtools.debugger.features.javascript-tracing", true); 127 128 const jsCode = `function foo1() {foo2()}; function foo2() {foo3()}; function foo3() {}; function bar() {}`; 129 const hud = await openNewTabAndConsole( 130 "data:text/html," + encodeURIComponent(`<script>${jsCode}</script>`) 131 ); 132 ok(hud, "web console opened"); 133 134 info("Test toggling the tracer ON"); 135 // Pass `console-api` specific classname as the command results aren't logged as "result". 136 // Instead the frontend log a message as a console API message. 137 await evaluateExpressionInConsole( 138 hud, 139 ":trace --logMethod console --max-depth 1", 140 "console-api" 141 ); 142 143 info("Execute some code trigerring the tracer"); 144 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 145 content.wrappedJSObject.foo1(); 146 }); 147 148 await waitFor(() => !!findTracerMessages(hud, `λ foo1`).length); 149 ok(true, "foo1 trace was printed to console"); 150 151 is( 152 findTracerMessages(hud, `λ foo2`).length, 153 0, 154 "We only see the first function thanks to the depth limit" 155 ); 156 157 info("Test toggling the tracer OFF"); 158 await evaluateExpressionInConsole(hud, ":trace", "console-api"); 159 160 info("Clear past traces"); 161 hud.ui.clearOutput(); 162 await waitFor(() => !findTracerMessages(hud, `λ foo1`).length); 163 ok("Console was cleared"); 164 165 info("Re-enable the tracing, but with a max record limit"); 166 await evaluateExpressionInConsole( 167 hud, 168 ":trace --logMethod console --max-records 1", 169 "console-api" 170 ); 171 172 info("Trigger some code again"); 173 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 174 content.wrappedJSObject.foo1(); 175 content.wrappedJSObject.bar(); 176 }); 177 178 await waitFor(() => !!findTracerMessages(hud, `λ foo3`).length); 179 await waitFor(() => !!findConsoleAPIMessage(hud, `Stopped tracing`)); 180 is( 181 findTracerMessages(hud, `λ foo1`).length, 182 1, 183 "Found the whole depth for the first event loop 1/3" 184 ); 185 is( 186 findTracerMessages(hud, `λ foo2`).length, 187 1, 188 "Found the whole depth for the first event loop 2/3" 189 ); 190 is( 191 findTracerMessages(hud, `λ foo3`).length, 192 1, 193 "Found the whole depth for the first event loop 3/3" 194 ); 195 is( 196 findTracerMessages(hud, `λ bar`).length, 197 0, 198 "But the second event loop was ignored" 199 ); 200 ok( 201 !!findConsoleAPIMessage( 202 hud, 203 `Stopped tracing (reason: max-records)`, 204 ".console-api" 205 ), 206 "And the tracer was automatically stopped" 207 ); 208 209 info("Enable tracing one last time without any restriction"); 210 await evaluateExpressionInConsole( 211 hud, 212 ":trace --logMethod console", 213 "console-api" 214 ); 215 info("Trigger various code again"); 216 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 217 content.wrappedJSObject.foo1(); 218 content.wrappedJSObject.bar(); 219 }); 220 await waitFor(() => !!findTracerMessages(hud, `λ foo1`).length); 221 await waitFor(() => !!findTracerMessages(hud, `λ foo2`).length); 222 await waitFor(() => !!findTracerMessages(hud, `λ foo3`).length); 223 await waitFor(() => !!findTracerMessages(hud, `λ bar`).length); 224 ok(true, "All traces were printed to console"); 225 }); 226 227 add_task(async function testDOMMutations() { 228 await pushPref("devtools.debugger.features.javascript-tracing", true); 229 230 const testScript = `/* this will be line 1 */ 231 function add() { 232 const element = document.createElement("hr"); 233 document.body.appendChild(element); 234 } 235 function attributes() { 236 document.querySelector("hr").setAttribute("hidden", "true"); 237 } 238 function remove() { 239 document.querySelector("hr").remove(); 240 } 241 /* Fake a real file name for this inline script */ 242 //# sourceURL=fake.js 243 `; 244 const hud = await openNewTabAndConsole( 245 `data:text/html,test-page<script>${encodeURIComponent(testScript)}</script>` 246 ); 247 ok(hud, "web console opened"); 248 249 let msg = await evaluateExpressionInConsole( 250 hud, 251 ":trace --dom-mutations foo", 252 "console-api" 253 ); 254 is( 255 msg.textContent.trim(), 256 ":trace --dom-mutations only accept a list of strings whose values can be: add,attributes,remove" 257 ); 258 259 msg = await evaluateExpressionInConsole( 260 hud, 261 ":trace --dom-mutations 42", 262 "console-api" 263 ); 264 is( 265 msg.textContent.trim(), 266 ":trace --dom-mutations accept only no arguments, or a list mutation type strings (add,attributes,remove)" 267 ); 268 269 info("Test toggling the tracer ON"); 270 // Pass `console-api` specific classname as the command results aren't logged as "result". 271 // Instead the frontend log a message as a console API message. 272 await evaluateExpressionInConsole( 273 hud, 274 ":trace --logMethod console --dom-mutations", 275 "console-api" 276 ); 277 278 info("Trigger some code to add a DOM Element"); 279 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 280 content.wrappedJSObject.add(); 281 }); 282 let traceNode = await waitFor( 283 () => findTracerMessages(hud, `DOM Mutation | add <hr>`)[0], 284 "Wait for the DOM Mutation trace for DOM element creation" 285 ); 286 is( 287 traceNode.querySelector(".message-location").textContent, 288 "fake.js:4:19", 289 "Add Mutation location is correct" 290 ); 291 292 info("Trigger some code to modify attributes of a DOM Element"); 293 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 294 content.wrappedJSObject.attributes(); 295 }); 296 traceNode = await waitFor( 297 () => 298 findTracerMessages( 299 hud, 300 `DOM Mutation | attributes <hr hidden="true">` 301 )[0], 302 "Wait for the DOM Mutation trace for DOM attributes modification" 303 ); 304 is( 305 traceNode.querySelector(".message-location").textContent, 306 "fake.js:7:34", 307 "Attributes Mutation location is correct" 308 ); 309 310 info("Trigger some code to remove a DOM Element"); 311 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 312 content.wrappedJSObject.remove(); 313 }); 314 traceNode = await waitFor( 315 () => 316 findTracerMessages(hud, `DOM Mutation | remove <hr hidden="true">`)[0], 317 "Wait for the DOM Mutation trace for DOM Element removal" 318 ); 319 is( 320 traceNode.querySelector(".message-location").textContent, 321 "fake.js:10:34", 322 "Remove Mutation location is correct" 323 ); 324 325 info("Stop tracing all mutations"); 326 await evaluateExpressionInConsole(hud, ":trace", "console-api"); 327 328 info("Clear past traces"); 329 hud.ui.clearOutput(); 330 await waitFor( 331 () => !findTracerMessages(hud, `remove(<hr hidden="true">)`).length 332 ); 333 ok("Console was cleared"); 334 335 info("Re-enable the tracing, but only with a subset of mutations"); 336 await evaluateExpressionInConsole( 337 hud, 338 ":trace --logMethod console --dom-mutations attributes,remove", 339 "console-api" 340 ); 341 342 info("Trigger all types of mutations"); 343 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 344 const element = content.document.createElement("hr"); 345 content.document.body.appendChild(element); 346 element.setAttribute("hidden", "true"); 347 element.remove(); 348 }); 349 await waitFor( 350 () => 351 !!findTracerMessages(hud, `DOM Mutation | attributes <hr hidden="true">`) 352 .length, 353 "Wait for the DOM Mutation trace for DOM attributes modification" 354 ); 355 356 await waitFor( 357 () => 358 !!findTracerMessages(hud, `DOM Mutation | remove <hr hidden="true">`) 359 .length, 360 "Wait for the DOM Mutation trace for DOM Element removal" 361 ); 362 is( 363 findTracerMessages(hud, `add <hr`).length, 364 0, 365 "DOM Element creation isn't traced" 366 ); 367 });