1 // Copyright 2013 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 "ui/accessibility/ax_node.h"
6 
7 #include <algorithm>
8 #include <utility>
9 
10 #include "base/strings/string16.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "build/build_config.h"
16 #include "third_party/skia/include/core/SkColor.h"
17 #include "ui/accessibility/ax_enums.mojom.h"
18 #include "ui/accessibility/ax_language_detection.h"
19 #include "ui/accessibility/ax_role_properties.h"
20 #include "ui/accessibility/ax_table_info.h"
21 #include "ui/accessibility/ax_tree.h"
22 #include "ui/gfx/transform.h"
23 
24 namespace ui {
25 
26 constexpr AXNode::AXID AXNode::kInvalidAXID;
27 
AXNode(AXNode::OwnerTree * tree,AXNode * parent,int32_t id,size_t index_in_parent,size_t unignored_index_in_parent)28 AXNode::AXNode(AXNode::OwnerTree* tree,
29                AXNode* parent,
30                int32_t id,
31                size_t index_in_parent,
32                size_t unignored_index_in_parent)
33     : tree_(tree),
34       index_in_parent_(index_in_parent),
35       unignored_index_in_parent_(unignored_index_in_parent),
36       parent_(parent) {
37   data_.id = id;
38 }
39 
40 AXNode::~AXNode() = default;
41 
GetUnignoredChildCount() const42 size_t AXNode::GetUnignoredChildCount() const {
43   // TODO(nektar): Should DCHECK if the node is not ignored.
44   DCHECK(!tree_->GetTreeUpdateInProgressState());
45   return unignored_child_count_;
46 }
47 
TakeData()48 AXNodeData&& AXNode::TakeData() {
49   return std::move(data_);
50 }
51 
GetUnignoredChildAtIndex(size_t index) const52 AXNode* AXNode::GetUnignoredChildAtIndex(size_t index) const {
53   DCHECK(!tree_->GetTreeUpdateInProgressState());
54   size_t count = 0;
55   for (auto it = UnignoredChildrenBegin(); it != UnignoredChildrenEnd(); ++it) {
56     if (count == index)
57       return it.get();
58     ++count;
59   }
60   return nullptr;
61 }
62 
GetUnignoredParent() const63 AXNode* AXNode::GetUnignoredParent() const {
64   DCHECK(!tree_->GetTreeUpdateInProgressState());
65   AXNode* result = parent();
66   while (result && result->IsIgnored())
67     result = result->parent();
68   return result;
69 }
70 
GetUnignoredIndexInParent() const71 size_t AXNode::GetUnignoredIndexInParent() const {
72   DCHECK(!tree_->GetTreeUpdateInProgressState());
73   return unignored_index_in_parent_;
74 }
75 
GetIndexInParent() const76 size_t AXNode::GetIndexInParent() const {
77   DCHECK(!tree_->GetTreeUpdateInProgressState());
78   return index_in_parent_;
79 }
80 
GetFirstUnignoredChild() const81 AXNode* AXNode::GetFirstUnignoredChild() const {
82   DCHECK(!tree_->GetTreeUpdateInProgressState());
83   return ComputeFirstUnignoredChildRecursive();
84 }
85 
GetLastUnignoredChild() const86 AXNode* AXNode::GetLastUnignoredChild() const {
87   DCHECK(!tree_->GetTreeUpdateInProgressState());
88   return ComputeLastUnignoredChildRecursive();
89 }
90 
GetDeepestFirstUnignoredChild() const91 AXNode* AXNode::GetDeepestFirstUnignoredChild() const {
92   if (!GetUnignoredChildCount())
93     return nullptr;
94 
95   AXNode* deepest_child = GetFirstUnignoredChild();
96   while (deepest_child->GetUnignoredChildCount()) {
97     deepest_child = deepest_child->GetFirstUnignoredChild();
98   }
99 
100   return deepest_child;
101 }
102 
GetDeepestLastUnignoredChild() const103 AXNode* AXNode::GetDeepestLastUnignoredChild() const {
104   if (!GetUnignoredChildCount())
105     return nullptr;
106 
107   AXNode* deepest_child = GetLastUnignoredChild();
108   while (deepest_child->GetUnignoredChildCount()) {
109     deepest_child = deepest_child->GetLastUnignoredChild();
110   }
111 
112   return deepest_child;
113 }
114 
115 // Search for the next sibling of this node, skipping over any ignored nodes
116 // encountered.
117 //
118 // In our search:
119 //   If we find an ignored sibling, we consider its children as our siblings.
120 //   If we run out of siblings, we consider an ignored parent's siblings as our
121 //     own siblings.
122 //
123 // Note: this behaviour of 'skipping over' an ignored node makes this subtly
124 // different to finding the next (direct) sibling which is unignored.
125 //
126 // Consider a tree, where (i) marks a node as ignored:
127 //
128 //   1
129 //   ├── 2
130 //   ├── 3(i)
131 //   │   └── 5
132 //   └── 4
133 //
134 // The next sibling of node 2 is node 3, which is ignored.
135 // The next unignored sibling of node 2 could be either:
136 //  1) node 4 - next unignored sibling in the literal tree, or
137 //  2) node 5 - next unignored sibling in the logical document.
138 //
139 // There is no next sibling of node 5.
140 // The next unignored sibling of node 5 could be either:
141 //  1) null   - no next sibling in the literal tree, or
142 //  2) node 4 - next unignored sibling in the logical document.
143 //
144 // In both cases, this method implements approach (2).
145 //
146 // TODO(chrishall): Can we remove this non-reflexive case by forbidding
147 //   GetNextUnignoredSibling calls on an ignored started node?
148 // Note: this means that Next/Previous-UnignoredSibling are not reflexive if
149 // either of the nodes in question are ignored. From above we get an example:
150 //   NextUnignoredSibling(3)     is 4, but
151 //   PreviousUnignoredSibling(4) is 5.
152 //
153 // The view of unignored siblings for node 3 includes both node 2 and node 4:
154 //    2 <-- [3(i)] --> 4
155 //
156 // Whereas nodes 2, 5, and 4 do not consider node 3 to be an unignored sibling:
157 // null <-- [2] --> 5
158 //    2 <-- [5] --> 4
159 //    5 <-- [4] --> null
GetNextUnignoredSibling() const160 AXNode* AXNode::GetNextUnignoredSibling() const {
161   DCHECK(!tree_->GetTreeUpdateInProgressState());
162   const AXNode* current = this;
163 
164   // If there are children of the |current| node still to consider.
165   bool considerChildren = false;
166 
167   while (current) {
168     // A |candidate| sibling to consider.
169     // If it is unignored then we have found our result.
170     // Otherwise promote it to |current| and consider its children.
171     AXNode* candidate;
172 
173     if (considerChildren && (candidate = current->GetFirstChild())) {
174       if (!candidate->IsIgnored())
175         return candidate;
176       current = candidate;
177 
178     } else if ((candidate = current->GetNextSibling())) {
179       if (!candidate->IsIgnored())
180         return candidate;
181       current = candidate;
182       // Look through the ignored candidate node to consider their children as
183       // though they were siblings.
184       considerChildren = true;
185 
186     } else {
187       // Continue our search through a parent iff they are ignored.
188       //
189       // If |current| has an ignored parent, then we consider the parent's
190       // siblings as though they were siblings of |current|.
191       //
192       // Given a tree:
193       //   1
194       //   ├── 2(?)
195       //   │   └── [4]
196       //   └── 3
197       //
198       // Node 4's view of siblings:
199       //   literal tree:   null <-- [4] --> null
200       //
201       // If node 2 is not ignored, then node 4's view doesn't change, and we
202       // have no more nodes to consider:
203       //   unignored tree: null <-- [4] --> null
204       //
205       // If instead node 2 is ignored, then node 4's view of siblings grows to
206       // include node 3, and we have more nodes to consider:
207       //   unignored tree: null <-- [4] --> 3
208       current = current->parent();
209       if (!current || !current->IsIgnored())
210         return nullptr;
211 
212       // We have already considered all relevant descendants of |current|.
213       considerChildren = false;
214     }
215   }
216 
217   return nullptr;
218 }
219 
220 // Search for the previous sibling of this node, skipping over any ignored nodes
221 // encountered.
222 //
223 // In our search for a sibling:
224 //   If we find an ignored sibling, we may consider its children as siblings.
225 //   If we run out of siblings, we may consider an ignored parent's siblings as
226 //     our own.
227 //
228 // See the documentation for |GetNextUnignoredSibling| for more details.
GetPreviousUnignoredSibling() const229 AXNode* AXNode::GetPreviousUnignoredSibling() const {
230   DCHECK(!tree_->GetTreeUpdateInProgressState());
231   const AXNode* current = this;
232 
233   // If there are children of the |current| node still to consider.
234   bool considerChildren = false;
235 
236   while (current) {
237     // A |candidate| sibling to consider.
238     // If it is unignored then we have found our result.
239     // Otherwise promote it to |current| and consider its children.
240     AXNode* candidate;
241 
242     if (considerChildren && (candidate = current->GetLastChild())) {
243       if (!candidate->IsIgnored())
244         return candidate;
245       current = candidate;
246 
247     } else if ((candidate = current->GetPreviousSibling())) {
248       if (!candidate->IsIgnored())
249         return candidate;
250       current = candidate;
251       // Look through the ignored candidate node to consider their children as
252       // though they were siblings.
253       considerChildren = true;
254 
255     } else {
256       // Continue our search through a parent iff they are ignored.
257       //
258       // If |current| has an ignored parent, then we consider the parent's
259       // siblings as though they were siblings of |current|.
260       //
261       // Given a tree:
262       //   1
263       //   ├── 2
264       //   └── 3(?)
265       //       └── [4]
266       //
267       // Node 4's view of siblings:
268       //   literal tree:   null <-- [4] --> null
269       //
270       // If node 3 is not ignored, then node 4's view doesn't change, and we
271       // have no more nodes to consider:
272       //   unignored tree: null <-- [4] --> null
273       //
274       // If instead node 3 is ignored, then node 4's view of siblings grows to
275       // include node 2, and we have more nodes to consider:
276       //   unignored tree:    2 <-- [4] --> null
277       current = current->parent();
278       if (!current || !current->IsIgnored())
279         return nullptr;
280 
281       // We have already considered all relevant descendants of |current|.
282       considerChildren = false;
283     }
284   }
285 
286   return nullptr;
287 }
288 
GetNextUnignoredInTreeOrder() const289 AXNode* AXNode::GetNextUnignoredInTreeOrder() const {
290   if (GetUnignoredChildCount())
291     return GetFirstUnignoredChild();
292 
293   const AXNode* node = this;
294   while (node) {
295     AXNode* sibling = node->GetNextUnignoredSibling();
296     if (sibling)
297       return sibling;
298 
299     node = node->GetUnignoredParent();
300   }
301 
302   return nullptr;
303 }
304 
GetPreviousUnignoredInTreeOrder() const305 AXNode* AXNode::GetPreviousUnignoredInTreeOrder() const {
306   AXNode* sibling = GetPreviousUnignoredSibling();
307   if (!sibling)
308     return GetUnignoredParent();
309 
310   if (sibling->GetUnignoredChildCount())
311     return sibling->GetDeepestLastUnignoredChild();
312 
313   return sibling;
314 }
315 
UnignoredChildrenBegin() const316 AXNode::UnignoredChildIterator AXNode::UnignoredChildrenBegin() const {
317   DCHECK(!tree_->GetTreeUpdateInProgressState());
318   return UnignoredChildIterator(this, GetFirstUnignoredChild());
319 }
320 
UnignoredChildrenEnd() const321 AXNode::UnignoredChildIterator AXNode::UnignoredChildrenEnd() const {
322   DCHECK(!tree_->GetTreeUpdateInProgressState());
323   return UnignoredChildIterator(this, nullptr);
324 }
325 
326 // The first (direct) child, ignored or unignored.
GetFirstChild() const327 AXNode* AXNode::GetFirstChild() const {
328   if (children().empty())
329     return nullptr;
330   return children()[0];
331 }
332 
333 // The last (direct) child, ignored or unignored.
GetLastChild() const334 AXNode* AXNode::GetLastChild() const {
335   size_t n = children().size();
336   if (n == 0)
337     return nullptr;
338   return children()[n - 1];
339 }
340 
341 // The previous (direct) sibling, ignored or unignored.
GetPreviousSibling() const342 AXNode* AXNode::GetPreviousSibling() const {
343   // Root nodes lack a parent, their index_in_parent should be 0.
344   DCHECK(!parent() ? index_in_parent() == 0 : true);
345   size_t index = index_in_parent();
346   if (index == 0)
347     return nullptr;
348   return parent()->children()[index - 1];
349 }
350 
351 // The next (direct) sibling, ignored or unignored.
GetNextSibling() const352 AXNode* AXNode::GetNextSibling() const {
353   if (!parent())
354     return nullptr;
355   size_t nextIndex = index_in_parent() + 1;
356   if (nextIndex >= parent()->children().size())
357     return nullptr;
358   return parent()->children()[nextIndex];
359 }
360 
IsText() const361 bool AXNode::IsText() const {
362   // In Legacy Layout, a list marker has no children and is thus represented on
363   // all platforms as a leaf node that exposes the marker itself, i.e., it forms
364   // part of the AX tree's text representation. In contrast, in Layout NG, a
365   // list marker has a static text child.
366   if (data().role == ax::mojom::Role::kListMarker)
367     return !children().size();
368   return ui::IsText(data().role);
369 }
370 
IsLineBreak() const371 bool AXNode::IsLineBreak() const {
372   return data().role == ax::mojom::Role::kLineBreak ||
373          (data().role == ax::mojom::Role::kInlineTextBox &&
374           data().GetBoolAttribute(
375               ax::mojom::BoolAttribute::kIsLineBreakingObject));
376 }
377 
SetData(const AXNodeData & src)378 void AXNode::SetData(const AXNodeData& src) {
379   data_ = src;
380 }
381 
SetLocation(int32_t offset_container_id,const gfx::RectF & location,gfx::Transform * transform)382 void AXNode::SetLocation(int32_t offset_container_id,
383                          const gfx::RectF& location,
384                          gfx::Transform* transform) {
385   data_.relative_bounds.offset_container_id = offset_container_id;
386   data_.relative_bounds.bounds = location;
387   if (transform) {
388     data_.relative_bounds.transform =
389         std::make_unique<gfx::Transform>(*transform);
390   } else {
391     data_.relative_bounds.transform.reset();
392   }
393 }
394 
SetIndexInParent(size_t index_in_parent)395 void AXNode::SetIndexInParent(size_t index_in_parent) {
396   index_in_parent_ = index_in_parent;
397 }
398 
UpdateUnignoredCachedValues()399 void AXNode::UpdateUnignoredCachedValues() {
400   if (!IsIgnored())
401     UpdateUnignoredCachedValuesRecursive(0);
402 }
403 
SwapChildren(std::vector<AXNode * > * children)404 void AXNode::SwapChildren(std::vector<AXNode*>* children) {
405   children->swap(children_);
406 }
407 
Destroy()408 void AXNode::Destroy() {
409   delete this;
410 }
411 
IsDescendantOf(const AXNode * ancestor) const412 bool AXNode::IsDescendantOf(const AXNode* ancestor) const {
413   if (this == ancestor)
414     return true;
415   if (parent())
416     return parent()->IsDescendantOf(ancestor);
417 
418   return false;
419 }
420 
GetOrComputeLineStartOffsets()421 std::vector<int> AXNode::GetOrComputeLineStartOffsets() {
422   std::vector<int> line_offsets;
423   if (data().GetIntListAttribute(ax::mojom::IntListAttribute::kCachedLineStarts,
424                                  &line_offsets)) {
425     return line_offsets;
426   }
427 
428   int start_offset = 0;
429   ComputeLineStartOffsets(&line_offsets, &start_offset);
430   data_.AddIntListAttribute(ax::mojom::IntListAttribute::kCachedLineStarts,
431                             line_offsets);
432   return line_offsets;
433 }
434 
ComputeLineStartOffsets(std::vector<int> * line_offsets,int * start_offset) const435 void AXNode::ComputeLineStartOffsets(std::vector<int>* line_offsets,
436                                      int* start_offset) const {
437   DCHECK(line_offsets);
438   DCHECK(start_offset);
439   for (const AXNode* child : children()) {
440     DCHECK(child);
441     if (!child->children().empty()) {
442       child->ComputeLineStartOffsets(line_offsets, start_offset);
443       continue;
444     }
445 
446     // Don't report if the first piece of text starts a new line or not.
447     if (*start_offset && !child->data().HasIntAttribute(
448                              ax::mojom::IntAttribute::kPreviousOnLineId)) {
449       // If there are multiple objects with an empty accessible label at the
450       // start of a line, only include a single line start offset.
451       if (line_offsets->empty() || line_offsets->back() != *start_offset)
452         line_offsets->push_back(*start_offset);
453     }
454 
455     base::string16 text =
456         child->data().GetString16Attribute(ax::mojom::StringAttribute::kName);
457     *start_offset += static_cast<int>(text.length());
458   }
459 }
460 
GetInheritedStringAttribute(ax::mojom::StringAttribute attribute) const461 const std::string& AXNode::GetInheritedStringAttribute(
462     ax::mojom::StringAttribute attribute) const {
463   const AXNode* current_node = this;
464   do {
465     if (current_node->data().HasStringAttribute(attribute))
466       return current_node->data().GetStringAttribute(attribute);
467     current_node = current_node->parent();
468   } while (current_node);
469   return base::EmptyString();
470 }
471 
GetInheritedString16Attribute(ax::mojom::StringAttribute attribute) const472 base::string16 AXNode::GetInheritedString16Attribute(
473     ax::mojom::StringAttribute attribute) const {
474   return base::UTF8ToUTF16(GetInheritedStringAttribute(attribute));
475 }
476 
GetLanguageInfo() const477 AXLanguageInfo* AXNode::GetLanguageInfo() const {
478   return language_info_.get();
479 }
480 
SetLanguageInfo(std::unique_ptr<AXLanguageInfo> lang_info)481 void AXNode::SetLanguageInfo(std::unique_ptr<AXLanguageInfo> lang_info) {
482   language_info_ = std::move(lang_info);
483 }
484 
ClearLanguageInfo()485 void AXNode::ClearLanguageInfo() {
486   language_info_.reset();
487 }
488 
GetHypertext() const489 std::string AXNode::GetHypertext() const {
490   if (IsLeaf())
491     return GetInnerText();
492 
493   // Construct the hypertext for this node, which contains the concatenation of
494   // the inner text of this node's textual children, and an embedded object
495   // character for all the other children.
496   const std::string embedded_character_str("\xEF\xBF\xBC");
497   std::string hypertext;
498   for (auto it = UnignoredChildrenBegin(); it != UnignoredChildrenEnd(); ++it) {
499     // Similar to Firefox, we don't expose text nodes in IAccessible2 and ATK
500     // hypertext with the embedded object character. We copy all of their text
501     // instead.
502     if (it->IsText()) {
503       hypertext += it->GetInnerText();
504     } else {
505       hypertext += embedded_character_str;
506     }
507   }
508   return hypertext;
509 }
510 
GetInnerText() const511 std::string AXNode::GetInnerText() const {
512   // If a text field has no descendants, then we compute its inner text from its
513   // value or its placeholder. Otherwise we prefer to look at its descendant
514   // text nodes because Blink doesn't always add all trailing white space to the
515   // value attribute.
516   const bool is_plain_text_field_without_descendants =
517       (data().IsTextField() && !GetUnignoredChildCount());
518   if (is_plain_text_field_without_descendants) {
519     std::string value =
520         data().GetStringAttribute(ax::mojom::StringAttribute::kValue);
521     // If the value is empty, then there might be some placeholder text in the
522     // text field, or any other name that is derived from visible contents, even
523     // if the text field has no children.
524     if (!value.empty())
525       return value;
526   }
527 
528   // Ordinarily, plain text fields are leaves. We need to exclude them from the
529   // set of leaf nodes when they expose any descendants. This is because we want
530   // to compute their inner text from their descendant text nodes as we don't
531   // always trust the "value" attribute provided by Blink.
532   const bool is_plain_text_field_with_descendants =
533       (data().IsTextField() && GetUnignoredChildCount());
534   if (IsLeaf() && !is_plain_text_field_with_descendants) {
535     switch (data().GetNameFrom()) {
536       case ax::mojom::NameFrom::kNone:
537       case ax::mojom::NameFrom::kUninitialized:
538       // The accessible name is not displayed on screen, e.g. aria-label, or is
539       // not displayed directly inside the node, e.g. an associated label
540       // element.
541       case ax::mojom::NameFrom::kAttribute:
542       // The node's accessible name is explicitly empty.
543       case ax::mojom::NameFrom::kAttributeExplicitlyEmpty:
544       // The accessible name does not represent the entirety of the node's inner
545       // text, e.g. a table's caption or a figure's figcaption.
546       case ax::mojom::NameFrom::kCaption:
547       case ax::mojom::NameFrom::kRelatedElement:
548       // The accessible name is not displayed directly inside the node but is
549       // visible via e.g. a tooltip.
550       case ax::mojom::NameFrom::kTitle:
551         return std::string();
552 
553       case ax::mojom::NameFrom::kContents:
554       // The placeholder text is initially displayed inside the text field and
555       // takes the place of its value.
556       case ax::mojom::NameFrom::kPlaceholder:
557       // The value attribute takes the place of the node's inner text, e.g. the
558       // value of a submit button is displayed inside the button itself.
559       case ax::mojom::NameFrom::kValue:
560         return data().GetStringAttribute(ax::mojom::StringAttribute::kName);
561     }
562   }
563 
564   std::string inner_text;
565   for (auto it = UnignoredChildrenBegin(); it != UnignoredChildrenEnd(); ++it) {
566     inner_text += it->GetInnerText();
567   }
568   return inner_text;
569 }
570 
GetLanguage() const571 std::string AXNode::GetLanguage() const {
572   // Walk up tree considering both detected and author declared languages.
573   for (const AXNode* cur = this; cur; cur = cur->parent()) {
574     // If language detection has assigned a language then we prefer that.
575     const AXLanguageInfo* lang_info = cur->GetLanguageInfo();
576     if (lang_info && !lang_info->language.empty()) {
577       return lang_info->language;
578     }
579 
580     // If the page author has declared a language attribute we fallback to that.
581     const AXNodeData& data = cur->data();
582     if (data.HasStringAttribute(ax::mojom::StringAttribute::kLanguage)) {
583       return data.GetStringAttribute(ax::mojom::StringAttribute::kLanguage);
584     }
585   }
586 
587   return std::string();
588 }
589 
GetValueForControl() const590 std::string AXNode::GetValueForControl() const {
591   if (data().IsTextField())
592     return GetValueForTextField();
593   if (data().IsRangeValueSupported())
594     return GetTextForRangeValue();
595   if (data().role == ax::mojom::Role::kColorWell)
596     return GetValueForColorWell();
597   if (!IsControl(data().role))
598     return std::string();
599   return data().GetStringAttribute(ax::mojom::StringAttribute::kValue);
600 }
601 
operator <<(std::ostream & stream,const AXNode & node)602 std::ostream& operator<<(std::ostream& stream, const AXNode& node) {
603   return stream << node.data().ToString();
604 }
605 
IsTable() const606 bool AXNode::IsTable() const {
607   return IsTableLike(data().role);
608 }
609 
GetTableColCount() const610 base::Optional<int> AXNode::GetTableColCount() const {
611   const AXTableInfo* table_info = GetAncestorTableInfo();
612   if (!table_info)
613     return base::nullopt;
614   return int{table_info->col_count};
615 }
616 
GetTableRowCount() const617 base::Optional<int> AXNode::GetTableRowCount() const {
618   const AXTableInfo* table_info = GetAncestorTableInfo();
619   if (!table_info)
620     return base::nullopt;
621   return int{table_info->row_count};
622 }
623 
GetTableAriaColCount() const624 base::Optional<int> AXNode::GetTableAriaColCount() const {
625   const AXTableInfo* table_info = GetAncestorTableInfo();
626   if (!table_info)
627     return base::nullopt;
628   return base::make_optional(table_info->aria_col_count);
629 }
630 
GetTableAriaRowCount() const631 base::Optional<int> AXNode::GetTableAriaRowCount() const {
632   const AXTableInfo* table_info = GetAncestorTableInfo();
633   if (!table_info)
634     return base::nullopt;
635   return base::make_optional(table_info->aria_row_count);
636 }
637 
GetTableCellCount() const638 base::Optional<int> AXNode::GetTableCellCount() const {
639   const AXTableInfo* table_info = GetAncestorTableInfo();
640   if (!table_info)
641     return base::nullopt;
642 
643   return static_cast<int>(table_info->unique_cell_ids.size());
644 }
645 
GetTableHasColumnOrRowHeaderNode() const646 base::Optional<bool> AXNode::GetTableHasColumnOrRowHeaderNode() const {
647   const AXTableInfo* table_info = GetAncestorTableInfo();
648   if (!table_info)
649     return base::nullopt;
650 
651   return !table_info->all_headers.empty();
652 }
653 
GetTableCellFromIndex(int index) const654 AXNode* AXNode::GetTableCellFromIndex(int index) const {
655   const AXTableInfo* table_info = GetAncestorTableInfo();
656   if (!table_info)
657     return nullptr;
658 
659   // There is a table but there is no cell with the given index.
660   if (index < 0 || size_t{index} >= table_info->unique_cell_ids.size()) {
661     return nullptr;
662   }
663 
664   return tree_->GetFromId(table_info->unique_cell_ids[size_t{index}]);
665 }
666 
GetTableCaption() const667 AXNode* AXNode::GetTableCaption() const {
668   const AXTableInfo* table_info = GetAncestorTableInfo();
669   if (!table_info)
670     return nullptr;
671 
672   return tree_->GetFromId(table_info->caption_id);
673 }
674 
GetTableCellFromCoords(int row_index,int col_index) const675 AXNode* AXNode::GetTableCellFromCoords(int row_index, int col_index) const {
676   const AXTableInfo* table_info = GetAncestorTableInfo();
677   if (!table_info)
678     return nullptr;
679 
680   // There is a table but the given coordinates are outside the table.
681   if (row_index < 0 || size_t{row_index} >= table_info->row_count ||
682       col_index < 0 || size_t{col_index} >= table_info->col_count) {
683     return nullptr;
684   }
685 
686   return tree_->GetFromId(
687       table_info->cell_ids[size_t{row_index}][size_t{col_index}]);
688 }
689 
GetTableColHeaderNodeIds() const690 std::vector<AXNode::AXID> AXNode::GetTableColHeaderNodeIds() const {
691   const AXTableInfo* table_info = GetAncestorTableInfo();
692   if (!table_info)
693     return std::vector<AXNode::AXID>();
694 
695   std::vector<AXNode::AXID> col_header_ids;
696   // Flatten and add column header ids of each column to |col_header_ids|.
697   for (std::vector<AXNode::AXID> col_headers_at_index :
698        table_info->col_headers) {
699     col_header_ids.insert(col_header_ids.end(), col_headers_at_index.begin(),
700                           col_headers_at_index.end());
701   }
702 
703   return col_header_ids;
704 }
705 
GetTableColHeaderNodeIds(int col_index) const706 std::vector<AXNode::AXID> AXNode::GetTableColHeaderNodeIds(
707     int col_index) const {
708   const AXTableInfo* table_info = GetAncestorTableInfo();
709   if (!table_info)
710     return std::vector<AXNode::AXID>();
711 
712   if (col_index < 0 || size_t{col_index} >= table_info->col_count)
713     return std::vector<AXNode::AXID>();
714 
715   return std::vector<AXNode::AXID>(table_info->col_headers[size_t{col_index}]);
716 }
717 
GetTableRowHeaderNodeIds(int row_index) const718 std::vector<AXNode::AXID> AXNode::GetTableRowHeaderNodeIds(
719     int row_index) const {
720   const AXTableInfo* table_info = GetAncestorTableInfo();
721   if (!table_info)
722     return std::vector<AXNode::AXID>();
723 
724   if (row_index < 0 || size_t{row_index} >= table_info->row_count)
725     return std::vector<AXNode::AXID>();
726 
727   return std::vector<AXNode::AXID>(table_info->row_headers[size_t{row_index}]);
728 }
729 
GetTableUniqueCellIds() const730 std::vector<AXNode::AXID> AXNode::GetTableUniqueCellIds() const {
731   const AXTableInfo* table_info = GetAncestorTableInfo();
732   if (!table_info)
733     return std::vector<AXNode::AXID>();
734 
735   return std::vector<AXNode::AXID>(table_info->unique_cell_ids);
736 }
737 
GetExtraMacNodes() const738 const std::vector<AXNode*>* AXNode::GetExtraMacNodes() const {
739   // Should only be available on the table node itself, not any of its children.
740   const AXTableInfo* table_info = tree_->GetTableInfo(this);
741   if (!table_info)
742     return nullptr;
743 
744   return &table_info->extra_mac_nodes;
745 }
746 
747 //
748 // Table row-like nodes.
749 //
750 
IsTableRow() const751 bool AXNode::IsTableRow() const {
752   return ui::IsTableRow(data().role);
753 }
754 
GetTableRowRowIndex() const755 base::Optional<int> AXNode::GetTableRowRowIndex() const {
756   if (!IsTableRow())
757     return base::nullopt;
758 
759   const AXTableInfo* table_info = GetAncestorTableInfo();
760   if (!table_info)
761     return base::nullopt;
762 
763   const auto& iter = table_info->row_id_to_index.find(id());
764   if (iter == table_info->row_id_to_index.end())
765     return base::nullopt;
766   return int{iter->second};
767 }
768 
GetTableRowNodeIds() const769 std::vector<AXNode::AXID> AXNode::GetTableRowNodeIds() const {
770   std::vector<AXNode::AXID> row_node_ids;
771   const AXTableInfo* table_info = GetAncestorTableInfo();
772   if (!table_info)
773     return row_node_ids;
774 
775   for (AXNode* node : table_info->row_nodes)
776     row_node_ids.push_back(node->data().id);
777 
778   return row_node_ids;
779 }
780 
781 #if defined(OS_APPLE)
782 
783 //
784 // Table column-like nodes. These nodes are only present on macOS.
785 //
786 
IsTableColumn() const787 bool AXNode::IsTableColumn() const {
788   return ui::IsTableColumn(data().role);
789 }
790 
GetTableColColIndex() const791 base::Optional<int> AXNode::GetTableColColIndex() const {
792   if (!IsTableColumn())
793     return base::nullopt;
794 
795   const AXTableInfo* table_info = GetAncestorTableInfo();
796   if (!table_info)
797     return base::nullopt;
798 
799   int index = 0;
800   for (const AXNode* node : table_info->extra_mac_nodes) {
801     if (node == this)
802       break;
803     index++;
804   }
805   return index;
806 }
807 
808 #endif  // defined(OS_APPLE)
809 
810 //
811 // Table cell-like nodes.
812 //
813 
IsTableCellOrHeader() const814 bool AXNode::IsTableCellOrHeader() const {
815   return IsCellOrTableHeader(data().role);
816 }
817 
GetTableCellIndex() const818 base::Optional<int> AXNode::GetTableCellIndex() const {
819   if (!IsTableCellOrHeader())
820     return base::nullopt;
821 
822   const AXTableInfo* table_info = GetAncestorTableInfo();
823   if (!table_info)
824     return base::nullopt;
825 
826   const auto& iter = table_info->cell_id_to_index.find(id());
827   if (iter != table_info->cell_id_to_index.end())
828     return int{iter->second};
829   return base::nullopt;
830 }
831 
GetTableCellColIndex() const832 base::Optional<int> AXNode::GetTableCellColIndex() const {
833   const AXTableInfo* table_info = GetAncestorTableInfo();
834   if (!table_info)
835     return base::nullopt;
836 
837   base::Optional<int> index = GetTableCellIndex();
838   if (!index)
839     return base::nullopt;
840 
841   return int{table_info->cell_data_vector[*index].col_index};
842 }
843 
GetTableCellRowIndex() const844 base::Optional<int> AXNode::GetTableCellRowIndex() const {
845   const AXTableInfo* table_info = GetAncestorTableInfo();
846   if (!table_info)
847     return base::nullopt;
848 
849   base::Optional<int> index = GetTableCellIndex();
850   if (!index)
851     return base::nullopt;
852 
853   return int{table_info->cell_data_vector[*index].row_index};
854 }
855 
GetTableCellColSpan() const856 base::Optional<int> AXNode::GetTableCellColSpan() const {
857   // If it's not a table cell, don't return a col span.
858   if (!IsTableCellOrHeader())
859     return base::nullopt;
860 
861   // Otherwise, try to return a colspan, with 1 as the default if it's not
862   // specified.
863   int col_span;
864   if (GetIntAttribute(ax::mojom::IntAttribute::kTableCellColumnSpan, &col_span))
865     return col_span;
866   return 1;
867 }
868 
GetTableCellRowSpan() const869 base::Optional<int> AXNode::GetTableCellRowSpan() const {
870   // If it's not a table cell, don't return a row span.
871   if (!IsTableCellOrHeader())
872     return base::nullopt;
873 
874   // Otherwise, try to return a row span, with 1 as the default if it's not
875   // specified.
876   int row_span;
877   if (GetIntAttribute(ax::mojom::IntAttribute::kTableCellRowSpan, &row_span))
878     return row_span;
879   return 1;
880 }
881 
GetTableCellAriaColIndex() const882 base::Optional<int> AXNode::GetTableCellAriaColIndex() const {
883   const AXTableInfo* table_info = GetAncestorTableInfo();
884   if (!table_info)
885     return base::nullopt;
886 
887   base::Optional<int> index = GetTableCellIndex();
888   if (!index)
889     return base::nullopt;
890 
891   return int{table_info->cell_data_vector[*index].aria_col_index};
892 }
893 
GetTableCellAriaRowIndex() const894 base::Optional<int> AXNode::GetTableCellAriaRowIndex() const {
895   const AXTableInfo* table_info = GetAncestorTableInfo();
896   if (!table_info)
897     return base::nullopt;
898 
899   base::Optional<int> index = GetTableCellIndex();
900   if (!index)
901     return base::nullopt;
902 
903   return int{table_info->cell_data_vector[*index].aria_row_index};
904 }
905 
GetTableCellColHeaderNodeIds() const906 std::vector<AXNode::AXID> AXNode::GetTableCellColHeaderNodeIds() const {
907   const AXTableInfo* table_info = GetAncestorTableInfo();
908   if (!table_info || table_info->col_count <= 0)
909     return std::vector<AXNode::AXID>();
910 
911   // If this node is not a cell, then return the headers for the first column.
912   int col_index = GetTableCellColIndex().value_or(0);
913 
914   return std::vector<AXNode::AXID>(table_info->col_headers[col_index]);
915 }
916 
GetTableCellColHeaders(std::vector<AXNode * > * col_headers) const917 void AXNode::GetTableCellColHeaders(std::vector<AXNode*>* col_headers) const {
918   DCHECK(col_headers);
919 
920   std::vector<int32_t> col_header_ids = GetTableCellColHeaderNodeIds();
921   IdVectorToNodeVector(col_header_ids, col_headers);
922 }
923 
GetTableCellRowHeaderNodeIds() const924 std::vector<AXNode::AXID> AXNode::GetTableCellRowHeaderNodeIds() const {
925   const AXTableInfo* table_info = GetAncestorTableInfo();
926   if (!table_info || table_info->row_count <= 0)
927     return std::vector<AXNode::AXID>();
928 
929   // If this node is not a cell, then return the headers for the first row.
930   int row_index = GetTableCellRowIndex().value_or(0);
931 
932   return std::vector<AXNode::AXID>(table_info->row_headers[row_index]);
933 }
934 
GetTableCellRowHeaders(std::vector<AXNode * > * row_headers) const935 void AXNode::GetTableCellRowHeaders(std::vector<AXNode*>* row_headers) const {
936   DCHECK(row_headers);
937 
938   std::vector<int32_t> row_header_ids = GetTableCellRowHeaderNodeIds();
939   IdVectorToNodeVector(row_header_ids, row_headers);
940 }
941 
IsCellOrHeaderOfARIATable() const942 bool AXNode::IsCellOrHeaderOfARIATable() const {
943   if (!IsTableCellOrHeader())
944     return false;
945 
946   const AXNode* node = this;
947   while (node && !node->IsTable())
948     node = node->parent();
949   if (!node)
950     return false;
951 
952   return node->data().role == ax::mojom::Role::kTable;
953 }
954 
IsCellOrHeaderOfARIAGrid() const955 bool AXNode::IsCellOrHeaderOfARIAGrid() const {
956   if (!IsTableCellOrHeader())
957     return false;
958 
959   const AXNode* node = this;
960   while (node && !node->IsTable())
961     node = node->parent();
962   if (!node)
963     return false;
964 
965   return node->data().role == ax::mojom::Role::kGrid ||
966          node->data().role == ax::mojom::Role::kTreeGrid;
967 }
968 
GetAncestorTableInfo() const969 AXTableInfo* AXNode::GetAncestorTableInfo() const {
970   const AXNode* node = this;
971   while (node && !node->IsTable())
972     node = node->parent();
973   if (node)
974     return tree_->GetTableInfo(node);
975   return nullptr;
976 }
977 
IdVectorToNodeVector(const std::vector<int32_t> & ids,std::vector<AXNode * > * nodes) const978 void AXNode::IdVectorToNodeVector(const std::vector<int32_t>& ids,
979                                   std::vector<AXNode*>* nodes) const {
980   for (int32_t id : ids) {
981     AXNode* node = tree_->GetFromId(id);
982     if (node)
983       nodes->push_back(node);
984   }
985 }
986 
GetHierarchicalLevel() const987 base::Optional<int> AXNode::GetHierarchicalLevel() const {
988   int hierarchical_level =
989       GetIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel);
990 
991   // According to the WAI_ARIA spec, a defined hierarchical level value is
992   // greater than 0.
993   // https://www.w3.org/TR/wai-aria-1.1/#aria-level
994   if (hierarchical_level > 0)
995     return hierarchical_level;
996 
997   return base::nullopt;
998 }
999 
IsOrderedSetItem() const1000 bool AXNode::IsOrderedSetItem() const {
1001   return ui::IsItemLike(data().role);
1002 }
1003 
IsOrderedSet() const1004 bool AXNode::IsOrderedSet() const {
1005   return ui::IsSetLike(data().role);
1006 }
1007 
1008 // Uses AXTree's cache to calculate node's PosInSet.
GetPosInSet()1009 base::Optional<int> AXNode::GetPosInSet() {
1010   return tree_->GetPosInSet(*this);
1011 }
1012 
1013 // Uses AXTree's cache to calculate node's SetSize.
GetSetSize()1014 base::Optional<int> AXNode::GetSetSize() {
1015   return tree_->GetSetSize(*this);
1016 }
1017 
1018 // Returns true if the role of ordered set matches the role of item.
1019 // Returns false otherwise.
SetRoleMatchesItemRole(const AXNode * ordered_set) const1020 bool AXNode::SetRoleMatchesItemRole(const AXNode* ordered_set) const {
1021   ax::mojom::Role item_role = data().role;
1022   // Switch on role of ordered set
1023   switch (ordered_set->data().role) {
1024     case ax::mojom::Role::kFeed:
1025       return item_role == ax::mojom::Role::kArticle;
1026     case ax::mojom::Role::kList:
1027       return item_role == ax::mojom::Role::kListItem;
1028     case ax::mojom::Role::kGroup:
1029       return item_role == ax::mojom::Role::kComment ||
1030              item_role == ax::mojom::Role::kListItem ||
1031              item_role == ax::mojom::Role::kMenuItem ||
1032              item_role == ax::mojom::Role::kMenuItemRadio ||
1033              item_role == ax::mojom::Role::kListBoxOption ||
1034              item_role == ax::mojom::Role::kTreeItem;
1035     case ax::mojom::Role::kMenu:
1036       return item_role == ax::mojom::Role::kMenuItem ||
1037              item_role == ax::mojom::Role::kMenuItemRadio ||
1038              item_role == ax::mojom::Role::kMenuItemCheckBox;
1039     case ax::mojom::Role::kMenuBar:
1040       return item_role == ax::mojom::Role::kMenuItem ||
1041              item_role == ax::mojom::Role::kMenuItemRadio ||
1042              item_role == ax::mojom::Role::kMenuItemCheckBox;
1043     case ax::mojom::Role::kTabList:
1044       return item_role == ax::mojom::Role::kTab;
1045     case ax::mojom::Role::kTree:
1046       return item_role == ax::mojom::Role::kTreeItem;
1047     case ax::mojom::Role::kListBox:
1048       return item_role == ax::mojom::Role::kListBoxOption;
1049     case ax::mojom::Role::kMenuListPopup:
1050       return item_role == ax::mojom::Role::kMenuListOption ||
1051              item_role == ax::mojom::Role::kMenuItem ||
1052              item_role == ax::mojom::Role::kMenuItemRadio ||
1053              item_role == ax::mojom::Role::kMenuItemCheckBox;
1054     case ax::mojom::Role::kRadioGroup:
1055       return item_role == ax::mojom::Role::kRadioButton;
1056     case ax::mojom::Role::kDescriptionList:
1057       // Only the term for each description list entry should receive posinset
1058       // and setsize.
1059       return item_role == ax::mojom::Role::kDescriptionListTerm ||
1060              item_role == ax::mojom::Role::kTerm;
1061     case ax::mojom::Role::kPopUpButton:
1062       // kPopUpButtons can wrap a kMenuListPopUp.
1063       return item_role == ax::mojom::Role::kMenuListPopup;
1064     default:
1065       return false;
1066   }
1067 }
1068 
IsIgnoredContainerForOrderedSet() const1069 bool AXNode::IsIgnoredContainerForOrderedSet() const {
1070   return IsIgnored() || IsEmbeddedGroup() ||
1071          data().role == ax::mojom::Role::kListItem ||
1072          data().role == ax::mojom::Role::kGenericContainer ||
1073          data().role == ax::mojom::Role::kUnknown;
1074 }
1075 
UpdateUnignoredCachedValuesRecursive(int startIndex)1076 int AXNode::UpdateUnignoredCachedValuesRecursive(int startIndex) {
1077   int count = 0;
1078   for (AXNode* child : children_) {
1079     if (child->IsIgnored()) {
1080       child->unignored_index_in_parent_ = 0;
1081       count += child->UpdateUnignoredCachedValuesRecursive(startIndex + count);
1082     } else {
1083       child->unignored_index_in_parent_ = startIndex + count++;
1084     }
1085   }
1086   unignored_child_count_ = count;
1087   return count;
1088 }
1089 
1090 // Finds ordered set that contains node.
1091 // Is not required for set's role to match node's role.
GetOrderedSet() const1092 AXNode* AXNode::GetOrderedSet() const {
1093   AXNode* result = parent();
1094   // Continue walking up while parent is invalid, ignored, a generic container,
1095   // unknown, or embedded group.
1096   while (result && result->IsIgnoredContainerForOrderedSet()) {
1097     result = result->parent();
1098   }
1099 
1100   return result;
1101 }
1102 
ComputeLastUnignoredChildRecursive() const1103 AXNode* AXNode::ComputeLastUnignoredChildRecursive() const {
1104   DCHECK(!tree_->GetTreeUpdateInProgressState());
1105   if (children().empty())
1106     return nullptr;
1107 
1108   for (int i = static_cast<int>(children().size()) - 1; i >= 0; --i) {
1109     AXNode* child = children_[i];
1110     if (!child->IsIgnored())
1111       return child;
1112 
1113     AXNode* descendant = child->ComputeLastUnignoredChildRecursive();
1114     if (descendant)
1115       return descendant;
1116   }
1117   return nullptr;
1118 }
1119 
ComputeFirstUnignoredChildRecursive() const1120 AXNode* AXNode::ComputeFirstUnignoredChildRecursive() const {
1121   DCHECK(!tree_->GetTreeUpdateInProgressState());
1122   for (size_t i = 0; i < children().size(); i++) {
1123     AXNode* child = children_[i];
1124     if (!child->IsIgnored())
1125       return child;
1126 
1127     AXNode* descendant = child->ComputeFirstUnignoredChildRecursive();
1128     if (descendant)
1129       return descendant;
1130   }
1131   return nullptr;
1132 }
1133 
GetTextForRangeValue() const1134 std::string AXNode::GetTextForRangeValue() const {
1135   DCHECK(data().IsRangeValueSupported());
1136   std::string range_value =
1137       data().GetStringAttribute(ax::mojom::StringAttribute::kValue);
1138   float numeric_value;
1139   if (range_value.empty() &&
1140       data().GetFloatAttribute(ax::mojom::FloatAttribute::kValueForRange,
1141                                &numeric_value)) {
1142     range_value = base::NumberToString(numeric_value);
1143   }
1144   return range_value;
1145 }
1146 
GetValueForColorWell() const1147 std::string AXNode::GetValueForColorWell() const {
1148   DCHECK_EQ(data().role, ax::mojom::Role::kColorWell);
1149   // static cast because SkColor is a 4-byte unsigned int
1150   unsigned int color = static_cast<unsigned int>(
1151       data().GetIntAttribute(ax::mojom::IntAttribute::kColorValue));
1152 
1153   unsigned int red = SkColorGetR(color);
1154   unsigned int green = SkColorGetG(color);
1155   unsigned int blue = SkColorGetB(color);
1156   return base::StringPrintf("%d%% red %d%% green %d%% blue", red * 100 / 255,
1157                             green * 100 / 255, blue * 100 / 255);
1158 }
1159 
GetValueForTextField() const1160 std::string AXNode::GetValueForTextField() const {
1161   DCHECK(data().IsTextField());
1162   std::string value =
1163       data().GetStringAttribute(ax::mojom::StringAttribute::kValue);
1164   // Some screen readers like Jaws and VoiceOver require a value to be set in
1165   // text fields with rich content, even though the same information is
1166   // available on the children.
1167   if (value.empty() && data().IsRichTextField())
1168     return GetInnerText();
1169   return value;
1170 }
1171 
IsIgnored() const1172 bool AXNode::IsIgnored() const {
1173   return data().IsIgnored();
1174 }
1175 
IsChildOfLeaf() const1176 bool AXNode::IsChildOfLeaf() const {
1177   const AXNode* ancestor = GetUnignoredParent();
1178   while (ancestor) {
1179     if (ancestor->IsLeaf())
1180       return true;
1181     ancestor = ancestor->GetUnignoredParent();
1182   }
1183   return false;
1184 }
1185 
IsLeaf() const1186 bool AXNode::IsLeaf() const {
1187   // A node is also a leaf if all of it's descendants are ignored.
1188   if (children().empty() || !GetUnignoredChildCount())
1189     return true;
1190 
1191 #if defined(OS_WIN)
1192   // On Windows, we want to hide the subtree of a collapsed <select> element.
1193   // Otherwise, ATs are always going to announce its options whether it's
1194   // collapsed or expanded. In the AXTree, this element corresponds to a node
1195   // with role ax::mojom::Role::kPopUpButton that is the parent of a node with
1196   // role ax::mojom::Role::kMenuListPopup.
1197   if (IsCollapsedMenuListPopUpButton())
1198     return true;
1199 #endif  // defined(OS_WIN)
1200 
1201   // These types of objects may have children that we use as internal
1202   // implementation details, but we want to expose them as leaves to platform
1203   // accessibility APIs because screen readers might be confused if they find
1204   // any children.
1205   if (data().IsPlainTextField() || IsText())
1206     return true;
1207 
1208   // Roles whose children are only presentational according to the ARIA and
1209   // HTML5 Specs should be hidden from screen readers.
1210   switch (data().role) {
1211     // According to the ARIA and Core-AAM specs:
1212     // https://w3c.github.io/aria/#button,
1213     // https://www.w3.org/TR/core-aam-1.1/#exclude_elements
1214     // buttons' children are presentational only and should be hidden from
1215     // screen readers. However, we cannot enforce the leafiness of buttons
1216     // because they may contain many rich, interactive descendants such as a day
1217     // in a calendar, and screen readers will need to interact with these
1218     // contents. See https://crbug.com/689204.
1219     // So we decided to not enforce the leafiness of buttons and expose all
1220     // children.
1221     case ax::mojom::Role::kButton:
1222       return false;
1223     case ax::mojom::Role::kDocCover:
1224     case ax::mojom::Role::kGraphicsSymbol:
1225     case ax::mojom::Role::kImage:
1226     case ax::mojom::Role::kMeter:
1227     case ax::mojom::Role::kScrollBar:
1228     case ax::mojom::Role::kSlider:
1229     case ax::mojom::Role::kSplitter:
1230     case ax::mojom::Role::kProgressIndicator:
1231       return true;
1232     default:
1233       return false;
1234   }
1235 }
1236 
IsInListMarker() const1237 bool AXNode::IsInListMarker() const {
1238   if (data().role == ax::mojom::Role::kListMarker)
1239     return true;
1240 
1241   // List marker node's children can only be text elements.
1242   if (!IsText())
1243     return false;
1244 
1245   // There is no need to iterate over all the ancestors of the current anchor
1246   // since a list marker node only has children on 2 levels.
1247   // i.e.:
1248   // AXLayoutObject role=kListMarker
1249   // ++StaticText
1250   // ++++InlineTextBox
1251   AXNode* parent_node = GetUnignoredParent();
1252   if (parent_node && parent_node->data().role == ax::mojom::Role::kListMarker)
1253     return true;
1254 
1255   AXNode* grandparent_node = parent_node->GetUnignoredParent();
1256   return grandparent_node &&
1257          grandparent_node->data().role == ax::mojom::Role::kListMarker;
1258 }
1259 
IsCollapsedMenuListPopUpButton() const1260 bool AXNode::IsCollapsedMenuListPopUpButton() const {
1261   if (data().role != ax::mojom::Role::kPopUpButton ||
1262       !data().HasState(ax::mojom::State::kCollapsed)) {
1263     return false;
1264   }
1265 
1266   // When a popup button contains a menu list popup, its only child is unignored
1267   // and is a menu list popup.
1268   AXNode* node = GetFirstUnignoredChild();
1269   if (!node)
1270     return false;
1271 
1272   return node->data().role == ax::mojom::Role::kMenuListPopup;
1273 }
1274 
GetCollapsedMenuListPopUpButtonAncestor() const1275 AXNode* AXNode::GetCollapsedMenuListPopUpButtonAncestor() const {
1276   AXNode* node = GetOrderedSet();
1277 
1278   if (!node)
1279     return nullptr;
1280 
1281   // The ordered set returned is either the popup element child of the popup
1282   // button (e.g., the AXMenuListPopup) or the popup button itself. We need
1283   // |node| to point to the popup button itself.
1284   if (node->data().role != ax::mojom::Role::kPopUpButton) {
1285     node = node->parent();
1286     if (!node)
1287       return nullptr;
1288   }
1289 
1290   return node->IsCollapsedMenuListPopUpButton() ? node : nullptr;
1291 }
1292 
IsEmbeddedGroup() const1293 bool AXNode::IsEmbeddedGroup() const {
1294   if (data().role != ax::mojom::Role::kGroup || !parent())
1295     return false;
1296 
1297   return ui::IsSetLike(parent()->data().role);
1298 }
1299 
GetTextFieldAncestor() const1300 AXNode* AXNode::GetTextFieldAncestor() const {
1301   // The descendants of a text field usually have State::kEditable, however in
1302   // the case of Role::kSearchBox or Role::kSpinButton being the text field
1303   // ancestor, its immediate descendant can have Role::kGenericContainer without
1304   // State::kEditable.
1305   for (AXNode* ancestor = const_cast<AXNode*>(this);
1306        ancestor &&
1307        (ancestor->data().HasState(ax::mojom::State::kEditable) ||
1308         ancestor->data().role == ax::mojom::Role::kGenericContainer);
1309        ancestor = ancestor->GetUnignoredParent()) {
1310     if (ancestor->data().IsTextField())
1311       return ancestor;
1312   }
1313   return nullptr;
1314 }
1315 
IsDescendantOfPlainTextField() const1316 bool AXNode::IsDescendantOfPlainTextField() const {
1317   AXNode* textfield_node = GetTextFieldAncestor();
1318   return textfield_node && textfield_node->data().IsPlainTextField();
1319 }
1320 
1321 }  // namespace ui
1322