1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/accessibility/browser_accessibility.h"
7 #include "base/logging.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "base/strings/string_util.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "content/browser/accessibility/browser_accessibility_manager.h"
12 #include "content/common/accessibility_messages.h"
16 #if !defined(OS_MACOSX) && \
19 // We have subclassess of BrowserAccessibility on Mac and Win. For any other
20 // platform, instantiate the base class.
22 BrowserAccessibility
* BrowserAccessibility::Create() {
23 return new BrowserAccessibility();
27 BrowserAccessibility::BrowserAccessibility()
32 BrowserAccessibility::~BrowserAccessibility() {
35 void BrowserAccessibility::Init(BrowserAccessibilityManager
* manager
,
41 void BrowserAccessibility::OnDataChanged() {
42 GetStringAttribute(ui::AX_ATTR_NAME
, &name_
);
43 GetStringAttribute(ui::AX_ATTR_VALUE
, &value_
);
46 bool BrowserAccessibility::PlatformIsLeaf() const {
47 if (InternalChildCount() == 0)
50 // All of these roles may have children that we use as internal
51 // implementation details, but we want to expose them as leaves
52 // to platform accessibility APIs.
54 case ui::AX_ROLE_SLIDER
:
55 case ui::AX_ROLE_STATIC_TEXT
:
56 case ui::AX_ROLE_TEXT_AREA
:
57 case ui::AX_ROLE_TEXT_FIELD
:
64 uint32
BrowserAccessibility::PlatformChildCount() const {
65 return PlatformIsLeaf() ? 0 : InternalChildCount();
68 bool BrowserAccessibility::IsNative() const {
72 bool BrowserAccessibility::IsDescendantOf(
73 BrowserAccessibility
* ancestor
) {
74 if (this == ancestor
) {
76 } else if (GetParent()) {
77 return GetParent()->IsDescendantOf(ancestor
);
83 BrowserAccessibility
* BrowserAccessibility::PlatformGetChild(
84 uint32 child_index
) const {
85 DCHECK(child_index
< InternalChildCount());
86 BrowserAccessibility
* result
= InternalGetChild(child_index
);
88 if (result
->HasBoolAttribute(ui::AX_ATTR_IS_AX_TREE_HOST
)) {
89 BrowserAccessibilityManager
* child_manager
=
90 manager_
->delegate()->AccessibilityGetChildFrame(result
->GetId());
92 result
= child_manager
->GetRoot();
98 BrowserAccessibility
* BrowserAccessibility::GetPreviousSibling() {
99 if (GetParent() && GetIndexInParent() > 0)
100 return GetParent()->InternalGetChild(GetIndexInParent() - 1);
105 BrowserAccessibility
* BrowserAccessibility::GetNextSibling() {
107 GetIndexInParent() >= 0 &&
108 GetIndexInParent() < static_cast<int>(
109 GetParent()->InternalChildCount() - 1)) {
110 return GetParent()->InternalGetChild(GetIndexInParent() + 1);
116 uint32
BrowserAccessibility::InternalChildCount() const {
117 if (!node_
|| !manager_
)
119 return static_cast<uint32
>(node_
->child_count());
122 BrowserAccessibility
* BrowserAccessibility::InternalGetChild(
123 uint32 child_index
) const {
124 if (!node_
|| !manager_
)
126 return manager_
->GetFromAXNode(node_
->children()[child_index
]);
129 BrowserAccessibility
* BrowserAccessibility::GetParent() const {
130 if (!node_
|| !manager_
)
132 ui::AXNode
* parent
= node_
->parent();
134 return manager_
->GetFromAXNode(parent
);
136 if (!manager_
->delegate())
139 BrowserAccessibility
* host_node
=
140 manager_
->delegate()->AccessibilityGetParentFrame();
144 return host_node
->GetParent();
147 int32
BrowserAccessibility::GetIndexInParent() const {
148 return node_
? node_
->index_in_parent() : -1;
151 int32
BrowserAccessibility::GetId() const {
152 return node_
? node_
->id() : -1;
155 const ui::AXNodeData
& BrowserAccessibility::GetData() const {
156 CR_DEFINE_STATIC_LOCAL(ui::AXNodeData
, empty_data
, ());
158 return node_
->data();
163 gfx::Rect
BrowserAccessibility::GetLocation() const {
164 return GetData().location
;
167 int32
BrowserAccessibility::GetRole() const {
168 return GetData().role
;
171 int32
BrowserAccessibility::GetState() const {
172 return GetData().state
;
175 const BrowserAccessibility::HtmlAttributes
&
176 BrowserAccessibility::GetHtmlAttributes() const {
177 return GetData().html_attributes
;
180 gfx::Rect
BrowserAccessibility::GetLocalBoundsRect() const {
181 gfx::Rect bounds
= GetLocation();
183 // Walk up the parent chain. Every time we encounter a Web Area, offset
184 // based on the scroll bars and then offset based on the origin of that
186 BrowserAccessibility
* parent
= GetParent();
187 bool need_to_offset_web_area
=
188 (GetRole() == ui::AX_ROLE_WEB_AREA
||
189 GetRole() == ui::AX_ROLE_ROOT_WEB_AREA
);
191 if (need_to_offset_web_area
&&
192 parent
->GetLocation().width() > 0 &&
193 parent
->GetLocation().height() > 0) {
194 bounds
.Offset(parent
->GetLocation().x(), parent
->GetLocation().y());
195 need_to_offset_web_area
= false;
198 // On some platforms, we don't want to take the root scroll offsets
200 if (parent
->GetRole() == ui::AX_ROLE_ROOT_WEB_AREA
&&
201 !manager()->UseRootScrollOffsetsWhenComputingBounds()) {
205 if (parent
->GetRole() == ui::AX_ROLE_WEB_AREA
||
206 parent
->GetRole() == ui::AX_ROLE_ROOT_WEB_AREA
) {
209 if (parent
->GetIntAttribute(ui::AX_ATTR_SCROLL_X
, &sx
) &&
210 parent
->GetIntAttribute(ui::AX_ATTR_SCROLL_Y
, &sy
)) {
211 bounds
.Offset(-sx
, -sy
);
213 need_to_offset_web_area
= true;
215 parent
= parent
->GetParent();
221 gfx::Rect
BrowserAccessibility::GetGlobalBoundsRect() const {
222 gfx::Rect bounds
= GetLocalBoundsRect();
224 // Adjust the bounds by the top left corner of the containing view's bounds
225 // in screen coordinates.
226 bounds
.Offset(manager_
->GetViewBounds().OffsetFromOrigin());
231 gfx::Rect
BrowserAccessibility::GetLocalBoundsForRange(int start
, int len
)
233 if (GetRole() != ui::AX_ROLE_STATIC_TEXT
) {
234 // Apply recursively to all static text descendants. For example, if
235 // you call it on a div with two text node children, it just calls
236 // GetLocalBoundsForRange on each of the two children (adjusting
237 // |start| for each one) and unions the resulting rects.
239 for (size_t i
= 0; i
< InternalChildCount(); ++i
) {
240 BrowserAccessibility
* child
= InternalGetChild(i
);
241 int child_len
= child
->GetStaticTextLenRecursive();
242 if (start
< child_len
&& start
+ len
> 0) {
243 gfx::Rect child_rect
= child
->GetLocalBoundsForRange(start
, len
);
244 bounds
.Union(child_rect
);
251 int end
= start
+ len
;
256 for (size_t i
= 0; i
< InternalChildCount() && child_end
< start
+ len
; ++i
) {
257 BrowserAccessibility
* child
= InternalGetChild(i
);
258 DCHECK_EQ(child
->GetRole(), ui::AX_ROLE_INLINE_TEXT_BOX
);
259 std::string child_text
;
260 child
->GetStringAttribute(ui::AX_ATTR_VALUE
, &child_text
);
261 int child_len
= static_cast<int>(child_text
.size());
262 child_start
= child_end
;
263 child_end
+= child_len
;
265 if (child_end
< start
)
268 int overlap_start
= std::max(start
, child_start
);
269 int overlap_end
= std::min(end
, child_end
);
271 int local_start
= overlap_start
- child_start
;
272 int local_end
= overlap_end
- child_start
;
274 gfx::Rect child_rect
= child
->GetLocation();
275 int text_direction
= child
->GetIntAttribute(
276 ui::AX_ATTR_TEXT_DIRECTION
);
277 const std::vector
<int32
>& character_offsets
= child
->GetIntListAttribute(
278 ui::AX_ATTR_CHARACTER_OFFSETS
);
279 int start_pixel_offset
=
280 local_start
> 0 ? character_offsets
[local_start
- 1] : 0;
281 int end_pixel_offset
=
282 local_end
> 0 ? character_offsets
[local_end
- 1] : 0;
284 gfx::Rect child_overlap_rect
;
285 switch (text_direction
) {
286 case ui::AX_TEXT_DIRECTION_NONE
:
287 case ui::AX_TEXT_DIRECTION_LR
: {
288 int left
= child_rect
.x() + start_pixel_offset
;
289 int right
= child_rect
.x() + end_pixel_offset
;
290 child_overlap_rect
= gfx::Rect(left
, child_rect
.y(),
291 right
- left
, child_rect
.height());
294 case ui::AX_TEXT_DIRECTION_RL
: {
295 int right
= child_rect
.right() - start_pixel_offset
;
296 int left
= child_rect
.right() - end_pixel_offset
;
297 child_overlap_rect
= gfx::Rect(left
, child_rect
.y(),
298 right
- left
, child_rect
.height());
301 case ui::AX_TEXT_DIRECTION_TB
: {
302 int top
= child_rect
.y() + start_pixel_offset
;
303 int bottom
= child_rect
.y() + end_pixel_offset
;
304 child_overlap_rect
= gfx::Rect(child_rect
.x(), top
,
305 child_rect
.width(), bottom
- top
);
308 case ui::AX_TEXT_DIRECTION_BT
: {
309 int bottom
= child_rect
.bottom() - start_pixel_offset
;
310 int top
= child_rect
.bottom() - end_pixel_offset
;
311 child_overlap_rect
= gfx::Rect(child_rect
.x(), top
,
312 child_rect
.width(), bottom
- top
);
319 if (bounds
.width() == 0 && bounds
.height() == 0)
320 bounds
= child_overlap_rect
;
322 bounds
.Union(child_overlap_rect
);
328 gfx::Rect
BrowserAccessibility::GetGlobalBoundsForRange(int start
, int len
)
330 gfx::Rect bounds
= GetLocalBoundsForRange(start
, len
);
332 // Adjust the bounds by the top left corner of the containing view's bounds
333 // in screen coordinates.
334 bounds
.Offset(manager_
->GetViewBounds().OffsetFromOrigin());
339 BrowserAccessibility
* BrowserAccessibility::BrowserAccessibilityForPoint(
340 const gfx::Point
& point
) {
341 // The best result found that's a child of this object.
342 BrowserAccessibility
* child_result
= NULL
;
343 // The best result that's an indirect descendant like grandchild, etc.
344 BrowserAccessibility
* descendant_result
= NULL
;
346 // Walk the children recursively looking for the BrowserAccessibility that
347 // most tightly encloses the specified point. Walk backwards so that in
348 // the absence of any other information, we assume the object that occurs
349 // later in the tree is on top of one that comes before it.
350 for (int i
= static_cast<int>(PlatformChildCount()) - 1; i
>= 0; --i
) {
351 BrowserAccessibility
* child
= PlatformGetChild(i
);
353 // Skip table columns because cells are only contained in rows,
355 if (child
->GetRole() == ui::AX_ROLE_COLUMN
)
358 if (child
->GetGlobalBoundsRect().Contains(point
)) {
359 BrowserAccessibility
* result
= child
->BrowserAccessibilityForPoint(point
);
360 if (result
== child
&& !child_result
)
361 child_result
= result
;
362 if (result
!= child
&& !descendant_result
)
363 descendant_result
= result
;
366 if (child_result
&& descendant_result
)
370 // Explanation of logic: it's possible that this point overlaps more than
371 // one child of this object. If so, as a heuristic we prefer if the point
372 // overlaps a descendant of one of the two children and not the other.
373 // As an example, suppose you have two rows of buttons - the buttons don't
374 // overlap, but the rows do. Without this heuristic, we'd greedily only
375 // consider one of the containers.
376 if (descendant_result
)
377 return descendant_result
;
384 void BrowserAccessibility::Destroy() {
385 // Allow the object to fire a TextRemoved notification.
389 manager_
->NotifyAccessibilityEvent(ui::AX_EVENT_HIDE
, this);
393 NativeReleaseReference();
396 void BrowserAccessibility::NativeReleaseReference() {
400 bool BrowserAccessibility::HasBoolAttribute(
401 ui::AXBoolAttribute attribute
) const {
402 const ui::AXNodeData
& data
= GetData();
403 for (size_t i
= 0; i
< data
.bool_attributes
.size(); ++i
) {
404 if (data
.bool_attributes
[i
].first
== attribute
)
412 bool BrowserAccessibility::GetBoolAttribute(
413 ui::AXBoolAttribute attribute
) const {
414 const ui::AXNodeData
& data
= GetData();
415 for (size_t i
= 0; i
< data
.bool_attributes
.size(); ++i
) {
416 if (data
.bool_attributes
[i
].first
== attribute
)
417 return data
.bool_attributes
[i
].second
;
423 bool BrowserAccessibility::GetBoolAttribute(
424 ui::AXBoolAttribute attribute
, bool* value
) const {
425 const ui::AXNodeData
& data
= GetData();
426 for (size_t i
= 0; i
< data
.bool_attributes
.size(); ++i
) {
427 if (data
.bool_attributes
[i
].first
== attribute
) {
428 *value
= data
.bool_attributes
[i
].second
;
436 bool BrowserAccessibility::HasFloatAttribute(
437 ui::AXFloatAttribute attribute
) const {
438 const ui::AXNodeData
& data
= GetData();
439 for (size_t i
= 0; i
< data
.float_attributes
.size(); ++i
) {
440 if (data
.float_attributes
[i
].first
== attribute
)
447 float BrowserAccessibility::GetFloatAttribute(
448 ui::AXFloatAttribute attribute
) const {
449 const ui::AXNodeData
& data
= GetData();
450 for (size_t i
= 0; i
< data
.float_attributes
.size(); ++i
) {
451 if (data
.float_attributes
[i
].first
== attribute
)
452 return data
.float_attributes
[i
].second
;
458 bool BrowserAccessibility::GetFloatAttribute(
459 ui::AXFloatAttribute attribute
, float* value
) const {
460 const ui::AXNodeData
& data
= GetData();
461 for (size_t i
= 0; i
< data
.float_attributes
.size(); ++i
) {
462 if (data
.float_attributes
[i
].first
== attribute
) {
463 *value
= data
.float_attributes
[i
].second
;
471 bool BrowserAccessibility::HasIntAttribute(
472 ui::AXIntAttribute attribute
) const {
473 const ui::AXNodeData
& data
= GetData();
474 for (size_t i
= 0; i
< data
.int_attributes
.size(); ++i
) {
475 if (data
.int_attributes
[i
].first
== attribute
)
482 int BrowserAccessibility::GetIntAttribute(ui::AXIntAttribute attribute
) const {
483 const ui::AXNodeData
& data
= GetData();
484 for (size_t i
= 0; i
< data
.int_attributes
.size(); ++i
) {
485 if (data
.int_attributes
[i
].first
== attribute
)
486 return data
.int_attributes
[i
].second
;
492 bool BrowserAccessibility::GetIntAttribute(
493 ui::AXIntAttribute attribute
, int* value
) const {
494 const ui::AXNodeData
& data
= GetData();
495 for (size_t i
= 0; i
< data
.int_attributes
.size(); ++i
) {
496 if (data
.int_attributes
[i
].first
== attribute
) {
497 *value
= data
.int_attributes
[i
].second
;
505 bool BrowserAccessibility::HasStringAttribute(
506 ui::AXStringAttribute attribute
) const {
507 const ui::AXNodeData
& data
= GetData();
508 for (size_t i
= 0; i
< data
.string_attributes
.size(); ++i
) {
509 if (data
.string_attributes
[i
].first
== attribute
)
516 const std::string
& BrowserAccessibility::GetStringAttribute(
517 ui::AXStringAttribute attribute
) const {
518 const ui::AXNodeData
& data
= GetData();
519 CR_DEFINE_STATIC_LOCAL(std::string
, empty_string
, ());
520 for (size_t i
= 0; i
< data
.string_attributes
.size(); ++i
) {
521 if (data
.string_attributes
[i
].first
== attribute
)
522 return data
.string_attributes
[i
].second
;
528 bool BrowserAccessibility::GetStringAttribute(
529 ui::AXStringAttribute attribute
, std::string
* value
) const {
530 const ui::AXNodeData
& data
= GetData();
531 for (size_t i
= 0; i
< data
.string_attributes
.size(); ++i
) {
532 if (data
.string_attributes
[i
].first
== attribute
) {
533 *value
= data
.string_attributes
[i
].second
;
541 base::string16
BrowserAccessibility::GetString16Attribute(
542 ui::AXStringAttribute attribute
) const {
543 std::string value_utf8
;
544 if (!GetStringAttribute(attribute
, &value_utf8
))
545 return base::string16();
546 return base::UTF8ToUTF16(value_utf8
);
549 bool BrowserAccessibility::GetString16Attribute(
550 ui::AXStringAttribute attribute
,
551 base::string16
* value
) const {
552 std::string value_utf8
;
553 if (!GetStringAttribute(attribute
, &value_utf8
))
555 *value
= base::UTF8ToUTF16(value_utf8
);
559 void BrowserAccessibility::SetStringAttribute(
560 ui::AXStringAttribute attribute
, const std::string
& value
) {
563 ui::AXNodeData data
= GetData();
564 for (size_t i
= 0; i
< data
.string_attributes
.size(); ++i
) {
565 if (data
.string_attributes
[i
].first
== attribute
) {
566 data
.string_attributes
[i
].second
= value
;
567 node_
->SetData(data
);
571 if (!value
.empty()) {
572 data
.string_attributes
.push_back(std::make_pair(attribute
, value
));
573 node_
->SetData(data
);
577 bool BrowserAccessibility::HasIntListAttribute(
578 ui::AXIntListAttribute attribute
) const {
579 const ui::AXNodeData
& data
= GetData();
580 for (size_t i
= 0; i
< data
.intlist_attributes
.size(); ++i
) {
581 if (data
.intlist_attributes
[i
].first
== attribute
)
588 const std::vector
<int32
>& BrowserAccessibility::GetIntListAttribute(
589 ui::AXIntListAttribute attribute
) const {
590 const ui::AXNodeData
& data
= GetData();
591 CR_DEFINE_STATIC_LOCAL(std::vector
<int32
>, empty_vector
, ());
592 for (size_t i
= 0; i
< data
.intlist_attributes
.size(); ++i
) {
593 if (data
.intlist_attributes
[i
].first
== attribute
)
594 return data
.intlist_attributes
[i
].second
;
600 bool BrowserAccessibility::GetIntListAttribute(
601 ui::AXIntListAttribute attribute
,
602 std::vector
<int32
>* value
) const {
603 const ui::AXNodeData
& data
= GetData();
604 for (size_t i
= 0; i
< data
.intlist_attributes
.size(); ++i
) {
605 if (data
.intlist_attributes
[i
].first
== attribute
) {
606 *value
= data
.intlist_attributes
[i
].second
;
614 bool BrowserAccessibility::GetHtmlAttribute(
615 const char* html_attr
, std::string
* value
) const {
616 for (size_t i
= 0; i
< GetHtmlAttributes().size(); ++i
) {
617 const std::string
& attr
= GetHtmlAttributes()[i
].first
;
618 if (LowerCaseEqualsASCII(attr
, html_attr
)) {
619 *value
= GetHtmlAttributes()[i
].second
;
627 bool BrowserAccessibility::GetHtmlAttribute(
628 const char* html_attr
, base::string16
* value
) const {
629 std::string value_utf8
;
630 if (!GetHtmlAttribute(html_attr
, &value_utf8
))
632 *value
= base::UTF8ToUTF16(value_utf8
);
636 bool BrowserAccessibility::GetAriaTristate(
637 const char* html_attr
,
639 bool* is_mixed
) const {
643 base::string16 value
;
644 if (!GetHtmlAttribute(html_attr
, &value
) ||
646 EqualsASCII(value
, "undefined")) {
647 return false; // Not set (and *is_defined is also false)
652 if (EqualsASCII(value
, "true"))
655 if (EqualsASCII(value
, "mixed"))
658 return false; // Not set
661 bool BrowserAccessibility::HasState(ui::AXState state_enum
) const {
662 return (GetState() >> state_enum
) & 1;
665 bool BrowserAccessibility::IsEditableText() const {
666 // These roles don't have readonly set, but they're not editable text.
667 if (GetRole() == ui::AX_ROLE_SCROLL_AREA
||
668 GetRole() == ui::AX_ROLE_COLUMN
||
669 GetRole() == ui::AX_ROLE_TABLE_HEADER_CONTAINER
) {
673 // Note: WebAXStateReadonly being false means it's either a text control,
674 // or contenteditable. We also check for editable text roles to cover
675 // another element that has role=textbox set on it.
676 return (!HasState(ui::AX_STATE_READ_ONLY
) ||
677 GetRole() == ui::AX_ROLE_TEXT_FIELD
||
678 GetRole() == ui::AX_ROLE_TEXT_AREA
);
681 bool BrowserAccessibility::IsWebAreaForPresentationalIframe() const {
682 if (GetRole() != ui::AX_ROLE_WEB_AREA
&&
683 GetRole() != ui::AX_ROLE_ROOT_WEB_AREA
) {
687 BrowserAccessibility
* parent
= GetParent();
691 BrowserAccessibility
* grandparent
= parent
->GetParent();
695 return grandparent
->GetRole() == ui::AX_ROLE_IFRAME_PRESENTATIONAL
;
698 std::string
BrowserAccessibility::GetTextRecursive() const {
699 if (!name_
.empty()) {
704 for (uint32 i
= 0; i
< PlatformChildCount(); ++i
)
705 result
+= PlatformGetChild(i
)->GetTextRecursive();
709 int BrowserAccessibility::GetStaticTextLenRecursive() const {
710 if (GetRole() == ui::AX_ROLE_STATIC_TEXT
)
711 return static_cast<int>(GetStringAttribute(ui::AX_ATTR_VALUE
).size());
714 for (size_t i
= 0; i
< InternalChildCount(); ++i
)
715 len
+= InternalGetChild(i
)->GetStaticTextLenRecursive();
719 } // namespace content