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;
......
// 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