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.
4
5 #include "content/browser/accessibility/browser_accessibility.h"
6
7 #include <cstddef>
8
9 #include <algorithm>
10 #include <iterator>
11
12 #include "base/logging.h"
13 #include "base/no_destructor.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "content/browser/accessibility/browser_accessibility_manager.h"
18 #include "content/browser/accessibility/browser_accessibility_state_impl.h"
19 #include "content/common/ax_serialization_utils.h"
20 #include "content/public/common/content_client.h"
21 #include "content/public/common/use_zoom_for_dsf_policy.h"
22 #include "third_party/blink/public/strings/grit/blink_strings.h"
23 #include "ui/accessibility/ax_enums.mojom.h"
24 #include "ui/accessibility/ax_node_position.h"
25 #include "ui/accessibility/ax_role_properties.h"
26 #include "ui/accessibility/ax_tree_id.h"
27 #include "ui/accessibility/platform/ax_unique_id.h"
28 #include "ui/gfx/geometry/rect_conversions.h"
29 #include "ui/gfx/geometry/rect_f.h"
30
31 namespace content {
32
33 #if !defined(PLATFORM_HAS_NATIVE_ACCESSIBILITY_IMPL)
34 // static
Create()35 BrowserAccessibility* BrowserAccessibility::Create() {
36 return new BrowserAccessibility();
37 }
38 #endif
39
40 // static
FromAXPlatformNodeDelegate(ui::AXPlatformNodeDelegate * delegate)41 BrowserAccessibility* BrowserAccessibility::FromAXPlatformNodeDelegate(
42 ui::AXPlatformNodeDelegate* delegate) {
43 if (!delegate || !delegate->IsWebContent())
44 return nullptr;
45 return static_cast<BrowserAccessibility*>(delegate);
46 }
47
48 BrowserAccessibility::BrowserAccessibility() = default;
49
50 BrowserAccessibility::~BrowserAccessibility() = default;
51
52 namespace {
53
GetTextContainerForPlainTextField(const BrowserAccessibility & text_field)54 const BrowserAccessibility* GetTextContainerForPlainTextField(
55 const BrowserAccessibility& text_field) {
56 DCHECK(text_field.IsPlainTextField());
57 DCHECK_EQ(1u, text_field.InternalChildCount());
58 // Text fields wrap their static text and inline text boxes in generic
59 // containers, and some, like input type=search, wrap the wrapper as well.
60 // Structure is like this:
61 // Text field
62 // -- Generic container
63 // ---- Generic container (optional, only occurs in some controls)
64 // ------ Static text <-- (optional, does not exist if field is empty)
65 // -------- Inline text box children (can be multiple)
66 // This method will return the lowest generic container.
67 const BrowserAccessibility* child = text_field.InternalGetFirstChild();
68 DCHECK_EQ(child->GetRole(), ax::mojom::Role::kGenericContainer);
69 DCHECK_LE(child->InternalChildCount(), 1u);
70 if (child->InternalChildCount() == 1) {
71 const BrowserAccessibility* grand_child = child->InternalGetFirstChild();
72 if (grand_child->GetRole() == ax::mojom::Role::kGenericContainer) {
73 // There is not always a static text child of the grandchild, but if there
74 // is, it must be static text.
75 DCHECK(!grand_child->InternalGetFirstChild() ||
76 grand_child->InternalGetFirstChild()->GetRole() ==
77 ax::mojom::Role::kStaticText);
78 return grand_child;
79 }
80 DCHECK_EQ(child->InternalGetFirstChild()->GetRole(),
81 ax::mojom::Role::kStaticText);
82 }
83 return child;
84 }
85
GetBoundaryTextOffsetInsideBaseAnchor(ax::mojom::MoveDirection direction,const BrowserAccessibilityPosition::AXPositionInstance & base,const BrowserAccessibilityPosition::AXPositionInstance & position)86 int GetBoundaryTextOffsetInsideBaseAnchor(
87 ax::mojom::MoveDirection direction,
88 const BrowserAccessibilityPosition::AXPositionInstance& base,
89 const BrowserAccessibilityPosition::AXPositionInstance& position) {
90 if (base->GetAnchor() == position->GetAnchor())
91 return position->text_offset();
92
93 // If the position is outside the anchor of the base position, then return
94 // the first or last position in the same direction.
95 switch (direction) {
96 case ax::mojom::MoveDirection::kNone:
97 NOTREACHED();
98 return position->text_offset();
99 case ax::mojom::MoveDirection::kBackward:
100 return base->CreatePositionAtStartOfAnchor()->text_offset();
101 case ax::mojom::MoveDirection::kForward:
102 return base->CreatePositionAtEndOfAnchor()->text_offset();
103 }
104 }
105
106 } // namespace
107
Init(BrowserAccessibilityManager * manager,ui::AXNode * node)108 void BrowserAccessibility::Init(BrowserAccessibilityManager* manager,
109 ui::AXNode* node) {
110 DCHECK(manager);
111 DCHECK(node);
112 manager_ = manager;
113 node_ = node;
114 }
115
PlatformIsLeaf() const116 bool BrowserAccessibility::PlatformIsLeaf() const {
117 // TODO(nektar): Remove in favor of IsLeaf.
118 return IsLeaf();
119 }
120
CanFireEvents() const121 bool BrowserAccessibility::CanFireEvents() const {
122 // Allow events unless this object would be trimmed away.
123 return !IsChildOfLeaf();
124 }
125
GetAXPlatformNode() const126 ui::AXPlatformNode* BrowserAccessibility::GetAXPlatformNode() const {
127 // Not all BrowserAccessibility subclasses can return an AXPlatformNode yet.
128 // So, here we just return nullptr.
129 return nullptr;
130 }
131
PlatformChildCount() const132 uint32_t BrowserAccessibility::PlatformChildCount() const {
133 if (PlatformIsLeaf())
134 return 0;
135 return PlatformGetRootOfChildTree() ? 1 : InternalChildCount();
136 }
137
PlatformGetParent() const138 BrowserAccessibility* BrowserAccessibility::PlatformGetParent() const {
139 ui::AXNode* parent = node()->GetUnignoredParent();
140 if (parent)
141 return manager()->GetFromAXNode(parent);
142
143 return manager()->GetParentNodeFromParentTree();
144 }
145
PlatformGetFirstChild() const146 BrowserAccessibility* BrowserAccessibility::PlatformGetFirstChild() const {
147 return PlatformGetChild(0);
148 }
149
PlatformGetLastChild() const150 BrowserAccessibility* BrowserAccessibility::PlatformGetLastChild() const {
151 BrowserAccessibility* child_tree_root = PlatformGetRootOfChildTree();
152 return child_tree_root ? child_tree_root : InternalGetLastChild();
153 }
154
PlatformGetNextSibling() const155 BrowserAccessibility* BrowserAccessibility::PlatformGetNextSibling() const {
156 return InternalGetNextSibling();
157 }
158
PlatformGetPreviousSibling() const159 BrowserAccessibility* BrowserAccessibility::PlatformGetPreviousSibling() const {
160 return InternalGetPreviousSibling();
161 }
162
163 BrowserAccessibility::PlatformChildIterator
PlatformChildrenBegin() const164 BrowserAccessibility::PlatformChildrenBegin() const {
165 return PlatformChildIterator(this, PlatformGetFirstChild());
166 }
167
168 BrowserAccessibility::PlatformChildIterator
PlatformChildrenEnd() const169 BrowserAccessibility::PlatformChildrenEnd() const {
170 return PlatformChildIterator(this, nullptr);
171 }
172
PlatformGetSelectionContainer() const173 BrowserAccessibility* BrowserAccessibility::PlatformGetSelectionContainer()
174 const {
175 BrowserAccessibility* container = PlatformGetParent();
176 while (container &&
177 !ui::IsContainerWithSelectableChildren(container->GetRole())) {
178 container = container->PlatformGetParent();
179 }
180 return container;
181 }
182
IsDescendantOf(const BrowserAccessibility * ancestor) const183 bool BrowserAccessibility::IsDescendantOf(
184 const BrowserAccessibility* ancestor) const {
185 if (!ancestor)
186 return false;
187
188 if (this == ancestor)
189 return true;
190
191 if (PlatformGetParent())
192 return PlatformGetParent()->IsDescendantOf(ancestor);
193
194 return false;
195 }
196
IsDocument() const197 bool BrowserAccessibility::IsDocument() const {
198 return ui::IsDocument(GetRole());
199 }
200
IsIgnored() const201 bool BrowserAccessibility::IsIgnored() const {
202 return node()->IsIgnored();
203 }
204
IsLineBreakObject() const205 bool BrowserAccessibility::IsLineBreakObject() const {
206 return node()->IsLineBreak();
207 }
208
PlatformGetChild(uint32_t child_index) const209 BrowserAccessibility* BrowserAccessibility::PlatformGetChild(
210 uint32_t child_index) const {
211 BrowserAccessibility* child_tree_root = PlatformGetRootOfChildTree();
212 if (child_tree_root) {
213 // A node with a child tree has only one child.
214 return child_index ? nullptr : child_tree_root;
215 }
216 return InternalGetChild(child_index);
217 }
218
PlatformGetClosestPlatformObject() const219 BrowserAccessibility* BrowserAccessibility::PlatformGetClosestPlatformObject()
220 const {
221 BrowserAccessibility* platform_object =
222 const_cast<BrowserAccessibility*>(this);
223 while (platform_object && platform_object->IsChildOfLeaf())
224 platform_object = platform_object->InternalGetParent();
225
226 DCHECK(platform_object);
227 return platform_object;
228 }
229
IsPreviousSiblingOnSameLine() const230 bool BrowserAccessibility::IsPreviousSiblingOnSameLine() const {
231 const BrowserAccessibility* previous_sibling = PlatformGetPreviousSibling();
232 if (!previous_sibling)
233 return false;
234
235 // Line linkage information might not be provided on non-leaf objects.
236 const BrowserAccessibility* leaf_object = PlatformDeepestFirstChild();
237 if (!leaf_object)
238 leaf_object = this;
239
240 int32_t previous_on_line_id;
241 if (leaf_object->GetIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
242 &previous_on_line_id)) {
243 const BrowserAccessibility* previous_on_line =
244 manager()->GetFromID(previous_on_line_id);
245 // In the case of a static text sibling, the object designated to be the
246 // previous object on this line might be one of its children, i.e. the last
247 // inline text box.
248 return previous_on_line &&
249 previous_on_line->IsDescendantOf(previous_sibling);
250 }
251 return false;
252 }
253
IsNextSiblingOnSameLine() const254 bool BrowserAccessibility::IsNextSiblingOnSameLine() const {
255 const BrowserAccessibility* next_sibling = PlatformGetNextSibling();
256 if (!next_sibling)
257 return false;
258
259 // Line linkage information might not be provided on non-leaf objects.
260 const BrowserAccessibility* leaf_object = PlatformDeepestLastChild();
261 if (!leaf_object)
262 leaf_object = this;
263
264 int32_t next_on_line_id;
265 if (leaf_object->GetIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
266 &next_on_line_id)) {
267 const BrowserAccessibility* next_on_line =
268 manager()->GetFromID(next_on_line_id);
269 // In the case of a static text sibling, the object designated to be the
270 // next object on this line might be one of its children, i.e. the first
271 // inline text box.
272 return next_on_line && next_on_line->IsDescendantOf(next_sibling);
273 }
274 return false;
275 }
276
PlatformDeepestFirstChild() const277 BrowserAccessibility* BrowserAccessibility::PlatformDeepestFirstChild() const {
278 if (!PlatformChildCount())
279 return nullptr;
280
281 BrowserAccessibility* deepest_child = PlatformGetFirstChild();
282 while (deepest_child->PlatformChildCount())
283 deepest_child = deepest_child->PlatformGetFirstChild();
284
285 return deepest_child;
286 }
287
PlatformDeepestLastChild() const288 BrowserAccessibility* BrowserAccessibility::PlatformDeepestLastChild() const {
289 if (!PlatformChildCount())
290 return nullptr;
291
292 BrowserAccessibility* deepest_child = PlatformGetLastChild();
293 while (deepest_child->PlatformChildCount()) {
294 deepest_child = deepest_child->PlatformGetLastChild();
295 }
296
297 return deepest_child;
298 }
299
InternalDeepestFirstChild() const300 BrowserAccessibility* BrowserAccessibility::InternalDeepestFirstChild() const {
301 if (!InternalChildCount())
302 return nullptr;
303
304 BrowserAccessibility* deepest_child = InternalGetFirstChild();
305 while (deepest_child->InternalChildCount())
306 deepest_child = deepest_child->InternalGetFirstChild();
307
308 return deepest_child;
309 }
310
InternalDeepestLastChild() const311 BrowserAccessibility* BrowserAccessibility::InternalDeepestLastChild() const {
312 if (!InternalChildCount())
313 return nullptr;
314
315 BrowserAccessibility* deepest_child = InternalGetLastChild();
316 while (deepest_child->InternalChildCount())
317 deepest_child = deepest_child->InternalGetLastChild();
318
319 return deepest_child;
320 }
321
InternalChildCount() const322 uint32_t BrowserAccessibility::InternalChildCount() const {
323 return node_->GetUnignoredChildCount();
324 }
325
InternalGetChild(uint32_t child_index) const326 BrowserAccessibility* BrowserAccessibility::InternalGetChild(
327 uint32_t child_index) const {
328 ui::AXNode* child_node = node_->GetUnignoredChildAtIndex(child_index);
329 if (!child_node)
330 return nullptr;
331
332 return manager_->GetFromAXNode(child_node);
333 }
334
InternalGetParent() const335 BrowserAccessibility* BrowserAccessibility::InternalGetParent() const {
336 ui::AXNode* child_node = node_->GetUnignoredParent();
337 if (!child_node)
338 return nullptr;
339
340 return manager_->GetFromAXNode(child_node);
341 }
342
InternalGetFirstChild() const343 BrowserAccessibility* BrowserAccessibility::InternalGetFirstChild() const {
344 return InternalGetChild(0);
345 }
346
InternalGetLastChild() const347 BrowserAccessibility* BrowserAccessibility::InternalGetLastChild() const {
348 ui::AXNode* child_node = node_->GetLastUnignoredChild();
349 if (!child_node)
350 return nullptr;
351
352 return manager_->GetFromAXNode(child_node);
353 }
354
InternalGetNextSibling() const355 BrowserAccessibility* BrowserAccessibility::InternalGetNextSibling() const {
356 ui::AXNode* child_node = node_->GetNextUnignoredSibling();
357 if (!child_node)
358 return nullptr;
359
360 return manager_->GetFromAXNode(child_node);
361 }
362
InternalGetPreviousSibling() const363 BrowserAccessibility* BrowserAccessibility::InternalGetPreviousSibling() const {
364 ui::AXNode* child_node = node_->GetPreviousUnignoredSibling();
365 if (!child_node)
366 return nullptr;
367
368 return manager_->GetFromAXNode(child_node);
369 }
370
371 BrowserAccessibility::InternalChildIterator
InternalChildrenBegin() const372 BrowserAccessibility::InternalChildrenBegin() const {
373 return InternalChildIterator(this, InternalGetFirstChild());
374 }
375
376 BrowserAccessibility::InternalChildIterator
InternalChildrenEnd() const377 BrowserAccessibility::InternalChildrenEnd() const {
378 return InternalChildIterator(this, nullptr);
379 }
380
GetId() const381 int32_t BrowserAccessibility::GetId() const {
382 return node()->id();
383 }
384
GetLocation() const385 gfx::RectF BrowserAccessibility::GetLocation() const {
386 return GetData().relative_bounds.bounds;
387 }
388
GetRole() const389 ax::mojom::Role BrowserAccessibility::GetRole() const {
390 return GetData().role;
391 }
392
GetState() const393 int32_t BrowserAccessibility::GetState() const {
394 return GetData().state;
395 }
396
397 const BrowserAccessibility::HtmlAttributes&
GetHtmlAttributes() const398 BrowserAccessibility::GetHtmlAttributes() const {
399 return GetData().html_attributes;
400 }
401
GetClippedScreenBoundsRect(ui::AXOffscreenResult * offscreen_result) const402 gfx::Rect BrowserAccessibility::GetClippedScreenBoundsRect(
403 ui::AXOffscreenResult* offscreen_result) const {
404 return GetBoundsRect(ui::AXCoordinateSystem::kScreenDIPs,
405 ui::AXClippingBehavior::kClipped, offscreen_result);
406 }
407
GetUnclippedScreenBoundsRect(ui::AXOffscreenResult * offscreen_result) const408 gfx::Rect BrowserAccessibility::GetUnclippedScreenBoundsRect(
409 ui::AXOffscreenResult* offscreen_result) const {
410 return GetBoundsRect(ui::AXCoordinateSystem::kScreenDIPs,
411 ui::AXClippingBehavior::kUnclipped, offscreen_result);
412 }
413
GetClippedRootFrameBoundsRect(ui::AXOffscreenResult * offscreen_result) const414 gfx::Rect BrowserAccessibility::GetClippedRootFrameBoundsRect(
415 ui::AXOffscreenResult* offscreen_result) const {
416 return GetBoundsRect(ui::AXCoordinateSystem::kRootFrame,
417 ui::AXClippingBehavior::kClipped, offscreen_result);
418 }
419
GetUnclippedRootFrameBoundsRect(ui::AXOffscreenResult * offscreen_result) const420 gfx::Rect BrowserAccessibility::GetUnclippedRootFrameBoundsRect(
421 ui::AXOffscreenResult* offscreen_result) const {
422 return GetBoundsRect(ui::AXCoordinateSystem::kRootFrame,
423 ui::AXClippingBehavior::kUnclipped, offscreen_result);
424 }
425
GetClippedFrameBoundsRect(ui::AXOffscreenResult * offscreen_result) const426 gfx::Rect BrowserAccessibility::GetClippedFrameBoundsRect(
427 ui::AXOffscreenResult* offscreen_result) const {
428 return GetBoundsRect(ui::AXCoordinateSystem::kFrame,
429 ui::AXClippingBehavior::kUnclipped, offscreen_result);
430 }
431
GetUnclippedRootFrameHypertextRangeBoundsRect(const int start_offset,const int end_offset,ui::AXOffscreenResult * offscreen_result) const432 gfx::Rect BrowserAccessibility::GetUnclippedRootFrameHypertextRangeBoundsRect(
433 const int start_offset,
434 const int end_offset,
435 ui::AXOffscreenResult* offscreen_result) const {
436 return GetHypertextRangeBoundsRect(
437 start_offset, end_offset, ui::AXCoordinateSystem::kRootFrame,
438 ui::AXClippingBehavior::kUnclipped, offscreen_result);
439 }
440
GetUnclippedScreenInnerTextRangeBoundsRect(const int start_offset,const int end_offset,ui::AXOffscreenResult * offscreen_result) const441 gfx::Rect BrowserAccessibility::GetUnclippedScreenInnerTextRangeBoundsRect(
442 const int start_offset,
443 const int end_offset,
444 ui::AXOffscreenResult* offscreen_result) const {
445 return GetInnerTextRangeBoundsRect(
446 start_offset, end_offset, ui::AXCoordinateSystem::kScreenDIPs,
447 ui::AXClippingBehavior::kUnclipped, offscreen_result);
448 }
449
GetUnclippedRootFrameInnerTextRangeBoundsRect(const int start_offset,const int end_offset,ui::AXOffscreenResult * offscreen_result) const450 gfx::Rect BrowserAccessibility::GetUnclippedRootFrameInnerTextRangeBoundsRect(
451 const int start_offset,
452 const int end_offset,
453 ui::AXOffscreenResult* offscreen_result) const {
454 return GetInnerTextRangeBoundsRect(
455 start_offset, end_offset, ui::AXCoordinateSystem::kRootFrame,
456 ui::AXClippingBehavior::kUnclipped, offscreen_result);
457 }
458
GetBoundsRect(const ui::AXCoordinateSystem coordinate_system,const ui::AXClippingBehavior clipping_behavior,ui::AXOffscreenResult * offscreen_result) const459 gfx::Rect BrowserAccessibility::GetBoundsRect(
460 const ui::AXCoordinateSystem coordinate_system,
461 const ui::AXClippingBehavior clipping_behavior,
462 ui::AXOffscreenResult* offscreen_result) const {
463 return RelativeToAbsoluteBounds(gfx::RectF(), coordinate_system,
464 clipping_behavior, offscreen_result);
465 }
466
GetHypertextRangeBoundsRect(const int start_offset,const int end_offset,const ui::AXCoordinateSystem coordinate_system,const ui::AXClippingBehavior clipping_behavior,ui::AXOffscreenResult * offscreen_result) const467 gfx::Rect BrowserAccessibility::GetHypertextRangeBoundsRect(
468 const int start_offset,
469 const int end_offset,
470 const ui::AXCoordinateSystem coordinate_system,
471 const ui::AXClippingBehavior clipping_behavior,
472 ui::AXOffscreenResult* offscreen_result) const {
473 int effective_start_offset = start_offset;
474 int effective_end_offset = end_offset;
475
476 if (effective_start_offset == effective_end_offset)
477 return gfx::Rect();
478 if (effective_start_offset > effective_end_offset)
479 std::swap(effective_start_offset, effective_end_offset);
480
481 const base::string16& text_str = GetHypertext();
482 if (effective_start_offset < 0 ||
483 effective_start_offset >= static_cast<int>(text_str.size()))
484 return gfx::Rect();
485 if (effective_end_offset < 0 ||
486 effective_end_offset > static_cast<int>(text_str.size()))
487 return gfx::Rect();
488
489 if (coordinate_system == ui::AXCoordinateSystem::kFrame) {
490 NOTIMPLEMENTED();
491 return gfx::Rect();
492 }
493
494 // Obtain bounds in root frame coordinates.
495 gfx::Rect bounds = GetRootFrameHypertextRangeBoundsRect(
496 effective_start_offset, effective_end_offset - effective_start_offset,
497 clipping_behavior, offscreen_result);
498
499 if (coordinate_system == ui::AXCoordinateSystem::kScreenDIPs ||
500 coordinate_system == ui::AXCoordinateSystem::kScreenPhysicalPixels) {
501 // Convert to screen coordinates.
502 bounds.Offset(
503 manager()->GetViewBoundsInScreenCoordinates().OffsetFromOrigin());
504 }
505
506 if (coordinate_system == ui::AXCoordinateSystem::kScreenPhysicalPixels) {
507 // Convert to physical pixels.
508 if (!IsUseZoomForDSFEnabled()) {
509 bounds =
510 gfx::ScaleToEnclosingRect(bounds, manager()->device_scale_factor());
511 }
512 }
513
514 return bounds;
515 }
516
GetRootFrameHypertextRangeBoundsRect(int start,int len,const ui::AXClippingBehavior clipping_behavior,ui::AXOffscreenResult * offscreen_result) const517 gfx::Rect BrowserAccessibility::GetRootFrameHypertextRangeBoundsRect(
518 int start,
519 int len,
520 const ui::AXClippingBehavior clipping_behavior,
521 ui::AXOffscreenResult* offscreen_result) const {
522 DCHECK_GE(start, 0);
523 DCHECK_GE(len, 0);
524
525 // Standard text fields such as textarea have an embedded div inside them that
526 // holds all the text.
527 // TODO(nektar): This is fragile! Replace with code that flattens tree.
528 if (IsPlainTextField() && InternalChildCount() == 1) {
529 return GetTextContainerForPlainTextField(*this)
530 ->GetRootFrameHypertextRangeBoundsRect(start, len, clipping_behavior,
531 offscreen_result);
532 }
533
534 if (GetRole() != ax::mojom::Role::kStaticText) {
535 gfx::Rect bounds;
536 for (InternalChildIterator it = InternalChildrenBegin();
537 it != InternalChildrenEnd() && len > 0; ++it) {
538 const BrowserAccessibility* child = it.get();
539 // Child objects are of length one, since they are represented by a single
540 // embedded object character. The exception is text-only objects.
541 int child_length_in_parent = 1;
542 if (child->IsText())
543 child_length_in_parent = static_cast<int>(child->GetHypertext().size());
544 if (start < child_length_in_parent) {
545 gfx::Rect child_rect;
546 if (child->IsText()) {
547 child_rect = child->GetRootFrameHypertextRangeBoundsRect(
548 start, len, clipping_behavior, offscreen_result);
549 } else {
550 child_rect = child->GetRootFrameHypertextRangeBoundsRect(
551 0, static_cast<int>(child->GetHypertext().size()),
552 clipping_behavior, offscreen_result);
553 }
554 bounds.Union(child_rect);
555 len -= (child_length_in_parent - start);
556 }
557 if (start > child_length_in_parent)
558 start -= child_length_in_parent;
559 else
560 start = 0;
561 }
562 // When past the end of text, the area will be 0.
563 // In this case, use bounds provided for the caret.
564 return bounds.IsEmpty() ? GetRootFrameHypertextBoundsPastEndOfText(
565 clipping_behavior, offscreen_result)
566 : bounds;
567 }
568
569 int end = start + len;
570 int child_start = 0;
571 int child_end = 0;
572 gfx::Rect bounds;
573 for (InternalChildIterator it = InternalChildrenBegin();
574 it != InternalChildrenEnd() && child_end < start + len; ++it) {
575 const BrowserAccessibility* child = it.get();
576 if (child->GetRole() != ax::mojom::Role::kInlineTextBox) {
577 DLOG(WARNING) << "BrowserAccessibility objects with role STATIC_TEXT "
578 << "should have children of role INLINE_TEXT_BOX.\n";
579 continue;
580 }
581
582 int child_length = static_cast<int>(child->GetHypertext().size());
583 child_start = child_end;
584 child_end += child_length;
585
586 if (child_end < start)
587 continue;
588
589 int overlap_start = std::max(start, child_start);
590 int overlap_end = std::min(end, child_end);
591
592 int local_start = overlap_start - child_start;
593 int local_end = overlap_end - child_start;
594 // |local_end| and |local_start| may equal |child_length| when the caret is
595 // at the end of a text field.
596 DCHECK_GE(local_start, 0);
597 DCHECK_LE(local_start, child_length);
598 DCHECK_GE(local_end, 0);
599 DCHECK_LE(local_end, child_length);
600
601 // Don't clip bounds. Some screen magnifiers (e.g. ZoomText) prefer to
602 // get unclipped bounds so that they can make smooth scrolling calculations.
603 gfx::Rect absolute_child_rect = child->RelativeToAbsoluteBounds(
604 child->GetInlineTextRect(local_start, local_end, child_length),
605 ui::AXCoordinateSystem::kRootFrame, clipping_behavior,
606 offscreen_result);
607 if (bounds.width() == 0 && bounds.height() == 0) {
608 bounds = absolute_child_rect;
609 } else {
610 bounds.Union(absolute_child_rect);
611 }
612 }
613
614 return bounds;
615 }
616
GetScreenHypertextRangeBoundsRect(int start,int len,const ui::AXClippingBehavior clipping_behavior,ui::AXOffscreenResult * offscreen_result) const617 gfx::Rect BrowserAccessibility::GetScreenHypertextRangeBoundsRect(
618 int start,
619 int len,
620 const ui::AXClippingBehavior clipping_behavior,
621 ui::AXOffscreenResult* offscreen_result) const {
622 gfx::Rect bounds = GetRootFrameHypertextRangeBoundsRect(
623 start, len, clipping_behavior, offscreen_result);
624
625 // Adjust the bounds by the top left corner of the containing view's bounds
626 // in screen coordinates.
627 bounds.Offset(
628 manager_->GetViewBoundsInScreenCoordinates().OffsetFromOrigin());
629
630 return bounds;
631 }
632
GetRootFrameHypertextBoundsPastEndOfText(const ui::AXClippingBehavior clipping_behavior,ui::AXOffscreenResult * offscreen_result) const633 gfx::Rect BrowserAccessibility::GetRootFrameHypertextBoundsPastEndOfText(
634 const ui::AXClippingBehavior clipping_behavior,
635 ui::AXOffscreenResult* offscreen_result) const {
636 // Step 1: get approximate caret bounds. The thickness may not yet be correct.
637 gfx::Rect bounds;
638 if (InternalChildCount() > 0) {
639 // When past the end of text, use bounds provided by a last child if
640 // available, and then correct for thickness of caret.
641 BrowserAccessibility* child = InternalGetLastChild();
642 int child_text_len = child->GetHypertext().size();
643 bounds = child->GetRootFrameHypertextRangeBoundsRect(
644 child_text_len, child_text_len, clipping_behavior, offscreen_result);
645 if (bounds.width() == 0 && bounds.height() == 0)
646 return bounds; // Inline text boxes info not yet available.
647 } else {
648 // Compute bounds of where caret would be, based on bounds of object.
649 bounds = GetBoundsRect(ui::AXCoordinateSystem::kRootFrame,
650 clipping_behavior, offscreen_result);
651 }
652
653 // Step 2: correct for the thickness of the caret.
654 auto text_direction = static_cast<ax::mojom::WritingDirection>(
655 GetIntAttribute(ax::mojom::IntAttribute::kTextDirection));
656 constexpr int kCaretThickness = 1;
657 switch (text_direction) {
658 case ax::mojom::WritingDirection::kNone:
659 case ax::mojom::WritingDirection::kLtr: {
660 bounds.set_width(kCaretThickness);
661 break;
662 }
663 case ax::mojom::WritingDirection::kRtl: {
664 bounds.set_x(bounds.right() - kCaretThickness);
665 bounds.set_width(kCaretThickness);
666 break;
667 }
668 case ax::mojom::WritingDirection::kTtb: {
669 bounds.set_height(kCaretThickness);
670 break;
671 }
672 case ax::mojom::WritingDirection::kBtt: {
673 bounds.set_y(bounds.bottom() - kCaretThickness);
674 bounds.set_height(kCaretThickness);
675 break;
676 }
677 }
678 return bounds;
679 }
680
GetInnerTextRangeBoundsRect(const int start_offset,const int end_offset,const ui::AXCoordinateSystem coordinate_system,const ui::AXClippingBehavior clipping_behavior,ui::AXOffscreenResult * offscreen_result) const681 gfx::Rect BrowserAccessibility::GetInnerTextRangeBoundsRect(
682 const int start_offset,
683 const int end_offset,
684 const ui::AXCoordinateSystem coordinate_system,
685 const ui::AXClippingBehavior clipping_behavior,
686 ui::AXOffscreenResult* offscreen_result) const {
687 const int inner_text_length = GetInnerText().length();
688 if (start_offset < 0 || end_offset > inner_text_length ||
689 start_offset > end_offset)
690 return gfx::Rect();
691
692 return GetInnerTextRangeBoundsRectInSubtree(
693 start_offset, end_offset, coordinate_system, clipping_behavior,
694 offscreen_result);
695 }
696
GetInnerTextRangeBoundsRectInSubtree(const int start_offset,const int end_offset,const ui::AXCoordinateSystem coordinate_system,const ui::AXClippingBehavior clipping_behavior,ui::AXOffscreenResult * offscreen_result) const697 gfx::Rect BrowserAccessibility::GetInnerTextRangeBoundsRectInSubtree(
698 const int start_offset,
699 const int end_offset,
700 const ui::AXCoordinateSystem coordinate_system,
701 const ui::AXClippingBehavior clipping_behavior,
702 ui::AXOffscreenResult* offscreen_result) const {
703 if (GetRole() == ax::mojom::Role::kInlineTextBox) {
704 return RelativeToAbsoluteBounds(
705 GetInlineTextRect(start_offset, end_offset, GetInnerText().length()),
706 coordinate_system, clipping_behavior, offscreen_result);
707 }
708
709 gfx::Rect bounds;
710 int child_offset_in_parent = 0;
711 for (InternalChildIterator it = InternalChildrenBegin();
712 it != InternalChildrenEnd(); ++it) {
713 const BrowserAccessibility* browser_accessibility_child = it.get();
714 const int child_inner_text_length =
715 browser_accessibility_child->GetInnerText().length();
716
717 // The text bounds queried are not in this subtree; skip it and continue.
718 const int child_start_offset =
719 std::max(start_offset - child_offset_in_parent, 0);
720 if (child_start_offset > child_inner_text_length) {
721 child_offset_in_parent += child_inner_text_length;
722 continue;
723 }
724
725 // The text bounds queried have already been gathered; short circuit.
726 const int child_end_offset =
727 std::min(end_offset - child_offset_in_parent, child_inner_text_length);
728 if (child_end_offset < 0)
729 return bounds;
730
731 // Increase the text bounds by the subtree text bounds.
732 const gfx::Rect child_bounds =
733 browser_accessibility_child->GetInnerTextRangeBoundsRectInSubtree(
734 child_start_offset, child_end_offset, coordinate_system,
735 clipping_behavior, offscreen_result);
736 if (bounds.IsEmpty())
737 bounds = child_bounds;
738 else
739 bounds.Union(child_bounds);
740
741 child_offset_in_parent += child_inner_text_length;
742 }
743
744 return bounds;
745 }
746
GetInlineTextRect(const int start_offset,const int end_offset,const int max_length) const747 gfx::RectF BrowserAccessibility::GetInlineTextRect(const int start_offset,
748 const int end_offset,
749 const int max_length) const {
750 DCHECK(start_offset >= 0 && end_offset >= 0 && start_offset <= end_offset);
751 int local_start_offset = start_offset, local_end_offset = end_offset;
752 const std::vector<int32_t>& character_offsets =
753 GetIntListAttribute(ax::mojom::IntListAttribute::kCharacterOffsets);
754 const int character_offsets_length = character_offsets.size();
755 if (character_offsets_length < max_length) {
756 // Blink might not return pixel offsets for all characters. Clamp the
757 // character range to be within the number of provided pixels.
758 local_start_offset = std::min(local_start_offset, character_offsets_length);
759 local_end_offset = std::min(local_end_offset, character_offsets_length);
760 }
761
762 const int start_pixel_offset =
763 local_start_offset > 0 ? character_offsets[local_start_offset - 1] : 0;
764 const int end_pixel_offset =
765 local_end_offset > 0 ? character_offsets[local_end_offset - 1] : 0;
766 const int max_pixel_offset =
767 character_offsets_length > 0
768 ? character_offsets[character_offsets_length - 1]
769 : 0;
770 const gfx::RectF location = GetLocation();
771 const int location_width = location.width();
772 const int location_height = location.height();
773
774 gfx::RectF bounds;
775 switch (static_cast<ax::mojom::WritingDirection>(
776 GetIntAttribute(ax::mojom::IntAttribute::kTextDirection))) {
777 case ax::mojom::WritingDirection::kNone:
778 case ax::mojom::WritingDirection::kLtr:
779 bounds =
780 gfx::RectF(start_pixel_offset, 0,
781 end_pixel_offset - start_pixel_offset, location_height);
782 break;
783 case ax::mojom::WritingDirection::kRtl: {
784 const int left = max_pixel_offset - end_pixel_offset;
785 const int right = max_pixel_offset - start_pixel_offset;
786 bounds = gfx::RectF(left, 0, right - left, location_height);
787 break;
788 }
789 case ax::mojom::WritingDirection::kTtb:
790 bounds = gfx::RectF(0, start_pixel_offset, location_width,
791 end_pixel_offset - start_pixel_offset);
792 break;
793 case ax::mojom::WritingDirection::kBtt: {
794 const int top = max_pixel_offset - end_pixel_offset;
795 const int bottom = max_pixel_offset - start_pixel_offset;
796 bounds = gfx::RectF(0, top, location_width, bottom - top);
797 break;
798 }
799 }
800
801 return bounds;
802 }
803
ApproximateHitTest(const gfx::Point & blink_screen_point)804 BrowserAccessibility* BrowserAccessibility::ApproximateHitTest(
805 const gfx::Point& blink_screen_point) {
806 // The best result found that's a child of this object.
807 BrowserAccessibility* child_result = nullptr;
808 // The best result that's an indirect descendant like grandchild, etc.
809 BrowserAccessibility* descendant_result = nullptr;
810
811 // Walk the children recursively looking for the BrowserAccessibility that
812 // most tightly encloses the specified point. Walk backwards so that in
813 // the absence of any other information, we assume the object that occurs
814 // later in the tree is on top of one that comes before it.
815 for (int i = static_cast<int>(PlatformChildCount()) - 1; i >= 0; --i) {
816 BrowserAccessibility* child = PlatformGetChild(i);
817
818 // Skip table columns because cells are only contained in rows,
819 // not columns.
820 if (child->GetRole() == ax::mojom::Role::kColumn)
821 continue;
822
823 if (child->GetClippedScreenBoundsRect().Contains(blink_screen_point)) {
824 BrowserAccessibility* result =
825 child->ApproximateHitTest(blink_screen_point);
826 if (result == child && !child_result)
827 child_result = result;
828 if (result != child && !descendant_result)
829 descendant_result = result;
830 }
831
832 if (child_result && descendant_result)
833 break;
834 }
835
836 // Explanation of logic: it's possible that this point overlaps more than
837 // one child of this object. If so, as a heuristic we prefer if the point
838 // overlaps a descendant of one of the two children and not the other.
839 // As an example, suppose you have two rows of buttons - the buttons don't
840 // overlap, but the rows do. Without this heuristic, we'd greedily only
841 // consider one of the containers.
842 if (descendant_result)
843 return descendant_result;
844 if (child_result)
845 return child_result;
846
847 return this;
848 }
849
HasBoolAttribute(ax::mojom::BoolAttribute attribute) const850 bool BrowserAccessibility::HasBoolAttribute(
851 ax::mojom::BoolAttribute attribute) const {
852 return GetData().HasBoolAttribute(attribute);
853 }
854
GetBoolAttribute(ax::mojom::BoolAttribute attribute) const855 bool BrowserAccessibility::GetBoolAttribute(
856 ax::mojom::BoolAttribute attribute) const {
857 return GetData().GetBoolAttribute(attribute);
858 }
859
GetBoolAttribute(ax::mojom::BoolAttribute attribute,bool * value) const860 bool BrowserAccessibility::GetBoolAttribute(ax::mojom::BoolAttribute attribute,
861 bool* value) const {
862 return GetData().GetBoolAttribute(attribute, value);
863 }
864
HasFloatAttribute(ax::mojom::FloatAttribute attribute) const865 bool BrowserAccessibility::HasFloatAttribute(
866 ax::mojom::FloatAttribute attribute) const {
867 return GetData().HasFloatAttribute(attribute);
868 }
869
GetFloatAttribute(ax::mojom::FloatAttribute attribute) const870 float BrowserAccessibility::GetFloatAttribute(
871 ax::mojom::FloatAttribute attribute) const {
872 return GetData().GetFloatAttribute(attribute);
873 }
874
GetFloatAttribute(ax::mojom::FloatAttribute attribute,float * value) const875 bool BrowserAccessibility::GetFloatAttribute(
876 ax::mojom::FloatAttribute attribute,
877 float* value) const {
878 return GetData().GetFloatAttribute(attribute, value);
879 }
880
HasInheritedStringAttribute(ax::mojom::StringAttribute attribute) const881 bool BrowserAccessibility::HasInheritedStringAttribute(
882 ax::mojom::StringAttribute attribute) const {
883 if (GetData().HasStringAttribute(attribute))
884 return true;
885 return PlatformGetParent() &&
886 PlatformGetParent()->HasInheritedStringAttribute(attribute);
887 }
888
GetInheritedStringAttribute(ax::mojom::StringAttribute attribute) const889 const std::string& BrowserAccessibility::GetInheritedStringAttribute(
890 ax::mojom::StringAttribute attribute) const {
891 return node_->GetInheritedStringAttribute(attribute);
892 }
893
GetInheritedString16Attribute(ax::mojom::StringAttribute attribute) const894 base::string16 BrowserAccessibility::GetInheritedString16Attribute(
895 ax::mojom::StringAttribute attribute) const {
896 return node_->GetInheritedString16Attribute(attribute);
897 }
898
HasIntAttribute(ax::mojom::IntAttribute attribute) const899 bool BrowserAccessibility::HasIntAttribute(
900 ax::mojom::IntAttribute attribute) const {
901 return GetData().HasIntAttribute(attribute);
902 }
903
GetIntAttribute(ax::mojom::IntAttribute attribute) const904 int BrowserAccessibility::GetIntAttribute(
905 ax::mojom::IntAttribute attribute) const {
906 return GetData().GetIntAttribute(attribute);
907 }
908
GetIntAttribute(ax::mojom::IntAttribute attribute,int * value) const909 bool BrowserAccessibility::GetIntAttribute(ax::mojom::IntAttribute attribute,
910 int* value) const {
911 return GetData().GetIntAttribute(attribute, value);
912 }
913
HasStringAttribute(ax::mojom::StringAttribute attribute) const914 bool BrowserAccessibility::HasStringAttribute(
915 ax::mojom::StringAttribute attribute) const {
916 return GetData().HasStringAttribute(attribute);
917 }
918
GetStringAttribute(ax::mojom::StringAttribute attribute) const919 const std::string& BrowserAccessibility::GetStringAttribute(
920 ax::mojom::StringAttribute attribute) const {
921 return GetData().GetStringAttribute(attribute);
922 }
923
GetStringAttribute(ax::mojom::StringAttribute attribute,std::string * value) const924 bool BrowserAccessibility::GetStringAttribute(
925 ax::mojom::StringAttribute attribute,
926 std::string* value) const {
927 return GetData().GetStringAttribute(attribute, value);
928 }
929
GetString16Attribute(ax::mojom::StringAttribute attribute) const930 base::string16 BrowserAccessibility::GetString16Attribute(
931 ax::mojom::StringAttribute attribute) const {
932 return GetData().GetString16Attribute(attribute);
933 }
934
GetString16Attribute(ax::mojom::StringAttribute attribute,base::string16 * value) const935 bool BrowserAccessibility::GetString16Attribute(
936 ax::mojom::StringAttribute attribute,
937 base::string16* value) const {
938 return GetData().GetString16Attribute(attribute, value);
939 }
940
HasIntListAttribute(ax::mojom::IntListAttribute attribute) const941 bool BrowserAccessibility::HasIntListAttribute(
942 ax::mojom::IntListAttribute attribute) const {
943 return GetData().HasIntListAttribute(attribute);
944 }
945
GetIntListAttribute(ax::mojom::IntListAttribute attribute) const946 const std::vector<int32_t>& BrowserAccessibility::GetIntListAttribute(
947 ax::mojom::IntListAttribute attribute) const {
948 return GetData().GetIntListAttribute(attribute);
949 }
950
GetIntListAttribute(ax::mojom::IntListAttribute attribute,std::vector<int32_t> * value) const951 bool BrowserAccessibility::GetIntListAttribute(
952 ax::mojom::IntListAttribute attribute,
953 std::vector<int32_t>* value) const {
954 return GetData().GetIntListAttribute(attribute, value);
955 }
956
GetHtmlAttribute(const char * html_attr,std::string * value) const957 bool BrowserAccessibility::GetHtmlAttribute(const char* html_attr,
958 std::string* value) const {
959 return GetData().GetHtmlAttribute(html_attr, value);
960 }
961
GetHtmlAttribute(const char * html_attr,base::string16 * value) const962 bool BrowserAccessibility::GetHtmlAttribute(const char* html_attr,
963 base::string16* value) const {
964 return GetData().GetHtmlAttribute(html_attr, value);
965 }
966
HasState(ax::mojom::State state_enum) const967 bool BrowserAccessibility::HasState(ax::mojom::State state_enum) const {
968 return GetData().HasState(state_enum);
969 }
970
HasAction(ax::mojom::Action action_enum) const971 bool BrowserAccessibility::HasAction(ax::mojom::Action action_enum) const {
972 return GetData().HasAction(action_enum);
973 }
974
IsWebAreaForPresentationalIframe() const975 bool BrowserAccessibility::IsWebAreaForPresentationalIframe() const {
976 if (GetRole() != ax::mojom::Role::kWebArea &&
977 GetRole() != ax::mojom::Role::kRootWebArea) {
978 return false;
979 }
980
981 BrowserAccessibility* parent = PlatformGetParent();
982 if (!parent)
983 return false;
984
985 return parent->GetRole() == ax::mojom::Role::kIframePresentational;
986 }
987
IsClickable() const988 bool BrowserAccessibility::IsClickable() const {
989 return GetData().IsClickable();
990 }
991
IsTextField() const992 bool BrowserAccessibility::IsTextField() const {
993 return GetData().IsTextField();
994 }
995
IsPasswordField() const996 bool BrowserAccessibility::IsPasswordField() const {
997 return GetData().IsPasswordField();
998 }
999
IsPlainTextField() const1000 bool BrowserAccessibility::IsPlainTextField() const {
1001 return GetData().IsPlainTextField();
1002 }
1003
IsRichTextField() const1004 bool BrowserAccessibility::IsRichTextField() const {
1005 return GetData().IsRichTextField();
1006 }
1007
HasExplicitlyEmptyName() const1008 bool BrowserAccessibility::HasExplicitlyEmptyName() const {
1009 return GetData().GetNameFrom() ==
1010 ax::mojom::NameFrom::kAttributeExplicitlyEmpty;
1011 }
1012
GetLiveRegionText() const1013 std::string BrowserAccessibility::GetLiveRegionText() const {
1014 if (IsIgnored())
1015 return "";
1016
1017 std::string text = GetStringAttribute(ax::mojom::StringAttribute::kName);
1018 if (!text.empty())
1019 return text;
1020
1021 for (InternalChildIterator it = InternalChildrenBegin();
1022 it != InternalChildrenEnd(); ++it) {
1023 const BrowserAccessibility* child = it.get();
1024 if (!child)
1025 continue;
1026
1027 text += child->GetLiveRegionText();
1028 }
1029 return text;
1030 }
1031
GetLineStartOffsets() const1032 std::vector<int> BrowserAccessibility::GetLineStartOffsets() const {
1033 return node()->GetOrComputeLineStartOffsets();
1034 }
1035
1036 BrowserAccessibilityPosition::AXPositionInstance
CreatePositionAt(int offset,ax::mojom::TextAffinity affinity) const1037 BrowserAccessibility::CreatePositionAt(int offset,
1038 ax::mojom::TextAffinity affinity) const {
1039 DCHECK(manager_);
1040 return BrowserAccessibilityPosition::CreateTextPosition(
1041 manager_->ax_tree_id(), GetId(), offset, affinity);
1042 }
1043
1044 // |offset| could either be a text character or a child index in case of
1045 // non-text objects.
1046 // Currently, to be safe, we convert to text leaf equivalents and we don't use
1047 // tree positions.
1048 // TODO(nektar): Remove this function once selection fixes in Blink are
1049 // thoroughly tested and convert to tree positions.
1050 BrowserAccessibilityPosition::AXPositionInstance
CreatePositionForSelectionAt(int offset) const1051 BrowserAccessibility::CreatePositionForSelectionAt(int offset) const {
1052 BrowserAccessibilityPositionInstance position =
1053 CreatePositionAt(offset, ax::mojom::TextAffinity::kDownstream)
1054 ->AsLeafTextPosition();
1055 if (position->GetAnchor() &&
1056 position->GetAnchor()->GetRole() == ax::mojom::Role::kInlineTextBox) {
1057 return position->CreateParentPosition();
1058 }
1059 return position;
1060 }
1061
GetText() const1062 base::string16 BrowserAccessibility::GetText() const {
1063 // Default to inner text for non-native accessibility implementations.
1064 return GetInnerText();
1065 }
1066
GetNameAsString16() const1067 base::string16 BrowserAccessibility::GetNameAsString16() const {
1068 return base::UTF8ToUTF16(GetName());
1069 }
1070
GetName() const1071 std::string BrowserAccessibility::GetName() const {
1072 if (GetRole() == ax::mojom::Role::kPortal &&
1073 GetData().GetNameFrom() == ax::mojom::NameFrom::kNone) {
1074 BrowserAccessibility* child_tree_root = PlatformGetRootOfChildTree();
1075 if (child_tree_root) {
1076 return child_tree_root->GetStringAttribute(
1077 ax::mojom::StringAttribute::kName);
1078 }
1079 }
1080 return GetStringAttribute(ax::mojom::StringAttribute::kName);
1081 }
1082
GetHypertext() const1083 base::string16 BrowserAccessibility::GetHypertext() const {
1084 // Overloaded by platforms which require a hypertext accessibility text
1085 // implementation.
1086 return base::string16();
1087 }
1088
GetInnerText() const1089 base::string16 BrowserAccessibility::GetInnerText() const {
1090 return base::UTF8ToUTF16(node()->GetInnerText());
1091 }
1092
GetValueForControl() const1093 base::string16 BrowserAccessibility::GetValueForControl() const {
1094 return base::UTF8ToUTF16(node()->GetValueForControl());
1095 }
1096
RelativeToAbsoluteBounds(gfx::RectF bounds,const ui::AXCoordinateSystem coordinate_system,const ui::AXClippingBehavior clipping_behavior,ui::AXOffscreenResult * offscreen_result) const1097 gfx::Rect BrowserAccessibility::RelativeToAbsoluteBounds(
1098 gfx::RectF bounds,
1099 const ui::AXCoordinateSystem coordinate_system,
1100 const ui::AXClippingBehavior clipping_behavior,
1101 ui::AXOffscreenResult* offscreen_result) const {
1102 const bool clip_bounds =
1103 clipping_behavior == ui::AXClippingBehavior::kClipped;
1104 bool offscreen = false;
1105 const BrowserAccessibility* node = this;
1106 while (node) {
1107 BrowserAccessibilityManager* manager = node->manager();
1108 bounds = manager->ax_tree()->RelativeToTreeBounds(node->node(), bounds,
1109 &offscreen, clip_bounds);
1110
1111 // On some platforms we need to unapply root scroll offsets.
1112 if (!manager->UseRootScrollOffsetsWhenComputingBounds()) {
1113 // Get the node that's the "root scroller", which isn't necessarily
1114 // the root of the tree.
1115 ui::AXNode::AXID root_scroller_id =
1116 manager->GetTreeData().root_scroller_id;
1117 BrowserAccessibility* root_scroller =
1118 manager->GetFromID(root_scroller_id);
1119 if (root_scroller) {
1120 int sx = 0;
1121 int sy = 0;
1122 if (root_scroller->GetIntAttribute(ax::mojom::IntAttribute::kScrollX,
1123 &sx) &&
1124 root_scroller->GetIntAttribute(ax::mojom::IntAttribute::kScrollY,
1125 &sy)) {
1126 bounds.Offset(sx, sy);
1127 }
1128 }
1129 }
1130
1131 if (coordinate_system == ui::AXCoordinateSystem::kFrame)
1132 break;
1133
1134 const BrowserAccessibility* root = manager->GetRoot();
1135 node = root->PlatformGetParent();
1136 }
1137
1138 if (coordinate_system == ui::AXCoordinateSystem::kScreenDIPs ||
1139 coordinate_system == ui::AXCoordinateSystem::kScreenPhysicalPixels) {
1140 // Most platforms include page scale factor in the transform on the root
1141 // node of the AXTree. That transform gets applied by the call to
1142 // RelativeToTreeBounds() in the loop above. However, if the root transform
1143 // did not include page scale factor, we need to apply it now.
1144 // TODO(crbug.com/1074116): this should probably apply visual viewport
1145 // offset as well.
1146 if (!content::AXShouldIncludePageScaleFactorInRoot()) {
1147 BrowserAccessibilityManager* root_manager = manager()->GetRootManager();
1148 if (root_manager)
1149 bounds.Scale(root_manager->GetPageScaleFactor());
1150 }
1151 bounds.Offset(
1152 manager()->GetViewBoundsInScreenCoordinates().OffsetFromOrigin());
1153 if (coordinate_system == ui::AXCoordinateSystem::kScreenPhysicalPixels &&
1154 !IsUseZoomForDSFEnabled())
1155 bounds.Scale(manager()->device_scale_factor());
1156 }
1157
1158 if (offscreen_result) {
1159 *offscreen_result = offscreen ? ui::AXOffscreenResult::kOffscreen
1160 : ui::AXOffscreenResult::kOnscreen;
1161 }
1162
1163 return gfx::ToEnclosingRect(bounds);
1164 }
1165
IsOffscreen() const1166 bool BrowserAccessibility::IsOffscreen() const {
1167 ui::AXOffscreenResult offscreen_result = ui::AXOffscreenResult::kOnscreen;
1168 RelativeToAbsoluteBounds(gfx::RectF(), ui::AXCoordinateSystem::kRootFrame,
1169 ui::AXClippingBehavior::kClipped, &offscreen_result);
1170 return offscreen_result == ui::AXOffscreenResult::kOffscreen;
1171 }
1172
IsMinimized() const1173 bool BrowserAccessibility::IsMinimized() const {
1174 return false;
1175 }
1176
IsText() const1177 bool BrowserAccessibility::IsText() const {
1178 return node()->IsText();
1179 }
1180
IsWebContent() const1181 bool BrowserAccessibility::IsWebContent() const {
1182 return true;
1183 }
1184
HasVisibleCaretOrSelection() const1185 bool BrowserAccessibility::HasVisibleCaretOrSelection() const {
1186 ui::AXTree::Selection unignored_selection =
1187 manager()->ax_tree()->GetUnignoredSelection();
1188 int32_t focus_id = unignored_selection.focus_object_id;
1189 BrowserAccessibility* focus_object = manager()->GetFromID(focus_id);
1190 if (!focus_object)
1191 return false;
1192
1193 // Text inputs can have sub-objects that are not exposed, and can cause issues
1194 // in determining whether a caret is present. Avoid this situation by
1195 // comparing against the closest platform object, which will be in the tree.
1196 BrowserAccessibility* platform_object = PlatformGetClosestPlatformObject();
1197 DCHECK(platform_object);
1198
1199 // Selection or caret will be visible in a focused editable area, or if caret
1200 // browsing is enabled.
1201 // Caret browsing should be looking at leaf text nodes so it might not return
1202 // expected results in this method. See https://crbug.com/1052091.
1203 if (platform_object->HasState(ax::mojom::State::kEditable) ||
1204 BrowserAccessibilityStateImpl::GetInstance()->IsCaretBrowsingEnabled()) {
1205 return IsPlainTextField() ? focus_object == platform_object
1206 : focus_object->IsDescendantOf(platform_object);
1207 }
1208
1209 // The selection will be visible in non-editable content only if it is not
1210 // collapsed into a caret.
1211 return (focus_id != unignored_selection.anchor_object_id ||
1212 unignored_selection.focus_offset !=
1213 unignored_selection.anchor_offset) &&
1214 focus_object->IsDescendantOf(platform_object);
1215 }
1216
GetNodesForNodeIdSet(const std::set<int32_t> & ids)1217 std::set<ui::AXPlatformNode*> BrowserAccessibility::GetNodesForNodeIdSet(
1218 const std::set<int32_t>& ids) {
1219 std::set<ui::AXPlatformNode*> nodes;
1220 for (int32_t node_id : ids) {
1221 if (ui::AXPlatformNode* node = GetFromNodeID(node_id)) {
1222 nodes.insert(node);
1223 }
1224 }
1225 return nodes;
1226 }
1227
GetTargetNodeForRelation(ax::mojom::IntAttribute attr)1228 ui::AXPlatformNode* BrowserAccessibility::GetTargetNodeForRelation(
1229 ax::mojom::IntAttribute attr) {
1230 DCHECK(ui::IsNodeIdIntAttribute(attr));
1231
1232 int target_id;
1233 if (!GetData().GetIntAttribute(attr, &target_id))
1234 return nullptr;
1235
1236 return GetFromNodeID(target_id);
1237 }
1238
1239 std::vector<ui::AXPlatformNode*>
GetTargetNodesForRelation(ax::mojom::IntListAttribute attr)1240 BrowserAccessibility::GetTargetNodesForRelation(
1241 ax::mojom::IntListAttribute attr) {
1242 DCHECK(ui::IsNodeIdIntListAttribute(attr));
1243
1244 std::vector<int32_t> target_ids;
1245 if (!GetIntListAttribute(attr, &target_ids))
1246 return std::vector<ui::AXPlatformNode*>();
1247
1248 // If we use std::set to eliminate duplicates, the resulting set will be
1249 // sorted by the id and we will lose the original order provided by the
1250 // author which may be of interest to ATs. The number of ids should be small.
1251
1252 std::vector<ui::AXPlatformNode*> nodes;
1253 for (int32_t target_id : target_ids) {
1254 if (ui::AXPlatformNode* node = GetFromNodeID(target_id)) {
1255 if (std::find(nodes.begin(), nodes.end(), node) == nodes.end())
1256 nodes.push_back(node);
1257 }
1258 }
1259
1260 return nodes;
1261 }
1262
GetReverseRelations(ax::mojom::IntAttribute attr)1263 std::set<ui::AXPlatformNode*> BrowserAccessibility::GetReverseRelations(
1264 ax::mojom::IntAttribute attr) {
1265 DCHECK(manager_);
1266 DCHECK(node_);
1267 DCHECK(ui::IsNodeIdIntAttribute(attr));
1268 return GetNodesForNodeIdSet(
1269 manager_->ax_tree()->GetReverseRelations(attr, GetData().id));
1270 }
1271
GetReverseRelations(ax::mojom::IntListAttribute attr)1272 std::set<ui::AXPlatformNode*> BrowserAccessibility::GetReverseRelations(
1273 ax::mojom::IntListAttribute attr) {
1274 DCHECK(manager_);
1275 DCHECK(node_);
1276 DCHECK(ui::IsNodeIdIntListAttribute(attr));
1277 return GetNodesForNodeIdSet(
1278 manager_->ax_tree()->GetReverseRelations(attr, GetData().id));
1279 }
1280
GetAuthorUniqueId() const1281 base::string16 BrowserAccessibility::GetAuthorUniqueId() const {
1282 base::string16 html_id;
1283 GetData().GetHtmlAttribute("id", &html_id);
1284 return html_id;
1285 }
1286
GetUniqueId() const1287 const ui::AXUniqueId& BrowserAccessibility::GetUniqueId() const {
1288 // This is not the same as GetData().id which comes from Blink, because
1289 // those ids are only unique within the Blink process. We need one that is
1290 // unique for the browser process.
1291 return unique_id_;
1292 }
1293
SubtreeToStringHelper(size_t level)1294 std::string BrowserAccessibility::SubtreeToStringHelper(size_t level) {
1295 std::string result(level * 2, '+');
1296 result += ToString();
1297 result += '\n';
1298
1299 for (InternalChildIterator it = InternalChildrenBegin();
1300 it != InternalChildrenEnd(); ++it) {
1301 BrowserAccessibility* child = it.get();
1302 DCHECK(child);
1303 result += child->SubtreeToStringHelper(level + 1);
1304 }
1305
1306 return result;
1307 }
1308
FindTextBoundary(ax::mojom::TextBoundary boundary,int offset,ax::mojom::MoveDirection direction,ax::mojom::TextAffinity affinity) const1309 base::Optional<int> BrowserAccessibility::FindTextBoundary(
1310 ax::mojom::TextBoundary boundary,
1311 int offset,
1312 ax::mojom::MoveDirection direction,
1313 ax::mojom::TextAffinity affinity) const {
1314 BrowserAccessibilityPositionInstance position =
1315 CreatePositionAt(offset, affinity);
1316
1317 // On Windows and Linux ATK, searching for a text boundary should always stop
1318 // at the boundary of the current object.
1319 auto boundary_behavior = ui::AXBoundaryBehavior::StopAtAnchorBoundary;
1320 // On Windows and Linux ATK, it is standard text navigation behavior to stop
1321 // if we are searching in the backwards direction and the current position is
1322 // already at the required text boundary.
1323 DCHECK_NE(direction, ax::mojom::MoveDirection::kNone);
1324 if (direction == ax::mojom::MoveDirection::kBackward)
1325 boundary_behavior = ui::AXBoundaryBehavior::StopIfAlreadyAtBoundary;
1326
1327 return GetBoundaryTextOffsetInsideBaseAnchor(
1328 direction, position,
1329 position->CreatePositionAtTextBoundary(boundary, direction,
1330 boundary_behavior));
1331 }
1332
1333 const std::vector<gfx::NativeViewAccessible>
GetUIADescendants() const1334 BrowserAccessibility::GetUIADescendants() const {
1335 // This method is only called on Windows. Other platforms should not call it.
1336 // The BrowserAccessibilityWin subclass overrides this method.
1337 NOTREACHED();
1338 return {};
1339 }
1340
GetLanguage() const1341 std::string BrowserAccessibility::GetLanguage() const {
1342 DCHECK(node_) << "Did you forget to call BrowserAccessibility::Init?";
1343 return node()->GetLanguage();
1344 }
1345
GetNativeViewAccessible()1346 gfx::NativeViewAccessible BrowserAccessibility::GetNativeViewAccessible() {
1347 // TODO(703369) On Windows, where we have started to migrate to an
1348 // AXPlatformNode implementation, the BrowserAccessibilityWin subclass has
1349 // overridden this method. On all other platforms, this method should not be
1350 // called yet. In the future, when all subclasses have moved over to be
1351 // implemented by AXPlatformNode, we may make this method completely virtual.
1352 NOTREACHED();
1353 return nullptr;
1354 }
1355
1356 //
1357 // AXPlatformNodeDelegate.
1358 //
GetData() const1359 const ui::AXNodeData& BrowserAccessibility::GetData() const {
1360 static base::NoDestructor<ui::AXNodeData> empty_data;
1361 if (node_)
1362 return node_->data();
1363 else
1364 return *empty_data;
1365 }
1366
GetTreeData() const1367 const ui::AXTreeData& BrowserAccessibility::GetTreeData() const {
1368 static base::NoDestructor<ui::AXTreeData> empty_data;
1369 if (manager())
1370 return manager()->GetTreeData();
1371 else
1372 return *empty_data;
1373 }
1374
GetUnignoredSelection() const1375 const ui::AXTree::Selection BrowserAccessibility::GetUnignoredSelection()
1376 const {
1377 DCHECK(manager());
1378 ui::AXTree::Selection selection =
1379 manager()->ax_tree()->GetUnignoredSelection();
1380
1381 // "selection.anchor_offset" and "selection.focus_ofset" might need to be
1382 // adjusted if the anchor or the focus nodes include ignored children.
1383 const BrowserAccessibility* anchor_object =
1384 manager()->GetFromID(selection.anchor_object_id);
1385 if (anchor_object && !anchor_object->PlatformIsLeaf()) {
1386 DCHECK_GE(selection.anchor_offset, 0);
1387 if (size_t{selection.anchor_offset} <
1388 anchor_object->node()->children().size()) {
1389 const ui::AXNode* anchor_child =
1390 anchor_object->node()->children()[selection.anchor_offset];
1391 DCHECK(anchor_child);
1392 selection.anchor_offset = int{anchor_child->GetUnignoredIndexInParent()};
1393 } else {
1394 selection.anchor_offset = anchor_object->GetChildCount();
1395 }
1396 }
1397
1398 const BrowserAccessibility* focus_object =
1399 manager()->GetFromID(selection.focus_object_id);
1400 if (focus_object && !focus_object->PlatformIsLeaf()) {
1401 DCHECK_GE(selection.focus_offset, 0);
1402 if (size_t{selection.focus_offset} <
1403 focus_object->node()->children().size()) {
1404 const ui::AXNode* focus_child =
1405 focus_object->node()->children()[selection.focus_offset];
1406 DCHECK(focus_child);
1407 selection.focus_offset = int{focus_child->GetUnignoredIndexInParent()};
1408 } else {
1409 selection.focus_offset = focus_object->GetChildCount();
1410 }
1411 }
1412
1413 return selection;
1414 }
1415
1416 ui::AXNodePosition::AXPositionInstance
CreateTextPositionAt(int offset) const1417 BrowserAccessibility::CreateTextPositionAt(int offset) const {
1418 DCHECK(manager_);
1419 return ui::AXNodePosition::CreateTextPosition(
1420 manager_->ax_tree_id(), GetId(), offset,
1421 ax::mojom::TextAffinity::kDownstream);
1422 }
1423
GetNSWindow()1424 gfx::NativeViewAccessible BrowserAccessibility::GetNSWindow() {
1425 NOTREACHED();
1426 return nullptr;
1427 }
1428
GetParent()1429 gfx::NativeViewAccessible BrowserAccessibility::GetParent() {
1430 BrowserAccessibility* parent = PlatformGetParent();
1431 if (parent)
1432 return parent->GetNativeViewAccessible();
1433
1434 BrowserAccessibilityDelegate* delegate =
1435 manager_->GetDelegateFromRootManager();
1436 if (!delegate)
1437 return nullptr;
1438
1439 return delegate->AccessibilityGetNativeViewAccessible();
1440 }
1441
GetChildCount() const1442 int BrowserAccessibility::GetChildCount() const {
1443 return int{PlatformChildCount()};
1444 }
1445
ChildAtIndex(int index)1446 gfx::NativeViewAccessible BrowserAccessibility::ChildAtIndex(int index) {
1447 BrowserAccessibility* child = PlatformGetChild(index);
1448 if (!child)
1449 return nullptr;
1450
1451 return child->GetNativeViewAccessible();
1452 }
1453
HasModalDialog() const1454 bool BrowserAccessibility::HasModalDialog() const {
1455 return false;
1456 }
1457
GetFirstChild()1458 gfx::NativeViewAccessible BrowserAccessibility::GetFirstChild() {
1459 BrowserAccessibility* child = PlatformGetFirstChild();
1460 if (!child)
1461 return nullptr;
1462
1463 return child->GetNativeViewAccessible();
1464 }
1465
GetLastChild()1466 gfx::NativeViewAccessible BrowserAccessibility::GetLastChild() {
1467 BrowserAccessibility* child = PlatformGetLastChild();
1468 if (!child)
1469 return nullptr;
1470
1471 return child->GetNativeViewAccessible();
1472 }
1473
GetNextSibling()1474 gfx::NativeViewAccessible BrowserAccessibility::GetNextSibling() {
1475 BrowserAccessibility* sibling = PlatformGetNextSibling();
1476 if (!sibling)
1477 return nullptr;
1478
1479 return sibling->GetNativeViewAccessible();
1480 }
1481
GetPreviousSibling()1482 gfx::NativeViewAccessible BrowserAccessibility::GetPreviousSibling() {
1483 BrowserAccessibility* sibling = PlatformGetPreviousSibling();
1484 if (!sibling)
1485 return nullptr;
1486
1487 return sibling->GetNativeViewAccessible();
1488 }
1489
IsChildOfLeaf() const1490 bool BrowserAccessibility::IsChildOfLeaf() const {
1491 return node()->IsChildOfLeaf();
1492 }
1493
IsLeaf() const1494 bool BrowserAccessibility::IsLeaf() const {
1495 // According to the ARIA and Core-AAM specs:
1496 // https://w3c.github.io/aria/#button,
1497 // https://www.w3.org/TR/core-aam-1.1/#exclude_elements
1498 // button's children are presentational only and should be hidden from
1499 // screen readers. However, we cannot enforce the leafiness of buttons
1500 // because they may contain many rich, interactive descendants such as a day
1501 // in a calendar, and screen readers will need to interact with these
1502 // contents. See https://crbug.com/689204.
1503 // So we decided to not enforce the leafiness of buttons and expose all
1504 // children. The only exception to enforce leafiness is when the button has
1505 // a single text child and to prevent screen readers from double speak.
1506 if (GetRole() == ax::mojom::Role::kButton) {
1507 uint32_t child_count = InternalChildCount();
1508 return !child_count ||
1509 (child_count == 1 && InternalGetFirstChild()->IsText());
1510 }
1511 return PlatformGetRootOfChildTree() ? false : node()->IsLeaf();
1512 }
1513
IsToplevelBrowserWindow()1514 bool BrowserAccessibility::IsToplevelBrowserWindow() {
1515 return false;
1516 }
1517
IsDescendantOfPlainTextField() const1518 bool BrowserAccessibility::IsDescendantOfPlainTextField() const {
1519 return node()->IsDescendantOfPlainTextField();
1520 }
1521
GetClosestPlatformObject() const1522 gfx::NativeViewAccessible BrowserAccessibility::GetClosestPlatformObject()
1523 const {
1524 return PlatformGetClosestPlatformObject()->GetNativeViewAccessible();
1525 }
1526
PlatformChildIterator(const PlatformChildIterator & it)1527 BrowserAccessibility::PlatformChildIterator::PlatformChildIterator(
1528 const PlatformChildIterator& it)
1529 : parent_(it.parent_), platform_iterator(it.platform_iterator) {}
1530
PlatformChildIterator(const BrowserAccessibility * parent,BrowserAccessibility * child)1531 BrowserAccessibility::PlatformChildIterator::PlatformChildIterator(
1532 const BrowserAccessibility* parent,
1533 BrowserAccessibility* child)
1534 : parent_(parent), platform_iterator(parent, child) {
1535 DCHECK(parent);
1536 }
1537
1538 BrowserAccessibility::PlatformChildIterator::~PlatformChildIterator() = default;
1539
operator ==(const ChildIterator & rhs) const1540 bool BrowserAccessibility::PlatformChildIterator::operator==(
1541 const ChildIterator& rhs) const {
1542 return GetIndexInParent() == rhs.GetIndexInParent();
1543 }
1544
operator !=(const ChildIterator & rhs) const1545 bool BrowserAccessibility::PlatformChildIterator::operator!=(
1546 const ChildIterator& rhs) const {
1547 return GetIndexInParent() != rhs.GetIndexInParent();
1548 }
1549
operator ++()1550 void BrowserAccessibility::PlatformChildIterator::operator++() {
1551 ++platform_iterator;
1552 }
1553
operator ++(int)1554 void BrowserAccessibility::PlatformChildIterator::operator++(int) {
1555 ++platform_iterator;
1556 }
1557
operator --()1558 void BrowserAccessibility::PlatformChildIterator::operator--() {
1559 --platform_iterator;
1560 }
1561
operator --(int)1562 void BrowserAccessibility::PlatformChildIterator::operator--(int) {
1563 --platform_iterator;
1564 }
1565
get() const1566 BrowserAccessibility* BrowserAccessibility::PlatformChildIterator::get() const {
1567 return platform_iterator.get();
1568 }
1569
1570 gfx::NativeViewAccessible
GetNativeViewAccessible() const1571 BrowserAccessibility::PlatformChildIterator::GetNativeViewAccessible() const {
1572 return platform_iterator->GetNativeViewAccessible();
1573 }
1574
GetIndexInParent() const1575 int BrowserAccessibility::PlatformChildIterator::GetIndexInParent() const {
1576 if (platform_iterator == parent_->PlatformChildrenEnd().platform_iterator)
1577 return parent_->PlatformChildCount();
1578
1579 return platform_iterator->GetIndexInParent();
1580 }
1581
operator *() const1582 BrowserAccessibility& BrowserAccessibility::PlatformChildIterator::operator*()
1583 const {
1584 return *platform_iterator;
1585 }
1586
operator ->() const1587 BrowserAccessibility* BrowserAccessibility::PlatformChildIterator::operator->()
1588 const {
1589 return platform_iterator.get();
1590 }
1591
1592 std::unique_ptr<ui::AXPlatformNodeDelegate::ChildIterator>
ChildrenBegin()1593 BrowserAccessibility::ChildrenBegin() {
1594 return std::make_unique<PlatformChildIterator>(PlatformChildrenBegin());
1595 }
1596
1597 std::unique_ptr<ui::AXPlatformNodeDelegate::ChildIterator>
ChildrenEnd()1598 BrowserAccessibility::ChildrenEnd() {
1599 return std::make_unique<PlatformChildIterator>(PlatformChildrenEnd());
1600 }
1601
HitTestSync(int physical_pixel_x,int physical_pixel_y) const1602 gfx::NativeViewAccessible BrowserAccessibility::HitTestSync(
1603 int physical_pixel_x,
1604 int physical_pixel_y) const {
1605 BrowserAccessibility* accessible = manager_->CachingAsyncHitTest(
1606 gfx::Point(physical_pixel_x, physical_pixel_y));
1607 if (!accessible)
1608 return nullptr;
1609
1610 return accessible->GetNativeViewAccessible();
1611 }
1612
GetFocus()1613 gfx::NativeViewAccessible BrowserAccessibility::GetFocus() {
1614 BrowserAccessibility* focused = manager()->GetFocus();
1615 if (!focused)
1616 return nullptr;
1617
1618 return focused->GetNativeViewAccessible();
1619 }
1620
GetFromNodeID(int32_t id)1621 ui::AXPlatformNode* BrowserAccessibility::GetFromNodeID(int32_t id) {
1622 BrowserAccessibility* node = manager_->GetFromID(id);
1623 if (!node)
1624 return nullptr;
1625
1626 return node->GetAXPlatformNode();
1627 }
1628
GetFromTreeIDAndNodeID(const ui::AXTreeID & ax_tree_id,int32_t id)1629 ui::AXPlatformNode* BrowserAccessibility::GetFromTreeIDAndNodeID(
1630 const ui::AXTreeID& ax_tree_id,
1631 int32_t id) {
1632 BrowserAccessibilityManager* manager =
1633 BrowserAccessibilityManager::FromID(ax_tree_id);
1634 if (!manager)
1635 return nullptr;
1636
1637 BrowserAccessibility* node = manager->GetFromID(id);
1638 if (!node)
1639 return nullptr;
1640
1641 return node->GetAXPlatformNode();
1642 }
1643
GetIndexInParent()1644 int BrowserAccessibility::GetIndexInParent() {
1645 if (manager()->GetRoot() == this && PlatformGetParent() == nullptr) {
1646 // If it is a root node of WebContent, it doesn't have a parent and a
1647 // valid index in parent. So it returns -1 in order to compute its
1648 // index at AXPlatformNodeBase.
1649 return -1;
1650 }
1651 return node()->GetUnignoredIndexInParent();
1652 }
1653
1654 gfx::AcceleratedWidget
GetTargetForNativeAccessibilityEvent()1655 BrowserAccessibility::GetTargetForNativeAccessibilityEvent() {
1656 BrowserAccessibilityDelegate* root_delegate =
1657 manager()->GetDelegateFromRootManager();
1658 if (!root_delegate)
1659 return gfx::kNullAcceleratedWidget;
1660 return root_delegate->AccessibilityGetAcceleratedWidget();
1661 }
1662
IsTable() const1663 bool BrowserAccessibility::IsTable() const {
1664 return node()->IsTable();
1665 }
1666
GetTableRowCount() const1667 base::Optional<int> BrowserAccessibility::GetTableRowCount() const {
1668 return node()->GetTableRowCount();
1669 }
1670
GetTableColCount() const1671 base::Optional<int> BrowserAccessibility::GetTableColCount() const {
1672 return node()->GetTableColCount();
1673 }
1674
GetTableAriaColCount() const1675 base::Optional<int> BrowserAccessibility::GetTableAriaColCount() const {
1676 return node()->GetTableAriaColCount();
1677 }
1678
GetTableAriaRowCount() const1679 base::Optional<int> BrowserAccessibility::GetTableAriaRowCount() const {
1680 return node()->GetTableAriaRowCount();
1681 }
1682
GetTableCellCount() const1683 base::Optional<int> BrowserAccessibility::GetTableCellCount() const {
1684 return node()->GetTableCellCount();
1685 }
1686
GetTableHasColumnOrRowHeaderNode() const1687 base::Optional<bool> BrowserAccessibility::GetTableHasColumnOrRowHeaderNode()
1688 const {
1689 return node()->GetTableHasColumnOrRowHeaderNode();
1690 }
1691
GetColHeaderNodeIds() const1692 std::vector<ui::AXNode::AXID> BrowserAccessibility::GetColHeaderNodeIds()
1693 const {
1694 return node()->GetTableColHeaderNodeIds();
1695 }
1696
GetColHeaderNodeIds(int col_index) const1697 std::vector<ui::AXNode::AXID> BrowserAccessibility::GetColHeaderNodeIds(
1698 int col_index) const {
1699 return node()->GetTableColHeaderNodeIds(col_index);
1700 }
1701
GetRowHeaderNodeIds() const1702 std::vector<ui::AXNode::AXID> BrowserAccessibility::GetRowHeaderNodeIds()
1703 const {
1704 return node()->GetTableCellRowHeaderNodeIds();
1705 }
1706
GetRowHeaderNodeIds(int row_index) const1707 std::vector<ui::AXNode::AXID> BrowserAccessibility::GetRowHeaderNodeIds(
1708 int row_index) const {
1709 return node()->GetTableRowHeaderNodeIds(row_index);
1710 }
1711
GetTableCaption() const1712 ui::AXPlatformNode* BrowserAccessibility::GetTableCaption() const {
1713 ui::AXNode* caption = node()->GetTableCaption();
1714 if (caption)
1715 return const_cast<BrowserAccessibility*>(this)->GetFromNodeID(
1716 caption->id());
1717
1718 return nullptr;
1719 }
1720
IsTableRow() const1721 bool BrowserAccessibility::IsTableRow() const {
1722 return node()->IsTableRow();
1723 }
1724
GetTableRowRowIndex() const1725 base::Optional<int> BrowserAccessibility::GetTableRowRowIndex() const {
1726 return node()->GetTableRowRowIndex();
1727 }
1728
IsTableCellOrHeader() const1729 bool BrowserAccessibility::IsTableCellOrHeader() const {
1730 return node()->IsTableCellOrHeader();
1731 }
1732
GetTableCellColIndex() const1733 base::Optional<int> BrowserAccessibility::GetTableCellColIndex() const {
1734 return node()->GetTableCellColIndex();
1735 }
1736
GetTableCellRowIndex() const1737 base::Optional<int> BrowserAccessibility::GetTableCellRowIndex() const {
1738 return node()->GetTableCellRowIndex();
1739 }
1740
GetTableCellColSpan() const1741 base::Optional<int> BrowserAccessibility::GetTableCellColSpan() const {
1742 return node()->GetTableCellColSpan();
1743 }
1744
GetTableCellRowSpan() const1745 base::Optional<int> BrowserAccessibility::GetTableCellRowSpan() const {
1746 return node()->GetTableCellRowSpan();
1747 }
1748
GetTableCellAriaColIndex() const1749 base::Optional<int> BrowserAccessibility::GetTableCellAriaColIndex() const {
1750 return node()->GetTableCellAriaColIndex();
1751 }
1752
GetTableCellAriaRowIndex() const1753 base::Optional<int> BrowserAccessibility::GetTableCellAriaRowIndex() const {
1754 return node()->GetTableCellAriaRowIndex();
1755 }
1756
GetCellId(int row_index,int col_index) const1757 base::Optional<int32_t> BrowserAccessibility::GetCellId(int row_index,
1758 int col_index) const {
1759 ui::AXNode* cell = node()->GetTableCellFromCoords(row_index, col_index);
1760 if (!cell)
1761 return base::nullopt;
1762 return cell->id();
1763 }
1764
GetTableCellIndex() const1765 base::Optional<int> BrowserAccessibility::GetTableCellIndex() const {
1766 return node()->GetTableCellIndex();
1767 }
1768
CellIndexToId(int cell_index) const1769 base::Optional<int32_t> BrowserAccessibility::CellIndexToId(
1770 int cell_index) const {
1771 ui::AXNode* cell = node()->GetTableCellFromIndex(cell_index);
1772 if (!cell)
1773 return base::nullopt;
1774 return cell->id();
1775 }
1776
IsCellOrHeaderOfARIATable() const1777 bool BrowserAccessibility::IsCellOrHeaderOfARIATable() const {
1778 return node()->IsCellOrHeaderOfARIATable();
1779 }
1780
IsCellOrHeaderOfARIAGrid() const1781 bool BrowserAccessibility::IsCellOrHeaderOfARIAGrid() const {
1782 return node()->IsCellOrHeaderOfARIAGrid();
1783 }
1784
AccessibilityPerformAction(const ui::AXActionData & data)1785 bool BrowserAccessibility::AccessibilityPerformAction(
1786 const ui::AXActionData& data) {
1787 switch (data.action) {
1788 case ax::mojom::Action::kDoDefault:
1789 manager_->DoDefaultAction(*this);
1790 return true;
1791 case ax::mojom::Action::kFocus:
1792 manager_->SetFocus(*this);
1793 return true;
1794 case ax::mojom::Action::kScrollToPoint: {
1795 // Convert the target point from screen coordinates to frame coordinates.
1796 gfx::Point target =
1797 data.target_point - manager_->GetRoot()
1798 ->GetUnclippedScreenBoundsRect()
1799 .OffsetFromOrigin();
1800 manager_->ScrollToPoint(*this, target);
1801 return true;
1802 }
1803 case ax::mojom::Action::kScrollToMakeVisible:
1804 manager_->ScrollToMakeVisible(
1805 *this, data.target_rect, data.horizontal_scroll_alignment,
1806 data.vertical_scroll_alignment, data.scroll_behavior);
1807 return true;
1808 case ax::mojom::Action::kSetScrollOffset:
1809 manager_->SetScrollOffset(*this, data.target_point);
1810 return true;
1811 case ax::mojom::Action::kSetSelection: {
1812 // "data.anchor_offset" and "data.focus_ofset" might need to be adjusted
1813 // if the anchor or the focus nodes include ignored children.
1814 ui::AXActionData selection = data;
1815 const BrowserAccessibility* anchor_object =
1816 manager()->GetFromID(selection.anchor_node_id);
1817 DCHECK(anchor_object);
1818 if (!anchor_object->PlatformIsLeaf()) {
1819 DCHECK_GE(selection.anchor_offset, 0);
1820 const BrowserAccessibility* anchor_child =
1821 anchor_object->InternalGetChild(uint32_t{selection.anchor_offset});
1822 if (anchor_child) {
1823 selection.anchor_offset =
1824 int{anchor_child->node()->index_in_parent()};
1825 selection.anchor_node_id = anchor_child->node()->parent()->id();
1826 } else {
1827 // Since the child was not found, the only alternative is that this is
1828 // an "after children" position.
1829 selection.anchor_offset =
1830 int{anchor_object->node()->children().size()};
1831 }
1832 }
1833
1834 const BrowserAccessibility* focus_object =
1835 manager()->GetFromID(selection.focus_node_id);
1836 DCHECK(focus_object);
1837 if (!focus_object->PlatformIsLeaf()) {
1838 DCHECK_GE(selection.focus_offset, 0);
1839 const BrowserAccessibility* focus_child =
1840 focus_object->InternalGetChild(uint32_t{selection.focus_offset});
1841 if (focus_child) {
1842 selection.focus_offset = int{focus_child->node()->index_in_parent()};
1843 selection.focus_node_id = focus_child->node()->parent()->id();
1844 } else {
1845 // Since the child was not found, the only alternative is that this is
1846 // an "after children" position.
1847 selection.focus_offset = int{focus_object->node()->children().size()};
1848 }
1849 }
1850
1851 manager_->SetSelection(selection);
1852 return true;
1853 }
1854 case ax::mojom::Action::kSetValue:
1855 manager_->SetValue(*this, data.value);
1856 return true;
1857 case ax::mojom::Action::kSetSequentialFocusNavigationStartingPoint:
1858 manager_->SetSequentialFocusNavigationStartingPoint(*this);
1859 return true;
1860 case ax::mojom::Action::kShowContextMenu:
1861 manager_->ShowContextMenu(*this);
1862 return true;
1863 default:
1864 return false;
1865 }
1866 }
1867
GetLocalizedStringForImageAnnotationStatus(ax::mojom::ImageAnnotationStatus status) const1868 base::string16 BrowserAccessibility::GetLocalizedStringForImageAnnotationStatus(
1869 ax::mojom::ImageAnnotationStatus status) const {
1870 ContentClient* content_client = content::GetContentClient();
1871
1872 int message_id = 0;
1873 switch (status) {
1874 case ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation:
1875 message_id = IDS_AX_IMAGE_ELIGIBLE_FOR_ANNOTATION;
1876 break;
1877 case ax::mojom::ImageAnnotationStatus::kAnnotationPending:
1878 message_id = IDS_AX_IMAGE_ANNOTATION_PENDING;
1879 break;
1880 case ax::mojom::ImageAnnotationStatus::kAnnotationAdult:
1881 message_id = IDS_AX_IMAGE_ANNOTATION_ADULT;
1882 break;
1883 case ax::mojom::ImageAnnotationStatus::kAnnotationEmpty:
1884 case ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed:
1885 message_id = IDS_AX_IMAGE_ANNOTATION_NO_DESCRIPTION;
1886 break;
1887 case ax::mojom::ImageAnnotationStatus::kNone:
1888 case ax::mojom::ImageAnnotationStatus::kWillNotAnnotateDueToScheme:
1889 case ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation:
1890 case ax::mojom::ImageAnnotationStatus::kSilentlyEligibleForAnnotation:
1891 case ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded:
1892 return base::string16();
1893 }
1894
1895 DCHECK(message_id);
1896
1897 return content_client->GetLocalizedString(message_id);
1898 }
1899
1900 base::string16
GetLocalizedRoleDescriptionForUnlabeledImage() const1901 BrowserAccessibility::GetLocalizedRoleDescriptionForUnlabeledImage() const {
1902 ContentClient* content_client = content::GetContentClient();
1903 return content_client->GetLocalizedString(
1904 IDS_AX_UNLABELED_IMAGE_ROLE_DESCRIPTION);
1905 }
1906
GetLocalizedStringForLandmarkType() const1907 base::string16 BrowserAccessibility::GetLocalizedStringForLandmarkType() const {
1908 ContentClient* content_client = content::GetContentClient();
1909 const ui::AXNodeData& data = GetData();
1910
1911 switch (data.role) {
1912 case ax::mojom::Role::kBanner:
1913 case ax::mojom::Role::kHeader:
1914 return content_client->GetLocalizedString(IDS_AX_ROLE_BANNER);
1915
1916 case ax::mojom::Role::kComplementary:
1917 return content_client->GetLocalizedString(IDS_AX_ROLE_COMPLEMENTARY);
1918
1919 case ax::mojom::Role::kContentInfo:
1920 case ax::mojom::Role::kFooter:
1921 return content_client->GetLocalizedString(IDS_AX_ROLE_CONTENT_INFO);
1922
1923 case ax::mojom::Role::kRegion:
1924 case ax::mojom::Role::kSection:
1925 if (data.HasStringAttribute(ax::mojom::StringAttribute::kName))
1926 return content_client->GetLocalizedString(IDS_AX_ROLE_REGION);
1927 FALLTHROUGH;
1928
1929 default:
1930 return {};
1931 }
1932 }
1933
GetLocalizedStringForRoleDescription() const1934 base::string16 BrowserAccessibility::GetLocalizedStringForRoleDescription()
1935 const {
1936 ContentClient* content_client = content::GetContentClient();
1937 const ui::AXNodeData& data = GetData();
1938
1939 switch (data.role) {
1940 case ax::mojom::Role::kArticle:
1941 return content_client->GetLocalizedString(IDS_AX_ROLE_ARTICLE);
1942
1943 case ax::mojom::Role::kAudio:
1944 return content_client->GetLocalizedString(IDS_AX_ROLE_AUDIO);
1945
1946 case ax::mojom::Role::kCode:
1947 return content_client->GetLocalizedString(IDS_AX_ROLE_CODE);
1948
1949 case ax::mojom::Role::kColorWell:
1950 return content_client->GetLocalizedString(IDS_AX_ROLE_COLOR_WELL);
1951
1952 case ax::mojom::Role::kContentInfo:
1953 return content_client->GetLocalizedString(IDS_AX_ROLE_CONTENT_INFO);
1954
1955 case ax::mojom::Role::kDate:
1956 return content_client->GetLocalizedString(IDS_AX_ROLE_DATE);
1957
1958 case ax::mojom::Role::kDateTime: {
1959 std::string input_type;
1960 if (data.GetStringAttribute(ax::mojom::StringAttribute::kInputType,
1961 &input_type)) {
1962 if (input_type == "datetime-local") {
1963 return content_client->GetLocalizedString(
1964 IDS_AX_ROLE_DATE_TIME_LOCAL);
1965 } else if (input_type == "week") {
1966 return content_client->GetLocalizedString(IDS_AX_ROLE_WEEK);
1967 }
1968 }
1969 return {};
1970 }
1971
1972 case ax::mojom::Role::kDetails:
1973 return content_client->GetLocalizedString(IDS_AX_ROLE_DETAILS);
1974
1975 case ax::mojom::Role::kEmphasis:
1976 return content_client->GetLocalizedString(IDS_AX_ROLE_EMPHASIS);
1977
1978 case ax::mojom::Role::kFigure:
1979 return content_client->GetLocalizedString(IDS_AX_ROLE_FIGURE);
1980
1981 case ax::mojom::Role::kFooter:
1982 case ax::mojom::Role::kFooterAsNonLandmark:
1983 return content_client->GetLocalizedString(IDS_AX_ROLE_FOOTER);
1984
1985 case ax::mojom::Role::kHeader:
1986 case ax::mojom::Role::kHeaderAsNonLandmark:
1987 return content_client->GetLocalizedString(IDS_AX_ROLE_HEADER);
1988
1989 case ax::mojom::Role::kMark:
1990 return content_client->GetLocalizedString(IDS_AX_ROLE_MARK);
1991
1992 case ax::mojom::Role::kMeter:
1993 return content_client->GetLocalizedString(IDS_AX_ROLE_METER);
1994
1995 case ax::mojom::Role::kSearchBox:
1996 return content_client->GetLocalizedString(IDS_AX_ROLE_SEARCH_BOX);
1997
1998 case ax::mojom::Role::kSection: {
1999 if (data.HasStringAttribute(ax::mojom::StringAttribute::kName))
2000 return content_client->GetLocalizedString(IDS_AX_ROLE_SECTION);
2001
2002 return {};
2003 }
2004
2005 case ax::mojom::Role::kStatus:
2006 return content_client->GetLocalizedString(IDS_AX_ROLE_OUTPUT);
2007
2008 case ax::mojom::Role::kStrong:
2009 return content_client->GetLocalizedString(IDS_AX_ROLE_STRONG);
2010
2011 case ax::mojom::Role::kSwitch:
2012 return content_client->GetLocalizedString(IDS_AX_ROLE_SWITCH);
2013
2014 case ax::mojom::Role::kTextField: {
2015 std::string input_type;
2016 if (data.GetStringAttribute(ax::mojom::StringAttribute::kInputType,
2017 &input_type)) {
2018 if (input_type == "email") {
2019 return content_client->GetLocalizedString(IDS_AX_ROLE_EMAIL);
2020 } else if (input_type == "tel") {
2021 return content_client->GetLocalizedString(IDS_AX_ROLE_TELEPHONE);
2022 } else if (input_type == "url") {
2023 return content_client->GetLocalizedString(IDS_AX_ROLE_URL);
2024 }
2025 }
2026 return {};
2027 }
2028
2029 case ax::mojom::Role::kTime:
2030 return content_client->GetLocalizedString(IDS_AX_ROLE_TIME);
2031
2032 default:
2033 return {};
2034 }
2035 }
2036
GetStyleNameAttributeAsLocalizedString() const2037 base::string16 BrowserAccessibility::GetStyleNameAttributeAsLocalizedString()
2038 const {
2039 const BrowserAccessibility* current_node = this;
2040 while (current_node) {
2041 if (current_node->GetData().role == ax::mojom::Role::kMark) {
2042 ContentClient* content_client = content::GetContentClient();
2043 return content_client->GetLocalizedString(IDS_AX_ROLE_MARK);
2044 }
2045 current_node = current_node->PlatformGetParent();
2046 }
2047 return {};
2048 }
2049
ShouldIgnoreHoveredStateForTesting()2050 bool BrowserAccessibility::ShouldIgnoreHoveredStateForTesting() {
2051 BrowserAccessibilityStateImpl* accessibility_state =
2052 BrowserAccessibilityStateImpl::GetInstance();
2053 return accessibility_state->disable_hot_tracking_for_testing();
2054 }
2055
IsOrderedSetItem() const2056 bool BrowserAccessibility::IsOrderedSetItem() const {
2057 return node()->IsOrderedSetItem();
2058 }
2059
IsOrderedSet() const2060 bool BrowserAccessibility::IsOrderedSet() const {
2061 return node()->IsOrderedSet();
2062 }
2063
GetPosInSet() const2064 base::Optional<int> BrowserAccessibility::GetPosInSet() const {
2065 return node()->GetPosInSet();
2066 }
2067
GetSetSize() const2068 base::Optional<int> BrowserAccessibility::GetSetSize() const {
2069 return node()->GetSetSize();
2070 }
2071
IsInListMarker() const2072 bool BrowserAccessibility::IsInListMarker() const {
2073 return node()->IsInListMarker();
2074 }
2075
IsCollapsedMenuListPopUpButton() const2076 bool BrowserAccessibility::IsCollapsedMenuListPopUpButton() const {
2077 return node()->IsCollapsedMenuListPopUpButton();
2078 }
2079
2080 BrowserAccessibility*
GetCollapsedMenuListPopUpButtonAncestor() const2081 BrowserAccessibility::GetCollapsedMenuListPopUpButtonAncestor() const {
2082 ui::AXNode* popup_button = node()->GetCollapsedMenuListPopUpButtonAncestor();
2083 if (!popup_button)
2084 return nullptr;
2085 return manager()->GetFromAXNode(popup_button);
2086 }
2087
GetTextFieldAncestor() const2088 BrowserAccessibility* BrowserAccessibility::GetTextFieldAncestor() const {
2089 ui::AXNode* text_field_ancestor = node()->GetTextFieldAncestor();
2090 if (!text_field_ancestor)
2091 return nullptr;
2092 return manager()->GetFromAXNode(text_field_ancestor);
2093 }
2094
ToString() const2095 std::string BrowserAccessibility::ToString() const {
2096 return GetData().ToString();
2097 }
2098
SetHypertextSelection(int start_offset,int end_offset)2099 bool BrowserAccessibility::SetHypertextSelection(int start_offset,
2100 int end_offset) {
2101 manager()->SetSelection(
2102 AXPlatformRange(CreatePositionForSelectionAt(start_offset),
2103 CreatePositionForSelectionAt(end_offset)));
2104 return true;
2105 }
2106
PlatformGetRootOfChildTree() const2107 BrowserAccessibility* BrowserAccessibility::PlatformGetRootOfChildTree() const {
2108 std::string child_tree_id;
2109 if (!GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId,
2110 &child_tree_id)) {
2111 return nullptr;
2112 }
2113 DCHECK_EQ(node_->children().size(), 0u)
2114 << "A node should not have both children and a child tree.";
2115
2116 BrowserAccessibilityManager* child_manager =
2117 BrowserAccessibilityManager::FromID(AXTreeID::FromString(child_tree_id));
2118 if (child_manager && child_manager->GetRoot()->PlatformGetParent() == this)
2119 return child_manager->GetRoot();
2120 return nullptr;
2121 }
2122
ComputeTextAttributes() const2123 ui::TextAttributeList BrowserAccessibility::ComputeTextAttributes() const {
2124 return ui::TextAttributeList();
2125 }
2126
GetInheritedFontFamilyName() const2127 std::string BrowserAccessibility::GetInheritedFontFamilyName() const {
2128 return GetInheritedStringAttribute(ax::mojom::StringAttribute::kFontFamily);
2129 }
2130
GetSpellingAndGrammarAttributes() const2131 ui::TextAttributeMap BrowserAccessibility::GetSpellingAndGrammarAttributes()
2132 const {
2133 ui::TextAttributeMap spelling_attributes;
2134 if (IsText()) {
2135 const std::vector<int32_t>& marker_types =
2136 GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerTypes);
2137 const std::vector<int>& marker_starts =
2138 GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerStarts);
2139 const std::vector<int>& marker_ends =
2140 GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerEnds);
2141 for (size_t i = 0; i < marker_types.size(); ++i) {
2142 bool is_spelling_error =
2143 (marker_types[i] &
2144 static_cast<int32_t>(ax::mojom::MarkerType::kSpelling)) != 0;
2145 bool is_grammar_error =
2146 (marker_types[i] &
2147 static_cast<int32_t>(ax::mojom::MarkerType::kGrammar)) != 0;
2148
2149 if (!is_spelling_error && !is_grammar_error)
2150 continue;
2151
2152 ui::TextAttributeList start_attributes;
2153 if (is_spelling_error && is_grammar_error)
2154 start_attributes.push_back(
2155 std::make_pair("invalid", "spelling,grammar"));
2156 else if (is_spelling_error)
2157 start_attributes.push_back(std::make_pair("invalid", "spelling"));
2158 else if (is_grammar_error)
2159 start_attributes.push_back(std::make_pair("invalid", "grammar"));
2160
2161 int start_offset = marker_starts[i];
2162 int end_offset = marker_ends[i];
2163 spelling_attributes[start_offset] = start_attributes;
2164 spelling_attributes[end_offset] = ui::TextAttributeList();
2165 }
2166 }
2167
2168 if (IsPlainTextField()) {
2169 int start_offset = 0;
2170 for (BrowserAccessibility* static_text =
2171 BrowserAccessibilityManager::NextTextOnlyObject(
2172 InternalGetFirstChild());
2173 static_text; static_text = static_text->InternalGetNextSibling()) {
2174 ui::TextAttributeMap text_spelling_attributes =
2175 static_text->GetSpellingAndGrammarAttributes();
2176 for (auto& attribute : text_spelling_attributes) {
2177 spelling_attributes[start_offset + attribute.first] =
2178 std::move(attribute.second);
2179 }
2180 start_offset += static_cast<int>(static_text->GetHypertext().length());
2181 }
2182 }
2183
2184 return spelling_attributes;
2185 }
2186
2187 // static
MergeSpellingAndGrammarIntoTextAttributes(const ui::TextAttributeMap & spelling_attributes,int start_offset,ui::TextAttributeMap * text_attributes)2188 void BrowserAccessibility::MergeSpellingAndGrammarIntoTextAttributes(
2189 const ui::TextAttributeMap& spelling_attributes,
2190 int start_offset,
2191 ui::TextAttributeMap* text_attributes) {
2192 if (!text_attributes) {
2193 NOTREACHED();
2194 return;
2195 }
2196
2197 ui::TextAttributeList prev_attributes;
2198 for (const auto& spelling_attribute : spelling_attributes) {
2199 int offset = start_offset + spelling_attribute.first;
2200 auto iterator = text_attributes->find(offset);
2201 if (iterator == text_attributes->end()) {
2202 text_attributes->emplace(offset, prev_attributes);
2203 iterator = text_attributes->find(offset);
2204 } else {
2205 prev_attributes = iterator->second;
2206 }
2207
2208 ui::TextAttributeList& existing_attributes = iterator->second;
2209 // There might be a spelling attribute already in the list of text
2210 // attributes, originating from "aria-invalid", that is being overwritten
2211 // by a spelling marker. If it already exists, prefer it over this
2212 // automatically computed attribute.
2213 if (!HasInvalidAttribute(existing_attributes)) {
2214 // Does not exist -- insert our own.
2215 existing_attributes.insert(existing_attributes.end(),
2216 spelling_attribute.second.begin(),
2217 spelling_attribute.second.end());
2218 }
2219 }
2220 }
2221
ComputeTextAttributeMap(const ui::TextAttributeList & default_attributes) const2222 ui::TextAttributeMap BrowserAccessibility::ComputeTextAttributeMap(
2223 const ui::TextAttributeList& default_attributes) const {
2224 ui::TextAttributeMap attributes_map;
2225 if (PlatformIsLeaf() || IsPlainTextField()) {
2226 attributes_map[0] = default_attributes;
2227 const ui::TextAttributeMap spelling_attributes =
2228 GetSpellingAndGrammarAttributes();
2229 MergeSpellingAndGrammarIntoTextAttributes(
2230 spelling_attributes, 0 /* start_offset */, &attributes_map);
2231 return attributes_map;
2232 }
2233
2234 DCHECK(PlatformChildCount());
2235
2236 int start_offset = 0;
2237 for (BrowserAccessibility::PlatformChildIterator it = PlatformChildrenBegin();
2238 it != PlatformChildrenEnd(); ++it) {
2239 BrowserAccessibility* child = it.get();
2240 DCHECK(child);
2241 ui::TextAttributeList attributes(child->ComputeTextAttributes());
2242
2243 if (attributes_map.empty()) {
2244 attributes_map[start_offset] = attributes;
2245 } else {
2246 // Only add the attributes for this child if we are at the start of a new
2247 // style span.
2248 ui::TextAttributeList previous_attributes =
2249 attributes_map.rbegin()->second;
2250 // Must check the size, otherwise if attributes is a subset of
2251 // prev_attributes, they would appear to be equal.
2252 if (attributes.size() != previous_attributes.size() ||
2253 !std::equal(attributes.begin(), attributes.end(),
2254 previous_attributes.begin())) {
2255 attributes_map[start_offset] = attributes;
2256 }
2257 }
2258
2259 if (child->IsText()) {
2260 const ui::TextAttributeMap spelling_attributes =
2261 child->GetSpellingAndGrammarAttributes();
2262 MergeSpellingAndGrammarIntoTextAttributes(spelling_attributes,
2263 start_offset, &attributes_map);
2264 start_offset += child->GetHypertext().length();
2265 } else {
2266 start_offset += 1;
2267 }
2268 }
2269
2270 return attributes_map;
2271 }
2272
2273 // static
HasInvalidAttribute(const ui::TextAttributeList & attributes)2274 bool BrowserAccessibility::HasInvalidAttribute(
2275 const ui::TextAttributeList& attributes) {
2276 return std::find_if(attributes.begin(), attributes.end(),
2277 [](const ui::TextAttribute& attribute) {
2278 return attribute.first == "invalid";
2279 }) != attributes.end();
2280 }
2281
HasListAncestor(const BrowserAccessibility * node)2282 static bool HasListAncestor(const BrowserAccessibility* node) {
2283 if (node == nullptr)
2284 return false;
2285
2286 if (ui::IsStaticList(node->GetRole()))
2287 return true;
2288
2289 return HasListAncestor(node->InternalGetParent());
2290 }
2291
HasListDescendant(const BrowserAccessibility * current,const BrowserAccessibility * root)2292 static bool HasListDescendant(const BrowserAccessibility* current,
2293 const BrowserAccessibility* root) {
2294 // Do not check the root when looking for a list descendant.
2295 if (current != root && ui::IsStaticList(current->GetRole()))
2296 return true;
2297
2298 for (auto it = current->InternalChildrenBegin();
2299 it != current->InternalChildrenEnd(); ++it) {
2300 if (HasListDescendant(it.get(), root))
2301 return true;
2302 }
2303 return false;
2304 }
2305
IsHierarchicalList() const2306 bool BrowserAccessibility::IsHierarchicalList() const {
2307 if (!ui::IsStaticList(GetRole()))
2308 return false;
2309 return HasListDescendant(this, this) || HasListAncestor(InternalGetParent());
2310 }
2311
2312 } // namespace content
2313