Skip to content

Commit 49e52fe

Browse files
authored
[a11y] Use the new update semantics embedder API (#39)
1 parent ecdcd8b commit 49e52fe

34 files changed

+823
-214
lines changed

flutter/shell/platform/common/accessibility_bridge.cc

Lines changed: 146 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
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

2734
AccessibilityBridge::~AccessibilityBridge() {
2835
event_generator_.ReleaseTree();
29-
tree_.RemoveObserver(static_cast<ui::AXTreeObserver*>(this));
36+
tree_->RemoveObserver(static_cast<ui::AXTreeObserver*>(this));
3037
}
3138

3239
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);
3542
}
3643

3744
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] =
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

124141
const ui::AXTreeData& AccessibilityBridge::GetAXTreeData() const {
125-
return tree_.data();
142+
return tree_->data();
126143
}
127144

128145
const 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

433456
void AccessibilityBridge::SetIntAttributesFromFlutterUpdate(
@@ -520,11 +543,12 @@ void AccessibilityBridge::SetTooltipFromFlutterUpdate(
520543
void 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

555579
AccessibilityBridge::SemanticsNode
556580
AccessibilityBridge::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

606630
AccessibilityBridge::SemanticsCustomAction
607631
AccessibilityBridge::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(
649673
gfx::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

Comments
 (0)