| 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..5f7bcacbae1f02ca1b5f66c6d723242393ae4c31 100644
|
| --- a/content/browser/accessibility/browser_accessibility_cocoa.mm
|
| +++ b/content/browser/accessibility/browser_accessibility_cocoa.mm
|
| @@ -15,6 +15,7 @@
|
| #include "content/app/strings/grit/content_strings.h"
|
| #include "content/browser/accessibility/browser_accessibility_manager.h"
|
| #include "content/browser/accessibility/browser_accessibility_manager_mac.h"
|
| +#include "content/browser/accessibility/one_shot_accessibility_tree_search.h"
|
| #include "content/public/common/content_client.h"
|
| #import "ui/accessibility/platform/ax_platform_node_mac.h"
|
|
|
| @@ -24,15 +25,20 @@
|
| extern "C" void NSAccessibilityUnregisterUniqueIdForUIElement(id element);
|
|
|
| using ui::AXNodeData;
|
| +using content::AccessibilityMatchPredicate;
|
| using content::BrowserAccessibility;
|
| using content::BrowserAccessibilityDelegate;
|
| using content::BrowserAccessibilityManager;
|
| using content::BrowserAccessibilityManagerMac;
|
| using content::ContentClient;
|
| +using content::OneShotAccessibilityTreeSearch;
|
| typedef ui::AXStringAttribute StringAttribute;
|
|
|
| namespace {
|
|
|
| +// VoiceOver uses -1 to mean "no limit" for AXResultsLimit.
|
| +const int kAXResultsLimitNoLimit = -1;
|
| +
|
| // Returns an autoreleased copy of the AXNodeData's attribute.
|
| NSString* NSStringForStringAttribute(
|
| BrowserAccessibility* browserAccessibility,
|
| @@ -50,6 +56,279 @@ 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 OneShotAccessibilityTreeSearch.
|
| +AccessibilityMatchPredicate PredicateForSearchKey(NSString* searchKey) {
|
| + if ([searchKey isEqualToString:@"AXAnyTypeSearchKey"]) {
|
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) {
|
| + return true;
|
| + };
|
| + } else if ([searchKey isEqualToString:@"AXBlockquoteSameLevelSearchKey"] ||
|
| + [searchKey isEqualToString:@"AXBlockquoteSearchKey"]) {
|
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) {
|
| + // TODO(dmazzoni): implement the "same level" part.
|
| + return current->GetRole() == ui::AX_ROLE_BLOCKQUOTE;
|
| + };
|
| + } else if ([searchKey isEqualToString:@"AXBoldFontSearchKey"]) {
|
| + // TODO(dmazzoni): implement this.
|
| + return nullptr;
|
| + } else if ([searchKey isEqualToString:@"AXButtonSearchKey"]) {
|
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) {
|
| + return (current->GetRole() == ui::AX_ROLE_BUTTON ||
|
| + current->GetRole() == ui::AX_ROLE_MENU_BUTTON ||
|
| + current->GetRole() == ui::AX_ROLE_POP_UP_BUTTON ||
|
| + current->GetRole() == ui::AX_ROLE_SWITCH ||
|
| + 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) {
|
| + if (current->IsControl())
|
| + return true;
|
| + if (current->HasState(ui::AX_STATE_FOCUSABLE) &&
|
| + current->GetRole() != ui::AX_ROLE_IMAGE_MAP_LINK &&
|
| + current->GetRole() != ui::AX_ROLE_LINK) {
|
| + return true;
|
| + }
|
| + return false;
|
| + };
|
| + } else if ([searchKey isEqualToString:@"AXDifferentTypeSearchKey"]) {
|
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) {
|
| + return current->GetRole() != start->GetRole();
|
| + };
|
| + } else if ([searchKey isEqualToString:@"AXFontChangeSearchKey"]) {
|
| + // TODO(dmazzoni): implement this.
|
| + return nullptr;
|
| + } else if ([searchKey isEqualToString:@"AXFontColorChangeSearchKey"]) {
|
| + // TODO(dmazzoni): implement this.
|
| + return nullptr;
|
| + } 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);
|
| + };
|
| + } 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 &&
|
| + start->GetRole() == ui::AX_ROLE_HEADING &&
|
| + (current->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) ==
|
| + start->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL)));
|
| + };
|
| + } else if ([searchKey isEqualToString:@"AXHeadingSearchKey"]) {
|
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) {
|
| + return current->GetRole() == ui::AX_ROLE_HEADING;
|
| + };
|
| + } else if ([searchKey isEqualToString:@"AXHighlightedSearchKey"]) {
|
| + // TODO(dmazzoni): implement this.
|
| + return nullptr;
|
| + } else if ([searchKey isEqualToString:@"AXItalicFontSearchKey"]) {
|
| + // TODO(dmazzoni): implement this.
|
| + return nullptr;
|
| + } 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"]) {
|
| + // TODO(dmazzoni): implement this.
|
| + return nullptr;
|
| + } else if ([searchKey isEqualToString:@"AXOutlineSearchKey"]) {
|
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) {
|
| + return current->GetRole() == ui::AX_ROLE_TREE;
|
| + };
|
| + } else if ([searchKey isEqualToString:@"AXPlainTextSearchKey"]) {
|
| + // TODO(dmazzoni): implement this.
|
| + return nullptr;
|
| + } 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"]) {
|
| + // TODO(dmazzoni): implement this.
|
| + return nullptr;
|
| + } else if ([searchKey isEqualToString:@"AXTableSameLevelSearchKey"]) {
|
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) {
|
| + // TODO(dmazzoni): implement the "same level" part.
|
| + return current->GetRole() == ui::AX_ROLE_GRID ||
|
| + current->GetRole() == ui::AX_ROLE_TABLE;
|
| + };
|
| + } else if ([searchKey isEqualToString:@"AXTableSearchKey"]) {
|
| + return [](BrowserAccessibility* start, BrowserAccessibility* current) {
|
| + return current->GetRole() == ui::AX_ROLE_GRID ||
|
| + 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"]) {
|
| + // TODO(dmazzoni): implement this.
|
| + return nullptr;
|
| + } 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 a OneShotAccessibilityTreeSearch object given the parameters
|
| +// passed to AXUIElementCountForSearchPredicate or
|
| +// AXUIElementsForSearchPredicate. Return true on success.
|
| +bool InitializeAccessibilityTreeSearch(
|
| + OneShotAccessibilityTreeSearch* 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::OneShotAccessibilityTreeSearch::Direction direction =
|
| + content::OneShotAccessibilityTreeSearch::FORWARDS;
|
| + NSString* directionParameter = [dictionary objectForKey:@"AXDirection"];
|
| + if ([directionParameter isKindOfClass:[NSString class]]) {
|
| + if ([directionParameter isEqualToString:@"AXDirectionNext"])
|
| + direction = content::OneShotAccessibilityTreeSearch::FORWARDS;
|
| + else if ([directionParameter isEqualToString:@"AXDirectionPrevious"])
|
| + direction = content::OneShotAccessibilityTreeSearch::BACKWARDS;
|
| + }
|
| +
|
| + int resultsLimit = kAXResultsLimitNoLimit;
|
| + 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->SetImmediateDescendantsOnly(immediateDescendantsOnly);
|
| + search->SetVisibleOnly(visibleOnly);
|
| + search->SetSearchText(searchText);
|
| +
|
| + // Mac uses resultsLimit == -1 for unlimited, that that's
|
| + // the default for OneShotAccessibilityTreeSearch already.
|
| + // Only set the results limit if it's nonnegative.
|
| + if (resultsLimit >= 0)
|
| + search->SetResultLimit(resultsLimit);
|
| +
|
| + 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 +836,10 @@ NSDictionary* attributeToMethodNameMap = nil;
|
| nil;
|
| }
|
|
|
| +- (content::BrowserAccessibility*)browserAccessibility {
|
| + return browserAccessibility_;
|
| +}
|
| +
|
| - (NSPoint)pointInScreen:(NSPoint)origin
|
| size:(NSSize)size {
|
| if (!browserAccessibility_)
|
| @@ -1213,6 +1496,26 @@ NSDictionary* attributeToMethodNameMap = nil;
|
| pointInScreen.x, pointInScreen.y, rect.width(), rect.height());
|
| return [NSValue valueWithRect:nsrect];
|
| }
|
| + if ([attribute isEqualToString:@"AXUIElementCountForSearchPredicate"]) {
|
| + OneShotAccessibilityTreeSearch search(browserAccessibility_->manager());
|
| + if (InitializeAccessibilityTreeSearch(&search, parameter))
|
| + return [NSNumber numberWithInt:search.CountMatches()];
|
| + return nil;
|
| + }
|
| +
|
| + if ([attribute isEqualToString:@"AXUIElementsForSearchPredicate"]) {
|
| + OneShotAccessibilityTreeSearch 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 +1536,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 +1559,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.
|
|
|