tor-browser

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

browser_resources_console_messages.js (17748B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 // Test the ResourceCommand API around CONSOLE_MESSAGE
      7 //
      8 // Reproduces assertions from: devtools/shared/webconsole/test/chrome/test_cached_messages.html
      9 // And now more. Once we remove the console actor's startListeners in favor of watcher class
     10 // We could remove that other old test.
     11 
     12 const FISSION_TEST_URL = URL_ROOT_SSL + "fission_document.html";
     13 const IFRAME_URL = URL_ROOT_ORG_SSL + "fission_iframe.html";
     14 
     15 add_task(async function () {
     16  info("Execute test in top level document");
     17  await testTabConsoleMessagesResources(false);
     18  await testTabConsoleMessagesResourcesWithIgnoreExistingResources(false);
     19 
     20  info("Execute test in an iframe document, possibly remote with fission");
     21  await testTabConsoleMessagesResources(true);
     22  await testTabConsoleMessagesResourcesWithIgnoreExistingResources(true);
     23 });
     24 
     25 async function testTabConsoleMessagesResources(executeInIframe) {
     26  const tab = await addTab(FISSION_TEST_URL);
     27 
     28  const { client, resourceCommand, targetCommand } =
     29    await initResourceCommand(tab);
     30 
     31  info(
     32    "Log some messages *before* calling ResourceCommand.watchResources in order to " +
     33      "assert the behavior of already existing messages."
     34  );
     35  await logExistingMessages(tab.linkedBrowser, executeInIframe);
     36 
     37  const targetDocumentUrl = executeInIframe ? IFRAME_URL : FISSION_TEST_URL;
     38 
     39  let runtimeDoneResolve;
     40  const expectedExistingCalls =
     41    getExpectedExistingConsoleCalls(targetDocumentUrl);
     42  const expectedRuntimeCalls =
     43    getExpectedRuntimeConsoleCalls(targetDocumentUrl);
     44  const onRuntimeDone = new Promise(resolve => (runtimeDoneResolve = resolve));
     45  const onAvailable = resources => {
     46    for (const resource of resources) {
     47      is(
     48        resource.resourceType,
     49        resourceCommand.TYPES.CONSOLE_MESSAGE,
     50        "Received a message"
     51      );
     52      const isCachedMessage = !!expectedExistingCalls.length;
     53      const expected = (
     54        isCachedMessage ? expectedExistingCalls : expectedRuntimeCalls
     55      ).shift();
     56      checkConsoleAPICall(resource, expected);
     57      is(
     58        resource.isAlreadyExistingResource,
     59        isCachedMessage,
     60        "isAlreadyExistingResource has the expected value"
     61      );
     62 
     63      if (!expectedRuntimeCalls.length) {
     64        runtimeDoneResolve();
     65      }
     66    }
     67  };
     68 
     69  await resourceCommand.watchResources(
     70    [resourceCommand.TYPES.CONSOLE_MESSAGE],
     71    {
     72      onAvailable,
     73    }
     74  );
     75  is(
     76    expectedExistingCalls.length,
     77    0,
     78    "Got the expected number of existing messages"
     79  );
     80 
     81  info(
     82    "Now log messages *after* the call to ResourceCommand.watchResources and after having received all existing messages"
     83  );
     84  await logRuntimeMessages(tab.linkedBrowser, executeInIframe);
     85 
     86  info("Waiting for all runtime messages");
     87  await onRuntimeDone;
     88 
     89  is(
     90    expectedRuntimeCalls.length,
     91    0,
     92    "Got the expected number of runtime messages"
     93  );
     94 
     95  targetCommand.destroy();
     96  await client.close();
     97 }
     98 
     99 async function testTabConsoleMessagesResourcesWithIgnoreExistingResources(
    100  executeInIframe
    101 ) {
    102  info("Test ignoreExistingResources option for console messages");
    103  const tab = await addTab(FISSION_TEST_URL);
    104 
    105  const { client, resourceCommand, targetCommand } =
    106    await initResourceCommand(tab);
    107 
    108  info(
    109    "Check whether onAvailable will not be called with existing console messages"
    110  );
    111  await logExistingMessages(tab.linkedBrowser, executeInIframe);
    112 
    113  const availableResources = [];
    114  await resourceCommand.watchResources(
    115    [resourceCommand.TYPES.CONSOLE_MESSAGE],
    116    {
    117      onAvailable: resources => availableResources.push(...resources),
    118      ignoreExistingResources: true,
    119    }
    120  );
    121  is(
    122    availableResources.length,
    123    0,
    124    "onAvailable wasn't called for existing console messages"
    125  );
    126 
    127  info(
    128    "Check whether onAvailable will be called with the future console messages"
    129  );
    130  await logRuntimeMessages(tab.linkedBrowser, executeInIframe);
    131  const targetDocumentUrl = executeInIframe ? IFRAME_URL : FISSION_TEST_URL;
    132  const expectedRuntimeConsoleCalls =
    133    getExpectedRuntimeConsoleCalls(targetDocumentUrl);
    134  await waitUntil(
    135    () => availableResources.length === expectedRuntimeConsoleCalls.length
    136  );
    137  const expectedTargetFront = executeInIframe
    138    ? targetCommand
    139        .getAllTargets([targetCommand.TYPES.FRAME])
    140        .find(target => target.url == IFRAME_URL)
    141    : targetCommand.targetFront;
    142  for (let i = 0; i < expectedRuntimeConsoleCalls.length; i++) {
    143    const resource = availableResources[i];
    144    is(
    145      resource.targetFront,
    146      expectedTargetFront,
    147      "The targetFront property is the expected one"
    148    );
    149    const expected = expectedRuntimeConsoleCalls[i];
    150    checkConsoleAPICall(resource, expected);
    151    is(
    152      resource.isAlreadyExistingResource,
    153      false,
    154      "isAlreadyExistingResource is false since we're ignoring existing resources"
    155    );
    156  }
    157 
    158  targetCommand.destroy();
    159  await client.close();
    160 }
    161 
    162 async function logExistingMessages(browser, executeInIframe) {
    163  let browsingContext = browser.browsingContext;
    164  if (executeInIframe) {
    165    browsingContext = await SpecialPowers.spawn(
    166      browser,
    167      [],
    168      function frameScript() {
    169        return content.document.querySelector("iframe").browsingContext;
    170      }
    171    );
    172  }
    173  return evalInBrowsingContext(browsingContext, function pageScript() {
    174    console.log("foobarBaz-log", undefined);
    175    console.info("foobarBaz-info", null);
    176    console.warn("foobarBaz-warn", document.body);
    177  });
    178 }
    179 
    180 /**
    181 * Helper function similar to spawn, but instead of executing the script
    182 * as a Frame Script, with privileges and including test harness in stacktraces,
    183 * execute the script as a regular page script, without privileges and without any
    184 * preceding stack.
    185 *
    186 * @param {BrowsingContext} The browsing context into which the script should be evaluated
    187 * @param {Function | string} The JS to execute in the browsing context
    188 *
    189 * @return {Promise} Which resolves once the JS is done executing in the page
    190 */
    191 function evalInBrowsingContext(browsingContext, script) {
    192  return SpecialPowers.spawn(browsingContext, [String(script)], expr => {
    193    const document = content.document;
    194    const scriptEl = document.createElement("script");
    195    document.body.appendChild(scriptEl);
    196    // Force the immediate execution of the stringified JS function passed in `expr`
    197    scriptEl.textContent = "new " + expr;
    198    scriptEl.remove();
    199  });
    200 }
    201 
    202 // For both existing and runtime messages, we execute console API
    203 // from a page script evaluated via evalInBrowsingContext.
    204 // Records here the function used to execute the script in the page.
    205 const EXPECTED_FUNCTION_NAME = "pageScript";
    206 
    207 const NUMBER_REGEX = /^\d+$/;
    208 // timeStamp are the result of a number in microsecond divided by 1000.
    209 // so we can't expect a precise number of decimals, or even if there would
    210 // be decimals at all.
    211 const FRACTIONAL_NUMBER_REGEX = /^\d+(\.\d{1,3})?$/;
    212 
    213 function getExpectedExistingConsoleCalls(documentFilename) {
    214  const defaultProperties = {
    215    filename: documentFilename,
    216    columnNumber: NUMBER_REGEX,
    217    lineNumber: NUMBER_REGEX,
    218    timeStamp: FRACTIONAL_NUMBER_REGEX,
    219    innerWindowID: NUMBER_REGEX,
    220    chromeContext: undefined,
    221    counter: undefined,
    222    prefix: undefined,
    223    private: undefined,
    224    stacktrace: undefined,
    225    styles: undefined,
    226    timer: undefined,
    227  };
    228 
    229  return [
    230    {
    231      ...defaultProperties,
    232      level: "log",
    233      arguments: ["foobarBaz-log", { type: "undefined" }],
    234    },
    235    {
    236      ...defaultProperties,
    237      level: "info",
    238      arguments: ["foobarBaz-info", { type: "null" }],
    239    },
    240    {
    241      ...defaultProperties,
    242      level: "warn",
    243      arguments: ["foobarBaz-warn", { type: "object", actor: /[a-z]/ }],
    244    },
    245  ];
    246 }
    247 
    248 const longString = new Array(DevToolsServer.LONG_STRING_LENGTH + 2).join("a");
    249 function getExpectedRuntimeConsoleCalls(documentFilename) {
    250  const defaultStackFrames = [
    251    // This is the usage of "new " + expr from `evalInBrowsingContext`
    252    {
    253      filename: documentFilename,
    254      lineNumber: NUMBER_REGEX,
    255      columnNumber: NUMBER_REGEX,
    256    },
    257  ];
    258 
    259  const defaultProperties = {
    260    filename: documentFilename,
    261    columnNumber: NUMBER_REGEX,
    262    lineNumber: NUMBER_REGEX,
    263    timeStamp: FRACTIONAL_NUMBER_REGEX,
    264    innerWindowID: NUMBER_REGEX,
    265    chromeContext: undefined,
    266    counter: undefined,
    267    prefix: undefined,
    268    private: undefined,
    269    stacktrace: undefined,
    270    styles: undefined,
    271    timer: undefined,
    272  };
    273 
    274  return [
    275    {
    276      ...defaultProperties,
    277      level: "log",
    278      arguments: ["foobarBaz-log", { type: "undefined" }],
    279    },
    280    {
    281      ...defaultProperties,
    282      level: "log",
    283      arguments: ["Float from not a number: NaN"],
    284    },
    285    {
    286      ...defaultProperties,
    287      level: "log",
    288      arguments: ["Float from string: 1.200000"],
    289    },
    290    {
    291      ...defaultProperties,
    292      level: "log",
    293      arguments: ["Float from number: 1.300000"],
    294    },
    295    {
    296      ...defaultProperties,
    297      level: "log",
    298      arguments: ["Float from number with precision: 1.00"],
    299    },
    300    {
    301      ...defaultProperties,
    302      level: "log",
    303      arguments: [
    304        // Even if a precision of 200 was requested, it's capped at 15
    305        `Float from number with high precision: 2.${"0".repeat(15)}`,
    306      ],
    307    },
    308    {
    309      ...defaultProperties,
    310      level: "log",
    311      arguments: ["Integer from number: 3"],
    312    },
    313    {
    314      ...defaultProperties,
    315      level: "log",
    316      arguments: ["Integer from number with precision: 04"],
    317    },
    318    {
    319      ...defaultProperties,
    320      level: "log",
    321      arguments: [
    322        // The precision is not capped for integers
    323        `Integer from number with high precision: ${"5".padStart(200, "0")}`,
    324      ],
    325    },
    326    {
    327      ...defaultProperties,
    328      level: "log",
    329      arguments: ["BigInt 123 and 456"],
    330    },
    331    {
    332      ...defaultProperties,
    333      level: "log",
    334      arguments: ["message with ", "style"],
    335      styles: ["color: blue;", "background: red; font-size: 2em;"],
    336    },
    337    {
    338      ...defaultProperties,
    339      level: "info",
    340      arguments: ["foobarBaz-info", { type: "null" }],
    341    },
    342    {
    343      ...defaultProperties,
    344      level: "warn",
    345      arguments: ["foobarBaz-warn", { type: "object", actor: /[a-z]/ }],
    346    },
    347    {
    348      ...defaultProperties,
    349      level: "debug",
    350      arguments: [{ type: "null" }],
    351    },
    352    {
    353      ...defaultProperties,
    354      level: "trace",
    355      stacktrace: [
    356        {
    357          filename: documentFilename,
    358          functionName: EXPECTED_FUNCTION_NAME,
    359        },
    360        ...defaultStackFrames,
    361      ],
    362    },
    363    {
    364      ...defaultProperties,
    365      level: "dir",
    366      arguments: [
    367        {
    368          type: "object",
    369          actor: /[a-z]/,
    370          class: "HTMLDocument",
    371        },
    372        {
    373          type: "object",
    374          actor: /[a-z]/,
    375          class: "Location",
    376        },
    377      ],
    378    },
    379    {
    380      ...defaultProperties,
    381      level: "log",
    382      arguments: [
    383        "foo",
    384        {
    385          type: "longString",
    386          initial: longString.substring(
    387            0,
    388            DevToolsServer.LONG_STRING_INITIAL_LENGTH
    389          ),
    390          length: longString.length,
    391          actor: /[a-z]/,
    392        },
    393      ],
    394    },
    395    {
    396      ...defaultProperties,
    397      level: "count",
    398      arguments: ["myCounter"],
    399      counter: {
    400        count: 1,
    401        label: "myCounter",
    402      },
    403    },
    404    {
    405      ...defaultProperties,
    406      level: "count",
    407      arguments: ["myCounter"],
    408      counter: {
    409        count: 2,
    410        label: "myCounter",
    411      },
    412    },
    413    {
    414      ...defaultProperties,
    415      level: "count",
    416      arguments: ["default"],
    417      counter: {
    418        count: 1,
    419        label: "default",
    420      },
    421    },
    422    {
    423      ...defaultProperties,
    424      level: "countReset",
    425      arguments: ["myCounter"],
    426      counter: {
    427        count: 0,
    428        label: "myCounter",
    429      },
    430    },
    431    {
    432      ...defaultProperties,
    433      level: "countReset",
    434      arguments: ["unknownCounter"],
    435      counter: {
    436        error: "counterDoesntExist",
    437        label: "unknownCounter",
    438      },
    439    },
    440    {
    441      ...defaultProperties,
    442      level: "time",
    443      arguments: ["myTimer"],
    444      timer: {
    445        name: "myTimer",
    446      },
    447    },
    448    {
    449      ...defaultProperties,
    450      level: "time",
    451      arguments: ["myTimer"],
    452      timer: {
    453        name: "myTimer",
    454        error: "timerAlreadyExists",
    455      },
    456    },
    457    {
    458      ...defaultProperties,
    459      level: "timeLog",
    460      arguments: ["myTimer"],
    461      timer: {
    462        name: "myTimer",
    463        duration: NUMBER_REGEX,
    464      },
    465    },
    466    {
    467      ...defaultProperties,
    468      level: "timeEnd",
    469      arguments: ["myTimer"],
    470      timer: {
    471        name: "myTimer",
    472        duration: NUMBER_REGEX,
    473      },
    474    },
    475    {
    476      ...defaultProperties,
    477      level: "time",
    478      arguments: ["default"],
    479      timer: {
    480        name: "default",
    481      },
    482    },
    483    {
    484      ...defaultProperties,
    485      level: "timeLog",
    486      arguments: ["default"],
    487      timer: {
    488        name: "default",
    489        duration: NUMBER_REGEX,
    490      },
    491    },
    492    {
    493      ...defaultProperties,
    494      level: "timeEnd",
    495      arguments: ["default"],
    496      timer: {
    497        name: "default",
    498        duration: NUMBER_REGEX,
    499      },
    500    },
    501    {
    502      ...defaultProperties,
    503      level: "timeLog",
    504      arguments: ["unknownTimer"],
    505      timer: {
    506        name: "unknownTimer",
    507        error: "timerDoesntExist",
    508      },
    509    },
    510    {
    511      ...defaultProperties,
    512      level: "timeEnd",
    513      arguments: ["unknownTimer"],
    514      timer: {
    515        name: "unknownTimer",
    516        error: "timerDoesntExist",
    517      },
    518    },
    519    {
    520      ...defaultProperties,
    521      level: "error",
    522      arguments: ["foobarBaz-asmjs-error", { type: "undefined" }],
    523 
    524      stacktrace: [
    525        {
    526          filename: documentFilename,
    527          functionName: "fromAsmJS",
    528        },
    529        {
    530          filename: documentFilename,
    531          functionName: "inAsmJS2",
    532        },
    533        {
    534          filename: documentFilename,
    535          functionName: "inAsmJS1",
    536        },
    537        {
    538          filename: documentFilename,
    539          functionName: EXPECTED_FUNCTION_NAME,
    540        },
    541        ...defaultStackFrames,
    542      ],
    543    },
    544    {
    545      ...defaultProperties,
    546      level: "log",
    547      filename:
    548        "chrome://mochitests/content/browser/devtools/shared/commands/resource/tests/browser_resources_console_messages.js",
    549      arguments: [
    550        {
    551          type: "object",
    552          actor: /[a-z]/,
    553          class: "Restricted",
    554        },
    555      ],
    556      chromeContext: true,
    557    },
    558  ];
    559 }
    560 
    561 async function logRuntimeMessages(browser, executeInIframe) {
    562  let browsingContext = browser.browsingContext;
    563  if (executeInIframe) {
    564    browsingContext = await SpecialPowers.spawn(
    565      browser,
    566      [],
    567      function frameScript() {
    568        return content.document.querySelector("iframe").browsingContext;
    569      }
    570    );
    571  }
    572  // First inject LONG_STRING_LENGTH in global scope it order to easily use it after
    573  await evalInBrowsingContext(
    574    browsingContext,
    575    `function () {window.LONG_STRING_LENGTH = ${DevToolsServer.LONG_STRING_LENGTH};}`
    576  );
    577  await evalInBrowsingContext(browsingContext, function pageScript() {
    578    const _longString = new Array(window.LONG_STRING_LENGTH + 2).join("a");
    579 
    580    console.log("foobarBaz-log", undefined);
    581 
    582    console.log("Float from not a number: %f", "foo");
    583    console.log("Float from string: %f", "1.2");
    584    console.log("Float from number: %f", 1.3);
    585    console.log("Float from number with precision: %.2f", 1);
    586    console.log("Float from number with high precision: %.200f", 2);
    587    console.log("Integer from number: %i", 3.14);
    588    console.log("Integer from number with precision: %.2i", 4);
    589    console.log("Integer from number with high precision: %.200i", 5);
    590    console.log("BigInt %d and %i", 123n, 456n);
    591    console.log(
    592      "%cmessage with %cstyle",
    593      "color: blue;",
    594      "background: red; font-size: 2em;"
    595    );
    596 
    597    console.info("foobarBaz-info", null);
    598    console.warn("foobarBaz-warn", document.documentElement);
    599    console.debug(null);
    600    console.trace();
    601    console.dir(document, location);
    602    console.log("foo", _longString);
    603 
    604    console.count("myCounter");
    605    console.count("myCounter");
    606    console.count();
    607    console.countReset("myCounter");
    608    // will cause warnings because unknownCounter doesn't exist
    609    console.countReset("unknownCounter");
    610 
    611    console.time("myTimer");
    612    // will cause warning because myTimer already exist
    613    console.time("myTimer");
    614    console.timeLog("myTimer");
    615    console.timeEnd("myTimer");
    616    console.time();
    617    console.timeLog();
    618    console.timeEnd();
    619    // // will cause warnings because unknownTimer doesn't exist
    620    console.timeLog("unknownTimer");
    621    console.timeEnd("unknownTimer");
    622 
    623    function fromAsmJS() {
    624      console.error("foobarBaz-asmjs-error", undefined);
    625    }
    626 
    627    (function (global, foreign) {
    628      "use asm";
    629      function inAsmJS2() {
    630        foreign.fromAsmJS();
    631      }
    632      function inAsmJS1() {
    633        inAsmJS2();
    634      }
    635      return inAsmJS1;
    636    })(null, { fromAsmJS })();
    637  });
    638  await SpecialPowers.spawn(browsingContext, [], function frameScript() {
    639    const sandbox = new Cu.Sandbox(null, { invisibleToDebugger: true });
    640    const sandboxObj = sandbox.eval("new Object");
    641    content.console.log(sandboxObj);
    642  });
    643 }
    644 
    645 // Copied from devtools/shared/webconsole/test/chrome/common.js
    646 function checkConsoleAPICall(call, expected) {
    647  is(
    648    call.arguments?.length || 0,
    649    expected.arguments?.length || 0,
    650    "number of arguments"
    651  );
    652 
    653  checkObject(call, expected);
    654 }