tor-browser

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

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