Skip to content

Commit fd0cec0

Browse files
authored
[a11y] Sync Flutter 3.7 source code (#23)
1 parent e6ecb94 commit fd0cec0

24 files changed

+379
-178
lines changed

flutter/shell/platform/common/accessibility_bridge.cc

Lines changed: 113 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -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

4543
void 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

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

144161
void 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+
430520
void 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

Comments
 (0)