tor-browser

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

browser_webextension_inspected_window.js (15121B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const TEST_RELOAD_URL = `${URL_ROOT_SSL}/inspectedwindow-reload-target.sjs`;
      7 
      8 async function setup(pageUrl) {
      9  // Disable bfcache for Fission for now.
     10  // If Fission is disabled, the pref is no-op.
     11  await SpecialPowers.pushPrefEnv({
     12    set: [["fission.bfcacheInParent", false]],
     13  });
     14 
     15  const extension = ExtensionTestUtils.loadExtension({
     16    background() {
     17      // This is just an empty extension used to ensure that the caller extension uuid
     18      // actually exists.
     19    },
     20  });
     21 
     22  await extension.startup();
     23 
     24  const fakeExtCallerInfo = {
     25    url: WebExtensionPolicy.getByID(extension.id).getURL(
     26      "fake-caller-script.js"
     27    ),
     28    lineNumber: 1,
     29    addonId: extension.id,
     30  };
     31 
     32  const tab = await addTab(pageUrl);
     33 
     34  const commands = await CommandsFactory.forTab(tab, { isWebExtension: true });
     35  await commands.targetCommand.startListening();
     36 
     37  const webConsoleFront =
     38    await commands.targetCommand.targetFront.getFront("console");
     39 
     40  return {
     41    webConsoleFront,
     42    commands,
     43    extension,
     44    fakeExtCallerInfo,
     45  };
     46 }
     47 
     48 async function teardown({ commands, extension }) {
     49  await commands.destroy();
     50  gBrowser.removeCurrentTab();
     51  await extension.unload();
     52 }
     53 
     54 function waitForNextTabNavigated(commands) {
     55  const target = commands.targetCommand.targetFront;
     56  return new Promise(resolve => {
     57    target.on("tabNavigated", function tabNavigatedListener(pkt) {
     58      if (pkt.state == "stop" && !pkt.isFrameSwitching) {
     59        target.off("tabNavigated", tabNavigatedListener);
     60        resolve();
     61      }
     62    });
     63  });
     64 }
     65 
     66 // Script used as the injectedScript option in the inspectedWindow.reload tests.
     67 function injectedScript() {
     68  if (!window.pageScriptExecutedFirst) {
     69    window.addEventListener(
     70      "DOMContentLoaded",
     71      function () {
     72        if (document.querySelector("pre")) {
     73          document.querySelector("pre").textContent =
     74            "injected script executed first";
     75        }
     76      },
     77      { once: true }
     78    );
     79  }
     80 }
     81 
     82 // Script evaluated in the target tab, to collect the results of injectedScript
     83 // evaluation in the inspectedWindow.reload tests.
     84 function collectEvalResults() {
     85  const results = [];
     86  let iframeDoc = document;
     87 
     88  while (iframeDoc) {
     89    if (iframeDoc.querySelector("pre")) {
     90      results.push(iframeDoc.querySelector("pre").textContent);
     91    }
     92    const iframe = iframeDoc.querySelector("iframe");
     93    iframeDoc = iframe ? iframe.contentDocument : null;
     94  }
     95  return JSON.stringify(results);
     96 }
     97 
     98 add_task(async function test_successfull_inspectedWindowEval_result() {
     99  const { commands, extension, fakeExtCallerInfo } = await setup(URL_ROOT_SSL);
    100 
    101  const result = await commands.inspectedWindowCommand.eval(
    102    fakeExtCallerInfo,
    103    "window.location",
    104    {}
    105  );
    106 
    107  ok(result.value, "Got a result from inspectedWindow eval");
    108  is(
    109    result.value.href,
    110    URL_ROOT_SSL,
    111    "Got the expected window.location.href property value"
    112  );
    113  is(
    114    result.value.protocol,
    115    "https:",
    116    "Got the expected window.location.protocol property value"
    117  );
    118 
    119  await teardown({ commands, extension });
    120 });
    121 
    122 add_task(async function test_successfull_inspectedWindowEval_resultAsGrip() {
    123  const { commands, extension, fakeExtCallerInfo, webConsoleFront } =
    124    await setup(URL_ROOT_SSL);
    125 
    126  let result = await commands.inspectedWindowCommand.eval(
    127    fakeExtCallerInfo,
    128    "window",
    129    {
    130      evalResultAsGrip: true,
    131      toolboxConsoleActorID: webConsoleFront.actor,
    132    }
    133  );
    134 
    135  ok(result.valueGrip, "Got a result from inspectedWindow eval");
    136  ok(result.valueGrip.actor, "Got a object actor as expected");
    137  is(result.valueGrip.type, "object", "Got a value grip of type object");
    138  is(
    139    result.valueGrip.class,
    140    "Window",
    141    "Got a value grip which is instanceof Location"
    142  );
    143 
    144  // Test invalid evalResultAsGrip request.
    145  result = await commands.inspectedWindowCommand.eval(
    146    fakeExtCallerInfo,
    147    "window",
    148    {
    149      evalResultAsGrip: true,
    150    }
    151  );
    152 
    153  ok(
    154    !result.value && !result.valueGrip,
    155    "Got a null result from the invalid inspectedWindow eval call"
    156  );
    157  ok(
    158    result.exceptionInfo.isError,
    159    "Got an API Error result from inspectedWindow eval"
    160  );
    161  ok(
    162    !result.exceptionInfo.isException,
    163    "An error isException is false as expected"
    164  );
    165  is(
    166    result.exceptionInfo.code,
    167    "E_PROTOCOLERROR",
    168    "Got the expected 'code' property in the error result"
    169  );
    170  is(
    171    result.exceptionInfo.description,
    172    "Inspector protocol error: %s - %s",
    173    "Got the expected 'description' property in the error result"
    174  );
    175  is(
    176    result.exceptionInfo.details.length,
    177    2,
    178    "The 'details' array property should contains 1 element"
    179  );
    180  is(
    181    result.exceptionInfo.details[0],
    182    "Unexpected invalid sidebar panel expression request",
    183    "Got the expected content in the error results's details"
    184  );
    185  is(
    186    result.exceptionInfo.details[1],
    187    "missing toolboxConsoleActorID",
    188    "Got the expected content in the error results's details"
    189  );
    190 
    191  await teardown({ commands, extension });
    192 });
    193 
    194 add_task(async function test_error_inspectedWindowEval_result() {
    195  const { commands, extension, fakeExtCallerInfo } = await setup(URL_ROOT_SSL);
    196 
    197  const result = await commands.inspectedWindowCommand.eval(
    198    fakeExtCallerInfo,
    199    "window",
    200    {}
    201  );
    202 
    203  ok(!result.value, "Got a null result from inspectedWindow eval");
    204  ok(
    205    result.exceptionInfo.isError,
    206    "Got an API Error result from inspectedWindow eval"
    207  );
    208  ok(
    209    !result.exceptionInfo.isException,
    210    "An error isException is false as expected"
    211  );
    212  is(
    213    result.exceptionInfo.code,
    214    "E_PROTOCOLERROR",
    215    "Got the expected 'code' property in the error result"
    216  );
    217  is(
    218    result.exceptionInfo.description,
    219    "Inspector protocol error: %s",
    220    "Got the expected 'description' property in the error result"
    221  );
    222  is(
    223    result.exceptionInfo.details.length,
    224    1,
    225    "The 'details' array property should contains 1 element"
    226  );
    227  ok(
    228    result.exceptionInfo.details[0].includes("cyclic object value"),
    229    "Got the expected content in the error results's details"
    230  );
    231 
    232  await teardown({ commands, extension });
    233 });
    234 
    235 add_task(async function test_exception_inspectedWindowEval_result() {
    236  const { commands, extension, fakeExtCallerInfo } = await setup(URL_ROOT_SSL);
    237 
    238  const result = await commands.inspectedWindowCommand.eval(
    239    fakeExtCallerInfo,
    240    "throw Error('fake eval error');",
    241    {}
    242  );
    243 
    244  ok(result.exceptionInfo.isException, "Got an exception as expected");
    245  ok(!result.value, "Got an undefined eval value");
    246  ok(!result.exceptionInfo.isError, "An exception should not be isError=true");
    247  ok(
    248    result.exceptionInfo.value.includes("Error: fake eval error"),
    249    "Got the expected exception message"
    250  );
    251 
    252  const expectedCallerInfo = `called from ${fakeExtCallerInfo.url}:${fakeExtCallerInfo.lineNumber}`;
    253  ok(
    254    result.exceptionInfo.value.includes(expectedCallerInfo),
    255    "Got the expected caller info in the exception message"
    256  );
    257 
    258  const expectedStack = `eval code:1:7`;
    259  ok(
    260    result.exceptionInfo.value.includes(expectedStack),
    261    "Got the expected stack trace in the exception message"
    262  );
    263 
    264  await teardown({ commands, extension });
    265 });
    266 
    267 add_task(async function test_exception_inspectedWindowReload() {
    268  const { commands, extension, fakeExtCallerInfo } = await setup(
    269    `${TEST_RELOAD_URL}?test=cache`
    270  );
    271 
    272  // Test reload with bypassCache=false.
    273 
    274  const waitForNoBypassCacheReload = waitForNextTabNavigated(commands);
    275  const reloadResult = await commands.inspectedWindowCommand.reload(
    276    fakeExtCallerInfo,
    277    {
    278      ignoreCache: false,
    279    }
    280  );
    281 
    282  ok(
    283    !reloadResult,
    284    "Got the expected undefined result from inspectedWindow reload"
    285  );
    286 
    287  await waitForNoBypassCacheReload;
    288 
    289  const noBypassCacheEval = await commands.scriptCommand.execute(
    290    "document.body.textContent"
    291  );
    292 
    293  is(
    294    noBypassCacheEval.result,
    295    "empty cache headers",
    296    "Got the expected result with reload forceBypassCache=false"
    297  );
    298 
    299  // Test reload with bypassCache=true.
    300 
    301  const waitForForceBypassCacheReload = waitForNextTabNavigated(commands);
    302  await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {
    303    ignoreCache: true,
    304  });
    305 
    306  await waitForForceBypassCacheReload;
    307 
    308  const forceBypassCacheEval = await commands.scriptCommand.execute(
    309    "document.body.textContent"
    310  );
    311 
    312  is(
    313    forceBypassCacheEval.result,
    314    "no-cache:no-cache",
    315    "Got the expected result with reload forceBypassCache=true"
    316  );
    317 
    318  await teardown({ commands, extension });
    319 });
    320 
    321 add_task(async function test_exception_inspectedWindowReload_customUserAgent() {
    322  const { commands, extension, fakeExtCallerInfo } = await setup(
    323    `${TEST_RELOAD_URL}?test=user-agent`
    324  );
    325 
    326  // Test reload with custom userAgent.
    327 
    328  const waitForCustomUserAgentReload = waitForNextTabNavigated(commands);
    329  await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {
    330    userAgent: "Customized User Agent",
    331  });
    332 
    333  await waitForCustomUserAgentReload;
    334 
    335  const customUserAgentEval = await commands.scriptCommand.execute(
    336    "document.body.textContent"
    337  );
    338 
    339  is(
    340    customUserAgentEval.result,
    341    "Customized User Agent",
    342    "Got the expected result on reload with a customized userAgent"
    343  );
    344 
    345  // Test reload with no custom userAgent.
    346 
    347  const waitForNoCustomUserAgentReload = waitForNextTabNavigated(commands);
    348  await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {});
    349 
    350  await waitForNoCustomUserAgentReload;
    351 
    352  const noCustomUserAgentEval = await commands.scriptCommand.execute(
    353    "document.body.textContent"
    354  );
    355 
    356  is(
    357    noCustomUserAgentEval.result,
    358    window.navigator.userAgent,
    359    "Got the expected result with reload without a customized userAgent"
    360  );
    361 
    362  await teardown({ commands, extension });
    363 });
    364 
    365 add_task(async function test_exception_inspectedWindowReload_injectedScript() {
    366  const { commands, extension, fakeExtCallerInfo } = await setup(
    367    `${TEST_RELOAD_URL}?test=injected-script&frames=3`
    368  );
    369 
    370  // Test reload with an injectedScript.
    371 
    372  const waitForInjectedScriptReload = waitForNextTabNavigated(commands);
    373  await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {
    374    injectedScript: `new ${injectedScript}`,
    375  });
    376  await waitForInjectedScriptReload;
    377 
    378  const injectedScriptEval = await commands.scriptCommand.execute(
    379    `(${collectEvalResults})()`
    380  );
    381 
    382  const expectedResult = new Array(5).fill("injected script executed first");
    383 
    384  SimpleTest.isDeeply(
    385    JSON.parse(injectedScriptEval.result),
    386    expectedResult,
    387    "Got the expected result on reload with an injected script"
    388  );
    389 
    390  // Test reload without an injectedScript.
    391 
    392  const waitForNoInjectedScriptReload = waitForNextTabNavigated(commands);
    393  await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {});
    394  await waitForNoInjectedScriptReload;
    395 
    396  const noInjectedScriptEval = await commands.scriptCommand.execute(
    397    `(${collectEvalResults})()`
    398  );
    399 
    400  const newExpectedResult = new Array(5).fill("injected script NOT executed");
    401 
    402  SimpleTest.isDeeply(
    403    JSON.parse(noInjectedScriptEval.result),
    404    newExpectedResult,
    405    "Got the expected result on reload with no injected script"
    406  );
    407 
    408  await teardown({ commands, extension });
    409 });
    410 
    411 add_task(async function test_exception_inspectedWindowReload_multiple_calls() {
    412  const { commands, extension, fakeExtCallerInfo } = await setup(
    413    `${TEST_RELOAD_URL}?test=user-agent`
    414  );
    415 
    416  // Test reload with custom userAgent three times (and then
    417  // check that only the first one has affected the page reload.
    418 
    419  const waitForCustomUserAgentReload = waitForNextTabNavigated(commands);
    420 
    421  commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {
    422    userAgent: "Customized User Agent 1",
    423  });
    424  commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {
    425    userAgent: "Customized User Agent 2",
    426  });
    427 
    428  await waitForCustomUserAgentReload;
    429 
    430  const customUserAgentEval = await commands.scriptCommand.execute(
    431    "document.body.textContent"
    432  );
    433 
    434  is(
    435    customUserAgentEval.result,
    436    "Customized User Agent 1",
    437    "Got the expected result on reload with a customized userAgent"
    438  );
    439 
    440  // Test reload with no custom userAgent.
    441 
    442  const waitForNoCustomUserAgentReload = waitForNextTabNavigated(commands);
    443  await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {});
    444 
    445  await waitForNoCustomUserAgentReload;
    446 
    447  const noCustomUserAgentEval = await commands.scriptCommand.execute(
    448    "document.body.textContent"
    449  );
    450 
    451  is(
    452    noCustomUserAgentEval.result,
    453    window.navigator.userAgent,
    454    "Got the expected result with reload without a customized userAgent"
    455  );
    456 
    457  await teardown({ commands, extension });
    458 });
    459 
    460 add_task(async function test_exception_inspectedWindowReload_stopped() {
    461  const { commands, extension, fakeExtCallerInfo } = await setup(
    462    `${TEST_RELOAD_URL}?test=injected-script&frames=3`
    463  );
    464 
    465  // Test reload on a page that calls window.stop() immediately during the page loading
    466 
    467  const waitForPageLoad = waitForNextTabNavigated(commands);
    468  await commands.inspectedWindowCommand.eval(
    469    fakeExtCallerInfo,
    470    "window.location += '&stop=windowStop'"
    471  );
    472 
    473  info("Load a webpage that calls 'window.stop()' while is still loading");
    474  await waitForPageLoad;
    475 
    476  info("Starting a reload with an injectedScript");
    477  const waitForInjectedScriptReload = waitForNextTabNavigated(commands);
    478  await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {
    479    injectedScript: `new ${injectedScript}`,
    480  });
    481  await waitForInjectedScriptReload;
    482 
    483  const injectedScriptEval = await commands.scriptCommand.execute(
    484    `(${collectEvalResults})()`
    485  );
    486 
    487  // The page should have stopped during the reload and only one injected script
    488  // is expected.
    489  const expectedResult = new Array(1).fill("injected script executed first");
    490 
    491  SimpleTest.isDeeply(
    492    JSON.parse(injectedScriptEval.result),
    493    expectedResult,
    494    "The injected script has been executed on the 'stopped' page reload"
    495  );
    496 
    497  // Reload again with no options.
    498 
    499  info("Reload the tab again without any reload options");
    500  const waitForNoInjectedScriptReload = waitForNextTabNavigated(commands);
    501  await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {});
    502  await waitForNoInjectedScriptReload;
    503 
    504  const noInjectedScriptEval = await commands.scriptCommand.execute(
    505    `(${collectEvalResults})()`
    506  );
    507 
    508  // The page should have stopped during the reload and no injected script should
    509  // have been executed during this second reload (or it would mean that the previous
    510  // customized reload was still pending and has wrongly affected the second reload)
    511  const newExpectedResult = new Array(1).fill("injected script NOT executed");
    512 
    513  SimpleTest.isDeeply(
    514    JSON.parse(noInjectedScriptEval.result),
    515    newExpectedResult,
    516    "No injectedScript should have been evaluated during the second reload"
    517  );
    518 
    519  await teardown({ commands, extension });
    520 });
    521 
    522 // TODO: check eval with $0 binding once implemented (Bug 1300590)