summaryrefslogtreecommitdiffstats
path: root/devtools/client/framework/test/browser_toolbox_zoom_popup.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/framework/test/browser_toolbox_zoom_popup.js')
-rw-r--r--devtools/client/framework/test/browser_toolbox_zoom_popup.js213
1 files changed, 213 insertions, 0 deletions
diff --git a/devtools/client/framework/test/browser_toolbox_zoom_popup.js b/devtools/client/framework/test/browser_toolbox_zoom_popup.js
new file mode 100644
index 0000000000..7b764bf703
--- /dev/null
+++ b/devtools/client/framework/test/browser_toolbox_zoom_popup.js
@@ -0,0 +1,213 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the popup menu position when zooming in the devtools panel.
+
+const { Toolbox } = require("resource://devtools/client/framework/toolbox.js");
+
+// Use a simple URL in order to prevent displacing the left position of the
+// frames menu.
+const TEST_URL = "data:text/html;charset=utf-8,<iframe/>";
+
+add_task(async function () {
+ registerCleanupFunction(async function () {
+ Services.prefs.clearUserPref("devtools.toolbox.zoomValue");
+ });
+ const zoom = 1.4;
+ Services.prefs.setCharPref("devtools.toolbox.zoomValue", zoom.toString(10));
+
+ info("Load iframe page for checking the frame menu with x1.4 zoom.");
+ await addTab(TEST_URL);
+ const tab = gBrowser.selectedTab;
+ const toolbox = await gDevTools.showToolboxForTab(tab, {
+ toolId: "inspector",
+ hostType: Toolbox.HostType.WINDOW,
+ });
+ const inspector = toolbox.getCurrentPanel();
+ const hostWindow = toolbox.win.parent;
+ const originWidth = hostWindow.outerWidth;
+ const originHeight = hostWindow.outerHeight;
+
+ info(`Waiting for the toolbox window will to be rendered with zoom x${zoom}`);
+ await waitUntil(() => {
+ return parseFloat(toolbox.win.browsingContext.fullZoom.toFixed(1)) === zoom;
+ });
+
+ info(
+ "Resizing and moving the toolbox window in order to display the chevron menu."
+ );
+ // If the window is displayed bottom of screen, the menu might be displayed
+ // above the button so move it to the top of the screen first.
+ await moveWindowTo(hostWindow, 10, 10);
+
+ // Shrink the width of the window such that the inspector's tab menu button
+ // and chevron button are visible.
+ const prevTabs = toolbox.doc.querySelectorAll(".devtools-tab").length;
+ info("Shrinking window");
+
+ hostWindow.resizeTo(400, hostWindow.outerHeight);
+ await waitUntil(() => {
+ info(`Waiting for chevron(${hostWindow.outerWidth})`);
+ return (
+ hostWindow.outerWidth === 400 &&
+ toolbox.doc.getElementById("tools-chevron-menu-button") &&
+ inspector.panelDoc.querySelector(".all-tabs-menu") &&
+ prevTabs != toolbox.doc.querySelectorAll(".devtools-tab").length
+ );
+ });
+
+ const menuList = [
+ toolbox.win.document.getElementById("toolbox-meatball-menu-button"),
+ toolbox.win.document.getElementById("command-button-frames"),
+ toolbox.win.document.getElementById("tools-chevron-menu-button"),
+ inspector.panelDoc.querySelector(".all-tabs-menu"),
+ ];
+
+ for (const menu of menuList) {
+ const { buttonBounds, menuType, menuBounds, arrowBounds } =
+ await getButtonAndMenuInfo(toolbox, menu);
+
+ switch (menuType) {
+ case "native":
+ {
+ // Allow rounded error and platform offset value.
+ // horizontal : IntID::ContextMenuOffsetHorizontal of GTK and Windows
+ // uses 2.
+ // vertical: IntID::ContextMenuOffsetVertical of macOS uses -6.
+ const xDelta = Math.abs(menuBounds.left - buttonBounds.left);
+ const yDelta = Math.abs(menuBounds.top - buttonBounds.bottom);
+ Assert.less(
+ xDelta,
+ 2,
+ "xDelta is lower than 2: " + xDelta + ". #" + menu.id
+ );
+ Assert.less(
+ yDelta,
+ 6,
+ "yDelta is lower than 6: " + yDelta + ". #" + menu.id
+ );
+ }
+ break;
+
+ case "doorhanger":
+ {
+ // Calculate the center of the button and center of the arrow and
+ // check they align.
+ const buttonCenter = buttonBounds.left + buttonBounds.width / 2;
+ const arrowCenter = arrowBounds.left + arrowBounds.width / 2;
+ const delta = Math.abs(arrowCenter - buttonCenter);
+ Assert.lessOrEqual(
+ Math.round(delta),
+ 1,
+ "Center of arrow is within 1px of button center" +
+ ` (delta: ${delta})`
+ );
+ }
+ break;
+ }
+ }
+
+ const onResize = once(hostWindow, "resize");
+ hostWindow.resizeTo(originWidth, originHeight);
+ await onResize;
+
+ await toolbox.destroy();
+ gBrowser.removeCurrentTab();
+});
+
+function convertScreenToDoc(popup, doc) {
+ const rect = popup.getOuterScreenRect();
+ const screenX = doc.defaultView.mozInnerScreenX;
+ const screenY = doc.defaultView.mozInnerScreenY;
+ const scale =
+ popup.ownerGlobal.devicePixelRatio / doc.ownerGlobal.devicePixelRatio;
+ return new DOMRect(
+ rect.x * scale - screenX,
+ rect.y * scale - screenY,
+ rect.width * scale,
+ rect.height * scale
+ );
+}
+
+/**
+ * Get the bounds of a menu button and its popup panel. The popup panel is
+ * measured by clicking the menu button and looking for its panel (and then
+ * hiding it again).
+ *
+ * @param {Object} doc
+ * The toolbox document to query.
+ * @param {Object} menuButton
+ * The button whose size and popup size we should measure.
+ * @return {Object}
+ * An object with the following properties:
+ * - buttonBounds {DOMRect} Bounds of the button.
+ * - menuType {string} Type of the menu, "native" or "doorhanger".
+ * - menuBounds {DOMRect} Bounds of the menu panel.
+ * - arrowBounds {DOMRect|null} Bounds of the arrow. Only set when
+ * menuType is "doorhanger", null otherwise.
+ */
+async function getButtonAndMenuInfo(toolbox, menuButton) {
+ const { doc, topDoc } = toolbox;
+ info("Show popup menu with click event.");
+ AccessibilityUtils.setEnv({
+ // Keyboard accessibility is handled on the toolbox toolbar container level.
+ // Users can use arrow keys to navigate between and select tabs.
+ nonNegativeTabIndexRule: false,
+ });
+ EventUtils.sendMouseEvent(
+ {
+ type: "click",
+ screenX: 1,
+ },
+ menuButton,
+ doc.defaultView
+ );
+ AccessibilityUtils.resetEnv();
+
+ let menuPopup;
+ let menuType;
+ let menuBounds = null;
+ let arrowBounds = null;
+ if (menuButton.hasAttribute("aria-controls")) {
+ menuType = "doorhanger";
+ menuPopup = doc.getElementById(menuButton.getAttribute("aria-controls"));
+ await waitUntil(() => menuPopup.classList.contains("tooltip-visible"));
+ // menuPopup can be a non-menupopup element, e.g. div. Call getBoxQuads to
+ // get its bounds.
+ menuBounds = menuPopup.getBoxQuads({ relativeTo: doc })[0].getBounds();
+ } else {
+ menuType = "native";
+ await waitUntil(() => {
+ const popupset = topDoc.querySelector("popupset");
+ menuPopup = popupset?.querySelector('menupopup[menu-api="true"]');
+ return menuPopup?.state === "open";
+ });
+ // menuPopup is a XUL menupopup element. Call getOuterScreenRect(), which is
+ // suported on both native and non-native menupopup implementations.
+ menuBounds = convertScreenToDoc(menuPopup, doc);
+ }
+ ok(menuPopup, "Menu popup is displayed.");
+
+ const buttonBounds = menuButton
+ .getBoxQuads({ relativeTo: doc })[0]
+ .getBounds();
+
+ if (menuType === "doorhanger") {
+ const arrow = menuPopup.querySelector(".tooltip-arrow");
+ arrowBounds = arrow.getBoxQuads({ relativeTo: doc })[0].getBounds();
+ }
+
+ info("Hide popup menu.");
+ if (menuType === "doorhanger") {
+ EventUtils.sendKey("Escape", doc.defaultView);
+ await waitUntil(() => !menuPopup.classList.contains("tooltip-visible"));
+ } else {
+ const popupHidden = once(menuPopup, "popuphidden");
+ menuPopup.hidePopup();
+ await popupHidden;
+ }
+
+ return { buttonBounds, menuType, menuBounds, arrowBounds };
+}