blob: 4a0d4bc9c6802510ca6e23d5307ab8eff95f0fcd [file] [log] [blame]
// 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 <stdint.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"
#include "ui/accessibility/ax_enums.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_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(
BrowserAccessibility* scope)
: tree_(scope->manager()),
scope_node_(scope),
start_node_(scope),
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_);
CHECK(start_node);
if (!scope_node_->PlatformGetParent() ||
start_node->IsDescendantOf(scope_node_->PlatformGetParent())) {
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();
}
did_search_ = true;
}
void OneShotAccessibilityTreeSearch::SearchByIteratingOverChildren() {
// Iterate over the children of scope_node_.
// If start_node_ is specified, iterate over the first child past that
// node.
uint32_t count = scope_node_->PlatformChildCount();
if (count == 0)
return;
// We only care about immediate children of scope_node_, so walk up
// start_node_ until we get to an immediate child. If it isn't a child,
// we ignore start_node_.
while (start_node_ && start_node_->PlatformGetParent() != scope_node_)
start_node_ = start_node_->PlatformGetParent();
uint32_t index = (direction_ == FORWARDS ? 0 : count - 1);
if (start_node_) {
index = start_node_->GetIndexInParent();
if (direction_ == FORWARDS)
index++;
else
index--;
}
while (index < count &&
(result_limit_ == UNLIMITED_RESULTS ||
static_cast<int>(matches_.size()) < result_limit_)) {
BrowserAccessibility* node = scope_node_->PlatformGetChild(index);
if (Matches(node))
matches_.push_back(node);
if (direction_ == FORWARDS)
index++;
else
index--;
}
}
void OneShotAccessibilityTreeSearch::SearchByWalkingTree() {
BrowserAccessibility* node = nullptr;
node = start_node_;
if (node != scope_node_ || result_limit_ == 1) {
if (direction_ == FORWARDS)
node = tree_->NextInTreeOrder(start_node_);
else
node = tree_->PreviousInTreeOrder(start_node_);
}
BrowserAccessibility* stop_node = scope_node_->PlatformGetParent();
while (node &&
node != stop_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;
}
//
// Predicates
//
bool AccessibilityArticlePredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return node->GetRole() == ui::AX_ROLE_ARTICLE;
}
bool AccessibilityButtonPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
switch (node->GetRole()) {
case ui::AX_ROLE_BUTTON:
case ui::AX_ROLE_MENU_BUTTON:
case ui::AX_ROLE_POP_UP_BUTTON:
case ui::AX_ROLE_SWITCH:
case ui::AX_ROLE_TOGGLE_BUTTON:
return true;
default:
return false;
}
}
bool AccessibilityBlockquotePredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return node->GetRole() == ui::AX_ROLE_BLOCKQUOTE;
}
bool AccessibilityCheckboxPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return (node->GetRole() == ui::AX_ROLE_CHECK_BOX ||
node->GetRole() == ui::AX_ROLE_MENU_ITEM_CHECK_BOX);
}
bool AccessibilityComboboxPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return (node->GetRole() == ui::AX_ROLE_COMBO_BOX ||
node->GetRole() == ui::AX_ROLE_POP_UP_BUTTON);
}
bool AccessibilityControlPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
if (node->IsControl())
return true;
if (node->HasState(ui::AX_STATE_FOCUSABLE) &&
node->GetRole() != ui::AX_ROLE_IFRAME &&
node->GetRole() != ui::AX_ROLE_IFRAME_PRESENTATIONAL &&
node->GetRole() != ui::AX_ROLE_IMAGE_MAP_LINK &&
node->GetRole() != ui::AX_ROLE_LINK &&
node->GetRole() != ui::AX_ROLE_WEB_AREA &&
node->GetRole() != ui::AX_ROLE_ROOT_WEB_AREA) {
return true;
}
return false;
}
bool AccessibilityFocusablePredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
bool focusable = node->HasState(ui::AX_STATE_FOCUSABLE);
if (node->GetRole() == ui::AX_ROLE_IFRAME ||
node->GetRole() == ui::AX_ROLE_IFRAME_PRESENTATIONAL ||
node->GetRole() == ui::AX_ROLE_WEB_AREA ||
node->GetRole() == ui::AX_ROLE_ROOT_WEB_AREA) {
focusable = false;
}
return focusable;
}
bool AccessibilityGraphicPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return node->GetRole() == ui::AX_ROLE_IMAGE;
}
bool AccessibilityHeadingPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return (node->GetRole() == ui::AX_ROLE_HEADING);
}
bool AccessibilityH1Predicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return (node->GetRole() == ui::AX_ROLE_HEADING &&
node->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) == 1);
}
bool AccessibilityH2Predicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return (node->GetRole() == ui::AX_ROLE_HEADING &&
node->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) == 2);
}
bool AccessibilityH3Predicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return (node->GetRole() == ui::AX_ROLE_HEADING &&
node->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) == 3);
}
bool AccessibilityH4Predicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return (node->GetRole() == ui::AX_ROLE_HEADING &&
node->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) == 4);
}
bool AccessibilityH5Predicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return (node->GetRole() == ui::AX_ROLE_HEADING &&
node->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) == 5);
}
bool AccessibilityH6Predicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return (node->GetRole() == ui::AX_ROLE_HEADING &&
node->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) == 6);
}
bool AccessibilityHeadingSameLevelPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return (node->GetRole() == ui::AX_ROLE_HEADING &&
start->GetRole() == ui::AX_ROLE_HEADING &&
(node->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL) ==
start->GetIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL)));
}
bool AccessibilityFramePredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
if (node->IsWebAreaForPresentationalIframe())
return false;
if (!node->PlatformGetParent())
return false;
return (node->GetRole() == ui::AX_ROLE_WEB_AREA ||
node->GetRole() == ui::AX_ROLE_ROOT_WEB_AREA);
}
bool AccessibilityLandmarkPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
switch (node->GetRole()) {
case ui::AX_ROLE_APPLICATION:
case ui::AX_ROLE_ARTICLE:
case ui::AX_ROLE_BANNER:
case ui::AX_ROLE_COMPLEMENTARY:
case ui::AX_ROLE_CONTENT_INFO:
case ui::AX_ROLE_MAIN:
case ui::AX_ROLE_NAVIGATION:
case ui::AX_ROLE_SEARCH:
case ui::AX_ROLE_REGION:
return true;
default:
return false;
}
}
bool AccessibilityLinkPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return (node->GetRole() == ui::AX_ROLE_LINK ||
node->GetRole() == ui::AX_ROLE_IMAGE_MAP_LINK);
}
bool AccessibilityListPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return (node->GetRole() == ui::AX_ROLE_LIST_BOX ||
node->GetRole() == ui::AX_ROLE_LIST ||
node->GetRole() == ui::AX_ROLE_DESCRIPTION_LIST);
}
bool AccessibilityListItemPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return (node->GetRole() == ui::AX_ROLE_LIST_ITEM ||
node->GetRole() == ui::AX_ROLE_DESCRIPTION_LIST_TERM ||
node->GetRole() == ui::AX_ROLE_LIST_BOX_OPTION);
}
bool AccessibilityLiveRegionPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return node->HasStringAttribute(ui::AX_ATTR_LIVE_STATUS);
}
bool AccessibilityMainPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return (node->GetRole() == ui::AX_ROLE_MAIN);
}
bool AccessibilityMediaPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
const std::string& tag = node->GetStringAttribute(ui::AX_ATTR_HTML_TAG);
return tag == "audio" || tag == "video";
}
bool AccessibilityRadioButtonPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return (node->GetRole() == ui::AX_ROLE_RADIO_BUTTON ||
node->GetRole() == ui::AX_ROLE_MENU_ITEM_RADIO);
}
bool AccessibilityRadioGroupPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return node->GetRole() == ui::AX_ROLE_RADIO_GROUP;
}
bool AccessibilityTablePredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return (node->GetRole() == ui::AX_ROLE_TABLE ||
node->GetRole() == ui::AX_ROLE_GRID);
}
bool AccessibilityTextfieldPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return (node->IsSimpleTextControl() || node->IsRichTextControl());
}
bool AccessibilityTextStyleBoldPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
int32_t style = node->GetIntAttribute(ui::AX_ATTR_TEXT_STYLE);
return 0 != (style & ui::AX_TEXT_STYLE_BOLD);
}
bool AccessibilityTextStyleItalicPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
int32_t style = node->GetIntAttribute(ui::AX_ATTR_TEXT_STYLE);
return 0 != (style & ui::AX_TEXT_STYLE_BOLD);
}
bool AccessibilityTextStyleUnderlinePredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
int32_t style = node->GetIntAttribute(ui::AX_ATTR_TEXT_STYLE);
return 0 != (style & ui::AX_TEXT_STYLE_UNDERLINE);
}
bool AccessibilityTreePredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return (node->IsSimpleTextControl() || node->IsRichTextControl());
}
bool AccessibilityUnvisitedLinkPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return ((node->GetRole() == ui::AX_ROLE_LINK ||
node->GetRole() == ui::AX_ROLE_IMAGE_MAP_LINK) &&
!node->HasState(ui::AX_STATE_VISITED));
}
bool AccessibilityVisitedLinkPredicate(
BrowserAccessibility* start, BrowserAccessibility* node) {
return ((node->GetRole() == ui::AX_ROLE_LINK ||
node->GetRole() == ui::AX_ROLE_IMAGE_MAP_LINK) &&
node->HasState(ui::AX_STATE_VISITED));
}
} // namespace content