tor-browser

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

head.js (18320B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 // Load the shared-head file first.
      7 Services.scriptloader.loadSubScript(
      8  "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
      9  this
     10 );
     11 
     12 /* import-globals-from helper-mocks.js */
     13 Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "helper-mocks.js", this);
     14 
     15 Services.scriptloader.loadSubScript(
     16  "chrome://mochitests/content/browser/devtools/client/webconsole/test/browser/shared-head.js",
     17  this
     18 );
     19 
     20 // Make sure the ADB addon is removed and ADB is stopped when the test ends.
     21 registerCleanupFunction(async function () {
     22  try {
     23    const {
     24      adbAddon,
     25    } = require("resource://devtools/client/shared/remote-debugging/adb/adb-addon.js");
     26    await adbAddon.uninstall();
     27  } catch (e) {
     28    // Will throw if the addon is already uninstalled, ignore exceptions here.
     29  }
     30  const {
     31    adbProcess,
     32  } = require("resource://devtools/client/shared/remote-debugging/adb/adb-process.js");
     33  await adbProcess.kill();
     34 
     35  const {
     36    remoteClientManager,
     37  } = require("resource://devtools/client/shared/remote-debugging/remote-client-manager.js");
     38  await remoteClientManager.removeAllClients();
     39 });
     40 
     41 async function openAboutDebugging({
     42  enableWorkerUpdates,
     43  enableLocalTabs = true,
     44 } = {}) {
     45  if (!enableWorkerUpdates) {
     46    silenceWorkerUpdates();
     47  }
     48 
     49  // This preference changes value depending on the build type, tests need to use a
     50  // consistent value regarless of the build used.
     51  await pushPref(
     52    "devtools.aboutdebugging.local-tab-debugging",
     53    enableLocalTabs
     54  );
     55 
     56  info("opening about:debugging");
     57 
     58  const tab = await addTab("about:debugging");
     59  const browser = tab.linkedBrowser;
     60  const document = browser.contentDocument;
     61  const window = browser.contentWindow;
     62 
     63  info("Wait until Connect page is displayed");
     64  await waitUntil(() => document.querySelector(".qa-connect-page"));
     65 
     66  return { tab, document, window };
     67 }
     68 
     69 async function openAboutDevtoolsToolbox(
     70  doc,
     71  tab,
     72  win,
     73  targetText = "about:debugging",
     74  shouldWaitToolboxReady = true
     75 ) {
     76  info("Open about:devtools-toolbox page");
     77 
     78  info("Wait for the target to appear: " + targetText);
     79  await waitUntil(() => findDebugTargetByText(targetText, doc));
     80 
     81  const target = findDebugTargetByText(targetText, doc);
     82  ok(target, `${targetText} target appeared`);
     83 
     84  const {
     85    DEBUG_TARGETS,
     86  } = require("resource://devtools/client/aboutdebugging/src/constants.js");
     87  const isWebExtension = target.dataset.qaTargetType == DEBUG_TARGETS.EXTENSION;
     88 
     89  const inspectButton = target.querySelector(".qa-debug-target-inspect-button");
     90  ok(inspectButton, `Inspect button for ${targetText} appeared`);
     91  inspectButton.click();
     92  const onToolboxReady = gDevTools.once("toolbox-ready");
     93 
     94  info("Wait for about debugging requests to settle");
     95  await waitForAboutDebuggingRequests(win.AboutDebugging.store);
     96 
     97  if (shouldWaitToolboxReady) {
     98    info("Wait for onToolboxReady");
     99    await onToolboxReady;
    100  }
    101 
    102  const { runtimes } = win.AboutDebugging.store.getState();
    103  const isOnThisFirefox = runtimes.selectedRuntimeId === "this-firefox";
    104  const isLocalWebExtension = isWebExtension && isOnThisFirefox;
    105 
    106  // Local WebExtension toolboxes open in a dedicated window
    107  if (isLocalWebExtension) {
    108    const toolbox = await onToolboxReady;
    109    // For some reason the test helpers prevents the toolbox from being automatically focused on opening,
    110    // whereas it is IRL.
    111    const focusedWin = Services.focus.focusedWindow;
    112    if (focusedWin?.top != toolbox.win) {
    113      info("Wait for the toolbox window to be focused");
    114      await new Promise(r => {
    115        // focus event only fired on the chrome event handler and in capture phase
    116        toolbox.win.docShell.chromeEventHandler.addEventListener("focus", r, {
    117          once: true,
    118          capture: true,
    119        });
    120        toolbox.win.focus();
    121      });
    122      info("The toolbox is focused");
    123    }
    124    return {
    125      devtoolsBrowser: null,
    126      devtoolsDocument: toolbox.doc,
    127      devtoolsTab: null,
    128      devtoolsWindow: toolbox.win,
    129    };
    130  }
    131 
    132  info("Wait until a new tab is opened");
    133  await waitUntil(() => tab.nextElementSibling);
    134 
    135  info("Wait for about:devtools-toolbox tab will be selected");
    136  const devtoolsTab = tab.nextElementSibling;
    137  await waitUntil(() => gBrowser.selectedTab === devtoolsTab);
    138  const devtoolsBrowser = gBrowser.selectedBrowser;
    139  info("Wait for about:devtools-toolbox tab to have the expected URL");
    140  await waitUntil(() =>
    141    devtoolsBrowser.contentWindow.location.href.startsWith(
    142      "about:devtools-toolbox?"
    143    )
    144  );
    145 
    146  if (!shouldWaitToolboxReady) {
    147    // Wait for show error page.
    148    await waitUntil(() =>
    149      devtoolsBrowser.contentDocument.querySelector(".qa-error-page")
    150    );
    151  }
    152 
    153  return {
    154    devtoolsBrowser,
    155    devtoolsDocument: devtoolsBrowser.contentDocument,
    156    devtoolsTab,
    157    devtoolsWindow: devtoolsBrowser.contentWindow,
    158  };
    159 }
    160 
    161 async function closeAboutDevtoolsToolbox(
    162  aboutDebuggingDocument,
    163  devtoolsTab,
    164  win
    165 ) {
    166  // Wait for all requests to settle on the opened about:devtools toolbox.
    167  const devtoolsBrowser = devtoolsTab.linkedBrowser;
    168  const devtoolsWindow = devtoolsBrowser.contentWindow;
    169  const toolbox = getToolbox(devtoolsWindow);
    170 
    171  info("Wait for requests to settle");
    172  await toolbox.commands.client.waitForRequestsToSettle({
    173    ignoreOrphanedFronts: true,
    174  });
    175 
    176  info("Close about:devtools-toolbox page");
    177  const onToolboxDestroyed = gDevTools.once("toolbox-destroyed");
    178 
    179  info("Wait for removeTab");
    180  await removeTab(devtoolsTab);
    181 
    182  info("Wait for toolbox destroyed");
    183  await onToolboxDestroyed;
    184 
    185  // Changing the tab will also trigger a request to list tabs, so wait until the selected
    186  // tab has changed to wait for requests to settle.
    187  info("Wait until aboutdebugging is selected");
    188  await waitUntil(() => gBrowser.selectedTab !== devtoolsTab);
    189 
    190  // Wait for removing about:devtools-toolbox tab info from about:debugging.
    191  info("Wait until about:devtools-toolbox is removed from debug targets");
    192  await waitUntil(
    193    () => !findDebugTargetByText("Toolbox - ", aboutDebuggingDocument)
    194  );
    195 
    196  await waitForAboutDebuggingRequests(win.AboutDebugging.store);
    197 }
    198 
    199 async function closeWebExtAboutDevtoolsToolbox(devtoolsWindow, win) {
    200  // Wait for all requests to settle on the opened about:devtools toolbox.
    201  const toolbox = getToolbox(devtoolsWindow);
    202  await toolbox.commands.client.waitForRequestsToSettle();
    203 
    204  info("Close the toolbox and wait for its destruction");
    205  await toolbox.destroy();
    206 
    207  await waitForAboutDebuggingRequests(win.AboutDebugging.store);
    208 }
    209 
    210 async function reloadAboutDebugging(tab) {
    211  info("reload about:debugging");
    212 
    213  await reloadBrowser(tab.linkedBrowser);
    214  const browser = tab.linkedBrowser;
    215  const document = browser.contentDocument;
    216  const window = browser.contentWindow;
    217  info("wait for the initial about:debugging requests to settle");
    218  await waitForAboutDebuggingRequests(window.AboutDebugging.store);
    219 
    220  return document;
    221 }
    222 
    223 // Wait for all about:debugging target request actions to succeed.
    224 // They will typically be triggered after watching a new runtime or loading
    225 // about:debugging.
    226 function waitForRequestsSuccess(store) {
    227  return Promise.all([
    228    waitForDispatch(store, "REQUEST_EXTENSIONS_SUCCESS"),
    229    waitForDispatch(store, "REQUEST_TABS_SUCCESS"),
    230    waitForDispatch(store, "REQUEST_WORKERS_SUCCESS"),
    231  ]);
    232 }
    233 
    234 /**
    235 * Wait for all aboutdebugging REQUEST_*_SUCCESS actions to settle, meaning here
    236 * that no new request has been dispatched after the provided delay.
    237 */
    238 async function waitForAboutDebuggingRequests(store, delay = 500) {
    239  let hasSettled = false;
    240 
    241  // After each iteration of this while loop, we check is the timerPromise had the time
    242  // to resolve or if we captured a REQUEST_*_SUCCESS action before.
    243  while (!hasSettled) {
    244    let timer;
    245 
    246    // This timer will be executed only if no REQUEST_*_SUCCESS action is dispatched
    247    // during the delay. We consider that when no request are received for some time, it
    248    // means there are no ongoing requests anymore.
    249    const timerPromise = new Promise(resolve => {
    250      timer = setTimeout(() => {
    251        hasSettled = true;
    252        resolve();
    253      }, delay);
    254    });
    255 
    256    // Wait either for a REQUEST_*_SUCCESS to be dispatched, or for the timer to resolve.
    257    await Promise.race([
    258      waitForDispatch(store, "REQUEST_EXTENSIONS_SUCCESS"),
    259      waitForDispatch(store, "REQUEST_TABS_SUCCESS"),
    260      waitForDispatch(store, "REQUEST_WORKERS_SUCCESS"),
    261      timerPromise,
    262    ]);
    263 
    264    // Clear the timer to avoid setting hasSettled to true accidently unless timerPromise
    265    // was the first to resolve.
    266    clearTimeout(timer);
    267  }
    268 }
    269 
    270 /**
    271 * Navigate to "This Firefox"
    272 */
    273 async function selectThisFirefoxPage(doc, store) {
    274  info("Select This Firefox page");
    275 
    276  const onRequestSuccess = waitForRequestsSuccess(store);
    277  doc.location.hash = "#/runtime/this-firefox";
    278  info("Wait for requests to be complete");
    279  await onRequestSuccess;
    280 
    281  info("Wait for runtime page to be rendered");
    282  await waitUntil(() => doc.querySelector(".qa-runtime-page"));
    283 
    284  // Navigating to this-firefox will trigger a title change for the
    285  // about:debugging tab. This title change _might_ trigger a tablist update.
    286  // If it does, we should make sure to wait for pending tab requests.
    287  await waitForAboutDebuggingRequests(store);
    288 }
    289 
    290 /**
    291 * Navigate to the Connect page. Resolves when the Connect page is rendered.
    292 */
    293 async function selectConnectPage(doc) {
    294  const sidebarItems = doc.querySelectorAll(".qa-sidebar-item");
    295  const connectSidebarItem = [...sidebarItems].find(element => {
    296    return element.textContent === "Setup";
    297  });
    298  ok(connectSidebarItem, "Sidebar contains a Connect item");
    299  const connectLink = connectSidebarItem.querySelector(".qa-sidebar-link");
    300  ok(connectLink, "Sidebar contains a Connect link");
    301 
    302  info("Click on the Connect link in the sidebar");
    303  connectLink.click();
    304 
    305  info("Wait until Connect page is displayed");
    306  await waitUntil(() => doc.querySelector(".qa-connect-page"));
    307 }
    308 
    309 function getDebugTargetPane(title, document) {
    310  // removes the suffix "(<NUMBER>)" in debug target pane's title, if needed
    311  const sanitizeTitle = x => {
    312    return x.replace(/\s+\(\d+\)$/, "");
    313  };
    314 
    315  const targetTitle = sanitizeTitle(title);
    316  for (const titleEl of document.querySelectorAll(
    317    ".qa-debug-target-pane-title"
    318  )) {
    319    if (sanitizeTitle(titleEl.textContent) !== targetTitle) {
    320      continue;
    321    }
    322 
    323    return titleEl.closest(".qa-debug-target-pane");
    324  }
    325 
    326  return null;
    327 }
    328 
    329 function findDebugTargetByText(text, document) {
    330  const targets = [...document.querySelectorAll(".qa-debug-target-item")];
    331  return targets.find(target => target.textContent.includes(text));
    332 }
    333 
    334 function findSidebarItemByText(text, document) {
    335  const sidebarItems = document.querySelectorAll(".qa-sidebar-item");
    336  return [...sidebarItems].find(element => {
    337    return element.textContent.includes(text);
    338  });
    339 }
    340 
    341 function findSidebarItemLinkByText(text, document) {
    342  const links = document.querySelectorAll(".qa-sidebar-link");
    343  return [...links].find(element => {
    344    return element.textContent.includes(text);
    345  });
    346 }
    347 
    348 async function connectToRuntime(deviceName, document) {
    349  info(`Wait until the sidebar item for ${deviceName} appears`);
    350  await waitUntil(() => findSidebarItemByText(deviceName, document));
    351  const sidebarItem = findSidebarItemByText(deviceName, document);
    352  const connectButton = sidebarItem.querySelector(".qa-connect-button");
    353  ok(
    354    connectButton,
    355    `Connect button is displayed for the runtime ${deviceName}`
    356  );
    357 
    358  info("Click on the connect button and wait until it disappears");
    359  connectButton.click();
    360  await waitUntil(() => !sidebarItem.querySelector(".qa-connect-button"));
    361 }
    362 
    363 async function waitForRuntimePage(name, document) {
    364  await waitUntil(() => {
    365    const runtimeInfo = document.querySelector(".qa-runtime-name");
    366    return runtimeInfo && runtimeInfo.textContent.includes(name);
    367  });
    368 }
    369 
    370 async function selectRuntime(deviceName, name, document) {
    371  const sidebarItem = findSidebarItemByText(deviceName, document);
    372  const store = document.defaultView.AboutDebugging.store;
    373  const onSelectPageSuccess = waitForDispatch(store, "SELECT_PAGE_SUCCESS");
    374 
    375  sidebarItem.querySelector(".qa-sidebar-link").click();
    376 
    377  await waitForRuntimePage(name, document);
    378 
    379  info("Wait for SELECT_PAGE_SUCCESS to be dispatched");
    380  await onSelectPageSuccess;
    381 }
    382 
    383 function getToolbox(win) {
    384  return gDevTools.getToolboxes().find(toolbox => toolbox.win === win);
    385 }
    386 
    387 /**
    388 * Open the performance profiler dialog. Assumes the client is a mocked remote runtime
    389 * client.
    390 */
    391 async function openProfilerDialog(client, doc) {
    392  const onProfilerLoaded = new Promise(r => {
    393    client.loadPerformanceProfiler = r;
    394  });
    395 
    396  info("Click on the Profile Runtime button");
    397  const profileButton = doc.querySelector(".qa-profile-runtime-button");
    398  profileButton.click();
    399 
    400  info(
    401    "Wait for the loadPerformanceProfiler callback to be executed on client-wrapper"
    402  );
    403  return onProfilerLoaded;
    404 }
    405 
    406 /**
    407 * The "This Firefox" string depends on the brandShortName, which will be different
    408 * depending on the channel where tests are running.
    409 */
    410 function getThisFirefoxString(aboutDebuggingWindow) {
    411  const loader = aboutDebuggingWindow.getBrowserLoaderForWindow();
    412  const { l10n } = loader.require(
    413    "resource://devtools/client/aboutdebugging/src/modules/l10n.js"
    414  );
    415  return l10n.getString("about-debugging-this-firefox-runtime-name");
    416 }
    417 
    418 function waitUntilUsbDeviceIsUnplugged(deviceName, aboutDebuggingDocument) {
    419  info("Wait until the USB sidebar item appears as unplugged");
    420  return waitUntil(() => {
    421    const sidebarItem = findSidebarItemByText(
    422      deviceName,
    423      aboutDebuggingDocument
    424    );
    425    return !!sidebarItem.querySelector(".qa-runtime-item-unplugged");
    426  });
    427 }
    428 
    429 /**
    430 * Changing the selected tab in the current browser will trigger a tablist
    431 * update.
    432 * If the currently selected page is "this-firefox", we should wait for the
    433 * the corresponding REQUEST_TABS_SUCCESS that will be triggered by the change.
    434 *
    435 * @param {Browser} browser
    436 *        The browser instance to update.
    437 * @param {XULTab} tab
    438 *        The tab to select.
    439 * @param {object} store
    440 *        The about:debugging redux store.
    441 */
    442 async function updateSelectedTab(browser, tab, store) {
    443  info("Update the selected tab");
    444 
    445  const { runtimes, ui } = store.getState();
    446  const isOnThisFirefox =
    447    runtimes.selectedRuntimeId === "this-firefox" &&
    448    ui.selectedPage === "runtime";
    449 
    450  // A tabs request will only be issued if we are on this-firefox.
    451  const onTabsSuccess = isOnThisFirefox
    452    ? waitForDispatch(store, "REQUEST_TABS_SUCCESS")
    453    : null;
    454 
    455  // Update the selected tab.
    456  browser.selectedTab = tab;
    457 
    458  if (onTabsSuccess) {
    459    info("Wait for the tablist update after updating the selected tab");
    460    await onTabsSuccess;
    461  }
    462 }
    463 
    464 /**
    465 * Synthesizes key input inside the DebugTargetInfo's URL component.
    466 *
    467 * @param {DevToolsToolbox} toolbox
    468 *        The DevToolsToolbox debugging the target.
    469 * @param {HTMLElement} inputEl
    470 *        The <input> element to submit the URL with.
    471 * @param {string}  url
    472 *        The URL to navigate to.
    473 */
    474 async function synthesizeUrlKeyInput(toolbox, inputEl, url) {
    475  const { devtoolsDocument, devtoolsWindow } = toolbox;
    476  info("Wait for URL input to be focused.");
    477  const onInputFocused = waitUntil(
    478    () => devtoolsDocument.activeElement === inputEl
    479  );
    480  inputEl.focus();
    481  await onInputFocused;
    482 
    483  info("Synthesize entering URL into text field");
    484  const onInputChange = waitUntil(() => inputEl.value === url);
    485  for (const key of url.split("")) {
    486    EventUtils.synthesizeKey(key, {}, devtoolsWindow);
    487  }
    488  await onInputChange;
    489 
    490  info("Submit URL to navigate to");
    491  EventUtils.synthesizeKey("KEY_Enter");
    492 }
    493 
    494 /**
    495 * Click on a given add-on widget button so that its browser actor is fired.
    496 * Typically a popup would open, or a listener would be called in the background page.
    497 *
    498 * @param {string} addonId
    499 *        The ID of the add-on to click on.
    500 */
    501 function clickOnAddonWidget(addonId) {
    502  // Devtools are in another window and may have the focus.
    503  // Ensure focusing the browser window when clicking on the widget.
    504  const focusedWin = Services.focus.focusedWindow;
    505  if (focusedWin != window) {
    506    window.focus();
    507  }
    508  // Find the browserAction button that will show the webextension popup.
    509  const widgetId = addonId.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
    510  const browserActionId = widgetId + "-browser-action";
    511  const browserActionEl = window.document.getElementById(browserActionId);
    512  ok(browserActionEl, "Got the browserAction button from the browser UI");
    513 
    514  info("Show the web extension popup");
    515  browserActionEl
    516    .querySelector(".unified-extensions-item-action-button")
    517    .click();
    518 }
    519 
    520 // Create basic addon data as the DevToolsClient would return it.
    521 function createAddonData({
    522  id,
    523  name,
    524  isSystem = false,
    525  hidden = false,
    526  temporary = false,
    527 }) {
    528  return {
    529    actor: `actorid-${id}`,
    530    hidden,
    531    iconURL: `moz-extension://${id}/icon-url.png`,
    532    id,
    533    manifestURL: `moz-extension://${id}/manifest-url.json`,
    534    name,
    535    isSystem,
    536    temporarilyInstalled: temporary,
    537    debuggable: true,
    538  };
    539 }
    540 
    541 async function connectToLocalFirefox({ runtimeId, runtimeName, deviceName }) {
    542  // This is a client to the current Firefox.
    543  const clientWrapper = await createLocalClientWrapper();
    544 
    545  // enable USB devices mocks
    546  const mocks = new Mocks();
    547  const usbClient = mocks.createUSBRuntime(runtimeId, {
    548    deviceName,
    549    name: runtimeName,
    550    clientWrapper,
    551  });
    552 
    553  // Wrap a disconnect helper for convenience for the caller.
    554  const disconnect = doc =>
    555    disconnectFromLocalFirefox({
    556      doc,
    557      runtimeId,
    558      deviceName,
    559      mocks,
    560    });
    561 
    562  return { disconnect, mocks, usbClient };
    563 }
    564 /* exported connectToLocalFirefox */
    565 
    566 async function disconnectFromLocalFirefox({
    567  doc,
    568  mocks,
    569  runtimeId,
    570  deviceName,
    571 }) {
    572  info("Remove USB runtime");
    573  mocks.removeUSBRuntime(runtimeId);
    574  mocks.emitUSBUpdate();
    575  await waitUntilUsbDeviceIsUnplugged(deviceName, doc);
    576 }
    577 /* exported disconnectFromLocalFirefox */