tor-browser

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

head.js (14020B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 // shared-head.js handles imports, constants, and utility functions
      5 Services.scriptloader.loadSubScript(
      6  "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
      7  this
      8 );
      9 Services.scriptloader.loadSubScript(
     10  "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js",
     11  this
     12 );
     13 
     14 const EventEmitter = require("resource://devtools/shared/event-emitter.js");
     15 
     16 /**
     17 * Retrieve all tool ids compatible with a target created for the provided tab.
     18 *
     19 * @param {XULTab} tab
     20 *        The tab for which we want to get the list of supported toolIds
     21 * @return {Array<string>} array of tool ids
     22 */
     23 async function getSupportedToolIds(tab) {
     24  info("Getting the entire list of tools supported in this tab");
     25 
     26  let shouldDestroyToolbox = false;
     27 
     28  // Get the toolbox for this tab, or create one if needed.
     29  let toolbox = gDevTools.getToolboxForTab(tab);
     30  if (!toolbox) {
     31    toolbox = await gDevTools.showToolboxForTab(tab);
     32    shouldDestroyToolbox = true;
     33  }
     34 
     35  const toolIds = gDevTools
     36    .getToolDefinitionArray()
     37    .filter(def => def.isToolSupported(toolbox))
     38    .map(def => def.id);
     39 
     40  if (shouldDestroyToolbox) {
     41    // Only close the toolbox if it was explicitly created here.
     42    await toolbox.destroy();
     43  }
     44 
     45  return toolIds;
     46 }
     47 
     48 function toggleAllTools(state) {
     49  for (const [, tool] of gDevTools._tools) {
     50    if (!tool.visibilityswitch) {
     51      continue;
     52    }
     53    if (state) {
     54      Services.prefs.setBoolPref(tool.visibilityswitch, true);
     55    } else {
     56      Services.prefs.clearUserPref(tool.visibilityswitch);
     57    }
     58  }
     59 }
     60 
     61 async function getParentProcessActors(callback) {
     62  const commands = await CommandsFactory.forMainProcess();
     63  const mainProcessTargetFront = await commands.descriptorFront.getTarget();
     64 
     65  callback(commands.client, mainProcessTargetFront);
     66 }
     67 
     68 function getSourceActor(sources, url) {
     69  const item = sources.getItemForAttachment(a => a.source.url === url);
     70  return item && item.value;
     71 }
     72 
     73 /**
     74 * Synthesize a keypress from a <key> element, taking into account
     75 * any modifiers.
     76 *
     77 * @param {Element} el the <key> element to synthesize
     78 */
     79 function synthesizeKeyElement(el) {
     80  const key = el.getAttribute("key") || el.getAttribute("keycode");
     81  const mod = {};
     82  el.getAttribute("modifiers")
     83    .split(" ")
     84    .forEach(m => (mod[m + "Key"] = true));
     85  info(`Synthesizing: key=${key}, mod=${JSON.stringify(mod)}`);
     86  EventUtils.synthesizeKey(key, mod, el.ownerDocument.defaultView);
     87 }
     88 
     89 /**
     90 * Check the toolbox host type and prefs to make sure they match the
     91 * expected values
     92 *
     93 * @param {Toolbox}
     94 * @param {HostType} hostType
     95 *        One of {SIDE, BOTTOM, WINDOW} from Toolbox.HostType
     96 * @param {HostType} Optional previousHostType
     97 *        The host that will be switched to when calling switchToPreviousHost
     98 */
     99 function checkHostType(toolbox, hostType, previousHostType) {
    100  is(toolbox.hostType, hostType, "host type is " + hostType);
    101 
    102  const pref = Services.prefs.getCharPref("devtools.toolbox.host");
    103  is(pref, hostType, "host pref is " + hostType);
    104 
    105  if (previousHostType) {
    106    is(
    107      Services.prefs.getCharPref("devtools.toolbox.previousHost"),
    108      previousHostType,
    109      "The previous host is correct"
    110    );
    111  }
    112 }
    113 
    114 /**
    115 * Create a new <script> referencing URL.  Return a promise that
    116 * resolves when this has happened
    117 *
    118 * @param {string} url
    119 *        the url
    120 * @return {Promise} a promise that resolves when the element has been created
    121 */
    122 function createScript(url) {
    123  info(`Creating script: ${url}`);
    124  // This is not ideal if called multiple times, as it loads the frame script
    125  // separately each time.  See bug 1443680.
    126  return SpecialPowers.spawn(gBrowser.selectedBrowser, [url], urlChild => {
    127    const script = content.document.createElement("script");
    128    script.setAttribute("src", urlChild);
    129    content.document.body.appendChild(script);
    130  });
    131 }
    132 
    133 /**
    134 * Wait for the toolbox to notice that a given source is loaded
    135 *
    136 * @param {Toolbox} toolbox
    137 * @param {string} url
    138 *        the url to wait for
    139 * @return {Promise} a promise that is resolved when the source is loaded
    140 */
    141 function waitForSourceLoad(toolbox, url) {
    142  info(`Waiting for source ${url} to be available...`);
    143  return new Promise(resolve => {
    144    const { resourceCommand } = toolbox.commands;
    145 
    146    function onAvailable(sources) {
    147      for (const source of sources) {
    148        if (source.url === url) {
    149          resourceCommand.unwatchResources([resourceCommand.TYPES.SOURCE], {
    150            onAvailable,
    151          });
    152          resolve();
    153        }
    154      }
    155    }
    156    resourceCommand.watchResources([resourceCommand.TYPES.SOURCE], {
    157      onAvailable,
    158      // Ignore the cached resources as we always listen *before*
    159      // the action creating a source.
    160      ignoreExistingResources: true,
    161    });
    162  });
    163 }
    164 
    165 /**
    166 * When a Toolbox is started it creates a DevToolPanel for each of the tools
    167 * by calling toolDefinition.build(). The returned object should
    168 * at least implement these functions. They will be used by the ToolBox.
    169 *
    170 * There may be no benefit in doing this as an abstract type, but if nothing
    171 * else gives us a place to write documentation.
    172 */
    173 class DevToolPanel extends EventEmitter {
    174  constructor(iframeWindow, toolbox) {
    175    super();
    176 
    177    this._toolbox = toolbox;
    178    this._window = iframeWindow;
    179  }
    180  open() {
    181    return new Promise(resolve => {
    182      executeSoon(() => {
    183        resolve(this);
    184      });
    185    });
    186  }
    187 
    188  get document() {
    189    return this._window.document;
    190  }
    191 
    192  get target() {
    193    return this._toolbox.target;
    194  }
    195 
    196  get toolbox() {
    197    return this._toolbox;
    198  }
    199 
    200  destroy() {
    201    return Promise.resolve(null);
    202  }
    203 }
    204 
    205 /**
    206 * Create a simple devtools test panel that implements the minimum API needed to be
    207 * registered and opened in the toolbox.
    208 */
    209 function createTestPanel(iframeWindow, toolbox) {
    210  return new DevToolPanel(iframeWindow, toolbox);
    211 }
    212 
    213 async function openChevronMenu(toolbox) {
    214  const chevronMenuButton = toolbox.doc.querySelector(".tools-chevron-menu");
    215  EventUtils.synthesizeMouseAtCenter(chevronMenuButton, {}, toolbox.win);
    216 
    217  const menuPopup = toolbox.doc.getElementById(
    218    "tools-chevron-menu-button-panel"
    219  );
    220  ok(menuPopup, "tools-chevron-menupopup is available");
    221 
    222  info("Waiting for the menu popup to be displayed");
    223  await waitUntil(() => menuPopup.classList.contains("tooltip-visible"));
    224 }
    225 
    226 async function closeChevronMenu(toolbox) {
    227  // In order to close the popup menu with escape key, set the focus to the chevron
    228  // button at first.
    229  const chevronMenuButton = toolbox.doc.querySelector(".tools-chevron-menu");
    230  chevronMenuButton.focus();
    231 
    232  EventUtils.sendKey("ESCAPE", toolbox.doc.defaultView);
    233  const menuPopup = toolbox.doc.getElementById(
    234    "tools-chevron-menu-button-panel"
    235  );
    236 
    237  info("Closing the chevron popup menu");
    238  await waitUntil(() => !menuPopup.classList.contains("tooltip-visible"));
    239 }
    240 
    241 function prepareToolTabReorderTest(toolbox, startingOrder) {
    242  Services.prefs.setCharPref(
    243    "devtools.toolbox.tabsOrder",
    244    startingOrder.join(",")
    245  );
    246  ok(
    247    !toolbox.doc.getElementById("tools-chevron-menu-button"),
    248    "The size of the screen being too small"
    249  );
    250 
    251  for (const id of startingOrder) {
    252    ok(getElementByToolId(toolbox, id), `Tab element should exist for ${id}`);
    253  }
    254 }
    255 
    256 async function dndToolTab(toolbox, dragTarget, dropTarget, passedTargets = []) {
    257  info(`Drag ${dragTarget} to ${dropTarget}`);
    258  const dragTargetEl = getElementByToolIdOrExtensionIdOrSelector(
    259    toolbox,
    260    dragTarget
    261  );
    262 
    263  const onReady = dragTargetEl.classList.contains("selected")
    264    ? Promise.resolve()
    265    : toolbox.once("select");
    266  EventUtils.synthesizeMouseAtCenter(
    267    dragTargetEl,
    268    { type: "mousedown" },
    269    dragTargetEl.ownerGlobal
    270  );
    271  await onReady;
    272 
    273  for (const passedTarget of passedTargets) {
    274    info(`Via ${passedTarget}`);
    275    const passedTargetEl = getElementByToolIdOrExtensionIdOrSelector(
    276      toolbox,
    277      passedTarget
    278    );
    279    EventUtils.synthesizeMouseAtCenter(
    280      passedTargetEl,
    281      { type: "mousemove" },
    282      passedTargetEl.ownerGlobal
    283    );
    284  }
    285 
    286  if (dropTarget) {
    287    const dropTargetEl = getElementByToolIdOrExtensionIdOrSelector(
    288      toolbox,
    289      dropTarget
    290    );
    291    EventUtils.synthesizeMouseAtCenter(
    292      dropTargetEl,
    293      { type: "mousemove" },
    294      dropTargetEl.ownerGlobal
    295    );
    296    EventUtils.synthesizeMouseAtCenter(
    297      dropTargetEl,
    298      { type: "mouseup" },
    299      dropTargetEl.ownerGlobal
    300    );
    301  } else {
    302    const containerEl = toolbox.doc.getElementById("toolbox-container");
    303    EventUtils.synthesizeMouse(
    304      containerEl,
    305      0,
    306      0,
    307      { type: "mouseout" },
    308      containerEl.ownerGlobal
    309    );
    310  }
    311 
    312  // Wait for updating the preference.
    313  await new Promise(resolve => {
    314    const onUpdated = () => {
    315      Services.prefs.removeObserver("devtools.toolbox.tabsOrder", onUpdated);
    316      resolve();
    317    };
    318 
    319    Services.prefs.addObserver("devtools.toolbox.tabsOrder", onUpdated);
    320  });
    321 }
    322 
    323 function assertToolTabOrder(toolbox, expectedOrder) {
    324  info("Check the order of the tabs on the toolbar");
    325 
    326  const tabEls = toolbox.doc.querySelectorAll(".devtools-tab");
    327 
    328  for (let i = 0; i < expectedOrder.length; i++) {
    329    const isOrdered =
    330      tabEls[i].dataset.id === expectedOrder[i] ||
    331      tabEls[i].dataset.extensionId === expectedOrder[i];
    332    ok(isOrdered, `The tab[${expectedOrder[i]}] should exist at [${i}]`);
    333  }
    334 }
    335 
    336 function assertToolTabSelected(toolbox, dragTarget) {
    337  info("Check whether the drag target was selected");
    338  const dragTargetEl = getElementByToolIdOrExtensionIdOrSelector(
    339    toolbox,
    340    dragTarget
    341  );
    342  ok(
    343    dragTargetEl.classList.contains("selected"),
    344    "The dragged tool should be selected"
    345  );
    346 }
    347 
    348 function assertToolTabPreferenceOrder(expectedOrder) {
    349  info("Check the order in DevTools preference for tabs order");
    350  is(
    351    Services.prefs.getCharPref("devtools.toolbox.tabsOrder"),
    352    expectedOrder.join(","),
    353    "The preference should be correct"
    354  );
    355 }
    356 
    357 function getElementByToolId(toolbox, id) {
    358  for (const tabEl of toolbox.doc.querySelectorAll(".devtools-tab")) {
    359    if (tabEl.dataset.id === id || tabEl.dataset.extensionId === id) {
    360      return tabEl;
    361    }
    362  }
    363 
    364  return null;
    365 }
    366 
    367 function getElementByToolIdOrExtensionIdOrSelector(toolbox, idOrSelector) {
    368  const tabEl = getElementByToolId(toolbox, idOrSelector);
    369  return tabEl ? tabEl : toolbox.doc.querySelector(idOrSelector);
    370 }
    371 
    372 /**
    373 * Returns a toolbox tab element, even if it's overflowed
    374 */
    375 function getToolboxTab(doc, toolId) {
    376  return (
    377    doc.getElementById(`toolbox-tab-${toolId}`) ||
    378    doc.getElementById(`tools-chevron-menupopup-${toolId}`)
    379  );
    380 }
    381 
    382 function getWindow(toolbox) {
    383  return toolbox.topWindow;
    384 }
    385 
    386 async function resizeWindow(toolbox, width, height) {
    387  const hostWindow = toolbox.win.parent;
    388  const originalWidth = hostWindow.outerWidth;
    389  const originalHeight = hostWindow.outerHeight;
    390  const toWidth = width || originalWidth;
    391  const toHeight = height || originalHeight;
    392 
    393  const onResize = once(hostWindow, "resize");
    394  hostWindow.resizeTo(toWidth, toHeight);
    395  await onResize;
    396 }
    397 
    398 function assertSelectedLocationInDebugger(debuggerPanel, line, column) {
    399  const location = debuggerPanel._selectors.getSelectedLocation(
    400    debuggerPanel._getState()
    401  );
    402  is(location.line, line);
    403  is(location.column, column);
    404 }
    405 
    406 /**
    407 * Open a new tab on about:devtools-toolbox with the provided params object used as
    408 * queryString.
    409 */
    410 async function openAboutToolbox(params) {
    411  info("Open about:devtools-toolbox");
    412  const querystring = new URLSearchParams();
    413  Object.keys(params).forEach(x => querystring.append(x, params[x]));
    414 
    415  const tab = await addTab(`about:devtools-toolbox?${querystring}`);
    416  const browser = tab.linkedBrowser;
    417 
    418  return {
    419    tab,
    420    document: browser.contentDocument,
    421  };
    422 }
    423 
    424 /**
    425 * Load FTL.
    426 *
    427 * @param {Toolbox} toolbox
    428 *        Toolbox instance.
    429 * @param {string} path
    430 *        Path to the FTL file.
    431 */
    432 function loadFTL(toolbox, path) {
    433  const win = toolbox.doc.ownerGlobal;
    434 
    435  if (win.MozXULElement) {
    436    win.MozXULElement.insertFTLIfNeeded(path);
    437  }
    438 }
    439 
    440 /**
    441 * Emit a reload key shortcut from a given toolbox, and wait for the reload to
    442 * be completed.
    443 *
    444 * @param {string} shortcut
    445 *        The key shortcut to send, as expected by the devtools shortcuts
    446 *        helpers (eg. "CmdOrCtrl+F5").
    447 * @param {Toolbox} toolbox
    448 *        The toolbox through which the event should be emitted.
    449 */
    450 async function sendToolboxReloadShortcut(shortcut, toolbox) {
    451  const promises = [];
    452 
    453  // If we have a jsdebugger panel, wait for it to complete its reload.
    454  const jsdebugger = toolbox.getPanel("jsdebugger");
    455  if (jsdebugger) {
    456    promises.push(jsdebugger.once("reloaded"));
    457  }
    458 
    459  // If we have an inspector panel, wait for it to complete its reload.
    460  const inspector = toolbox.getPanel("inspector");
    461  if (inspector) {
    462    promises.push(
    463      inspector.once("reloaded"),
    464      inspector.once("inspector-updated")
    465    );
    466  }
    467 
    468  const loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
    469  promises.push(loadPromise);
    470 
    471  info("Focus the toolbox window and emit the reload shortcut: " + shortcut);
    472  toolbox.win.focus();
    473  synthesizeKeyShortcut(shortcut, toolbox.win);
    474 
    475  info("Wait for page and toolbox reload promises");
    476  await Promise.all(promises);
    477 }
    478 
    479 function getErrorIcon(toolbox) {
    480  return toolbox.doc.querySelector(".toolbox-error");
    481 }
    482 
    483 function getErrorIconCount(toolbox) {
    484  const textContent = getErrorIcon(toolbox)?.textContent;
    485  try {
    486    const int = parseInt(textContent, 10);
    487    // 99+ parses to 99, so we check if the parsedInt does not match the textContent.
    488    return int.toString() === textContent ? int : textContent;
    489  } catch (e) {
    490    // In case the parseInt threw, return the actual textContent so the test can display
    491    // an easy to debug failure.
    492    return textContent;
    493  }
    494 }