Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
ax_node.cc
Go to the documentation of this file.
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 "ax_node.h"
6
7#include <algorithm>
8#include <utility>
9
10#include "ax_enums.h"
11#include "ax_role_properties.h"
12#include "ax_table_info.h"
13#include "ax_tree.h"
14#include "ax_tree_manager.h"
15#include "ax_tree_manager_map.h"
16#include "base/color_utils.h"
17#include "base/string_utils.h"
18
19namespace ui {
20
22
24 AXNode* parent,
25 int32_t id,
26 size_t index_in_parent,
27 size_t unignored_index_in_parent)
28 : tree_(tree),
29 index_in_parent_(index_in_parent),
30 unignored_index_in_parent_(unignored_index_in_parent),
31 parent_(parent) {
32 data_.id = id;
33}
34
35AXNode::~AXNode() = default;
36
38 // TODO(nektar): Should BASE_DCHECK if the node is not ignored.
40 return unignored_child_count_;
41}
42
44 return std::move(data_);
45}
46
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
60 AXNode* result = parent();
61 while (result && result->IsIgnored())
62 result = result->parent();
63 return result;
64}
65
68 return unignored_index_in_parent_;
69}
70
73 return index_in_parent_;
74}
75
78 return ComputeFirstUnignoredChildRecursive();
79}
80
83 return ComputeLastUnignoredChildRecursive();
84}
85
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
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
110// Search for the next sibling of this node, skipping over any ignored nodes
111// encountered.
112//
113// In our search:
114// If we find an ignored sibling, we consider its children as our siblings.
115// If we run out of siblings, we consider an ignored parent's siblings as our
116// own siblings.
117//
118// Note: this behaviour of 'skipping over' an ignored node makes this subtly
119// different to finding the next (direct) sibling which is unignored.
120//
121// Consider a tree, where (i) marks a node as ignored:
122//
123// 1
124// ├── 2
125// ├── 3(i)
126// │ └── 5
127// └── 4
128//
129// The next sibling of node 2 is node 3, which is ignored.
130// The next unignored sibling of node 2 could be either:
131// 1) node 4 - next unignored sibling in the literal tree, or
132// 2) node 5 - next unignored sibling in the logical document.
133//
134// There is no next sibling of node 5.
135// The next unignored sibling of node 5 could be either:
136// 1) null - no next sibling in the literal tree, or
137// 2) node 4 - next unignored sibling in the logical document.
138//
139// In both cases, this method implements approach (2).
140//
141// TODO(chrishall): Can we remove this non-reflexive case by forbidding
142// GetNextUnignoredSibling calls on an ignored started node?
143// Note: this means that Next/Previous-UnignoredSibling are not reflexive if
144// either of the nodes in question are ignored. From above we get an example:
145// NextUnignoredSibling(3) is 4, but
146// PreviousUnignoredSibling(4) is 5.
147//
148// The view of unignored siblings for node 3 includes both node 2 and node 4:
149// 2 <-- [3(i)] --> 4
150//
151// Whereas nodes 2, 5, and 4 do not consider node 3 to be an unignored sibling:
152// null <-- [2] --> 5
153// 2 <-- [5] --> 4
154// 5 <-- [4] --> null
157 const AXNode* current = this;
158
159 // If there are children of the |current| node still to consider.
160 bool considerChildren = false;
161
162 while (current) {
163 // A |candidate| sibling to consider.
164 // If it is unignored then we have found our result.
165 // Otherwise promote it to |current| and consider its children.
166 AXNode* candidate;
167
168 if (considerChildren && (candidate = current->GetFirstChild())) {
169 if (!candidate->IsIgnored())
170 return candidate;
171 current = candidate;
172
173 } else if ((candidate = current->GetNextSibling())) {
174 if (!candidate->IsIgnored())
175 return candidate;
176 current = candidate;
177 // Look through the ignored candidate node to consider their children as
178 // though they were siblings.
179 considerChildren = true;
180
181 } else {
182 // Continue our search through a parent iff they are ignored.
183 //
184 // If |current| has an ignored parent, then we consider the parent's
185 // siblings as though they were siblings of |current|.
186 //
187 // Given a tree:
188 // 1
189 // ├── 2(?)
190 // │ └── [4]
191 // └── 3
192 //
193 // Node 4's view of siblings:
194 // literal tree: null <-- [4] --> null
195 //
196 // If node 2 is not ignored, then node 4's view doesn't change, and we
197 // have no more nodes to consider:
198 // unignored tree: null <-- [4] --> null
199 //
200 // If instead node 2 is ignored, then node 4's view of siblings grows to
201 // include node 3, and we have more nodes to consider:
202 // unignored tree: null <-- [4] --> 3
203 current = current->parent();
204 if (!current || !current->IsIgnored())
205 return nullptr;
206
207 // We have already considered all relevant descendants of |current|.
208 considerChildren = false;
209 }
210 }
211
212 return nullptr;
213}
214
215// Search for the previous sibling of this node, skipping over any ignored nodes
216// encountered.
217//
218// In our search for a sibling:
219// If we find an ignored sibling, we may consider its children as siblings.
220// If we run out of siblings, we may consider an ignored parent's siblings as
221// our own.
222//
223// See the documentation for |GetNextUnignoredSibling| for more details.
226 const AXNode* current = this;
227
228 // If there are children of the |current| node still to consider.
229 bool considerChildren = false;
230
231 while (current) {
232 // A |candidate| sibling to consider.
233 // If it is unignored then we have found our result.
234 // Otherwise promote it to |current| and consider its children.
235 AXNode* candidate;
236
237 if (considerChildren && (candidate = current->GetLastChild())) {
238 if (!candidate->IsIgnored())
239 return candidate;
240 current = candidate;
241
242 } else if ((candidate = current->GetPreviousSibling())) {
243 if (!candidate->IsIgnored())
244 return candidate;
245 current = candidate;
246 // Look through the ignored candidate node to consider their children as
247 // though they were siblings.
248 considerChildren = true;
249
250 } else {
251 // Continue our search through a parent iff they are ignored.
252 //
253 // If |current| has an ignored parent, then we consider the parent's
254 // siblings as though they were siblings of |current|.
255 //
256 // Given a tree:
257 // 1
258 // ├── 2
259 // └── 3(?)
260 // └── [4]
261 //
262 // Node 4's view of siblings:
263 // literal tree: null <-- [4] --> null
264 //
265 // If node 3 is not ignored, then node 4's view doesn't change, and we
266 // have no more nodes to consider:
267 // unignored tree: null <-- [4] --> null
268 //
269 // If instead node 3 is ignored, then node 4's view of siblings grows to
270 // include node 2, and we have more nodes to consider:
271 // unignored tree: 2 <-- [4] --> null
272 current = current->parent();
273 if (!current || !current->IsIgnored())
274 return nullptr;
275
276 // We have already considered all relevant descendants of |current|.
277 considerChildren = false;
278 }
279 }
280
281 return nullptr;
282}
283
286 return GetFirstUnignoredChild();
287
288 const AXNode* node = this;
289 while (node) {
290 AXNode* sibling = node->GetNextUnignoredSibling();
291 if (sibling)
292 return sibling;
293
294 node = node->GetUnignoredParent();
295 }
296
297 return nullptr;
298}
299
302 if (!sibling)
303 return GetUnignoredParent();
304
305 if (sibling->GetUnignoredChildCount())
306 return sibling->GetDeepestLastUnignoredChild();
307
308 return sibling;
309}
310
315
320
321// The first (direct) child, ignored or unignored.
323 if (children().empty())
324 return nullptr;
325 return children()[0];
326}
327
328// The last (direct) child, ignored or unignored.
330 size_t n = children().size();
331 if (n == 0)
332 return nullptr;
333 return children()[n - 1];
334}
335
336// The previous (direct) sibling, ignored or unignored.
338 // Root nodes lack a parent, their index_in_parent should be 0.
339 BASE_DCHECK(!parent() ? index_in_parent() == 0 : true);
340 size_t index = index_in_parent();
341 if (index == 0)
342 return nullptr;
343 return parent()->children()[index - 1];
344}
345
346// The next (direct) sibling, ignored or unignored.
348 if (!parent())
349 return nullptr;
350 size_t nextIndex = index_in_parent() + 1;
351 if (nextIndex >= parent()->children().size())
352 return nullptr;
353 return parent()->children()[nextIndex];
354}
355
356bool AXNode::IsText() const {
357 // In Legacy Layout, a list marker has no children and is thus represented on
358 // all platforms as a leaf node that exposes the marker itself, i.e., it forms
359 // part of the AX tree's text representation. In contrast, in Layout NG, a
360 // list marker has a static text child.
362 return !children().size();
363 return ui::IsText(data().role);
364}
365
372
373void AXNode::SetData(const AXNodeData& src) {
374 data_ = src;
375}
376
377void AXNode::SetLocation(int32_t offset_container_id,
378 const gfx::RectF& location,
380 data_.relative_bounds.offset_container_id = offset_container_id;
381 data_.relative_bounds.bounds = location;
382 if (transform) {
384 std::make_unique<gfx::Transform>(*transform);
385 } else {
386 data_.relative_bounds.transform.reset();
387 }
388}
389
390void AXNode::SetIndexInParent(size_t index_in_parent) {
391 index_in_parent_ = index_in_parent;
392}
393
395 if (!IsIgnored())
396 UpdateUnignoredCachedValuesRecursive(0);
397}
398
399void AXNode::SwapChildren(std::vector<AXNode*>* children) {
400 children->swap(children_);
401}
402
404 delete this;
405}
406
407bool AXNode::IsDescendantOf(const AXNode* ancestor) const {
408 if (this == ancestor)
409 return true;
410 if (parent())
411 return parent()->IsDescendantOf(ancestor);
412
413 return false;
414}
415
417 std::vector<int> line_offsets;
419 &line_offsets)) {
420 return line_offsets;
421 }
422
423 int start_offset = 0;
424 ComputeLineStartOffsets(&line_offsets, &start_offset);
426 line_offsets);
427 return line_offsets;
428}
429
430void AXNode::ComputeLineStartOffsets(std::vector<int>* line_offsets,
431 int* start_offset) const {
432 BASE_DCHECK(line_offsets);
433 BASE_DCHECK(start_offset);
434 for (const AXNode* child : children()) {
435 BASE_DCHECK(child);
436 if (!child->children().empty()) {
437 child->ComputeLineStartOffsets(line_offsets, start_offset);
438 continue;
439 }
440
441 // Don't report if the first piece of text starts a new line or not.
442 if (*start_offset && !child->data().HasIntAttribute(
444 // If there are multiple objects with an empty accessible label at the
445 // start of a line, only include a single line start offset.
446 if (line_offsets->empty() || line_offsets->back() != *start_offset)
447 line_offsets->push_back(*start_offset);
448 }
449
450 std::u16string text =
451 child->data().GetString16Attribute(ax::mojom::StringAttribute::kName);
452 *start_offset += static_cast<int>(text.length());
453 }
454}
455
457 ax::mojom::StringAttribute attribute) const {
458 const AXNode* current_node = this;
459 do {
460 if (current_node->data().HasStringAttribute(attribute))
461 return current_node->data().GetStringAttribute(attribute);
462 current_node = current_node->parent();
463 } while (current_node);
464 return base::EmptyString();
465}
466
471
472std::string AXNode::GetInnerText() const {
473 // If a text field has no descendants, then we compute its inner text from its
474 // value or its placeholder. Otherwise we prefer to look at its descendant
475 // text nodes because Blink doesn't always add all trailing white space to the
476 // value attribute.
477 const bool is_plain_text_field_without_descendants =
479 if (is_plain_text_field_without_descendants) {
480 std::string value =
482 // If the value is empty, then there might be some placeholder text in the
483 // text field, or any other name that is derived from visible contents, even
484 // if the text field has no children.
485 if (!value.empty())
486 return value;
487 }
488
489 // Ordinarily, plain text fields are leaves. We need to exclude them from the
490 // set of leaf nodes when they expose any descendants. This is because we want
491 // to compute their inner text from their descendant text nodes as we don't
492 // always trust the "value" attribute provided by Blink.
493 const bool is_plain_text_field_with_descendants =
495 if (IsLeaf() && !is_plain_text_field_with_descendants) {
496 switch (data().GetNameFrom()) {
499 // The accessible name is not displayed on screen, e.g. aria-label, or is
500 // not displayed directly inside the node, e.g. an associated label
501 // element.
503 // The node's accessible name is explicitly empty.
505 // The accessible name does not represent the entirety of the node's inner
506 // text, e.g. a table's caption or a figure's figcaption.
509 // The accessible name is not displayed directly inside the node but is
510 // visible via e.g. a tooltip.
512 return std::string();
513
515 // The placeholder text is initially displayed inside the text field and
516 // takes the place of its value.
518 // The value attribute takes the place of the node's inner text, e.g. the
519 // value of a submit button is displayed inside the button itself.
522 }
523 }
524
525 std::string inner_text;
526 for (auto it = UnignoredChildrenBegin(); it != UnignoredChildrenEnd(); ++it) {
527 inner_text += it->GetInnerText();
528 }
529 return inner_text;
530}
531
532std::string AXNode::GetLanguage() const {
533 return std::string();
534}
535
536std::ostream& operator<<(std::ostream& stream, const AXNode& node) {
537 return stream << node.data().ToString();
538}
539
540bool AXNode::IsTable() const {
541 return IsTableLike(data().role);
542}
543
544std::optional<int> AXNode::GetTableColCount() const {
545 const AXTableInfo* table_info = GetAncestorTableInfo();
546 if (!table_info)
547 return std::nullopt;
548 return static_cast<int>(table_info->col_count);
549}
550
551std::optional<int> AXNode::GetTableRowCount() const {
552 const AXTableInfo* table_info = GetAncestorTableInfo();
553 if (!table_info)
554 return std::nullopt;
555 return static_cast<int>(table_info->row_count);
556}
557
558std::optional<int> AXNode::GetTableAriaColCount() const {
559 const AXTableInfo* table_info = GetAncestorTableInfo();
560 if (!table_info)
561 return std::nullopt;
562 return std::make_optional(table_info->aria_col_count);
563}
564
565std::optional<int> AXNode::GetTableAriaRowCount() const {
566 const AXTableInfo* table_info = GetAncestorTableInfo();
567 if (!table_info)
568 return std::nullopt;
569 return std::make_optional(table_info->aria_row_count);
570}
571
572std::optional<int> AXNode::GetTableCellCount() const {
573 const AXTableInfo* table_info = GetAncestorTableInfo();
574 if (!table_info)
575 return std::nullopt;
576
577 return static_cast<int>(table_info->unique_cell_ids.size());
578}
579
580std::optional<bool> AXNode::GetTableHasColumnOrRowHeaderNode() const {
581 const AXTableInfo* table_info = GetAncestorTableInfo();
582 if (!table_info)
583 return std::nullopt;
584
585 return !table_info->all_headers.empty();
586}
587
589 const AXTableInfo* table_info = GetAncestorTableInfo();
590 if (!table_info)
591 return nullptr;
592
593 // There is a table but there is no cell with the given index.
594 if (index < 0 ||
595 static_cast<size_t>(index) >= table_info->unique_cell_ids.size()) {
596 return nullptr;
597 }
598
599 return tree_->GetFromId(
600 table_info->unique_cell_ids[static_cast<size_t>(index)]);
601}
602
604 const AXTableInfo* table_info = GetAncestorTableInfo();
605 if (!table_info)
606 return nullptr;
607
608 return tree_->GetFromId(table_info->caption_id);
609}
610
611AXNode* AXNode::GetTableCellFromCoords(int row_index, int col_index) const {
612 const AXTableInfo* table_info = GetAncestorTableInfo();
613 if (!table_info)
614 return nullptr;
615
616 // There is a table but the given coordinates are outside the table.
617 if (row_index < 0 ||
618 static_cast<size_t>(row_index) >= table_info->row_count ||
619 col_index < 0 ||
620 static_cast<size_t>(col_index) >= table_info->col_count) {
621 return nullptr;
622 }
623
624 return tree_->GetFromId(table_info->cell_ids[static_cast<size_t>(row_index)]
625 [static_cast<size_t>(col_index)]);
626}
627
628std::vector<AXNode::AXID> AXNode::GetTableColHeaderNodeIds() const {
629 const AXTableInfo* table_info = GetAncestorTableInfo();
630 if (!table_info)
631 return std::vector<AXNode::AXID>();
632
633 std::vector<AXNode::AXID> col_header_ids;
634 // Flatten and add column header ids of each column to |col_header_ids|.
635 for (std::vector<AXNode::AXID> col_headers_at_index :
636 table_info->col_headers) {
637 col_header_ids.insert(col_header_ids.end(), col_headers_at_index.begin(),
638 col_headers_at_index.end());
639 }
640
641 return col_header_ids;
642}
643
644std::vector<AXNode::AXID> AXNode::GetTableColHeaderNodeIds(
645 int col_index) const {
646 const AXTableInfo* table_info = GetAncestorTableInfo();
647 if (!table_info)
648 return std::vector<AXNode::AXID>();
649
650 if (col_index < 0 || static_cast<size_t>(col_index) >= table_info->col_count)
651 return std::vector<AXNode::AXID>();
652
653 return std::vector<AXNode::AXID>(
654 table_info->col_headers[static_cast<size_t>(col_index)]);
655}
656
657std::vector<AXNode::AXID> AXNode::GetTableRowHeaderNodeIds(
658 int row_index) const {
659 const AXTableInfo* table_info = GetAncestorTableInfo();
660 if (!table_info)
661 return std::vector<AXNode::AXID>();
662
663 if (row_index < 0 || static_cast<size_t>(row_index) >= table_info->row_count)
664 return std::vector<AXNode::AXID>();
665
666 return std::vector<AXNode::AXID>(
667 table_info->row_headers[static_cast<size_t>(row_index)]);
668}
669
670std::vector<AXNode::AXID> AXNode::GetTableUniqueCellIds() const {
671 const AXTableInfo* table_info = GetAncestorTableInfo();
672 if (!table_info)
673 return std::vector<AXNode::AXID>();
674
675 return std::vector<AXNode::AXID>(table_info->unique_cell_ids);
676}
677
678const std::vector<AXNode*>* AXNode::GetExtraMacNodes() const {
679 // Should only be available on the table node itself, not any of its children.
680 const AXTableInfo* table_info = tree_->GetTableInfo(this);
681 if (!table_info)
682 return nullptr;
683
684 return &table_info->extra_mac_nodes;
685}
686
687//
688// Table row-like nodes.
689//
690
691bool AXNode::IsTableRow() const {
692 return ui::IsTableRow(data().role);
693}
694
695std::optional<int> AXNode::GetTableRowRowIndex() const {
696 if (!IsTableRow())
697 return std::nullopt;
698
699 const AXTableInfo* table_info = GetAncestorTableInfo();
700 if (!table_info)
701 return std::nullopt;
702
703 const auto& iter = table_info->row_id_to_index.find(id());
704 if (iter == table_info->row_id_to_index.end())
705 return std::nullopt;
706 return static_cast<int>(iter->second);
707}
708
709std::vector<AXNode::AXID> AXNode::GetTableRowNodeIds() const {
710 std::vector<AXNode::AXID> row_node_ids;
711 const AXTableInfo* table_info = GetAncestorTableInfo();
712 if (!table_info)
713 return row_node_ids;
714
715 for (AXNode* node : table_info->row_nodes)
716 row_node_ids.push_back(node->data().id);
717
718 return row_node_ids;
719}
720
721#if defined(OS_APPLE)
722
723//
724// Table column-like nodes. These nodes are only present on macOS.
725//
726
727bool AXNode::IsTableColumn() const {
728 return ui::IsTableColumn(data().role);
729}
730
731std::optional<int> AXNode::GetTableColColIndex() const {
732 if (!IsTableColumn())
733 return std::nullopt;
734
735 const AXTableInfo* table_info = GetAncestorTableInfo();
736 if (!table_info)
737 return std::nullopt;
738
739 int index = 0;
740 for (const AXNode* node : table_info->extra_mac_nodes) {
741 if (node == this)
742 break;
743 index++;
744 }
745 return index;
746}
747
748#endif // defined(OS_APPLE)
749
750//
751// Table cell-like nodes.
752//
753
755 return IsCellOrTableHeader(data().role);
756}
757
758std::optional<int> AXNode::GetTableCellIndex() const {
759 if (!IsTableCellOrHeader())
760 return std::nullopt;
761
762 const AXTableInfo* table_info = GetAncestorTableInfo();
763 if (!table_info)
764 return std::nullopt;
765
766 const auto& iter = table_info->cell_id_to_index.find(id());
767 if (iter != table_info->cell_id_to_index.end())
768 return static_cast<int>(iter->second);
769 return std::nullopt;
770}
771
772std::optional<int> AXNode::GetTableCellColIndex() const {
773 const AXTableInfo* table_info = GetAncestorTableInfo();
774 if (!table_info)
775 return std::nullopt;
776
777 std::optional<int> index = GetTableCellIndex();
778 if (!index)
779 return std::nullopt;
780
781 return static_cast<int>(table_info->cell_data_vector[*index].col_index);
782}
783
784std::optional<int> AXNode::GetTableCellRowIndex() const {
785 const AXTableInfo* table_info = GetAncestorTableInfo();
786 if (!table_info)
787 return std::nullopt;
788
789 std::optional<int> index = GetTableCellIndex();
790 if (!index)
791 return std::nullopt;
792
793 return static_cast<int>(table_info->cell_data_vector[*index].row_index);
794}
795
796std::optional<int> AXNode::GetTableCellColSpan() const {
797 // If it's not a table cell, don't return a col span.
798 if (!IsTableCellOrHeader())
799 return std::nullopt;
800
801 // Otherwise, try to return a colspan, with 1 as the default if it's not
802 // specified.
803 int col_span;
805 return col_span;
806 return 1;
807}
808
809std::optional<int> AXNode::GetTableCellRowSpan() const {
810 // If it's not a table cell, don't return a row span.
811 if (!IsTableCellOrHeader())
812 return std::nullopt;
813
814 // Otherwise, try to return a row span, with 1 as the default if it's not
815 // specified.
816 int row_span;
818 return row_span;
819 return 1;
820}
821
822std::optional<int> AXNode::GetTableCellAriaColIndex() const {
823 const AXTableInfo* table_info = GetAncestorTableInfo();
824 if (!table_info)
825 return std::nullopt;
826
827 std::optional<int> index = GetTableCellIndex();
828 if (!index)
829 return std::nullopt;
830
831 return static_cast<int>(table_info->cell_data_vector[*index].aria_col_index);
832}
833
834std::optional<int> AXNode::GetTableCellAriaRowIndex() const {
835 const AXTableInfo* table_info = GetAncestorTableInfo();
836 if (!table_info)
837 return std::nullopt;
838
839 std::optional<int> index = GetTableCellIndex();
840 if (!index)
841 return std::nullopt;
842
843 return static_cast<int>(table_info->cell_data_vector[*index].aria_row_index);
844}
845
846std::vector<AXNode::AXID> AXNode::GetTableCellColHeaderNodeIds() const {
847 const AXTableInfo* table_info = GetAncestorTableInfo();
848 if (!table_info || table_info->col_count <= 0)
849 return std::vector<AXNode::AXID>();
850
851 // If this node is not a cell, then return the headers for the first column.
852 int col_index = GetTableCellColIndex().value_or(0);
853
854 return std::vector<AXNode::AXID>(table_info->col_headers[col_index]);
855}
856
857void AXNode::GetTableCellColHeaders(std::vector<AXNode*>* col_headers) const {
858 BASE_DCHECK(col_headers);
859
860 std::vector<int32_t> col_header_ids = GetTableCellColHeaderNodeIds();
861 IdVectorToNodeVector(col_header_ids, col_headers);
862}
863
864std::vector<AXNode::AXID> AXNode::GetTableCellRowHeaderNodeIds() const {
865 const AXTableInfo* table_info = GetAncestorTableInfo();
866 if (!table_info || table_info->row_count <= 0)
867 return std::vector<AXNode::AXID>();
868
869 // If this node is not a cell, then return the headers for the first row.
870 int row_index = GetTableCellRowIndex().value_or(0);
871
872 return std::vector<AXNode::AXID>(table_info->row_headers[row_index]);
873}
874
875void AXNode::GetTableCellRowHeaders(std::vector<AXNode*>* row_headers) const {
876 BASE_DCHECK(row_headers);
877
878 std::vector<int32_t> row_header_ids = GetTableCellRowHeaderNodeIds();
879 IdVectorToNodeVector(row_header_ids, row_headers);
880}
881
883 if (!IsTableCellOrHeader())
884 return false;
885
886 const AXNode* node = this;
887 while (node && !node->IsTable())
888 node = node->parent();
889 if (!node)
890 return false;
891
892 return node->data().role == ax::mojom::Role::kTable;
893}
894
896 if (!IsTableCellOrHeader())
897 return false;
898
899 const AXNode* node = this;
900 while (node && !node->IsTable())
901 node = node->parent();
902 if (!node)
903 return false;
904
905 return node->data().role == ax::mojom::Role::kGrid ||
907}
908
909AXTableInfo* AXNode::GetAncestorTableInfo() const {
910 const AXNode* node = this;
911 while (node && !node->IsTable())
912 node = node->parent();
913 if (node)
914 return tree_->GetTableInfo(node);
915 return nullptr;
916}
917
918void AXNode::IdVectorToNodeVector(const std::vector<int32_t>& ids,
919 std::vector<AXNode*>* nodes) const {
920 for (int32_t id : ids) {
921 AXNode* node = tree_->GetFromId(id);
922 if (node)
923 nodes->push_back(node);
924 }
925}
926
927std::optional<int> AXNode::GetHierarchicalLevel() const {
928 int hierarchical_level =
930
931 // According to the WAI_ARIA spec, a defined hierarchical level value is
932 // greater than 0.
933 // https://www.w3.org/TR/wai-aria-1.1/#aria-level
934 if (hierarchical_level > 0)
935 return hierarchical_level;
936
937 return std::nullopt;
938}
939
941 return ui::IsItemLike(data().role);
942}
943
945 return ui::IsSetLike(data().role);
946}
947
948// Uses AXTree's cache to calculate node's PosInSet.
949std::optional<int> AXNode::GetPosInSet() {
950 return tree_->GetPosInSet(*this);
951}
952
953// Uses AXTree's cache to calculate node's SetSize.
954std::optional<int> AXNode::GetSetSize() {
955 return tree_->GetSetSize(*this);
956}
957
958// Returns true if the role of ordered set matches the role of item.
959// Returns false otherwise.
960bool AXNode::SetRoleMatchesItemRole(const AXNode* ordered_set) const {
961 ax::mojom::Role item_role = data().role;
962 // Switch on role of ordered set
963 switch (ordered_set->data().role) {
965 return item_role == ax::mojom::Role::kArticle;
967 return item_role == ax::mojom::Role::kListItem;
969 return item_role == ax::mojom::Role::kComment ||
970 item_role == ax::mojom::Role::kListItem ||
971 item_role == ax::mojom::Role::kMenuItem ||
972 item_role == ax::mojom::Role::kMenuItemRadio ||
973 item_role == ax::mojom::Role::kListBoxOption ||
974 item_role == ax::mojom::Role::kTreeItem;
976 return item_role == ax::mojom::Role::kMenuItem ||
977 item_role == ax::mojom::Role::kMenuItemRadio ||
980 return item_role == ax::mojom::Role::kMenuItem ||
981 item_role == ax::mojom::Role::kMenuItemRadio ||
984 return item_role == ax::mojom::Role::kTab;
986 return item_role == ax::mojom::Role::kTreeItem;
988 return item_role == ax::mojom::Role::kListBoxOption;
990 return item_role == ax::mojom::Role::kMenuListOption ||
991 item_role == ax::mojom::Role::kMenuItem ||
992 item_role == ax::mojom::Role::kMenuItemRadio ||
995 return item_role == ax::mojom::Role::kRadioButton;
997 // Only the term for each description list entry should receive posinset
998 // and setsize.
999 return item_role == ax::mojom::Role::kDescriptionListTerm ||
1000 item_role == ax::mojom::Role::kTerm;
1002 // kPopUpButtons can wrap a kMenuListPopUp.
1003 return item_role == ax::mojom::Role::kMenuListPopup;
1004 default:
1005 return false;
1006 }
1007}
1008
1015
1016int AXNode::UpdateUnignoredCachedValuesRecursive(int startIndex) {
1017 int count = 0;
1018 for (AXNode* child : children_) {
1019 if (child->IsIgnored()) {
1020 child->unignored_index_in_parent_ = 0;
1021 count += child->UpdateUnignoredCachedValuesRecursive(startIndex + count);
1022 } else {
1023 child->unignored_index_in_parent_ = startIndex + count++;
1024 }
1025 }
1026 unignored_child_count_ = count;
1027 return count;
1028}
1029
1030// Finds ordered set that contains node.
1031// Is not required for set's role to match node's role.
1033 AXNode* result = parent();
1034 // Continue walking up while parent is invalid, ignored, a generic container,
1035 // unknown, or embedded group.
1036 while (result && result->IsIgnoredContainerForOrderedSet()) {
1037 result = result->parent();
1038 }
1039
1040 return result;
1041}
1042
1043AXNode* AXNode::ComputeLastUnignoredChildRecursive() const {
1045 if (children().empty())
1046 return nullptr;
1047
1048 for (int i = static_cast<int>(children().size()) - 1; i >= 0; --i) {
1049 AXNode* child = children_[i];
1050 if (!child->IsIgnored())
1051 return child;
1052
1053 AXNode* descendant = child->ComputeLastUnignoredChildRecursive();
1054 if (descendant)
1055 return descendant;
1056 }
1057 return nullptr;
1058}
1059
1060AXNode* AXNode::ComputeFirstUnignoredChildRecursive() const {
1062 for (size_t i = 0; i < children().size(); i++) {
1063 AXNode* child = children_[i];
1064 if (!child->IsIgnored())
1065 return child;
1066
1067 AXNode* descendant = child->ComputeFirstUnignoredChildRecursive();
1068 if (descendant)
1069 return descendant;
1070 }
1071 return nullptr;
1072}
1073
1074bool AXNode::IsIgnored() const {
1075 return data().IsIgnored();
1076}
1077
1079 const AXNode* ancestor = GetUnignoredParent();
1080 while (ancestor) {
1081 if (ancestor->IsLeaf())
1082 return true;
1083 ancestor = ancestor->GetUnignoredParent();
1084 }
1085 return false;
1086}
1087
1088bool AXNode::IsLeaf() const {
1089 // A node is also a leaf if all of it's descendants are ignored.
1091 return true;
1092
1093#if defined(OS_WIN)
1094 // On Windows, we want to hide the subtree of a collapsed <select> element.
1095 // Otherwise, ATs are always going to announce its options whether it's
1096 // collapsed or expanded. In the AXTree, this element corresponds to a node
1097 // with role ax::mojom::Role::kPopUpButton that is the parent of a node with
1098 // role ax::mojom::Role::kMenuListPopup.
1100 return true;
1101#endif // defined(OS_WIN)
1102
1103 // These types of objects may have children that we use as internal
1104 // implementation details, but we want to expose them as leaves to platform
1105 // accessibility APIs because screen readers might be confused if they find
1106 // any children.
1107 if (data().IsPlainTextField() || IsText())
1108 return true;
1109
1110 // Roles whose children are only presentational according to the ARIA and
1111 // HTML5 Specs should be hidden from screen readers.
1112 switch (data().role) {
1113 // According to the ARIA and Core-AAM specs:
1114 // https://w3c.github.io/aria/#button,
1115 // https://www.w3.org/TR/core-aam-1.1/#exclude_elements
1116 // buttons' children are presentational only and should be hidden from
1117 // screen readers. However, we cannot enforce the leafiness of buttons
1118 // because they may contain many rich, interactive descendants such as a day
1119 // in a calendar, and screen readers will need to interact with these
1120 // contents. See https://crbug.com/689204.
1121 // So we decided to not enforce the leafiness of buttons and expose all
1122 // children.
1124 return false;
1133 return true;
1134 default:
1135 return false;
1136 }
1137}
1138
1140 if (data().role == ax::mojom::Role::kListMarker)
1141 return true;
1142
1143 // List marker node's children can only be text elements.
1144 if (!IsText())
1145 return false;
1146
1147 // There is no need to iterate over all the ancestors of the current anchor
1148 // since a list marker node only has children on 2 levels.
1149 // i.e.:
1150 // AXLayoutObject role=kListMarker
1151 // ++StaticText
1152 // ++++InlineTextBox
1153 AXNode* parent_node = GetUnignoredParent();
1154 if (parent_node && parent_node->data().role == ax::mojom::Role::kListMarker)
1155 return true;
1156
1157 AXNode* grandparent_node = parent_node->GetUnignoredParent();
1158 return grandparent_node &&
1159 grandparent_node->data().role == ax::mojom::Role::kListMarker;
1160}
1161
1163 if (data().role != ax::mojom::Role::kPopUpButton ||
1164 !data().HasState(ax::mojom::State::kCollapsed)) {
1165 return false;
1166 }
1167
1168 // When a popup button contains a menu list popup, its only child is unignored
1169 // and is a menu list popup.
1171 if (!node)
1172 return false;
1173
1174 return node->data().role == ax::mojom::Role::kMenuListPopup;
1175}
1176
1178 AXNode* node = GetOrderedSet();
1179
1180 if (!node)
1181 return nullptr;
1182
1183 // The ordered set returned is either the popup element child of the popup
1184 // button (e.g., the AXMenuListPopup) or the popup button itself. We need
1185 // |node| to point to the popup button itself.
1186 if (node->data().role != ax::mojom::Role::kPopUpButton) {
1187 node = node->parent();
1188 if (!node)
1189 return nullptr;
1190 }
1191
1192 return node->IsCollapsedMenuListPopUpButton() ? node : nullptr;
1193}
1194
1196 if (data().role != ax::mojom::Role::kGroup || !parent())
1197 return false;
1198
1199 return ui::IsSetLike(parent()->data().role);
1200}
1201
1203 AXNode* current_node = const_cast<AXNode*>(this);
1204 AXNode* lowest_unignored_node = current_node;
1205 for (; lowest_unignored_node && lowest_unignored_node->IsIgnored();
1206 lowest_unignored_node = lowest_unignored_node->parent()) {
1207 }
1208
1209 // `highest_leaf_node` could be nullptr.
1210 AXNode* highest_leaf_node = lowest_unignored_node;
1211 // For the purposes of this method, a leaf node does not include leaves in the
1212 // internal accessibility tree, only in the platform exposed tree.
1213 for (AXNode* ancestor_node = lowest_unignored_node; ancestor_node;
1214 ancestor_node = ancestor_node->GetUnignoredParent()) {
1215 if (ancestor_node->IsLeaf())
1216 highest_leaf_node = ancestor_node;
1217 }
1218 if (highest_leaf_node)
1219 return highest_leaf_node;
1220
1221 if (lowest_unignored_node)
1222 return lowest_unignored_node;
1223 return current_node;
1224}
1225
1228
1231 return parent;
1232
1234 }
1235
1236 return nullptr;
1237}
1238
1240 if (!ancestor)
1241 return false;
1242 if (this == ancestor)
1243 return true;
1246 return false;
1247}
1248
1251 if (parent_)
1252 return parent_;
1253 const AXTreeManager* manager =
1255 if (manager)
1256 return manager->GetParentNodeFromParentTreeAsAXNode();
1257 return nullptr;
1258}
1259
1261 BASE_DCHECK(tree())
1262 << "Cannot retrieve the current selection if the node is not "
1263 "attached to an accessibility tree.\n"
1264 << *this;
1266
1267 // "selection.anchor_offset" and "selection.focus_ofset" might need to be
1268 // adjusted if the anchor or the focus nodes include ignored children.
1269 //
1270 // TODO(nektar): Move this logic into its own "AXSelection" class and cache
1271 // the result for faster reuse.
1272 const AXNode* anchor = tree()->GetFromId(selection.anchor_object_id);
1273 if (anchor && !anchor->IsLeaf()) {
1274 BASE_DCHECK(selection.anchor_offset >= 0);
1275 if (static_cast<size_t>(selection.anchor_offset) <
1276 anchor->children().size()) {
1277 const AXNode* anchor_child = anchor->children()[selection.anchor_offset];
1278 BASE_DCHECK(anchor_child);
1279 selection.anchor_offset =
1280 static_cast<int>(anchor_child->GetUnignoredIndexInParent());
1281 } else {
1282 selection.anchor_offset =
1283 static_cast<int>(anchor->GetUnignoredChildCount());
1284 }
1285 }
1286
1287 const AXNode* focus = tree()->GetFromId(selection.focus_object_id);
1288 if (focus && !focus->IsLeaf()) {
1289 BASE_DCHECK(selection.focus_offset >= 0);
1290 if (static_cast<size_t>(selection.focus_offset) <
1291 focus->children().size()) {
1292 const AXNode* focus_child = focus->children()[selection.focus_offset];
1293 BASE_DCHECK(focus_child);
1294 selection.focus_offset =
1295 static_cast<int>(focus_child->GetUnignoredIndexInParent());
1296 } else {
1297 selection.focus_offset =
1298 static_cast<int>(focus->GetUnignoredChildCount());
1299 }
1300 }
1301 return selection;
1302}
1303
1304} // namespace ui
int count
virtual AXTableInfo * GetTableInfo(const AXNode *table_node) const =0
virtual bool GetTreeUpdateInProgressState() const =0
virtual std::optional< int > GetSetSize(const AXNode &node)=0
virtual AXNode * GetFromId(int32_t id) const =0
virtual AXTreeID GetAXTreeID() const =0
virtual std::optional< int > GetPosInSet(const AXNode &node)=0
virtual Selection GetUnignoredSelection() const =0
std::optional< int > GetSetSize()
Definition ax_node.cc:954
AXNode * GetCollapsedMenuListPopUpButtonAncestor() const
Definition ax_node.cc:1177
std::vector< AXNode::AXID > GetTableCellColHeaderNodeIds() const
Definition ax_node.cc:846
void Destroy()
Definition ax_node.cc:403
void UpdateUnignoredCachedValues()
Definition ax_node.cc:394
AXNode * GetDeepestLastUnignoredChild() const
Definition ax_node.cc:98
size_t GetUnignoredChildCount() const
Definition ax_node.cc:37
ChildIteratorBase< AXNode, &AXNode::GetNextUnignoredSibling, &AXNode::GetPreviousUnignoredSibling, &AXNode::GetFirstUnignoredChild, &AXNode::GetLastUnignoredChild > UnignoredChildIterator
Definition ax_node.h:143
std::vector< AXNode::AXID > GetTableColHeaderNodeIds() const
Definition ax_node.cc:628
std::vector< AXNode::AXID > GetTableCellRowHeaderNodeIds() const
Definition ax_node.cc:864
UnignoredChildIterator UnignoredChildrenEnd() const
Definition ax_node.cc:316
bool IsCellOrHeaderOfARIATable() const
Definition ax_node.cc:882
std::vector< AXNode::AXID > GetTableUniqueCellIds() const
Definition ax_node.cc:670
size_t GetIndexInParent() const
Definition ax_node.cc:71
AXNode * GetLowestPlatformAncestor() const
Definition ax_node.cc:1202
bool IsOrderedSetItem() const
Definition ax_node.cc:940
std::optional< int > GetTableCellIndex() const
Definition ax_node.cc:758
AXID id() const
Definition ax_node.h:110
void SetData(const AXNodeData &src)
Definition ax_node.cc:373
std::optional< int > GetTableCellRowIndex() const
Definition ax_node.cc:784
void SetIndexInParent(size_t index_in_parent)
Definition ax_node.cc:390
AXNode * GetParentCrossingTreeBoundary() const
Definition ax_node.cc:1249
bool IsEmbeddedGroup() const
Definition ax_node.cc:1195
size_t GetUnignoredIndexInParent() const
Definition ax_node.cc:66
AXNode * GetLastUnignoredChild() const
Definition ax_node.cc:81
bool IsCellOrHeaderOfARIAGrid() const
Definition ax_node.cc:895
bool IsTableCellOrHeader() const
Definition ax_node.cc:754
bool IsIgnoredContainerForOrderedSet() const
Definition ax_node.cc:1009
std::optional< int > GetTableCellAriaColIndex() const
Definition ax_node.cc:822
OwnerTree * tree() const
Definition ax_node.h:109
int32_t AXID
Definition ax_node.h:36
static constexpr AXID kInvalidAXID
Definition ax_node.h:41
std::vector< AXNode::AXID > GetTableRowHeaderNodeIds(int row_index) const
Definition ax_node.cc:657
AXNode * GetNextUnignoredInTreeOrder() const
Definition ax_node.cc:284
OwnerTree::Selection GetUnignoredSelection() const
Definition ax_node.cc:1260
std::optional< int > GetTableRowCount() const
Definition ax_node.cc:551
std::optional< bool > GetTableHasColumnOrRowHeaderNode() const
Definition ax_node.cc:580
AXNode * GetUnignoredParent() const
Definition ax_node.cc:58
bool IsInListMarker() const
Definition ax_node.cc:1139
AXNode * GetFirstUnignoredChild() const
Definition ax_node.cc:76
virtual ~AXNode()
AXNode * GetTextFieldAncestor() const
Definition ax_node.cc:1226
std::optional< int > GetTableRowRowIndex() const
Definition ax_node.cc:695
std::vector< int > GetOrComputeLineStartOffsets()
Definition ax_node.cc:416
AXNode * GetNextSibling() const
Definition ax_node.cc:347
void GetTableCellColHeaders(std::vector< AXNode * > *col_headers) const
Definition ax_node.cc:857
bool IsLeaf() const
Definition ax_node.cc:1088
const std::vector< int32_t > & GetIntListAttribute(ax::mojom::IntListAttribute attribute) const
Definition ax_node.h:263
std::vector< AXNode::AXID > GetTableRowNodeIds() const
Definition ax_node.cc:709
AXNode * GetLastChild() const
Definition ax_node.cc:329
AXNode * GetDeepestFirstUnignoredChild() const
Definition ax_node.cc:86
void SetLocation(int32_t offset_container_id, const gfx::RectF &location, gfx::Transform *transform)
Definition ax_node.cc:377
AXNode * GetUnignoredChildAtIndex(size_t index) const
Definition ax_node.cc:47
AXNode * GetTableCaption() const
Definition ax_node.cc:603
std::optional< int > GetTableCellAriaRowIndex() const
Definition ax_node.cc:834
std::optional< int > GetTableAriaColCount() const
Definition ax_node.cc:558
bool IsText() const
Definition ax_node.cc:356
std::optional< int > GetTableColCount() const
Definition ax_node.cc:544
bool IsTableRow() const
Definition ax_node.cc:691
const std::vector< AXNode * > & children() const
Definition ax_node.h:113
std::string GetLanguage() const
Definition ax_node.cc:532
std::optional< int > GetHierarchicalLevel() const
Definition ax_node.cc:927
void GetTableCellRowHeaders(std::vector< AXNode * > *row_headers) const
Definition ax_node.cc:875
std::optional< int > GetTableCellColSpan() const
Definition ax_node.cc:796
AXNode * GetPreviousUnignoredSibling() const
Definition ax_node.cc:224
AXNode * parent() const
Definition ax_node.h:111
UnignoredChildIterator UnignoredChildrenBegin() const
Definition ax_node.cc:311
std::optional< int > GetTableCellRowSpan() const
Definition ax_node.cc:809
bool IsTable() const
Definition ax_node.cc:540
AXNodeData && TakeData()
Definition ax_node.cc:43
const std::string & GetInheritedStringAttribute(ax::mojom::StringAttribute attribute) const
Definition ax_node.cc:456
std::u16string GetInheritedString16Attribute(ax::mojom::StringAttribute attribute) const
Definition ax_node.cc:467
bool SetRoleMatchesItemRole(const AXNode *ordered_set) const
Definition ax_node.cc:960
bool IsCollapsedMenuListPopUpButton() const
Definition ax_node.cc:1162
AXNode(OwnerTree *tree, AXNode *parent, int32_t id, size_t index_in_parent, size_t unignored_index_in_parent=0)
Definition ax_node.cc:23
int GetIntAttribute(ax::mojom::IntAttribute attribute) const
Definition ax_node.h:232
bool IsDescendantOfCrossingTreeBoundary(const AXNode *ancestor) const
Definition ax_node.cc:1239
bool IsChildOfLeaf() const
Definition ax_node.cc:1078
void SwapChildren(std::vector< AXNode * > *children)
Definition ax_node.cc:399
AXNode * GetFirstChild() const
Definition ax_node.cc:322
bool IsLineBreak() const
Definition ax_node.cc:366
AXNode * GetPreviousSibling() const
Definition ax_node.cc:337
std::optional< int > GetTableCellCount() const
Definition ax_node.cc:572
bool IsOrderedSet() const
Definition ax_node.cc:944
std::optional< int > GetTableCellColIndex() const
Definition ax_node.cc:772
bool IsIgnored() const
Definition ax_node.cc:1074
AXNode * GetPreviousUnignoredInTreeOrder() const
Definition ax_node.cc:300
AXNode * GetNextUnignoredSibling() const
Definition ax_node.cc:155
size_t index_in_parent() const
Definition ax_node.h:114
bool IsDescendantOf(const AXNode *ancestor) const
Definition ax_node.cc:407
AXNode * GetTableCellFromIndex(int index) const
Definition ax_node.cc:588
std::optional< int > GetPosInSet()
Definition ax_node.cc:949
AXNode * GetOrderedSet() const
Definition ax_node.cc:1032
AXNode * GetTableCellFromCoords(int row_index, int col_index) const
Definition ax_node.cc:611
const AXNodeData & data() const
Definition ax_node.h:112
const std::vector< AXNode * > * GetExtraMacNodes() const
Definition ax_node.cc:678
std::string GetInnerText() const
Definition ax_node.cc:472
std::optional< int > GetTableAriaRowCount() const
Definition ax_node.cc:565
std::vector< std::vector< int32_t > > col_headers
std::vector< CellData > cell_data_vector
std::vector< std::vector< int32_t > > cell_ids
std::unordered_map< int32_t, size_t > row_id_to_index
std::vector< AXNode * > extra_mac_nodes
std::unordered_map< int32_t, size_t > cell_id_to_index
std::vector< AXNode * > row_nodes
std::vector< int32_t > all_headers
std::vector< std::vector< int32_t > > row_headers
std::vector< int32_t > unique_cell_ids
AXTreeManager * GetManager(AXTreeID tree_id)
static AXTreeManagerMap & GetInstance()
EMSCRIPTEN_KEEPALIVE void empty()
uint8_t value
GAsyncResult * result
std::u16string text
const std::string & EmptyString()
std::u16string UTF8ToUTF16(std::string src)
bool IsTableColumn(ax::mojom::Role role)
bool IsCellOrTableHeader(const ax::mojom::Role role)
bool IsTableRow(ax::mojom::Role role)
bool IsItemLike(const ax::mojom::Role role)
bool IsTableLike(const ax::mojom::Role role)
std::ostream & operator<<(std::ostream &os, AXEventGenerator::Event event)
bool IsSetLike(const ax::mojom::Role role)
bool IsText(ax::mojom::Role role)
static SkColor4f transform(SkColor4f c, SkColorSpace *src, SkColorSpace *dst)
Definition p3.cpp:47
AXRelativeBounds relative_bounds
bool IsPlainTextField() const
bool HasState(ax::mojom::State state) const
void AddIntListAttribute(ax::mojom::IntListAttribute attribute, const std::vector< int32_t > &value)
bool HasStringAttribute(ax::mojom::StringAttribute attribute) const
bool IsTextField() const
ax::mojom::NameFrom GetNameFrom() const
bool IsRichTextField() const
virtual std::string ToString() const
bool IsIgnored() const
bool GetBoolAttribute(ax::mojom::BoolAttribute attribute) const
ax::mojom::Role role
const std::string & GetStringAttribute(ax::mojom::StringAttribute attribute) const
std::unique_ptr< gfx::Transform > transform
#define BASE_DCHECK(condition)
Definition logging.h:63