Commit 5ad9f1fd authored by dmazzoni's avatar dmazzoni Committed by Commit bot

Implement UIElementsForSearchPredicate accessibility APIs for OS X.

This enables VoiceOver in OS X 10.10 to use the rotor and other
keyboard shortcuts that move by element type.

BUG=471119

Review URL: https://codereview.chromium.org/1134653003

Cr-Commit-Position: refs/heads/master@{#329979}
parent 347d1e56
...@@ -759,6 +759,38 @@ bool BrowserAccessibility::IsWebAreaForPresentationalIframe() const { ...@@ -759,6 +759,38 @@ bool BrowserAccessibility::IsWebAreaForPresentationalIframe() const {
return grandparent->GetRole() == ui::AX_ROLE_IFRAME_PRESENTATIONAL; return grandparent->GetRole() == ui::AX_ROLE_IFRAME_PRESENTATIONAL;
} }
bool BrowserAccessibility::IsControl() const {
switch (GetRole()) {
case ui::AX_ROLE_BUTTON:
case ui::AX_ROLE_BUTTON_DROP_DOWN:
case ui::AX_ROLE_CHECK_BOX:
case ui::AX_ROLE_COLOR_WELL:
case ui::AX_ROLE_COMBO_BOX:
case ui::AX_ROLE_DISCLOSURE_TRIANGLE:
case ui::AX_ROLE_LIST_BOX:
case ui::AX_ROLE_MENU_BAR:
case ui::AX_ROLE_MENU_BUTTON:
case ui::AX_ROLE_MENU_ITEM:
case ui::AX_ROLE_MENU_ITEM_CHECK_BOX:
case ui::AX_ROLE_MENU_ITEM_RADIO:
case ui::AX_ROLE_MENU:
case ui::AX_ROLE_POP_UP_BUTTON:
case ui::AX_ROLE_RADIO_BUTTON:
case ui::AX_ROLE_SCROLL_BAR:
case ui::AX_ROLE_SEARCH_BOX:
case ui::AX_ROLE_SLIDER:
case ui::AX_ROLE_SPIN_BUTTON:
case ui::AX_ROLE_SWITCH:
case ui::AX_ROLE_TAB:
case ui::AX_ROLE_TEXT_FIELD:
case ui::AX_ROLE_TOGGLE_BUTTON:
case ui::AX_ROLE_TREE:
return true;
default:
return false;
}
}
int BrowserAccessibility::GetStaticTextLenRecursive() const { int BrowserAccessibility::GetStaticTextLenRecursive() const {
if (GetRole() == ui::AX_ROLE_STATIC_TEXT || if (GetRole() == ui::AX_ROLE_STATIC_TEXT ||
GetRole() == ui::AX_ROLE_LINE_BREAK) { GetRole() == ui::AX_ROLE_LINE_BREAK) {
......
...@@ -173,7 +173,6 @@ class CONTENT_EXPORT BrowserAccessibility { ...@@ -173,7 +173,6 @@ class CONTENT_EXPORT BrowserAccessibility {
typedef base::StringPairs HtmlAttributes; typedef base::StringPairs HtmlAttributes;
const HtmlAttributes& GetHtmlAttributes() const; const HtmlAttributes& GetHtmlAttributes() const;
// Returns true if this is a native platform-specific object, vs a // Returns true if this is a native platform-specific object, vs a
// cross-platform generic object. Don't call ToBrowserAccessibilityXXX if // cross-platform generic object. Don't call ToBrowserAccessibilityXXX if
// IsNative returns false. // IsNative returns false.
...@@ -262,6 +261,9 @@ class CONTENT_EXPORT BrowserAccessibility { ...@@ -262,6 +261,9 @@ class CONTENT_EXPORT BrowserAccessibility {
// True if this is a web area, and its grandparent is a presentational iframe. // True if this is a web area, and its grandparent is a presentational iframe.
bool IsWebAreaForPresentationalIframe() const; bool IsWebAreaForPresentationalIframe() const;
// Is any control, like a button, text field, etc.
bool IsControl() const;
protected: protected:
BrowserAccessibility(); BrowserAccessibility();
......
...@@ -41,6 +41,9 @@ ...@@ -41,6 +41,9 @@
// the manager. // the manager.
- (content::BrowserAccessibilityDelegate*)delegate; - (content::BrowserAccessibilityDelegate*)delegate;
// Get the BrowserAccessibility that this object wraps.
- (content::BrowserAccessibility*)browserAccessibility;
// Convert the local objet's origin to a global point. // Convert the local objet's origin to a global point.
- (NSPoint)pointInScreen:(NSPoint)origin - (NSPoint)pointInScreen:(NSPoint)origin
size:(NSSize)size; size:(NSSize)size;
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "content/app/strings/grit/content_strings.h" #include "content/app/strings/grit/content_strings.h"
#include "content/browser/accessibility/browser_accessibility_manager.h" #include "content/browser/accessibility/browser_accessibility_manager.h"
#include "content/browser/accessibility/browser_accessibility_manager_mac.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" #include "content/public/common/content_client.h"
#import "ui/accessibility/platform/ax_platform_node_mac.h" #import "ui/accessibility/platform/ax_platform_node_mac.h"
...@@ -24,15 +25,20 @@ ...@@ -24,15 +25,20 @@
extern "C" void NSAccessibilityUnregisterUniqueIdForUIElement(id element); extern "C" void NSAccessibilityUnregisterUniqueIdForUIElement(id element);
using ui::AXNodeData; using ui::AXNodeData;
using content::AccessibilityMatchPredicate;
using content::BrowserAccessibility; using content::BrowserAccessibility;
using content::BrowserAccessibilityDelegate; using content::BrowserAccessibilityDelegate;
using content::BrowserAccessibilityManager; using content::BrowserAccessibilityManager;
using content::BrowserAccessibilityManagerMac; using content::BrowserAccessibilityManagerMac;
using content::ContentClient; using content::ContentClient;
using content::OneShotAccessibilityTreeSearch;
typedef ui::AXStringAttribute StringAttribute; typedef ui::AXStringAttribute StringAttribute;
namespace { namespace {
// VoiceOver uses -1 to mean "no limit" for AXResultsLimit.
const int kAXResultsLimitNoLimit = -1;
// Returns an autoreleased copy of the AXNodeData's attribute. // Returns an autoreleased copy of the AXNodeData's attribute.
NSString* NSStringForStringAttribute( NSString* NSStringForStringAttribute(
BrowserAccessibility* browserAccessibility, BrowserAccessibility* browserAccessibility,
...@@ -50,6 +56,279 @@ bool GetState(BrowserAccessibility* accessibility, ui::AXState state) { ...@@ -50,6 +56,279 @@ bool GetState(BrowserAccessibility* accessibility, ui::AXState state) {
// A mapping from an accessibility attribute to its method name. // A mapping from an accessibility attribute to its method name.
NSDictionary* attributeToMethodNameMap = nil; 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 } // namespace
@implementation BrowserAccessibilityCocoa @implementation BrowserAccessibilityCocoa
...@@ -557,6 +836,10 @@ NSDictionary* attributeToMethodNameMap = nil; ...@@ -557,6 +836,10 @@ NSDictionary* attributeToMethodNameMap = nil;
nil; nil;
} }
- (content::BrowserAccessibility*)browserAccessibility {
return browserAccessibility_;
}
- (NSPoint)pointInScreen:(NSPoint)origin - (NSPoint)pointInScreen:(NSPoint)origin
size:(NSSize)size { size:(NSSize)size {
if (!browserAccessibility_) if (!browserAccessibility_)
...@@ -1212,6 +1495,26 @@ NSDictionary* attributeToMethodNameMap = nil; ...@@ -1212,6 +1495,26 @@ NSDictionary* attributeToMethodNameMap = nil;
pointInScreen.x, pointInScreen.y, rect.width(), rect.height()); pointInScreen.x, pointInScreen.y, rect.width(), rect.height());
return [NSValue valueWithRect:nsrect]; 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. // TODO(dtseng): support the following attributes.
if ([attribute isEqualTo: if ([attribute isEqualTo:
...@@ -1232,14 +1535,20 @@ NSDictionary* attributeToMethodNameMap = nil; ...@@ -1232,14 +1535,20 @@ NSDictionary* attributeToMethodNameMap = nil;
if (!browserAccessibility_) if (!browserAccessibility_)
return nil; return nil;
// General attributes.
NSMutableArray* ret = [NSMutableArray arrayWithObjects:
@"AXUIElementCountForSearchPredicate",
@"AXUIElementsForSearchPredicate",
nil];
if ([[self role] isEqualToString:NSAccessibilityTableRole] || if ([[self role] isEqualToString:NSAccessibilityTableRole] ||
[[self role] isEqualToString:NSAccessibilityGridRole]) { [[self role] isEqualToString:NSAccessibilityGridRole]) {
return [NSArray arrayWithObjects: [ret addObjectsFromArray:[NSArray arrayWithObjects:
NSAccessibilityCellForColumnAndRowParameterizedAttribute, NSAccessibilityCellForColumnAndRowParameterizedAttribute,
nil]; nil]];
} }
if ([[self role] isEqualToString:NSAccessibilityTextFieldRole]) { if ([[self role] isEqualToString:NSAccessibilityTextFieldRole]) {
return [NSArray arrayWithObjects: [ret addObjectsFromArray:[NSArray arrayWithObjects:
NSAccessibilityLineForIndexParameterizedAttribute, NSAccessibilityLineForIndexParameterizedAttribute,
NSAccessibilityRangeForLineParameterizedAttribute, NSAccessibilityRangeForLineParameterizedAttribute,
NSAccessibilityStringForRangeParameterizedAttribute, NSAccessibilityStringForRangeParameterizedAttribute,
...@@ -1249,14 +1558,14 @@ NSDictionary* attributeToMethodNameMap = nil; ...@@ -1249,14 +1558,14 @@ NSDictionary* attributeToMethodNameMap = nil;
NSAccessibilityRTFForRangeParameterizedAttribute, NSAccessibilityRTFForRangeParameterizedAttribute,
NSAccessibilityAttributedStringForRangeParameterizedAttribute, NSAccessibilityAttributedStringForRangeParameterizedAttribute,
NSAccessibilityStyleRangeForIndexParameterizedAttribute, NSAccessibilityStyleRangeForIndexParameterizedAttribute,
nil]; nil]];
} }
if ([self internalRole] == ui::AX_ROLE_STATIC_TEXT) { if ([self internalRole] == ui::AX_ROLE_STATIC_TEXT) {
return [NSArray arrayWithObjects: [ret addObjectsFromArray:[NSArray arrayWithObjects:
NSAccessibilityBoundsForRangeParameterizedAttribute, NSAccessibilityBoundsForRangeParameterizedAttribute,
nil]; nil]];
} }
return nil; return ret;
} }
// Returns an array of action names that this object will respond to. // Returns an array of action names that this object will respond to.
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/accessibility/one_shot_accessibility_tree_search.h"
#include "base/i18n/case_conversion.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "content/browser/accessibility/browser_accessibility.h"
#include "content/browser/accessibility/browser_accessibility_manager.h"
namespace content {
// Given a node, populate a vector with all of the strings from that node's
// attributes that might be relevant for a text search.
void GetNodeStrings(BrowserAccessibility* node,
std::vector<base::string16>* strings) {
if (node->HasStringAttribute(ui::AX_ATTR_NAME))
strings->push_back(node->GetString16Attribute(ui::AX_ATTR_NAME));
if (node->HasStringAttribute(ui::AX_ATTR_DESCRIPTION))
strings->push_back(node->GetString16Attribute(ui::AX_ATTR_DESCRIPTION));
if (node->HasStringAttribute(ui::AX_ATTR_HELP))
strings->push_back(node->GetString16Attribute(ui::AX_ATTR_HELP));
if (node->HasStringAttribute(ui::AX_ATTR_VALUE))
strings->push_back(node->GetString16Attribute(ui::AX_ATTR_VALUE));
if (node->HasStringAttribute(ui::AX_ATTR_PLACEHOLDER))
strings->push_back(node->GetString16Attribute(ui::AX_ATTR_PLACEHOLDER));
}
OneShotAccessibilityTreeSearch::OneShotAccessibilityTreeSearch(
BrowserAccessibilityManager* tree)
: tree_(tree),
start_node_(nullptr),
direction_(OneShotAccessibilityTreeSearch::FORWARDS),
result_limit_(UNLIMITED_RESULTS),
immediate_descendants_only_(false),
visible_only_(false),
did_search_(false) {
}
OneShotAccessibilityTreeSearch::~OneShotAccessibilityTreeSearch() {
}
void OneShotAccessibilityTreeSearch::SetStartNode(
BrowserAccessibility* start_node) {
DCHECK(!did_search_);
start_node_ = start_node;
}
void OneShotAccessibilityTreeSearch::SetDirection(Direction direction) {
DCHECK(!did_search_);
direction_ = direction;
}
void OneShotAccessibilityTreeSearch::SetResultLimit(int result_limit) {
DCHECK(!did_search_);
result_limit_ = result_limit;
}
void OneShotAccessibilityTreeSearch::SetImmediateDescendantsOnly(
bool immediate_descendants_only) {
DCHECK(!did_search_);
immediate_descendants_only_ = immediate_descendants_only;
}
void OneShotAccessibilityTreeSearch::SetVisibleOnly(bool visible_only) {
DCHECK(!did_search_);
visible_only_ = visible_only;
}
void OneShotAccessibilityTreeSearch::SetSearchText(const std::string& text) {
DCHECK(!did_search_);
search_text_ = text;
}
void OneShotAccessibilityTreeSearch::AddPredicate(
AccessibilityMatchPredicate predicate) {
DCHECK(!did_search_);
predicates_.push_back(predicate);
}
size_t OneShotAccessibilityTreeSearch::CountMatches() {
if (!did_search_)
Search();
return matches_.size();
}
BrowserAccessibility* OneShotAccessibilityTreeSearch::GetMatchAtIndex(
size_t index) {
if (!did_search_)
Search();
CHECK(index < matches_.size());
return matches_[index];
}
void OneShotAccessibilityTreeSearch::Search()
{
if (immediate_descendants_only_) {
SearchByIteratingOverChildren();
} else {
SearchByWalkingTree();
}
}
void OneShotAccessibilityTreeSearch::SearchByIteratingOverChildren() {
if (!start_node_)
return;
for (unsigned i = 0;
i < start_node_->PlatformChildCount() &&
(result_limit_ == UNLIMITED_RESULTS ||
static_cast<int>(matches_.size()) < result_limit_);
++i) {
BrowserAccessibility* child = start_node_->PlatformGetChild(i);
if (Matches(child))
matches_.push_back(child);
}
}
void OneShotAccessibilityTreeSearch::SearchByWalkingTree() {
BrowserAccessibility* node = nullptr;
if (start_node_) {
if (direction_ == FORWARDS)
node = tree_->NextInTreeOrder(start_node_);
else
node = tree_->PreviousInTreeOrder(start_node_);
} else {
start_node_ = tree_->GetRoot();
node = start_node_;
}
while (node && (result_limit_ == UNLIMITED_RESULTS ||
static_cast<int>(matches_.size()) < result_limit_)) {
if (Matches(node))
matches_.push_back(node);
if (direction_ == FORWARDS)
node = tree_->NextInTreeOrder(node);
else
node = tree_->PreviousInTreeOrder(node);
}
}
bool OneShotAccessibilityTreeSearch::Matches(BrowserAccessibility* node) {
for (size_t i = 0; i < predicates_.size(); ++i) {
if (!predicates_[i](start_node_, node))
return false;
}
if (visible_only_) {
if (node->HasState(ui::AX_STATE_INVISIBLE) ||
node->HasState(ui::AX_STATE_OFFSCREEN)) {
return false;
}
}
if (!search_text_.empty()) {
base::string16 search_text_lower =
base::i18n::ToLower(base::UTF8ToUTF16(search_text_));
std::vector<base::string16> node_strings;
GetNodeStrings(node, &node_strings);
bool found_text_match = false;
for (size_t i = 0; i < node_strings.size(); ++i) {
base::string16 node_string_lower =
base::i18n::ToLower(node_strings[i]);
if (node_string_lower.find(search_text_lower) !=
base::string16::npos) {
found_text_match = true;
break;
}
}
if (!found_text_match)
return false;
}
return true;
}
} // namespace content
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_ACCESSIBILITY_ACCESSIBILITY_TREE_SEARCH_H_
#define CONTENT_BROWSER_ACCESSIBILITY_ACCESSIBILITY_TREE_SEARCH_H_
#include <string>
#include <vector>
#include "base/macros.h"
#include "content/common/content_export.h"
namespace content {
class BrowserAccessibility;
class BrowserAccessibilityManager;
// A function that returns whether or not a given node matches, given the
// start element of the search as an optional comparator.
typedef bool (*AccessibilityMatchPredicate)(
BrowserAccessibility* start_element,
BrowserAccessibility* this_element);
// This class provides an interface for searching the accessibility tree from
// a given starting node, with a few built-in options and allowing an arbitrary
// number of predicates that can be used to restrict the search.
//
// This class is meant to perform one search. Initialize it, then iterate
// over the matches, and then delete it.
//
// This class stores raw pointers to the matches in the tree! Don't keep this
// object around if the tree is mutating.
class CONTENT_EXPORT OneShotAccessibilityTreeSearch {
public:
enum Direction { FORWARDS, BACKWARDS };
const int UNLIMITED_RESULTS = -1;
OneShotAccessibilityTreeSearch(BrowserAccessibilityManager* tree);
virtual ~OneShotAccessibilityTreeSearch();
//
// Search parameters. All of these are optional.
//
// Sets the node where the search starts. The first potential match will
// be the one immediately following this one. This node will be used as
// the first arguement to any predicates.
void SetStartNode(BrowserAccessibility* start_node);
// Search forwards or backwards in an in-order traversal of the tree.
void SetDirection(Direction direction);
// Set the maximum number of results, or UNLIMITED_RESULTS
// for no limit (default).
void SetResultLimit(int result_limit);
// If true, only searches children of |start_node| and doesn't
// recurse.
void SetImmediateDescendantsOnly(bool immediate_descendants_only);
// If true, only considers nodes that aren't invisible or offscreen.
void SetVisibleOnly(bool visible_only);
// Restricts the matches to only nodes where |text| is found as a
// substring of any of that node's accessible text, including its
// name, description, or value. Case-insensitive.
void SetSearchText(const std::string& text);
// Restricts the matches to only those that satisfy all predicates.
void AddPredicate(AccessibilityMatchPredicate predicate);
//
// Calling either of these executes the search.
//
size_t CountMatches();
BrowserAccessibility* GetMatchAtIndex(size_t index);
private:
void Search();
void SearchByWalkingTree();
void SearchByIteratingOverChildren();
bool Matches(BrowserAccessibility* node);
BrowserAccessibilityManager* tree_;
BrowserAccessibility* start_node_;
Direction direction_;
int result_limit_;
bool immediate_descendants_only_;
bool visible_only_;
std::string search_text_;
std::vector<AccessibilityMatchPredicate> predicates_;
std::vector<BrowserAccessibility*> matches_;
bool did_search_;
DISALLOW_COPY_AND_ASSIGN(OneShotAccessibilityTreeSearch);
};
} // namespace content
#endif // CONTENT_BROWSER_ACCESSIBILITY_ACCESSIBILITY_TREE_SEARCH_H_
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/memory/scoped_ptr.h"
#include "content/browser/accessibility/browser_accessibility.h"
#include "content/browser/accessibility/browser_accessibility_manager.h"
#include "content/browser/accessibility/one_shot_accessibility_tree_search.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
class TestBrowserAccessibilityManager : public BrowserAccessibilityManager {
public:
TestBrowserAccessibilityManager(const ui::AXTreeUpdate& initial_tree)
: BrowserAccessibilityManager(initial_tree,
nullptr,
new BrowserAccessibilityFactory()) {}
};
} // namespace
class OneShotAccessibilityTreeSearchTest : public testing::Test {
public:
OneShotAccessibilityTreeSearchTest() {}
~OneShotAccessibilityTreeSearchTest() override {}
protected:
void SetUp() override;
scoped_ptr<BrowserAccessibilityManager> tree_;
private:
DISALLOW_COPY_AND_ASSIGN(OneShotAccessibilityTreeSearchTest);
};
void OneShotAccessibilityTreeSearchTest::SetUp() {
ui::AXNodeData root;
root.id = 1;
root.SetName("Document");
root.role = ui::AX_ROLE_ROOT_WEB_AREA;
root.state = 0;
root.child_ids.push_back(2);
root.child_ids.push_back(3);
root.child_ids.push_back(6);
ui::AXNodeData heading;
heading.id = 2;
heading.SetName("Heading");
heading.role = ui::AX_ROLE_HEADING;
heading.state = 0;
ui::AXNodeData list;
list.id = 3;
list.role = ui::AX_ROLE_LIST;
list.state = 0;
list.child_ids.push_back(4);
list.child_ids.push_back(5);
ui::AXNodeData list_item_1;
list_item_1.id = 4;
list_item_1.SetName("Autobots");
list_item_1.role = ui::AX_ROLE_LIST_ITEM;
list_item_1.state = 0;
ui::AXNodeData list_item_2;
list_item_2.id = 5;
list_item_2.SetName("Decepticons");
list_item_2.role = ui::AX_ROLE_LIST_ITEM;
list_item_2.state = 0;
ui::AXNodeData footer;
footer.id = 6;
footer.SetName("Footer");
footer.role = ui::AX_ROLE_FOOTER;
footer.state = 1 << ui::AX_STATE_OFFSCREEN;
tree_.reset(new TestBrowserAccessibilityManager(
MakeAXTreeUpdate(root, heading, list, list_item_1, list_item_2, footer)));
}
TEST_F(OneShotAccessibilityTreeSearchTest, GetAll) {
OneShotAccessibilityTreeSearch search(tree_.get());
ASSERT_EQ(6U, search.CountMatches());
}
TEST_F(OneShotAccessibilityTreeSearchTest, ForwardsWithStartNode) {
OneShotAccessibilityTreeSearch search(tree_.get());
search.SetStartNode(tree_->GetFromID(4));
ASSERT_EQ(2U, search.CountMatches());
EXPECT_EQ(5, search.GetMatchAtIndex(0)->GetId());
EXPECT_EQ(6, search.GetMatchAtIndex(1)->GetId());
}
TEST_F(OneShotAccessibilityTreeSearchTest, BackwardsWithStartNode) {
OneShotAccessibilityTreeSearch search(tree_.get());
search.SetStartNode(tree_->GetFromID(4));
search.SetDirection(OneShotAccessibilityTreeSearch::BACKWARDS);
ASSERT_EQ(3U, search.CountMatches());
EXPECT_EQ(3, search.GetMatchAtIndex(0)->GetId());
EXPECT_EQ(2, search.GetMatchAtIndex(1)->GetId());
EXPECT_EQ(1, search.GetMatchAtIndex(2)->GetId());
}
TEST_F(OneShotAccessibilityTreeSearchTest, ResultLimitZero) {
OneShotAccessibilityTreeSearch search(tree_.get());
search.SetResultLimit(0);
ASSERT_EQ(0U, search.CountMatches());
}
TEST_F(OneShotAccessibilityTreeSearchTest, ResultLimitFive) {
OneShotAccessibilityTreeSearch search(tree_.get());
search.SetResultLimit(5);
ASSERT_EQ(5U, search.CountMatches());
}
TEST_F(OneShotAccessibilityTreeSearchTest, DescendantsOnly) {
OneShotAccessibilityTreeSearch search(tree_.get());
search.SetStartNode(tree_->GetFromID(1));
search.SetImmediateDescendantsOnly(true);
ASSERT_EQ(3U, search.CountMatches());
EXPECT_EQ(2, search.GetMatchAtIndex(0)->GetId());
EXPECT_EQ(3, search.GetMatchAtIndex(1)->GetId());
EXPECT_EQ(6, search.GetMatchAtIndex(2)->GetId());
}
TEST_F(OneShotAccessibilityTreeSearchTest, VisibleOnly) {
OneShotAccessibilityTreeSearch search(tree_.get());
search.SetVisibleOnly(true);
ASSERT_EQ(5U, search.CountMatches());
EXPECT_EQ(1, search.GetMatchAtIndex(0)->GetId());
EXPECT_EQ(2, search.GetMatchAtIndex(1)->GetId());
EXPECT_EQ(3, search.GetMatchAtIndex(2)->GetId());
EXPECT_EQ(4, search.GetMatchAtIndex(3)->GetId());
EXPECT_EQ(5, search.GetMatchAtIndex(4)->GetId());
}
TEST_F(OneShotAccessibilityTreeSearchTest, CaseInsensitiveStringMatch) {
OneShotAccessibilityTreeSearch search(tree_.get());
search.SetSearchText("eCEptiCOn");
ASSERT_EQ(1U, search.CountMatches());
EXPECT_EQ(5, search.GetMatchAtIndex(0)->GetId());
}
TEST_F(OneShotAccessibilityTreeSearchTest, OnePredicate) {
OneShotAccessibilityTreeSearch search(tree_.get());
search.AddPredicate([](BrowserAccessibility* start,
BrowserAccessibility* current) {
return current->GetRole() == ui::AX_ROLE_LIST_ITEM;
});
ASSERT_EQ(2U, search.CountMatches());
EXPECT_EQ(4, search.GetMatchAtIndex(0)->GetId());
EXPECT_EQ(5, search.GetMatchAtIndex(1)->GetId());
}
TEST_F(OneShotAccessibilityTreeSearchTest, TwoPredicates) {
OneShotAccessibilityTreeSearch search(tree_.get());
search.AddPredicate([](BrowserAccessibility* start,
BrowserAccessibility* current) {
return (current->GetRole() == ui::AX_ROLE_LIST ||
current->GetRole() == ui::AX_ROLE_LIST_ITEM);
});
search.AddPredicate([](BrowserAccessibility* start,
BrowserAccessibility* current) {
return (current->GetId() % 2 == 1);
});
ASSERT_EQ(2U, search.CountMatches());
EXPECT_EQ(3, search.GetMatchAtIndex(0)->GetId());
EXPECT_EQ(5, search.GetMatchAtIndex(1)->GetId());
}
} // namespace content
...@@ -315,6 +315,8 @@ ...@@ -315,6 +315,8 @@
'browser/accessibility/browser_accessibility_state_impl_win.cc', 'browser/accessibility/browser_accessibility_state_impl_win.cc',
'browser/accessibility/browser_accessibility_win.cc', 'browser/accessibility/browser_accessibility_win.cc',
'browser/accessibility/browser_accessibility_win.h', 'browser/accessibility/browser_accessibility_win.h',
'browser/accessibility/one_shot_accessibility_tree_search.cc',
'browser/accessibility/one_shot_accessibility_tree_search.h',
'browser/android/animation_utils.h', 'browser/android/animation_utils.h',
'browser/android/browser_jni_registrar.cc', 'browser/android/browser_jni_registrar.cc',
'browser/android/browser_jni_registrar.h', 'browser/android/browser_jni_registrar.h',
......
...@@ -315,6 +315,7 @@ ...@@ -315,6 +315,7 @@
'browser/accessibility/browser_accessibility_mac_unittest.mm', 'browser/accessibility/browser_accessibility_mac_unittest.mm',
'browser/accessibility/browser_accessibility_manager_unittest.cc', 'browser/accessibility/browser_accessibility_manager_unittest.cc',
'browser/accessibility/browser_accessibility_win_unittest.cc', 'browser/accessibility/browser_accessibility_win_unittest.cc',
'browser/accessibility/one_shot_accessibility_tree_search_unittest.cc',
'browser/appcache/appcache_database_unittest.cc', 'browser/appcache/appcache_database_unittest.cc',
'browser/appcache/appcache_disk_cache_unittest.cc', 'browser/appcache/appcache_disk_cache_unittest.cc',
'browser/appcache/appcache_group_unittest.cc', 'browser/appcache/appcache_group_unittest.cc',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment