tor-browser

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

browser_toolbox_options.js (16677B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 // Tests that changing preferences in the options panel updates the prefs
      7 // and toggles appropriate things in the toolbox.
      8 
      9 var doc = null,
     10  toolbox = null,
     11  panelWin = null,
     12  modifiedPrefs = [];
     13 const L10N = new LocalizationHelper(
     14  "devtools/client/locales/toolbox.properties"
     15 );
     16 const { PrefObserver } = require("resource://devtools/client/shared/prefs.js");
     17 const {
     18  BOOLEAN_CONFIGURATION_PREFS,
     19 } = require("resource://devtools/client/framework/toolbox.js");
     20 
     21 add_task(async function () {
     22  const URL =
     23    "data:text/html;charset=utf8,test for dynamically registering " +
     24    "and unregistering tools";
     25  registerNewTool();
     26  const tab = await addTab(URL);
     27  toolbox = await gDevTools.showToolboxForTab(tab);
     28 
     29  doc = toolbox.doc;
     30  await registerNewPerToolboxTool();
     31  await testSelectTool();
     32  await testOptionsShortcut();
     33  await testOptions();
     34  await testToggleTools();
     35 
     36  // Test that registered WebExtensions becomes entries in the
     37  // options panel and toggling their checkbox toggle the related
     38  // preference.
     39  await registerNewWebExtensions();
     40  await testToggleWebExtensions();
     41 
     42  await cleanup();
     43 });
     44 
     45 function registerNewTool() {
     46  const toolDefinition = {
     47    id: "testTool",
     48    isToolSupported: () => true,
     49    visibilityswitch: "devtools.test-tool.enabled",
     50    url: "about:blank",
     51    label: "someLabel",
     52  };
     53 
     54  ok(gDevTools, "gDevTools exists");
     55  ok(
     56    !gDevTools.getToolDefinitionMap().has("testTool"),
     57    "The tool is not registered"
     58  );
     59 
     60  gDevTools.registerTool(toolDefinition);
     61  ok(
     62    gDevTools.getToolDefinitionMap().has("testTool"),
     63    "The tool is registered"
     64  );
     65 }
     66 
     67 // Register a fake WebExtension to check that it is
     68 // listed in the toolbox options.
     69 function registerNewWebExtensions() {
     70  // Register some fake extensions and init the related preferences
     71  // (similarly to ext-devtools.js).
     72  for (let i = 0; i < 2; i++) {
     73    const extPref = `devtools.webextensions.fakeExtId${i}.enabled`;
     74    Services.prefs.setBoolPref(extPref, true);
     75 
     76    toolbox.registerWebExtension(`fakeUUID${i}`, {
     77      name: `Fake WebExtension ${i}`,
     78      pref: extPref,
     79    });
     80  }
     81 }
     82 
     83 function registerNewPerToolboxTool() {
     84  const toolDefinition = {
     85    id: "test-pertoolbox-tool",
     86    isToolSupported: () => true,
     87    visibilityswitch: "devtools.test-pertoolbox-tool.enabled",
     88    url: "about:blank",
     89    label: "perToolboxSomeLabel",
     90  };
     91 
     92  ok(gDevTools, "gDevTools exists");
     93  ok(
     94    !gDevTools.getToolDefinitionMap().has("test-pertoolbox-tool"),
     95    "The per-toolbox tool is not registered globally"
     96  );
     97 
     98  ok(toolbox, "toolbox exists");
     99  ok(
    100    !toolbox.hasAdditionalTool("test-pertoolbox-tool"),
    101    "The per-toolbox tool is not yet registered to the toolbox"
    102  );
    103 
    104  toolbox.addAdditionalTool(toolDefinition);
    105 
    106  ok(
    107    !gDevTools.getToolDefinitionMap().has("test-pertoolbox-tool"),
    108    "The per-toolbox tool is not registered globally"
    109  );
    110  ok(
    111    toolbox.hasAdditionalTool("test-pertoolbox-tool"),
    112    "The per-toolbox tool has been registered to the toolbox"
    113  );
    114 }
    115 
    116 async function testSelectTool() {
    117  info("Checking to make sure that the options panel can be selected.");
    118 
    119  const onceSelected = toolbox.once("options-selected");
    120  toolbox.selectTool("options");
    121  await onceSelected;
    122  ok(true, "Toolbox selected via selectTool method");
    123 }
    124 
    125 async function testOptionsShortcut() {
    126  info("Selecting another tool, then reselecting options panel with keyboard.");
    127 
    128  await toolbox.selectTool("webconsole");
    129  is(toolbox.currentToolId, "webconsole", "webconsole is selected");
    130  synthesizeKeyShortcut(L10N.getStr("toolbox.help.key"));
    131  is(toolbox.currentToolId, "options", "Toolbox selected via shortcut key");
    132  synthesizeKeyShortcut(L10N.getStr("toolbox.help.key"));
    133  is(toolbox.currentToolId, "webconsole", "webconsole is reselected");
    134  synthesizeKeyShortcut(L10N.getStr("toolbox.help.key"));
    135  is(toolbox.currentToolId, "options", "Toolbox selected via shortcut key");
    136 }
    137 
    138 async function testOptions() {
    139  const tool = toolbox.getPanel("options");
    140  panelWin = tool.panelWin;
    141  const prefNodes = tool.panelDoc.querySelectorAll(
    142    "input[type=checkbox][data-pref]"
    143  );
    144  ok(
    145    [...prefNodes].some(prefNode => prefNode.hasAttribute("data-force-reload")),
    146    "There's at least one checkbox with the data-force-reload attribute"
    147  );
    148 
    149  // Store modified pref names so that they can be cleared on error.
    150  for (const node of tool.panelDoc.querySelectorAll("[data-pref]")) {
    151    const pref = node.getAttribute("data-pref");
    152    modifiedPrefs.push(pref);
    153  }
    154 
    155  for (const node of prefNodes) {
    156    const prefValue = GetPref(node.getAttribute("data-pref"));
    157 
    158    // Test clicking the checkbox for each options pref
    159    await testMouseClick(node, prefValue);
    160 
    161    // Do again with opposite values to reset prefs
    162    await testMouseClick(node, !prefValue);
    163  }
    164 
    165  const prefSelects = tool.panelDoc.querySelectorAll("select[data-pref]");
    166  for (const node of prefSelects) {
    167    await testSelect(node);
    168  }
    169 }
    170 
    171 async function testSelect(select) {
    172  const pref = select.getAttribute("data-pref");
    173  const options = Array.from(select.options);
    174  info("Checking select for: " + pref);
    175 
    176  is(
    177    `${select.options[select.selectedIndex].value}`,
    178    `${GetPref(pref)}`,
    179    "select starts out selected"
    180  );
    181 
    182  for (const option of options) {
    183    if (options.indexOf(option) === select.selectedIndex) {
    184      continue;
    185    }
    186 
    187    const observer = new PrefObserver("devtools.");
    188 
    189    let changeSeen = false;
    190    const changeSeenPromise = new Promise(resolve => {
    191      observer.once(pref, () => {
    192        changeSeen = true;
    193        is(
    194          `${GetPref(pref)}`,
    195          `${option.value}`,
    196          "Preference been switched for " + pref
    197        );
    198        resolve();
    199      });
    200    });
    201 
    202    select.selectedIndex = options.indexOf(option);
    203    const changeEvent = new Event("change");
    204    select.dispatchEvent(changeEvent);
    205 
    206    await changeSeenPromise;
    207 
    208    ok(changeSeen, "Correct pref was changed");
    209    observer.destroy();
    210  }
    211 }
    212 
    213 async function testMouseClick(node, prefValue) {
    214  const observer = new PrefObserver("devtools.");
    215 
    216  const pref = node.getAttribute("data-pref");
    217  let changeSeen = false;
    218  const changeSeenPromise = new Promise(resolve => {
    219    observer.once(pref, () => {
    220      changeSeen = true;
    221      is(GetPref(pref), !prefValue, "New value is correct for " + pref);
    222      resolve();
    223    });
    224  });
    225 
    226  // if changing the setting reloads the page, waits for the toolbox to be reloaded
    227  const waitForDevToolsReload = node.hasAttribute("data-force-reload")
    228    ? await watchForDevToolsReload(gBrowser.selectedBrowser)
    229    : null;
    230 
    231  const onNewConfigurationApplied = Object.keys(
    232    BOOLEAN_CONFIGURATION_PREFS
    233  ).includes(pref)
    234    ? toolbox.once("new-configuration-applied")
    235    : null;
    236 
    237  node.scrollIntoView();
    238 
    239  // We use executeSoon here to ensure that the element is in view and
    240  // clickable.
    241  executeSoon(function () {
    242    info("Click event synthesized for pref " + pref);
    243    EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
    244  });
    245 
    246  await changeSeenPromise;
    247 
    248  ok(changeSeen, "Correct pref was changed");
    249 
    250  if (onNewConfigurationApplied) {
    251    await onNewConfigurationApplied;
    252    ok(true, `Configuration was changed when updating pref "${pref}"`);
    253  }
    254 
    255  if (waitForDevToolsReload) {
    256    await waitForDevToolsReload();
    257    ok(true, `The page was reloaded when toggling ${node.outerHTML}`);
    258  }
    259 
    260  observer.destroy();
    261 }
    262 
    263 async function testToggleWebExtensions() {
    264  const disabledExtensions = new Set();
    265  const toggleableWebExtensions = toolbox.listWebExtensions();
    266 
    267  function toggleWebExtension(node) {
    268    node.scrollIntoView();
    269    EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
    270  }
    271 
    272  function assertExpectedDisabledExtensions() {
    273    for (const ext of toggleableWebExtensions) {
    274      if (disabledExtensions.has(ext)) {
    275        ok(
    276          !toolbox.isWebExtensionEnabled(ext.uuid),
    277          `The WebExtension "${ext.name}" should be disabled`
    278        );
    279      } else {
    280        ok(
    281          toolbox.isWebExtensionEnabled(ext.uuid),
    282          `The WebExtension "${ext.name}" should  be enabled`
    283        );
    284      }
    285    }
    286  }
    287 
    288  function assertAllExtensionsDisabled() {
    289    const enabledUUIDs = toggleableWebExtensions
    290      .filter(ext => toolbox.isWebExtensionEnabled(ext.uuid))
    291      .map(ext => ext.uuid);
    292 
    293    Assert.deepEqual(
    294      enabledUUIDs,
    295      [],
    296      "All the registered WebExtensions should be disabled"
    297    );
    298  }
    299 
    300  function assertAllExtensionsEnabled() {
    301    const disabledUUIDs = toolbox
    302      .listWebExtensions()
    303      .filter(ext => !toolbox.isWebExtensionEnabled(ext.uuid))
    304      .map(ext => ext.uuid);
    305 
    306    Assert.deepEqual(
    307      disabledUUIDs,
    308      [],
    309      "All the registered WebExtensions should be enabled"
    310    );
    311  }
    312 
    313  function getWebExtensionNodes() {
    314    const toolNodes = panelWin.document.querySelectorAll(
    315      "#default-tools-box input[type=checkbox]:not([data-unsupported])," +
    316        "#additional-tools-box input[type=checkbox]:not([data-unsupported])"
    317    );
    318 
    319    return [...toolNodes].filter(node => {
    320      return toggleableWebExtensions.some(
    321        ({ uuid }) => node.getAttribute("id") === `webext-${uuid}`
    322      );
    323    });
    324  }
    325 
    326  let webExtensionNodes = getWebExtensionNodes();
    327 
    328  is(
    329    webExtensionNodes.length,
    330    toggleableWebExtensions.length,
    331    "There should be a toggle checkbox for every WebExtension registered"
    332  );
    333 
    334  for (const ext of toggleableWebExtensions) {
    335    ok(
    336      toolbox.isWebExtensionEnabled(ext.uuid),
    337      `The WebExtension "${ext.name}" is initially enabled`
    338    );
    339  }
    340 
    341  // Store modified pref names so that they can be cleared on error.
    342  for (const ext of toggleableWebExtensions) {
    343    modifiedPrefs.push(ext.pref);
    344  }
    345 
    346  // Turn each registered WebExtension to disabled.
    347  for (const node of webExtensionNodes) {
    348    toggleWebExtension(node);
    349 
    350    const toggledExt = toggleableWebExtensions.find(ext => {
    351      return node.id == `webext-${ext.uuid}`;
    352    });
    353    ok(toggledExt, "Found a WebExtension for the checkbox element");
    354    disabledExtensions.add(toggledExt);
    355 
    356    assertExpectedDisabledExtensions();
    357  }
    358 
    359  assertAllExtensionsDisabled();
    360 
    361  // Turn each registered WebExtension to enabled.
    362  for (const node of webExtensionNodes) {
    363    toggleWebExtension(node);
    364 
    365    const toggledExt = toggleableWebExtensions.find(ext => {
    366      return node.id == `webext-${ext.uuid}`;
    367    });
    368    ok(toggledExt, "Found a WebExtension for the checkbox element");
    369    disabledExtensions.delete(toggledExt);
    370 
    371    assertExpectedDisabledExtensions();
    372  }
    373 
    374  assertAllExtensionsEnabled();
    375 
    376  // Unregister the WebExtensions one by one, and check that only the expected
    377  // ones have been unregistered, and the remaining onea are still listed.
    378  for (const ext of toggleableWebExtensions) {
    379    ok(
    380      !!toolbox.listWebExtensions().length,
    381      "There should still be extensions registered"
    382    );
    383    toolbox.unregisterWebExtension(ext.uuid);
    384 
    385    const registeredUUIDs = toolbox.listWebExtensions().map(item => item.uuid);
    386    ok(
    387      !registeredUUIDs.includes(ext.uuid),
    388      `the WebExtension "${ext.name}" should have been unregistered`
    389    );
    390 
    391    webExtensionNodes = getWebExtensionNodes();
    392 
    393    const checkboxEl = webExtensionNodes.find(
    394      el => el.id === `webext-${ext.uuid}`
    395    );
    396    is(
    397      checkboxEl,
    398      undefined,
    399      "The unregistered WebExtension checkbox should have been removed"
    400    );
    401 
    402    is(
    403      registeredUUIDs.length,
    404      webExtensionNodes.length,
    405      "There should be the expected number of WebExtensions checkboxes"
    406    );
    407  }
    408 
    409  is(
    410    toolbox.listWebExtensions().length,
    411    0,
    412    "All WebExtensions have been unregistered"
    413  );
    414 
    415  webExtensionNodes = getWebExtensionNodes();
    416 
    417  is(
    418    webExtensionNodes.length,
    419    0,
    420    "There should not be any checkbox for the unregistered WebExtensions"
    421  );
    422 }
    423 
    424 function getToolNode(id) {
    425  return panelWin.document.getElementById(id);
    426 }
    427 
    428 async function testToggleTools() {
    429  const toolNodes = panelWin.document.querySelectorAll(
    430    "#default-tools-box input[type=checkbox]:not([data-unsupported])," +
    431      "#additional-tools-box input[type=checkbox]:not([data-unsupported])"
    432  );
    433  const toolNodeIds = [...toolNodes].map(node => node.id);
    434  const enabledToolIds = [...toolNodes]
    435    .filter(node => node.checked)
    436    .map(node => node.id);
    437 
    438  const toggleableTools = gDevTools
    439    .getDefaultTools()
    440    .filter(tool => {
    441      return tool.visibilityswitch;
    442    })
    443    .concat(gDevTools.getAdditionalTools())
    444    .concat(toolbox.getAdditionalTools());
    445 
    446  for (const node of toolNodes) {
    447    const id = node.getAttribute("id");
    448    ok(
    449      toggleableTools.some(tool => tool.id === id),
    450      "There should be a toggle checkbox for: " + id
    451    );
    452  }
    453 
    454  // Store modified pref names so that they can be cleared on error.
    455  for (const tool of toggleableTools) {
    456    const pref = tool.visibilityswitch;
    457    modifiedPrefs.push(pref);
    458  }
    459 
    460  // Toggle each tool
    461  for (const id of toolNodeIds) {
    462    await toggleTool(getToolNode(id));
    463  }
    464 
    465  // Toggle again to reset tool enablement state
    466  for (const id of toolNodeIds) {
    467    await toggleTool(getToolNode(id));
    468  }
    469 
    470  // Test that a tool can still be added when no tabs are present:
    471  // Disable all tools
    472  for (const id of enabledToolIds) {
    473    await toggleTool(getToolNode(id));
    474  }
    475  // Re-enable the tools which are enabled by default
    476  for (const id of enabledToolIds) {
    477    await toggleTool(getToolNode(id));
    478  }
    479 
    480  // Toggle first, middle, and last tools to ensure that toolbox tabs are
    481  // inserted in order
    482  const firstToolId = toolNodeIds[0];
    483  const middleToolId = toolNodeIds[(toolNodeIds.length / 2) | 0];
    484  const lastToolId = toolNodeIds[toolNodeIds.length - 1];
    485 
    486  await toggleTool(getToolNode(firstToolId));
    487  await toggleTool(getToolNode(firstToolId));
    488  await toggleTool(getToolNode(middleToolId));
    489  await toggleTool(getToolNode(middleToolId));
    490  await toggleTool(getToolNode(lastToolId));
    491  await toggleTool(getToolNode(lastToolId));
    492 }
    493 
    494 /**
    495 * Toggle tool node checkbox. Note: because toggling the checkbox will result in
    496 * re-rendering of the tool list, we must re-query the checkboxes every time.
    497 */
    498 async function toggleTool(node) {
    499  const toolId = node.getAttribute("id");
    500 
    501  const registeredPromise = new Promise(resolve => {
    502    if (node.checked) {
    503      gDevTools.once(
    504        "tool-unregistered",
    505        checkUnregistered.bind(null, toolId, resolve)
    506      );
    507    } else {
    508      gDevTools.once(
    509        "tool-registered",
    510        checkRegistered.bind(null, toolId, resolve)
    511      );
    512    }
    513  });
    514  node.scrollIntoView();
    515  EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
    516 
    517  await registeredPromise;
    518 }
    519 
    520 function checkUnregistered(toolId, resolve, data) {
    521  if (data == toolId) {
    522    ok(true, "Correct tool removed");
    523    // checking tab on the toolbox
    524    ok(
    525      !doc.getElementById("toolbox-tab-" + toolId),
    526      "Tab removed for " + toolId
    527    );
    528  } else {
    529    ok(false, "Something went wrong, " + toolId + " was not unregistered");
    530  }
    531  resolve();
    532 }
    533 
    534 async function checkRegistered(toolId, resolve, data) {
    535  if (data == toolId) {
    536    ok(true, "Correct tool added back");
    537    // checking tab on the toolbox
    538    const button = await lookupButtonForToolId(toolId);
    539    ok(button, "Tab added back for " + toolId);
    540  } else {
    541    ok(false, "Something went wrong, " + toolId + " was not registered");
    542  }
    543  resolve();
    544 }
    545 
    546 function GetPref(name) {
    547  const type = Services.prefs.getPrefType(name);
    548  switch (type) {
    549    case Services.prefs.PREF_STRING:
    550      return Services.prefs.getCharPref(name);
    551    case Services.prefs.PREF_INT:
    552      return Services.prefs.getIntPref(name);
    553    case Services.prefs.PREF_BOOL:
    554      return Services.prefs.getBoolPref(name);
    555    default:
    556      throw new Error("Unknown type");
    557  }
    558 }
    559 
    560 /**
    561 * Find the button from specified toolId.
    562 * Generally, button which access to the tool panel is in toolbox or
    563 * tools menu(in the Chevron menu).
    564 */
    565 async function lookupButtonForToolId(toolId) {
    566  let button = doc.getElementById("toolbox-tab-" + toolId);
    567  if (!button) {
    568    // search from the tools menu.
    569    await openChevronMenu(toolbox);
    570    button = doc.querySelector("#tools-chevron-menupopup-" + toolId);
    571 
    572    await closeChevronMenu(toolbox);
    573  }
    574  return button;
    575 }
    576 
    577 async function cleanup() {
    578  gDevTools.unregisterTool("testTool");
    579  await toolbox.destroy();
    580  gBrowser.removeCurrentTab();
    581  for (const pref of modifiedPrefs) {
    582    Services.prefs.clearUserPref(pref);
    583  }
    584  toolbox = doc = panelWin = modifiedPrefs = null;
    585 }