Bug 1681292: Ensure menu items correctly expose AXVisibleChildren and AXChildren r=eeejay

Differential Revision: https://phabricator.services.mozilla.com/D99949
This commit is contained in:
Morgan Reschenberg 2021-01-07 21:33:21 +00:00
parent 29a8164b84
commit 2bc463ad54
4 changed files with 150 additions and 0 deletions

View File

@ -217,6 +217,9 @@
// AXIdentifier
- (NSString* _Nullable)moxIdentifier;
// AXVisibleChildren
- (NSArray* _Nullable)moxVisibleChildren;
// Outline Attributes
// AXDisclosing

View File

@ -86,6 +86,15 @@
// override
- (NSString*)moxLabel;
// override
- (NSArray*)moxChildren;
// override
- (NSArray*)moxVisibleChildren;
// override
- (id)moxTitleUIElement;
// override
- (void)moxPostNotification:(NSString*)notification;

View File

@ -154,6 +154,48 @@ using namespace mozilla::a11y;
return @"";
}
- (NSArray*)moxChildren {
// We differ from Webkit and Apple-native menus here; they expose
// all children regardless of whether or not the menu is open.
// In testing, VoiceOver doesn't seem to actually care what happens
// here as long as AXVisibleChildren is exposed correctly, so
// we expose children only when the menu is open to avoid
// changing ignoreWithParent/ignoreChild and/or isAccessibilityElement
if (mIsOpened) {
return [super moxChildren];
}
return nil;
}
- (NSArray*)moxVisibleChildren {
// VO expects us to expose two lists of children on menus: all children
// (done above in moxChildren), and children which are visible (here).
// In our code, these are essentially the same list, since at the time of
// wiritng we filter for visibility in isAccessibilityElement before
// passing anything to VO.
return [[self moxChildren]
filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
mozAccessible* child,
NSDictionary* bindings) {
if (Accessible* acc = [child geckoAccessible].AsAccessible()) {
if (acc->IsContent() && acc->GetContent()->IsXULElement()) {
return ((acc->VisibilityState() & states::INVISIBLE) == 0);
}
}
return true;
}]];
}
- (id)moxTitleUIElement {
id parent = [self moxUnignoredParent];
if ([parent isKindOfClass:[mozAccessible class]] &&
[parent geckoAccessible].Role() == roles::PARENT_MENUITEM) {
return parent;
}
return nil;
}
- (void)moxPostNotification:(NSString*)notification {
[super moxPostNotification:notification];

View File

@ -179,3 +179,99 @@ add_task(async () => {
}
);
});
/**
* Test context menu
*/
add_task(async () => {
await BrowserTestUtils.withNewTab(
{
gBrowser,
url:
'data:text/html,<a id="exampleLink" href="https://example.com">link</a>',
},
async browser => {
// synthesize a right click on the link to open the link context menu
let menu = document.getElementById("contentAreaContextMenu");
await BrowserTestUtils.synthesizeMouse(
"#exampleLink",
2,
2,
{ type: "contextmenu" },
browser
);
await BrowserTestUtils.waitForPopupEvent(menu, "shown");
menu = await getMacAccessible(menu);
const menuChildren = menu.getAttributeValue("AXChildren");
// menu contains 11 items and 3 splitters for 14 items total
is(
menuChildren.length,
14,
"Context menu on link contains fourteen items"
);
for (let i = 0; i < menuChildren.length; i++) {
// items at indicies 4, 9, and 11 are the splitters, everything else should be a menu item
if (i == 4 || i == 9 || i == 11) {
is(
menuChildren[i].getAttributeValue("AXRole"),
"AXSplitter",
"found splitter in menu"
);
} else {
is(
menuChildren[i].getAttributeValue("AXRole"),
"AXMenuItem",
"found menu item in menu"
);
}
}
// submenus are at indicies 1 and 10
// first check they have no children when hidden
is(
menuChildren[1].getAttributeValue("AXChildren").length,
0,
"Submenu 1 has no chldren when hidden"
);
is(
menuChildren[10].getAttributeValue("AXChildren").length,
0,
"Submenu 2 has no chldren when hidden"
);
// focus the first submenu
const contextMenu = document.getElementById(
"context-openlinkinusercontext-menu"
);
EventUtils.synthesizeKey("KEY_ArrowDown");
EventUtils.synthesizeKey("KEY_ArrowDown");
EventUtils.synthesizeKey("KEY_ArrowRight");
await BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
// verify submenu-menuitem's attributes
is(
menuChildren[1].getAttributeValue("AXChildren").length,
1,
"Submenu 1 has one child when open"
);
const subMenu = menuChildren[1].getAttributeValue("AXChildren")[0];
is(
subMenu.getAttributeValue("AXRole"),
"AXMenu",
"submenu has role of menu"
);
const subMenuChildren = subMenu.getAttributeValue("AXChildren");
is(subMenuChildren.length, 4, "sub menu has 4 children");
is(
subMenu.getAttributeValue("AXVisibleChildren").length,
4,
"submenu has 4 visible children"
);
// close context menu
EventUtils.synthesizeKey("KEY_Escape");
EventUtils.synthesizeKey("KEY_Escape");
}
);
});