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

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: 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..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.

Powered by Google App Engine
This is Rietveld 408576698