tor-browser

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

browser_toolbox_zoom_popup.js (7344B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 // Test the popup menu position when zooming in the devtools panel.
      7 
      8 const { Toolbox } = require("resource://devtools/client/framework/toolbox.js");
      9 
     10 // Use a simple URL in order to prevent displacing the left position of the
     11 // frames menu.
     12 const TEST_URL = "data:text/html;charset=utf-8,<iframe/>";
     13 
     14 add_task(async function () {
     15  const zoom = 1.4;
     16  await pushPref("devtools.toolbox.zoomValue", zoom.toString(10));
     17 
     18  info("Load iframe page for checking the frame menu with x1.4 zoom.");
     19  await addTab(TEST_URL);
     20  const tab = gBrowser.selectedTab;
     21  const toolbox = await gDevTools.showToolboxForTab(tab, {
     22    toolId: "inspector",
     23    hostType: Toolbox.HostType.WINDOW,
     24  });
     25  const inspector = toolbox.getCurrentPanel();
     26  const hostWindow = toolbox.win.parent;
     27  const originWidth = hostWindow.outerWidth;
     28  const originHeight = hostWindow.outerHeight;
     29 
     30  info(`Waiting for the toolbox window will to be rendered with zoom x${zoom}`);
     31  await waitUntil(() => {
     32    return parseFloat(toolbox.win.browsingContext.fullZoom.toFixed(1)) === zoom;
     33  });
     34 
     35  info(
     36    "Resizing and moving the toolbox window in order to display the chevron menu."
     37  );
     38  // If the window is displayed bottom of screen, the menu might be displayed
     39  // above the button so move it to the top of the screen first.
     40  await moveWindowTo(hostWindow, 10, 10);
     41 
     42  // Shrink the width of the window such that the inspector's tab menu button
     43  // and chevron button are visible.
     44  const prevTabs = toolbox.doc.querySelectorAll(".devtools-tab").length;
     45  info("Shrinking window");
     46 
     47  hostWindow.resizeTo(400, hostWindow.outerHeight);
     48  await waitUntil(() => {
     49    info(`Waiting for chevron(${hostWindow.outerWidth})`);
     50    return (
     51      hostWindow.outerWidth === 400 &&
     52      toolbox.doc.getElementById("tools-chevron-menu-button") &&
     53      inspector.panelDoc.querySelector(".all-tabs-menu") &&
     54      prevTabs != toolbox.doc.querySelectorAll(".devtools-tab").length
     55    );
     56  });
     57 
     58  const menuList = [
     59    toolbox.win.document.getElementById("toolbox-meatball-menu-button"),
     60    toolbox.win.document.getElementById("command-button-frames"),
     61    toolbox.win.document.getElementById("tools-chevron-menu-button"),
     62    inspector.panelDoc.querySelector(".all-tabs-menu"),
     63  ];
     64 
     65  for (const menu of menuList) {
     66    const { buttonBounds, menuType, menuBounds, arrowBounds } =
     67      await getButtonAndMenuInfo(toolbox, menu);
     68 
     69    switch (menuType) {
     70      case "native":
     71        {
     72          // Allow rounded error and platform offset value.
     73          // horizontal : IntID::ContextMenuOffsetHorizontal of GTK and Windows
     74          //              uses 2.
     75          // vertical: IntID::ContextMenuOffsetVertical of macOS uses -6.
     76          const xDelta = Math.abs(menuBounds.left - buttonBounds.left);
     77          const yDelta = Math.abs(menuBounds.top - buttonBounds.bottom);
     78          Assert.less(
     79            xDelta,
     80            2,
     81            "xDelta is lower than 2: " + xDelta + ". #" + menu.id
     82          );
     83          Assert.less(
     84            yDelta,
     85            6,
     86            "yDelta is lower than 6: " + yDelta + ". #" + menu.id
     87          );
     88        }
     89        break;
     90 
     91      case "doorhanger":
     92        {
     93          // Calculate the center of the button and center of the arrow and
     94          // check they align.
     95          const buttonCenter = buttonBounds.left + buttonBounds.width / 2;
     96          const arrowCenter = arrowBounds.left + arrowBounds.width / 2;
     97          const delta = Math.abs(arrowCenter - buttonCenter);
     98          Assert.lessOrEqual(
     99            Math.round(delta),
    100            1,
    101            "Center of arrow is within 1px of button center" +
    102              ` (delta: ${delta})`
    103          );
    104        }
    105        break;
    106    }
    107  }
    108 
    109  const onResize = once(hostWindow, "resize");
    110  hostWindow.resizeTo(originWidth, originHeight);
    111  await onResize;
    112 
    113  await toolbox.destroy();
    114  gBrowser.removeCurrentTab();
    115 });
    116 
    117 function convertScreenToDoc(popup, doc) {
    118  const rect = popup.getOuterScreenRect();
    119  const screenX = doc.defaultView.mozInnerScreenX;
    120  const screenY = doc.defaultView.mozInnerScreenY;
    121  const scale =
    122    popup.ownerGlobal.devicePixelRatio / doc.ownerGlobal.devicePixelRatio;
    123  return new DOMRect(
    124    rect.x * scale - screenX,
    125    rect.y * scale - screenY,
    126    rect.width * scale,
    127    rect.height * scale
    128  );
    129 }
    130 
    131 /**
    132 * Get the bounds of a menu button and its popup panel. The popup panel is
    133 * measured by clicking the menu button and looking for its panel (and then
    134 * hiding it again).
    135 *
    136 * @param {object} doc
    137 *        The toolbox document to query.
    138 * @param {object} menuButton
    139 *        The button whose size and popup size we should measure.
    140 * @return {object}
    141 *         An object with the following properties:
    142 *         - buttonBounds {DOMRect} Bounds of the button.
    143 *         - menuType {string} Type of the menu, "native" or "doorhanger".
    144 *         - menuBounds {DOMRect} Bounds of the menu panel.
    145 *         - arrowBounds {DOMRect|null} Bounds of the arrow. Only set when
    146 *                       menuType is "doorhanger", null otherwise.
    147 */
    148 async function getButtonAndMenuInfo(toolbox, menuButton) {
    149  const { doc, topDoc } = toolbox;
    150  info("Show popup menu with click event.");
    151  AccessibilityUtils.setEnv({
    152    // Keyboard accessibility is handled on the toolbox toolbar container level.
    153    // Users can use arrow keys to navigate between and select tabs.
    154    nonNegativeTabIndexRule: false,
    155  });
    156  EventUtils.sendMouseEvent(
    157    {
    158      type: "click",
    159      screenX: 1,
    160    },
    161    menuButton,
    162    doc.defaultView
    163  );
    164  AccessibilityUtils.resetEnv();
    165 
    166  let menuPopup;
    167  let menuType;
    168  let menuBounds = null;
    169  let arrowBounds = null;
    170  if (menuButton.hasAttribute("aria-controls")) {
    171    menuType = "doorhanger";
    172    menuPopup = doc.getElementById(menuButton.getAttribute("aria-controls"));
    173    await waitUntil(() => menuPopup.classList.contains("tooltip-visible"));
    174    // menuPopup can be a non-menupopup element, e.g. div. Call getBoxQuads to
    175    // get its bounds.
    176    menuBounds = menuPopup.getBoxQuads({ relativeTo: doc })[0].getBounds();
    177  } else {
    178    menuType = "native";
    179    await waitUntil(() => {
    180      const popupset = topDoc.querySelector("popupset");
    181      menuPopup = popupset?.querySelector('menupopup[menu-api="true"]');
    182      return menuPopup?.state === "open";
    183    });
    184    // menuPopup is a XUL menupopup element. Call getOuterScreenRect(), which is
    185    // suported on both native and non-native menupopup implementations.
    186    menuBounds = convertScreenToDoc(menuPopup, doc);
    187  }
    188  ok(menuPopup, "Menu popup is displayed.");
    189 
    190  const buttonBounds = menuButton
    191    .getBoxQuads({ relativeTo: doc })[0]
    192    .getBounds();
    193 
    194  if (menuType === "doorhanger") {
    195    const arrow = menuPopup.querySelector(".tooltip-arrow");
    196    arrowBounds = arrow.getBoxQuads({ relativeTo: doc })[0].getBounds();
    197  }
    198 
    199  info("Hide popup menu.");
    200  if (menuType === "doorhanger") {
    201    EventUtils.sendKey("Escape", doc.defaultView);
    202    await waitUntil(() => !menuPopup.classList.contains("tooltip-visible"));
    203  } else {
    204    const popupHidden = once(menuPopup, "popuphidden");
    205    menuPopup.hidePopup();
    206    await popupHidden;
    207  }
    208 
    209  return { buttonBounds, menuType, menuBounds, arrowBounds };
    210 }