From 1baf91ec07a0930265a8c71f8112c1f648e5394a Mon Sep 17 00:00:00 2001 From: Morgan Reschenberg Date: Tue, 19 Jan 2021 22:04:52 +0000 Subject: [PATCH] Bug 1686164: Implement ignoreWithParent for context menu items and submenus. r=eeejay Differential Revision: https://phabricator.services.mozilla.com/D101652 --- accessible/mac/mozSelectableElements.h | 9 ++- accessible/mac/mozSelectableElements.mm | 69 ++++++++++++++++----- accessible/tests/browser/mac/browser_app.js | 20 +++--- 3 files changed, 69 insertions(+), 29 deletions(-) diff --git a/accessible/mac/mozSelectableElements.h b/accessible/mac/mozSelectableElements.h index 58545bf591b5..2baf07c94214 100644 --- a/accessible/mac/mozSelectableElements.h +++ b/accessible/mac/mozSelectableElements.h @@ -87,10 +87,10 @@ - (NSString*)moxLabel; // override -- (NSArray*)moxChildren; +- (NSArray*)moxVisibleChildren; // override -- (NSArray*)moxVisibleChildren; +- (BOOL)moxIgnoreWithParent:(mozAccessible*)parent; // override - (id)moxTitleUIElement; @@ -101,6 +101,8 @@ // override - (void)expire; +- (BOOL)isOpened; + @end @interface mozMenuItemAccessible : mozSelectableChildAccessible @@ -108,6 +110,9 @@ // override - (NSString*)moxLabel; +// override +- (BOOL)moxIgnoreWithParent:(mozAccessible*)parent; + // override - (NSString*)moxMenuItemMarkChar; diff --git a/accessible/mac/mozSelectableElements.mm b/accessible/mac/mozSelectableElements.mm index 229b8b0bdc2c..c68877c69139 100644 --- a/accessible/mac/mozSelectableElements.mm +++ b/accessible/mac/mozSelectableElements.mm @@ -154,25 +154,36 @@ 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]; +- (BOOL)moxIgnoreWithParent:(mozAccessible*)parent { + // This helps us generate the correct moxChildren array for + // a sub menu -- that returned array should contain all + // menu items, regardless of if they are visible or not. + // Because moxChildren does ignore filtering, and because + // our base ignore method filters out invisible accessibles, + // we override this method. + if ([parent geckoAccessible].Role() == roles::PARENT_MENUITEM) { + // We are a submenu. If our parent menu item is in an open menu + // we should not be ignored + id grandparent = [parent moxUnignoredParent]; + if (grandparent && [grandparent isKindOfClass:[mozMenuAccessible class]]) { + mozMenuAccessible* parentMenu = (mozMenuAccessible*)grandparent; + if ([parentMenu isOpened]) { + return NO; + } + } } - return nil; + + // Otherwise, we call into our superclass's ignore method + // to handle menus that are not submenus + return [super moxIgnoreWithParent:parent]; } - (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. + // (done in moxChildren), and children which are visible (here). + // We implement ignoreWithParent for both menus and menu items + // to ensure moxChildren returns a complete list of children regardless + // of visibility, see comments in those methods for additional info. return [[self moxChildren] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL( mozAccessible* child, @@ -188,8 +199,7 @@ using namespace mozilla::a11y; - (id)moxTitleUIElement { id parent = [self moxUnignoredParent]; - if ([parent isKindOfClass:[mozAccessible class]] && - [parent geckoAccessible].Role() == roles::PARENT_MENUITEM) { + if ([parent isKindOfClass:[mozAccessible class]]) { return parent; } @@ -216,6 +226,10 @@ using namespace mozilla::a11y; [super expire]; } +- (BOOL)isOpened { + return mIsOpened; +} + @end @implementation mozMenuItemAccessible @@ -224,6 +238,29 @@ using namespace mozilla::a11y; return @""; } +- (BOOL)moxIgnoreWithParent:(mozAccessible*)parent { + // This helps us generate the correct moxChildren array for + // a mozMenuAccessible; the returned array should contain all + // menu items, regardless of if they are visible or not. + // Because moxChildren does ignore filtering, and because + // our base ignore method filters out invisible accessibles, + // we override this method. + id grandparent = [parent moxUnignoredParent]; + if (grandparent && [grandparent isKindOfClass:[mozAccessible class]]) { + mozAccessible* acc = static_cast(grandparent); + if ([acc geckoAccessible].Role() == roles::PARENT_MENUITEM) { + mozMenuAccessible* parentMenu = (mozMenuAccessible*)parent; + // if we are a menu item in a submenu, display only when + // parent menu item is open + return [parentMenu moxIgnoreWithParent:acc]; + } + } + + // Otherwise, we call into our superclass's method to handle + // menuitems that are not within submenus + return [super moxIgnoreWithParent:parent]; +} + - (NSString*)moxMenuItemMarkChar { Accessible* acc = mGeckoAccessible.AsAccessible(); if (acc && acc->IsContent() && diff --git a/accessible/tests/browser/mac/browser_app.js b/accessible/tests/browser/mac/browser_app.js index d704e280dd5d..489759a3f368 100644 --- a/accessible/tests/browser/mac/browser_app.js +++ b/accessible/tests/browser/mac/browser_app.js @@ -206,7 +206,8 @@ add_task(async () => { { type: "contextmenu" }, browser ); - await BrowserTestUtils.waitForPopupEvent(menu, "shown"); + await waitForMacEvent("AXMenuOpened"); + menu = await getMacAccessible(menu); const menuChildren = menu.getAttributeValue("AXChildren"); // menu contains 12 items and 3 splitters for 15 items total @@ -236,24 +237,21 @@ add_task(async () => { // 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" + menuChildren[1].getAttributeValue("AXVisibleChildren"), + null, + "Submenu 1 has no visible chldren when hidden" ); is( - menuChildren[11].getAttributeValue("AXChildren").length, - 0, - "Submenu 2 has no chldren when hidden" + menuChildren[11].getAttributeValue("AXVisibleChildren"), + null, + "Submenu 2 has no visible 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"); + await waitForMacEvent("AXMenuOpened"); // verify submenu-menuitem's attributes is(