@@ -19,9 +19,7 @@ constexpr int kHasScrollingAction =
1919 FlutterSemanticsAction::kFlutterSemanticsActionScrollDown ;
2020
2121// AccessibilityBridge
22- AccessibilityBridge::AccessibilityBridge (
23- std::unique_ptr<AccessibilityBridgeDelegate> delegate)
24- : delegate_(std::move(delegate)) {
22+ AccessibilityBridge::AccessibilityBridge () {
2523 event_generator_.SetTree (&tree_);
2624 tree_.AddObserver (static_cast <ui::AXTreeObserver*>(this ));
2725}
@@ -43,7 +41,30 @@ void AccessibilityBridge::AddFlutterSemanticsCustomActionUpdate(
4341}
4442
4543void AccessibilityBridge::CommitUpdates () {
44+ // AXTree cannot move a node in a single update.
45+ // This must be split across two updates:
46+ //
47+ // * Update 1: remove nodes from their old parents.
48+ // * Update 2: re-add nodes (including their children) to their new parents.
49+ //
50+ // First, start by removing nodes if necessary.
51+ std::optional<ui::AXTreeUpdate> remove_reparented =
52+ CreateRemoveReparentedNodesUpdate ();
53+ if (remove_reparented.has_value ()) {
54+ tree_.Unserialize (remove_reparented.value ());
55+
56+ std::string error = tree_.error ();
57+ if (!error.empty ()) {
58+ FML_LOG (ERROR) << " Failed to update ui::AXTree, error: " << error;
59+ assert (false );
60+ return ;
61+ }
62+ }
63+
64+ // Second, apply the pending node updates. This also moves reparented nodes to
65+ // their new parents if needed.
4666 ui::AXTreeUpdate update{.tree_data = tree_.data ()};
67+
4768 // Figure out update order, ui::AXTree only accepts update in tree order,
4869 // where parent node must come before the child node in
4970 // ui::AXTreeUpdate.nodes. We start with picking a random node and turn the
@@ -63,7 +84,7 @@ void AccessibilityBridge::CommitUpdates() {
6384
6485 for (size_t i = results.size (); i > 0 ; i--) {
6586 for (SemanticsNode node : results[i - 1 ]) {
66- ConvertFluterUpdate (node, update);
87+ ConvertFlutterUpdate (node, update);
6788 }
6889 }
6990
@@ -73,7 +94,7 @@ void AccessibilityBridge::CommitUpdates() {
7394
7495 std::string error = tree_.error ();
7596 if (!error.empty ()) {
76- BASE_LOG ( ) << " Failed to update ui::AXTree, error: " << error;
97+ FML_LOG (ERROR ) << " Failed to update ui::AXTree, error: " << error;
7798 return ;
7899 }
79100 // Handles accessibility events as the result of the semantics update.
@@ -84,7 +105,7 @@ void AccessibilityBridge::CommitUpdates() {
84105 continue ;
85106 }
86107
87- delegate_-> OnAccessibilityEvent (targeted_event);
108+ OnAccessibilityEvent (targeted_event);
88109 }
89110 event_generator_.ClearEvents ();
90111}
@@ -105,20 +126,16 @@ const ui::AXTreeData& AccessibilityBridge::GetAXTreeData() const {
105126}
106127
107128const std::vector<ui::AXEventGenerator::TargetedEvent>
108- AccessibilityBridge::GetPendingEvents () {
129+ AccessibilityBridge::GetPendingEvents () const {
109130 std::vector<ui::AXEventGenerator::TargetedEvent> result (
110131 event_generator_.begin (), event_generator_.end ());
111132 return result;
112133}
113134
114- void AccessibilityBridge::UpdateDelegate (
115- std::unique_ptr<AccessibilityBridgeDelegate> delegate) {
116- delegate_ = std::move (delegate);
117- // Recreate FlutterPlatformNodeDelegates since they may contain stale state
118- // from the previous AccessibilityBridgeDelegate.
135+ void AccessibilityBridge::RecreateNodeDelegates () {
119136 for (const auto & [node_id, old_platform_node_delegate] : id_wrapper_map_) {
120137 std::shared_ptr<FlutterPlatformNodeDelegate> platform_node_delegate =
121- delegate_-> CreateFlutterPlatformNodeDelegate ();
138+ CreateFlutterPlatformNodeDelegate ();
122139 platform_node_delegate->Init (
123140 std::static_pointer_cast<FlutterPlatformNodeDelegate::OwnerBridge>(
124141 shared_from_this ()),
@@ -143,7 +160,7 @@ void AccessibilityBridge::OnRoleChanged(ui::AXTree* tree,
143160
144161void AccessibilityBridge::OnNodeCreated (ui::AXTree* tree, ui::AXNode* node) {
145162 BASE_DCHECK (node);
146- id_wrapper_map_[node->id ()] = delegate_-> CreateFlutterPlatformNodeDelegate ();
163+ id_wrapper_map_[node->id ()] = CreateFlutterPlatformNodeDelegate ();
147164 id_wrapper_map_[node->id ()]->Init (
148165 std::static_pointer_cast<FlutterPlatformNodeDelegate::OwnerBridge>(
149166 shared_from_this ()),
@@ -177,8 +194,64 @@ void AccessibilityBridge::OnAtomicUpdateFinished(
177194 }
178195}
179196
197+ std::optional<ui::AXTreeUpdate>
198+ AccessibilityBridge::CreateRemoveReparentedNodesUpdate () {
199+ std::unordered_map<int32_t , ui::AXNodeData> updates;
200+
201+ for (auto node_update : pending_semantics_node_updates_) {
202+ for (int32_t child_id : node_update.second .children_in_traversal_order ) {
203+ // Skip nodes that don't exist or have a parent in the current tree.
204+ ui::AXNode* child = tree_.GetFromId (child_id);
205+ if (!child) {
206+ continue ;
207+ }
208+
209+ // Flutter's root node should never be reparented.
210+ assert (child->parent ());
211+
212+ // Skip nodes whose parents are unchanged.
213+ if (child->parent ()->id () == node_update.second .id ) {
214+ continue ;
215+ }
216+
217+ // This pending update moves the current child node.
218+ // That new child must have a corresponding pending update.
219+ assert (pending_semantics_node_updates_.find (child_id) !=
220+ pending_semantics_node_updates_.end ());
221+
222+ // Create an update to remove the child from its previous parent.
223+ int32_t parent_id = child->parent ()->id ();
224+ if (updates.find (parent_id) == updates.end ()) {
225+ updates[parent_id] = tree_.GetFromId (parent_id)->data ();
226+ }
227+
228+ ui::AXNodeData* parent = &updates[parent_id];
229+ auto iter = std::find (parent->child_ids .begin (), parent->child_ids .end (),
230+ child_id);
231+
232+ assert (iter != parent->child_ids .end ());
233+ parent->child_ids .erase (iter);
234+ }
235+ }
236+
237+ if (updates.empty ()) {
238+ return std::nullopt ;
239+ }
240+
241+ ui::AXTreeUpdate update{
242+ .tree_data = tree_.data (),
243+ .nodes = std::vector<ui::AXNodeData>(),
244+ };
245+
246+ for (std::pair<int32_t , ui::AXNodeData> data : updates) {
247+ update.nodes .push_back (std::move (data.second ));
248+ }
249+
250+ return update;
251+ }
252+
180253// Private method.
181- void AccessibilityBridge::GetSubTreeList (SemanticsNode target,
254+ void AccessibilityBridge::GetSubTreeList (const SemanticsNode& target,
182255 std::vector<SemanticsNode>& result) {
183256 result.push_back (target);
184257 for (int32_t child : target.children_in_traversal_order ) {
@@ -191,8 +264,8 @@ void AccessibilityBridge::GetSubTreeList(SemanticsNode target,
191264 }
192265}
193266
194- void AccessibilityBridge::ConvertFluterUpdate (const SemanticsNode& node,
195- ui::AXTreeUpdate& tree_update) {
267+ void AccessibilityBridge::ConvertFlutterUpdate (const SemanticsNode& node,
268+ ui::AXTreeUpdate& tree_update) {
196269 ui::AXNodeData node_data;
197270 node_data.id = node.id ;
198271 SetRoleFromFlutterUpdate (node_data, node);
@@ -204,6 +277,7 @@ void AccessibilityBridge::ConvertFluterUpdate(const SemanticsNode& node,
204277 SetStringListAttributesFromFlutterUpdate (node_data, node);
205278 SetNameFromFlutterUpdate (node_data, node);
206279 SetValueFromFlutterUpdate (node_data, node);
280+ SetTooltipFromFlutterUpdate (node_data, node);
207281 node_data.relative_bounds .bounds .SetRect (node.rect .left , node.rect .top ,
208282 node.rect .right - node.rect .left ,
209283 node.rect .bottom - node.rect .top );
@@ -375,11 +449,21 @@ void AccessibilityBridge::SetIntAttributesFromFlutterUpdate(
375449 node_data.AddIntAttribute (ax::mojom::IntAttribute::kTextSelStart , sel_start);
376450 node_data.AddIntAttribute (ax::mojom::IntAttribute::kTextSelEnd , sel_end);
377451
378- if (node_data.role == ax::mojom::Role::kRadioButton ) {
452+ if (node_data.role == ax::mojom::Role::kRadioButton ||
453+ node_data.role == ax::mojom::Role::kCheckBox ) {
454+ node_data.AddIntAttribute (
455+ ax::mojom::IntAttribute::kCheckedState ,
456+ static_cast <int32_t >(
457+ flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsCheckStateMixed
458+ ? ax::mojom::CheckedState::kMixed
459+ : flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsChecked
460+ ? ax::mojom::CheckedState::kTrue
461+ : ax::mojom::CheckedState::kFalse ));
462+ } else if (node_data.role == ax::mojom::Role::kToggleButton ) {
379463 node_data.AddIntAttribute (
380464 ax::mojom::IntAttribute::kCheckedState ,
381465 static_cast <int32_t >(
382- flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsChecked
466+ flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsToggled
383467 ? ax::mojom::CheckedState::kTrue
384468 : ax::mojom::CheckedState::kFalse ));
385469 }
@@ -427,6 +511,12 @@ void AccessibilityBridge::SetValueFromFlutterUpdate(ui::AXNodeData& node_data,
427511 node_data.SetValue (node.value );
428512}
429513
514+ void AccessibilityBridge::SetTooltipFromFlutterUpdate (
515+ ui::AXNodeData& node_data,
516+ const SemanticsNode& node) {
517+ node_data.SetTooltip (node.tooltip );
518+ }
519+
430520void AccessibilityBridge::SetTreeData (const SemanticsNode& node,
431521 ui::AXTreeUpdate& tree_update) {
432522 FlutterSemanticsFlag flags = node.flags ;
@@ -493,6 +583,9 @@ AccessibilityBridge::FromFlutterSemanticsNode(
493583 if (flutter_node->decreased_value ) {
494584 result.decreased_value = std::string (flutter_node->decreased_value );
495585 }
586+ if (flutter_node->tooltip ) {
587+ result.tooltip = std::string (flutter_node->tooltip );
588+ }
496589 result.text_direction = flutter_node->text_direction ;
497590 result.rect = flutter_node->rect ;
498591 result.transform = flutter_node->transform ;
@@ -530,7 +623,7 @@ void AccessibilityBridge::SetLastFocusedId(AccessibilityNodeId node_id) {
530623 auto last_focused_child =
531624 GetFlutterPlatformNodeDelegateFromID (last_focused_id_);
532625 if (!last_focused_child.expired ()) {
533- delegate_-> DispatchAccessibilityAction (
626+ DispatchAccessibilityAction (
534627 last_focused_id_,
535628 FlutterSemanticsAction::
536629 kFlutterSemanticsActionDidLoseAccessibilityFocus ,
@@ -560,11 +653,4 @@ gfx::RectF AccessibilityBridge::RelativeToGlobalBounds(const ui::AXNode* node,
560653 clip_bounds);
561654}
562655
563- void AccessibilityBridge::DispatchAccessibilityAction (
564- AccessibilityNodeId target,
565- FlutterSemanticsAction action,
566- fml::MallocMapping data) {
567- delegate_->DispatchAccessibilityAction (target, action, std::move (data));
568- }
569-
570656} // namespace flutter
0 commit comments