7
7
#include < functional>
8
8
#include < utility>
9
9
10
+ #include " flutter/third_party/accessibility/ax/ax_tree_manager_map.h"
10
11
#include " flutter/third_party/accessibility/ax/ax_tree_update.h"
11
12
#include " flutter/third_party/accessibility/base/logging.h"
12
13
@@ -19,24 +20,30 @@ constexpr int kHasScrollingAction =
19
20
FlutterSemanticsAction::kFlutterSemanticsActionScrollDown ;
20
21
21
22
// AccessibilityBridge
22
- AccessibilityBridge::AccessibilityBridge () {
23
- event_generator_.SetTree (&tree_);
24
- tree_.AddObserver (static_cast <ui::AXTreeObserver*>(this ));
23
+ AccessibilityBridge::AccessibilityBridge ()
24
+ : tree_(std::make_unique<ui::AXTree>()) {
25
+ event_generator_.SetTree (tree_.get ());
26
+ tree_->AddObserver (static_cast <ui::AXTreeObserver*>(this ));
27
+ ui::AXTreeData data = tree_->data ();
28
+ data.tree_id = ui::AXTreeID::CreateNewAXTreeID ();
29
+ tree_->UpdateData (data);
30
+ ui::AXTreeManagerMap::GetInstance ().AddTreeManager (tree_->GetAXTreeID (),
31
+ this );
25
32
}
26
33
27
34
AccessibilityBridge::~AccessibilityBridge () {
28
35
event_generator_.ReleaseTree ();
29
- tree_. RemoveObserver (static_cast <ui::AXTreeObserver*>(this ));
36
+ tree_-> RemoveObserver (static_cast <ui::AXTreeObserver*>(this ));
30
37
}
31
38
32
39
void AccessibilityBridge::AddFlutterSemanticsNodeUpdate (
33
- const FlutterSemanticsNode* node) {
34
- pending_semantics_node_updates_[node-> id ] = FromFlutterSemanticsNode (node);
40
+ const FlutterSemanticsNode2& node) {
41
+ pending_semantics_node_updates_[node. id ] = FromFlutterSemanticsNode (node);
35
42
}
36
43
37
44
void AccessibilityBridge::AddFlutterSemanticsCustomActionUpdate (
38
- const FlutterSemanticsCustomAction* action) {
39
- pending_semantics_custom_action_updates_[action-> id ] =
45
+ const FlutterSemanticsCustomAction2& action) {
46
+ pending_semantics_custom_action_updates_[action. id ] =
40
47
FromFlutterSemanticsCustomAction (action);
41
48
}
42
49
@@ -51,9 +58,9 @@ void AccessibilityBridge::CommitUpdates() {
51
58
std::optional<ui::AXTreeUpdate> remove_reparented =
52
59
CreateRemoveReparentedNodesUpdate ();
53
60
if (remove_reparented.has_value ()) {
54
- tree_. Unserialize (remove_reparented.value ());
61
+ tree_-> Unserialize (remove_reparented.value ());
55
62
56
- std::string error = tree_. error ();
63
+ std::string error = tree_-> error ();
57
64
if (!error.empty ()) {
58
65
FML_LOG (ERROR) << " Failed to update ui::AXTree, error: " << error;
59
66
assert (false );
@@ -63,15 +70,16 @@ void AccessibilityBridge::CommitUpdates() {
63
70
64
71
// Second, apply the pending node updates. This also moves reparented nodes to
65
72
// their new parents if needed.
66
- ui::AXTreeUpdate update{.tree_data = tree_. data ()};
73
+ ui::AXTreeUpdate update{.tree_data = tree_-> data ()};
67
74
68
75
// Figure out update order, ui::AXTree only accepts update in tree order,
69
76
// where parent node must come before the child node in
70
77
// ui::AXTreeUpdate.nodes. We start with picking a random node and turn the
71
78
// entire subtree into a list. We pick another node from the remaining update,
72
79
// and keep doing so until the update map is empty. We then concatenate the
73
80
// lists in the reversed order, this guarantees parent updates always come
74
- // before child updates.
81
+ // before child updates. If the root is in the update, it is guaranteed to
82
+ // be the first node of the last list.
75
83
std::vector<std::vector<SemanticsNode>> results;
76
84
while (!pending_semantics_node_updates_.empty ()) {
77
85
auto begin = pending_semantics_node_updates_.begin ();
@@ -88,11 +96,20 @@ void AccessibilityBridge::CommitUpdates() {
88
96
}
89
97
}
90
98
91
- tree_.Unserialize (update);
99
+ // The first update must set the tree's root, which is guaranteed to be the
100
+ // last list's first node. A tree's root node never changes, though it can be
101
+ // modified.
102
+ if (!results.empty () && GetRootAsAXNode ()->id () == ui::AXNode::kInvalidAXID ) {
103
+ FML_DCHECK (!results.back ().empty ());
104
+
105
+ update.root_id = results.back ().front ().id ;
106
+ }
107
+
108
+ tree_->Unserialize (update);
92
109
pending_semantics_node_updates_.clear ();
93
110
pending_semantics_custom_action_updates_.clear ();
94
111
95
- std::string error = tree_. error ();
112
+ std::string error = tree_-> error ();
96
113
if (!error.empty ()) {
97
114
FML_LOG (ERROR) << " Failed to update ui::AXTree, error: " << error;
98
115
return ;
@@ -122,7 +139,7 @@ AccessibilityBridge::GetFlutterPlatformNodeDelegateFromID(
122
139
}
123
140
124
141
const ui::AXTreeData& AccessibilityBridge::GetAXTreeData () const {
125
- return tree_. data ();
142
+ return tree_-> data ();
126
143
}
127
144
128
145
const std::vector<ui::AXEventGenerator::TargetedEvent>
@@ -201,7 +218,7 @@ AccessibilityBridge::CreateRemoveReparentedNodesUpdate() {
201
218
for (auto node_update : pending_semantics_node_updates_) {
202
219
for (int32_t child_id : node_update.second .children_in_traversal_order ) {
203
220
// Skip nodes that don't exist or have a parent in the current tree.
204
- ui::AXNode* child = tree_. GetFromId (child_id);
221
+ ui::AXNode* child = tree_-> GetFromId (child_id);
205
222
if (!child) {
206
223
continue ;
207
224
}
@@ -222,7 +239,7 @@ AccessibilityBridge::CreateRemoveReparentedNodesUpdate() {
222
239
// Create an update to remove the child from its previous parent.
223
240
int32_t parent_id = child->parent ()->id ();
224
241
if (updates.find (parent_id) == updates.end ()) {
225
- updates[parent_id] = tree_. GetFromId (parent_id)->data ();
242
+ updates[parent_id] = tree_-> GetFromId (parent_id)->data ();
226
243
}
227
244
228
245
ui::AXNodeData* parent = &updates[parent_id];
@@ -239,7 +256,7 @@ AccessibilityBridge::CreateRemoveReparentedNodesUpdate() {
239
256
}
240
257
241
258
ui::AXTreeUpdate update{
242
- .tree_data = tree_. data (),
259
+ .tree_data = tree_-> data (),
243
260
.nodes = std::vector<ui::AXNodeData>(),
244
261
};
245
262
@@ -428,6 +445,12 @@ void AccessibilityBridge::SetBooleanAttributesFromFlutterUpdate(
428
445
ax::mojom::BoolAttribute::kEditableRoot ,
429
446
flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField &&
430
447
(flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly ) == 0 );
448
+ // Mark nodes as line breaking so that screen readers don't
449
+ // merge all consecutive objects into one.
450
+ // TODO(schectman): When should a node have this attribute set?
451
+ // https://github.com/flutter/flutter/issues/118184
452
+ node_data.AddBoolAttribute (ax::mojom::BoolAttribute::kIsLineBreakingObject ,
453
+ true );
431
454
}
432
455
433
456
void AccessibilityBridge::SetIntAttributesFromFlutterUpdate (
@@ -520,11 +543,12 @@ void AccessibilityBridge::SetTooltipFromFlutterUpdate(
520
543
void AccessibilityBridge::SetTreeData (const SemanticsNode& node,
521
544
ui::AXTreeUpdate& tree_update) {
522
545
FlutterSemanticsFlag flags = node.flags ;
523
- // Set selection if:
546
+ // Set selection of the focused node if:
524
547
// 1. this text field has a valid selection
525
548
// 2. this text field doesn't have a valid selection but had selection stored
526
549
// in the tree.
527
- if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField ) {
550
+ if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField &&
551
+ flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsFocused ) {
528
552
if (node.text_selection_base != -1 ) {
529
553
tree_update.tree_data .sel_anchor_object_id = node.id ;
530
554
tree_update.tree_data .sel_anchor_offset = node.text_selection_base ;
@@ -554,66 +578,66 @@ void AccessibilityBridge::SetTreeData(const SemanticsNode& node,
554
578
555
579
AccessibilityBridge::SemanticsNode
556
580
AccessibilityBridge::FromFlutterSemanticsNode (
557
- const FlutterSemanticsNode* flutter_node) {
581
+ const FlutterSemanticsNode2& flutter_node) {
558
582
SemanticsNode result;
559
- result.id = flutter_node-> id ;
560
- result.flags = flutter_node-> flags ;
561
- result.actions = flutter_node-> actions ;
562
- result.text_selection_base = flutter_node-> text_selection_base ;
563
- result.text_selection_extent = flutter_node-> text_selection_extent ;
564
- result.scroll_child_count = flutter_node-> scroll_child_count ;
565
- result.scroll_index = flutter_node-> scroll_index ;
566
- result.scroll_position = flutter_node-> scroll_position ;
567
- result.scroll_extent_max = flutter_node-> scroll_extent_max ;
568
- result.scroll_extent_min = flutter_node-> scroll_extent_min ;
569
- result.elevation = flutter_node-> elevation ;
570
- result.thickness = flutter_node-> thickness ;
571
- if (flutter_node-> label ) {
572
- result.label = std::string (flutter_node-> label );
573
- }
574
- if (flutter_node-> hint ) {
575
- result.hint = std::string (flutter_node-> hint );
576
- }
577
- if (flutter_node-> value ) {
578
- result.value = std::string (flutter_node-> value );
579
- }
580
- if (flutter_node-> increased_value ) {
581
- result.increased_value = std::string (flutter_node-> increased_value );
582
- }
583
- if (flutter_node-> decreased_value ) {
584
- result.decreased_value = std::string (flutter_node-> decreased_value );
585
- }
586
- if (flutter_node-> tooltip ) {
587
- result.tooltip = std::string (flutter_node-> tooltip );
588
- }
589
- result.text_direction = flutter_node-> text_direction ;
590
- result.rect = flutter_node-> rect ;
591
- result.transform = flutter_node-> transform ;
592
- if (flutter_node-> child_count > 0 ) {
583
+ result.id = flutter_node. id ;
584
+ result.flags = flutter_node. flags ;
585
+ result.actions = flutter_node. actions ;
586
+ result.text_selection_base = flutter_node. text_selection_base ;
587
+ result.text_selection_extent = flutter_node. text_selection_extent ;
588
+ result.scroll_child_count = flutter_node. scroll_child_count ;
589
+ result.scroll_index = flutter_node. scroll_index ;
590
+ result.scroll_position = flutter_node. scroll_position ;
591
+ result.scroll_extent_max = flutter_node. scroll_extent_max ;
592
+ result.scroll_extent_min = flutter_node. scroll_extent_min ;
593
+ result.elevation = flutter_node. elevation ;
594
+ result.thickness = flutter_node. thickness ;
595
+ if (flutter_node. label ) {
596
+ result.label = std::string (flutter_node. label );
597
+ }
598
+ if (flutter_node. hint ) {
599
+ result.hint = std::string (flutter_node. hint );
600
+ }
601
+ if (flutter_node. value ) {
602
+ result.value = std::string (flutter_node. value );
603
+ }
604
+ if (flutter_node. increased_value ) {
605
+ result.increased_value = std::string (flutter_node. increased_value );
606
+ }
607
+ if (flutter_node. decreased_value ) {
608
+ result.decreased_value = std::string (flutter_node. decreased_value );
609
+ }
610
+ if (flutter_node. tooltip ) {
611
+ result.tooltip = std::string (flutter_node. tooltip );
612
+ }
613
+ result.text_direction = flutter_node. text_direction ;
614
+ result.rect = flutter_node. rect ;
615
+ result.transform = flutter_node. transform ;
616
+ if (flutter_node. child_count > 0 ) {
593
617
result.children_in_traversal_order = std::vector<int32_t >(
594
- flutter_node-> children_in_traversal_order ,
595
- flutter_node-> children_in_traversal_order + flutter_node-> child_count );
618
+ flutter_node. children_in_traversal_order ,
619
+ flutter_node. children_in_traversal_order + flutter_node. child_count );
596
620
}
597
- if (flutter_node-> custom_accessibility_actions_count > 0 ) {
621
+ if (flutter_node. custom_accessibility_actions_count > 0 ) {
598
622
result.custom_accessibility_actions = std::vector<int32_t >(
599
- flutter_node-> custom_accessibility_actions ,
600
- flutter_node-> custom_accessibility_actions +
601
- flutter_node-> custom_accessibility_actions_count );
623
+ flutter_node. custom_accessibility_actions ,
624
+ flutter_node. custom_accessibility_actions +
625
+ flutter_node. custom_accessibility_actions_count );
602
626
}
603
627
return result;
604
628
}
605
629
606
630
AccessibilityBridge::SemanticsCustomAction
607
631
AccessibilityBridge::FromFlutterSemanticsCustomAction (
608
- const FlutterSemanticsCustomAction* flutter_custom_action) {
632
+ const FlutterSemanticsCustomAction2& flutter_custom_action) {
609
633
SemanticsCustomAction result;
610
- result.id = flutter_custom_action-> id ;
611
- result.override_action = flutter_custom_action-> override_action ;
612
- if (flutter_custom_action-> label ) {
613
- result.label = std::string (flutter_custom_action-> label );
634
+ result.id = flutter_custom_action. id ;
635
+ result.override_action = flutter_custom_action. override_action ;
636
+ if (flutter_custom_action. label ) {
637
+ result.label = std::string (flutter_custom_action. label );
614
638
}
615
- if (flutter_custom_action-> hint ) {
616
- result.hint = std::string (flutter_custom_action-> hint );
639
+ if (flutter_custom_action. hint ) {
640
+ result.hint = std::string (flutter_custom_action. hint );
617
641
}
618
642
return result;
619
643
}
@@ -649,8 +673,60 @@ gfx::NativeViewAccessible AccessibilityBridge::GetNativeAccessibleFromId(
649
673
gfx::RectF AccessibilityBridge::RelativeToGlobalBounds (const ui::AXNode* node,
650
674
bool & offscreen,
651
675
bool clip_bounds) {
652
- return tree_.RelativeToTreeBounds (node, gfx::RectF (), &offscreen,
653
- clip_bounds);
676
+ return tree_->RelativeToTreeBounds (node, gfx::RectF (), &offscreen,
677
+ clip_bounds);
678
+ }
679
+
680
+ ui::AXNode* AccessibilityBridge::GetNodeFromTree (
681
+ ui::AXTreeID tree_id,
682
+ ui::AXNode::AXID node_id) const {
683
+ return GetNodeFromTree (node_id);
684
+ }
685
+
686
+ ui::AXNode* AccessibilityBridge::GetNodeFromTree (
687
+ ui::AXNode::AXID node_id) const {
688
+ return tree_->GetFromId (node_id);
689
+ }
690
+
691
+ ui::AXTreeID AccessibilityBridge::GetTreeID () const {
692
+ return tree_->GetAXTreeID ();
693
+ }
694
+
695
+ ui::AXTreeID AccessibilityBridge::GetParentTreeID () const {
696
+ return ui::AXTreeIDUnknown ();
697
+ }
698
+
699
+ ui::AXNode* AccessibilityBridge::GetRootAsAXNode () const {
700
+ return tree_->root ();
701
+ }
702
+
703
+ ui::AXNode* AccessibilityBridge::GetParentNodeFromParentTreeAsAXNode () const {
704
+ return nullptr ;
705
+ }
706
+
707
+ ui::AXTree* AccessibilityBridge::GetTree () const {
708
+ return tree_.get ();
709
+ }
710
+
711
+ ui::AXPlatformNode* AccessibilityBridge::GetPlatformNodeFromTree (
712
+ const ui::AXNode::AXID node_id) const {
713
+ auto platform_delegate_weak = GetFlutterPlatformNodeDelegateFromID (node_id);
714
+ auto platform_delegate = platform_delegate_weak.lock ();
715
+ if (!platform_delegate) {
716
+ return nullptr ;
717
+ }
718
+ return platform_delegate->GetPlatformNode ();
719
+ }
720
+
721
+ ui::AXPlatformNode* AccessibilityBridge::GetPlatformNodeFromTree (
722
+ const ui::AXNode& node) const {
723
+ return GetPlatformNodeFromTree (node.id ());
724
+ }
725
+
726
+ ui::AXPlatformNodeDelegate* AccessibilityBridge::RootDelegate () const {
727
+ return GetFlutterPlatformNodeDelegateFromID (GetRootAsAXNode ()->id ())
728
+ .lock ()
729
+ .get ();
654
730
}
655
731
656
732
} // namespace flutter
0 commit comments