| Flutter iOS Embedder |

accessibility_bridge.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "accessibility_bridge.h"
6
7 #include <functional>
8 #include <utility>
9
10 #include "flutter/third_party/accessibility/ax/ax_tree_manager_map.h"
11 #include "flutter/third_party/accessibility/ax/ax_tree_update.h"
12 #include "flutter/third_party/accessibility/base/logging.h"
13
14 namespace flutter { // namespace
15
16 constexpr int kHasScrollingAction =
17 FlutterSemanticsAction::kFlutterSemanticsActionScrollLeft |
18 FlutterSemanticsAction::kFlutterSemanticsActionScrollRight |
19 FlutterSemanticsAction::kFlutterSemanticsActionScrollUp |
20 FlutterSemanticsAction::kFlutterSemanticsActionScrollDown;
21
22 // AccessibilityBridge
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);
32 }
33
34 AccessibilityBridge::~AccessibilityBridge() {
35 event_generator_.ReleaseTree();
36 tree_->RemoveObserver(static_cast<ui::AXTreeObserver*>(this));
37 }
38
39 void AccessibilityBridge::AddFlutterSemanticsNodeUpdate(
40 const FlutterSemanticsNode2& node) {
41 pending_semantics_node_updates_[node.id] = FromFlutterSemanticsNode(node);
42 }
43
44 void AccessibilityBridge::AddFlutterSemanticsCustomActionUpdate(
45 const FlutterSemanticsCustomAction2& action) {
46 pending_semantics_custom_action_updates_[action.id] =
47 FromFlutterSemanticsCustomAction(action);
48 }
49
50 void AccessibilityBridge::CommitUpdates() {
51 // AXTree cannot move a node in a single update.
52 // This must be split across two updates:
53 //
54 // * Update 1: remove nodes from their old parents.
55 // * Update 2: re-add nodes (including their children) to their new parents.
56 //
57 // First, start by removing nodes if necessary.
58 std::optional<ui::AXTreeUpdate> remove_reparented =
59 CreateRemoveReparentedNodesUpdate();
60 if (remove_reparented.has_value()) {
61 tree_->Unserialize(remove_reparented.value());
62
63 std::string error = tree_->error();
64 if (!error.empty()) {
65 FML_LOG(ERROR) << "Failed to update ui::AXTree, error: " << error;
66 assert(false);
67 return;
68 }
69 }
70
71 // Second, apply the pending node updates. This also moves reparented nodes to
72 // their new parents if needed.
73 ui::AXTreeUpdate update{.tree_data = tree_->data()};
74
75 // Figure out update order, ui::AXTree only accepts update in tree order,
76 // where parent node must come before the child node in
77 // ui::AXTreeUpdate.nodes. We start with picking a random node and turn the
78 // entire subtree into a list. We pick another node from the remaining update,
79 // and keep doing so until the update map is empty. We then concatenate the
80 // lists in the reversed order, this guarantees parent updates always come
81 // before child updates. If the root is in the update, it is guaranteed to
82 // be the first node of the last list.
83 std::vector<std::vector<SemanticsNode>> results;
84 while (!pending_semantics_node_updates_.empty()) {
85 auto begin = pending_semantics_node_updates_.begin();
86 SemanticsNode target = begin->second;
87 std::vector<SemanticsNode> sub_tree_list;
88 GetSubTreeList(target, sub_tree_list);
89 results.push_back(sub_tree_list);
90 pending_semantics_node_updates_.erase(begin);
91 }
92
93 for (size_t i = results.size(); i > 0; i--) {
94 for (const SemanticsNode& node : results[i - 1]) {
95 ConvertFlutterUpdate(node, update);
96 }
97 }
98
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);
109 pending_semantics_node_updates_.clear();
110 pending_semantics_custom_action_updates_.clear();
111
112 std::string error = tree_->error();
113 if (!error.empty()) {
114 FML_LOG(ERROR) << "Failed to update ui::AXTree, error: " << error;
115 return;
116 }
117 // Handles accessibility events as the result of the semantics update.
118 for (const auto& targeted_event : event_generator_) {
119 auto event_target =
120 GetFlutterPlatformNodeDelegateFromID(targeted_event.node->id());
121 if (event_target.expired()) {
122 continue;
123 }
124
125 OnAccessibilityEvent(targeted_event);
126 }
127 event_generator_.ClearEvents();
128 }
129
130 std::weak_ptr<FlutterPlatformNodeDelegate>
131 AccessibilityBridge::GetFlutterPlatformNodeDelegateFromID(
132 AccessibilityNodeId id) const {
133 const auto iter = id_wrapper_map_.find(id);
134 if (iter != id_wrapper_map_.end()) {
135 return iter->second;
136 }
137
138 return std::weak_ptr<FlutterPlatformNodeDelegate>();
139 }
140
141 const ui::AXTreeData& AccessibilityBridge::GetAXTreeData() const {
142 return tree_->data();
143 }
144
145 const std::vector<ui::AXEventGenerator::TargetedEvent>
146 AccessibilityBridge::GetPendingEvents() const {
147 std::vector<ui::AXEventGenerator::TargetedEvent> result(
148 event_generator_.begin(), event_generator_.end());
149 return result;
150 }
151
152 void AccessibilityBridge::OnNodeWillBeDeleted(ui::AXTree* tree,
153 ui::AXNode* node) {}
154
155 void AccessibilityBridge::OnSubtreeWillBeDeleted(ui::AXTree* tree,
156 ui::AXNode* node) {}
157
158 void AccessibilityBridge::OnNodeReparented(ui::AXTree* tree, ui::AXNode* node) {
159 }
160
161 void AccessibilityBridge::OnRoleChanged(ui::AXTree* tree,
162 ui::AXNode* node,
163 ax::mojom::Role old_role,
164 ax::mojom::Role new_role) {}
165
166 void AccessibilityBridge::OnNodeDataChanged(
167 ui::AXTree* tree,
168 const ui::AXNodeData& old_node_data,
169 const ui::AXNodeData& new_node_data) {
170 auto platform_view =
171 GetFlutterPlatformNodeDelegateFromID(new_node_data.id).lock();
172 if (platform_view) {
173 platform_view->NodeDataChanged(old_node_data, new_node_data);
174 }
175 }
176
177 void AccessibilityBridge::OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) {
178 BASE_DCHECK(node);
179 id_wrapper_map_[node->id()] = CreateFlutterPlatformNodeDelegate();
180 id_wrapper_map_[node->id()]->Init(
181 std::static_pointer_cast<FlutterPlatformNodeDelegate::OwnerBridge>(
182 shared_from_this()),
183 node);
184 }
185
186 void AccessibilityBridge::OnNodeDeleted(ui::AXTree* tree,
187 AccessibilityNodeId node_id) {
188 BASE_DCHECK(node_id != ui::AXNode::kInvalidAXID);
189 if (id_wrapper_map_.find(node_id) != id_wrapper_map_.end()) {
190 id_wrapper_map_.erase(node_id);
191 }
192 }
193
194 void AccessibilityBridge::OnAtomicUpdateFinished(
195 ui::AXTree* tree,
196 bool root_changed,
197 const std::vector<ui::AXTreeObserver::Change>& changes) {
198 // The Flutter semantics update does not include child->parent relationship
199 // We have to update the relative bound offset container id here in order
200 // to calculate the screen bound correctly.
201 for (const auto& change : changes) {
202 ui::AXNode* node = change.node;
203 const ui::AXNodeData& data = node->data();
204 AccessibilityNodeId offset_container_id = -1;
205 if (node->parent()) {
206 offset_container_id = node->parent()->id();
207 }
208 node->SetLocation(offset_container_id, data.relative_bounds.bounds,
209 data.relative_bounds.transform.get());
210 }
211 }
212
213 std::optional<ui::AXTreeUpdate>
214 AccessibilityBridge::CreateRemoveReparentedNodesUpdate() {
215 std::unordered_map<int32_t, ui::AXNodeData> updates;
216
217 for (const auto& node_update : pending_semantics_node_updates_) {
218 for (int32_t child_id : node_update.second.children_in_traversal_order) {
219 // Skip nodes that don't exist or have a parent in the current tree.
220 ui::AXNode* child = tree_->GetFromId(child_id);
221 if (!child) {
222 continue;
223 }
224
225 // Flutter's root node should never be reparented.
226 assert(child->parent());
227
228 // Skip nodes whose parents are unchanged.
229 if (child->parent()->id() == node_update.second.id) {
230 continue;
231 }
232
233 // This pending update moves the current child node.
234 // That new child must have a corresponding pending update.
235 assert(pending_semantics_node_updates_.find(child_id) !=
236 pending_semantics_node_updates_.end());
237
238 // Create an update to remove the child from its previous parent.
239 int32_t parent_id = child->parent()->id();
240 if (updates.find(parent_id) == updates.end()) {
241 updates[parent_id] = tree_->GetFromId(parent_id)->data();
242 }
243
244 ui::AXNodeData* parent = &updates[parent_id];
245 auto iter = std::find(parent->child_ids.begin(), parent->child_ids.end(),
246 child_id);
247
248 assert(iter != parent->child_ids.end());
249 parent->child_ids.erase(iter);
250 }
251 }
252
253 if (updates.empty()) {
254 return std::nullopt;
255 }
256
257 ui::AXTreeUpdate update{
258 .tree_data = tree_->data(),
259 .nodes = std::vector<ui::AXNodeData>(),
260 };
261
262 for (std::pair<int32_t, ui::AXNodeData> data : updates) {
263 update.nodes.push_back(std::move(data.second));
264 }
265
266 return update;
267 }
268
269 // Private method.
270 void AccessibilityBridge::GetSubTreeList(const SemanticsNode& target,
271 std::vector<SemanticsNode>& result) {
272 result.push_back(target);
273 for (int32_t child : target.children_in_traversal_order) {
274 auto iter = pending_semantics_node_updates_.find(child);
275 if (iter != pending_semantics_node_updates_.end()) {
276 SemanticsNode node = iter->second;
277 GetSubTreeList(node, result);
278 pending_semantics_node_updates_.erase(iter);
279 }
280 }
281 }
282
283 void AccessibilityBridge::ConvertFlutterUpdate(const SemanticsNode& node,
284 ui::AXTreeUpdate& tree_update) {
285 ui::AXNodeData node_data;
286 node_data.id = node.id;
287 SetRoleFromFlutterUpdate(node_data, node);
288 SetStateFromFlutterUpdate(node_data, node);
289 SetActionsFromFlutterUpdate(node_data, node);
290 SetBooleanAttributesFromFlutterUpdate(node_data, node);
291 SetIntAttributesFromFlutterUpdate(node_data, node);
292 SetIntListAttributesFromFlutterUpdate(node_data, node);
293 SetStringListAttributesFromFlutterUpdate(node_data, node);
294 SetIdentifierFromFlutterUpdate(node_data, node);
295 SetNameFromFlutterUpdate(node_data, node);
296 SetValueFromFlutterUpdate(node_data, node);
297 SetTooltipFromFlutterUpdate(node_data, node);
298 node_data.relative_bounds.bounds.SetRect(node.rect.left, node.rect.top,
299 node.rect.right - node.rect.left,
300 node.rect.bottom - node.rect.top);
301 node_data.relative_bounds.transform = std::make_unique<gfx::Transform>(
302 node.transform.scaleX, node.transform.skewX, node.transform.transX, 0,
303 node.transform.skewY, node.transform.scaleY, node.transform.transY, 0,
304 node.transform.pers0, node.transform.pers1, node.transform.pers2, 0, 0, 0,
305 0, 0);
306 for (auto child : node.children_in_traversal_order) {
307 node_data.child_ids.push_back(child);
308 }
309 SetTreeData(node, tree_update);
310 tree_update.nodes.push_back(node_data);
311 }
312
313 void AccessibilityBridge::SetRoleFromFlutterUpdate(ui::AXNodeData& node_data,
314 const SemanticsNode& node) {
315 const FlutterSemanticsFlags* flags = node.flags;
316 FML_DCHECK(flags) << "SemanticsNode::flags must not be null";
317 if (flags->is_button) {
318 node_data.role = ax::mojom::Role::kButton;
319 return;
320 }
321 if (flags->is_text_field && !flags->is_read_only) {
322 node_data.role = ax::mojom::Role::kTextField;
323 return;
324 }
325 if (flags->is_header) {
326 node_data.role = ax::mojom::Role::kHeader;
327 return;
328 }
329 if (flags->is_image) {
330 node_data.role = ax::mojom::Role::kImage;
331 return;
332 }
333 if (flags->is_link) {
334 node_data.role = ax::mojom::Role::kLink;
335 return;
336 }
337
338 if (flags->is_in_mutually_exclusive_group &&
339 flags->is_checked != FlutterCheckState::kFlutterCheckStateNone) {
340 node_data.role = ax::mojom::Role::kRadioButton;
341 return;
342 }
343 if (flags->is_checked != FlutterCheckState::kFlutterCheckStateNone) {
344 node_data.role = ax::mojom::Role::kCheckBox;
345 return;
346 }
347 if (flags->is_toggled != FlutterTristate::kFlutterTristateNone) {
348 node_data.role = ax::mojom::Role::kSwitch;
349 return;
350 }
351 if (flags->is_slider) {
352 node_data.role = ax::mojom::Role::kSlider;
353 return;
354 }
355 // If the state cannot be derived from the flutter flags, we fallback to group
356 // or static text.
357 if (node.children_in_traversal_order.empty()) {
358 node_data.role = ax::mojom::Role::kStaticText;
359 } else {
360 node_data.role = ax::mojom::Role::kGroup;
361 }
362 }
363
364 void AccessibilityBridge::SetStateFromFlutterUpdate(ui::AXNodeData& node_data,
365 const SemanticsNode& node) {
366 const FlutterSemanticsFlags* flags = node.flags;
367 FlutterSemanticsAction actions = node.actions;
368 if (flags->is_expanded == FlutterTristate::kFlutterTristateTrue) {
369 node_data.AddState(ax::mojom::State::kExpanded);
370 } else if (flags->is_expanded == FlutterTristate::kFlutterTristateFalse) {
371 node_data.AddState(ax::mojom::State::kCollapsed);
372 }
373 if (flags->is_text_field && !flags->is_read_only) {
374 node_data.AddState(ax::mojom::State::kEditable);
375 }
376 if (node_data.role == ax::mojom::Role::kStaticText &&
377 (actions & kHasScrollingAction) == 0 && node.value.empty() &&
378 node.label.empty() && node.hint.empty()) {
379 node_data.AddState(ax::mojom::State::kIgnored);
380 } else {
381 // kFlutterSemanticsFlagIsFocusable means a keyboard focusable, it is
382 // different from semantics focusable.
383 // TODO(chunhtai): figure out whether something is not semantics focusable.
384 node_data.AddState(ax::mojom::State::kFocusable);
385 }
386 }
387
388 void AccessibilityBridge::SetActionsFromFlutterUpdate(
389 ui::AXNodeData& node_data,
390 const SemanticsNode& node) {
391 FlutterSemanticsAction actions = node.actions;
392 if (actions & FlutterSemanticsAction::kFlutterSemanticsActionTap) {
393 node_data.AddAction(ax::mojom::Action::kDoDefault);
394 }
395 if (actions & FlutterSemanticsAction::kFlutterSemanticsActionScrollLeft) {
396 node_data.AddAction(ax::mojom::Action::kScrollLeft);
397 }
398 if (actions & FlutterSemanticsAction::kFlutterSemanticsActionScrollRight) {
399 node_data.AddAction(ax::mojom::Action::kScrollRight);
400 }
401 if (actions & FlutterSemanticsAction::kFlutterSemanticsActionScrollUp) {
402 node_data.AddAction(ax::mojom::Action::kScrollUp);
403 }
404 if (actions & FlutterSemanticsAction::kFlutterSemanticsActionScrollDown) {
405 node_data.AddAction(ax::mojom::Action::kScrollDown);
406 }
407 if (actions & FlutterSemanticsAction::kFlutterSemanticsActionIncrease) {
408 node_data.AddAction(ax::mojom::Action::kIncrement);
409 }
410 if (actions & FlutterSemanticsAction::kFlutterSemanticsActionDecrease) {
411 node_data.AddAction(ax::mojom::Action::kDecrement);
412 }
413 // Every node has show on screen action.
414 node_data.AddAction(ax::mojom::Action::kScrollToMakeVisible);
415
416 if (actions & FlutterSemanticsAction::kFlutterSemanticsActionSetSelection) {
417 node_data.AddAction(ax::mojom::Action::kSetSelection);
418 }
419 if (actions & FlutterSemanticsAction::
420 kFlutterSemanticsActionDidGainAccessibilityFocus) {
421 node_data.AddAction(ax::mojom::Action::kSetAccessibilityFocus);
422 }
423 if (actions & FlutterSemanticsAction::
424 kFlutterSemanticsActionDidLoseAccessibilityFocus) {
425 node_data.AddAction(ax::mojom::Action::kClearAccessibilityFocus);
426 }
427 if (actions & FlutterSemanticsAction::kFlutterSemanticsActionCustomAction) {
428 node_data.AddAction(ax::mojom::Action::kCustomAction);
429 }
430 }
431
432 void AccessibilityBridge::SetBooleanAttributesFromFlutterUpdate(
433 ui::AXNodeData& node_data,
434 const SemanticsNode& node) {
435 FlutterSemanticsAction actions = node.actions;
436 const FlutterSemanticsFlags* flags = node.flags;
437 node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kScrollable,
438 actions & kHasScrollingAction);
439 node_data.AddBoolAttribute(
440 ax::mojom::BoolAttribute::kClickable,
441 actions & FlutterSemanticsAction::kFlutterSemanticsActionTap);
442 // TODO(chunhtai): figure out if there is a node that does not clip overflow.
443 node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kClipsChildren,
444 !node.children_in_traversal_order.empty());
445 node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected,
446 flags->is_selected);
447 node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot,
448 flags->is_text_field && !flags->is_read_only);
449 // Mark nodes as line breaking so that screen readers don't
450 // merge all consecutive objects into one.
451 // TODO(schectman): When should a node have this attribute set?
452 // https://github.com/flutter/flutter/issues/118184
453 node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
454 true);
455 }
456
457 void AccessibilityBridge::SetIntAttributesFromFlutterUpdate(
458 ui::AXNodeData& node_data,
459 const SemanticsNode& node) {
460 const FlutterSemanticsFlags* flags = node.flags;
461 node_data.AddIntAttribute(ax::mojom::IntAttribute::kTextDirection,
462 node.text_direction);
463
464 int sel_start = node.text_selection_base;
465 int sel_end = node.text_selection_extent;
466 if (flags->is_text_field && !flags->is_read_only && !node.value.empty()) {
467 // By default the text field selection should be at the end.
468 sel_start = sel_start == -1 ? node.value.length() : sel_start;
469 sel_end = sel_end == -1 ? node.value.length() : sel_end;
470 }
471 node_data.AddIntAttribute(ax::mojom::IntAttribute::kTextSelStart, sel_start);
472 node_data.AddIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, sel_end);
473
474 if (node_data.role == ax::mojom::Role::kRadioButton ||
475 node_data.role == ax::mojom::Role::kCheckBox) {
476 node_data.AddIntAttribute(
477 ax::mojom::IntAttribute::kCheckedState,
478 static_cast<int32_t>(
479 (flags->is_checked == FlutterCheckState::kFlutterCheckStateMixed)
480 ? ax::mojom::CheckedState::kMixed
481 : (flags->is_checked == FlutterCheckState::kFlutterCheckStateTrue)
482 ? ax::mojom::CheckedState::kTrue
483 : ax::mojom::CheckedState::kFalse));
484 } else if (node_data.role == ax::mojom::Role::kSwitch) {
485 node_data.AddIntAttribute(
486 ax::mojom::IntAttribute::kCheckedState,
487 static_cast<int32_t>(
488 (flags->is_toggled == FlutterTristate::kFlutterTristateTrue)
489 ? ax::mojom::CheckedState::kTrue
490 : ax::mojom::CheckedState::kFalse));
491 }
492 }
493
494 void AccessibilityBridge::SetIntListAttributesFromFlutterUpdate(
495 ui::AXNodeData& node_data,
496 const SemanticsNode& node) {
497 FlutterSemanticsAction actions = node.actions;
498 if (actions & FlutterSemanticsAction::kFlutterSemanticsActionCustomAction) {
499 std::vector<int32_t> custom_action_ids;
500 custom_action_ids.reserve(node.custom_accessibility_actions.size());
501 for (size_t i = 0; i < node.custom_accessibility_actions.size(); i++) {
502 custom_action_ids.push_back(node.custom_accessibility_actions[i]);
503 }
504 node_data.AddIntListAttribute(ax::mojom::IntListAttribute::kCustomActionIds,
505 custom_action_ids);
506 }
507 }
508
509 void AccessibilityBridge::SetStringListAttributesFromFlutterUpdate(
510 ui::AXNodeData& node_data,
511 const SemanticsNode& node) {
512 FlutterSemanticsAction actions = node.actions;
513 if (actions & FlutterSemanticsAction::kFlutterSemanticsActionCustomAction) {
514 std::vector<std::string> custom_action_description;
515 for (size_t i = 0; i < node.custom_accessibility_actions.size(); i++) {
516 auto iter = pending_semantics_custom_action_updates_.find(
517 node.custom_accessibility_actions[i]);
518 BASE_DCHECK(iter != pending_semantics_custom_action_updates_.end());
519 custom_action_description.push_back(iter->second.label);
520 }
521 node_data.AddStringListAttribute(
522 ax::mojom::StringListAttribute::kCustomActionDescriptions,
523 custom_action_description);
524 }
525 }
526
527 void AccessibilityBridge::SetIdentifierFromFlutterUpdate(
528 ui::AXNodeData& node_data,
529 const SemanticsNode& node) {
530 node_data.AddStringAttribute(ax::mojom::StringAttribute::kIdentifier,
531 node.identifier);
532 }
533
534 void AccessibilityBridge::SetNameFromFlutterUpdate(ui::AXNodeData& node_data,
535 const SemanticsNode& node) {
536 node_data.SetName(node.label);
537 }
538
539 void AccessibilityBridge::SetValueFromFlutterUpdate(ui::AXNodeData& node_data,
540 const SemanticsNode& node) {
541 node_data.SetValue(node.value);
542 }
543
544 void AccessibilityBridge::SetTooltipFromFlutterUpdate(
545 ui::AXNodeData& node_data,
546 const SemanticsNode& node) {
547 node_data.SetTooltip(node.tooltip);
548 }
549
550 void AccessibilityBridge::SetTreeData(const SemanticsNode& node,
551 ui::AXTreeUpdate& tree_update) {
552 const FlutterSemanticsFlags* flags = node.flags;
553 // Set selection of the focused node if:
554 // 1. this text field has a valid selection
555 // 2. this text field doesn't have a valid selection but had selection stored
556 // in the tree.
557 if (flags->is_text_field &&
558 flags->is_focused == FlutterTristate::kFlutterTristateTrue) {
559 if (node.text_selection_base != -1) {
560 tree_update.tree_data.sel_anchor_object_id = node.id;
561 tree_update.tree_data.sel_anchor_offset = node.text_selection_base;
562 tree_update.tree_data.sel_focus_object_id = node.id;
563 tree_update.tree_data.sel_focus_offset = node.text_selection_extent;
564 tree_update.has_tree_data = true;
565 } else if (tree_update.tree_data.sel_anchor_object_id == node.id) {
566 tree_update.tree_data.sel_anchor_object_id = ui::AXNode::kInvalidAXID;
567 tree_update.tree_data.sel_anchor_offset = -1;
568 tree_update.tree_data.sel_focus_object_id = ui::AXNode::kInvalidAXID;
569 tree_update.tree_data.sel_focus_offset = -1;
570 tree_update.has_tree_data = true;
571 }
572 }
573
574 if (flags->is_focused == FlutterTristate::kFlutterTristateTrue &&
575 tree_update.tree_data.focus_id != node.id) {
576 tree_update.tree_data.focus_id = node.id;
577 tree_update.has_tree_data = true;
578 } else if (flags->is_focused != FlutterTristate::kFlutterTristateTrue &&
579 tree_update.tree_data.focus_id == node.id) {
580 tree_update.tree_data.focus_id = ui::AXNode::kInvalidAXID;
581 tree_update.has_tree_data = true;
582 }
583 }
584
585 AccessibilityBridge::SemanticsNode
586 AccessibilityBridge::FromFlutterSemanticsNode(
587 const FlutterSemanticsNode2& flutter_node) {
588 SemanticsNode result;
589 result.id = flutter_node.id;
590 FML_DCHECK(flutter_node.flags2)
591 << "FlutterSemanticsNode2::flags2 must not be null";
592
593 result.flags = flutter_node.flags2;
594 result.actions = flutter_node.actions;
595 result.heading_level = flutter_node.heading_level;
596 result.text_selection_base = flutter_node.text_selection_base;
597 result.text_selection_extent = flutter_node.text_selection_extent;
598 result.scroll_child_count = flutter_node.scroll_child_count;
599 result.scroll_index = flutter_node.scroll_index;
600 result.scroll_position = flutter_node.scroll_position;
601 result.scroll_extent_max = flutter_node.scroll_extent_max;
602 result.scroll_extent_min = flutter_node.scroll_extent_min;
603 if (flutter_node.label) {
604 result.label = std::string(flutter_node.label);
605 }
606 if (flutter_node.hint) {
607 result.hint = std::string(flutter_node.hint);
608 }
609 if (flutter_node.value) {
610 result.value = std::string(flutter_node.value);
611 }
612 if (flutter_node.increased_value) {
613 result.increased_value = std::string(flutter_node.increased_value);
614 }
615 if (flutter_node.decreased_value) {
616 result.decreased_value = std::string(flutter_node.decreased_value);
617 }
618 if (flutter_node.tooltip) {
619 result.tooltip = std::string(flutter_node.tooltip);
620 }
621 result.text_direction = flutter_node.text_direction;
622 result.rect = flutter_node.rect;
623 result.transform = flutter_node.transform;
624 if (flutter_node.child_count > 0) {
625 result.children_in_traversal_order = std::vector<int32_t>(
626 flutter_node.children_in_traversal_order,
627 flutter_node.children_in_traversal_order + flutter_node.child_count);
628 }
629 if (flutter_node.custom_accessibility_actions_count > 0) {
630 result.custom_accessibility_actions = std::vector<int32_t>(
631 flutter_node.custom_accessibility_actions,
632 flutter_node.custom_accessibility_actions +
633 flutter_node.custom_accessibility_actions_count);
634 }
635 if (flutter_node.identifier) {
636 result.identifier = std::string(flutter_node.identifier);
637 }
638 return result;
639 }
640
641 AccessibilityBridge::SemanticsCustomAction
642 AccessibilityBridge::FromFlutterSemanticsCustomAction(
643 const FlutterSemanticsCustomAction2& flutter_custom_action) {
644 SemanticsCustomAction result;
645 result.id = flutter_custom_action.id;
646 result.override_action = flutter_custom_action.override_action;
647 if (flutter_custom_action.label) {
648 result.label = std::string(flutter_custom_action.label);
649 }
650 if (flutter_custom_action.hint) {
651 result.hint = std::string(flutter_custom_action.hint);
652 }
653 return result;
654 }
655
656 void AccessibilityBridge::SetLastFocusedId(AccessibilityNodeId node_id) {
657 if (last_focused_id_ != node_id) {
658 auto last_focused_child =
659 GetFlutterPlatformNodeDelegateFromID(last_focused_id_);
660 if (!last_focused_child.expired()) {
661 DispatchAccessibilityAction(
662 last_focused_id_,
663 FlutterSemanticsAction::
664 kFlutterSemanticsActionDidLoseAccessibilityFocus,
665 {});
666 }
667 last_focused_id_ = node_id;
668 }
669 }
670
671 AccessibilityNodeId AccessibilityBridge::GetLastFocusedId() {
672 return last_focused_id_;
673 }
674
675 gfx::NativeViewAccessible AccessibilityBridge::GetNativeAccessibleFromId(
676 AccessibilityNodeId id) {
677 auto platform_node_delegate = GetFlutterPlatformNodeDelegateFromID(id).lock();
678 if (!platform_node_delegate) {
679 return nullptr;
680 }
681 return platform_node_delegate->GetNativeViewAccessible();
682 }
683
684 gfx::RectF AccessibilityBridge::RelativeToGlobalBounds(const ui::AXNode* node,
685 bool& offscreen,
686 bool clip_bounds) {
687 return tree_->RelativeToTreeBounds(node, gfx::RectF(), &offscreen,
688 clip_bounds);
689 }
690
691 ui::AXNode* AccessibilityBridge::GetNodeFromTree(
692 ui::AXTreeID tree_id,
693 ui::AXNode::AXID node_id) const {
694 return GetNodeFromTree(node_id);
695 }
696
697 ui::AXNode* AccessibilityBridge::GetNodeFromTree(
698 ui::AXNode::AXID node_id) const {
699 return tree_->GetFromId(node_id);
700 }
701
702 ui::AXTreeID AccessibilityBridge::GetTreeID() const {
703 return tree_->GetAXTreeID();
704 }
705
706 ui::AXTreeID AccessibilityBridge::GetParentTreeID() const {
707 return ui::AXTreeIDUnknown();
708 }
709
710 ui::AXNode* AccessibilityBridge::GetRootAsAXNode() const {
711 return tree_->root();
712 }
713
714 ui::AXNode* AccessibilityBridge::GetParentNodeFromParentTreeAsAXNode() const {
715 return nullptr;
716 }
717
718 ui::AXTree* AccessibilityBridge::GetTree() const {
719 return tree_.get();
720 }
721
722 ui::AXPlatformNode* AccessibilityBridge::GetPlatformNodeFromTree(
723 const ui::AXNode::AXID node_id) const {
724 auto platform_delegate_weak = GetFlutterPlatformNodeDelegateFromID(node_id);
725 auto platform_delegate = platform_delegate_weak.lock();
726 if (!platform_delegate) {
727 return nullptr;
728 }
729 return platform_delegate->GetPlatformNode();
730 }
731
732 ui::AXPlatformNode* AccessibilityBridge::GetPlatformNodeFromTree(
733 const ui::AXNode& node) const {
734 return GetPlatformNodeFromTree(node.id());
735 }
736
737 ui::AXPlatformNodeDelegate* AccessibilityBridge::RootDelegate() const {
738 return GetFlutterPlatformNodeDelegateFromID(GetRootAsAXNode()->id())
739 .lock()
740 .get();
741 }
742
743 } // namespace flutter
platform_view
std::unique_ptr< flutter::PlatformViewIOS > platform_view
Definition: FlutterEnginePlatformViewTest.mm:68
flutter::AccessibilityBridge::GetTree
ui::AXTree * GetTree() const override
Definition: accessibility_bridge.cc:718
flutter::AccessibilityBridge::RootDelegate
ui::AXPlatformNodeDelegate * RootDelegate() const override
Definition: accessibility_bridge.cc:737
flutter::AccessibilityBridge::GetFlutterPlatformNodeDelegateFromID
std::weak_ptr< FlutterPlatformNodeDelegate > GetFlutterPlatformNodeDelegateFromID(AccessibilityNodeId id) const
Get the flutter platform node delegate with the given id from this accessibility bridge....
Definition: accessibility_bridge.cc:131
flutter::AccessibilityBridge::CreateFlutterPlatformNodeDelegate
virtual std::shared_ptr< FlutterPlatformNodeDelegate > CreateFlutterPlatformNodeDelegate()=0
Creates a platform specific FlutterPlatformNodeDelegate. Ownership passes to the caller....
flutter::AccessibilityBridge::AddFlutterSemanticsNodeUpdate
void AddFlutterSemanticsNodeUpdate(const FlutterSemanticsNode2 &node)
Adds a semantics node update to the pending semantics update. Calling this method alone will NOT upda...
Definition: accessibility_bridge.cc:39
flutter::AccessibilityBridge::AddFlutterSemanticsCustomActionUpdate
void AddFlutterSemanticsCustomActionUpdate(const FlutterSemanticsCustomAction2 &action)
Adds a custom semantics action update to the pending semantics update. Calling this method alone will...
Definition: accessibility_bridge.cc:44
flutter::AccessibilityBridge::GetPlatformNodeFromTree
ui::AXPlatformNode * GetPlatformNodeFromTree(const ui::AXNode::AXID node_id) const override
Definition: accessibility_bridge.cc:722
flutter::AccessibilityBridge::GetAXTreeData
const ui::AXTreeData & GetAXTreeData() const
Get the ax tree data from this accessibility bridge. The tree data contains information such as the i...
Definition: accessibility_bridge.cc:141
flutter::AccessibilityBridge::GetParentTreeID
ui::AXTreeID GetParentTreeID() const override
Definition: accessibility_bridge.cc:706
flutter::AccessibilityBridge::GetRootAsAXNode
ui::AXNode * GetRootAsAXNode() const override
Definition: accessibility_bridge.cc:710
flutter::AccessibilityBridge::~AccessibilityBridge
~AccessibilityBridge()
Definition: accessibility_bridge.cc:34
flutter::AccessibilityBridge::GetParentNodeFromParentTreeAsAXNode
ui::AXNode * GetParentNodeFromParentTreeAsAXNode() const override
Definition: accessibility_bridge.cc:714
flutter::AccessibilityBridge::AccessibilityBridge
AccessibilityBridge()
Creates a new instance of a accessibility bridge.
Definition: accessibility_bridge.cc:23
flutter::AccessibilityBridge::OnAccessibilityEvent
virtual void OnAccessibilityEvent(ui::AXEventGenerator::TargetedEvent targeted_event)=0
Handle accessibility events generated due to accessibility tree changes. These events are needed to b...
flutter::AccessibilityBridge::GetTreeID
ui::AXTreeID GetTreeID() const override
Definition: accessibility_bridge.cc:702
flutter::AccessibilityBridge::GetPendingEvents
const std::vector< ui::AXEventGenerator::TargetedEvent > GetPendingEvents() const
Gets all pending accessibility events generated during semantics updates. This is useful when decidin...
Definition: accessibility_bridge.cc:146
flutter::AccessibilityBridge::CommitUpdates
void CommitUpdates()
Flushes the pending updates and applies them to this accessibility bridge. Calling this with no pendi...
Definition: accessibility_bridge.cc:50
flutter::AccessibilityBridge::GetNodeFromTree
ui::AXNode * GetNodeFromTree(const ui::AXTreeID tree_id, const ui::AXNode::AXID node_id) const override
Definition: accessibility_bridge.cc:691
flutter::FlutterPlatformNodeDelegate::OwnerBridge::DispatchAccessibilityAction
virtual void DispatchAccessibilityAction(AccessibilityNodeId target, FlutterSemanticsAction action, fml::MallocMapping data)=0
Dispatch accessibility action back to the Flutter framework. These actions are generated in the nativ...
accessibility_bridge.h
flutter
Definition: accessibility_bridge.h:26
flutter::kHasScrollingAction
constexpr int kHasScrollingAction
Definition: accessibility_bridge.cc:16
flutter::AccessibilityNodeId
ui::AXNode::AXID AccessibilityNodeId
Definition: flutter_platform_node_delegate.h:15