77#include < functional>
88#include < utility>
99
10+ #include " flutter/third_party/accessibility/ax/ax_tree_manager_map.h"
1011#include " flutter/third_party/accessibility/ax/ax_tree_update.h"
1112#include " flutter/third_party/accessibility/base/logging.h"
1213
@@ -19,24 +20,30 @@ constexpr int kHasScrollingAction =
1920 FlutterSemanticsAction::kFlutterSemanticsActionScrollDown ;
2021
2122// 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 );
2532}
2633
2734AccessibilityBridge::~AccessibilityBridge () {
2835 event_generator_.ReleaseTree ();
29- tree_. RemoveObserver (static_cast <ui::AXTreeObserver*>(this ));
36+ tree_-> RemoveObserver (static_cast <ui::AXTreeObserver*>(this ));
3037}
3138
3239void 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);
3542}
3643
3744void 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 ] =
4047 FromFlutterSemanticsCustomAction (action);
4148}
4249
@@ -51,9 +58,9 @@ void AccessibilityBridge::CommitUpdates() {
5158 std::optional<ui::AXTreeUpdate> remove_reparented =
5259 CreateRemoveReparentedNodesUpdate ();
5360 if (remove_reparented.has_value ()) {
54- tree_. Unserialize (remove_reparented.value ());
61+ tree_-> Unserialize (remove_reparented.value ());
5562
56- std::string error = tree_. error ();
63+ std::string error = tree_-> error ();
5764 if (!error.empty ()) {
5865 FML_LOG (ERROR) << " Failed to update ui::AXTree, error: " << error;
5966 assert (false );
@@ -63,15 +70,16 @@ void AccessibilityBridge::CommitUpdates() {
6370
6471 // Second, apply the pending node updates. This also moves reparented nodes to
6572 // their new parents if needed.
66- ui::AXTreeUpdate update{.tree_data = tree_. data ()};
73+ ui::AXTreeUpdate update{.tree_data = tree_-> data ()};
6774
6875 // Figure out update order, ui::AXTree only accepts update in tree order,
6976 // where parent node must come before the child node in
7077 // ui::AXTreeUpdate.nodes. We start with picking a random node and turn the
7178 // entire subtree into a list. We pick another node from the remaining update,
7279 // and keep doing so until the update map is empty. We then concatenate the
7380 // 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.
7583 std::vector<std::vector<SemanticsNode>> results;
7684 while (!pending_semantics_node_updates_.empty ()) {
7785 auto begin = pending_semantics_node_updates_.begin ();
@@ -88,11 +96,20 @@ void AccessibilityBridge::CommitUpdates() {
8896 }
8997 }
9098
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);
92109 pending_semantics_node_updates_.clear ();
93110 pending_semantics_custom_action_updates_.clear ();
94111
95- std::string error = tree_. error ();
112+ std::string error = tree_-> error ();
96113 if (!error.empty ()) {
97114 FML_LOG (ERROR) << " Failed to update ui::AXTree, error: " << error;
98115 return ;
@@ -122,7 +139,7 @@ AccessibilityBridge::GetFlutterPlatformNodeDelegateFromID(
122139}
123140
124141const ui::AXTreeData& AccessibilityBridge::GetAXTreeData () const {
125- return tree_. data ();
142+ return tree_-> data ();
126143}
127144
128145const std::vector<ui::AXEventGenerator::TargetedEvent>
@@ -201,7 +218,7 @@ AccessibilityBridge::CreateRemoveReparentedNodesUpdate() {
201218 for (auto node_update : pending_semantics_node_updates_) {
202219 for (int32_t child_id : node_update.second .children_in_traversal_order ) {
203220 // 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);
205222 if (!child) {
206223 continue ;
207224 }
@@ -222,7 +239,7 @@ AccessibilityBridge::CreateRemoveReparentedNodesUpdate() {
222239 // Create an update to remove the child from its previous parent.
223240 int32_t parent_id = child->parent ()->id ();
224241 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 ();
226243 }
227244
228245 ui::AXNodeData* parent = &updates[parent_id];
@@ -239,7 +256,7 @@ AccessibilityBridge::CreateRemoveReparentedNodesUpdate() {
239256 }
240257
241258 ui::AXTreeUpdate update{
242- .tree_data = tree_. data (),
259+ .tree_data = tree_-> data (),
243260 .nodes = std::vector<ui::AXNodeData>(),
244261 };
245262
@@ -428,6 +445,12 @@ void AccessibilityBridge::SetBooleanAttributesFromFlutterUpdate(
428445 ax::mojom::BoolAttribute::kEditableRoot ,
429446 flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField &&
430447 (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 );
431454}
432455
433456void AccessibilityBridge::SetIntAttributesFromFlutterUpdate (
@@ -520,11 +543,12 @@ void AccessibilityBridge::SetTooltipFromFlutterUpdate(
520543void AccessibilityBridge::SetTreeData (const SemanticsNode& node,
521544 ui::AXTreeUpdate& tree_update) {
522545 FlutterSemanticsFlag flags = node.flags ;
523- // Set selection if:
546+ // Set selection of the focused node if:
524547 // 1. this text field has a valid selection
525548 // 2. this text field doesn't have a valid selection but had selection stored
526549 // in the tree.
527- if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField ) {
550+ if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField &&
551+ flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsFocused ) {
528552 if (node.text_selection_base != -1 ) {
529553 tree_update.tree_data .sel_anchor_object_id = node.id ;
530554 tree_update.tree_data .sel_anchor_offset = node.text_selection_base ;
@@ -554,66 +578,66 @@ void AccessibilityBridge::SetTreeData(const SemanticsNode& node,
554578
555579AccessibilityBridge::SemanticsNode
556580AccessibilityBridge::FromFlutterSemanticsNode (
557- const FlutterSemanticsNode* flutter_node) {
581+ const FlutterSemanticsNode2& flutter_node) {
558582 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 ) {
593617 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 );
596620 }
597- if (flutter_node-> custom_accessibility_actions_count > 0 ) {
621+ if (flutter_node. custom_accessibility_actions_count > 0 ) {
598622 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 );
602626 }
603627 return result;
604628}
605629
606630AccessibilityBridge::SemanticsCustomAction
607631AccessibilityBridge::FromFlutterSemanticsCustomAction (
608- const FlutterSemanticsCustomAction* flutter_custom_action) {
632+ const FlutterSemanticsCustomAction2& flutter_custom_action) {
609633 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 );
614638 }
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 );
617641 }
618642 return result;
619643}
@@ -649,8 +673,60 @@ gfx::NativeViewAccessible AccessibilityBridge::GetNativeAccessibleFromId(
649673gfx::RectF AccessibilityBridge::RelativeToGlobalBounds (const ui::AXNode* node,
650674 bool & offscreen,
651675 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 ();
654730}
655731
656732} // namespace flutter
0 commit comments