AppUiTestDelegate.sys.mjs (6622B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 import { Assert } from "resource://testing-common/Assert.sys.mjs"; 5 import { BrowserTestUtils } from "resource://testing-common/BrowserTestUtils.sys.mjs"; 6 7 const lazy = {}; 8 9 ChromeUtils.defineESModuleGetters(lazy, { 10 CustomizableUI: 11 "moz-src:///browser/components/customizableui/CustomizableUI.sys.mjs", 12 }); 13 14 async function promiseAnimationFrame(window) { 15 await new Promise(resolve => window.requestAnimationFrame(resolve)); 16 17 let { tm } = Services; 18 return new Promise(resolve => tm.dispatchToMainThread(resolve)); 19 } 20 21 function makeWidgetId(id) { 22 id = id.toLowerCase(); 23 return id.replace(/[^a-z0-9_-]/g, "_"); 24 } 25 26 async function getPageActionButtonId(window, extensionId) { 27 // This would normally be set automatically on navigation, and cleared 28 // when the user types a value into the URL bar, to show and hide page 29 // identity info and icons such as page action buttons. 30 // 31 // Unfortunately, that doesn't happen automatically in browser chrome 32 // tests. 33 window.gURLBar.setPageProxyState("valid"); 34 35 let { gIdentityHandler } = window.gBrowser.ownerGlobal; 36 // If the current tab is blank and the previously selected tab was an internal 37 // page, the urlbar will now be showing the internal identity box due to the 38 // setPageProxyState call above. The page action button is hidden in that 39 // case, so make sure we're not showing the internal identity box. 40 gIdentityHandler._identityBox.classList.remove("chromeUI"); 41 42 await promiseAnimationFrame(window); 43 44 return window.BrowserPageActions.urlbarButtonNodeIDForActionID( 45 makeWidgetId(extensionId) 46 ); 47 } 48 49 async function getPageActionButton(window, extensionId) { 50 let pageActionId = await getPageActionButtonId(window, extensionId); 51 return window.document.getElementById(pageActionId); 52 } 53 54 async function clickPageAction(window, extensionId, modifiers = {}) { 55 let pageActionId = await getPageActionButtonId(window, extensionId); 56 await BrowserTestUtils.synthesizeMouseAtCenter( 57 `#${pageActionId}`, 58 modifiers, 59 window.browsingContext 60 ); 61 return new Promise(resolve => Services.tm.dispatchToMainThread(resolve)); 62 } 63 64 function getBrowserActionWidgetId(extensionId) { 65 return makeWidgetId(extensionId) + "-browser-action"; 66 } 67 68 function getBrowserActionWidget(extensionId) { 69 return lazy.CustomizableUI.getWidget(getBrowserActionWidgetId(extensionId)); 70 } 71 72 async function showBrowserAction(window, extensionId) { 73 let group = getBrowserActionWidget(extensionId); 74 let widget = group.forWindow(window); 75 if (!widget.node) { 76 return; 77 } 78 79 let navbar = window.document.getElementById("nav-bar"); 80 if (group.areaType == lazy.CustomizableUI.TYPE_TOOLBAR) { 81 Assert.equal( 82 widget.overflowed, 83 navbar.hasAttribute("overflowing"), 84 "Expect widget overflow state to match toolbar" 85 ); 86 } else if (group.areaType == lazy.CustomizableUI.TYPE_PANEL) { 87 let panel = window.gUnifiedExtensions.panel; 88 let shown = BrowserTestUtils.waitForPopupEvent(panel, "shown"); 89 window.gUnifiedExtensions.togglePanel(); 90 await shown; 91 } 92 } 93 94 async function clickBrowserAction(window, extensionId, modifiers) { 95 await promiseAnimationFrame(window); 96 await showBrowserAction(window, extensionId); 97 98 if (modifiers) { 99 let widgetId = getBrowserActionWidgetId(extensionId); 100 BrowserTestUtils.synthesizeMouseAtCenter( 101 `#${widgetId}`, 102 modifiers, 103 window.browsingContext 104 ); 105 } else { 106 let widget = getBrowserActionWidget(extensionId).forWindow(window); 107 widget.node.querySelector(".unified-extensions-item-action-button").click(); 108 } 109 } 110 111 async function promisePopupShown(popup) { 112 return new Promise(resolve => { 113 if (popup.state == "open") { 114 resolve(); 115 } else { 116 let onPopupShown = () => { 117 popup.removeEventListener("popupshown", onPopupShown); 118 resolve(); 119 }; 120 popup.addEventListener("popupshown", onPopupShown); 121 } 122 }); 123 } 124 125 function awaitBrowserLoaded(browser) { 126 if ( 127 browser.ownerGlobal.document.readyState === "complete" && 128 browser.currentURI.spec !== "about:blank" 129 ) { 130 return Promise.resolve(); 131 } 132 return new Promise(resolve => { 133 const listener = () => { 134 if (browser.currentURI.spec === "about:blank") { 135 return; 136 } 137 browser.removeEventListener("AppTestDelegate:load", listener); 138 resolve(); 139 }; 140 browser.addEventListener("AppTestDelegate:load", listener); 141 }); 142 } 143 144 function getPanelForNode(node) { 145 return node.closest("panel"); 146 } 147 148 async function awaitExtensionPanel(window, extensionId, awaitLoad = true) { 149 let { originalTarget: browser } = await BrowserTestUtils.waitForEvent( 150 window.document, 151 "WebExtPopupLoaded", 152 true, 153 event => event.detail.extension.id === extensionId 154 ); 155 156 await Promise.all([ 157 promisePopupShown(getPanelForNode(browser)), 158 159 awaitLoad && awaitBrowserLoaded(browser), 160 ]); 161 162 return browser; 163 } 164 165 function closeBrowserAction(window, extensionId) { 166 let group = getBrowserActionWidget(extensionId); 167 168 let node = window.document.getElementById(group.viewId); 169 lazy.CustomizableUI.hidePanelForNode(node); 170 171 return Promise.resolve(); 172 } 173 174 function getPageActionPopup(window, extensionId) { 175 let panelId = makeWidgetId(extensionId) + "-panel"; 176 return window.document.getElementById(panelId); 177 } 178 179 function closePageAction(window, extensionId) { 180 let node = getPageActionPopup(window, extensionId); 181 if (node) { 182 return promisePopupShown(node).then(() => { 183 node.hidePopup(); 184 }); 185 } 186 187 return Promise.resolve(); 188 } 189 190 function openNewForegroundTab(window, url, waitForLoad = true) { 191 return BrowserTestUtils.openNewForegroundTab( 192 window.gBrowser, 193 url, 194 waitForLoad 195 ); 196 } 197 198 async function removeTab(tab) { 199 BrowserTestUtils.removeTab(tab); 200 } 201 202 // These methods are exported so that they can be used in head.js but are 203 // *not* part of the AppUiTestDelegate API. 204 export var AppUiTestInternals = { 205 awaitBrowserLoaded, 206 getBrowserActionWidget, 207 getBrowserActionWidgetId, 208 getPageActionButton, 209 getPageActionPopup, 210 getPanelForNode, 211 makeWidgetId, 212 promiseAnimationFrame, 213 promisePopupShown, 214 showBrowserAction, 215 }; 216 217 // These methods are part of the AppUiTestDelegate API. All implementations need 218 // to be kept in sync. For details, see: 219 // testing/specialpowers/content/AppTestDelegateParent.sys.mjs 220 export var AppUiTestDelegate = { 221 awaitExtensionPanel, 222 clickBrowserAction, 223 clickPageAction, 224 closeBrowserAction, 225 closePageAction, 226 openNewForegroundTab, 227 removeTab, 228 };