Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
ax_platform_node_base.cc
Go to the documentation of this file.
1// Copyright 2014 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
6
7#include <algorithm>
8#include <iomanip>
9#include <limits>
10#include <set>
11#include <sstream>
12#include <string>
13#include <unordered_map>
14#include <utility>
15#include <vector>
16
17#include "ax/ax_action_data.h"
18#include "ax/ax_enums.h"
19#include "ax/ax_node_data.h"
21#include "ax/ax_tree_data.h"
23#include "base/color_utils.h"
24#include "base/string_utils.h"
25#include "compute_attributes.h"
27
28namespace ui {
29
30namespace {
31
32// Check for descendant comment, using limited depth first search.
33bool FindDescendantRoleWithMaxDepth(AXPlatformNodeBase* node,
34 ax::mojom::Role descendant_role,
35 int max_depth,
36 int max_children_to_check) {
37 if (node->GetData().role == descendant_role)
38 return true;
39 if (max_depth <= 1)
40 return false;
41
42 int num_children_to_check =
43 std::min(node->GetChildCount(), max_children_to_check);
44 for (int index = 0; index < num_children_to_check; index++) {
45 auto* child = static_cast<AXPlatformNodeBase*>(
46 AXPlatformNode::FromNativeViewAccessible(node->ChildAtIndex(index)));
47 if (child &&
48 FindDescendantRoleWithMaxDepth(child, descendant_role, max_depth - 1,
49 max_children_to_check)) {
50 return true;
51 }
52 }
53
54 return false;
55}
56
57} // namespace
58
59const char16_t AXPlatformNodeBase::kEmbeddedCharacter = L'\xfffc';
60
61// Map from each AXPlatformNode's unique id to its instance.
62using UniqueIdMap = std::unordered_map<int32_t, AXPlatformNode*>;
64
65// static
67 auto iter = g_unique_id_map.find(unique_id);
68 if (iter != g_unique_id_map.end())
69 return iter->second;
70
71 return nullptr;
72}
73
74// static
78
80
82
84 delegate_ = delegate;
85
86 // This must be called after assigning our delegate.
88}
89
91 static const base::NoDestructor<AXNodeData> empty_data;
92 if (delegate_)
93 return delegate_->GetData();
94 return *empty_data;
95}
96
98 if (delegate_)
99 return delegate_->GetFocus();
100 return nullptr;
101}
102
104 if (delegate_)
105 return delegate_->GetParent();
106 return nullptr;
107}
108
110 if (delegate_)
111 return delegate_->GetChildCount();
112 return 0;
113}
114
116 if (delegate_)
117 return delegate_->ChildAtIndex(index);
118 return nullptr;
119}
120
121std::string AXPlatformNodeBase::GetName() const {
122 if (delegate_)
123 return delegate_->GetName();
124 return std::string();
125}
126
128 std::string name = GetName();
129 if (name.empty())
130 return std::u16string();
131 return base::UTF8ToUTF16(name);
132}
133
136 if (!parent)
137 return std::nullopt;
138
139 int child_count = parent->GetChildCount();
140 if (child_count == 0) {
141 // |child_count| could be 0 if the parent is IsLeaf.
142 BASE_DCHECK(parent->IsLeaf());
143 return std::nullopt;
144 }
145
146 // Ask the delegate for the index in parent, and return it if it's plausible.
147 //
148 // Delegates are allowed to not implement this (ViewsAXPlatformNodeDelegate
149 // returns -1). Also, delegates may not know the correct answer if this
150 // node is the root of a tree that's embedded in another tree, in which
151 // case the delegate should return -1 and we'll compute it.
152 int index = delegate_ ? delegate_->GetIndexInParent() : -1;
153 if (index >= 0 && index < child_count)
154 return index;
155
156 // Otherwise, search the parent's children.
158 for (int i = 0; i < child_count; i++) {
159 if (parent->ChildAtIndex(i) == current)
160 return i;
161 }
162
163 // If the parent has a modal dialog, it doesn't count other children.
164 if (parent->delegate_ && parent->delegate_->HasModalDialog())
165 return std::nullopt;
166
167 BASE_LOG()
168 << "Unable to find the child in the list of its parent's children.";
170 return std::nullopt;
171}
172
173std::stack<gfx::NativeViewAccessible> AXPlatformNodeBase::GetAncestors() {
174 std::stack<gfx::NativeViewAccessible> ancestors;
176 while (current_node) {
177 ancestors.push(current_node);
178 current_node = FromNativeViewAccessible(current_node)->GetParent();
179 }
180
181 return ancestors;
182}
183
185 // We define two node's relative positions in the following way:
186 // 1. this->CompareTo(other) == 0:
187 // - |this| and |other| are the same node.
188 // 2. this->CompareTo(other) < 0:
189 // - |this| is an ancestor of |other|.
190 // - |this|'s first uncommon ancestor comes before |other|'s first uncommon
191 // ancestor. The first uncommon ancestor is defined as the immediate child
192 // of the lowest common anestor of the two nodes. The first uncommon
193 // ancestor of |this| and |other| share the same parent (i.e. lowest common
194 // ancestor), so we can just compare the first uncommon ancestors' child
195 // indices to determine their relative positions.
196 // 3. this->CompareTo(other) == nullopt:
197 // - |this| and |other| are not comparable. E.g. they do not have a common
198 // ancestor.
199 //
200 // Another way to look at the nodes' relative positions/logical orders is that
201 // they are equivalent to pre-order traversal of the tree. If we pre-order
202 // traverse from the root, the node that we visited earlier is always going to
203 // be before (logically less) the node we visit later.
204
205 if (this == &other)
206 return std::optional<int>(0);
207
208 // Compute the ancestor stacks of both positions and traverse them from the
209 // top most ancestor down, so we can discover the first uncommon ancestors.
210 // The first uncommon ancestor is the immediate child of the lowest common
211 // ancestor.
212 gfx::NativeViewAccessible common_ancestor = nullptr;
213 std::stack<gfx::NativeViewAccessible> our_ancestors = GetAncestors();
214 std::stack<gfx::NativeViewAccessible> other_ancestors = other.GetAncestors();
215
216 // Start at the root and traverse down. Keep going until the |this|'s ancestor
217 // chain and |other|'s ancestor chain disagree. The last node before they
218 // disagree is the lowest common ancestor.
219 while (!our_ancestors.empty() && !other_ancestors.empty() &&
220 our_ancestors.top() == other_ancestors.top()) {
221 common_ancestor = our_ancestors.top();
222 our_ancestors.pop();
223 other_ancestors.pop();
224 }
225
226 // Nodes do not have a common ancestor, they are not comparable.
227 if (!common_ancestor)
228 return std::nullopt;
229
230 // Compute the logical order when the common ancestor is |this| or |other|.
231 auto* common_ancestor_platform_node =
232 FromNativeViewAccessible(common_ancestor);
233 if (common_ancestor_platform_node == this)
234 return std::optional<int>(-1);
235 if (common_ancestor_platform_node == &other)
236 return std::optional<int>(1);
237
238 // Compute the logical order of |this| and |other| by using their first
239 // uncommon ancestors.
240 if (!our_ancestors.empty() && !other_ancestors.empty()) {
241 std::optional<int> this_index_in_parent =
242 FromNativeViewAccessible(our_ancestors.top())->GetIndexInParent();
243 std::optional<int> other_index_in_parent =
244 FromNativeViewAccessible(other_ancestors.top())->GetIndexInParent();
245
246 if (!this_index_in_parent || !other_index_in_parent)
247 return std::nullopt;
248
249 int this_uncommon_ancestor_index = this_index_in_parent.value();
250 int other_uncommon_ancestor_index = other_index_in_parent.value();
251 if (this_uncommon_ancestor_index == other_uncommon_ancestor_index) {
252 BASE_LOG()
253 << "Deepest uncommon ancestors should truly be uncommon, i.e. not "
254 "the same.";
256 }
257
258 return std::optional<int>(this_uncommon_ancestor_index -
259 other_uncommon_ancestor_index);
260 }
261
262 return std::nullopt;
263}
264
265// AXPlatformNode overrides.
266
269
271
272 delegate_ = nullptr;
273 Dispose();
274}
275
277 delete this;
278}
279
283
286
287#if defined(OS_APPLE)
288void AXPlatformNodeBase::AnnounceText(const std::u16string& text) {}
289#endif
290
294
296 if (!ancestor)
297 return false;
298
299 if (this == ancestor)
300 return true;
301
303 if (!parent)
304 return false;
305
306 return parent->IsDescendantOf(ancestor);
307}
308
313
318// Helpers.
319
325
331
337
343
345 if (!delegate_)
346 return false;
347 if (!node)
348 return false;
349 if (node == this)
350 return true;
351 gfx::NativeViewAccessible native_parent = node->GetParent();
352 if (!native_parent)
353 return false;
354 AXPlatformNodeBase* parent = FromNativeViewAccessible(native_parent);
355 return IsDescendant(parent);
356}
357
359 ax::mojom::BoolAttribute attribute) const {
360 if (!delegate_)
361 return false;
362 return GetData().HasBoolAttribute(attribute);
363}
364
366 ax::mojom::BoolAttribute attribute) const {
367 if (!delegate_)
368 return false;
369 return GetData().GetBoolAttribute(attribute);
370}
371
373 bool* value) const {
374 if (!delegate_)
375 return false;
376 return GetData().GetBoolAttribute(attribute, value);
377}
378
380 ax::mojom::FloatAttribute attribute) const {
381 if (!delegate_)
382 return false;
383 return GetData().HasFloatAttribute(attribute);
384}
385
387 ax::mojom::FloatAttribute attribute) const {
388 if (!delegate_)
389 return false;
390 return GetData().GetFloatAttribute(attribute);
391}
392
394 float* value) const {
395 if (!delegate_)
396 return false;
397 return GetData().GetFloatAttribute(attribute, value);
398}
399
401 ax::mojom::IntAttribute attribute) const {
402 if (!delegate_)
403 return false;
404 return GetData().HasIntAttribute(attribute);
405}
406
408 ax::mojom::IntAttribute attribute) const {
409 if (!delegate_)
410 return 0;
411 return GetData().GetIntAttribute(attribute);
412}
413
415 int* value) const {
416 if (!delegate_)
417 return false;
418 return GetData().GetIntAttribute(attribute, value);
419}
420
422 ax::mojom::StringAttribute attribute) const {
423 if (!delegate_)
424 return false;
425 return GetData().HasStringAttribute(attribute);
426}
427
429 ax::mojom::StringAttribute attribute) const {
430 if (!delegate_)
431 return base::EmptyString();
432 return GetData().GetStringAttribute(attribute);
433}
434
437 std::string* value) const {
438 if (!delegate_)
439 return false;
440 return GetData().GetStringAttribute(attribute, value);
441}
442
444 ax::mojom::StringAttribute attribute) const {
445 if (!delegate_)
446 return std::u16string();
447 return GetData().GetString16Attribute(attribute);
448}
449
452 std::u16string* value) const {
453 if (!delegate_)
454 return false;
455 return GetData().GetString16Attribute(attribute, value);
456}
457
459 ax::mojom::StringAttribute attribute) const {
460 const AXPlatformNodeBase* current_node = this;
461
462 do {
463 if (!current_node->delegate_) {
464 return false;
465 }
466
467 if (current_node->GetData().HasStringAttribute(attribute)) {
468 return true;
469 }
470
471 current_node = FromNativeViewAccessible(current_node->GetParent());
472 } while (current_node);
473
474 return false;
475}
476
478 ax::mojom::StringAttribute attribute) const {
479 const AXPlatformNodeBase* current_node = this;
480
481 do {
482 if (!current_node->delegate_)
483 return base::EmptyString();
484
485 if (current_node->GetData().HasStringAttribute(attribute)) {
486 return current_node->GetData().GetStringAttribute(attribute);
487 }
488
489 current_node = FromNativeViewAccessible(current_node->GetParent());
490 } while (current_node);
491
492 return base::EmptyString();
493}
494
499
502 std::string* value) const {
503 const AXPlatformNodeBase* current_node = this;
504
505 do {
506 if (!current_node->delegate_) {
507 return false;
508 }
509
510 if (current_node->GetData().GetStringAttribute(attribute, value)) {
511 return true;
512 }
513
514 current_node = FromNativeViewAccessible(current_node->GetParent());
515 } while (current_node);
516
517 return false;
518}
519
522 std::u16string* value) const {
523 std::string value_utf8;
524 if (!GetInheritedStringAttribute(attribute, &value_utf8))
525 return false;
526 *value = base::UTF8ToUTF16(value_utf8);
527 return true;
528}
529
531 ax::mojom::IntListAttribute attribute) const {
532 if (!delegate_)
533 return false;
534 return GetData().HasIntListAttribute(attribute);
535}
536
537const std::vector<int32_t>& AXPlatformNodeBase::GetIntListAttribute(
538 ax::mojom::IntListAttribute attribute) const {
539 static const base::NoDestructor<std::vector<int32_t>> empty_data;
540 if (!delegate_)
541 return *empty_data;
542 return GetData().GetIntListAttribute(attribute);
543}
544
547 std::vector<int32_t>* value) const {
548 if (!delegate_)
549 return false;
550 return GetData().GetIntListAttribute(attribute, value);
551}
552
553// static
559
561 int end_offset) {
562 if (!delegate_)
563 return false;
564 return delegate_->SetHypertextSelection(start_offset, end_offset);
565}
566
568 return ui::IsDocument(GetData().role);
569}
570
572 switch (GetData().role) {
573 // An ARIA 1.1+ role of "cell", or a role of "row" inside
574 // an ARIA 1.1 role of "table", should not be selectable.
575 // ARIA "table" is not interactable, ARIA "grid" is.
580 // An ARIA grid subwidget is only selectable if explicitly marked as
581 // selected (or not) with the aria-selected property.
583 return false;
584
586 if (!table)
587 return false;
588
589 return table->GetData().role == ax::mojom::Role::kGrid ||
590 table->GetData().role == ax::mojom::Role::kTreeGrid;
591 }
592 // https://www.w3.org/TR/core-aam-1.1/#mapping_state-property_table
593 // SelectionItem.IsSelected is exposed when aria-checked is True or False,
594 // for 'radio' and 'menuitemradio' roles.
597 if (GetData().GetCheckedState() == ax::mojom::CheckedState::kTrue ||
598 GetData().GetCheckedState() == ax::mojom::CheckedState::kFalse)
599 return true;
600 return false;
601 }
602 // https://www.w3.org/TR/wai-aria-1.1/#aria-selected
603 // SelectionItem.IsSelected is exposed when aria-select is True or False.
610 default:
611 return false;
612 }
613}
614
616 return GetData().IsTextField();
617}
618
622
626
628 return delegate_ && delegate_->IsText();
629}
630
631std::u16string AXPlatformNodeBase::GetHypertext() const {
632 if (!delegate_)
633 return std::u16string();
634
635 // Hypertext of platform leaves, which internally are composite objects, are
636 // represented with the inner text of the internal composite object. These
637 // don't exist on non-web content.
638 if (IsChildOfLeaf())
639 return GetInnerText();
640
643 return hypertext_.hypertext;
644}
645
646std::u16string AXPlatformNodeBase::GetInnerText() const {
647 if (!delegate_)
648 return std::u16string();
649 return delegate_->GetInnerText();
650}
651
653 float fval;
654 std::u16string value =
656
657 if (value.empty() &&
660 }
661 return value;
662}
663
664std::u16string
677
679 std::u16string role_description =
681
682 if (!role_description.empty()) {
683 return role_description;
684 }
685
687}
688
690 if (!delegate_)
691 return nullptr;
692 AXPlatformNodeBase* container = const_cast<AXPlatformNodeBase*>(this);
693 while (container &&
695 gfx::NativeViewAccessible parent_accessible = container->GetParent();
696 AXPlatformNodeBase* parent = FromNativeViewAccessible(parent_accessible);
697
698 container = parent;
699 }
700 return container;
701}
702
704 if (!delegate_)
705 return nullptr;
706 AXPlatformNodeBase* table = const_cast<AXPlatformNodeBase*>(this);
707 while (table && !IsTableLike(table->GetData().role)) {
708 gfx::NativeViewAccessible parent_accessible = table->GetParent();
709 AXPlatformNodeBase* parent = FromNativeViewAccessible(parent_accessible);
710
711 table = parent;
712 }
713 return table;
714}
715
717 if (!delegate_)
718 return nullptr;
719
721 if (!table)
722 return nullptr;
723
724 BASE_DCHECK(table->delegate_);
725 return static_cast<AXPlatformNodeBase*>(table->delegate_->GetTableCaption());
726}
727
729 if (!delegate_)
730 return nullptr;
731 if (!IsTableLike(GetData().role) && !IsCellOrTableHeader(GetData().role))
732 return nullptr;
733
735 if (!table)
736 return nullptr;
737
738 BASE_DCHECK(table->delegate_);
739 std::optional<int32_t> cell_id = table->delegate_->CellIndexToId(index);
740 if (!cell_id)
741 return nullptr;
742
743 return static_cast<AXPlatformNodeBase*>(
744 table->delegate_->GetFromNodeID(*cell_id));
745}
746
748 int column) const {
749 if (!IsTableLike(GetData().role) && !IsCellOrTableHeader(GetData().role))
750 return nullptr;
751
754 return nullptr;
755
756 if (row < 0 || row >= *GetTableRowCount() || column < 0 ||
757 column >= *GetTableColumnCount()) {
758 return nullptr;
759 }
760
761 BASE_DCHECK(table->delegate_);
762 std::optional<int32_t> cell_id = table->delegate_->GetCellId(row, column);
763 if (!cell_id)
764 return nullptr;
765
766 return static_cast<AXPlatformNodeBase*>(
767 table->delegate_->GetFromNodeID(*cell_id));
768}
769
770std::optional<int> AXPlatformNodeBase::GetTableCellIndex() const {
771 if (!delegate_)
772 return std::nullopt;
774}
775
776std::optional<int> AXPlatformNodeBase::GetTableColumn() const {
777 if (!delegate_)
778 return std::nullopt;
780}
781
782std::optional<int> AXPlatformNodeBase::GetTableColumnCount() const {
783 if (!delegate_)
784 return std::nullopt;
785
787 if (!table)
788 return std::nullopt;
789
790 BASE_DCHECK(table->delegate_);
791 return table->delegate_->GetTableColCount();
792}
793
795 if (!delegate_)
796 return std::nullopt;
797
799 if (!table)
800 return std::nullopt;
801
802 BASE_DCHECK(table->delegate_);
803 return table->delegate_->GetTableAriaColCount();
804}
805
806std::optional<int> AXPlatformNodeBase::GetTableColumnSpan() const {
807 if (!delegate_)
808 return std::nullopt;
810}
811
812std::optional<int> AXPlatformNodeBase::GetTableRow() const {
813 if (!delegate_)
814 return std::nullopt;
815 if (delegate_->IsTableRow())
819 return std::nullopt;
820}
821
822std::optional<int> AXPlatformNodeBase::GetTableRowCount() const {
823 if (!delegate_)
824 return std::nullopt;
825
827 if (!table)
828 return std::nullopt;
829
830 BASE_DCHECK(table->delegate_);
831 return table->delegate_->GetTableRowCount();
832}
833
835 if (!delegate_)
836 return std::nullopt;
837
839 if (!table)
840 return std::nullopt;
841
842 BASE_DCHECK(table->delegate_);
843 return table->delegate_->GetTableAriaRowCount();
844}
845
846std::optional<int> AXPlatformNodeBase::GetTableRowSpan() const {
847 if (!delegate_)
848 return std::nullopt;
850}
851
852std::optional<float> AXPlatformNodeBase::GetFontSizeInPoints() const {
853 float font_size;
854 // Attribute has no default value.
856 // The IA2 Spec requires the value to be in pt, not in pixels.
857 // There are 72 points per inch.
858 // We assume that there are 96 pixels per inch on a standard display.
859 // TODO(nektar): Figure out the current value of pixels per inch.
860 float points = font_size * 72.0 / 96.0;
861
862 // Round to the nearest 0.5 points.
863 points = std::round(points * 2.0) / 2.0;
864 return points;
865 }
866 return std::nullopt;
867}
868
870 const AXTree::Selection* unignored_selection) {
872 return false;
873
874 if (IsPlainTextField() &&
877 return true;
878 }
879
880 // The caret is always at the focus of the selection.
881 int32_t focus_id;
882 if (unignored_selection)
883 focus_id = unignored_selection->focus_object_id;
884 else
886
887 AXPlatformNodeBase* focus_object =
888 static_cast<AXPlatformNodeBase*>(delegate_->GetFromNodeID(focus_id));
889
890 if (!focus_object)
891 return false;
892
893 return focus_object->IsDescendantOf(this);
894}
895
897 return delegate_ && delegate_->IsLeaf();
898}
899
903
907
916
924
932
933std::u16string AXPlatformNodeBase::GetValue() const {
934 // Expose slider value.
936 return GetRangeValueText();
937
938 // On Windows, the value of a document should be its URL.
939 if (ui::IsDocument(GetData().role))
941
942 std::u16string value =
944
945 // Some screen readers like Jaws and VoiceOver require a
946 // value to be set in text fields with rich content, even though the same
947 // information is available on the children.
948 if (value.empty() && IsRichTextField())
949 return GetInnerText();
950
951 return value;
952}
953
956 // Expose some HTML and ARIA attributes in the IAccessible2 attributes string
957 // "display", "tag", and "xml-roles" have somewhat unusual names for
958 // historical reasons. Aside from that virtually every ARIA attribute
959 // is exposed in a really straightforward way, i.e. "aria-foo" is exposed
960 // as "foo".
962 attributes);
965 attributes);
967 attributes);
968
970 attributes);
973 AddAttributeToList("autocomplete", "list", attributes);
974 }
975
976 std::u16string role_description =
978 if (!role_description.empty() ||
980 AddAttributeToList("roledescription", base::UTF16ToUTF8(role_description),
981 attributes);
982 }
983
985 attributes);
986
988 attributes);
991 attributes);
992
994 AddAttributeToList("checkable", "true", attributes);
995
996 if (IsInvisibleOrIgnored()) // Note: NVDA prefers this over INVISIBLE state.
997 AddAttributeToList("hidden", "true", attributes);
998
999 // Expose live region attributes.
1001 attributes);
1003 attributes);
1005 attributes);
1006 // Busy is usually associated with live regions but can occur anywhere:
1008
1009 // Expose container live region attributes.
1011 "container-live", attributes);
1013 "container-relevant", attributes);
1015 "container-atomic", attributes);
1017 "container-busy", attributes);
1018
1019 // Expose the non-standard explicit-name IA2 attribute.
1020 int name_from;
1022 name_from != static_cast<int32_t>(ax::mojom::NameFrom::kContents)) {
1023 AddAttributeToList("explicit-name", "true", attributes);
1024 }
1025
1026 // Expose the aria-haspopup attribute.
1027 int32_t has_popup;
1029 switch (static_cast<ax::mojom::HasPopup>(has_popup)) {
1031 break;
1033 AddAttributeToList("haspopup", "true", attributes);
1034 break;
1036 AddAttributeToList("haspopup", "menu", attributes);
1037 break;
1039 AddAttributeToList("haspopup", "listbox", attributes);
1040 break;
1042 AddAttributeToList("haspopup", "tree", attributes);
1043 break;
1045 AddAttributeToList("haspopup", "grid", attributes);
1046 break;
1048 AddAttributeToList("haspopup", "dialog", attributes);
1049 break;
1050 }
1051 } else if (GetData().HasState(ax::mojom::State::kAutofillAvailable)) {
1052 AddAttributeToList("haspopup", "menu", attributes);
1053 }
1054
1055 // Expose the aria-current attribute.
1056 int32_t aria_current_state;
1058 &aria_current_state)) {
1059 switch (static_cast<ax::mojom::AriaCurrentState>(aria_current_state)) {
1061 break;
1063 AddAttributeToList("current", "false", attributes);
1064 break;
1066 AddAttributeToList("current", "true", attributes);
1067 break;
1069 AddAttributeToList("current", "page", attributes);
1070 break;
1072 AddAttributeToList("current", "step", attributes);
1073 break;
1075 AddAttributeToList("current", "location", attributes);
1076 break;
1078 AddAttributeToList("current", "unclippedLocation", attributes);
1079 break;
1081 AddAttributeToList("current", "date", attributes);
1082 break;
1084 AddAttributeToList("current", "time", attributes);
1085 break;
1086 }
1087 }
1088
1089 // Expose table cell index.
1090 if (IsCellOrTableHeader(GetData().role)) {
1091 std::optional<int> index = delegate_->GetTableCellIndex();
1092 if (index) {
1093 std::string str_index(base::NumberToString(*index));
1094 AddAttributeToList("table-cell-index", str_index, attributes);
1095 }
1096 }
1098 AddAttributeToList("layout-guess", "true", attributes);
1099
1100 // Expose aria-colcount and aria-rowcount in a table, grid or treegrid if they
1101 // are different from its physical dimensions.
1102 if (IsTableLike(GetData().role) &&
1106 attributes);
1108 attributes);
1109 }
1110
1111 if (IsCellOrTableHeader(GetData().role) || IsTableRow(GetData().role)) {
1112 // Expose aria-colindex and aria-rowindex in a cell or row only if they are
1113 // different from the table's physical coordinates.
1118 if (!IsTableRow(GetData().role)) {
1120 "colindex", attributes);
1121 }
1123 attributes);
1124 }
1125
1126 // Experimental: expose aria-rowtext / aria-coltext. Not standardized
1127 // yet, but obscure enough that it's safe to expose.
1128 // http://crbug.com/791634
1129 for (size_t i = 0; i < GetData().html_attributes.size(); ++i) {
1130 const std::string& attr = GetData().html_attributes[i].first;
1131 const std::string& value = GetData().html_attributes[i].second;
1132 if (attr == "aria-coltext") {
1133 AddAttributeToList("coltext", value, attributes);
1134 }
1135 if (attr == "aria-rowtext") {
1136 AddAttributeToList("rowtext", value, attributes);
1137 }
1138 }
1139 }
1140
1141 // Expose row or column header sort direction.
1142 int32_t sort_direction;
1143 if (IsTableHeader(GetData().role) &&
1145 &sort_direction)) {
1146 switch (static_cast<ax::mojom::SortDirection>(sort_direction)) {
1148 break;
1150 AddAttributeToList("sort", "none", attributes);
1151 break;
1153 AddAttributeToList("sort", "ascending", attributes);
1154 break;
1156 AddAttributeToList("sort", "descending", attributes);
1157 break;
1159 AddAttributeToList("sort", "other", attributes);
1160 break;
1161 }
1162 }
1163
1164 if (IsCellOrTableHeader(GetData().role)) {
1165 // Expose colspan attribute.
1166 std::string colspan;
1167 if (GetData().GetHtmlAttribute("aria-colspan", &colspan)) {
1168 AddAttributeToList("colspan", colspan, attributes);
1169 }
1170 // Expose rowspan attribute.
1171 std::string rowspan;
1172 if (GetData().GetHtmlAttribute("aria-rowspan", &rowspan)) {
1173 AddAttributeToList("rowspan", rowspan, attributes);
1174 }
1175 }
1176
1177 // Expose slider value.
1180 std::string value = base::UTF16ToUTF8(GetRangeValueText());
1181 if (!value.empty())
1182 AddAttributeToList("valuetext", value, attributes);
1183 }
1184
1185 // Expose dropeffect attribute.
1186 // aria-dropeffect is deprecated in WAI-ARIA 1.1.
1188 std::string dropeffect = GetData().DropeffectBitfieldToString();
1189 AddAttributeToList("dropeffect", dropeffect, attributes);
1190 }
1191
1192 // Expose grabbed attribute.
1193 // aria-grabbed is deprecated in WAI-ARIA 1.1.
1195
1196 // Expose class attribute.
1197 std::string class_attr;
1199 &class_attr)) {
1200 AddAttributeToList("class", class_attr, attributes);
1201 }
1202
1203 // Expose datetime attribute.
1204 std::string datetime;
1205 if (GetData().role == ax::mojom::Role::kTime &&
1206 GetData().GetHtmlAttribute("datetime", &datetime)) {
1207 AddAttributeToList("datetime", datetime, attributes);
1208 }
1209
1210 // Expose id attribute.
1211 std::string id;
1212 if (GetData().GetHtmlAttribute("id", &id)) {
1213 AddAttributeToList("id", id, attributes);
1214 }
1215
1216 // Expose src attribute.
1217 std::string src;
1218 if (GetData().role == ax::mojom::Role::kImage &&
1219 GetData().GetHtmlAttribute("src", &src)) {
1220 AddAttributeToList("src", src, attributes);
1221 }
1222
1224 auto text_align = static_cast<ax::mojom::TextAlign>(
1226 switch (text_align) {
1228 break;
1230 AddAttributeToList("text-align", "left", attributes);
1231 break;
1233 AddAttributeToList("text-align", "right", attributes);
1234 break;
1236 AddAttributeToList("text-align", "center", attributes);
1237 break;
1239 AddAttributeToList("text-align", "justify", attributes);
1240 break;
1241 }
1242 }
1243
1244 float text_indent;
1246 0.0f) {
1247 // Round value to two decimal places.
1248 std::stringstream value;
1249 value << std::fixed << std::setprecision(2) << text_indent << "mm";
1250 AddAttributeToList("text-indent", value.str(), attributes);
1251 }
1252
1253 // Text fields need to report the attribute "text-model:a1" to instruct
1254 // screen readers to use IAccessible2 APIs to handle text editing in this
1255 // object (as opposed to treating it like a native Windows text box).
1256 // The text-model:a1 attribute is documented here:
1257 // http://www.linuxfoundation.org/collaborate/workgroups/accessibility/ia2/ia2_implementation_guide
1258 if (IsTextField())
1259 AddAttributeToList("text-model", "a1", attributes);
1260
1261 std::string details_roles = ComputeDetailsRoles();
1262 if (!details_roles.empty())
1263 AddAttributeToList("details-roles", details_roles, attributes);
1264}
1265
1267 const ax::mojom::StringAttribute attribute,
1268 const char* name,
1269 PlatformAttributeList* attributes) {
1270 BASE_DCHECK(attributes);
1271 std::string value;
1272 if (GetStringAttribute(attribute, &value)) {
1273 AddAttributeToList(name, value, attributes);
1274 }
1275}
1276
1278 const ax::mojom::BoolAttribute attribute,
1279 const char* name,
1280 PlatformAttributeList* attributes) {
1281 BASE_DCHECK(attributes);
1282 bool value;
1283 if (GetBoolAttribute(attribute, &value)) {
1284 AddAttributeToList(name, value ? "true" : "false", attributes);
1285 }
1286}
1287
1289 const ax::mojom::IntAttribute attribute,
1290 const char* name,
1291 PlatformAttributeList* attributes) {
1292 BASE_DCHECK(attributes);
1293
1294 auto maybe_value = ComputeAttribute(delegate_, attribute);
1295 if (maybe_value.has_value()) {
1296 std::string str_value = base::NumberToString(maybe_value.value());
1297 AddAttributeToList(name, str_value, attributes);
1298 }
1299}
1300
1302 const std::string& value,
1303 PlatformAttributeList* attributes) {
1304 AddAttributeToList(name, value.c_str(), attributes);
1305}
1306
1307AXHypertext::AXHypertext() = default;
1308AXHypertext::~AXHypertext() = default;
1309AXHypertext::AXHypertext(const AXHypertext& other) = default;
1310AXHypertext& AXHypertext::operator=(const AXHypertext& other) = default;
1311
1313 if (!delegate_)
1314 return;
1316
1317 if (IsLeaf()) {
1319 hypertext_.needs_update = false;
1320 return;
1321 }
1322
1323 // Construct the hypertext for this node, which contains the concatenation
1324 // of all of the static text and widespace of this node's children and an
1325 // embedded object character for all the other children. Build up a map from
1326 // the character index of each embedded object character to the id of the
1327 // child object it points to.
1328 std::u16string hypertext;
1330 child_iter != AXPlatformNodeChildrenEnd(); ++child_iter) {
1331 // Similar to Firefox, we don't expose text-only objects in IA2 and ATK
1332 // hypertext with the embedded object character. We copy all of their text
1333 // instead.
1334 if (child_iter->IsText()) {
1335 hypertext_.hypertext += child_iter->GetNameAsString16();
1336 } else {
1337 int32_t char_offset = static_cast<int32_t>(hypertext_.hypertext.size());
1338 int32_t child_unique_id = child_iter->GetUniqueId();
1339 int32_t index = static_cast<int32_t>(hypertext_.hyperlinks.size());
1340 hypertext_.hyperlink_offset_to_index[char_offset] = index;
1341 hypertext_.hyperlinks.push_back(child_unique_id);
1343 }
1344 }
1345
1346 hypertext_.needs_update = false;
1347}
1348
1350 const char* value,
1351 PlatformAttributeList* attributes) {
1352}
1353
1354std::optional<int> AXPlatformNodeBase::GetPosInSet() const {
1355 if (!delegate_)
1356 return std::nullopt;
1357 return delegate_->GetPosInSet();
1358}
1359
1360std::optional<int> AXPlatformNodeBase::GetSetSize() const {
1361 if (!delegate_)
1362 return std::nullopt;
1363 return delegate_->GetSetSize();
1364}
1365
1367 // ax::mojom::Action::kScrollToMakeVisible wants a target rect in *local*
1368 // coords.
1369 gfx::Rect r = gfx::ToEnclosingRect(GetData().relative_bounds.bounds);
1370 r -= r.OffsetFromOrigin();
1371 switch (scroll_type) {
1373 r = gfx::Rect(r.x(), r.y(), 0, 0);
1374 break;
1376 r = gfx::Rect(r.right(), r.bottom(), 0, 0);
1377 break;
1379 r = gfx::Rect(r.x(), r.y(), r.width(), 0);
1380 break;
1382 r = gfx::Rect(r.x(), r.bottom(), r.width(), 0);
1383 break;
1385 r = gfx::Rect(r.x(), r.y(), 0, r.height());
1386 break;
1388 r = gfx::Rect(r.right(), r.y(), 0, r.height());
1389 break;
1391 break;
1392 }
1393
1394 ui::AXActionData action_data;
1395 action_data.target_node_id = GetData().id;
1397 action_data.horizontal_scroll_alignment =
1399 action_data.vertical_scroll_alignment =
1401 action_data.scroll_behavior =
1403 action_data.target_rect = r;
1405 return true;
1406}
1407
1408// static
1410 std::string* output) {
1411 BASE_DCHECK(output);
1412 // According to the IA2 spec and AT-SPI2, these characters need to be escaped
1413 // with a backslash: backslash, colon, comma, equals and semicolon. Note
1414 // that backslash must be replaced first.
1415 base::ReplaceChars(input, "\\", "\\\\", output);
1416 base::ReplaceChars(*output, ":", "\\:", output);
1417 base::ReplaceChars(*output, ",", "\\,", output);
1418 base::ReplaceChars(*output, "=", "\\=", output);
1419 base::ReplaceChars(*output, ";", "\\;", output);
1420}
1421
1423 int offset) {
1424 std::map<int32_t, int32_t>::iterator iterator =
1426 if (iterator == hypertext_.hyperlink_offset_to_index.end())
1427 return nullptr;
1428
1429 int32_t index = iterator->second;
1430 BASE_DCHECK(index >= 0);
1431 BASE_DCHECK(index < static_cast<int32_t>(hypertext_.hyperlinks.size()));
1432 int32_t id = hypertext_.hyperlinks[index];
1433 auto* hyperlink =
1435 if (!hyperlink)
1436 return nullptr;
1437 return hyperlink;
1438}
1439
1441 AXPlatformNodeBase* child) {
1442 if (hypertext_.hyperlinks.empty())
1443 return -1;
1444
1445 auto iterator = std::find(hypertext_.hyperlinks.begin(),
1446 hypertext_.hyperlinks.end(), child->GetUniqueId());
1447 if (iterator == hypertext_.hyperlinks.end())
1448 return -1;
1449
1450 return static_cast<int32_t>(iterator - hypertext_.hyperlinks.begin());
1451}
1452
1454 int32_t hyperlink_index) {
1455 for (auto& offset_index : hypertext_.hyperlink_offset_to_index) {
1456 if (offset_index.second == hyperlink_index)
1457 return offset_index.first;
1458 }
1459 return -1;
1460}
1461
1463 AXPlatformNodeBase* child) {
1464 // TODO(dougt) BASE_DCHECK(child.owner()->PlatformGetParent() == owner());
1465
1466 if (IsLeaf())
1467 return -1;
1468
1469 // Handle the case when we are dealing with a text-only child.
1470 // Text-only children should not be present at tree roots and so no
1471 // cross-tree traversal is necessary.
1472 if (child->IsText()) {
1473 int32_t hypertext_offset = 0;
1474 for (auto child_iter = AXPlatformNodeChildrenBegin();
1475 child_iter != AXPlatformNodeChildrenEnd() && child_iter.get() != child;
1476 ++child_iter) {
1477 if (child_iter->IsText()) {
1478 hypertext_offset +=
1479 static_cast<int32_t>(child_iter->GetHypertext().size());
1480 } else {
1481 ++hypertext_offset;
1482 }
1483 }
1484 return hypertext_offset;
1485 }
1486
1487 int32_t hyperlink_index = GetHyperlinkIndexFromChild(child);
1488 if (hyperlink_index < 0)
1489 return -1;
1490
1491 return GetHypertextOffsetFromHyperlinkIndex(hyperlink_index);
1492}
1493
1495 AXPlatformNodeBase* descendant) {
1496 auto* parent_object = static_cast<AXPlatformNodeBase*>(
1498 while (parent_object && parent_object != this) {
1499 descendant = parent_object;
1500 parent_object = static_cast<AXPlatformNodeBase*>(
1501 FromNativeViewAccessible(descendant->GetParent()));
1502 }
1503 if (!parent_object)
1504 return -1;
1505
1506 return parent_object->GetHypertextOffsetFromChild(descendant);
1507}
1508
1510 AXPlatformNodeBase* endpoint_object,
1511 int endpoint_offset) {
1512 // There are three cases:
1513 // 1. The selection endpoint is inside this object but not one of its
1514 // descendants, or is in an ancestor of this object. endpoint_offset should be
1515 // returned, possibly adjusted from a child offset to a hypertext offset.
1516 // 2. The selection endpoint is a descendant of this object. The offset of the
1517 // character in this object's hypertext corresponding to the subtree in which
1518 // the endpoint is located should be returned.
1519 // 3. The selection endpoint is in a completely different part of the tree.
1520 // Either 0 or hypertext length should be returned depending on the direction
1521 // that one needs to travel to find the endpoint.
1522 //
1523 // TODO(nektar): Replace all this logic with the use of AXNodePosition.
1524
1525 // Case 1. Is the endpoint object equal to this object or an ancestor of this
1526 // object?
1527 //
1528 // IsDescendantOf includes the case when endpoint_object == this.
1529 if (IsDescendantOf(endpoint_object)) {
1530 if (endpoint_object->IsLeaf()) {
1531 BASE_DCHECK(endpoint_object == this);
1532 return endpoint_offset;
1533 } else {
1534 BASE_DCHECK(endpoint_offset >= 0);
1535 BASE_DCHECK(endpoint_offset <=
1536 endpoint_object->GetDelegate()->GetChildCount());
1537
1538 // Adjust the |endpoint_offset| because the selection endpoint is a tree
1539 // position, i.e. it represents a child index and not a text offset.
1540 if (endpoint_offset >= endpoint_object->GetChildCount()) {
1541 return static_cast<int>(endpoint_object->GetHypertext().size());
1542 } else {
1543 auto* child = static_cast<AXPlatformNodeBase*>(FromNativeViewAccessible(
1544 endpoint_object->ChildAtIndex(endpoint_offset)));
1545 BASE_DCHECK(child);
1546 return endpoint_object->GetHypertextOffsetFromChild(child);
1547 }
1548 }
1549 }
1550
1551 AXPlatformNodeBase* common_parent = this;
1552 std::optional<int> index_in_common_parent = GetIndexInParent();
1553 while (common_parent && !endpoint_object->IsDescendantOf(common_parent)) {
1554 index_in_common_parent = common_parent->GetIndexInParent();
1555 common_parent = static_cast<AXPlatformNodeBase*>(
1556 FromNativeViewAccessible(common_parent->GetParent()));
1557 }
1558 if (!common_parent)
1559 return -1;
1560
1561 BASE_DCHECK(!(common_parent->IsText()));
1562
1563 // Case 2. Is the selection endpoint inside a descendant of this object?
1564 //
1565 // We already checked in case 1 if our endpoint object is equal to this
1566 // object. We can safely assume that it is a descendant or in a completely
1567 // different part of the tree.
1568 if (common_parent == this) {
1569 int32_t hypertext_offset =
1570 GetHypertextOffsetFromDescendant(endpoint_object);
1571 auto* parent = static_cast<AXPlatformNodeBase*>(
1572 FromNativeViewAccessible(endpoint_object->GetParent()));
1573 if (parent == this && endpoint_object->IsText()) {
1574 // Due to a historical design decision, the hypertext of the immediate
1575 // parents of text objects includes all their text. We therefore need to
1576 // adjust the hypertext offset in the parent by adding any text offset.
1577 hypertext_offset += endpoint_offset;
1578 }
1579
1580 return hypertext_offset;
1581 }
1582
1583 // Case 3. Is the selection endpoint in a completely different part of the
1584 // tree?
1585 //
1586 // We can safely assume that the endpoint is in another part of the tree or
1587 // at common parent, and that this object is a descendant of common parent.
1588 std::optional<int> endpoint_index_in_common_parent;
1589 for (auto child_iter = common_parent->AXPlatformNodeChildrenBegin();
1590 child_iter != common_parent->AXPlatformNodeChildrenEnd(); ++child_iter) {
1591 if (endpoint_object->IsDescendantOf(child_iter.get())) {
1592 endpoint_index_in_common_parent = child_iter->GetIndexInParent();
1593 break;
1594 }
1595 }
1596
1597 if (endpoint_index_in_common_parent < index_in_common_parent)
1598 return 0;
1599 if (endpoint_index_in_common_parent > index_in_common_parent)
1600 return static_cast<int32_t>(GetHypertext().size());
1601
1603 return -1;
1604}
1605
1607 BASE_DCHECK(selection);
1608 int32_t anchor_id = selection->anchor_object_id;
1609 AXPlatformNodeBase* anchor_object =
1610 static_cast<AXPlatformNodeBase*>(delegate_->GetFromNodeID(anchor_id));
1611
1612 if (!anchor_object)
1613 return -1;
1614
1615 int anchor_offset = static_cast<int>(selection->anchor_offset);
1616 return GetHypertextOffsetFromEndpoint(anchor_object, anchor_offset);
1617}
1618
1620 BASE_DCHECK(selection);
1621 int32_t focus_id = selection->focus_object_id;
1622 AXPlatformNodeBase* focus_object =
1623 static_cast<AXPlatformNodeBase*>(GetDelegate()->GetFromNodeID(focus_id));
1624 if (!focus_object)
1625 return -1;
1626
1627 int focus_offset = static_cast<int>(selection->focus_offset);
1628 return GetHypertextOffsetFromEndpoint(focus_object, focus_offset);
1629}
1630
1632 int* selection_end) {
1633 GetSelectionOffsets(nullptr, selection_start, selection_end);
1634}
1635
1637 int* selection_start,
1638 int* selection_end) {
1639 BASE_DCHECK(selection_start && selection_end);
1640
1641 if (IsPlainTextField() &&
1643 selection_start) &&
1645 return;
1646 }
1647
1648 // If the unignored selection has not been computed yet, compute it now.
1649 AXTree::Selection unignored_selection;
1650 if (!selection) {
1651 unignored_selection = delegate_->GetUnignoredSelection();
1652 selection = &unignored_selection;
1653 }
1654 BASE_DCHECK(selection);
1655 GetSelectionOffsetsFromTree(selection, selection_start, selection_end);
1656}
1657
1659 const AXTree::Selection* selection,
1660 int* selection_start,
1661 int* selection_end) {
1662 BASE_DCHECK(selection_start && selection_end);
1663
1664 *selection_start = GetSelectionAnchor(selection);
1665 *selection_end = GetSelectionFocus(selection);
1666 if (*selection_start < 0 || *selection_end < 0)
1667 return;
1668
1669 // There are three cases when a selection would start and end on the same
1670 // character:
1671 // 1. Anchor and focus are both in a subtree that is to the right of this
1672 // object.
1673 // 2. Anchor and focus are both in a subtree that is to the left of this
1674 // object.
1675 // 3. Anchor and focus are in a subtree represented by a single embedded
1676 // object character.
1677 // Only case 3 refers to a valid selection because cases 1 and 2 fall
1678 // outside this object in their entirety.
1679 // Selections that span more than one character are by definition inside
1680 // this object, so checking them is not necessary.
1681 if (*selection_start == *selection_end && !HasCaret(selection)) {
1682 *selection_start = -1;
1683 *selection_end = -1;
1684 return;
1685 }
1686
1687 // The IA2 Spec says that if the largest of the two offsets falls on an
1688 // embedded object character and if there is a selection in that embedded
1689 // object, it should be incremented by one so that it points after the
1690 // embedded object character.
1691 // This is a signal to AT software that the embedded object is also part of
1692 // the selection.
1693 int* largest_offset =
1694 (*selection_start <= *selection_end) ? selection_end : selection_start;
1695 AXPlatformNodeBase* hyperlink =
1696 GetHyperlinkFromHypertextOffset(*largest_offset);
1697 if (!hyperlink)
1698 return;
1699
1700 int hyperlink_selection_start, hyperlink_selection_end;
1701 hyperlink->GetSelectionOffsets(selection, &hyperlink_selection_start,
1702 &hyperlink_selection_end);
1703 if (hyperlink_selection_start >= 0 && hyperlink_selection_end >= 0 &&
1704 hyperlink_selection_start != hyperlink_selection_end) {
1705 ++(*largest_offset);
1706 }
1707}
1708
1710 const AXHypertext& old_hypertext,
1711 size_t old_char_index,
1712 size_t new_char_index) {
1713 if (old_char_index >= old_hypertext.hypertext.size() ||
1714 new_char_index >= hypertext_.hypertext.size()) {
1715 return false;
1716 }
1717
1718 // For anything other than the "embedded character", we just compare the
1719 // characters directly.
1720 char16_t old_ch = old_hypertext.hypertext[old_char_index];
1721 char16_t new_ch = hypertext_.hypertext[new_char_index];
1722 if (old_ch != new_ch)
1723 return false;
1724 if (new_ch != kEmbeddedCharacter)
1725 return true;
1726
1727 // If it's an embedded character, they're only identical if the child id
1728 // the hyperlink points to is the same.
1729 const std::map<int32_t, int32_t>& old_offset_to_index =
1730 old_hypertext.hyperlink_offset_to_index;
1731 const std::vector<int32_t>& old_hyperlinks = old_hypertext.hyperlinks;
1732 int32_t old_hyperlinkscount = static_cast<int32_t>(old_hyperlinks.size());
1733 auto iter = old_offset_to_index.find(static_cast<int32_t>(old_char_index));
1734 int old_index = (iter != old_offset_to_index.end()) ? iter->second : -1;
1735 int old_child_id = (old_index >= 0 && old_index < old_hyperlinkscount)
1736 ? old_hyperlinks[old_index]
1737 : -1;
1738
1739 const std::map<int32_t, int32_t>& new_offset_to_index =
1741 const std::vector<int32_t>& new_hyperlinks = hypertext_.hyperlinks;
1742 int32_t new_hyperlinkscount = static_cast<int32_t>(new_hyperlinks.size());
1743 iter = new_offset_to_index.find(static_cast<int32_t>(new_char_index));
1744 int new_index = (iter != new_offset_to_index.end()) ? iter->second : -1;
1745 int new_child_id = (new_index >= 0 && new_index < new_hyperlinkscount)
1746 ? new_hyperlinks[new_index]
1747 : -1;
1748
1749 return old_child_id == new_child_id;
1750}
1751
1752// Return true if the index represents a text character.
1753bool AXPlatformNodeBase::IsText(const std::u16string& text,
1754 size_t index,
1755 bool is_indexed_from_end) {
1756 size_t text_len = text.size();
1757 if (index == text_len)
1758 return false;
1759 auto ch = text[is_indexed_from_end ? text_len - index - 1 : index];
1760 return ch != kEmbeddedCharacter;
1761}
1762
1766
1768 gfx::Point point) const {
1769 // First, scope the search to the node that contains point.
1770 AXPlatformNodeBase* nearest_node =
1772 GetDelegate()->HitTestSync(point.x(), point.y())));
1773
1774 if (!nearest_node)
1775 return nullptr;
1776
1777 AXPlatformNodeBase* parent = nearest_node;
1778 // GetFirstChild does not consider if the parent is a leaf.
1779 AXPlatformNodeBase* current_descendant =
1780 parent->GetChildCount() ? parent->GetFirstChild() : nullptr;
1781 AXPlatformNodeBase* nearest_descendant = nullptr;
1782 float shortest_distance;
1783 while (parent && current_descendant) {
1784 // Manhattan Distance is used to provide faster distance estimates.
1785 float current_distance = current_descendant->GetDelegate()
1788
1789 if (!nearest_descendant || current_distance < shortest_distance) {
1790 shortest_distance = current_distance;
1791 nearest_descendant = current_descendant;
1792 }
1793
1794 // Traverse
1795 AXPlatformNodeBase* next_sibling = current_descendant->GetNextSibling();
1796 if (next_sibling) {
1797 current_descendant = next_sibling;
1798 } else {
1799 // We have gone through all siblings, update nearest and descend if
1800 // possible.
1801 if (nearest_descendant) {
1802 nearest_node = nearest_descendant;
1803 // If the nearest node is a leaf that does not have a child tree, break.
1804 if (!nearest_node->GetChildCount())
1805 break;
1806
1807 parent = nearest_node;
1808 current_descendant = parent->GetFirstChild();
1809
1810 // Reset nearest_descendant to force the nearest node to be a descendant
1811 // of "parent".
1812 nearest_descendant = nullptr;
1813 }
1814 }
1815 }
1816 return nearest_node;
1817}
1818
1820 // For text objects, find the text position nearest to the point.The nearest
1821 // index of a non-text object is implicitly 0. Text fields such as textarea
1822 // have an embedded div inside them that holds all the text,
1823 // GetRangeBoundsRect will correctly handle these nodes
1824 int nearest_index = 0;
1825 const AXCoordinateSystem coordinate_system = AXCoordinateSystem::kScreenDIPs;
1826 const AXClippingBehavior clipping_behavior = AXClippingBehavior::kUnclipped;
1827
1828 // Manhattan Distance is used to provide faster distance estimates.
1829 // get the distance from the point to the bounds of each character.
1830 float shortest_distance = GetDelegate()
1832 0, 1, coordinate_system, clipping_behavior)
1834 for (int i = 1, text_length = GetInnerText().length(); i < text_length; ++i) {
1835 float current_distance =
1836 GetDelegate()
1837 ->GetInnerTextRangeBoundsRect(i, i + 1, coordinate_system,
1838 clipping_behavior)
1840 if (current_distance < shortest_distance) {
1841 shortest_distance = current_distance;
1842 nearest_index = i;
1843 }
1844 }
1845 return nearest_index;
1846}
1847
1849 const AXPlatformNodeBase* target = this;
1850 // The aria-invalid=spelling/grammar need to be exposed as text attributes for
1851 // a range matching the visual underline representing the error.
1852 if (static_cast<ax::mojom::InvalidState>(
1855 target->IsText() && target->GetParent()) {
1856 // Text nodes need to reflect the invalid state of their parent object,
1857 // otherwise spelling and grammar errors communicated through aria-invalid
1858 // won't be reflected in text attributes.
1859 target = static_cast<AXPlatformNodeBase*>(
1860 FromNativeViewAccessible(target->GetParent()));
1861 }
1862
1863 std::string invalid_value("");
1864 // Note: spelling+grammar errors case is disallowed and not supported. It
1865 // could possibly arise with aria-invalid on the ancestor of a spelling error,
1866 // but this is not currently described in any spec and no real-world use cases
1867 // have been found.
1868 switch (static_cast<ax::mojom::InvalidState>(
1872 break;
1874 invalid_value = "true";
1875 break;
1877 if (!target->GetStringAttribute(
1879 // Set the attribute to "true", since we cannot be more specific.
1880 invalid_value = "true";
1881 }
1882 break;
1883 }
1884 }
1885 return invalid_value;
1886}
1887
1889 ui::TextAttributeList attributes;
1890
1891 // We include list markers for now, but there might be other objects that are
1892 // auto generated.
1893 // TODO(nektar): Compute what objects are auto-generated in Blink.
1895 attributes.push_back(std::make_pair("auto-generated", "true"));
1896
1897 int color;
1899 unsigned int alpha = ColorGetA(color);
1900 unsigned int red = ColorGetR(color);
1901 unsigned int green = ColorGetG(color);
1902 unsigned int blue = ColorGetB(color);
1903 // Don't expose default value of pure white.
1904 if (alpha && (red != 255 || green != 255 || blue != 255)) {
1905 std::string color_value = "rgb(" + base::NumberToString(red) + ',' +
1906 base::NumberToString(green) + ',' +
1907 base::NumberToString(blue) + ')';
1908 SanitizeTextAttributeValue(color_value, &color_value);
1909 attributes.push_back(std::make_pair("background-color", color_value));
1910 }
1911 }
1912
1914 unsigned int red = ColorGetR(color);
1915 unsigned int green = ColorGetG(color);
1916 unsigned int blue = ColorGetB(color);
1917 // Don't expose default value of black.
1918 if (red || green || blue) {
1919 std::string color_value = "rgb(" + base::NumberToString(red) + ',' +
1920 base::NumberToString(green) + ',' +
1921 base::NumberToString(blue) + ')';
1922 SanitizeTextAttributeValue(color_value, &color_value);
1923 attributes.push_back(std::make_pair("color", color_value));
1924 }
1925 }
1926
1927 // First try to get the inherited font family name from the delegate. If we
1928 // cannot find any name, fall back to looking the hierarchy of this node's
1929 // AXNodeData instead.
1930 std::string font_family(GetDelegate()->GetInheritedFontFamilyName());
1931 if (font_family.empty()) {
1932 font_family =
1934 }
1935
1936 // Attribute has no default value.
1937 if (!font_family.empty()) {
1938 SanitizeTextAttributeValue(font_family, &font_family);
1939 attributes.push_back(std::make_pair("font-family", font_family));
1940 }
1941
1942 std::optional<float> font_size_in_points = GetFontSizeInPoints();
1943 // Attribute has no default value.
1944 if (font_size_in_points) {
1945 attributes.push_back(std::make_pair(
1946 "font-size", base::NumberToString(*font_size_in_points) + "pt"));
1947 }
1948
1949 // TODO(nektar): Add Blink support for the following attributes:
1950 // text-line-through-mode, text-line-through-width, text-outline:false,
1951 // text-position:baseline, text-shadow:none, text-underline-mode:continuous.
1952
1954 if (text_style) {
1955 if (GetData().HasTextStyle(ax::mojom::TextStyle::kBold))
1956 attributes.push_back(std::make_pair("font-weight", "bold"));
1957 if (GetData().HasTextStyle(ax::mojom::TextStyle::kItalic))
1958 attributes.push_back(std::make_pair("font-style", "italic"));
1959 if (GetData().HasTextStyle(ax::mojom::TextStyle::kLineThrough)) {
1960 // TODO(nektar): Figure out a more specific value.
1961 attributes.push_back(std::make_pair("text-line-through-style", "solid"));
1962 }
1963 if (GetData().HasTextStyle(ax::mojom::TextStyle::kUnderline)) {
1964 // TODO(nektar): Figure out a more specific value.
1965 attributes.push_back(std::make_pair("text-underline-style", "solid"));
1966 }
1967 }
1968
1969 // Screen readers look at the text attributes to determine if something is
1970 // misspelled, so we need to propagate any spelling attributes from immediate
1971 // parents of text-only objects.
1972 std::string invalid_value = GetInvalidValue();
1973 if (!invalid_value.empty())
1974 attributes.push_back(std::make_pair("invalid", invalid_value));
1975
1976 std::string language = GetDelegate()->GetLanguage();
1977 if (!language.empty()) {
1978 SanitizeTextAttributeValue(language, &language);
1979 attributes.push_back(std::make_pair("language", language));
1980 }
1981
1982 auto text_direction = static_cast<ax::mojom::WritingDirection>(
1984 switch (text_direction) {
1986 break;
1988 attributes.push_back(std::make_pair("writing-mode", "lr"));
1989 break;
1991 attributes.push_back(std::make_pair("writing-mode", "rl"));
1992 break;
1994 attributes.push_back(std::make_pair("writing-mode", "tb"));
1995 break;
1997 // Not listed in the IA2 Spec.
1998 attributes.push_back(std::make_pair("writing-mode", "bt"));
1999 break;
2000 }
2001
2002 auto text_position = static_cast<ax::mojom::TextPosition>(
2004 switch (text_position) {
2006 break;
2008 attributes.push_back(std::make_pair("text-position", "sub"));
2009 break;
2011 attributes.push_back(std::make_pair("text-position", "super"));
2012 break;
2013 }
2014
2015 return attributes;
2016}
2017
2019 int max_items = GetMaxSelectableItems();
2020 if (!max_items)
2021 return 0;
2022 return GetSelectedItems(max_items);
2023}
2024
2026 int selected_index) const {
2027 BASE_DCHECK(selected_index >= 0);
2028 int max_items = GetMaxSelectableItems();
2029 if (max_items == 0)
2030 return nullptr;
2031 if (selected_index >= max_items)
2032 return nullptr;
2033
2034 std::vector<AXPlatformNodeBase*> selected_children;
2035 int requested_count = selected_index + 1;
2036 int returned_count = GetSelectedItems(requested_count, &selected_children);
2037
2038 if (returned_count <= selected_index)
2039 return nullptr;
2040
2041 BASE_DCHECK(!selected_children.empty());
2042 BASE_DCHECK(selected_index < static_cast<int>(selected_children.size()));
2043 return selected_children[selected_index];
2044}
2045
2047 int max_items,
2048 std::vector<AXPlatformNodeBase*>* out_selected_items) const {
2049 int selected_count = 0;
2050 for (auto child_iter = AXPlatformNodeChildrenBegin();
2051 child_iter != AXPlatformNodeChildrenEnd() && selected_count < max_items;
2052 ++child_iter) {
2053 if (!IsItemLike(child_iter->GetData().role)) {
2054 selected_count += child_iter->GetSelectedItems(max_items - selected_count,
2055 out_selected_items);
2056 } else if (child_iter->GetBoolAttribute(
2058 selected_count++;
2059 if (out_selected_items)
2060 out_selected_items->emplace_back(child_iter.get());
2061 }
2062 }
2063 return selected_count;
2064}
2065
2067 std::string* output) const {
2068 BASE_DCHECK(output);
2069}
2070
2071std::string AXPlatformNodeBase::ComputeDetailsRoles() const {
2072 const std::vector<int32_t>& details_ids =
2074 if (details_ids.empty())
2075 return std::string();
2076
2077 std::set<std::string> details_roles_set;
2078
2079 for (int id : details_ids) {
2080 AXPlatformNodeBase* detail_object =
2081 static_cast<AXPlatformNodeBase*>(delegate_->GetFromNodeID(id));
2082 if (!detail_object)
2083 continue;
2084 switch (detail_object->GetData().role) {
2086 details_roles_set.insert("comment");
2087 break;
2089 details_roles_set.insert("definition");
2090 break;
2092 details_roles_set.insert("doc-endnote");
2093 break;
2095 details_roles_set.insert("doc-footnote");
2096 break;
2099 // These should still report comment if there are comments inside them.
2100 constexpr int kMaxChildrenToCheck = 8;
2101 constexpr int kMaxDepthToCheck = 4;
2102 if (FindDescendantRoleWithMaxDepth(
2103 detail_object, ax::mojom::Role::kComment, kMaxDepthToCheck,
2104 kMaxChildrenToCheck)) {
2105 details_roles_set.insert("comment");
2106 break;
2107 }
2108 // FALLTHROUGH;
2109 }
2110 default:
2111 // Use * to indicate some other role.
2112 details_roles_set.insert("*");
2113 break;
2114 }
2115 }
2116
2117 // Create space delimited list of types. The set will not be large, as there
2118 // are not very many possible types.
2119 std::vector<std::string> details_roles_vector(details_roles_set.begin(),
2120 details_roles_set.end());
2121 return base::JoinString(details_roles_vector, " ");
2122}
2123
2125 if (!GetData().HasState(ax::mojom::State::kFocusable))
2126 return 0;
2127
2128 if (IsLeaf())
2129 return 0;
2130
2132 return 0;
2133
2134 int max_items = 1;
2136 max_items = std::numeric_limits<int>::max();
2137 return max_items;
2138}
2139
2140} // namespace ui
static const int points[]
SkColor4f color
SI F table(const skcms_Curve *curve, F v)
ax::mojom::Event event_type
constexpr int y() const
Definition point.h:49
constexpr int x() const
Definition point.h:48
constexpr int height() const
Definition rect.h:79
int ManhattanDistanceToPoint(const Point &point) const
Definition rect.cc:276
constexpr int right() const
Definition rect.h:96
constexpr int bottom() const
Definition rect.h:97
constexpr int y() const
Definition rect.h:69
Vector2d OffsetFromOrigin() const
Definition rect.h:108
constexpr int x() const
Definition rect.h:62
constexpr int width() const
Definition rect.h:76
NodeType * get() const
Definition ax_node.h:597
std::u16string GetInheritedString16Attribute(ax::mojom::StringAttribute attribute) const
~AXPlatformNodeBase() override
std::string GetInvalidValue() const
AXPlatformNodeBase * GetLastChild() const
void GetSelectionOffsetsFromTree(const AXTree::Selection *selection, int *selection_start, int *selection_end)
static AXPlatformNode * GetFromUniqueId(int32_t unique_id)
AXPlatformNodeBase * GetSelectionContainer() const
AXPlatformNodeBase * GetSelectedItem(int selected_index) const
gfx::NativeViewAccessible GetNativeViewAccessible() override
bool HasIntListAttribute(ax::mojom::IntListAttribute attribute) const
bool GetString16Attribute(ax::mojom::StringAttribute attribute, std::u16string *value) const
std::optional< int > GetPosInSet() const
void NotifyAccessibilityEvent(ax::mojom::Event event_type) override
AXPlatformNodeDelegate * GetDelegate() const override
std::u16string GetRangeValueText() const
std::u16string GetRoleDescription() const
bool IsDescendant(AXPlatformNodeBase *descendant)
int32_t GetHypertextOffsetFromChild(AXPlatformNodeBase *child)
bool HasCaret(const AXTree::Selection *unignored_selection=nullptr)
gfx::NativeViewAccessible GetFocus()
static const char16_t kEmbeddedCharacter
std::optional< int > GetTableColumnSpan() const
gfx::NativeViewAccessible ChildAtIndex(int index) const
AXPlatformNodeChildIterator AXPlatformNodeChildrenBegin() const
AXPlatformNodeBase * GetHyperlinkFromHypertextOffset(int offset)
std::optional< int > GetTableRowSpan() const
std::optional< float > GetFontSizeInPoints() const
std::optional< int > GetTableCellIndex() const
std::u16string GetInnerText() const
AXPlatformNodeChildIterator AXPlatformNodeChildrenEnd() const
virtual std::u16string GetValue() const
virtual bool IsPlatformCheckable() const
int32_t GetHypertextOffsetFromDescendant(AXPlatformNodeBase *descendant)
std::u16string GetHypertext() const
const std::vector< int32_t > & GetIntListAttribute(ax::mojom::IntListAttribute attribute) const
virtual void Init(AXPlatformNodeDelegate *delegate)
std::optional< int > GetTableRowCount() const
std::vector< std::u16string > PlatformAttributeList
const std::string & GetStringAttribute(ax::mojom::StringAttribute attribute) const
static AXPlatformNodeBase * FromNativeViewAccessible(gfx::NativeViewAccessible accessible)
float GetFloatAttribute(ax::mojom::FloatAttribute attr) const
ui::AXNode::ChildIteratorBase< AXPlatformNodeBase, &AXPlatformNodeBase::GetNextSibling, &AXPlatformNodeBase::GetPreviousSibling, &AXPlatformNodeBase::GetFirstChild, &AXPlatformNodeBase::GetLastChild > AXPlatformNodeChildIterator
int GetSelectionFocus(const AXTree::Selection *selection)
AXPlatformNodeBase * GetPreviousSibling() const
virtual void SanitizeTextAttributeValue(const std::string &input, std::string *output) const
bool SetHypertextSelection(int start_offset, int end_offset)
AXPlatformNodeBase * NearestLeafToPoint(gfx::Point point) const
std::optional< int > GetTableAriaRowCount() const
std::optional< int > GetTableRow() const
static void SanitizeStringAttribute(const std::string &input, std::string *output)
bool HasBoolAttribute(ax::mojom::BoolAttribute attr) const
void AddAttributeToList(const ax::mojom::StringAttribute attribute, const char *name, PlatformAttributeList *attributes)
AXPlatformNodeBase * GetTableCell(int index) const
void GetSelectionOffsets(int *selection_start, int *selection_end)
bool HasStringAttribute(ax::mojom::StringAttribute attribute) const
std::optional< int > GetTableAriaColumnCount() const
int GetSelectedItems(int max_items, std::vector< AXPlatformNodeBase * > *out_selected_items=nullptr) const
bool IsDescendantOf(AXPlatformNode *ancestor) const override
int32_t GetHypertextOffsetFromHyperlinkIndex(int32_t hyperlink_index)
int NearestTextIndexToPoint(gfx::Point point)
const AXNodeData & GetData() const
AXPlatformNodeDelegate * delegate_
AXPlatformNodeBase * GetFirstChild() const
std::optional< int > GetSetSize() const
ui::TextAttributeList ComputeTextAttributes() const
int32_t GetHyperlinkIndexFromChild(AXPlatformNodeBase *child)
static size_t GetInstanceCountForTesting()
std::optional< int > GetTableColumn() const
bool HasFloatAttribute(ax::mojom::FloatAttribute attr) const
std::optional< int > CompareTo(AXPlatformNodeBase &other)
bool ScrollToNode(ScrollType scroll_type)
gfx::NativeViewAccessible GetParent() const
AXPlatformNodeBase * GetTableCaption() const
bool HasInheritedStringAttribute(ax::mojom::StringAttribute attribute) const
AXPlatformNodeBase * GetNextSibling() const
bool GetBoolAttribute(ax::mojom::BoolAttribute attr) const
AXPlatformNodeBase * GetTable() const
std::optional< int > GetTableColumnCount() const
std::u16string GetNameAsString16() const
int GetHypertextOffsetFromEndpoint(AXPlatformNodeBase *endpoint_object, int endpoint_offset)
std::stack< gfx::NativeViewAccessible > GetAncestors()
void ComputeAttributes(PlatformAttributeList *attributes)
bool HasIntAttribute(ax::mojom::IntAttribute attribute) const
bool IsSameHypertextCharacter(const AXHypertext &old_hypertext, size_t old_char_index, size_t new_char_index)
std::u16string GetRoleDescriptionFromImageAnnotationStatusOrFromAttribute() const
int GetSelectionAnchor(const AXTree::Selection *selection)
const std::string & GetInheritedStringAttribute(ax::mojom::StringAttribute attribute) const
int GetIntAttribute(ax::mojom::IntAttribute attribute) const
virtual std::optional< int > GetIndexInParent()
virtual gfx::NativeViewAccessible GetFocus()=0
virtual gfx::NativeViewAccessible GetLastChild()=0
virtual std::optional< int > GetTableCellRowSpan() const =0
virtual std::u16string GetInnerText() const =0
virtual std::optional< int > GetTableRowCount() const =0
virtual std::optional< int > GetTableCellIndex() const =0
virtual std::optional< int > GetTableCellAriaColIndex() const =0
virtual int GetIndexInParent()=0
virtual gfx::NativeViewAccessible GetNextSibling()=0
virtual std::string GetName() const =0
virtual std::optional< int > GetSetSize() const =0
virtual std::optional< int > GetTableCellAriaRowIndex() const =0
virtual const AXTree::Selection GetUnignoredSelection() const =0
virtual gfx::NativeViewAccessible GetPreviousSibling()=0
virtual std::u16string GetLocalizedStringForRoleDescription() const =0
virtual std::u16string GetLocalizedRoleDescriptionForUnlabeledImage() const =0
virtual AXPlatformNode * GetFromNodeID(int32_t id)=0
virtual bool AccessibilityPerformAction(const AXActionData &data)=0
virtual std::optional< int > GetTableCellRowIndex() const =0
virtual const AXNodeData & GetData() const =0
virtual std::optional< int > GetTableColCount() const =0
virtual bool IsLeaf() const =0
virtual std::optional< int > GetPosInSet() const =0
virtual std::string GetLanguage() const =0
virtual bool IsTableCellOrHeader() const =0
virtual std::optional< int > GetTableRowRowIndex() const =0
virtual bool IsTableRow() const =0
virtual std::optional< int > GetTableAriaColCount() const =0
virtual std::optional< int > GetTableCellColIndex() const =0
virtual gfx::Rect GetClippedScreenBoundsRect(AXOffscreenResult *offscreen_result=nullptr) const =0
virtual std::optional< int > GetTableCellColSpan() const =0
virtual gfx::NativeViewAccessible GetFirstChild()=0
virtual bool IsText() const =0
virtual bool HasModalDialog() const =0
virtual const AXTreeData & GetTreeData() const =0
virtual gfx::NativeViewAccessible ChildAtIndex(int index)=0
virtual bool IsChildOfLeaf() const =0
virtual std::optional< int > GetTableAriaRowCount() const =0
virtual gfx::Rect GetInnerTextRangeBoundsRect(const int start_offset, const int end_offset, const AXCoordinateSystem coordinate_system, const AXClippingBehavior clipping_behavior, AXOffscreenResult *offscreen_result=nullptr) const =0
virtual int GetChildCount() const =0
virtual bool SetHypertextSelection(int start_offset, int end_offset)=0
virtual gfx::NativeViewAccessible GetParent()=0
static AXPlatformNode * FromNativeViewAccessible(gfx::NativeViewAccessible accessible)
virtual void Destroy()
int32_t GetUniqueId() const
#define ColorGetA(color)
Definition color_utils.h:10
#define ColorGetG(color)
Definition color_utils.h:18
#define ColorGetR(color)
Definition color_utils.h:14
#define ColorGetB(color)
Definition color_utils.h:22
uint8_t value
uint32_t * target
const char * name
Definition fuchsia.cc:50
size_t length
std::u16string text
std::string UTF16ToUTF8(std::u16string src)
void ReplaceChars(std::string in, std::string from, std::string to, std::string *out)
const std::string & EmptyString()
std::u16string UTF8ToUTF16(std::string src)
std::string JoinString(std::vector< std::string > tokens, std::string delimiter)
std::string NumberToString(int32_t number)
std::u16string NumberToString16(float number)
Rect ToEnclosingRect(const RectF &r)
UnimplementedNativeViewAccessible * NativeViewAccessible
UniqueIdMap g_unique_id_map
bool IsRangeValueSupported(const ax::mojom::Role role)
bool IsContainerWithSelectableChildren(const ax::mojom::Role role)
bool IsCellOrTableHeader(const ax::mojom::Role role)
bool IsTableRow(ax::mojom::Role role)
bool IsItemLike(const ax::mojom::Role role)
std::vector< TextAttribute > TextAttributeList
bool IsTableLike(const ax::mojom::Role role)
bool IsTableHeader(ax::mojom::Role role)
std::optional< int32_t > ComputeAttribute(const ui::AXPlatformNodeDelegate *delegate, ax::mojom::IntAttribute attribute)
std::unordered_map< int32_t, AXPlatformNode * > UniqueIdMap
bool IsDocument(const ax::mojom::Role role)
Point offset
ax::mojom::ScrollBehavior scroll_behavior
ax::mojom::ScrollAlignment vertical_scroll_alignment
gfx::Rect target_rect
ax::mojom::Action action
ax::mojom::ScrollAlignment horizontal_scroll_alignment
std::vector< int32_t > hyperlinks
std::u16string hypertext
std::map< int32_t, int32_t > hyperlink_offset_to_index
AXHypertext & operator=(const AXHypertext &other)
bool IsPlainTextField() const
bool HasIntAttribute(ax::mojom::IntAttribute attribute) const
bool HasCheckedState() const
bool HasIntListAttribute(ax::mojom::IntListAttribute attribute) const
bool HasFloatAttribute(ax::mojom::FloatAttribute attribute) const
bool HasStringAttribute(ax::mojom::StringAttribute attribute) const
bool IsTextField() const
std::string DropeffectBitfieldToString() const
bool IsRichTextField() const
std::vector< std::pair< std::string, std::string > > html_attributes
bool GetBoolAttribute(ax::mojom::BoolAttribute attribute) const
ax::mojom::Role role
const std::vector< int32_t > & GetIntListAttribute(ax::mojom::IntListAttribute attribute) const
float GetFloatAttribute(ax::mojom::FloatAttribute attribute) const
int GetIntAttribute(ax::mojom::IntAttribute attribute) const
bool IsInvisibleOrIgnored() const
bool HasBoolAttribute(ax::mojom::BoolAttribute attribute) const
const std::string & GetStringAttribute(ax::mojom::StringAttribute attribute) const
bool GetString16Attribute(ax::mojom::StringAttribute attribute, std::u16string *value) const
std::string url
const uintptr_t id
#define BASE_DCHECK(condition)
Definition logging.h:63
#define BASE_UNREACHABLE()
Definition logging.h:69
#define BASE_LOG()
Definition logging.h:54