browser_dbg-javascript-tracer-sidebar.js (14094B)
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. 6 7 "use strict"; 8 9 add_task(async function () { 10 const dbg = await initDebugger("doc-scripts.html"); 11 12 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () { 13 // Register a global event listener to cover listeners set to DOM Element as well as on the global 14 // This may regress the DOM Events panel to show more than one entry for the click event. 15 content.eval(`window.onclick = () => {};`); 16 }); 17 18 info("Force the log method to be the debugger sidebar"); 19 await toggleJsTracerMenuItem(dbg, "#jstracer-menu-item-debugger-sidebar"); 20 21 info("Enable the tracing"); 22 await toggleJsTracer(dbg.toolbox); 23 24 is( 25 dbg.selectors.getSelectedPrimaryPaneTab(), 26 "tracer", 27 "The tracer sidebar is automatically shown on start" 28 ); 29 30 const argumentSearchInput = findElementWithSelector( 31 dbg, 32 `#tracer-tab-panel .call-tree-container input` 33 ); 34 is( 35 argumentSearchInput.disabled, 36 true, 37 "The input to search by values is disabled" 38 ); 39 40 info("Toggle off and on in order to record with values"); 41 await toggleJsTracer(dbg.toolbox); 42 info("Enable values recording"); 43 await toggleJsTracerMenuItem(dbg, "#jstracer-menu-item-log-values"); 44 await toggleJsTracer(dbg.toolbox); 45 46 const topLevelThreadActorID = 47 dbg.toolbox.commands.targetCommand.targetFront.threadFront.actorID; 48 info("Wait for tracing to be enabled"); 49 await waitForState(dbg, () => { 50 return dbg.selectors.getIsThreadCurrentlyTracing(topLevelThreadActorID); 51 }); 52 53 is( 54 argumentSearchInput.disabled, 55 false, 56 "The input to search by values is no longer disabled" 57 ); 58 59 let tracerMessage = findElementWithSelector( 60 dbg, 61 "#tracer-tab-panel .tracer-message" 62 ); 63 is(tracerMessage.textContent, "Waiting for the first JavaScript executions"); 64 65 invokeInTab("main"); 66 67 info("Wait for the call tree to appear in the tracer panel"); 68 const tracerTree = await waitForElementWithSelector( 69 dbg, 70 "#tracer-tab-panel .tree" 71 ); 72 73 info("Wait for the expected traces to appear in the call tree"); 74 let traces = await waitFor(() => { 75 const elements = tracerTree.querySelectorAll(".trace-line"); 76 if (elements.length == 3) { 77 return elements; 78 } 79 return false; 80 }); 81 is(traces[0].textContent, "λ main simple1.js:1:17"); 82 is(traces[1].textContent, "λ foo simple2.js:1:13"); 83 is(traces[2].textContent, "λ bar simple2.js:3:5"); 84 ok( 85 !findElement(dbg, "tracedLine"), 86 "Before selecting any trace, no line is highlighted in CodeMirror" 87 ); 88 89 info("Select the trace for the call to `foo`"); 90 EventUtils.synthesizeMouseAtCenter(traces[1], {}, dbg.win); 91 92 let focusedTrace = await waitFor( 93 () => tracerTree.querySelector(".tree-node.focused .trace-line"), 94 "Wait for the line to be focused in the tracer panel" 95 ); 96 is(focusedTrace, traces[1], "The clicked trace is now focused"); 97 await waitFor( 98 () => findElement(dbg, "tracedLine"), 99 "Wait for the traced line to be highlighted in CodeMirror" 100 ); 101 ok( 102 findElement(dbg, "tracedLine"), 103 "When a trace is selected, the line is highlighted in CodeMirror" 104 ); 105 const tracePanel = await waitFor(() => findElement(dbg, "tracePanel")); 106 ok(tracePanel, "The trace panel is shown on trace selection"); 107 is( 108 tracePanel.querySelectorAll(".trace-item").length, 109 1, 110 "There is only one call to foo() reported in the trace panel" 111 ); 112 113 // Naive sanity checks for inlines previews 114 await assertInlinePreviews( 115 dbg, 116 [ 117 { 118 previews: [ 119 { identifier: "x:", value: "1" }, 120 { identifier: "y:", value: "2" }, 121 ], 122 line: 1, 123 }, 124 ], 125 "foo" 126 ); 127 128 // Naive sanity checks for popup previews on hovering 129 { 130 const { element: popupEl, tokenEl } = await tryHovering( 131 dbg, 132 1, 133 14, 134 "previewPopup" 135 ); 136 is(popupEl.querySelector(".objectBox")?.textContent, "1"); 137 await closePreviewForToken(dbg, tokenEl, "previewPopup"); 138 } 139 140 { 141 const { element: popupEl, tokenEl } = await tryHovering( 142 dbg, 143 1, 144 17, 145 "previewPopup" 146 ); 147 is(popupEl.querySelector(".objectBox")?.textContent, "2"); 148 await closePreviewForToken(dbg, tokenEl, "previewPopup"); 149 } 150 151 let focusedPausedFrame = findElementWithSelector( 152 dbg, 153 ".frames .frame.selected" 154 ); 155 ok(!focusedPausedFrame, "Before pausing, there is no selected paused frame"); 156 157 info("Trigger a breakpoint"); 158 const onResumed = SpecialPowers.spawn( 159 gBrowser.selectedBrowser, 160 [], 161 async function () { 162 content.eval("debugger;"); 163 } 164 ); 165 await waitForPaused(dbg); 166 await waitForSelectedLocation(dbg, 1, 1); 167 168 focusedPausedFrame = findElementWithSelector(dbg, ".frames .frame.selected"); 169 ok( 170 !!focusedPausedFrame, 171 "When paused, a frame is selected in the call stack panel" 172 ); 173 174 focusedTrace = tracerTree.querySelector(".tree-node.focused .trace-line"); 175 is(focusedTrace, null, "When pausing, there is no trace selected anymore"); 176 177 info("Re select the tracer frame while being paused"); 178 EventUtils.synthesizeMouseAtCenter(traces[1], {}, dbg.win); 179 180 await waitForSelectedLocation(dbg, 1, 13); 181 focusedPausedFrame = findElementWithSelector(dbg, ".frames .frame.selected"); 182 ok( 183 !focusedPausedFrame, 184 "While paused, if we select a tracer frame, the paused frame is no longer highlighted in the call stack panel" 185 ); 186 const highlightedPausedFrame = findElementWithSelector( 187 dbg, 188 ".frames .frame.inactive" 189 ); 190 ok( 191 !!highlightedPausedFrame, 192 "But it is still highlighted as inactive with a grey background" 193 ); 194 ok( 195 findElement(dbg, "tracedLine"), 196 "When a trace is selected, while being paused, the line is highlighted as traced in CodeMirror" 197 ); 198 ok( 199 !findElement(dbg, "pausedLine"), 200 "The traced line is not highlighted as paused" 201 ); 202 203 await resume(dbg); 204 await onResumed; 205 206 ok( 207 findElement(dbg, "tracedLine"), 208 "After resuming, the traced line is still highlighted in CodeMirror" 209 ); 210 211 // Trigger a click in the content page to verify we do trace DOM events 212 BrowserTestUtils.synthesizeMouseAtCenter( 213 "button", 214 {}, 215 gBrowser.selectedBrowser 216 ); 217 218 const [nodeClickTrace, globalClickTrace] = await waitFor(() => { 219 const elts = tracerTree.querySelectorAll(".tracer-dom-event"); 220 if (elts.length == 2) { 221 return elts; 222 } 223 return false; 224 }); 225 // This is the listener set on the <button> element 226 is(nodeClickTrace.textContent, "DOM | node.click"); 227 // This is the listener set on the window object 228 is(globalClickTrace.textContent, "DOM | global.click"); 229 230 await BrowserTestUtils.synthesizeKey("x", {}, gBrowser.selectedBrowser); 231 const keyTrace = await waitFor(() => { 232 // Scroll to bottom to ensure rendering the last elements (otherwise they are not because of VirtualizedTree) 233 tracerTree.scrollTop = tracerTree.scrollHeight; 234 const elts = tracerTree.querySelectorAll(".tracer-dom-event"); 235 if (elts.length == 3) { 236 return elts[2]; 237 } 238 return false; 239 }); 240 is(keyTrace.textContent, "DOM | global.keypress"); 241 242 info("Wait for the key listener function to be displayed"); 243 await waitFor(() => { 244 // Scroll to bottom to ensure rendering the last elements (otherwise they are not because of VirtualizedTree) 245 tracerTree.scrollTop = tracerTree.scrollHeight; 246 const elements = tracerTree.querySelectorAll(".trace-line"); 247 // Wait for the expected element to be rendered 248 if (elements[elements.length - 1].textContent.includes("keyListener")) { 249 return true; 250 } 251 return false; 252 }); 253 254 info("Trigger a DOM Mutation"); 255 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () { 256 content.eval(` 257 window.doMutation = () => { 258 const div = document.createElement("div"); 259 document.body.appendChild(div); 260 //# sourceURL=foo.js 261 }; 262 `); 263 content.wrappedJSObject.doMutation(); 264 }); 265 266 // Wait for the `eval` and the `doMutation` calls to be rendered 267 traces = await waitFor(() => { 268 // Scroll to bottom to ensure rendering the last elements (otherwise they are not because of VirtualizedTree) 269 tracerTree.scrollTop = tracerTree.scrollHeight; 270 const elements = tracerTree.querySelectorAll(".trace-line"); 271 // Wait for the expected element to be rendered 272 if ( 273 elements[elements.length - 1].textContent.includes("window.doMutation") 274 ) { 275 return elements; 276 } 277 return false; 278 }); 279 280 const doMutationTrace = traces[traces.length - 1]; 281 is(doMutationTrace.textContent, "λ window.doMutation eval:2:33"); 282 283 // Expand the call to doMutation in order to show the DOM Mutation in the tree 284 doMutationTrace.querySelector(".arrow").click(); 285 286 const mutationTrace = await waitFor(() => 287 tracerTree.querySelector(".tracer-dom-mutation") 288 ); 289 is(mutationTrace.textContent, "DOM Mutation | add"); 290 291 // Click on the mutation trace to open its source 292 mutationTrace.click(); 293 await waitForSelectedSource(dbg, "foo.js"); 294 295 info("Open the DOM event list"); 296 const eventListToggleButton = await waitForElementWithSelector( 297 dbg, 298 "#tracer-tab-panel #tracer-events-tab" 299 ); 300 // Use synthesizeMouseAtCenter as calling click() method somehow triggers mouse over 301 // on the event categories... 302 EventUtils.synthesizeMouseAtCenter(eventListToggleButton, {}, dbg.win); 303 304 let domEventCategories = findAllElementsWithSelector( 305 dbg, 306 "#tracer-tab-panel .event-listener-category" 307 ); 308 is(domEventCategories.length, 2); 309 is(domEventCategories[0].textContent, "Keyboard"); 310 is(domEventCategories[1].textContent, "Mouse"); 311 312 info("Expand the Mouse category"); 313 domEventCategories[1] 314 .closest(".event-listener-header") 315 .querySelector(".event-listener-expand") 316 .click(); 317 const clickEventName = await waitFor(() => { 318 const eventNames = domEventCategories[1] 319 .closest(".event-listener-group") 320 .querySelectorAll(".event-listener-name"); 321 if (eventNames.length == 1) { 322 return eventNames[0]; 323 } 324 return false; 325 }, "There is only one mouse event"); 326 is( 327 clickEventName.textContent, 328 "click", 329 "and that one mouse event is 'click'" 330 ); 331 info("Fold the Mouse category"); 332 domEventCategories[1] 333 .closest(".event-listener-header") 334 .querySelector(".event-listener-expand") 335 .click(); 336 337 // Test event highlighting on mouse over 338 is( 339 findAllElementsWithSelector(dbg, ".tracer-slider-event.highlighted").length, 340 0, 341 "No event is highlighted in the timeline" 342 ); 343 info("Mouse over the Keyboard category"); 344 EventUtils.synthesizeMouseAtCenter( 345 domEventCategories[0], 346 { type: "mousemove" }, 347 dbg.win 348 ); 349 await waitFor(() => { 350 return ( 351 findAllElementsWithSelector(dbg, ".tracer-slider-event.highlighted") 352 .length == 1 353 ); 354 }, "The setTimeout event is highlighted in the timeline"); 355 356 // Before toggling some DOM events, assert that the three events are displayed in the timeline 357 // (node and global click, and node keypress) 358 is(findAllElementsWithSelector(dbg, ".tracer-slider-event").length, 3); 359 info("Toggle off the Keyboard and then the Mouse events"); 360 domEventCategories[0].click(); 361 await waitFor( 362 () => findAllElementsWithSelector(dbg, ".tracer-slider-event").length == 2 363 ); 364 domEventCategories[1].click(); 365 // Now that all events are disabled, there is no more trace displayed in the timeline 366 await waitFor( 367 () => !findAllElementsWithSelector(dbg, ".tracer-slider-event").length 368 ); 369 tracerMessage = findElementWithSelector( 370 dbg, 371 "#tracer-tab-panel .tracer-message" 372 ); 373 is(tracerMessage.textContent, "All traces have been filtered out"); 374 375 info("Trigger a setTimeout to have a new event category"); 376 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () { 377 content.eval(` 378 window.setTimeout(function () { 379 console.log("timeout fired"); 380 }); 381 `); 382 }); 383 domEventCategories = await waitFor(() => { 384 const categories = findAllElementsWithSelector( 385 dbg, 386 "#tracer-tab-panel .event-listener-category" 387 ); 388 if (categories.length == 3) { 389 return categories; 390 } 391 return false; 392 }); 393 is(domEventCategories[2].textContent, "Timer"); 394 is( 395 findAllElementsWithSelector(dbg, ".tracer-slider-event").length, 396 1, 397 "The setTimeout callback is displayed in the timeline" 398 ); 399 400 info( 401 "Check each category checked status before enabling only keyboad instead of time" 402 ); 403 const domEventCheckboxes = findAllElementsWithSelector( 404 dbg, 405 `#tracer-tab-panel .event-listener-label input` 406 ); 407 is(domEventCheckboxes[0].checked, false); 408 is(domEventCheckboxes[1].checked, false); 409 is(domEventCheckboxes[2].checked, true); 410 411 info( 412 "CmdOrCtrl + click on the Keyboard categorie to force selecting only this category" 413 ); 414 EventUtils.synthesizeMouseAtCenter( 415 domEventCategories[0], 416 { [Services.appinfo.OS === "Darwin" ? "metaKey" : "ctrlKey"]: true }, 417 dbg.win 418 ); 419 420 info("Wait for the event checkboxes to be updated"); 421 await waitFor(() => { 422 return domEventCheckboxes[0].checked; 423 }); 424 is(domEventCheckboxes[0].checked, true); 425 is(domEventCheckboxes[1].checked, false); 426 is(domEventCheckboxes[2].checked, false); 427 428 // Test Disabling tracing 429 info("Disable the tracing"); 430 await toggleJsTracer(dbg.toolbox); 431 info("Wait for tracing to be disabled"); 432 await waitForState(dbg, () => { 433 return !dbg.selectors.getIsThreadCurrentlyTracing(topLevelThreadActorID); 434 }); 435 436 invokeInTab("inline_script2"); 437 438 // Let some time for the tracer to appear if we failed disabling the tracing 439 await wait(1000); 440 441 info("Reset back to the default value"); 442 await toggleJsTracerMenuItem(dbg, "#jstracer-menu-item-console"); 443 await toggleJsTracerMenuItem(dbg, "#jstracer-menu-item-log-values"); 444 });