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"
9 #include "base/logging.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "content/browser/accessibility/browser_accessibility_manager.h"
14 #include "content/common/accessibility_messages.h"
15 #include "ui/accessibility/ax_text_utils.h"
19 #if !defined(OS_WIN) && \
20 !defined(OS_MACOSX) && \
21 !defined(OS_ANDROID) && \
22 !(defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(USE_X11))
23 // We have subclassess of BrowserAccessibility on some platforms.
24 // On other platforms, instantiate the base class.
26 BrowserAccessibility
* BrowserAccessibility::Create() {
27 return new BrowserAccessibility();
31 BrowserAccessibility::BrowserAccessibility()
36 BrowserAccessibility::~BrowserAccessibility() {
39 void BrowserAccessibility::Init(BrowserAccessibilityManager
* manager
,
45 bool BrowserAccessibility::PlatformIsLeaf() const {
46 if (InternalChildCount() == 0)
49 // All of these roles may have children that we use as internal
50 // implementation details, but we want to expose them as leaves
51 // to platform accessibility APIs.
53 case ui::AX_ROLE_LINE_BREAK
:
54 case ui::AX_ROLE_SLIDER
:
55 case ui::AX_ROLE_STATIC_TEXT
:
56 case ui::AX_ROLE_TEXT_FIELD
:
63 uint32
BrowserAccessibility::PlatformChildCount() const {
64 if (HasIntAttribute(ui::AX_ATTR_CHILD_TREE_ID
)) {
65 BrowserAccessibilityManager
* child_manager
=
66 BrowserAccessibilityManager::FromID(
67 GetIntAttribute(ui::AX_ATTR_CHILD_TREE_ID
));
74 return PlatformIsLeaf() ? 0 : InternalChildCount();
77 bool BrowserAccessibility::IsNative() const {
81 bool BrowserAccessibility::IsDescendantOf(
82 BrowserAccessibility
* ancestor
) {
83 if (this == ancestor
) {
85 } else if (GetParent()) {
86 return GetParent()->IsDescendantOf(ancestor
);
92 BrowserAccessibility
* BrowserAccessibility::PlatformGetChild(
93 uint32 child_index
) const {
94 DCHECK(child_index
< PlatformChildCount());
95 BrowserAccessibility
* result
= nullptr;
97 if (HasIntAttribute(ui::AX_ATTR_CHILD_TREE_ID
)) {
98 BrowserAccessibilityManager
* child_manager
=
99 BrowserAccessibilityManager::FromID(
100 GetIntAttribute(ui::AX_ATTR_CHILD_TREE_ID
));
102 result
= child_manager
->GetRoot();
104 result
= InternalGetChild(child_index
);
110 bool BrowserAccessibility::PlatformIsChildOfLeaf() const {
111 BrowserAccessibility
* ancestor
= GetParent();
113 if (ancestor
->PlatformIsLeaf())
115 ancestor
= ancestor
->GetParent();
121 BrowserAccessibility
* BrowserAccessibility::GetPreviousSibling() {
122 if (GetParent() && GetIndexInParent() > 0)
123 return GetParent()->InternalGetChild(GetIndexInParent() - 1);
128 BrowserAccessibility
* BrowserAccessibility::GetNextSibling() {
130 GetIndexInParent() >= 0 &&
131 GetIndexInParent() < static_cast<int>(
132 GetParent()->InternalChildCount() - 1)) {
133 return GetParent()->InternalGetChild(GetIndexInParent() + 1);
139 uint32
BrowserAccessibility::InternalChildCount() const {
140 if (!node_
|| !manager_
)
142 return static_cast<uint32
>(node_
->child_count());
145 BrowserAccessibility
* BrowserAccessibility::InternalGetChild(
146 uint32 child_index
) const {
147 if (!node_
|| !manager_
)
149 return manager_
->GetFromAXNode(node_
->ChildAtIndex(child_index
));
152 BrowserAccessibility
* BrowserAccessibility::GetParent() const {
153 if (!node_
|| !manager_
)
155 ui::AXNode
* parent
= node_
->parent();
157 return manager_
->GetFromAXNode(parent
);
159 return manager_
->GetParentNodeFromParentTree();
162 int32
BrowserAccessibility::GetIndexInParent() const {
163 return node_
? node_
->index_in_parent() : -1;
166 int32
BrowserAccessibility::GetId() const {
167 return node_
? node_
->id() : -1;
170 const ui::AXNodeData
& BrowserAccessibility::GetData() const {
171 CR_DEFINE_STATIC_LOCAL(ui::AXNodeData
, empty_data
, ());
173 return node_
->data();
178 gfx::Rect
BrowserAccessibility::GetLocation() const {
179 return GetData().location
;
182 int32
BrowserAccessibility::GetRole() const {
183 return GetData().role
;
186 int32
BrowserAccessibility::GetState() const {
187 return GetData().state
;
190 const BrowserAccessibility::HtmlAttributes
&
191 BrowserAccessibility::GetHtmlAttributes() const {
192 return GetData().html_attributes
;
195 gfx::Rect
BrowserAccessibility::GetLocalBoundsRect() const {
196 gfx::Rect bounds
= GetLocation();
197 FixEmptyBounds(&bounds
);
198 return ElementBoundsToLocalBounds(bounds
);
201 gfx::Rect
BrowserAccessibility::GetGlobalBoundsRect() const {
202 gfx::Rect bounds
= GetLocalBoundsRect();
204 // Adjust the bounds by the top left corner of the containing view's bounds
205 // in screen coordinates.
206 bounds
.Offset(manager_
->GetViewBounds().OffsetFromOrigin());
211 gfx::Rect
BrowserAccessibility::GetLocalBoundsForRange(int start
, int len
)
213 if (GetRole() != ui::AX_ROLE_STATIC_TEXT
) {
214 // Apply recursively to all static text descendants. For example, if
215 // you call it on a div with two text node children, it just calls
216 // GetLocalBoundsForRange on each of the two children (adjusting
217 // |start| for each one) and unions the resulting rects.
219 for (size_t i
= 0; i
< InternalChildCount(); ++i
) {
220 BrowserAccessibility
* child
= InternalGetChild(i
);
221 int child_len
= child
->GetStaticTextLenRecursive();
222 if (start
< child_len
&& start
+ len
> 0) {
223 gfx::Rect child_rect
= child
->GetLocalBoundsForRange(start
, len
);
224 bounds
.Union(child_rect
);
228 return ElementBoundsToLocalBounds(bounds
);
231 int end
= start
+ len
;
236 for (size_t i
= 0; i
< InternalChildCount() && child_end
< start
+ len
; ++i
) {
237 BrowserAccessibility
* child
= InternalGetChild(i
);
238 if (child
->GetRole() != ui::AX_ROLE_INLINE_TEXT_BOX
) {
239 DLOG(WARNING
) << "BrowserAccessibility objects with role STATIC_TEXT " <<
240 "should have children of role INLINE_TEXT_BOX.";
244 std::string child_text
;
245 child
->GetStringAttribute(ui::AX_ATTR_VALUE
, &child_text
);
246 int child_len
= static_cast<int>(child_text
.size());
247 child_start
= child_end
;
248 child_end
+= child_len
;
250 if (child_end
< start
)
253 int overlap_start
= std::max(start
, child_start
);
254 int overlap_end
= std::min(end
, child_end
);
256 int local_start
= overlap_start
- child_start
;
257 int local_end
= overlap_end
- child_start
;
259 gfx::Rect child_rect
= child
->GetLocation();
260 int text_direction
= child
->GetIntAttribute(
261 ui::AX_ATTR_TEXT_DIRECTION
);
262 const std::vector
<int32
>& character_offsets
= child
->GetIntListAttribute(
263 ui::AX_ATTR_CHARACTER_OFFSETS
);
264 int start_pixel_offset
=
265 local_start
> 0 ? character_offsets
[local_start
- 1] : 0;
266 int end_pixel_offset
=
267 local_end
> 0 ? character_offsets
[local_end
- 1] : 0;
269 gfx::Rect child_overlap_rect
;
270 switch (text_direction
) {
271 case ui::AX_TEXT_DIRECTION_NONE
:
272 case ui::AX_TEXT_DIRECTION_LTR
: {
273 int left
= child_rect
.x() + start_pixel_offset
;
274 int right
= child_rect
.x() + end_pixel_offset
;
275 child_overlap_rect
= gfx::Rect(left
, child_rect
.y(),
276 right
- left
, child_rect
.height());
279 case ui::AX_TEXT_DIRECTION_RTL
: {
280 int right
= child_rect
.right() - start_pixel_offset
;
281 int left
= child_rect
.right() - end_pixel_offset
;
282 child_overlap_rect
= gfx::Rect(left
, child_rect
.y(),
283 right
- left
, child_rect
.height());
286 case ui::AX_TEXT_DIRECTION_TTB
: {
287 int top
= child_rect
.y() + start_pixel_offset
;
288 int bottom
= child_rect
.y() + end_pixel_offset
;
289 child_overlap_rect
= gfx::Rect(child_rect
.x(), top
,
290 child_rect
.width(), bottom
- top
);
293 case ui::AX_TEXT_DIRECTION_BTT
: {
294 int bottom
= child_rect
.bottom() - start_pixel_offset
;
295 int top
= child_rect
.bottom() - end_pixel_offset
;
296 child_overlap_rect
= gfx::Rect(child_rect
.x(), top
,
297 child_rect
.width(), bottom
- top
);
304 if (bounds
.width() == 0 && bounds
.height() == 0)
305 bounds
= child_overlap_rect
;
307 bounds
.Union(child_overlap_rect
);
310 return ElementBoundsToLocalBounds(bounds
);
313 gfx::Rect
BrowserAccessibility::GetGlobalBoundsForRange(int start
, int len
)
315 gfx::Rect bounds
= GetLocalBoundsForRange(start
, len
);
317 // Adjust the bounds by the top left corner of the containing view's bounds
318 // in screen coordinates.
319 bounds
.Offset(manager_
->GetViewBounds().OffsetFromOrigin());
324 int BrowserAccessibility::GetWordStartBoundary(
325 int start
, ui::TextBoundaryDirection direction
) const {
326 DCHECK_GE(start
, -1);
327 // Special offset that indicates that a word boundary has not been found.
328 int word_start_not_found
= GetStaticTextLenRecursive();
329 int word_start
= word_start_not_found
;
332 case ui::AX_ROLE_STATIC_TEXT
: {
333 int prev_word_start
= word_start_not_found
;
337 // Go through the inline text boxes.
338 for (size_t i
= 0; i
< InternalChildCount(); ++i
) {
339 // The next child starts where the previous one ended.
340 child_start
= child_end
;
341 BrowserAccessibility
* child
= InternalGetChild(i
);
342 DCHECK_EQ(child
->GetRole(), ui::AX_ROLE_INLINE_TEXT_BOX
);
343 const std::string
& child_text
= child
->GetStringAttribute(
345 int child_len
= static_cast<int>(child_text
.size());
346 child_end
+= child_len
; // End is one past the last character.
348 const std::vector
<int32
>& word_starts
= child
->GetIntListAttribute(
349 ui::AX_ATTR_WORD_STARTS
);
350 if (word_starts
.empty()) {
351 word_start
= child_end
;
355 int local_start
= start
- child_start
;
356 std::vector
<int32
>::const_iterator iter
= std::upper_bound(
357 word_starts
.begin(), word_starts
.end(), local_start
);
358 if (iter
!= word_starts
.end()) {
359 if (direction
== ui::FORWARDS_DIRECTION
) {
360 word_start
= child_start
+ *iter
;
361 } else if (direction
== ui::BACKWARDS_DIRECTION
) {
362 if (iter
== word_starts
.begin()) {
363 // Return the position of the last word in the previous child.
364 word_start
= prev_word_start
;
366 word_start
= child_start
+ *(iter
- 1);
374 // No word start that is greater than the requested offset has been
376 prev_word_start
= child_start
+ *(iter
- 1);
377 if (direction
== ui::FORWARDS_DIRECTION
) {
378 word_start
= child_end
;
379 } else if (direction
== ui::BACKWARDS_DIRECTION
) {
380 word_start
= prev_word_start
;
388 case ui::AX_ROLE_LINE_BREAK
:
389 // Words never start at a line break.
390 return word_start_not_found
;
393 // If there are no children, the word start boundary is still unknown or
394 // found previously depending on the direction.
395 if (!InternalChildCount())
396 return word_start_not_found
;
399 for (size_t i
= 0; i
< InternalChildCount(); ++i
) {
400 BrowserAccessibility
* child
= InternalGetChild(i
);
401 int child_len
= child
->GetStaticTextLenRecursive();
402 int child_word_start
= child
->GetWordStartBoundary(start
, direction
);
403 if (child_word_start
< child_len
) {
404 // We have found a possible word boundary.
405 word_start
= child_start
+ child_word_start
;
408 // Decide when to stop searching.
409 if ((word_start
!= word_start_not_found
&&
410 direction
== ui::FORWARDS_DIRECTION
) ||
411 (start
< child_len
&&
412 direction
== ui::BACKWARDS_DIRECTION
)) {
416 child_start
+= child_len
;
417 if (start
>= child_len
)
426 BrowserAccessibility
* BrowserAccessibility::BrowserAccessibilityForPoint(
427 const gfx::Point
& point
) {
428 // The best result found that's a child of this object.
429 BrowserAccessibility
* child_result
= NULL
;
430 // The best result that's an indirect descendant like grandchild, etc.
431 BrowserAccessibility
* descendant_result
= NULL
;
433 // Walk the children recursively looking for the BrowserAccessibility that
434 // most tightly encloses the specified point. Walk backwards so that in
435 // the absence of any other information, we assume the object that occurs
436 // later in the tree is on top of one that comes before it.
437 for (int i
= static_cast<int>(PlatformChildCount()) - 1; i
>= 0; --i
) {
438 BrowserAccessibility
* child
= PlatformGetChild(i
);
440 // Skip table columns because cells are only contained in rows,
442 if (child
->GetRole() == ui::AX_ROLE_COLUMN
)
445 if (child
->GetGlobalBoundsRect().Contains(point
)) {
446 BrowserAccessibility
* result
= child
->BrowserAccessibilityForPoint(point
);
447 if (result
== child
&& !child_result
)
448 child_result
= result
;
449 if (result
!= child
&& !descendant_result
)
450 descendant_result
= result
;
453 if (child_result
&& descendant_result
)
457 // Explanation of logic: it's possible that this point overlaps more than
458 // one child of this object. If so, as a heuristic we prefer if the point
459 // overlaps a descendant of one of the two children and not the other.
460 // As an example, suppose you have two rows of buttons - the buttons don't
461 // overlap, but the rows do. Without this heuristic, we'd greedily only
462 // consider one of the containers.
463 if (descendant_result
)
464 return descendant_result
;
471 void BrowserAccessibility::Destroy() {
472 // Allow the object to fire a TextRemoved notification.
473 manager_
->NotifyAccessibilityEvent(ui::AX_EVENT_HIDE
, this);
477 NativeReleaseReference();
480 void BrowserAccessibility::NativeReleaseReference() {
484 bool BrowserAccessibility::HasBoolAttribute(
485 ui::AXBoolAttribute attribute
) const {
486 return GetData().HasBoolAttribute(attribute
);
489 bool BrowserAccessibility::GetBoolAttribute(
490 ui::AXBoolAttribute attribute
) const {
491 return GetData().GetBoolAttribute(attribute
);
494 bool BrowserAccessibility::GetBoolAttribute(
495 ui::AXBoolAttribute attribute
, bool* value
) const {
496 return GetData().GetBoolAttribute(attribute
, value
);
499 bool BrowserAccessibility::HasFloatAttribute(
500 ui::AXFloatAttribute attribute
) const {
501 return GetData().HasFloatAttribute(attribute
);
504 float BrowserAccessibility::GetFloatAttribute(
505 ui::AXFloatAttribute attribute
) const {
506 return GetData().GetFloatAttribute(attribute
);
509 bool BrowserAccessibility::GetFloatAttribute(
510 ui::AXFloatAttribute attribute
, float* value
) const {
511 return GetData().GetFloatAttribute(attribute
, value
);
514 bool BrowserAccessibility::HasIntAttribute(
515 ui::AXIntAttribute attribute
) const {
516 return GetData().HasIntAttribute(attribute
);
519 int BrowserAccessibility::GetIntAttribute(ui::AXIntAttribute attribute
) const {
520 return GetData().GetIntAttribute(attribute
);
523 bool BrowserAccessibility::GetIntAttribute(
524 ui::AXIntAttribute attribute
, int* value
) const {
525 return GetData().GetIntAttribute(attribute
, value
);
528 bool BrowserAccessibility::HasStringAttribute(
529 ui::AXStringAttribute attribute
) const {
530 return GetData().HasStringAttribute(attribute
);
533 const std::string
& BrowserAccessibility::GetStringAttribute(
534 ui::AXStringAttribute attribute
) const {
535 return GetData().GetStringAttribute(attribute
);
538 bool BrowserAccessibility::GetStringAttribute(
539 ui::AXStringAttribute attribute
, std::string
* value
) const {
540 return GetData().GetStringAttribute(attribute
, value
);
543 base::string16
BrowserAccessibility::GetString16Attribute(
544 ui::AXStringAttribute attribute
) const {
545 return GetData().GetString16Attribute(attribute
);
548 bool BrowserAccessibility::GetString16Attribute(
549 ui::AXStringAttribute attribute
,
550 base::string16
* value
) const {
551 return GetData().GetString16Attribute(attribute
, value
);
554 bool BrowserAccessibility::HasIntListAttribute(
555 ui::AXIntListAttribute attribute
) const {
556 return GetData().HasIntListAttribute(attribute
);
559 const std::vector
<int32
>& BrowserAccessibility::GetIntListAttribute(
560 ui::AXIntListAttribute attribute
) const {
561 return GetData().GetIntListAttribute(attribute
);
564 bool BrowserAccessibility::GetIntListAttribute(
565 ui::AXIntListAttribute attribute
,
566 std::vector
<int32
>* value
) const {
567 return GetData().GetIntListAttribute(attribute
, value
);
570 bool BrowserAccessibility::GetHtmlAttribute(
571 const char* html_attr
, std::string
* value
) const {
572 return GetData().GetHtmlAttribute(html_attr
, value
);
575 bool BrowserAccessibility::GetHtmlAttribute(
576 const char* html_attr
, base::string16
* value
) const {
577 return GetData().GetHtmlAttribute(html_attr
, value
);
580 bool BrowserAccessibility::GetAriaTristate(
581 const char* html_attr
,
583 bool* is_mixed
) const {
587 base::string16 value
;
588 if (!GetHtmlAttribute(html_attr
, &value
) ||
590 base::EqualsASCII(value
, "undefined")) {
591 return false; // Not set (and *is_defined is also false)
596 if (base::EqualsASCII(value
, "true"))
599 if (base::EqualsASCII(value
, "mixed"))
602 return false; // Not set
605 bool BrowserAccessibility::HasState(ui::AXState state_enum
) const {
606 return (GetState() >> state_enum
) & 1;
609 bool BrowserAccessibility::IsCellOrTableHeaderRole() const {
610 return (GetRole() == ui::AX_ROLE_CELL
||
611 GetRole() == ui::AX_ROLE_COLUMN_HEADER
||
612 GetRole() == ui::AX_ROLE_ROW_HEADER
);
615 bool BrowserAccessibility::IsEditableText() const {
616 // These roles don't have readonly set, but they're not editable text.
617 if (GetRole() == ui::AX_ROLE_SCROLL_AREA
||
618 GetRole() == ui::AX_ROLE_COLUMN
||
619 GetRole() == ui::AX_ROLE_TABLE_HEADER_CONTAINER
) {
623 // Note: WebAXStateReadonly being false means it's either a text control,
624 // or contenteditable. We also check for editable text roles to cover
625 // another element that has role=textbox set on it.
626 return (!HasState(ui::AX_STATE_READ_ONLY
) ||
627 GetRole() == ui::AX_ROLE_TEXT_FIELD
);
630 bool BrowserAccessibility::IsWebAreaForPresentationalIframe() const {
631 if (GetRole() != ui::AX_ROLE_WEB_AREA
&&
632 GetRole() != ui::AX_ROLE_ROOT_WEB_AREA
) {
636 BrowserAccessibility
* parent
= GetParent();
640 BrowserAccessibility
* grandparent
= parent
->GetParent();
644 return grandparent
->GetRole() == ui::AX_ROLE_IFRAME_PRESENTATIONAL
;
647 bool BrowserAccessibility::IsControl() const {
649 case ui::AX_ROLE_BUTTON
:
650 case ui::AX_ROLE_BUTTON_DROP_DOWN
:
651 case ui::AX_ROLE_CHECK_BOX
:
652 case ui::AX_ROLE_COLOR_WELL
:
653 case ui::AX_ROLE_COMBO_BOX
:
654 case ui::AX_ROLE_DISCLOSURE_TRIANGLE
:
655 case ui::AX_ROLE_LIST_BOX
:
656 case ui::AX_ROLE_MENU_BAR
:
657 case ui::AX_ROLE_MENU_BUTTON
:
658 case ui::AX_ROLE_MENU_ITEM
:
659 case ui::AX_ROLE_MENU_ITEM_CHECK_BOX
:
660 case ui::AX_ROLE_MENU_ITEM_RADIO
:
661 case ui::AX_ROLE_MENU
:
662 case ui::AX_ROLE_POP_UP_BUTTON
:
663 case ui::AX_ROLE_RADIO_BUTTON
:
664 case ui::AX_ROLE_SCROLL_BAR
:
665 case ui::AX_ROLE_SEARCH_BOX
:
666 case ui::AX_ROLE_SLIDER
:
667 case ui::AX_ROLE_SPIN_BUTTON
:
668 case ui::AX_ROLE_SWITCH
:
669 case ui::AX_ROLE_TAB
:
670 case ui::AX_ROLE_TEXT_FIELD
:
671 case ui::AX_ROLE_TOGGLE_BUTTON
:
672 case ui::AX_ROLE_TREE
:
679 int BrowserAccessibility::GetStaticTextLenRecursive() const {
680 if (GetRole() == ui::AX_ROLE_STATIC_TEXT
||
681 GetRole() == ui::AX_ROLE_LINE_BREAK
) {
682 return static_cast<int>(GetStringAttribute(ui::AX_ATTR_VALUE
).size());
686 for (size_t i
= 0; i
< InternalChildCount(); ++i
)
687 len
+= InternalGetChild(i
)->GetStaticTextLenRecursive();
691 void BrowserAccessibility::FixEmptyBounds(gfx::Rect
* bounds
) const
693 if (bounds
->width() > 0 && bounds
->height() > 0)
696 for (size_t i
= 0; i
< InternalChildCount(); ++i
) {
697 // Compute the bounds of each child - this calls FixEmptyBounds
698 // recursively if necessary.
699 BrowserAccessibility
* child
= InternalGetChild(i
);
700 gfx::Rect child_bounds
= child
->GetLocalBoundsRect();
702 // Ignore children that don't have valid bounds themselves.
703 if (child_bounds
.width() == 0 || child_bounds
.height() == 0)
706 // For the first valid child, just set the bounds to that child's bounds.
707 if (bounds
->width() == 0 || bounds
->height() == 0) {
708 *bounds
= child_bounds
;
712 // Union each additional child's bounds.
713 bounds
->Union(child_bounds
);
717 gfx::Rect
BrowserAccessibility::ElementBoundsToLocalBounds(gfx::Rect bounds
)
719 // Walk up the parent chain. Every time we encounter a Web Area, offset
720 // based on the scroll bars and then offset based on the origin of that
722 BrowserAccessibility
* parent
= GetParent();
723 bool need_to_offset_web_area
=
724 (GetRole() == ui::AX_ROLE_WEB_AREA
||
725 GetRole() == ui::AX_ROLE_ROOT_WEB_AREA
);
727 if (need_to_offset_web_area
&&
728 parent
->GetLocation().width() > 0 &&
729 parent
->GetLocation().height() > 0) {
730 bounds
.Offset(parent
->GetLocation().x(), parent
->GetLocation().y());
731 need_to_offset_web_area
= false;
734 // On some platforms, we don't want to take the root scroll offsets
736 if (parent
->GetRole() == ui::AX_ROLE_ROOT_WEB_AREA
&&
737 !manager()->UseRootScrollOffsetsWhenComputingBounds()) {
741 if (parent
->GetRole() == ui::AX_ROLE_WEB_AREA
||
742 parent
->GetRole() == ui::AX_ROLE_ROOT_WEB_AREA
) {
745 if (parent
->GetIntAttribute(ui::AX_ATTR_SCROLL_X
, &sx
) &&
746 parent
->GetIntAttribute(ui::AX_ATTR_SCROLL_Y
, &sy
)) {
747 bounds
.Offset(-sx
, -sy
);
749 need_to_offset_web_area
= true;
751 parent
= parent
->GetParent();
757 } // namespace content