// 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 "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" namespace content { typedef AccessibilityNodeData::BoolAttribute BoolAttribute; typedef AccessibilityNodeData::FloatAttribute FloatAttribute; typedef AccessibilityNodeData::IntAttribute IntAttribute; typedef AccessibilityNodeData::StringAttribute StringAttribute; #if !defined(OS_MACOSX) && \ !defined(OS_WIN) && \ !defined(TOOLKIT_GTK) && \ !defined(OS_ANDROID) // We have subclassess of BrowserAccessibility on Mac, Linux/GTK, // and Win. For any other platform, instantiate the base class. // static BrowserAccessibility* BrowserAccessibility::Create() { return new BrowserAccessibility(); } #endif BrowserAccessibility::BrowserAccessibility() : manager_(NULL), parent_(NULL), index_in_parent_(0), renderer_id_(0), role_(0), state_(0), instance_active_(false) { } BrowserAccessibility::~BrowserAccessibility() { } bool BrowserAccessibility::PlatformIsLeaf() const { return role_ == blink::WebAXRoleStaticText || child_count() == 0; } uint32 BrowserAccessibility::PlatformChildCount() const { return PlatformIsLeaf() ? 0 : children_.size(); } void BrowserAccessibility::DetachTree( std::vector* nodes) { nodes->push_back(this); for (size_t i = 0; i < children_.size(); ++i) children_[i]->DetachTree(nodes); children_.clear(); parent_ = NULL; } void BrowserAccessibility::InitializeTreeStructure( BrowserAccessibilityManager* manager, BrowserAccessibility* parent, int32 renderer_id, int32 index_in_parent) { manager_ = manager; parent_ = parent; renderer_id_ = renderer_id; index_in_parent_ = index_in_parent; } void BrowserAccessibility::InitializeData(const AccessibilityNodeData& src) { DCHECK_EQ(renderer_id_, src.id); role_ = src.role; state_ = src.state; string_attributes_ = src.string_attributes; int_attributes_ = src.int_attributes; float_attributes_ = src.float_attributes; bool_attributes_ = src.bool_attributes; intlist_attributes_ = src.intlist_attributes; html_attributes_ = src.html_attributes; location_ = src.location; instance_active_ = true; GetStringAttribute(AccessibilityNodeData::ATTR_NAME, &name_); GetStringAttribute(AccessibilityNodeData::ATTR_VALUE, &value_); PreInitialize(); } bool BrowserAccessibility::IsNative() const { return false; } void BrowserAccessibility::SwapChildren( std::vector& children) { children.swap(children_); } void BrowserAccessibility::UpdateParent(BrowserAccessibility* parent, int index_in_parent) { parent_ = parent; index_in_parent_ = index_in_parent; } void BrowserAccessibility::SetLocation(const gfx::Rect& new_location) { location_ = new_location; } bool BrowserAccessibility::IsDescendantOf( BrowserAccessibility* ancestor) { if (this == ancestor) { return true; } else if (parent_) { return parent_->IsDescendantOf(ancestor); } return false; } BrowserAccessibility* BrowserAccessibility::PlatformGetChild( uint32 child_index) const { DCHECK(child_index < children_.size()); return children_[child_index]; } BrowserAccessibility* BrowserAccessibility::GetPreviousSibling() { if (parent_ && index_in_parent_ > 0) return parent_->children_[index_in_parent_ - 1]; return NULL; } BrowserAccessibility* BrowserAccessibility::GetNextSibling() { if (parent_ && index_in_parent_ >= 0 && index_in_parent_ < static_cast(parent_->children_.size() - 1)) { return parent_->children_[index_in_parent_ + 1]; } return NULL; } gfx::Rect BrowserAccessibility::GetLocalBoundsRect() const { gfx::Rect bounds = location_; // 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 = parent_; bool need_to_offset_web_area = (role_ == blink::WebAXRoleWebArea || role_ == blink::WebAXRoleRootWebArea); while (parent) { if (need_to_offset_web_area && parent->location().width() > 0 && parent->location().height() > 0) { bounds.Offset(parent->location().x(), parent->location().y()); need_to_offset_web_area = false; } // On some platforms, we don't want to take the root scroll offsets // into account. if (parent->role() == blink::WebAXRoleRootWebArea && !manager()->UseRootScrollOffsetsWhenComputingBounds()) { break; } if (parent->role() == blink::WebAXRoleWebArea || parent->role() == blink::WebAXRoleRootWebArea) { int sx = 0; int sy = 0; if (parent->GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X, &sx) && parent->GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_Y, &sy)) { bounds.Offset(-sx, -sy); } need_to_offset_web_area = true; } parent = parent->parent(); } return 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 { DCHECK_EQ(role_, blink::WebAXRoleStaticText); int end = start + len; int child_start = 0; int child_end = 0; gfx::Rect bounds; for (size_t i = 0; i < children_.size() && child_end < start + len; ++i) { BrowserAccessibility* child = children_[i]; DCHECK_EQ(child->role(), blink::WebAXRoleInlineTextBox); std::string child_text; child->GetStringAttribute(AccessibilityNodeData::ATTR_VALUE, &child_text); int child_len = static_cast(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->location(); int text_direction = child->GetIntAttribute( AccessibilityNodeData::ATTR_TEXT_DIRECTION); const std::vector& character_offsets = child->GetIntListAttribute( AccessibilityNodeData::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 blink::WebAXTextDirectionLR: { 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 blink::WebAXTextDirectionRL: { 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 blink::WebAXTextDirectionTB: { 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 blink::WebAXTextDirectionBT: { 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 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; } BrowserAccessibility* BrowserAccessibility::BrowserAccessibilityForPoint( const gfx::Point& point) { // Walk the children recursively looking for the BrowserAccessibility that // most tightly encloses the specified point. for (int i = static_cast(PlatformChildCount()) - 1; i >= 0; --i) { BrowserAccessibility* child = PlatformGetChild(i); if (child->GetGlobalBoundsRect().Contains(point)) return child->BrowserAccessibilityForPoint(point); } return this; } void BrowserAccessibility::Destroy() { for (std::vector::iterator iter = children_.begin(); iter != children_.end(); ++iter) { (*iter)->Destroy(); } children_.clear(); // Allow the object to fire a TextRemoved notification. name_.clear(); value_.clear(); PostInitialize(); manager_->NotifyAccessibilityEvent( blink::WebAXEventHide, this); instance_active_ = false; manager_->RemoveNode(this); NativeReleaseReference(); } void BrowserAccessibility::NativeReleaseReference() { delete this; } bool BrowserAccessibility::HasBoolAttribute(BoolAttribute attribute) const { for (size_t i = 0; i < bool_attributes_.size(); ++i) { if (bool_attributes_[i].first == attribute) return true; } return false; } bool BrowserAccessibility::GetBoolAttribute(BoolAttribute attribute) const { for (size_t i = 0; i < bool_attributes_.size(); ++i) { if (bool_attributes_[i].first == attribute) return bool_attributes_[i].second; } return false; } bool BrowserAccessibility::GetBoolAttribute( BoolAttribute attribute, bool* value) const { for (size_t i = 0; i < bool_attributes_.size(); ++i) { if (bool_attributes_[i].first == attribute) { *value = bool_attributes_[i].second; return true; } } return false; } bool BrowserAccessibility::HasFloatAttribute(FloatAttribute attribute) const { for (size_t i = 0; i < float_attributes_.size(); ++i) { if (float_attributes_[i].first == attribute) return true; } return false; } float BrowserAccessibility::GetFloatAttribute(FloatAttribute attribute) const { for (size_t i = 0; i < float_attributes_.size(); ++i) { if (float_attributes_[i].first == attribute) return float_attributes_[i].second; } return 0.0; } bool BrowserAccessibility::GetFloatAttribute( FloatAttribute attribute, float* value) const { for (size_t i = 0; i < float_attributes_.size(); ++i) { if (float_attributes_[i].first == attribute) { *value = float_attributes_[i].second; return true; } } return false; } bool BrowserAccessibility::HasIntAttribute(IntAttribute attribute) const { for (size_t i = 0; i < int_attributes_.size(); ++i) { if (int_attributes_[i].first == attribute) return true; } return false; } int BrowserAccessibility::GetIntAttribute(IntAttribute attribute) const { for (size_t i = 0; i < int_attributes_.size(); ++i) { if (int_attributes_[i].first == attribute) return int_attributes_[i].second; } return 0; } bool BrowserAccessibility::GetIntAttribute( IntAttribute attribute, int* value) const { for (size_t i = 0; i < int_attributes_.size(); ++i) { if (int_attributes_[i].first == attribute) { *value = int_attributes_[i].second; return true; } } return false; } bool BrowserAccessibility::HasStringAttribute(StringAttribute attribute) const { for (size_t i = 0; i < string_attributes_.size(); ++i) { if (string_attributes_[i].first == attribute) return true; } return false; } const std::string& BrowserAccessibility::GetStringAttribute( StringAttribute attribute) const { CR_DEFINE_STATIC_LOCAL(std::string, empty_string, ()); for (size_t i = 0; i < string_attributes_.size(); ++i) { if (string_attributes_[i].first == attribute) return string_attributes_[i].second; } return empty_string; } bool BrowserAccessibility::GetStringAttribute( StringAttribute attribute, std::string* value) const { for (size_t i = 0; i < string_attributes_.size(); ++i) { if (string_attributes_[i].first == attribute) { *value = string_attributes_[i].second; return true; } } return false; } base::string16 BrowserAccessibility::GetString16Attribute( StringAttribute attribute) const { std::string value_utf8; if (!GetStringAttribute(attribute, &value_utf8)) return base::string16(); return UTF8ToUTF16(value_utf8); } bool BrowserAccessibility::GetString16Attribute( StringAttribute attribute, base::string16* value) const { std::string value_utf8; if (!GetStringAttribute(attribute, &value_utf8)) return false; *value = UTF8ToUTF16(value_utf8); return true; } void BrowserAccessibility::SetStringAttribute( StringAttribute attribute, const std::string& value) { for (size_t i = 0; i < string_attributes_.size(); ++i) { if (string_attributes_[i].first == attribute) { string_attributes_[i].second = value; return; } } if (!value.empty()) string_attributes_.push_back(std::make_pair(attribute, value)); } bool BrowserAccessibility::HasIntListAttribute( AccessibilityNodeData::IntListAttribute attribute) const { for (size_t i = 0; i < intlist_attributes_.size(); ++i) { if (intlist_attributes_[i].first == attribute) return true; } return false; } const std::vector& BrowserAccessibility::GetIntListAttribute( AccessibilityNodeData::IntListAttribute attribute) const { CR_DEFINE_STATIC_LOCAL(std::vector, empty_vector, ()); for (size_t i = 0; i < intlist_attributes_.size(); ++i) { if (intlist_attributes_[i].first == attribute) return intlist_attributes_[i].second; } return empty_vector; } bool BrowserAccessibility::GetIntListAttribute( AccessibilityNodeData::IntListAttribute attribute, std::vector* value) const { for (size_t i = 0; i < intlist_attributes_.size(); ++i) { if (intlist_attributes_[i].first == attribute) { *value = intlist_attributes_[i].second; return true; } } return false; } bool BrowserAccessibility::GetHtmlAttribute( const char* html_attr, std::string* value) const { for (size_t i = 0; i < html_attributes_.size(); ++i) { const std::string& attr = html_attributes_[i].first; if (LowerCaseEqualsASCII(attr, html_attr)) { *value = html_attributes_[i].second; return true; } } return false; } bool BrowserAccessibility::GetHtmlAttribute( const char* html_attr, base::string16* value) const { std::string value_utf8; if (!GetHtmlAttribute(html_attr, &value_utf8)) return false; *value = UTF8ToUTF16(value_utf8); return true; } 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() || EqualsASCII(value, "undefined")) { return false; // Not set (and *is_defined is also false) } *is_defined = true; if (EqualsASCII(value, "true")) return true; if (EqualsASCII(value, "mixed")) *is_mixed = true; return false; // Not set } bool BrowserAccessibility::HasState(blink::WebAXState state_enum) const { return (state_ >> state_enum) & 1; } bool BrowserAccessibility::IsEditableText() const { // These roles don't have readonly set, but they're not editable text. if (role_ == blink::WebAXRoleScrollArea || role_ == blink::WebAXRoleColumn || role_ == blink::WebAXRoleTableHeaderContainer) { return false; } // Note: WebAXStateReadonly being false means it's either a text control, // or contenteditable. We also check for editable text roles to cover // another element that has role=textbox set on it. return (!HasState(blink::WebAXStateReadonly) || role_ == blink::WebAXRoleTextField || role_ == blink::WebAXRoleTextArea); } std::string BrowserAccessibility::GetTextRecursive() const { if (!name_.empty()) { return name_; } std::string result; for (uint32 i = 0; i < PlatformChildCount(); ++i) result += PlatformGetChild(i)->GetTextRecursive(); return result; } } // namespace content