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_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "ui/accessibility/ax_enums.mojom.h"
14 #include "ui/accessibility/ax_language_detection.h"
15 #include "ui/accessibility/ax_role_properties.h"
16 #include "ui/accessibility/ax_table_info.h"
17 #include "ui/accessibility/ax_tree.h"
18 #include "ui/gfx/transform.h"
19 
20 namespace ui {
21 
22 constexpr AXNode::AXID AXNode::kInvalidAXID;
23 
AXNode(AXNode::OwnerTree * tree,AXNode * parent,int32_t id,size_t index_in_parent,size_t unignored_index_in_parent)24 AXNode::AXNode(AXNode::OwnerTree* tree,
25                AXNode* parent,
26                int32_t id,
27                size_t index_in_parent,
28                size_t unignored_index_in_parent)
29     : tree_(tree),
30       index_in_parent_(index_in_parent),
31       unignored_index_in_parent_(unignored_index_in_parent),
32       parent_(parent) {
33   data_.id = id;
34 }
35 
36 AXNode::~AXNode() = default;
37 
GetUnignoredChildCount() const38 size_t AXNode::GetUnignoredChildCount() const {
39   DCHECK(!tree_->GetTreeUpdateInProgressState());
40   return unignored_child_count_;
41 }
42 
TakeData()43 AXNodeData&& AXNode::TakeData() {
44   return std::move(data_);
45 }
46 
GetUnignoredChildAtIndex(size_t index) const47 AXNode* AXNode::GetUnignoredChildAtIndex(size_t index) const {
48   DCHECK(!tree_->GetTreeUpdateInProgressState());
49   size_t count = 0;
50   for (auto it = UnignoredChildrenBegin(); it != UnignoredChildrenEnd(); ++it) {
51     if (count == index)
52       return it.get();
53     ++count;
54   }
55   return nullptr;
56 }
57 
GetUnignoredParent() const58 AXNode* AXNode::GetUnignoredParent() const {
59   DCHECK(!tree_->GetTreeUpdateInProgressState());
60   AXNode* result = parent();
61   while (result && result->IsIgnored())
62     result = result->parent();
63   return result;
64 }
65 
GetUnignoredIndexInParent() const66 size_t AXNode::GetUnignoredIndexInParent() const {
67   DCHECK(!tree_->GetTreeUpdateInProgressState());
68   return unignored_index_in_parent_;
69 }
70 
GetIndexInParent() const71 size_t AXNode::GetIndexInParent() const {
72   DCHECK(!tree_->GetTreeUpdateInProgressState());
73   return index_in_parent_;
74 }
75 
GetFirstUnignoredChild() const76 AXNode* AXNode::GetFirstUnignoredChild() const {
77   DCHECK(!tree_->GetTreeUpdateInProgressState());
78   return ComputeFirstUnignoredChildRecursive();
79 }
80 
GetLastUnignoredChild() const81 AXNode* AXNode::GetLastUnignoredChild() const {
82   DCHECK(!tree_->GetTreeUpdateInProgressState());
83   return ComputeLastUnignoredChildRecursive();
84 }
85 
GetDeepestFirstUnignoredChild() const86 AXNode* AXNode::GetDeepestFirstUnignoredChild() const {
87   if (!GetUnignoredChildCount())
88     return nullptr;
89 
90   AXNode* deepest_child = GetFirstUnignoredChild();
91   while (deepest_child->GetUnignoredChildCount()) {
92     deepest_child = deepest_child->GetFirstUnignoredChild();
93   }
94 
95   return deepest_child;
96 }
97 
GetDeepestLastUnignoredChild() const98 AXNode* AXNode::GetDeepestLastUnignoredChild() const {
99   if (!GetUnignoredChildCount())
100     return nullptr;
101 
102   AXNode* deepest_child = GetLastUnignoredChild();
103   while (deepest_child->GetUnignoredChildCount()) {
104     deepest_child = deepest_child->GetLastUnignoredChild();
105   }
106 
107   return deepest_child;
108 }
109 
GetNextUnignoredSibling() const110 AXNode* AXNode::GetNextUnignoredSibling() const {
111   DCHECK(!tree_->GetTreeUpdateInProgressState());
112   AXNode* parent_node = parent();
113   size_t index = index_in_parent() + 1;
114   while (parent_node) {
115     if (index < parent_node->children().size()) {
116       AXNode* child = parent_node->children()[index];
117       if (!child->IsIgnored())
118         return child;  // valid position (unignored child)
119 
120       // If the node is ignored, drill down to the ignored node's first child.
121       parent_node = child;
122       index = 0;
123     } else {
124       // If the parent is not ignored and we are past all of its children, there
125       // is no next sibling.
126       if (!parent_node->IsIgnored())
127         return nullptr;
128 
129       // If the parent is ignored and we are past all of its children, continue
130       // on to the parent's next sibling.
131       index = parent_node->index_in_parent() + 1;
132       parent_node = parent_node->parent();
133     }
134   }
135   return nullptr;
136 }
137 
GetPreviousUnignoredSibling() const138 AXNode* AXNode::GetPreviousUnignoredSibling() const {
139   DCHECK(!tree_->GetTreeUpdateInProgressState());
140   AXNode* parent_node = parent();
141   base::Optional<size_t> index;
142   if (index_in_parent() > 0)
143     index = index_in_parent() - 1;
144   while (parent_node) {
145     if (index.has_value()) {
146       AXNode* child = parent_node->children()[index.value()];
147       if (!child->IsIgnored())
148         return child;  // valid position (unignored child)
149 
150       // If the node is ignored, drill down to the ignored node's last child.
151       parent_node = child;
152       if (parent_node->children().empty())
153         index = base::nullopt;
154       else
155         index = parent_node->children().size() - 1;
156     } else {
157       // If the parent is not ignored and we are past all of its children, there
158       // is no next sibling.
159       if (!parent_node->IsIgnored())
160         return nullptr;
161 
162       // If the parent is ignored and we are past all of its children, continue
163       // on to the parent's previous sibling.
164       if (parent_node->index_in_parent() == 0)
165         index = base::nullopt;
166       else
167         index = parent_node->index_in_parent() - 1;
168       parent_node = parent_node->parent();
169     }
170   }
171   return nullptr;
172 }
173 
GetNextUnignoredInTreeOrder() const174 AXNode* AXNode::GetNextUnignoredInTreeOrder() const {
175   if (GetUnignoredChildCount())
176     return GetFirstUnignoredChild();
177 
178   const AXNode* node = this;
179   while (node) {
180     AXNode* sibling = node->GetNextUnignoredSibling();
181     if (sibling)
182       return sibling;
183 
184     node = node->GetUnignoredParent();
185   }
186 
187   return nullptr;
188 }
189 
GetPreviousUnignoredInTreeOrder() const190 AXNode* AXNode::GetPreviousUnignoredInTreeOrder() const {
191   AXNode* sibling = GetPreviousUnignoredSibling();
192   if (!sibling)
193     return GetUnignoredParent();
194 
195   if (sibling->GetUnignoredChildCount())
196     return sibling->GetDeepestLastUnignoredChild();
197 
198   return sibling;
199 }
200 
UnignoredChildrenBegin() const201 AXNode::UnignoredChildIterator AXNode::UnignoredChildrenBegin() const {
202   DCHECK(!tree_->GetTreeUpdateInProgressState());
203   return UnignoredChildIterator(this, GetFirstUnignoredChild());
204 }
205 
UnignoredChildrenEnd() const206 AXNode::UnignoredChildIterator AXNode::UnignoredChildrenEnd() const {
207   DCHECK(!tree_->GetTreeUpdateInProgressState());
208   return UnignoredChildIterator(this, nullptr);
209 }
210 
IsText() const211 bool AXNode::IsText() const {
212   return data().role == ax::mojom::Role::kStaticText ||
213          data().role == ax::mojom::Role::kLineBreak ||
214          data().role == ax::mojom::Role::kInlineTextBox;
215 }
216 
IsLineBreak() const217 bool AXNode::IsLineBreak() const {
218   return data().role == ax::mojom::Role::kLineBreak ||
219          (data().role == ax::mojom::Role::kInlineTextBox &&
220           data().GetBoolAttribute(
221               ax::mojom::BoolAttribute::kIsLineBreakingObject));
222 }
223 
SetData(const AXNodeData & src)224 void AXNode::SetData(const AXNodeData& src) {
225   data_ = src;
226 }
227 
SetLocation(int32_t offset_container_id,const gfx::RectF & location,gfx::Transform * transform)228 void AXNode::SetLocation(int32_t offset_container_id,
229                          const gfx::RectF& location,
230                          gfx::Transform* transform) {
231   data_.relative_bounds.offset_container_id = offset_container_id;
232   data_.relative_bounds.bounds = location;
233   if (transform) {
234     data_.relative_bounds.transform =
235         std::make_unique<gfx::Transform>(*transform);
236   } else {
237     data_.relative_bounds.transform.reset();
238   }
239 }
240 
SetIndexInParent(size_t index_in_parent)241 void AXNode::SetIndexInParent(size_t index_in_parent) {
242   index_in_parent_ = index_in_parent;
243 }
244 
UpdateUnignoredCachedValues()245 void AXNode::UpdateUnignoredCachedValues() {
246   if (!IsIgnored())
247     UpdateUnignoredCachedValuesRecursive(0);
248 }
249 
SwapChildren(std::vector<AXNode * > * children)250 void AXNode::SwapChildren(std::vector<AXNode*>* children) {
251   children->swap(children_);
252 }
253 
Destroy()254 void AXNode::Destroy() {
255   delete this;
256 }
257 
IsDescendantOf(const AXNode * ancestor) const258 bool AXNode::IsDescendantOf(const AXNode* ancestor) const {
259   if (this == ancestor)
260     return true;
261   if (parent())
262     return parent()->IsDescendantOf(ancestor);
263 
264   return false;
265 }
266 
GetOrComputeLineStartOffsets()267 std::vector<int> AXNode::GetOrComputeLineStartOffsets() {
268   std::vector<int> line_offsets;
269   if (data().GetIntListAttribute(ax::mojom::IntListAttribute::kCachedLineStarts,
270                                  &line_offsets)) {
271     return line_offsets;
272   }
273 
274   int start_offset = 0;
275   ComputeLineStartOffsets(&line_offsets, &start_offset);
276   data_.AddIntListAttribute(ax::mojom::IntListAttribute::kCachedLineStarts,
277                             line_offsets);
278   return line_offsets;
279 }
280 
ComputeLineStartOffsets(std::vector<int> * line_offsets,int * start_offset) const281 void AXNode::ComputeLineStartOffsets(std::vector<int>* line_offsets,
282                                      int* start_offset) const {
283   DCHECK(line_offsets);
284   DCHECK(start_offset);
285   for (const AXNode* child : children()) {
286     DCHECK(child);
287     if (!child->children().empty()) {
288       child->ComputeLineStartOffsets(line_offsets, start_offset);
289       continue;
290     }
291 
292     // Don't report if the first piece of text starts a new line or not.
293     if (*start_offset && !child->data().HasIntAttribute(
294                              ax::mojom::IntAttribute::kPreviousOnLineId)) {
295       // If there are multiple objects with an empty accessible label at the
296       // start of a line, only include a single line start offset.
297       if (line_offsets->empty() || line_offsets->back() != *start_offset)
298         line_offsets->push_back(*start_offset);
299     }
300 
301     base::string16 text =
302         child->data().GetString16Attribute(ax::mojom::StringAttribute::kName);
303     *start_offset += static_cast<int>(text.length());
304   }
305 }
306 
GetInheritedStringAttribute(ax::mojom::StringAttribute attribute) const307 const std::string& AXNode::GetInheritedStringAttribute(
308     ax::mojom::StringAttribute attribute) const {
309   const AXNode* current_node = this;
310   do {
311     if (current_node->data().HasStringAttribute(attribute))
312       return current_node->data().GetStringAttribute(attribute);
313     current_node = current_node->parent();
314   } while (current_node);
315   return base::EmptyString();
316 }
317 
GetInheritedString16Attribute(ax::mojom::StringAttribute attribute) const318 base::string16 AXNode::GetInheritedString16Attribute(
319     ax::mojom::StringAttribute attribute) const {
320   return base::UTF8ToUTF16(GetInheritedStringAttribute(attribute));
321 }
322 
GetLanguageInfo() const323 AXLanguageInfo* AXNode::GetLanguageInfo() const {
324   return language_info_.get();
325 }
326 
SetLanguageInfo(std::unique_ptr<AXLanguageInfo> lang_info)327 void AXNode::SetLanguageInfo(std::unique_ptr<AXLanguageInfo> lang_info) {
328   language_info_ = std::move(lang_info);
329 }
330 
ClearLanguageInfo()331 void AXNode::ClearLanguageInfo() {
332   language_info_.reset();
333 }
334 
GetLanguage() const335 std::string AXNode::GetLanguage() const {
336   // Walk up tree considering both detected and author declared languages.
337   for (const AXNode* cur = this; cur; cur = cur->parent()) {
338     // If language detection has assigned a language then we prefer that.
339     const AXLanguageInfo* lang_info = cur->GetLanguageInfo();
340     if (lang_info && !lang_info->language.empty()) {
341       return lang_info->language;
342     }
343 
344     // If the page author has declared a language attribute we fallback to that.
345     const AXNodeData& data = cur->data();
346     if (data.HasStringAttribute(ax::mojom::StringAttribute::kLanguage)) {
347       return data.GetStringAttribute(ax::mojom::StringAttribute::kLanguage);
348     }
349   }
350 
351   return std::string();
352 }
353 
operator <<(std::ostream & stream,const AXNode & node)354 std::ostream& operator<<(std::ostream& stream, const AXNode& node) {
355   return stream << node.data().ToString();
356 }
357 
IsTable() const358 bool AXNode::IsTable() const {
359   return IsTableLike(data().role);
360 }
361 
GetTableColCount() const362 base::Optional<int> AXNode::GetTableColCount() const {
363   const AXTableInfo* table_info = GetAncestorTableInfo();
364   if (!table_info)
365     return base::nullopt;
366   return int(table_info->col_count);
367 }
368 
GetTableRowCount() const369 base::Optional<int> AXNode::GetTableRowCount() const {
370   const AXTableInfo* table_info = GetAncestorTableInfo();
371   if (!table_info)
372     return base::nullopt;
373   return int(table_info->row_count);
374 }
375 
GetTableAriaColCount() const376 base::Optional<int> AXNode::GetTableAriaColCount() const {
377   const AXTableInfo* table_info = GetAncestorTableInfo();
378   if (!table_info)
379     return base::nullopt;
380   return table_info->aria_col_count;
381 }
382 
GetTableAriaRowCount() const383 base::Optional<int> AXNode::GetTableAriaRowCount() const {
384   const AXTableInfo* table_info = GetAncestorTableInfo();
385   if (!table_info)
386     return base::nullopt;
387   return table_info->aria_row_count;
388 }
389 
GetTableCellCount() const390 base::Optional<int> AXNode::GetTableCellCount() const {
391   const AXTableInfo* table_info = GetAncestorTableInfo();
392   if (!table_info)
393     return base::nullopt;
394 
395   return static_cast<int>(table_info->unique_cell_ids.size());
396 }
397 
GetTableHasColumnOrRowHeaderNode() const398 base::Optional<bool> AXNode::GetTableHasColumnOrRowHeaderNode() const {
399   const AXTableInfo* table_info = GetAncestorTableInfo();
400   if (!table_info)
401     return base::nullopt;
402 
403   return !table_info->all_headers.empty();
404 }
405 
GetTableCellFromIndex(int index) const406 AXNode* AXNode::GetTableCellFromIndex(int index) const {
407   const AXTableInfo* table_info = GetAncestorTableInfo();
408   if (!table_info)
409     return nullptr;
410 
411   // There is a table but there is no cell with the given index.
412   if (index < 0 || size_t(index) >= table_info->unique_cell_ids.size()) {
413     return nullptr;
414   }
415 
416   return tree_->GetFromId(table_info->unique_cell_ids[size_t(index)]);
417 }
418 
GetTableCaption() const419 AXNode* AXNode::GetTableCaption() const {
420   const AXTableInfo* table_info = GetAncestorTableInfo();
421   if (!table_info)
422     return nullptr;
423 
424   return tree_->GetFromId(table_info->caption_id);
425 }
426 
GetTableCellFromCoords(int row_index,int col_index) const427 AXNode* AXNode::GetTableCellFromCoords(int row_index, int col_index) const {
428   const AXTableInfo* table_info = GetAncestorTableInfo();
429   if (!table_info)
430     return nullptr;
431 
432   // There is a table but the given coordinates are outside the table.
433   if (row_index < 0 || size_t(row_index) >= table_info->row_count ||
434       col_index < 0 || size_t(col_index) >= table_info->col_count) {
435     return nullptr;
436   }
437 
438   return tree_->GetFromId(
439       table_info->cell_ids[size_t(row_index)][size_t(col_index)]);
440 }
441 
GetTableColHeaderNodeIds(int col_index,std::vector<int32_t> * col_header_ids) const442 void AXNode::GetTableColHeaderNodeIds(
443     int col_index,
444     std::vector<int32_t>* col_header_ids) const {
445   DCHECK(col_header_ids);
446   const AXTableInfo* table_info = GetAncestorTableInfo();
447   if (!table_info)
448     return;
449 
450   if (col_index < 0 || size_t(col_index) >= table_info->col_count)
451     return;
452 
453   for (size_t i = 0; i < table_info->col_headers[size_t(col_index)].size(); i++)
454     col_header_ids->push_back(table_info->col_headers[size_t(col_index)][i]);
455 }
456 
GetTableRowHeaderNodeIds(int row_index,std::vector<int32_t> * row_header_ids) const457 void AXNode::GetTableRowHeaderNodeIds(
458     int row_index,
459     std::vector<int32_t>* row_header_ids) const {
460   DCHECK(row_header_ids);
461   const AXTableInfo* table_info = GetAncestorTableInfo();
462   if (!table_info)
463     return;
464 
465   if (row_index < 0 || size_t(row_index) >= table_info->row_count)
466     return;
467 
468   for (size_t i = 0; i < table_info->row_headers[size_t(row_index)].size(); i++)
469     row_header_ids->push_back(table_info->row_headers[size_t(row_index)][i]);
470 }
471 
GetTableUniqueCellIds(std::vector<int32_t> * cell_ids) const472 void AXNode::GetTableUniqueCellIds(std::vector<int32_t>* cell_ids) const {
473   DCHECK(cell_ids);
474   const AXTableInfo* table_info = GetAncestorTableInfo();
475   if (!table_info)
476     return;
477 
478   cell_ids->assign(table_info->unique_cell_ids.begin(),
479                    table_info->unique_cell_ids.end());
480 }
481 
GetExtraMacNodes() const482 const std::vector<AXNode*>* AXNode::GetExtraMacNodes() const {
483   // Should only be available on the table node itself, not any of its children.
484   const AXTableInfo* table_info = tree_->GetTableInfo(this);
485   if (!table_info)
486     return nullptr;
487 
488   return &table_info->extra_mac_nodes;
489 }
490 
491 //
492 // Table row-like nodes.
493 //
494 
IsTableRow() const495 bool AXNode::IsTableRow() const {
496   return ui::IsTableRow(data().role);
497 }
498 
GetTableRowRowIndex() const499 base::Optional<int> AXNode::GetTableRowRowIndex() const {
500   if (!IsTableRow())
501     return base::nullopt;
502 
503   const AXTableInfo* table_info = GetAncestorTableInfo();
504   if (!table_info)
505     return base::nullopt;
506 
507   const auto& iter = table_info->row_id_to_index.find(id());
508   if (iter == table_info->row_id_to_index.end())
509     return base::nullopt;
510   return int(iter->second);
511 }
512 
GetTableRowNodeIds() const513 std::vector<AXNode::AXID> AXNode::GetTableRowNodeIds() const {
514   std::vector<AXNode::AXID> row_node_ids;
515   const AXTableInfo* table_info = GetAncestorTableInfo();
516   if (!table_info)
517     return row_node_ids;
518 
519   for (AXNode* node : table_info->row_nodes)
520     row_node_ids.push_back(node->data().id);
521 
522   return row_node_ids;
523 }
524 
525 #if defined(OS_MACOSX)
526 
527 //
528 // Table column-like nodes. These nodes are only present on macOS.
529 //
530 
IsTableColumn() const531 bool AXNode::IsTableColumn() const {
532   return ui::IsTableColumn(data().role);
533 }
534 
GetTableColColIndex() const535 base::Optional<int> AXNode::GetTableColColIndex() const {
536   if (!IsTableColumn())
537     return base::nullopt;
538 
539   const AXTableInfo* table_info = GetAncestorTableInfo();
540   if (!table_info)
541     return base::nullopt;
542 
543   int index = 0;
544   for (const AXNode* node : table_info->extra_mac_nodes) {
545     if (node == this)
546       break;
547     index++;
548   }
549   return index;
550 }
551 
552 #endif  // defined(OS_MACOSX)
553 
554 //
555 // Table cell-like nodes.
556 //
557 
IsTableCellOrHeader() const558 bool AXNode::IsTableCellOrHeader() const {
559   return IsCellOrTableHeader(data().role);
560 }
561 
GetTableCellIndex() const562 base::Optional<int> AXNode::GetTableCellIndex() const {
563   if (!IsTableCellOrHeader())
564     return base::nullopt;
565 
566   const AXTableInfo* table_info = GetAncestorTableInfo();
567   if (!table_info)
568     return base::nullopt;
569 
570   const auto& iter = table_info->cell_id_to_index.find(id());
571   if (iter != table_info->cell_id_to_index.end())
572     return int(iter->second);
573   return base::nullopt;
574 }
575 
GetTableCellColIndex() const576 base::Optional<int> AXNode::GetTableCellColIndex() const {
577   const AXTableInfo* table_info = GetAncestorTableInfo();
578   if (!table_info)
579     return base::nullopt;
580 
581   base::Optional<int> index = GetTableCellIndex();
582   if (!index)
583     return base::nullopt;
584 
585   return int(table_info->cell_data_vector[*index].col_index);
586 }
587 
GetTableCellRowIndex() const588 base::Optional<int> AXNode::GetTableCellRowIndex() const {
589   const AXTableInfo* table_info = GetAncestorTableInfo();
590   if (!table_info)
591     return base::nullopt;
592 
593   base::Optional<int> index = GetTableCellIndex();
594   if (!index)
595     return base::nullopt;
596 
597   return int(table_info->cell_data_vector[*index].row_index);
598 }
599 
GetTableCellColSpan() const600 base::Optional<int> AXNode::GetTableCellColSpan() const {
601   // If it's not a table cell, don't return a col span.
602   if (!IsTableCellOrHeader())
603     return base::nullopt;
604 
605   // Otherwise, try to return a colspan, with 1 as the default if it's not
606   // specified.
607   int col_span;
608   if (GetIntAttribute(ax::mojom::IntAttribute::kTableCellColumnSpan, &col_span))
609     return col_span;
610   return 1;
611 }
612 
GetTableCellRowSpan() const613 base::Optional<int> AXNode::GetTableCellRowSpan() const {
614   // If it's not a table cell, don't return a row span.
615   if (!IsTableCellOrHeader())
616     return base::nullopt;
617 
618   // Otherwise, try to return a row span, with 1 as the default if it's not
619   // specified.
620   int row_span;
621   if (GetIntAttribute(ax::mojom::IntAttribute::kTableCellRowSpan, &row_span))
622     return row_span;
623   return 1;
624 }
625 
GetTableCellAriaColIndex() const626 base::Optional<int> AXNode::GetTableCellAriaColIndex() const {
627   const AXTableInfo* table_info = GetAncestorTableInfo();
628   if (!table_info)
629     return base::nullopt;
630 
631   base::Optional<int> index = GetTableCellIndex();
632   if (!index)
633     return base::nullopt;
634 
635   return int(table_info->cell_data_vector[*index].aria_col_index);
636 }
637 
GetTableCellAriaRowIndex() const638 base::Optional<int> AXNode::GetTableCellAriaRowIndex() const {
639   const AXTableInfo* table_info = GetAncestorTableInfo();
640   if (!table_info)
641     return base::nullopt;
642 
643   base::Optional<int> index = GetTableCellIndex();
644   if (!index)
645     return base::nullopt;
646 
647   return int(table_info->cell_data_vector[*index].aria_row_index);
648 }
649 
GetTableCellColHeaderNodeIds(std::vector<int32_t> * col_header_ids) const650 void AXNode::GetTableCellColHeaderNodeIds(
651     std::vector<int32_t>* col_header_ids) const {
652   DCHECK(col_header_ids);
653   const AXTableInfo* table_info = GetAncestorTableInfo();
654   if (!table_info || table_info->col_count <= 0)
655     return;
656 
657   // If this node is not a cell, then return the headers for the first column.
658   int col_index = GetTableCellColIndex().value_or(0);
659   const auto& col = table_info->col_headers[col_index];
660   for (int header : col)
661     col_header_ids->push_back(header);
662 }
663 
GetTableCellColHeaders(std::vector<AXNode * > * col_headers) const664 void AXNode::GetTableCellColHeaders(std::vector<AXNode*>* col_headers) const {
665   DCHECK(col_headers);
666 
667   std::vector<int32_t> col_header_ids;
668   GetTableCellColHeaderNodeIds(&col_header_ids);
669   IdVectorToNodeVector(col_header_ids, col_headers);
670 }
671 
GetTableCellRowHeaderNodeIds(std::vector<int32_t> * row_header_ids) const672 void AXNode::GetTableCellRowHeaderNodeIds(
673     std::vector<int32_t>* row_header_ids) const {
674   DCHECK(row_header_ids);
675   const AXTableInfo* table_info = GetAncestorTableInfo();
676   if (!table_info || table_info->row_count <= 0)
677     return;
678 
679   // If this node is not a cell, then return the headers for the first row.
680   int row_index = GetTableCellRowIndex().value_or(0);
681   const auto& row = table_info->row_headers[row_index];
682   for (int header : row)
683     row_header_ids->push_back(header);
684 }
685 
GetTableCellRowHeaders(std::vector<AXNode * > * row_headers) const686 void AXNode::GetTableCellRowHeaders(std::vector<AXNode*>* row_headers) const {
687   DCHECK(row_headers);
688 
689   std::vector<int32_t> row_header_ids;
690   GetTableCellRowHeaderNodeIds(&row_header_ids);
691   IdVectorToNodeVector(row_header_ids, row_headers);
692 }
693 
IsCellOrHeaderOfARIATable() const694 bool AXNode::IsCellOrHeaderOfARIATable() const {
695   if (!IsTableCellOrHeader())
696     return false;
697 
698   const AXNode* node = this;
699   while (node && !node->IsTable())
700     node = node->parent();
701   if (!node)
702     return false;
703 
704   return node->data().role == ax::mojom::Role::kTable;
705 }
706 
IsCellOrHeaderOfARIAGrid() const707 bool AXNode::IsCellOrHeaderOfARIAGrid() const {
708   if (!IsTableCellOrHeader())
709     return false;
710 
711   const AXNode* node = this;
712   while (node && !node->IsTable())
713     node = node->parent();
714   if (!node)
715     return false;
716 
717   return node->data().role == ax::mojom::Role::kGrid ||
718          node->data().role == ax::mojom::Role::kTreeGrid;
719 }
720 
GetAncestorTableInfo() const721 AXTableInfo* AXNode::GetAncestorTableInfo() const {
722   const AXNode* node = this;
723   while (node && !node->IsTable())
724     node = node->parent();
725   if (node)
726     return tree_->GetTableInfo(node);
727   return nullptr;
728 }
729 
IdVectorToNodeVector(const std::vector<int32_t> & ids,std::vector<AXNode * > * nodes) const730 void AXNode::IdVectorToNodeVector(const std::vector<int32_t>& ids,
731                                   std::vector<AXNode*>* nodes) const {
732   for (int32_t id : ids) {
733     AXNode* node = tree_->GetFromId(id);
734     if (node)
735       nodes->push_back(node);
736   }
737 }
738 
GetHierarchicalLevel() const739 base::Optional<int> AXNode::GetHierarchicalLevel() const {
740   int hierarchical_level =
741       GetIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel);
742 
743   // According to the WAI_ARIA spec, a defined hierarchical level value is
744   // greater than 0.
745   // https://www.w3.org/TR/wai-aria-1.1/#aria-level
746   if (hierarchical_level > 0)
747     return hierarchical_level;
748 
749   return base::nullopt;
750 }
751 
IsOrderedSetItem() const752 bool AXNode::IsOrderedSetItem() const {
753   return ui::IsItemLike(data().role);
754 }
755 
IsOrderedSet() const756 bool AXNode::IsOrderedSet() const {
757   return ui::IsSetLike(data().role);
758 }
759 
760 // pos_in_set and set_size related functions.
761 // Uses AXTree's cache to calculate node's pos_in_set.
GetPosInSet()762 base::Optional<int> AXNode::GetPosInSet() {
763   // Only allow this to be called on nodes that can hold pos_in_set values,
764   // which are defined in the ARIA spec.
765   if (!IsOrderedSetItem() || IsIgnored())
766     return base::nullopt;
767 
768   const AXNode* ordered_set = GetOrderedSet();
769   if (!ordered_set) {
770     return base::nullopt;
771   }
772 
773   // If tree is being updated, return no value.
774   if (tree()->GetTreeUpdateInProgressState())
775     return base::nullopt;
776 
777   // See AXTree::GetPosInSet
778   return tree_->GetPosInSet(*this, ordered_set);
779 }
780 
781 // Uses AXTree's cache to calculate node's set_size.
GetSetSize()782 base::Optional<int> AXNode::GetSetSize() {
783   // Only allow this to be called on nodes that can hold set_size values, which
784   // are defined in the ARIA spec.
785   if ((!IsOrderedSetItem() && !IsOrderedSet()) || IsIgnored())
786     return base::nullopt;
787 
788   // If node is item-like, find its outerlying ordered set. Otherwise,
789   // this node is the ordered set.
790   const AXNode* ordered_set = this;
791   if (IsItemLike(data().role))
792     ordered_set = GetOrderedSet();
793   if (!ordered_set)
794     return base::nullopt;
795 
796   // If tree is being updated, return no value.
797   if (tree()->GetTreeUpdateInProgressState())
798     return base::nullopt;
799 
800   // See AXTree::GetSetSize
801   int32_t set_size = tree_->GetSetSize(*this, ordered_set);
802   if (set_size < 0)
803     return base::nullopt;
804   return set_size;
805 }
806 
807 // Returns true if the role of ordered set matches the role of item.
808 // Returns false otherwise.
SetRoleMatchesItemRole(const AXNode * ordered_set) const809 bool AXNode::SetRoleMatchesItemRole(const AXNode* ordered_set) const {
810   ax::mojom::Role item_role = data().role;
811   // Switch on role of ordered set
812   switch (ordered_set->data().role) {
813     case ax::mojom::Role::kFeed:
814       return item_role == ax::mojom::Role::kArticle;
815     case ax::mojom::Role::kList:
816       return item_role == ax::mojom::Role::kListItem;
817     case ax::mojom::Role::kGroup:
818       return item_role == ax::mojom::Role::kComment ||
819              item_role == ax::mojom::Role::kListItem ||
820              item_role == ax::mojom::Role::kMenuItem ||
821              item_role == ax::mojom::Role::kMenuItemRadio ||
822              item_role == ax::mojom::Role::kTreeItem;
823     case ax::mojom::Role::kMenu:
824       return item_role == ax::mojom::Role::kMenuItem ||
825              item_role == ax::mojom::Role::kMenuItemRadio ||
826              item_role == ax::mojom::Role::kMenuItemCheckBox;
827     case ax::mojom::Role::kMenuBar:
828       return item_role == ax::mojom::Role::kMenuItem ||
829              item_role == ax::mojom::Role::kMenuItemRadio ||
830              item_role == ax::mojom::Role::kMenuItemCheckBox;
831     case ax::mojom::Role::kTabList:
832       return item_role == ax::mojom::Role::kTab;
833     case ax::mojom::Role::kTree:
834       return item_role == ax::mojom::Role::kTreeItem;
835     case ax::mojom::Role::kListBox:
836       return item_role == ax::mojom::Role::kListBoxOption;
837     case ax::mojom::Role::kMenuListPopup:
838       return item_role == ax::mojom::Role::kMenuListOption ||
839              item_role == ax::mojom::Role::kMenuItem ||
840              item_role == ax::mojom::Role::kMenuItemRadio ||
841              item_role == ax::mojom::Role::kMenuItemCheckBox;
842     case ax::mojom::Role::kRadioGroup:
843       return item_role == ax::mojom::Role::kRadioButton;
844     case ax::mojom::Role::kDescriptionList:
845       // Only the term for each description list entry should receive posinset
846       // and setsize.
847       return item_role == ax::mojom::Role::kDescriptionListTerm ||
848              item_role == ax::mojom::Role::kTerm;
849     case ax::mojom::Role::kPopUpButton:
850       // kPopUpButtons can wrap a kMenuListPopUp.
851       return item_role == ax::mojom::Role::kMenuListPopup;
852     default:
853       return false;
854   }
855 }
856 
IsIgnoredContainerForOrderedSet() const857 bool AXNode::IsIgnoredContainerForOrderedSet() const {
858   return IsIgnored() || data().role == ax::mojom::Role::kListItem ||
859          data().role == ax::mojom::Role::kGenericContainer ||
860          data().role == ax::mojom::Role::kUnknown;
861 }
862 
UpdateUnignoredCachedValuesRecursive(int startIndex)863 int AXNode::UpdateUnignoredCachedValuesRecursive(int startIndex) {
864   int count = 0;
865   for (AXNode* child : children_) {
866     if (child->IsIgnored()) {
867       child->unignored_index_in_parent_ = 0;
868       count += child->UpdateUnignoredCachedValuesRecursive(startIndex + count);
869     } else {
870       child->unignored_index_in_parent_ = startIndex + count++;
871     }
872   }
873   unignored_child_count_ = count;
874   return count;
875 }
876 
877 // Finds ordered set that immediately contains node.
878 // Is not required for set's role to match node's role.
GetOrderedSet() const879 AXNode* AXNode::GetOrderedSet() const {
880   AXNode* result = parent();
881   // Continue walking up while parent is invalid, ignored, a generic container,
882   // or unknown.
883   while (result && (result->IsIgnored() ||
884                     result->data().role == ax::mojom::Role::kGenericContainer ||
885                     result->data().role == ax::mojom::Role::kUnknown)) {
886     result = result->parent();
887   }
888   return result;
889 }
890 
ComputeLastUnignoredChildRecursive() const891 AXNode* AXNode::ComputeLastUnignoredChildRecursive() const {
892   DCHECK(!tree_->GetTreeUpdateInProgressState());
893   if (children().empty())
894     return nullptr;
895 
896   for (int i = static_cast<int>(children().size()) - 1; i >= 0; --i) {
897     AXNode* child = children_[i];
898     if (!child->IsIgnored())
899       return child;
900 
901     AXNode* descendant = child->ComputeLastUnignoredChildRecursive();
902     if (descendant)
903       return descendant;
904   }
905   return nullptr;
906 }
907 
ComputeFirstUnignoredChildRecursive() const908 AXNode* AXNode::ComputeFirstUnignoredChildRecursive() const {
909   DCHECK(!tree_->GetTreeUpdateInProgressState());
910   for (size_t i = 0; i < children().size(); i++) {
911     AXNode* child = children_[i];
912     if (!child->IsIgnored())
913       return child;
914 
915     AXNode* descendant = child->ComputeFirstUnignoredChildRecursive();
916     if (descendant)
917       return descendant;
918   }
919   return nullptr;
920 }
921 
IsIgnored() const922 bool AXNode::IsIgnored() const {
923   return data().IsIgnored();
924 }
925 
IsInListMarker() const926 bool AXNode::IsInListMarker() const {
927   if (data().role == ax::mojom::Role::kListMarker)
928     return true;
929 
930   // List marker node's children can only be text elements.
931   if (!IsText())
932     return false;
933 
934   // There is no need to iterate over all the ancestors of the current anchor
935   // since a list marker node only has children on 2 levels.
936   // i.e.:
937   // AXLayoutObject role=kListMarker
938   // ++StaticText
939   // ++++InlineTextBox
940   AXNode* parent_node = GetUnignoredParent();
941   if (parent_node && parent_node->data().role == ax::mojom::Role::kListMarker)
942     return true;
943 
944   AXNode* grandparent_node = parent_node->GetUnignoredParent();
945   return grandparent_node &&
946          grandparent_node->data().role == ax::mojom::Role::kListMarker;
947 }
948 
949 }  // namespace ui
950