Bug 1881181 - Wait for mac accessible to be destroyed properly. r=morgan
[gecko.git] / accessible / tests / browser / mac / browser_app.js
blobcd06776c5326c0b6dc907b8a492a794de22260da
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/. */
5 "use strict";
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 }
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   });
31 /**
32  * Test a11yUtils announcements are exposed to VO
33  */
34 add_task(async () => {
35   const tab = await BrowserTestUtils.openNewForegroundTab(
36     gBrowser,
37     "data:text/html,"
38   );
39   const alert = document.getElementById("a11y-announcement");
40   ok(alert, "Found alert to send announcements");
42   const alerted = waitForMacEvent("AXAnnouncementRequested", (iface, data) => {
43     return data.AXAnnouncementKey == "hello world";
44   });
46   A11yUtils.announce({
47     raw: "hello world",
48   });
49   await alerted;
50   await BrowserTestUtils.removeTab(tab);
51 });
53 /**
54  * Test browser tabs
55  */
56 add_task(async () => {
57   let newTabs = await Promise.all([
58     BrowserTestUtils.openNewForegroundTab(
59       gBrowser,
60       "data:text/html,<title>Two</title>"
61     ),
62     BrowserTestUtils.openNewForegroundTab(
63       gBrowser,
64       "data:text/html,<title>Three</title>"
65     ),
66     BrowserTestUtils.openNewForegroundTab(
67       gBrowser,
68       "data:text/html,<title>Four</title>"
69     ),
70   ]);
72   // Mochitests spawn with a tab, and we've opened 3 more for a total of 4 tabs
73   is(gBrowser.tabs.length, 4, "We now have 4 open tabs");
75   let tablist = await getMacAccessible("tabbrowser-tabs");
76   is(
77     tablist.getAttributeValue("AXRole"),
78     "AXTabGroup",
79     "Correct role for tablist"
80   );
82   let tabMacAccs = tablist.getAttributeValue("AXTabs");
83   is(tabMacAccs.length, 4, "4 items in AXTabs");
85   let selectedTabs = tablist.getAttributeValue("AXSelectedChildren");
86   is(selectedTabs.length, 1, "one selected tab");
88   let tab = selectedTabs[0];
89   is(tab.getAttributeValue("AXRole"), "AXRadioButton", "Correct role for tab");
90   is(
91     tab.getAttributeValue("AXSubrole"),
92     "AXTabButton",
93     "Correct subrole for tab"
94   );
95   is(tab.getAttributeValue("AXTitle"), "Four", "Correct title for tab");
97   let tabToSelect = tabMacAccs[2];
98   is(
99     tabToSelect.getAttributeValue("AXTitle"),
100     "Three",
101     "Correct title for tab"
102   );
104   let actions = tabToSelect.actionNames;
105   ok(true, actions);
106   ok(actions.includes("AXPress"), "Has switch action");
108   // When tab is clicked selection of tab group changes,
109   // and focus goes to the web area. Wait for both.
110   let evt = Promise.all([
111     waitForMacEvent("AXSelectedChildrenChanged"),
112     waitForMacEvent(
113       "AXFocusedUIElementChanged",
114       iface => iface.getAttributeValue("AXRole") == "AXWebArea"
115     ),
116   ]);
117   tabToSelect.performAction("AXPress");
118   await evt;
120   selectedTabs = tablist.getAttributeValue("AXSelectedChildren");
121   is(selectedTabs.length, 1, "one selected tab");
122   is(
123     selectedTabs[0].getAttributeValue("AXTitle"),
124     "Three",
125     "Correct title for tab"
126   );
128   // Close all open tabs
129   await Promise.all(newTabs.map(t => BrowserTestUtils.removeTab(t)));
133  * Test ignored invisible items in root
134  */
135 add_task(async () => {
136   await BrowserTestUtils.withNewTab(
137     {
138       gBrowser,
139       url: "about:license",
140     },
141     async browser => {
142       let root = await getMacAccessible(document);
143       let rootChildCount = () => root.getAttributeValue("AXChildren").length;
145       // With no popups, the root accessible has 5 visible children:
146       // 1. Tab bar (#TabsToolbar)
147       // 2. Navigation bar (#nav-bar)
148       // 3. Content area (#tabbrowser-tabpanels)
149       // 4. Some fullscreen pointer grabber (#fullscreen-and-pointerlock-wrapper)
150       // 5. Accessibility announcements dialog (#a11y-announcement)
151       let baseRootChildCount = 5;
152       is(
153         rootChildCount(),
154         baseRootChildCount,
155         "Root with no popups has 5 children"
156       );
158       // Open a context menu
159       const menu = document.getElementById("contentAreaContextMenu");
160       if (
161         Services.prefs.getBoolPref("widget.macos.native-context-menus", false)
162       ) {
163         // Native context menu - do not expect accessibility notifications.
164         let popupshown = BrowserTestUtils.waitForPopupEvent(menu, "shown");
165         EventUtils.synthesizeMouseAtCenter(document.body, {
166           type: "contextmenu",
167         });
168         await popupshown;
170         is(
171           rootChildCount(),
172           baseRootChildCount,
173           "Native context menus do not show up in the root children"
174         );
176         // Close context menu
177         let popuphidden = BrowserTestUtils.waitForPopupEvent(menu, "hidden");
178         menu.hidePopup();
179         await popuphidden;
180       } else {
181         // Non-native menu
182         EventUtils.synthesizeMouseAtCenter(document.body, {
183           type: "contextmenu",
184         });
185         await waitForMacEvent("AXMenuOpened");
187         // Now root has 1 more child
188         is(rootChildCount(), baseRootChildCount + 1, "Root has 1 more child");
190         // Close context menu
191         let closed = waitForMacEvent("AXMenuClosed", "contentAreaContextMenu");
192         EventUtils.synthesizeKey("KEY_Escape");
193         await BrowserTestUtils.waitForPopupEvent(menu, "hidden");
194         await closed;
195       }
197       // We're back to base child count
198       is(rootChildCount(), baseRootChildCount, "Root has original child count");
200       // Open site identity popup
201       document.getElementById("identity-icon-box").click();
202       const identityPopup = document.getElementById("identity-popup");
203       await BrowserTestUtils.waitForPopupEvent(identityPopup, "shown");
205       // Now root has another child
206       is(rootChildCount(), baseRootChildCount + 1, "Root has another child");
208       // Close popup
209       let hide = waitForMacEvent("AXUIElementDestroyed");
210       EventUtils.synthesizeKey("KEY_Escape");
211       await BrowserTestUtils.waitForPopupEvent(identityPopup, "hidden");
212       await hide;
214       // We're back to the base child count
215       is(rootChildCount(), baseRootChildCount, "Root has the base child count");
216     }
217   );
221  * Tests for location bar
222  */
223 add_task(async () => {
224   await BrowserTestUtils.withNewTab(
225     {
226       gBrowser,
227       // eslint-disable-next-line @microsoft/sdl/no-insecure-url
228       url: "http://example.com",
229     },
230     async browser => {
231       let input = await getMacAccessible("urlbar-input");
232       is(
233         input.getAttributeValue("AXValue"),
234         // eslint-disable-next-line @microsoft/sdl/no-insecure-url
235         UrlbarTestUtils.trimURL("http://example.com"),
236         "Location bar has correct value"
237       );
238     }
239   );
243  * Test context menu
244  */
245 add_task(async () => {
246   if (Services.prefs.getBoolPref("widget.macos.native-context-menus", false)) {
247     ok(true, "We cannot inspect native context menu contents; skip this test.");
248     return;
249   }
251   await BrowserTestUtils.withNewTab(
252     {
253       gBrowser,
254       url: 'data:text/html,<a id="exampleLink" href="https://example.com">link</a>',
255     },
256     async browser => {
257       if (!Services.search.isInitialized) {
258         let aStatus = await Services.search.init();
259         Assert.ok(Components.isSuccessCode(aStatus));
260         Assert.ok(Services.search.isInitialized);
261       }
263       const hasContainers =
264         Services.prefs.getBoolPref("privacy.userContext.enabled") &&
265         !!ContextualIdentityService.getPublicIdentities().length;
266       info(`${hasContainers ? "Do" : "Don't"} expect containers item.`);
267       const hasInspectA11y =
268         Services.prefs.getBoolPref("devtools.everOpened", false) ||
269         Services.prefs.getIntPref("devtools.selfxss.count", 0) > 0;
270       info(`${hasInspectA11y ? "Do" : "Don't"} expect inspect a11y item.`);
272       // synthesize a right click on the link to open the link context menu
273       let menu = document.getElementById("contentAreaContextMenu");
274       await BrowserTestUtils.synthesizeMouseAtCenter(
275         "#exampleLink",
276         { type: "contextmenu" },
277         browser
278       );
279       await waitForMacEvent("AXMenuOpened");
281       menu = await getMacAccessible(menu);
282       let menuChildren = menu.getAttributeValue("AXChildren");
283       const expectedChildCount = 12 + +hasContainers + +hasInspectA11y;
284       is(
285         menuChildren.length,
286         expectedChildCount,
287         `Context menu on link contains ${expectedChildCount} items.`
288       );
289       // items at indicies 3, 9, and 11 are the splitters when containers exist
290       // everything else should be a menu item, otherwise indicies of splitters are
291       // 3, 8, and 10
292       const splitterIndicies = hasContainers ? [4, 9, 11] : [3, 8, 10];
293       for (let i = 0; i < menuChildren.length; i++) {
294         if (splitterIndicies.includes(i)) {
295           is(
296             menuChildren[i].getAttributeValue("AXRole"),
297             "AXSplitter",
298             "found splitter in menu"
299           );
300         } else {
301           is(
302             menuChildren[i].getAttributeValue("AXRole"),
303             "AXMenuItem",
304             "found menu item in menu"
305           );
306         }
307       }
309       // check the containers sub menu in depth if it exists
310       if (hasContainers) {
311         is(
312           menuChildren[1].getAttributeValue("AXVisibleChildren"),
313           null,
314           "Submenu 1 has no visible chldren when hidden"
315         );
317         // focus the first submenu
318         EventUtils.synthesizeKey("KEY_ArrowDown");
319         EventUtils.synthesizeKey("KEY_ArrowDown");
320         EventUtils.synthesizeKey("KEY_ArrowRight");
321         await waitForMacEvent("AXMenuOpened");
323         // after the submenu is opened, refetch it
324         menu = document.getElementById("contentAreaContextMenu");
325         menu = await getMacAccessible(menu);
326         menuChildren = menu.getAttributeValue("AXChildren");
328         // verify submenu-menuitem's attributes
329         is(
330           menuChildren[1].getAttributeValue("AXChildren").length,
331           1,
332           "Submenu 1 has one child when open"
333         );
334         const subMenu = menuChildren[1].getAttributeValue("AXChildren")[0];
335         is(
336           subMenu.getAttributeValue("AXRole"),
337           "AXMenu",
338           "submenu has role of menu"
339         );
340         const subMenuChildren = subMenu.getAttributeValue("AXChildren");
341         is(subMenuChildren.length, 4, "sub menu has 4 children");
342         is(
343           subMenu.getAttributeValue("AXVisibleChildren").length,
344           4,
345           "submenu has 4 visible children"
346         );
348         // close context menu
349         EventUtils.synthesizeKey("KEY_Escape");
350         await waitForMacEvent("AXMenuClosed");
351       }
353       EventUtils.synthesizeKey("KEY_Escape");
354       await waitForMacEvent("AXMenuClosed");
355     }
356   );