Chromium Code Reviews| Index: content/browser/accessibility/browser_accessibility_cocoa.mm |
| diff --git a/content/browser/accessibility/browser_accessibility_cocoa.mm b/content/browser/accessibility/browser_accessibility_cocoa.mm |
| index d333ae1d748999b0776c392e4dbca52ce15bf8bc..bd8091511caaf06cc205107d205e8a09f8d8dab7 100644 |
| --- a/content/browser/accessibility/browser_accessibility_cocoa.mm |
| +++ b/content/browser/accessibility/browser_accessibility_cocoa.mm |
| @@ -13,6 +13,7 @@ |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "content/app/strings/grit/content_strings.h" |
| +#include "content/browser/accessibility/accessibility_tree_search.h" |
| #include "content/browser/accessibility/browser_accessibility_manager.h" |
| #include "content/browser/accessibility/browser_accessibility_manager_mac.h" |
| #include "content/public/common/content_client.h" |
| @@ -24,6 +25,8 @@ |
| extern "C" void NSAccessibilityUnregisterUniqueIdForUIElement(id element); |
| using ui::AXNodeData; |
| +using content::AccessibilityMatchPredicate; |
| +using content::AccessibilityTreeSearch; |
| using content::BrowserAccessibility; |
| using content::BrowserAccessibilityDelegate; |
| using content::BrowserAccessibilityManager; |
| @@ -50,6 +53,251 @@ bool GetState(BrowserAccessibility* accessibility, ui::AXState state) { |
| // A mapping from an accessibility attribute to its method name. |
| NSDictionary* attributeToMethodNameMap = nil; |
| +// Given a search key provided to AXUIElementCountForSearchPredicate or |
| +// AXUIElementsForSearchPredicate, return a predicate that can be added |
| +// to AccessibilityTreeSearch. |
| +AccessibilityMatchPredicate PredicateForSearchKey(NSString* searchKey) { |
| + if ([searchKey isEqualToString:@"AXAnyTypeSearchKey"]) { |
|
David Tseng
2015/05/08 21:58:25
Is this string defined in NS*? Is this API availab
dmazzoni
2015/05/13 04:22:21
It's not even in 10.10. As far as I can tell the s
|
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return true; |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXBlockquoteSameLevelSearchKey"] || |
| + [searchKey isEqualToString:@"AXBlockquoteSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return current->GetRole() == ui::AX_ROLE_BLOCKQUOTE; |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXBoldFontSearchKey"]) { |
| + return nullptr; // TODO(dmazzoni): implement this. |
|
David Tseng
2015/05/08 21:58:25
Can you move the TODO into its own line above?
dmazzoni
2015/05/13 04:22:21
Done throughout.
|
| + } else if ([searchKey isEqualToString:@"AXButtonSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return (current->GetRole() == ui::AX_ROLE_BUTTON || |
|
David Tseng
2015/05/08 21:58:25
Not sure if they exist on Mac, but menuButton and
dmazzoni
2015/05/13 04:22:21
Done.
|
| + current->GetRole() == ui::AX_ROLE_POP_UP_BUTTON || |
| + current->GetRole() == ui::AX_ROLE_TOGGLE_BUTTON); |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXCheckBoxSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return (current->GetRole() == ui::AX_ROLE_CHECK_BOX || |
| + current->GetRole() == ui::AX_ROLE_MENU_ITEM_CHECK_BOX); |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXControlSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return current->HasState(ui::AX_STATE_FOCUSABLE); |
|
David Tseng
2015/05/08 21:58:25
If a button doesn't take focus (like if it were di
dmazzoni
2015/05/13 04:22:20
No, good point. I implemented IsControl but decide
|
| + }; |
| + } else if ([searchKey isEqualToString:@"AXDifferentTypeSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return current->GetRole() != start->GetRole(); |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXFontChangeSearchKey"]) { |
| + return nullptr; // TODO(dmazzoni): implement this. |
|
David Tseng
2015/05/08 21:58:25
Ditto
|
| + } else if ([searchKey isEqualToString:@"AXFontColorChangeSearchKey"]) { |
| + return nullptr; // TODO(dmazzoni): implement this. |
|
David Tseng
2015/05/08 21:58:25
Ditto
|
| + } else if ([searchKey isEqualToString:@"AXFrameSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + if (current->IsWebAreaForPresentationalIframe()) |
| + return false; |
| + return (current->GetRole() == ui::AX_ROLE_WEB_AREA || |
| + current->GetRole() == ui::AX_ROLE_ROOT_WEB_AREA); |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXGraphicSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return current->GetRole() == ui::AX_ROLE_IMAGE; |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXHeadingLevel1SearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return (current->GetRole() == ui::AX_ROLE_HEADING && |
| + current->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) == 1); |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXHeadingLevel2SearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return (current->GetRole() == ui::AX_ROLE_HEADING && |
| + current->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) == 2); |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXHeadingLevel3SearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return (current->GetRole() == ui::AX_ROLE_HEADING && |
| + current->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) == 3); |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXHeadingLevel4SearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return (current->GetRole() == ui::AX_ROLE_HEADING && |
| + current->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) == 4); |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXHeadingLevel5SearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return (current->GetRole() == ui::AX_ROLE_HEADING && |
| + current->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) == 5); |
|
David Tseng
2015/05/08 21:58:25
Maybe worth writing a helper returning a predicate
dmazzoni
2015/05/13 04:22:21
It's only 6 heading levels and they're 2 lines eac
|
| + }; |
| + } else if ([searchKey isEqualToString:@"AXHeadingLevel6SearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return (current->GetRole() == ui::AX_ROLE_HEADING && |
| + current->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) == 6); |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXHeadingSameLevelSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return (current->GetRole() == ui::AX_ROLE_HEADING && |
| + current->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) == |
| + start->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL)); |
|
David Tseng
2015/05/08 21:58:25
Need to check start is a heading too?
dmazzoni
2015/05/13 04:22:20
Done.
|
| + }; |
| + } else if ([searchKey isEqualToString:@"AXHeadingSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return current->GetRole() == ui::AX_ROLE_HEADING; |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXHighlightedSearchKey"]) { |
| + return nullptr; // TODO(dmazzoni): implement this. |
| + } else if ([searchKey isEqualToString:@"AXItalicFontSearchKey"]) { |
| + return nullptr; // TODO(dmazzoni): implement this. |
| + } else if ([searchKey isEqualToString:@"AXLandmarkSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return (current->GetRole() == ui::AX_ROLE_APPLICATION || |
| + current->GetRole() == ui::AX_ROLE_BANNER || |
| + current->GetRole() == ui::AX_ROLE_COMPLEMENTARY || |
| + current->GetRole() == ui::AX_ROLE_CONTENT_INFO || |
| + current->GetRole() == ui::AX_ROLE_FORM || |
| + current->GetRole() == ui::AX_ROLE_MAIN || |
| + current->GetRole() == ui::AX_ROLE_NAVIGATION || |
| + current->GetRole() == ui::AX_ROLE_SEARCH); |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXLinkSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return current->GetRole() == ui::AX_ROLE_LINK; |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXListSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return current->GetRole() == ui::AX_ROLE_LIST; |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXLiveRegionSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return current->HasStringAttribute(ui::AX_ATTR_LIVE_STATUS); |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXMisspelledWordSearchKey"]) { |
| + return nullptr; // TODO(dmazzoni): implement this. |
| + } else if ([searchKey isEqualToString:@"AXOutlineSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return current->GetRole() == ui::AX_ROLE_TREE; |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXPlainTextSearchKey"]) { |
| + return nullptr; // TODO(dmazzoni): implement this. |
| + } else if ([searchKey isEqualToString:@"AXRadioGroupSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return current->GetRole() == ui::AX_ROLE_RADIO_GROUP; |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXSameTypeSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return current->GetRole() == start->GetRole(); |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXStaticTextSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return current->GetRole() == ui::AX_ROLE_STATIC_TEXT; |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXStyleChangeSearchKey"]) { |
| + return nullptr; // TODO(dmazzoni): implement this. |
| + } else if ([searchKey isEqualToString:@"AXTableSameLevelSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return current->GetRole() == ui::AX_ROLE_TABLE; |
|
David Tseng
2015/05/08 21:58:25
What about grid? I think this means you need to ch
dmazzoni
2015/05/13 04:22:21
Done.
Added TODO for nested, I wasn't planning on
|
| + }; |
| + } else if ([searchKey isEqualToString:@"AXTableSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return current->GetRole() == ui::AX_ROLE_TABLE; |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXTextFieldSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return current->GetRole() == ui::AX_ROLE_TEXT_FIELD; |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXUnderlineSearchKey"]) { |
| + return nullptr; // TODO(dmazzoni): implement this. |
| + } else if ([searchKey isEqualToString:@"AXUnvisitedLinkSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return (current->GetRole() == ui::AX_ROLE_LINK && |
| + !current->HasState(ui::AX_STATE_VISITED)); |
| + }; |
| + } else if ([searchKey isEqualToString:@"AXVisitedLinkSearchKey"]) { |
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) { |
| + return (current->GetRole() == ui::AX_ROLE_LINK && |
| + current->HasState(ui::AX_STATE_VISITED)); |
| + }; |
| + } |
| + |
| + return nullptr; |
| +} |
| + |
| +// Initialize an AccessibilityTreeSearch object given the parameters |
| +// passed to AXUIElementCountForSearchPredicate or |
| +// AXUIElementsForSearchPredicate. Return true on success. |
| +bool InitializeAccessibilityTreeSearch( |
| + AccessibilityTreeSearch* search, |
| + id parameter) { |
| + if (![parameter isKindOfClass:[NSDictionary class]]) |
| + return false; |
| + NSDictionary* dictionary = parameter; |
| + |
| + id startElementParameter = [dictionary objectForKey:@"AXStartElement"]; |
| + BrowserAccessibility* startNode = nullptr; |
| + if ([startElementParameter isKindOfClass:[BrowserAccessibilityCocoa class]]) { |
| + BrowserAccessibilityCocoa* startNodeCocoa = |
| + (BrowserAccessibilityCocoa*)startElementParameter; |
| + startNode = [startNodeCocoa browserAccessibility]; |
| + } |
| + |
| + bool immediateDescendantsOnly = false; |
| + NSNumber *immediateDescendantsOnlyParameter = |
| + [dictionary objectForKey:@"AXImmediateDescendantsOnly"]; |
| + if ([immediateDescendantsOnlyParameter isKindOfClass:[NSNumber class]]) |
| + immediateDescendantsOnly = [immediateDescendantsOnlyParameter boolValue]; |
| + |
| + bool visibleOnly = false; |
| + NSNumber *visibleOnlyParameter = [dictionary objectForKey:@"AXVisibleOnly"]; |
| + if ([visibleOnlyParameter isKindOfClass:[NSNumber class]]) |
| + visibleOnly = [visibleOnlyParameter boolValue]; |
| + |
| + content::AccessibilityTreeSearch::Direction direction = |
| + content::AccessibilityTreeSearch::FORWARDS; |
| + NSString* directionParameter = [dictionary objectForKey:@"AXDirection"]; |
| + if ([directionParameter isKindOfClass:[NSString class]]) { |
| + if ([directionParameter isEqualToString:@"AXDirectionNext"]) |
| + direction = content::AccessibilityTreeSearch::FORWARDS; |
| + else if ([directionParameter isEqualToString:@"AXDirectionPrevious"]) |
| + direction = content::AccessibilityTreeSearch::BACKWARDS; |
| + } |
| + |
| + int resultsLimit = 0; |
| + NSNumber* resultsLimitParameter = [dictionary objectForKey:@"AXResultsLimit"]; |
| + if ([resultsLimitParameter isKindOfClass:[NSNumber class]]) |
| + resultsLimit = [resultsLimitParameter intValue]; |
| + |
| + std::string searchText; |
| + NSString* searchTextParameter = [dictionary objectForKey:@"AXSearchText"]; |
| + if ([searchTextParameter isKindOfClass:[NSString class]]) |
| + searchText = base::SysNSStringToUTF8(searchTextParameter); |
| + |
| + search->SetStartNode(startNode); |
| + search->SetDirection(direction); |
| + search->SetResultLimit(resultsLimit); |
| + search->SetImmediateDescendantsOnly(immediateDescendantsOnly); |
| + search->SetVisibleOnly(visibleOnly); |
| + search->SetSearchText(searchText); |
|
David Tseng
2015/05/08 21:58:25
Looks like you do have all args available here; co
dmazzoni
2015/05/13 04:22:20
Two reasons not to:
* I think this is more readabl
|
| + |
| + id searchKey = [dictionary objectForKey:@"AXSearchKey"]; |
| + if ([searchKey isKindOfClass:[NSString class]]) { |
| + AccessibilityMatchPredicate predicate = |
| + PredicateForSearchKey((NSString*)searchKey); |
| + if (predicate) |
| + search->AddPredicate(predicate); |
| + } else if ([searchKey isKindOfClass:[NSArray class]]) { |
| + size_t searchKeyCount = static_cast<size_t>([searchKey count]); |
| + for (size_t i = 0; i < searchKeyCount; ++i) { |
| + id key = [searchKey objectAtIndex:i]; |
| + if ([key isKindOfClass:[NSString class]]) { |
| + AccessibilityMatchPredicate predicate = |
| + PredicateForSearchKey((NSString*)key); |
| + if (predicate) |
| + search->AddPredicate(predicate); |
| + } |
| + } |
| + } |
| + |
| + return true; |
| +} |
| + |
| } // namespace |
| @implementation BrowserAccessibilityCocoa |
| @@ -557,6 +805,10 @@ NSDictionary* attributeToMethodNameMap = nil; |
| nil; |
| } |
| +- (content::BrowserAccessibility*)browserAccessibility { |
| + return browserAccessibility_; |
| +} |
| + |
| - (NSPoint)pointInScreen:(NSPoint)origin |
| size:(NSSize)size { |
| if (!browserAccessibility_) |
| @@ -1213,6 +1465,26 @@ NSDictionary* attributeToMethodNameMap = nil; |
| pointInScreen.x, pointInScreen.y, rect.width(), rect.height()); |
| return [NSValue valueWithRect:nsrect]; |
| } |
| + if ([attribute isEqualToString:@"AXUIElementCountForSearchPredicate"]) { |
| + AccessibilityTreeSearch search(browserAccessibility_->manager()); |
| + if (InitializeAccessibilityTreeSearch(&search, parameter)) |
| + return [NSNumber numberWithInt:search.CountMatches()]; |
| + return nil; |
| + } |
| + |
| + if ([attribute isEqualToString:@"AXUIElementsForSearchPredicate"]) { |
| + AccessibilityTreeSearch search(browserAccessibility_->manager()); |
| + if (InitializeAccessibilityTreeSearch(&search, parameter)) { |
| + size_t count = search.CountMatches(); |
| + NSMutableArray* result = [NSMutableArray arrayWithCapacity:count]; |
| + for (size_t i = 0; i < count; i++) { |
| + BrowserAccessibility* match = search.GetMatchAtIndex(i); |
| + [result addObject:match->ToBrowserAccessibilityCocoa()]; |
| + } |
| + return result; |
| + } |
| + return nil; |
| + } |
| // TODO(dtseng): support the following attributes. |
| if ([attribute isEqualTo: |
| @@ -1233,14 +1505,20 @@ NSDictionary* attributeToMethodNameMap = nil; |
| if (!browserAccessibility_) |
| return nil; |
| + // General attributes. |
| + NSMutableArray* ret = [NSMutableArray arrayWithObjects: |
| + @"AXUIElementCountForSearchPredicate", |
| + @"AXUIElementsForSearchPredicate", |
| + nil]; |
| + |
| if ([[self role] isEqualToString:NSAccessibilityTableRole] || |
| [[self role] isEqualToString:NSAccessibilityGridRole]) { |
| - return [NSArray arrayWithObjects: |
| + [ret addObjectsFromArray:[NSArray arrayWithObjects: |
| NSAccessibilityCellForColumnAndRowParameterizedAttribute, |
| - nil]; |
| + nil]]; |
| } |
| if ([[self role] isEqualToString:NSAccessibilityTextFieldRole]) { |
| - return [NSArray arrayWithObjects: |
| + [ret addObjectsFromArray:[NSArray arrayWithObjects: |
| NSAccessibilityLineForIndexParameterizedAttribute, |
| NSAccessibilityRangeForLineParameterizedAttribute, |
| NSAccessibilityStringForRangeParameterizedAttribute, |
| @@ -1250,14 +1528,14 @@ NSDictionary* attributeToMethodNameMap = nil; |
| NSAccessibilityRTFForRangeParameterizedAttribute, |
| NSAccessibilityAttributedStringForRangeParameterizedAttribute, |
| NSAccessibilityStyleRangeForIndexParameterizedAttribute, |
| - nil]; |
| + nil]]; |
| } |
| if ([self internalRole] == ui::AX_ROLE_STATIC_TEXT) { |
| - return [NSArray arrayWithObjects: |
| + [ret addObjectsFromArray:[NSArray arrayWithObjects: |
| NSAccessibilityBoundsForRangeParameterizedAttribute, |
| - nil]; |
| + nil]]; |
| } |
| - return nil; |
| + return ret; |
| } |
| // Returns an array of action names that this object will respond to. |