diff options
author | dmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-01 22:58:52 +0000 |
---|---|---|
committer | dmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-01 22:58:52 +0000 |
commit | 111e7e6c5f51a35d39ccf1cff75e676b1b0d8f6b (patch) | |
tree | 0f62236b143d97c2c22696cb238cfc230e5ad545 | |
parent | 3b00c2d3dc17dfe7927b4884d029a3aa80afb6d3 (diff) | |
download | chromium_src-111e7e6c5f51a35d39ccf1cff75e676b1b0d8f6b.zip chromium_src-111e7e6c5f51a35d39ccf1cff75e676b1b0d8f6b.tar.gz chromium_src-111e7e6c5f51a35d39ccf1cff75e676b1b0d8f6b.tar.bz2 |
Support list / listbox accessibility attributes on Mac.
This change adds support for AXSelectedChildren, AXVisibleChildren,
and AXOrientation for lists and list boxes. Those are sufficient for VoiceOver
to read, e.g. (3 of 5) when navigating a list of 5 items.
BUG=381971
Review URL: https://codereview.chromium.org/348183007
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@280906 0039d316-1c4b-4281-b951-d872f2087c98
20 files changed, 185 insertions, 16 deletions
diff --git a/content/browser/accessibility/accessibility_tree_formatter_mac.mm b/content/browser/accessibility/accessibility_tree_formatter_mac.mm index e037d4d..69e5c76 100644 --- a/content/browser/accessibility/accessibility_tree_formatter_mac.mm +++ b/content/browser/accessibility/accessibility_tree_formatter_mac.mm @@ -94,19 +94,35 @@ scoped_ptr<base::ListValue> PopulateArray(NSArray* array) { scoped_ptr<base::StringValue> StringForBrowserAccessibility( BrowserAccessibilityCocoa* obj) { - NSString* description = [obj role]; - id value = [obj value]; + NSMutableArray* tokens = [[NSMutableArray alloc] init]; + + // Always include the role + id role = [obj role]; + [tokens addObject:role]; + + // If the role is "group", include the role description as well. id roleDescription = [obj roleDescription]; - if (value && ![value isEqual:@""]) { - description = [NSString stringWithFormat:@"%@ %@", description, value]; - } else if ([description isEqualToString:NSAccessibilityGroupRole] && - roleDescription != nil && - ![roleDescription isEqualToString:@""]) { - description = [NSString stringWithFormat:@"%@ %@", - description, roleDescription]; + if ([role isEqualToString:NSAccessibilityGroupRole] && + roleDescription != nil && + ![roleDescription isEqualToString:@""]) { + [tokens addObject:roleDescription]; } + + // Include the description, title, or value - the first one not empty. + id title = [obj title]; + id description = [obj description]; + id value = [obj value]; + if (description && ![description isEqual:@""]) { + [tokens addObject:description]; + } else if (title && ![title isEqual:@""]) { + [tokens addObject:title]; + } else if (value && ![value isEqual:@""]) { + [tokens addObject:value]; + } + + NSString* result = [tokens componentsJoinedByString:@" "]; return scoped_ptr<base::StringValue>( - new base::StringValue(SysNSStringToUTF16(description))).Pass(); + new base::StringValue(SysNSStringToUTF16(result))).Pass(); } scoped_ptr<base::Value> PopulateObject(id value) { @@ -154,9 +170,11 @@ NSArray* BuildAllAttributesArray() { NSAccessibilityOrientationAttribute, @"AXRequired", NSAccessibilityRowIndexRangeAttribute, + NSAccessibilitySelectedChildrenAttribute, NSAccessibilityTitleUIElementAttribute, NSAccessibilityURLAttribute, NSAccessibilityVisibleCharacterRangeAttribute, + NSAccessibilityVisibleChildrenAttribute, @"AXVisited", @"AXLinkedUIElements", nil]; diff --git a/content/browser/accessibility/browser_accessibility_cocoa.h b/content/browser/accessibility/browser_accessibility_cocoa.h index 5fc708f..0f01a7a 100644 --- a/content/browser/accessibility/browser_accessibility_cocoa.h +++ b/content/browser/accessibility/browser_accessibility_cocoa.h @@ -95,6 +95,7 @@ @property(nonatomic, readonly) NSArray* rowHeaders; @property(nonatomic, readonly) NSValue* rowIndexRange; @property(nonatomic, readonly) NSArray* rows; +@property(nonatomic, readonly) NSArray* selectedChildren; // The size of this object. @property(nonatomic, readonly) NSValue* size; // A string indicating the subrole of this object as far as accessibility @@ -109,6 +110,7 @@ @property(nonatomic, readonly) NSString* valueDescription; @property(nonatomic, readonly) NSValue* visibleCharacterRange; @property(nonatomic, readonly) NSArray* visibleCells; +@property(nonatomic, readonly) NSArray* visibleChildren; @property(nonatomic, readonly) NSArray* visibleColumns; @property(nonatomic, readonly) NSArray* visibleRows; @property(nonatomic, readonly) NSNumber* visited; diff --git a/content/browser/accessibility/browser_accessibility_cocoa.mm b/content/browser/accessibility/browser_accessibility_cocoa.mm index 232e482..2ed21f4 100644 --- a/content/browser/accessibility/browser_accessibility_cocoa.mm +++ b/content/browser/accessibility/browser_accessibility_cocoa.mm @@ -270,6 +270,7 @@ NSDictionary* attributeToMethodNameMap = nil; { NSAccessibilityRowIndexRangeAttribute, @"rowIndexRange" }, { NSAccessibilityRowsAttribute, @"rows" }, // TODO(aboxhall): expose NSAccessibilityServesAsTitleForUIElementsAttribute + { NSAccessibilitySelectedChildrenAttribute, @"selectedChildren" }, { NSAccessibilitySizeAttribute, @"size" }, { NSAccessibilitySubroleAttribute, @"subrole" }, { NSAccessibilityTabsAttribute, @"tabs" }, @@ -281,6 +282,7 @@ NSDictionary* attributeToMethodNameMap = nil; { NSAccessibilityValueDescriptionAttribute, @"valueDescription" }, { NSAccessibilityVisibleCharacterRangeAttribute, @"visibleCharacterRange" }, { NSAccessibilityVisibleCellsAttribute, @"visibleCells" }, + { NSAccessibilityVisibleChildrenAttribute, @"visibleChildren" }, { NSAccessibilityVisibleColumnsAttribute, @"visibleColumns" }, { NSAccessibilityVisibleRowsAttribute, @"visibleRows" }, { NSAccessibilityWindowAttribute, @"window" }, @@ -628,6 +630,11 @@ NSDictionary* attributeToMethodNameMap = nil; if ([self internalRole] == ui::AX_ROLE_SPIN_BUTTON) return NSAccessibilityVerticalOrientationValue; + if ([self internalRole] == ui::AX_ROLE_LIST || + [self internalRole] == ui::AX_ROLE_LIST_BOX) { + return NSAccessibilityVerticalOrientationValue; + } + if (GetState(browserAccessibility_, ui::AX_STATE_VERTICAL)) return NSAccessibilityVerticalOrientationValue; else @@ -829,6 +836,40 @@ NSDictionary* attributeToMethodNameMap = nil; return ret; } +- (NSArray*)selectedChildren { + NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; + + BrowserAccessibilityManager* manager = browserAccessibility_->manager(); + BrowserAccessibility* focusedChild = + manager->GetFocus(browserAccessibility_); + if (focusedChild && focusedChild != browserAccessibility_) { + // First try the focused child. + [ret addObject:focusedChild->ToBrowserAccessibilityCocoa()]; + } else { + // Next try the active descendant. + int activeDescendantId; + if (browserAccessibility_->GetIntAttribute( + ui::AX_ATTR_ACTIVEDESCENDANT_ID, &activeDescendantId)) { + BrowserAccessibility* activeDescendant = + manager->GetFromID(activeDescendantId); + if (activeDescendant) + [ret addObject:activeDescendant->ToBrowserAccessibilityCocoa()]; + } else { + // Otherwise return any children with the "selected" state, which + // may come from aria-selected. + uint32 childCount = browserAccessibility_->PlatformChildCount(); + for (uint32 index = 0; index < childCount; ++index) { + BrowserAccessibility* child = + browserAccessibility_->PlatformGetChild(index); + if (child->HasState(ui::AX_STATE_SELECTED)) + [ret addObject:child->ToBrowserAccessibilityCocoa()]; + } + } + } + + return ret; +} + // Returns the size of this object. - (NSValue*)size { gfx::Rect bounds = browserAccessibility_->GetLocalBoundsRect(); @@ -1002,6 +1043,18 @@ NSDictionary* attributeToMethodNameMap = nil; return ret; } +- (NSArray*)visibleChildren { + NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; + uint32 childCount = browserAccessibility_->PlatformChildCount(); + for (uint32 index = 0; index < childCount; ++index) { + BrowserAccessibilityCocoa* child = + browserAccessibility_->PlatformGetChild(index)-> + ToBrowserAccessibilityCocoa(); + [ret addObject:child]; + } + return ret; +} + - (NSArray*)visibleColumns { return [self columns]; } @@ -1396,6 +1449,12 @@ NSDictionary* attributeToMethodNameMap = nil; nil]]; } } + } else if ([role isEqualToString:NSAccessibilityListRole]) { + [ret addObjectsFromArray:[NSArray arrayWithObjects: + NSAccessibilityOrientationAttribute, + NSAccessibilitySelectedChildrenAttribute, + NSAccessibilityVisibleChildrenAttribute, + nil]]; } // Add the url attribute only if it has a valid url. diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc index d373f7b..543b06c 100644 --- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc +++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc @@ -301,6 +301,21 @@ IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAriaList) { RunTest(FILE_PATH_LITERAL("aria-list.html")); } +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, + AccessibilityAriaListBoxActiveDescendant) { + RunTest(FILE_PATH_LITERAL("aria-listbox-activedescendant.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, + AccessibilityAriaListBoxAriaSelected) { + RunTest(FILE_PATH_LITERAL("aria-listbox-aria-selected.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, + AccessibilityAriaListBoxChildFocus) { + RunTest(FILE_PATH_LITERAL("aria-listbox-childfocus.html")); +} + IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAriaMenu) { RunTest(FILE_PATH_LITERAL("aria-menu.html")); } diff --git a/content/test/data/accessibility/aria-labelledby-heading-expected-mac.txt b/content/test/data/accessibility/aria-labelledby-heading-expected-mac.txt index da6c21d..b09d6d47 100644 --- a/content/test/data/accessibility/aria-labelledby-heading-expected-mac.txt +++ b/content/test/data/accessibility/aria-labelledby-heading-expected-mac.txt @@ -1,5 +1,5 @@ AXWebArea AXGroup - AXTextField AXTitleUIElement='AXHeading 2' + AXTextField AXTitleUIElement='AXHeading h2' AXHeading AXTitle='h2' AXValue='2' AXStaticText AXValue='h2' diff --git a/content/test/data/accessibility/aria-list-expected-mac.txt b/content/test/data/accessibility/aria-list-expected-mac.txt index 8ae9e03..7beb5ea 100644 --- a/content/test/data/accessibility/aria-list-expected-mac.txt +++ b/content/test/data/accessibility/aria-list-expected-mac.txt @@ -1,5 +1,5 @@ AXWebArea - AXList AXSubrole=AXContentList + AXList AXSubrole=AXContentList AXOrientation='AXVerticalOrientation' AXSelectedChildren=[] AXVisibleChildren=["AXGroup group 1","AXGroup group 2","AXGroup group 3"] AXGroup AXStaticText AXValue='Item 1' AXGroup diff --git a/content/test/data/accessibility/aria-list.html b/content/test/data/accessibility/aria-list.html index 2e69bee..1aa4cd1 100644 --- a/content/test/data/accessibility/aria-list.html +++ b/content/test/data/accessibility/aria-list.html @@ -1,12 +1,15 @@ <!-- @MAC-ALLOW:AXSubrole* +@MAC-ALLOW:AXOrientation* +@MAC-ALLOW:AXSelectedChildren* +@MAC-ALLOW:AXVisibleChildren* --> <html> <body> <div role="list"> - <div role="listitem">Item 1</div> - <div role="listitem">Item 2</div> - <div role="listitem">Item 3</div> + <div aria-label="1" role="listitem">Item 1</div> + <div aria-label="2" role="listitem">Item 2</div> + <div aria-label="3" role="listitem">Item 3</div> </div> </body> </html> diff --git a/content/test/data/accessibility/aria-listbox-activedescendant-expected-android.txt b/content/test/data/accessibility/aria-listbox-activedescendant-expected-android.txt new file mode 100644 index 0000000..8620d59 --- /dev/null +++ b/content/test/data/accessibility/aria-listbox-activedescendant-expected-android.txt @@ -0,0 +1 @@ +#<skip - need to generate expectation> diff --git a/content/test/data/accessibility/aria-listbox-activedescendant-expected-mac.txt b/content/test/data/accessibility/aria-listbox-activedescendant-expected-mac.txt new file mode 100644 index 0000000..21a73f8 --- /dev/null +++ b/content/test/data/accessibility/aria-listbox-activedescendant-expected-mac.txt @@ -0,0 +1,5 @@ +AXWebArea + AXList AXOrientation='AXVerticalOrientation' AXSelectedChildren=["AXStaticText 3"] AXVisibleChildren=["AXStaticText 1","AXStaticText 2","AXStaticText 3"] + AXStaticText AXTitle='Item 1' + AXStaticText AXTitle='Item 2' + AXStaticText AXTitle='Item 3' diff --git a/content/test/data/accessibility/aria-listbox-activedescendant-expected-win.txt b/content/test/data/accessibility/aria-listbox-activedescendant-expected-win.txt new file mode 100644 index 0000000..8620d59 --- /dev/null +++ b/content/test/data/accessibility/aria-listbox-activedescendant-expected-win.txt @@ -0,0 +1 @@ +#<skip - need to generate expectation> diff --git a/content/test/data/accessibility/aria-listbox-activedescendant.html b/content/test/data/accessibility/aria-listbox-activedescendant.html new file mode 100644 index 0000000..7a5c230 --- /dev/null +++ b/content/test/data/accessibility/aria-listbox-activedescendant.html @@ -0,0 +1,16 @@ +<!-- +@MAC-ALLOW:AXSubrole* +@MAC-ALLOW:AXOrientation* +@MAC-ALLOW:AXSelectedChildren* +@MAC-ALLOW:AXVisibleChildren* +--> +<html> +<body> +<div role="listbox" tabIndex="0" aria-activedescendant="3"> + <div id="1" aria-label="1" role="option">Item 1</div> + <div id="2" aria-label="2" role="option">Item 2</div> + <div id="3" aria-label="3" role="option">Item 3</div> +</div> +<script>document.querySelector('*[role="listbox"]').focus();</script> +</body> +</html> diff --git a/content/test/data/accessibility/aria-listbox-aria-selected-expected-android.txt b/content/test/data/accessibility/aria-listbox-aria-selected-expected-android.txt new file mode 100644 index 0000000..8620d59 --- /dev/null +++ b/content/test/data/accessibility/aria-listbox-aria-selected-expected-android.txt @@ -0,0 +1 @@ +#<skip - need to generate expectation> diff --git a/content/test/data/accessibility/aria-listbox-aria-selected-expected-mac.txt b/content/test/data/accessibility/aria-listbox-aria-selected-expected-mac.txt new file mode 100644 index 0000000..c521df9a --- /dev/null +++ b/content/test/data/accessibility/aria-listbox-aria-selected-expected-mac.txt @@ -0,0 +1,7 @@ +AXWebArea + AXList AXOrientation='AXVerticalOrientation' AXSelectedChildren=["AXStaticText 4","AXStaticText 5"] AXVisibleChildren=["AXStaticText 1","AXStaticText 2","AXStaticText 3","AXStaticText 4","AXStaticText 5"] + AXStaticText AXTitle='Item 1' + AXStaticText AXTitle='Item 2' + AXStaticText AXTitle='Item 3' + AXStaticText AXTitle='Item 4' + AXStaticText AXTitle='Item 5' diff --git a/content/test/data/accessibility/aria-listbox-aria-selected-expected-win.txt b/content/test/data/accessibility/aria-listbox-aria-selected-expected-win.txt new file mode 100644 index 0000000..8620d59 --- /dev/null +++ b/content/test/data/accessibility/aria-listbox-aria-selected-expected-win.txt @@ -0,0 +1 @@ +#<skip - need to generate expectation> diff --git a/content/test/data/accessibility/aria-listbox-aria-selected.html b/content/test/data/accessibility/aria-listbox-aria-selected.html new file mode 100644 index 0000000..50c8037 --- /dev/null +++ b/content/test/data/accessibility/aria-listbox-aria-selected.html @@ -0,0 +1,17 @@ +<!-- +@MAC-ALLOW:AXSubrole* +@MAC-ALLOW:AXOrientation* +@MAC-ALLOW:AXSelectedChildren* +@MAC-ALLOW:AXVisibleChildren* +--> +<html> +<body> +<div role="listbox"> + <div tabIndex="-1" aria-label="1" role="option">Item 1</div> + <div tabIndex="-1" aria-label="2" role="option">Item 2</div> + <div tabIndex="-1" aria-label="3" role="option">Item 3</div> + <div tabIndex="-1" aria-selected="true" aria-label="4" role="option">Item 4</div> + <div tabIndex="-1" aria-selected="true" aria-label="5" role="option">Item 5</div> +</div> +</body> +</html> diff --git a/content/test/data/accessibility/aria-listbox-childfocus-expected-android.txt b/content/test/data/accessibility/aria-listbox-childfocus-expected-android.txt new file mode 100644 index 0000000..8620d59 --- /dev/null +++ b/content/test/data/accessibility/aria-listbox-childfocus-expected-android.txt @@ -0,0 +1 @@ +#<skip - need to generate expectation> diff --git a/content/test/data/accessibility/aria-listbox-childfocus-expected-mac.txt b/content/test/data/accessibility/aria-listbox-childfocus-expected-mac.txt new file mode 100644 index 0000000..81da39e --- /dev/null +++ b/content/test/data/accessibility/aria-listbox-childfocus-expected-mac.txt @@ -0,0 +1,5 @@ +AXWebArea + AXList AXOrientation='AXVerticalOrientation' AXSelectedChildren=["AXStaticText 2"] AXVisibleChildren=["AXStaticText 1","AXStaticText 2","AXStaticText 3"] + AXStaticText AXTitle='Item 1' + AXStaticText AXTitle='Item 2' + AXStaticText AXTitle='Item 3' diff --git a/content/test/data/accessibility/aria-listbox-childfocus-expected-win.txt b/content/test/data/accessibility/aria-listbox-childfocus-expected-win.txt new file mode 100644 index 0000000..8620d59 --- /dev/null +++ b/content/test/data/accessibility/aria-listbox-childfocus-expected-win.txt @@ -0,0 +1 @@ +#<skip - need to generate expectation> diff --git a/content/test/data/accessibility/aria-listbox-childfocus.html b/content/test/data/accessibility/aria-listbox-childfocus.html new file mode 100644 index 0000000..c05497e --- /dev/null +++ b/content/test/data/accessibility/aria-listbox-childfocus.html @@ -0,0 +1,16 @@ +<!-- +@MAC-ALLOW:AXSubrole* +@MAC-ALLOW:AXOrientation* +@MAC-ALLOW:AXSelectedChildren* +@MAC-ALLOW:AXVisibleChildren* +--> +<html> +<body> +<div role="listbox"> + <div tabIndex="-1" aria-label="1" role="option">Item 1</div> + <div tabIndex="0" aria-label="2" role="option">Item 2</div> + <div tabIndex="-1" aria-label="3" role="option">Item 3</div> +</div> +<script>document.querySelector('*[aria-label="2"]').focus();</script> +</body> +</html> diff --git a/content/test/data/accessibility/aria-menu-expected-mac.txt b/content/test/data/accessibility/aria-menu-expected-mac.txt index 7c1715a..eab78cf 100644 --- a/content/test/data/accessibility/aria-menu-expected-mac.txt +++ b/content/test/data/accessibility/aria-menu-expected-mac.txt @@ -1,6 +1,6 @@ AXWebArea AXMenuBar - AXMenuItem AXTitle='File' AXLinkedUIElements=["AXMenu"] + AXMenuItem AXTitle='File' AXLinkedUIElements=["AXMenu File"] AXMenuItem AXTitle='Edit' AXMenuItem AXTitle='View' AXMenu AXDescription='File' |