browser_app.js (12282B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { UrlbarTestUtils } = ChromeUtils.importESModule( 8 "resource://testing-common/UrlbarTestUtils.sys.mjs" 9 ); 10 /* import-globals-from ../../mochitest/role.js */ 11 /* import-globals-from ../../mochitest/states.js */ 12 loadScripts( 13 { name: "role.js", dir: MOCHITESTS_DIR }, 14 { name: "states.js", dir: MOCHITESTS_DIR } 15 ); 16 17 function getMacAccessible(accOrElmOrID) { 18 return new Promise(resolve => { 19 let intervalId = setInterval(() => { 20 let acc = getAccessible(accOrElmOrID); 21 if (acc) { 22 clearInterval(intervalId); 23 resolve( 24 acc.nativeInterface.QueryInterface(Ci.nsIAccessibleMacInterface) 25 ); 26 } 27 }, 10); 28 }); 29 } 30 31 add_setup(async function () { 32 await SpecialPowers.pushPrefEnv({ 33 set: [["browser.urlbar.trustPanel.featureGate", false]], 34 }); 35 }); 36 37 /** 38 * Test a11yUtils announcements are exposed to VO 39 */ 40 add_task(async () => { 41 const tab = await BrowserTestUtils.openNewForegroundTab( 42 gBrowser, 43 "data:text/html," 44 ); 45 const alert = document.getElementById("a11y-announcement"); 46 ok(alert, "Found alert to send announcements"); 47 48 const alerted = waitForMacEvent("AXAnnouncementRequested", (iface, data) => { 49 return data.AXAnnouncementKey == "hello world"; 50 }); 51 52 A11yUtils.announce({ 53 raw: "hello world", 54 }); 55 await alerted; 56 await BrowserTestUtils.removeTab(tab); 57 }); 58 59 /** 60 * Test browser tabs 61 */ 62 add_task(async () => { 63 let newTabs = await Promise.all([ 64 BrowserTestUtils.openNewForegroundTab( 65 gBrowser, 66 "data:text/html,<title>Two</title>" 67 ), 68 BrowserTestUtils.openNewForegroundTab( 69 gBrowser, 70 "data:text/html,<title>Three</title>" 71 ), 72 BrowserTestUtils.openNewForegroundTab( 73 gBrowser, 74 "data:text/html,<title>Four</title>" 75 ), 76 ]); 77 78 // Mochitests spawn with a tab, and we've opened 3 more for a total of 4 tabs 79 is(gBrowser.tabs.length, 4, "We now have 4 open tabs"); 80 81 let tablist = await getMacAccessible("tabbrowser-tabs"); 82 is( 83 tablist.getAttributeValue("AXRole"), 84 "AXTabGroup", 85 "Correct role for tablist" 86 ); 87 88 let tabMacAccs = tablist.getAttributeValue("AXTabs"); 89 is(tabMacAccs.length, 4, "4 items in AXTabs"); 90 91 let selectedTabs = tablist.getAttributeValue("AXSelectedChildren"); 92 is(selectedTabs.length, 1, "one selected tab"); 93 94 let tab = selectedTabs[0]; 95 is(tab.getAttributeValue("AXRole"), "AXRadioButton", "Correct role for tab"); 96 is( 97 tab.getAttributeValue("AXSubrole"), 98 "AXTabButton", 99 "Correct subrole for tab" 100 ); 101 is(tab.getAttributeValue("AXTitle"), "Four", "Correct title for tab"); 102 103 let tabToSelect = tabMacAccs[2]; 104 is( 105 tabToSelect.getAttributeValue("AXTitle"), 106 "Three", 107 "Correct title for tab" 108 ); 109 110 let actions = tabToSelect.actionNames; 111 ok(true, actions); 112 ok(actions.includes("AXPress"), "Has switch action"); 113 114 // When tab is clicked selection of tab group changes, 115 // and focus goes to the web area. Wait for both. 116 let evt = Promise.all([ 117 waitForMacEvent("AXSelectedChildrenChanged"), 118 waitForMacEvent( 119 "AXFocusedUIElementChanged", 120 iface => iface.getAttributeValue("AXRole") == "AXWebArea" 121 ), 122 ]); 123 tabToSelect.performAction("AXPress"); 124 await evt; 125 126 selectedTabs = tablist.getAttributeValue("AXSelectedChildren"); 127 is(selectedTabs.length, 1, "one selected tab"); 128 is( 129 selectedTabs[0].getAttributeValue("AXTitle"), 130 "Three", 131 "Correct title for tab" 132 ); 133 134 // Close all open tabs 135 await Promise.all(newTabs.map(t => BrowserTestUtils.removeTab(t))); 136 }); 137 138 /** 139 * Test ignored invisible items in root 140 */ 141 add_task(async () => { 142 await BrowserTestUtils.withNewTab( 143 { 144 gBrowser, 145 url: "about:license", 146 }, 147 async () => { 148 let root = await getMacAccessible(document); 149 let rootChildCount = () => root.getAttributeValue("AXChildren").length; 150 151 // With no popups, the root accessible has 5 visible children: 152 // 1. Tab bar (#TabsToolbar) 153 // 2. Navigation bar (#nav-bar) 154 // 3. Notifications toolbar (#notifications-toolbar) 155 // 4. Content area (#tabbrowser-tabpanels) 156 // 5. Accessibility announcements dialog (#a11y-announcement) 157 let baseRootChildCount = 5; 158 is( 159 rootChildCount(), 160 baseRootChildCount, 161 `Root with no popups has ${baseRootChildCount} children` 162 ); 163 164 // Open a context menu 165 const menu = document.getElementById("contentAreaContextMenu"); 166 if ( 167 Services.prefs.getBoolPref("widget.macos.native-context-menus", false) 168 ) { 169 // Native context menu - do not expect accessibility notifications. 170 let popupshown = BrowserTestUtils.waitForPopupEvent(menu, "shown"); 171 EventUtils.synthesizeMouseAtCenter(document.body, { 172 type: "contextmenu", 173 }); 174 await popupshown; 175 176 is( 177 rootChildCount(), 178 baseRootChildCount, 179 "Native context menus do not show up in the root children" 180 ); 181 182 // Close context menu 183 let popuphidden = BrowserTestUtils.waitForPopupEvent(menu, "hidden"); 184 menu.hidePopup(); 185 await popuphidden; 186 } else { 187 // Non-native menu 188 EventUtils.synthesizeMouseAtCenter(document.body, { 189 type: "contextmenu", 190 }); 191 await waitForMacEvent("AXMenuOpened"); 192 193 // Now root has 1 more child 194 is(rootChildCount(), baseRootChildCount + 1, "Root has 1 more child"); 195 196 // Close context menu 197 let closed = waitForMacEvent("AXMenuClosed", "contentAreaContextMenu"); 198 EventUtils.synthesizeKey("KEY_Escape"); 199 await BrowserTestUtils.waitForPopupEvent(menu, "hidden"); 200 await closed; 201 } 202 203 // We're back to base child count 204 is(rootChildCount(), baseRootChildCount, "Root has original child count"); 205 206 // Open site identity popup 207 document.getElementById("identity-icon-box").click(); 208 const identityPopup = document.getElementById("identity-popup"); 209 await BrowserTestUtils.waitForPopupEvent(identityPopup, "shown"); 210 211 // Now root has another child 212 is(rootChildCount(), baseRootChildCount + 1, "Root has another child"); 213 214 // Close popup 215 let hide = waitForMacEvent("AXUIElementDestroyed"); 216 EventUtils.synthesizeKey("KEY_Escape"); 217 await BrowserTestUtils.waitForPopupEvent(identityPopup, "hidden"); 218 await hide; 219 220 // We're back to the base child count 221 is(rootChildCount(), baseRootChildCount, "Root has the base child count"); 222 } 223 ); 224 }); 225 226 /** 227 * Tests for location bar 228 */ 229 add_task(async () => { 230 await BrowserTestUtils.withNewTab( 231 { 232 gBrowser, 233 // eslint-disable-next-line @microsoft/sdl/no-insecure-url 234 url: "http://example.com", 235 }, 236 async () => { 237 let input = await getMacAccessible(gURLBar.inputField); 238 is( 239 input.getAttributeValue("AXValue"), 240 // eslint-disable-next-line @microsoft/sdl/no-insecure-url 241 UrlbarTestUtils.trimURL("http://example.com"), 242 "Location bar has correct value" 243 ); 244 } 245 ); 246 }); 247 248 /** 249 * Tests attributed text in nav bar has no invisible AXAttachments 250 */ 251 add_task(async () => { 252 await BrowserTestUtils.withNewTab( 253 { 254 gBrowser, 255 // eslint-disable-next-line @microsoft/sdl/no-insecure-url 256 url: "http://example.com", 257 }, 258 async () => { 259 let root = await getMacAccessible(document); 260 let navBar = await getMacAccessible("nav-bar"); 261 let elemRange = root.getParameterizedAttributeValue( 262 "AXTextMarkerRangeForUIElement", 263 navBar 264 ); 265 let attributedString = root.getParameterizedAttributeValue( 266 "AXAttributedStringForTextMarkerRange", 267 elemRange 268 ); 269 let attachmentRoles = attributedString.map(s => 270 s.AXAttachment ? s.AXAttachment.getAttributeValue("AXRole") : null 271 ); 272 ok( 273 !attachmentRoles.includes("AXMenu"), 274 "Collapsed menu should be embedded in attributed text" 275 ); 276 } 277 ); 278 }); 279 280 /** 281 * Test context menu 282 */ 283 add_task(async () => { 284 if (Services.prefs.getBoolPref("widget.macos.native-context-menus", false)) { 285 ok(true, "We cannot inspect native context menu contents; skip this test."); 286 return; 287 } 288 289 await BrowserTestUtils.withNewTab( 290 { 291 gBrowser, 292 url: 'data:text/html,<a id="exampleLink" href="https://example.com">link</a>', 293 }, 294 async browser => { 295 if (!Services.search.isInitialized) { 296 let aStatus = await Services.search.init(); 297 Assert.ok(Components.isSuccessCode(aStatus)); 298 Assert.ok(Services.search.isInitialized); 299 } 300 301 const hasContainers = 302 Services.prefs.getBoolPref("privacy.userContext.enabled") && 303 !!ContextualIdentityService.getPublicIdentities().length; 304 info(`${hasContainers ? "Do" : "Don't"} expect containers item.`); 305 const hasInspectA11y = 306 Services.prefs.getBoolPref("devtools.everOpened", false) || 307 Services.prefs.getIntPref("devtools.selfxss.count", 0) > 0; 308 info(`${hasInspectA11y ? "Do" : "Don't"} expect inspect a11y item.`); 309 310 // synthesize a right click on the link to open the link context menu 311 let menu = document.getElementById("contentAreaContextMenu"); 312 await BrowserTestUtils.synthesizeMouseAtCenter( 313 "#exampleLink", 314 { type: "contextmenu" }, 315 browser 316 ); 317 await waitForMacEvent("AXMenuOpened"); 318 319 menu = await getMacAccessible(menu); 320 let menuChildren = menu.getAttributeValue("AXChildren"); 321 const expectedChildCount = 12 + +hasContainers + +hasInspectA11y; 322 is( 323 menuChildren.length, 324 expectedChildCount, 325 `Context menu on link contains ${expectedChildCount} items.` 326 ); 327 // items at indicies 3, 9, and 11 are the splitters when containers exist 328 // everything else should be a menu item, otherwise indicies of splitters are 329 // 3, 8, and 10 330 const splitterIndicies = hasContainers ? [4, 9, 11] : [3, 8, 10]; 331 for (let i = 0; i < menuChildren.length; i++) { 332 if (splitterIndicies.includes(i)) { 333 is( 334 menuChildren[i].getAttributeValue("AXRole"), 335 "AXSplitter", 336 "found splitter in menu" 337 ); 338 } else { 339 is( 340 menuChildren[i].getAttributeValue("AXRole"), 341 "AXMenuItem", 342 "found menu item in menu" 343 ); 344 } 345 } 346 347 // check the containers sub menu in depth if it exists 348 if (hasContainers) { 349 is( 350 menuChildren[1].getAttributeValue("AXVisibleChildren"), 351 null, 352 "Submenu 1 has no visible chldren when hidden" 353 ); 354 355 // focus the first submenu 356 EventUtils.synthesizeKey("KEY_ArrowDown"); 357 EventUtils.synthesizeKey("KEY_ArrowDown"); 358 EventUtils.synthesizeKey("KEY_ArrowRight"); 359 await waitForMacEvent("AXMenuOpened"); 360 361 // after the submenu is opened, refetch it 362 menu = document.getElementById("contentAreaContextMenu"); 363 menu = await getMacAccessible(menu); 364 menuChildren = menu.getAttributeValue("AXChildren"); 365 366 // verify submenu-menuitem's attributes 367 is( 368 menuChildren[1].getAttributeValue("AXChildren").length, 369 1, 370 "Submenu 1 has one child when open" 371 ); 372 const subMenu = menuChildren[1].getAttributeValue("AXChildren")[0]; 373 is( 374 subMenu.getAttributeValue("AXRole"), 375 "AXMenu", 376 "submenu has role of menu" 377 ); 378 const subMenuChildren = subMenu.getAttributeValue("AXChildren"); 379 is(subMenuChildren.length, 4, "sub menu has 4 children"); 380 is( 381 subMenu.getAttributeValue("AXVisibleChildren").length, 382 4, 383 "submenu has 4 visible children" 384 ); 385 386 // close context menu 387 EventUtils.synthesizeKey("KEY_Escape"); 388 await waitForMacEvent("AXMenuClosed"); 389 } 390 391 EventUtils.synthesizeKey("KEY_Escape"); 392 await waitForMacEvent("AXMenuClosed"); 393 } 394 ); 395 });