| // Copyright (c) 2012 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/browser_accessibility.h" |
| |
| #include <algorithm> |
| |
| #include "base/logging.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "content/browser/accessibility/browser_accessibility_manager.h" |
| #include "content/common/accessibility_messages.h" |
| #include "ui/accessibility/ax_text_utils.h" |
| |
| namespace content { |
| |
| #if !defined(OS_WIN) && \ |
| !defined(OS_MACOSX) && \ |
| !defined(OS_ANDROID) && \ |
| !(defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(USE_X11)) |
| // We have subclassess of BrowserAccessibility on some platforms. |
| // On other platforms, instantiate the base class. |
| // static |
| BrowserAccessibility* BrowserAccessibility::Create() { |
| return new BrowserAccessibility(); |
| } |
| #endif |
| |
| BrowserAccessibility::BrowserAccessibility() |
| : manager_(NULL), |
| node_(NULL) { |
| } |
| |
| BrowserAccessibility::~BrowserAccessibility() { |
| } |
| |
| void BrowserAccessibility::Init(BrowserAccessibilityManager* manager, |
| ui::AXNode* node) { |
| manager_ = manager; |
| node_ = node; |
| } |
| |
| bool BrowserAccessibility::PlatformIsLeaf() const { |
| if (InternalChildCount() == 0) |
| return true; |
| |
| // All of these roles may have children that we use as internal |
| // implementation details, but we want to expose them as leaves |
| // to platform accessibility APIs. |
| switch (GetRole()) { |
| case ui::AX_ROLE_COMBO_BOX: |
| case ui::AX_ROLE_LINE_BREAK: |
| case ui::AX_ROLE_SLIDER: |
| case ui::AX_ROLE_STATIC_TEXT: |
| case ui::AX_ROLE_TEXT_FIELD: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| uint32 BrowserAccessibility::PlatformChildCount() const { |
| if (HasIntAttribute(ui::AX_ATTR_CHILD_TREE_ID)) { |
| BrowserAccessibilityManager* child_manager = |
| BrowserAccessibilityManager::FromID( |
| GetIntAttribute(ui::AX_ATTR_CHILD_TREE_ID)); |
| if (child_manager) |
| return 1; |
| |
| return 0; |
| } |
| |
| return PlatformIsLeaf() ? 0 : InternalChildCount(); |
| } |
| |
| bool BrowserAccessibility::IsNative() const { |
| return false; |
| } |
| |
| bool BrowserAccessibility::IsDescendantOf( |
| const BrowserAccessibility* ancestor) const { |
| if (this == ancestor) { |
| return true; |
| } else if (GetParent()) { |
| return GetParent()->IsDescendantOf(ancestor); |
| } |
| |
| return false; |
| } |
| |
| bool BrowserAccessibility::IsTextOnlyObject() const { |
| return GetRole() == ui::AX_ROLE_STATIC_TEXT || |
| GetRole() == ui::AX_ROLE_LINE_BREAK; |
| } |
| |
| BrowserAccessibility* BrowserAccessibility::PlatformGetChild( |
| uint32 child_index) const { |
| DCHECK(child_index < PlatformChildCount()); |
| BrowserAccessibility* result = nullptr; |
| |
| if (HasIntAttribute(ui::AX_ATTR_CHILD_TREE_ID)) { |
| BrowserAccessibilityManager* child_manager = |
| BrowserAccessibilityManager::FromID( |
| GetIntAttribute(ui::AX_ATTR_CHILD_TREE_ID)); |
| if (child_manager) |
| result = child_manager->GetRoot(); |
| } else { |
| result = InternalGetChild(child_index); |
| } |
| |
| return result; |
| } |
| |
| bool BrowserAccessibility::PlatformIsChildOfLeaf() const { |
| BrowserAccessibility* ancestor = GetParent(); |
| while (ancestor) { |
| if (ancestor->PlatformIsLeaf()) |
| return true; |
| ancestor = ancestor->GetParent(); |
| } |
| |
| return false; |
| } |
| |
| BrowserAccessibility* BrowserAccessibility::GetPreviousSibling() const { |
| if (GetParent() && GetIndexInParent() > 0) |
| return GetParent()->InternalGetChild(GetIndexInParent() - 1); |
| |
| return nullptr; |
| } |
| |
| BrowserAccessibility* BrowserAccessibility::GetNextSibling() const { |
| if (GetParent() && |
| GetIndexInParent() >= 0 && |
| GetIndexInParent() < static_cast<int>( |
| GetParent()->InternalChildCount() - 1)) { |
| return GetParent()->InternalGetChild(GetIndexInParent() + 1); |
| } |
| |
| return nullptr; |
| } |
| |
| BrowserAccessibility* BrowserAccessibility::PlatformDeepestFirstChild() const { |
| if (!PlatformChildCount()) |
| return nullptr; |
| |
| auto deepest_child = PlatformGetChild(0); |
| while (deepest_child->PlatformChildCount()) |
| deepest_child = deepest_child->PlatformGetChild(0); |
| |
| return deepest_child; |
| } |
| |
| BrowserAccessibility* BrowserAccessibility::PlatformDeepestLastChild() const { |
| if (!PlatformChildCount()) |
| return nullptr; |
| |
| auto deepest_child = PlatformGetChild(PlatformChildCount() - 1); |
| while (deepest_child->PlatformChildCount()) |
| deepest_child = deepest_child->PlatformGetChild(PlatformChildCount() - 1); |
| |
| return deepest_child; |
| } |
| |
| uint32 BrowserAccessibility::InternalChildCount() const { |
| if (!node_ || !manager_) |
| return 0; |
| return static_cast<uint32>(node_->child_count()); |
| } |
| |
| BrowserAccessibility* BrowserAccessibility::InternalGetChild( |
| uint32 child_index) const { |
| if (!node_ || !manager_) |
| return NULL; |
| return manager_->GetFromAXNode(node_->ChildAtIndex(child_index)); |
| } |
| |
| BrowserAccessibility* BrowserAccessibility::GetParent() const { |
| if (!node_ || !manager_) |
| return NULL; |
| ui::AXNode* parent = node_->parent(); |
| if (parent) |
| return manager_->GetFromAXNode(parent); |
| |
| return manager_->GetParentNodeFromParentTree(); |
| } |
| |
| int32 BrowserAccessibility::GetIndexInParent() const { |
| return node_ ? node_->index_in_parent() : -1; |
| } |
| |
| int32 BrowserAccessibility::GetId() const { |
| return node_ ? node_->id() : -1; |
| } |
| |
| const ui::AXNodeData& BrowserAccessibility::GetData() const { |
| CR_DEFINE_STATIC_LOCAL(ui::AXNodeData, empty_data, ()); |
| if (node_) |
| return node_->data(); |
| else |
| return empty_data; |
| } |
| |
| gfx::Rect BrowserAccessibility::GetLocation() const { |
| return GetData().location; |
| } |
| |
| int32 BrowserAccessibility::GetRole() const { |
| return GetData().role; |
| } |
| |
| int32 BrowserAccessibility::GetState() const { |
| return GetData().state; |
| } |
| |
| const BrowserAccessibility::HtmlAttributes& |
| BrowserAccessibility::GetHtmlAttributes() const { |
| return GetData().html_attributes; |
| } |
| |
| gfx::Rect BrowserAccessibility::GetLocalBoundsRect() const { |
| gfx::Rect bounds = GetLocation(); |
| FixEmptyBounds(&bounds); |
| return ElementBoundsToLocalBounds(bounds); |
| } |
| |
| gfx::Rect BrowserAccessibility::GetGlobalBoundsRect() const { |
| gfx::Rect bounds = GetLocalBoundsRect(); |
| |
| // Adjust the bounds by the top left corner of the containing view's bounds |
| // in screen coordinates. |
| bounds.Offset(manager_->GetViewBounds().OffsetFromOrigin()); |
| |
| return bounds; |
| } |
| |
| gfx::Rect BrowserAccessibility::GetLocalBoundsForRange(int start, int len) |
| const { |
| if (GetRole() != ui::AX_ROLE_STATIC_TEXT) { |
| // Apply recursively to all static text descendants. For example, if |
| // you call it on a div with two text node children, it just calls |
| // GetLocalBoundsForRange on each of the two children (adjusting |
| // |start| for each one) and unions the resulting rects. |
| gfx::Rect bounds; |
| for (size_t i = 0; i < InternalChildCount(); ++i) { |
| BrowserAccessibility* child = InternalGetChild(i); |
| int child_len = child->GetStaticTextLenRecursive(); |
| if (start < child_len && start + len > 0) { |
| gfx::Rect child_rect = child->GetLocalBoundsForRange(start, len); |
| bounds.Union(child_rect); |
| } |
| start -= child_len; |
| } |
| return ElementBoundsToLocalBounds(bounds); |
| } |
| |
| int end = start + len; |
| int child_start = 0; |
| int child_end = 0; |
| |
| gfx::Rect bounds; |
| for (size_t i = 0; i < InternalChildCount() && child_end < start + len; ++i) { |
| BrowserAccessibility* child = InternalGetChild(i); |
| if (child->GetRole() != ui::AX_ROLE_INLINE_TEXT_BOX) { |
| DLOG(WARNING) << "BrowserAccessibility objects with role STATIC_TEXT " << |
| "should have children of role INLINE_TEXT_BOX."; |
| continue; |
| } |
| |
| std::string child_text; |
| child->GetStringAttribute(ui::AX_ATTR_VALUE, &child_text); |
| int child_len = static_cast<int>(child_text.size()); |
| child_start = child_end; |
| child_end += child_len; |
| |
| if (child_end < start) |
| continue; |
| |
| int overlap_start = std::max(start, child_start); |
| int overlap_end = std::min(end, child_end); |
| |
| int local_start = overlap_start - child_start; |
| int local_end = overlap_end - child_start; |
| |
| gfx::Rect child_rect = child->GetLocation(); |
| int text_direction = child->GetIntAttribute( |
| ui::AX_ATTR_TEXT_DIRECTION); |
| const std::vector<int32>& character_offsets = child->GetIntListAttribute( |
| ui::AX_ATTR_CHARACTER_OFFSETS); |
| int start_pixel_offset = |
| local_start > 0 ? character_offsets[local_start - 1] : 0; |
| int end_pixel_offset = |
| local_end > 0 ? character_offsets[local_end - 1] : 0; |
| |
| gfx::Rect child_overlap_rect; |
| switch (text_direction) { |
| case ui::AX_TEXT_DIRECTION_NONE: |
| case ui::AX_TEXT_DIRECTION_LTR: { |
| int left = child_rect.x() + start_pixel_offset; |
| int right = child_rect.x() + end_pixel_offset; |
| child_overlap_rect = gfx::Rect(left, child_rect.y(), |
| right - left, child_rect.height()); |
| break; |
| } |
| case ui::AX_TEXT_DIRECTION_RTL: { |
| int right = child_rect.right() - start_pixel_offset; |
| int left = child_rect.right() - end_pixel_offset; |
| child_overlap_rect = gfx::Rect(left, child_rect.y(), |
| right - left, child_rect.height()); |
| break; |
| } |
| case ui::AX_TEXT_DIRECTION_TTB: { |
| int top = child_rect.y() + start_pixel_offset; |
| int bottom = child_rect.y() + end_pixel_offset; |
| child_overlap_rect = gfx::Rect(child_rect.x(), top, |
| child_rect.width(), bottom - top); |
| break; |
| } |
| case ui::AX_TEXT_DIRECTION_BTT: { |
| int bottom = child_rect.bottom() - start_pixel_offset; |
| int top = child_rect.bottom() - end_pixel_offset; |
| child_overlap_rect = gfx::Rect(child_rect.x(), top, |
| child_rect.width(), bottom - top); |
| break; |
| } |
| default: |
| NOTREACHED(); |
| } |
| |
| if (bounds.width() == 0 && bounds.height() == 0) |
| bounds = child_overlap_rect; |
| else |
| bounds.Union(child_overlap_rect); |
| } |
| |
| return ElementBoundsToLocalBounds(bounds); |
| } |
| |
| gfx::Rect BrowserAccessibility::GetGlobalBoundsForRange(int start, int len) |
| const { |
| gfx::Rect bounds = GetLocalBoundsForRange(start, len); |
| |
| // Adjust the bounds by the top left corner of the containing view's bounds |
| // in screen coordinates. |
| bounds.Offset(manager_->GetViewBounds().OffsetFromOrigin()); |
| |
| return bounds; |
| } |
| |
| int BrowserAccessibility::GetWordStartBoundary( |
| int start, ui::TextBoundaryDirection direction) const { |
| DCHECK_GE(start, -1); |
| // Special offset that indicates that a word boundary has not been found. |
| int word_start_not_found = GetStaticTextLenRecursive(); |
| int word_start = word_start_not_found; |
| |
| switch (GetRole()) { |
| case ui::AX_ROLE_STATIC_TEXT: { |
| int prev_word_start = word_start_not_found; |
| int child_start = 0; |
| int child_end = 0; |
| |
| // Go through the inline text boxes. |
| for (size_t i = 0; i < InternalChildCount(); ++i) { |
| // The next child starts where the previous one ended. |
| child_start = child_end; |
| BrowserAccessibility* child = InternalGetChild(i); |
| DCHECK_EQ(child->GetRole(), ui::AX_ROLE_INLINE_TEXT_BOX); |
| const std::string& child_text = child->GetStringAttribute( |
| ui::AX_ATTR_VALUE); |
| int child_len = static_cast<int>(child_text.size()); |
| child_end += child_len; // End is one past the last character. |
| |
| const std::vector<int32>& word_starts = child->GetIntListAttribute( |
| ui::AX_ATTR_WORD_STARTS); |
| if (word_starts.empty()) { |
| word_start = child_end; |
| continue; |
| } |
| |
| int local_start = start - child_start; |
| std::vector<int32>::const_iterator iter = std::upper_bound( |
| word_starts.begin(), word_starts.end(), local_start); |
| if (iter != word_starts.end()) { |
| if (direction == ui::FORWARDS_DIRECTION) { |
| word_start = child_start + *iter; |
| } else if (direction == ui::BACKWARDS_DIRECTION) { |
| if (iter == word_starts.begin()) { |
| // Return the position of the last word in the previous child. |
| word_start = prev_word_start; |
| } else { |
| word_start = child_start + *(iter - 1); |
| } |
| } else { |
| NOTREACHED(); |
| } |
| break; |
| } |
| |
| // No word start that is greater than the requested offset has been |
| // found. |
| prev_word_start = child_start + *(iter - 1); |
| if (direction == ui::FORWARDS_DIRECTION) { |
| word_start = child_end; |
| } else if (direction == ui::BACKWARDS_DIRECTION) { |
| word_start = prev_word_start; |
| } else { |
| NOTREACHED(); |
| } |
| } |
| return word_start; |
| } |
| |
| case ui::AX_ROLE_LINE_BREAK: |
| // Words never start at a line break. |
| return word_start_not_found; |
| |
| default: |
| // If there are no children, the word start boundary is still unknown or |
| // found previously depending on the direction. |
| if (!InternalChildCount()) |
| return word_start_not_found; |
| |
| int child_start = 0; |
| for (size_t i = 0; i < InternalChildCount(); ++i) { |
| BrowserAccessibility* child = InternalGetChild(i); |
| int child_len = child->GetStaticTextLenRecursive(); |
| int child_word_start = child->GetWordStartBoundary(start, direction); |
| if (child_word_start < child_len) { |
| // We have found a possible word boundary. |
| word_start = child_start + child_word_start; |
| } |
| |
| // Decide when to stop searching. |
| if ((word_start != word_start_not_found && |
| direction == ui::FORWARDS_DIRECTION) || |
| (start < child_len && |
| direction == ui::BACKWARDS_DIRECTION)) { |
| break; |
| } |
| |
| child_start += child_len; |
| if (start >= child_len) |
| start -= child_len; |
| else |
| start = -1; |
| } |
| return word_start; |
| } |
| } |
| |
| BrowserAccessibility* BrowserAccessibility::BrowserAccessibilityForPoint( |
| const gfx::Point& point) { |
| // The best result found that's a child of this object. |
| BrowserAccessibility* child_result = NULL; |
| // The best result that's an indirect descendant like grandchild, etc. |
| BrowserAccessibility* descendant_result = NULL; |
| |
| // Walk the children recursively looking for the BrowserAccessibility that |
| // most tightly encloses the specified point. Walk backwards so that in |
| // the absence of any other information, we assume the object that occurs |
| // later in the tree is on top of one that comes before it. |
| for (int i = static_cast<int>(PlatformChildCount()) - 1; i >= 0; --i) { |
| BrowserAccessibility* child = PlatformGetChild(i); |
| |
| // Skip table columns because cells are only contained in rows, |
| // not columns. |
| if (child->GetRole() == ui::AX_ROLE_COLUMN) |
| continue; |
| |
| if (child->GetGlobalBoundsRect().Contains(point)) { |
| BrowserAccessibility* result = child->BrowserAccessibilityForPoint(point); |
| if (result == child && !child_result) |
| child_result = result; |
| if (result != child && !descendant_result) |
| descendant_result = result; |
| } |
| |
| if (child_result && descendant_result) |
| break; |
| } |
| |
| // Explanation of logic: it's possible that this point overlaps more than |
| // one child of this object. If so, as a heuristic we prefer if the point |
| // overlaps a descendant of one of the two children and not the other. |
| // As an example, suppose you have two rows of buttons - the buttons don't |
| // overlap, but the rows do. Without this heuristic, we'd greedily only |
| // consider one of the containers. |
| if (descendant_result) |
| return descendant_result; |
| if (child_result) |
| return child_result; |
| |
| return this; |
| } |
| |
| void BrowserAccessibility::Destroy() { |
| // Allow the object to fire a TextRemoved notification. |
| manager_->NotifyAccessibilityEvent(ui::AX_EVENT_HIDE, this); |
| node_ = NULL; |
| manager_ = NULL; |
| |
| NativeReleaseReference(); |
| } |
| |
| void BrowserAccessibility::NativeReleaseReference() { |
| delete this; |
| } |
| |
| bool BrowserAccessibility::HasBoolAttribute( |
| ui::AXBoolAttribute attribute) const { |
| return GetData().HasBoolAttribute(attribute); |
| } |
| |
| bool BrowserAccessibility::GetBoolAttribute( |
| ui::AXBoolAttribute attribute) const { |
| return GetData().GetBoolAttribute(attribute); |
| } |
| |
| bool BrowserAccessibility::GetBoolAttribute( |
| ui::AXBoolAttribute attribute, bool* value) const { |
| return GetData().GetBoolAttribute(attribute, value); |
| } |
| |
| bool BrowserAccessibility::HasFloatAttribute( |
| ui::AXFloatAttribute attribute) const { |
| return GetData().HasFloatAttribute(attribute); |
| } |
| |
| float BrowserAccessibility::GetFloatAttribute( |
| ui::AXFloatAttribute attribute) const { |
| return GetData().GetFloatAttribute(attribute); |
| } |
| |
| bool BrowserAccessibility::GetFloatAttribute( |
| ui::AXFloatAttribute attribute, float* value) const { |
| return GetData().GetFloatAttribute(attribute, value); |
| } |
| |
| bool BrowserAccessibility::HasIntAttribute( |
| ui::AXIntAttribute attribute) const { |
| return GetData().HasIntAttribute(attribute); |
| } |
| |
| int BrowserAccessibility::GetIntAttribute(ui::AXIntAttribute attribute) const { |
| return GetData().GetIntAttribute(attribute); |
| } |
| |
| bool BrowserAccessibility::GetIntAttribute( |
| ui::AXIntAttribute attribute, int* value) const { |
| return GetData().GetIntAttribute(attribute, value); |
| } |
| |
| bool BrowserAccessibility::HasStringAttribute( |
| ui::AXStringAttribute attribute) const { |
| return GetData().HasStringAttribute(attribute); |
| } |
| |
| const std::string& BrowserAccessibility::GetStringAttribute( |
| ui::AXStringAttribute attribute) const { |
| return GetData().GetStringAttribute(attribute); |
| } |
| |
| bool BrowserAccessibility::GetStringAttribute( |
| ui::AXStringAttribute attribute, std::string* value) const { |
| return GetData().GetStringAttribute(attribute, value); |
| } |
| |
| base::string16 BrowserAccessibility::GetString16Attribute( |
| ui::AXStringAttribute attribute) const { |
| return GetData().GetString16Attribute(attribute); |
| } |
| |
| bool BrowserAccessibility::GetString16Attribute( |
| ui::AXStringAttribute attribute, |
| base::string16* value) const { |
| return GetData().GetString16Attribute(attribute, value); |
| } |
| |
| bool BrowserAccessibility::HasIntListAttribute( |
| ui::AXIntListAttribute attribute) const { |
| return GetData().HasIntListAttribute(attribute); |
| } |
| |
| const std::vector<int32>& BrowserAccessibility::GetIntListAttribute( |
| ui::AXIntListAttribute attribute) const { |
| return GetData().GetIntListAttribute(attribute); |
| } |
| |
| bool BrowserAccessibility::GetIntListAttribute( |
| ui::AXIntListAttribute attribute, |
| std::vector<int32>* value) const { |
| return GetData().GetIntListAttribute(attribute, value); |
| } |
| |
| bool BrowserAccessibility::GetHtmlAttribute( |
| const char* html_attr, std::string* value) const { |
| return GetData().GetHtmlAttribute(html_attr, value); |
| } |
| |
| bool BrowserAccessibility::GetHtmlAttribute( |
| const char* html_attr, base::string16* value) const { |
| return GetData().GetHtmlAttribute(html_attr, value); |
| } |
| |
| bool BrowserAccessibility::GetAriaTristate( |
| const char* html_attr, |
| bool* is_defined, |
| bool* is_mixed) const { |
| *is_defined = false; |
| *is_mixed = false; |
| |
| base::string16 value; |
| if (!GetHtmlAttribute(html_attr, &value) || |
| value.empty() || |
| base::EqualsASCII(value, "undefined")) { |
| return false; // Not set (and *is_defined is also false) |
| } |
| |
| *is_defined = true; |
| |
| if (base::EqualsASCII(value, "true")) |
| return true; |
| |
| if (base::EqualsASCII(value, "mixed")) |
| *is_mixed = true; |
| |
| return false; // Not set |
| } |
| |
| bool BrowserAccessibility::HasState(ui::AXState state_enum) const { |
| return (GetState() >> state_enum) & 1; |
| } |
| |
| bool BrowserAccessibility::IsCellOrTableHeaderRole() const { |
| return (GetRole() == ui::AX_ROLE_CELL || |
| GetRole() == ui::AX_ROLE_COLUMN_HEADER || |
| GetRole() == ui::AX_ROLE_ROW_HEADER); |
| } |
| |
| bool BrowserAccessibility::IsEditableText() const { |
| return HasState(ui::AX_STATE_EDITABLE); |
| } |
| |
| bool BrowserAccessibility::IsWebAreaForPresentationalIframe() const { |
| if (GetRole() != ui::AX_ROLE_WEB_AREA && |
| GetRole() != ui::AX_ROLE_ROOT_WEB_AREA) { |
| return false; |
| } |
| |
| BrowserAccessibility* parent = GetParent(); |
| if (!parent) |
| return false; |
| |
| BrowserAccessibility* grandparent = parent->GetParent(); |
| if (!grandparent) |
| return false; |
| |
| 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 { |
| if (GetRole() == ui::AX_ROLE_STATIC_TEXT || |
| GetRole() == ui::AX_ROLE_LINE_BREAK) { |
| return static_cast<int>(GetStringAttribute(ui::AX_ATTR_VALUE).size()); |
| } |
| |
| int len = 0; |
| for (size_t i = 0; i < InternalChildCount(); ++i) |
| len += InternalGetChild(i)->GetStaticTextLenRecursive(); |
| return len; |
| } |
| |
| void BrowserAccessibility::FixEmptyBounds(gfx::Rect* bounds) const |
| { |
| if (bounds->width() > 0 && bounds->height() > 0) |
| return; |
| |
| for (size_t i = 0; i < InternalChildCount(); ++i) { |
| // Compute the bounds of each child - this calls FixEmptyBounds |
| // recursively if necessary. |
| BrowserAccessibility* child = InternalGetChild(i); |
| gfx::Rect child_bounds = child->GetLocalBoundsRect(); |
| |
| // Ignore children that don't have valid bounds themselves. |
| if (child_bounds.width() == 0 || child_bounds.height() == 0) |
| continue; |
| |
| // For the first valid child, just set the bounds to that child's bounds. |
| if (bounds->width() == 0 || bounds->height() == 0) { |
| *bounds = child_bounds; |
| continue; |
| } |
| |
| // Union each additional child's bounds. |
| bounds->Union(child_bounds); |
| } |
| } |
| |
| gfx::Rect BrowserAccessibility::ElementBoundsToLocalBounds(gfx::Rect bounds) |
| const { |
| // Walk up the parent chain. Every time we encounter a Web Area, offset |
| // based on the scroll bars and then offset based on the origin of that |
| // nested web area. |
| BrowserAccessibility* parent = GetParent(); |
| bool need_to_offset_web_area = |
| (GetRole() == ui::AX_ROLE_WEB_AREA || |
| GetRole() == ui::AX_ROLE_ROOT_WEB_AREA); |
| while (parent) { |
| if (need_to_offset_web_area && |
| parent->GetLocation().width() > 0 && |
| parent->GetLocation().height() > 0) { |
| bounds.Offset(parent->GetLocation().x(), parent->GetLocation().y()); |
| need_to_offset_web_area = false; |
| } |
| |
| // On some platforms, we don't want to take the root scroll offsets |
| // into account. |
| if (parent->GetRole() == ui::AX_ROLE_ROOT_WEB_AREA && |
| !manager()->UseRootScrollOffsetsWhenComputingBounds()) { |
| break; |
| } |
| |
| if (parent->GetRole() == ui::AX_ROLE_WEB_AREA || |
| parent->GetRole() == ui::AX_ROLE_ROOT_WEB_AREA) { |
| int sx = 0; |
| int sy = 0; |
| if (parent->GetIntAttribute(ui::AX_ATTR_SCROLL_X, &sx) && |
| parent->GetIntAttribute(ui::AX_ATTR_SCROLL_Y, &sy)) { |
| bounds.Offset(-sx, -sy); |
| } |
| need_to_offset_web_area = true; |
| } |
| parent = parent->GetParent(); |
| } |
| |
| return bounds; |
| } |
| |
| } // namespace content |