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 bool BrowserAccessibility::PlatformIsLeaf() const {
42 if (InternalChildCount() == 0)
45 // All of these roles may have children that we use as internal
46 // implementation details, but we want to expose them as leaves
47 // to platform accessibility APIs.
49 case ui::AX_ROLE_LINE_BREAK
:
50 case ui::AX_ROLE_SLIDER
:
51 case ui::AX_ROLE_STATIC_TEXT
:
52 case ui::AX_ROLE_TEXT_AREA
:
53 case ui::AX_ROLE_TEXT_FIELD
:
60 uint32
BrowserAccessibility::PlatformChildCount() const {
61 return PlatformIsLeaf() ? 0 : InternalChildCount();
64 bool BrowserAccessibility::IsNative() const {
68 bool BrowserAccessibility::IsDescendantOf(
69 BrowserAccessibility
* ancestor
) {
70 if (this == ancestor
) {
72 } else if (GetParent()) {
73 return GetParent()->IsDescendantOf(ancestor
);
79 BrowserAccessibility
* BrowserAccessibility::PlatformGetChild(
80 uint32 child_index
) const {
81 DCHECK(child_index
< InternalChildCount());
82 BrowserAccessibility
* result
= InternalGetChild(child_index
);
84 if (result
->HasBoolAttribute(ui::AX_ATTR_IS_AX_TREE_HOST
)) {
85 BrowserAccessibilityManager
* child_manager
=
86 manager_
->delegate()->AccessibilityGetChildFrame(result
->GetId());
88 result
= child_manager
->GetRoot();
94 bool BrowserAccessibility::PlatformIsChildOfLeaf() const {
95 BrowserAccessibility
* ancestor
= GetParent();
97 if (ancestor
->PlatformIsLeaf())
99 ancestor
= ancestor
->GetParent();
105 BrowserAccessibility
* BrowserAccessibility::GetPreviousSibling() {
106 if (GetParent() && GetIndexInParent() > 0)
107 return GetParent()->InternalGetChild(GetIndexInParent() - 1);
112 BrowserAccessibility
* BrowserAccessibility::GetNextSibling() {
114 GetIndexInParent() >= 0 &&
115 GetIndexInParent() < static_cast<int>(
116 GetParent()->InternalChildCount() - 1)) {
117 return GetParent()->InternalGetChild(GetIndexInParent() + 1);
123 uint32
BrowserAccessibility::InternalChildCount() const {
124 if (!node_
|| !manager_
)
126 return static_cast<uint32
>(node_
->child_count());
129 BrowserAccessibility
* BrowserAccessibility::InternalGetChild(
130 uint32 child_index
) const {
131 if (!node_
|| !manager_
)
133 return manager_
->GetFromAXNode(node_
->children()[child_index
]);
136 BrowserAccessibility
* BrowserAccessibility::GetParent() const {
137 if (!node_
|| !manager_
)
139 ui::AXNode
* parent
= node_
->parent();
141 return manager_
->GetFromAXNode(parent
);
143 if (!manager_
->delegate())
146 BrowserAccessibility
* host_node
=
147 manager_
->delegate()->AccessibilityGetParentFrame();
151 return host_node
->GetParent();
154 int32
BrowserAccessibility::GetIndexInParent() const {
155 return node_
? node_
->index_in_parent() : -1;
158 int32
BrowserAccessibility::GetId() const {
159 return node_
? node_
->id() : -1;
162 const ui::AXNodeData
& BrowserAccessibility::GetData() const {
163 CR_DEFINE_STATIC_LOCAL(ui::AXNodeData
, empty_data
, ());
165 return node_
->data();
170 gfx::Rect
BrowserAccessibility::GetLocation() const {
171 return GetData().location
;
174 int32
BrowserAccessibility::GetRole() const {
175 return GetData().role
;
178 int32
BrowserAccessibility::GetState() const {
179 return GetData().state
;
182 const BrowserAccessibility::HtmlAttributes
&
183 BrowserAccessibility::GetHtmlAttributes() const {
184 return GetData().html_attributes
;
187 gfx::Rect
BrowserAccessibility::GetLocalBoundsRect() const {
188 gfx::Rect bounds
= GetLocation();
189 return ElementBoundsToLocalBounds(bounds
);
192 gfx::Rect
BrowserAccessibility::GetGlobalBoundsRect() const {
193 gfx::Rect bounds
= GetLocalBoundsRect();
195 // Adjust the bounds by the top left corner of the containing view's bounds
196 // in screen coordinates.
197 bounds
.Offset(manager_
->GetViewBounds().OffsetFromOrigin());
202 gfx::Rect
BrowserAccessibility::GetLocalBoundsForRange(int start
, int len
)
204 if (GetRole() != ui::AX_ROLE_STATIC_TEXT
) {
205 // Apply recursively to all static text descendants. For example, if
206 // you call it on a div with two text node children, it just calls
207 // GetLocalBoundsForRange on each of the two children (adjusting
208 // |start| for each one) and unions the resulting rects.
210 for (size_t i
= 0; i
< InternalChildCount(); ++i
) {
211 BrowserAccessibility
* child
= InternalGetChild(i
);
212 int child_len
= child
->GetStaticTextLenRecursive();
213 if (start
< child_len
&& start
+ len
> 0) {
214 gfx::Rect child_rect
= child
->GetLocalBoundsForRange(start
, len
);
215 bounds
.Union(child_rect
);
219 return ElementBoundsToLocalBounds(bounds
);
222 int end
= start
+ len
;
227 for (size_t i
= 0; i
< InternalChildCount() && child_end
< start
+ len
; ++i
) {
228 BrowserAccessibility
* child
= InternalGetChild(i
);
229 DCHECK_EQ(child
->GetRole(), ui::AX_ROLE_INLINE_TEXT_BOX
);
230 std::string child_text
;
231 child
->GetStringAttribute(ui::AX_ATTR_VALUE
, &child_text
);
232 int child_len
= static_cast<int>(child_text
.size());
233 child_start
= child_end
;
234 child_end
+= child_len
;
236 if (child_end
< start
)
239 int overlap_start
= std::max(start
, child_start
);
240 int overlap_end
= std::min(end
, child_end
);
242 int local_start
= overlap_start
- child_start
;
243 int local_end
= overlap_end
- child_start
;
245 gfx::Rect child_rect
= child
->GetLocation();
246 int text_direction
= child
->GetIntAttribute(
247 ui::AX_ATTR_TEXT_DIRECTION
);
248 const std::vector
<int32
>& character_offsets
= child
->GetIntListAttribute(
249 ui::AX_ATTR_CHARACTER_OFFSETS
);
250 int start_pixel_offset
=
251 local_start
> 0 ? character_offsets
[local_start
- 1] : 0;
252 int end_pixel_offset
=
253 local_end
> 0 ? character_offsets
[local_end
- 1] : 0;
255 gfx::Rect child_overlap_rect
;
256 switch (text_direction
) {
257 case ui::AX_TEXT_DIRECTION_NONE
:
258 case ui::AX_TEXT_DIRECTION_LR
: {
259 int left
= child_rect
.x() + start_pixel_offset
;
260 int right
= child_rect
.x() + end_pixel_offset
;
261 child_overlap_rect
= gfx::Rect(left
, child_rect
.y(),
262 right
- left
, child_rect
.height());
265 case ui::AX_TEXT_DIRECTION_RL
: {
266 int right
= child_rect
.right() - start_pixel_offset
;
267 int left
= child_rect
.right() - end_pixel_offset
;
268 child_overlap_rect
= gfx::Rect(left
, child_rect
.y(),
269 right
- left
, child_rect
.height());
272 case ui::AX_TEXT_DIRECTION_TB
: {
273 int top
= child_rect
.y() + start_pixel_offset
;
274 int bottom
= child_rect
.y() + end_pixel_offset
;
275 child_overlap_rect
= gfx::Rect(child_rect
.x(), top
,
276 child_rect
.width(), bottom
- top
);
279 case ui::AX_TEXT_DIRECTION_BT
: {
280 int bottom
= child_rect
.bottom() - start_pixel_offset
;
281 int top
= child_rect
.bottom() - end_pixel_offset
;
282 child_overlap_rect
= gfx::Rect(child_rect
.x(), top
,
283 child_rect
.width(), bottom
- top
);
290 if (bounds
.width() == 0 && bounds
.height() == 0)
291 bounds
= child_overlap_rect
;
293 bounds
.Union(child_overlap_rect
);
296 return ElementBoundsToLocalBounds(bounds
);
299 gfx::Rect
BrowserAccessibility::GetGlobalBoundsForRange(int start
, int len
)
301 gfx::Rect bounds
= GetLocalBoundsForRange(start
, len
);
303 // Adjust the bounds by the top left corner of the containing view's bounds
304 // in screen coordinates.
305 bounds
.Offset(manager_
->GetViewBounds().OffsetFromOrigin());
310 BrowserAccessibility
* BrowserAccessibility::BrowserAccessibilityForPoint(
311 const gfx::Point
& point
) {
312 // The best result found that's a child of this object.
313 BrowserAccessibility
* child_result
= NULL
;
314 // The best result that's an indirect descendant like grandchild, etc.
315 BrowserAccessibility
* descendant_result
= NULL
;
317 // Walk the children recursively looking for the BrowserAccessibility that
318 // most tightly encloses the specified point. Walk backwards so that in
319 // the absence of any other information, we assume the object that occurs
320 // later in the tree is on top of one that comes before it.
321 for (int i
= static_cast<int>(PlatformChildCount()) - 1; i
>= 0; --i
) {
322 BrowserAccessibility
* child
= PlatformGetChild(i
);
324 // Skip table columns because cells are only contained in rows,
326 if (child
->GetRole() == ui::AX_ROLE_COLUMN
)
329 if (child
->GetGlobalBoundsRect().Contains(point
)) {
330 BrowserAccessibility
* result
= child
->BrowserAccessibilityForPoint(point
);
331 if (result
== child
&& !child_result
)
332 child_result
= result
;
333 if (result
!= child
&& !descendant_result
)
334 descendant_result
= result
;
337 if (child_result
&& descendant_result
)
341 // Explanation of logic: it's possible that this point overlaps more than
342 // one child of this object. If so, as a heuristic we prefer if the point
343 // overlaps a descendant of one of the two children and not the other.
344 // As an example, suppose you have two rows of buttons - the buttons don't
345 // overlap, but the rows do. Without this heuristic, we'd greedily only
346 // consider one of the containers.
347 if (descendant_result
)
348 return descendant_result
;
355 void BrowserAccessibility::Destroy() {
356 // Allow the object to fire a TextRemoved notification.
357 manager_
->NotifyAccessibilityEvent(ui::AX_EVENT_HIDE
, this);
361 NativeReleaseReference();
364 void BrowserAccessibility::NativeReleaseReference() {
368 bool BrowserAccessibility::HasBoolAttribute(
369 ui::AXBoolAttribute attribute
) const {
370 const ui::AXNodeData
& data
= GetData();
371 for (size_t i
= 0; i
< data
.bool_attributes
.size(); ++i
) {
372 if (data
.bool_attributes
[i
].first
== attribute
)
380 bool BrowserAccessibility::GetBoolAttribute(
381 ui::AXBoolAttribute attribute
) const {
382 const ui::AXNodeData
& data
= GetData();
383 for (size_t i
= 0; i
< data
.bool_attributes
.size(); ++i
) {
384 if (data
.bool_attributes
[i
].first
== attribute
)
385 return data
.bool_attributes
[i
].second
;
391 bool BrowserAccessibility::GetBoolAttribute(
392 ui::AXBoolAttribute attribute
, bool* value
) const {
393 const ui::AXNodeData
& data
= GetData();
394 for (size_t i
= 0; i
< data
.bool_attributes
.size(); ++i
) {
395 if (data
.bool_attributes
[i
].first
== attribute
) {
396 *value
= data
.bool_attributes
[i
].second
;
404 bool BrowserAccessibility::HasFloatAttribute(
405 ui::AXFloatAttribute attribute
) const {
406 const ui::AXNodeData
& data
= GetData();
407 for (size_t i
= 0; i
< data
.float_attributes
.size(); ++i
) {
408 if (data
.float_attributes
[i
].first
== attribute
)
415 float BrowserAccessibility::GetFloatAttribute(
416 ui::AXFloatAttribute attribute
) const {
417 const ui::AXNodeData
& data
= GetData();
418 for (size_t i
= 0; i
< data
.float_attributes
.size(); ++i
) {
419 if (data
.float_attributes
[i
].first
== attribute
)
420 return data
.float_attributes
[i
].second
;
426 bool BrowserAccessibility::GetFloatAttribute(
427 ui::AXFloatAttribute attribute
, float* value
) const {
428 const ui::AXNodeData
& data
= GetData();
429 for (size_t i
= 0; i
< data
.float_attributes
.size(); ++i
) {
430 if (data
.float_attributes
[i
].first
== attribute
) {
431 *value
= data
.float_attributes
[i
].second
;
439 bool BrowserAccessibility::HasIntAttribute(
440 ui::AXIntAttribute attribute
) const {
441 const ui::AXNodeData
& data
= GetData();
442 for (size_t i
= 0; i
< data
.int_attributes
.size(); ++i
) {
443 if (data
.int_attributes
[i
].first
== attribute
)
450 int BrowserAccessibility::GetIntAttribute(ui::AXIntAttribute attribute
) const {
451 const ui::AXNodeData
& data
= GetData();
452 for (size_t i
= 0; i
< data
.int_attributes
.size(); ++i
) {
453 if (data
.int_attributes
[i
].first
== attribute
)
454 return data
.int_attributes
[i
].second
;
460 bool BrowserAccessibility::GetIntAttribute(
461 ui::AXIntAttribute attribute
, int* value
) const {
462 const ui::AXNodeData
& data
= GetData();
463 for (size_t i
= 0; i
< data
.int_attributes
.size(); ++i
) {
464 if (data
.int_attributes
[i
].first
== attribute
) {
465 *value
= data
.int_attributes
[i
].second
;
473 bool BrowserAccessibility::HasStringAttribute(
474 ui::AXStringAttribute attribute
) const {
475 const ui::AXNodeData
& data
= GetData();
476 for (size_t i
= 0; i
< data
.string_attributes
.size(); ++i
) {
477 if (data
.string_attributes
[i
].first
== attribute
)
484 const std::string
& BrowserAccessibility::GetStringAttribute(
485 ui::AXStringAttribute attribute
) const {
486 const ui::AXNodeData
& data
= GetData();
487 CR_DEFINE_STATIC_LOCAL(std::string
, empty_string
, ());
488 for (size_t i
= 0; i
< data
.string_attributes
.size(); ++i
) {
489 if (data
.string_attributes
[i
].first
== attribute
)
490 return data
.string_attributes
[i
].second
;
496 bool BrowserAccessibility::GetStringAttribute(
497 ui::AXStringAttribute attribute
, std::string
* value
) const {
498 const ui::AXNodeData
& data
= GetData();
499 for (size_t i
= 0; i
< data
.string_attributes
.size(); ++i
) {
500 if (data
.string_attributes
[i
].first
== attribute
) {
501 *value
= data
.string_attributes
[i
].second
;
509 base::string16
BrowserAccessibility::GetString16Attribute(
510 ui::AXStringAttribute attribute
) const {
511 std::string value_utf8
;
512 if (!GetStringAttribute(attribute
, &value_utf8
))
513 return base::string16();
514 return base::UTF8ToUTF16(value_utf8
);
517 bool BrowserAccessibility::GetString16Attribute(
518 ui::AXStringAttribute attribute
,
519 base::string16
* value
) const {
520 std::string value_utf8
;
521 if (!GetStringAttribute(attribute
, &value_utf8
))
523 *value
= base::UTF8ToUTF16(value_utf8
);
527 bool BrowserAccessibility::HasIntListAttribute(
528 ui::AXIntListAttribute attribute
) const {
529 const ui::AXNodeData
& data
= GetData();
530 for (size_t i
= 0; i
< data
.intlist_attributes
.size(); ++i
) {
531 if (data
.intlist_attributes
[i
].first
== attribute
)
538 const std::vector
<int32
>& BrowserAccessibility::GetIntListAttribute(
539 ui::AXIntListAttribute attribute
) const {
540 const ui::AXNodeData
& data
= GetData();
541 CR_DEFINE_STATIC_LOCAL(std::vector
<int32
>, empty_vector
, ());
542 for (size_t i
= 0; i
< data
.intlist_attributes
.size(); ++i
) {
543 if (data
.intlist_attributes
[i
].first
== attribute
)
544 return data
.intlist_attributes
[i
].second
;
550 bool BrowserAccessibility::GetIntListAttribute(
551 ui::AXIntListAttribute attribute
,
552 std::vector
<int32
>* value
) const {
553 const ui::AXNodeData
& data
= GetData();
554 for (size_t i
= 0; i
< data
.intlist_attributes
.size(); ++i
) {
555 if (data
.intlist_attributes
[i
].first
== attribute
) {
556 *value
= data
.intlist_attributes
[i
].second
;
564 bool BrowserAccessibility::GetHtmlAttribute(
565 const char* html_attr
, std::string
* value
) const {
566 for (size_t i
= 0; i
< GetHtmlAttributes().size(); ++i
) {
567 const std::string
& attr
= GetHtmlAttributes()[i
].first
;
568 if (LowerCaseEqualsASCII(attr
, html_attr
)) {
569 *value
= GetHtmlAttributes()[i
].second
;
577 bool BrowserAccessibility::GetHtmlAttribute(
578 const char* html_attr
, base::string16
* value
) const {
579 std::string value_utf8
;
580 if (!GetHtmlAttribute(html_attr
, &value_utf8
))
582 *value
= base::UTF8ToUTF16(value_utf8
);
586 bool BrowserAccessibility::GetAriaTristate(
587 const char* html_attr
,
589 bool* is_mixed
) const {
593 base::string16 value
;
594 if (!GetHtmlAttribute(html_attr
, &value
) ||
596 EqualsASCII(value
, "undefined")) {
597 return false; // Not set (and *is_defined is also false)
602 if (EqualsASCII(value
, "true"))
605 if (EqualsASCII(value
, "mixed"))
608 return false; // Not set
611 bool BrowserAccessibility::HasState(ui::AXState state_enum
) const {
612 return (GetState() >> state_enum
) & 1;
615 bool BrowserAccessibility::IsCellOrTableHeaderRole() const {
616 return (GetRole() == ui::AX_ROLE_CELL
||
617 GetRole() == ui::AX_ROLE_COLUMN_HEADER
||
618 GetRole() == ui::AX_ROLE_ROW_HEADER
);
621 bool BrowserAccessibility::IsEditableText() const {
622 // These roles don't have readonly set, but they're not editable text.
623 if (GetRole() == ui::AX_ROLE_SCROLL_AREA
||
624 GetRole() == ui::AX_ROLE_COLUMN
||
625 GetRole() == ui::AX_ROLE_TABLE_HEADER_CONTAINER
) {
629 // Note: WebAXStateReadonly being false means it's either a text control,
630 // or contenteditable. We also check for editable text roles to cover
631 // another element that has role=textbox set on it.
632 return (!HasState(ui::AX_STATE_READ_ONLY
) ||
633 GetRole() == ui::AX_ROLE_TEXT_FIELD
||
634 GetRole() == ui::AX_ROLE_TEXT_AREA
);
637 bool BrowserAccessibility::IsWebAreaForPresentationalIframe() const {
638 if (GetRole() != ui::AX_ROLE_WEB_AREA
&&
639 GetRole() != ui::AX_ROLE_ROOT_WEB_AREA
) {
643 BrowserAccessibility
* parent
= GetParent();
647 BrowserAccessibility
* grandparent
= parent
->GetParent();
651 return grandparent
->GetRole() == ui::AX_ROLE_IFRAME_PRESENTATIONAL
;
654 int BrowserAccessibility::GetStaticTextLenRecursive() const {
655 if (GetRole() == ui::AX_ROLE_STATIC_TEXT
)
656 return static_cast<int>(GetStringAttribute(ui::AX_ATTR_VALUE
).size());
659 for (size_t i
= 0; i
< InternalChildCount(); ++i
)
660 len
+= InternalGetChild(i
)->GetStaticTextLenRecursive();
664 BrowserAccessibility
* BrowserAccessibility::GetParentForBoundsCalculation()
666 if (!node_
|| !manager_
)
668 ui::AXNode
* parent
= node_
->parent();
670 return manager_
->GetFromAXNode(parent
);
672 if (!manager_
->delegate())
675 return manager_
->delegate()->AccessibilityGetParentFrame();
678 gfx::Rect
BrowserAccessibility::ElementBoundsToLocalBounds(gfx::Rect bounds
)
680 // Walk up the parent chain. Every time we encounter a Web Area, offset
681 // based on the scroll bars and then offset based on the origin of that
683 BrowserAccessibility
* parent
= GetParentForBoundsCalculation();
684 bool need_to_offset_web_area
=
685 (GetRole() == ui::AX_ROLE_WEB_AREA
||
686 GetRole() == ui::AX_ROLE_ROOT_WEB_AREA
);
688 if (need_to_offset_web_area
&&
689 parent
->GetLocation().width() > 0 &&
690 parent
->GetLocation().height() > 0) {
691 bounds
.Offset(parent
->GetLocation().x(), parent
->GetLocation().y());
692 need_to_offset_web_area
= false;
695 // On some platforms, we don't want to take the root scroll offsets
697 if (parent
->GetRole() == ui::AX_ROLE_ROOT_WEB_AREA
&&
698 !manager()->UseRootScrollOffsetsWhenComputingBounds()) {
702 if (parent
->GetRole() == ui::AX_ROLE_WEB_AREA
||
703 parent
->GetRole() == ui::AX_ROLE_ROOT_WEB_AREA
) {
706 if (parent
->GetIntAttribute(ui::AX_ATTR_SCROLL_X
, &sx
) &&
707 parent
->GetIntAttribute(ui::AX_ATTR_SCROLL_Y
, &sy
)) {
708 bounds
.Offset(-sx
, -sy
);
710 need_to_offset_web_area
= true;
712 parent
= parent
->GetParentForBoundsCalculation();
718 } // namespace content