tor-browser

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

browser_aboutdebugging_addons_debug_console.js (17987B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 "use strict";
      4 
      5 /* import-globals-from helper-addons.js */
      6 Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "helper-addons.js", this);
      7 
      8 // There are shutdown issues for which multiple rejections are left uncaught.
      9 // See bug 1018184 for resolving these issues.
     10 const { PromiseTestUtils } = ChromeUtils.importESModule(
     11  "resource://testing-common/PromiseTestUtils.sys.mjs"
     12 );
     13 PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/);
     14 
     15 const { ExtensionStorageIDB } = ChromeUtils.importESModule(
     16  "resource://gre/modules/ExtensionStorageIDB.sys.mjs"
     17 );
     18 
     19 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
     20 requestLongerTimeout(2);
     21 
     22 const ADDON_ID = "test-devtools-webextension@mozilla.org";
     23 const ADDON_NAME = "base-test-devtools-webextension";
     24 
     25 const OTHER_ADDON_ID = "other-test-devtools-webextension@mozilla.org";
     26 const OTHER_ADDON_NAME = "other-test-devtools-webextension";
     27 
     28 const POPUPONLY_ADDON_ID = "popuponly-test-devtools-webextension@mozilla.org";
     29 const POPUPONLY_ADDON_NAME = "popuponly-test-devtools-webextension";
     30 
     31 const BACKGROUND_ADDON_ID = "background-test-devtools-webextension@mozilla.org";
     32 const BACKGROUND_ADDON_NAME = "background-test-devtools-webextension";
     33 
     34 const TEST_URI = "https://example.com/document-builder.sjs?html=foo";
     35 
     36 /**
     37 * This test file ensures that the webextension addon developer toolbox:
     38 * - when the debug button is clicked on a webextension, the opened toolbox
     39 *   has a working webconsole with the background page as default target;
     40 */
     41 add_task(async function testWebExtensionsToolboxWebConsole() {
     42  await pushPref("devtools.webconsole.filter.css", true);
     43  await enableExtensionDebugging();
     44 
     45  await addTab(TEST_URI);
     46 
     47  const { document, tab, window } = await openAboutDebugging();
     48  await selectThisFirefoxPage(document, window.AboutDebugging.store);
     49 
     50  await installTemporaryExtensionFromXPI(
     51    {
     52      background() {
     53        /* global browser */
     54        window.myWebExtensionAddonFunction = async function () {
     55          console.log(
     56            "Background page function called",
     57            this.browser.runtime.getManifest()
     58          );
     59 
     60          const [t] = await browser.tabs.query({
     61            url: "https://example.com/document-builder*",
     62          });
     63          browser.tabs.sendMessage(t.id, {});
     64        };
     65 
     66        const style = document.createElement("style");
     67        style.textContent = "* { color: error; }";
     68        document.documentElement.appendChild(style);
     69 
     70        browser.runtime.onMessage.addListener(() => {
     71          browser.runtime.sendMessage("messageForOnMessageInPopup");
     72          throw new Error("onMessage exception from background page");
     73        });
     74 
     75        browser.storage.local.onChanged.addListener(() => {
     76          throw new Error("local.onChanged exception");
     77        });
     78 
     79        throw new Error("Background page exception");
     80      },
     81      extraProperties: {
     82        browser_action: {
     83          default_title: "WebExtension Popup Debugging",
     84          default_popup: "popup.html",
     85          default_area: "navbar",
     86        },
     87        permissions: ["storage", "https://example.com/*"],
     88        content_scripts: [
     89          {
     90            matches: ["<all_urls>"],
     91            js: ["content-script.js"],
     92          },
     93        ],
     94      },
     95      files: {
     96        "popup.html": `<!DOCTYPE html>
     97        <html>
     98          <head>
     99            <meta charset="utf-8">
    100            <script src="popup.js"></script>
    101          </head>
    102          <body>
    103            Popup
    104          </body>
    105        </html>
    106      `,
    107        "popup.js": function () {
    108          console.log("Popup log");
    109 
    110          const style = document.createElement("style");
    111          style.textContent = "* { color: popup-error; }";
    112          document.documentElement.appendChild(style);
    113 
    114          browser.runtime.onMessage.addListener(() => {
    115            throw new Error("onMessage exception from popup");
    116          });
    117 
    118          browser.runtime.sendMessage("messageForOnMessageInBackgroundPage");
    119 
    120          throw new Error("Popup exception");
    121        },
    122        "content-script.js": function () {
    123          browser.runtime.onMessage.addListener(() => {
    124            throw new Error("onMessage exception from content script");
    125          });
    126 
    127          throw new Error("Content script exception");
    128        },
    129      },
    130      id: ADDON_ID,
    131      name: ADDON_NAME,
    132    },
    133    document
    134  );
    135 
    136  // Install another addon in order to ensure we don't get its logs
    137  await installTemporaryExtensionFromXPI(
    138    {
    139      background() {
    140        console.log("Other addon log");
    141 
    142        const style = document.createElement("style");
    143        style.textContent = "* { background-color: error; }";
    144        document.documentElement.appendChild(style);
    145 
    146        throw new Error("Other addon exception");
    147      },
    148      extraProperties: {
    149        browser_action: {
    150          default_title: "Other addon popup",
    151          default_popup: "other-popup.html",
    152          default_area: "navbar",
    153        },
    154      },
    155      files: {
    156        "other-popup.html": `<!DOCTYPE html>
    157        <html>
    158          <head>
    159            <meta charset="utf-8">
    160            <script src="other-popup.js"></script>
    161          </head>
    162          <body>
    163            Other popup
    164          </body>
    165        </html>
    166      `,
    167        "other-popup.js": function () {
    168          console.log("Other popup log");
    169 
    170          const style = document.createElement("style");
    171          style.textContent = "* { background-color: popup-error; }";
    172          document.documentElement.appendChild(style);
    173 
    174          throw new Error("Other popup exception");
    175        },
    176      },
    177      id: OTHER_ADDON_ID,
    178      name: OTHER_ADDON_NAME,
    179    },
    180    document
    181  );
    182 
    183  // Trigger a browser.local storage change
    184  ExtensionStorageIDB.notifyListeners(ADDON_ID, {});
    185 
    186  const { devtoolsWindow } = await openAboutDevtoolsToolbox(
    187    document,
    188    tab,
    189    window,
    190    ADDON_NAME
    191  );
    192  const toolbox = getToolbox(devtoolsWindow);
    193  const webconsole = await toolbox.selectTool("webconsole");
    194  const { hud } = webconsole;
    195 
    196  info("Wait for the exception coming from the background page");
    197  await waitUntil(() => {
    198    return !!findMessagesByType(hud, "Background page exception", ".error")
    199      .length;
    200  });
    201 
    202  info("Trigger some code in the background page logging some stuff");
    203  const onLogMessage = waitUntil(() => {
    204    return !!findMessagesByType(
    205      hud,
    206      "Background page function called",
    207      ".message"
    208    ).length;
    209  });
    210  hud.ui.wrapper.dispatchEvaluateExpression("myWebExtensionAddonFunction()");
    211  await onLogMessage;
    212 
    213  info("Open the two add-ons popups to cover popups messages");
    214  const onPopupMessage = waitUntil(() => {
    215    return !!findMessagesByType(hud, "Popup exception", ".error").length;
    216  });
    217  clickOnAddonWidget(OTHER_ADDON_ID);
    218  clickOnAddonWidget(ADDON_ID);
    219  await onPopupMessage;
    220  await waitUntil(() => {
    221    return !!findMessagesByType(
    222      hud,
    223      "onMessage exception from background page",
    224      ".error"
    225    ).length;
    226  });
    227  await waitUntil(() => {
    228    return !!findMessagesByType(hud, "onMessage exception from popup", ".error")
    229      .length;
    230  });
    231  await waitUntil(() => {
    232    return !!findMessagesByType(hud, "local.onChanged exception", ".error")
    233      .length;
    234  });
    235 
    236  info("Assert the context of the evaluation context selector");
    237  const contextLabels = getContextLabels(toolbox);
    238  is(contextLabels.length, 3);
    239  is(contextLabels[0], "Web Extension Fallback Document");
    240  is(contextLabels[1], "/_generated_background_page.html");
    241  is(contextLabels[2], "/popup.html");
    242 
    243  info("Wait a bit to catch unexpected duplicates or mixed up messages");
    244  await wait(1000);
    245 
    246  is(
    247    findMessagesByType(hud, "Background page exception", ".error").length,
    248    1,
    249    "We get the background page exception"
    250  );
    251  is(
    252    findMessagesByType(hud, "Popup exception", ".error").length,
    253    1,
    254    "We get the popup exception"
    255  );
    256  is(
    257    findMessagesByType(
    258      hud,
    259      "Expected color but found ‘error’.  Error in parsing value for ‘color’.  Declaration dropped.",
    260      ".warn"
    261    ).length,
    262    1,
    263    "We get the addon's background page CSS error message"
    264  );
    265  is(
    266    findMessagesByType(
    267      hud,
    268      "Expected color but found ‘popup-error’.  Error in parsing value for ‘color’.  Declaration dropped.",
    269      ".warn"
    270    ).length,
    271    1,
    272    "We get the addon's popup CSS error message"
    273  );
    274 
    275  // Verify that we don't get the other addon log and errors
    276  ok(
    277    !findMessageByType(hud, "Other addon log", ".console-api"),
    278    "We don't get the other addon log"
    279  );
    280  ok(
    281    !findMessageByType(hud, "Other addon exception", ".console-api"),
    282    "We don't get the other addon exception"
    283  );
    284  ok(
    285    !findMessageByType(hud, "Other popup log", ".console-api"),
    286    "We don't get the other addon popup log"
    287  );
    288  ok(
    289    !findMessageByType(hud, "Other popup exception", ".error"),
    290    "We don't get the other addon popup exception"
    291  );
    292  ok(
    293    !findMessageByType(
    294      hud,
    295      "Expected color but found ‘error’.  Error in parsing value for ‘background-color’.  Declaration dropped.",
    296      ".warn"
    297    ),
    298    "We don't get the other addon's background page CSS error message"
    299  );
    300  ok(
    301    !findMessageByType(
    302      hud,
    303      "Expected color but found ‘popup-error’.  Error in parsing value for ‘background-color’.  Declaration dropped.",
    304      ".warn"
    305    ),
    306    "We don't get the other addon's popup CSS error message"
    307  );
    308 
    309  // Verify that console evaluations still work after reloading the page
    310  info("Reload the webextension document");
    311  const { onResource: onDomCompleteResource } =
    312    await toolbox.commands.resourceCommand.waitForNextResource(
    313      toolbox.commands.resourceCommand.TYPES.DOCUMENT_EVENT,
    314      {
    315        ignoreExistingResources: true,
    316        predicate: resource => {
    317          return (
    318            resource.name === "dom-complete" &&
    319            resource.targetFront.url.endsWith("background_page.html")
    320          );
    321        },
    322      }
    323    );
    324  hud.ui.wrapper.dispatchEvaluateExpression("location.reload()");
    325  await onDomCompleteResource;
    326 
    327  info("Try to evaluate something after reload");
    328 
    329  const onEvaluationResultAfterReload = waitUntil(() =>
    330    findMessageByType(hud, "result:2", ".result")
    331  );
    332  const onMessageAfterReload = waitUntil(() =>
    333    findMessageByType(hud, "message after reload", ".console-api")
    334  );
    335  hud.ui.wrapper.dispatchEvaluateExpression(
    336    "console.log('message after reload'); 'result:' + (1 + 1)"
    337  );
    338  // Both cover that the console.log worked
    339  await onMessageAfterReload;
    340  // And we received the evaluation result
    341  await onEvaluationResultAfterReload;
    342 
    343  await closeWebExtAboutDevtoolsToolbox(devtoolsWindow, window);
    344 
    345  info(
    346    "Open a toolbox against the tab in order to cover the content script exceptions"
    347  );
    348  const { devtoolsTab, devtoolsWindow: tabDevtoolsWindow } =
    349    await openAboutDevtoolsToolbox(document, tab, window, TEST_URI);
    350  const tabToolbox = getToolbox(tabDevtoolsWindow);
    351  const tabWebconsole = await tabToolbox.selectTool("webconsole");
    352  const tabHud = tabWebconsole.hud;
    353  await waitUntil(() => {
    354    return !!findMessagesByType(
    355      tabHud,
    356      "onMessage exception from content script",
    357      ".error"
    358    ).length;
    359  });
    360  await closeAboutDevtoolsToolbox(document, devtoolsTab, window);
    361 
    362  // Note that it seems to be important to remove the addons in the reverse order
    363  // from which they were installed...
    364  await removeTemporaryExtension(OTHER_ADDON_NAME, document);
    365  await removeTemporaryExtension(ADDON_NAME, document);
    366  await removeTab(tab);
    367 });
    368 
    369 add_task(async function testWebExtensionNoBgScript() {
    370  await pushPref("devtools.webconsole.filter.css", true);
    371  await enableExtensionDebugging();
    372  const { document, tab, window } = await openAboutDebugging();
    373  await selectThisFirefoxPage(document, window.AboutDebugging.store);
    374 
    375  await installTemporaryExtensionFromXPI(
    376    {
    377      extraProperties: {
    378        browser_action: {
    379          default_title: "WebExtension Popup Only",
    380          default_popup: "popup.html",
    381          default_area: "navbar",
    382        },
    383      },
    384      files: {
    385        "popup.html": `<!DOCTYPE html>
    386        <html>
    387          <head>
    388            <meta charset="utf-8">
    389            <script src="popup.js"></script>
    390          </head>
    391          <body>
    392            Popup
    393          </body>
    394        </html>
    395      `,
    396        "popup.js": function () {
    397          console.log("Popup-only log");
    398 
    399          const style = document.createElement("style");
    400          style.textContent = "* { color: popup-only-error; }";
    401          document.documentElement.appendChild(style);
    402 
    403          throw new Error("Popup-only exception");
    404        },
    405      },
    406      id: POPUPONLY_ADDON_ID,
    407      name: POPUPONLY_ADDON_NAME,
    408    },
    409    document
    410  );
    411 
    412  const { devtoolsWindow } = await openAboutDevtoolsToolbox(
    413    document,
    414    tab,
    415    window,
    416    POPUPONLY_ADDON_NAME
    417  );
    418  const toolbox = getToolbox(devtoolsWindow);
    419  const webconsole = await toolbox.selectTool("webconsole");
    420  const { hud } = webconsole;
    421 
    422  info("Open the add-on popup");
    423  const onPopupMessage = waitUntil(() => {
    424    return !!findMessagesByType(hud, "Popup-only exception", ".error").length;
    425  });
    426  clickOnAddonWidget(POPUPONLY_ADDON_ID);
    427  await onPopupMessage;
    428 
    429  info("Wait a bit to catch unexpected duplicates or mixed up messages");
    430  await wait(1000);
    431  is(
    432    findMessagesByType(hud, "Popup-only exception", ".error").length,
    433    1,
    434    "We get the popup exception"
    435  );
    436  is(
    437    findMessagesByType(hud, "Popup-only log", ".console-api").length,
    438    1,
    439    "We get the addon's popup log"
    440  );
    441  is(
    442    findMessagesByType(
    443      hud,
    444      "Expected color but found ‘popup-only-error’.  Error in parsing value for ‘color’.  Declaration dropped.",
    445      ".warn"
    446    ).length,
    447    1,
    448    "We get the addon's popup CSS error message"
    449  );
    450 
    451  await closeWebExtAboutDevtoolsToolbox(devtoolsWindow, window);
    452  await removeTemporaryExtension(POPUPONLY_ADDON_NAME, document);
    453  await removeTab(tab);
    454 });
    455 
    456 // Check that reloading the addon several times does not break the console,
    457 // see Bug 1778951.
    458 add_task(async function testWebExtensionTwoReloads() {
    459  await enableExtensionDebugging();
    460  const { document, tab, window } = await openAboutDebugging();
    461  await selectThisFirefoxPage(document, window.AboutDebugging.store);
    462 
    463  await installTemporaryExtensionFromXPI(
    464    {
    465      background() {
    466        console.log("Background page log");
    467      },
    468      extraProperties: {
    469        browser_action: {
    470          default_title: "WebExtension with background script",
    471          default_popup: "popup.html",
    472          default_area: "navbar",
    473        },
    474      },
    475      files: {
    476        "popup.html": `<!DOCTYPE html>
    477        <html>
    478          <body>
    479            Popup
    480          </body>
    481        </html>
    482      `,
    483      },
    484      id: BACKGROUND_ADDON_ID,
    485      name: BACKGROUND_ADDON_NAME,
    486    },
    487    document
    488  );
    489 
    490  // Retrieve the addonTarget element before calling `openAboutDevtoolsToolbox`,
    491  // otherwise it will pick the about:devtools-toolbox tab with the same name
    492  // instead.
    493  const addonTarget = findDebugTargetByText(BACKGROUND_ADDON_NAME, document);
    494 
    495  const { devtoolsWindow } = await openAboutDevtoolsToolbox(
    496    document,
    497    tab,
    498    window,
    499    BACKGROUND_ADDON_NAME
    500  );
    501  const toolbox = getToolbox(devtoolsWindow);
    502  const webconsole = await toolbox.selectTool("webconsole");
    503  const { hud } = webconsole;
    504 
    505  // Verify that console evaluations still work after reloading the addon
    506  info("Reload the webextension itself");
    507  let { onResource: onDomCompleteResource } =
    508    await toolbox.commands.resourceCommand.waitForNextResource(
    509      toolbox.commands.resourceCommand.TYPES.DOCUMENT_EVENT,
    510      {
    511        ignoreExistingResources: true,
    512        predicate: resource =>
    513          resource.name === "dom-complete" &&
    514          resource.targetFront.url.endsWith("background_page.html"),
    515      }
    516    );
    517  const reloadButton = addonTarget.querySelector(
    518    ".qa-temporary-extension-reload-button"
    519  );
    520  reloadButton.click();
    521  await onDomCompleteResource;
    522 
    523  info("Try to evaluate something after 1st addon reload");
    524  // Wait before evaluating the message, otherwise they might be cleaned up by
    525  // the console UI.
    526  info("Wait until the background script log is visible");
    527  await waitUntil(() =>
    528    findMessageByType(hud, "Background page log", ".message")
    529  );
    530 
    531  hud.ui.wrapper.dispatchEvaluateExpression("40+1");
    532  await waitUntil(() => findMessageByType(hud, "41", ".result"));
    533 
    534  info("Reload the extension a second time");
    535  ({ onResource: onDomCompleteResource } =
    536    await toolbox.commands.resourceCommand.waitForNextResource(
    537      toolbox.commands.resourceCommand.TYPES.DOCUMENT_EVENT,
    538      {
    539        ignoreExistingResources: true,
    540        predicate: resource =>
    541          resource.name === "dom-complete" &&
    542          resource.targetFront.url.endsWith("background_page.html"),
    543      }
    544    ));
    545  reloadButton.click();
    546  await onDomCompleteResource;
    547 
    548  info("Wait until the background script log is visible - after reload");
    549  await waitUntil(() =>
    550    findMessageByType(hud, "Background page log", ".message")
    551  );
    552 
    553  info("Try to evaluate something after 2nd addon reload");
    554  hud.ui.wrapper.dispatchEvaluateExpression("40+2");
    555  await waitUntil(() => findMessageByType(hud, "42", ".result"));
    556 
    557  await closeWebExtAboutDevtoolsToolbox(devtoolsWindow, window);
    558  await removeTemporaryExtension(BACKGROUND_ADDON_NAME, document);
    559  await removeTab(tab);
    560 });
    561 
    562 function getContextLabels(toolbox) {
    563  // Note that the context menu is in the top level chrome document (toolbox.xhtml)
    564  // instead of webconsole.xhtml.
    565  const labels = toolbox.doc.querySelectorAll(
    566    "#webconsole-console-evaluation-context-selector-menu-list li .label"
    567  );
    568  return Array.from(labels).map(item => item.textContent);
    569 }