Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1470)

Unified Diff: content/browser/accessibility/browser_accessibility_cocoa.mm

Issue 1134653003: Implement UIElementsForSearchPredicate accessibility APIs for OS X. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Address feedback Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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.

Powered by Google App Engine
This is Rietveld 408576698