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