tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 });