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