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 }