Flutter iOS Embedder
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
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
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 
35  event_generator_.ReleaseTree();
36  tree_->RemoveObserver(static_cast<ui::AXTreeObserver*>(this));
37 }
38 
40  const FlutterSemanticsNode2& node) {
41  pending_semantics_node_updates_[node.id] = FromFlutterSemanticsNode(node);
42 }
43 
45  const FlutterSemanticsCustomAction2& action) {
46  pending_semantics_custom_action_updates_[action.id] =
47  FromFlutterSemanticsCustomAction(action);
48 }
49 
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>
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>
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  SetNameFromFlutterUpdate(node_data, node);
295  SetValueFromFlutterUpdate(node_data, node);
296  SetTooltipFromFlutterUpdate(node_data, node);
297  node_data.relative_bounds.bounds.SetRect(node.rect.left, node.rect.top,
298  node.rect.right - node.rect.left,
299  node.rect.bottom - node.rect.top);
300  node_data.relative_bounds.transform = std::make_unique<gfx::Transform>(
301  node.transform.scaleX, node.transform.skewX, node.transform.transX, 0,
302  node.transform.skewY, node.transform.scaleY, node.transform.transY, 0,
303  node.transform.pers0, node.transform.pers1, node.transform.pers2, 0, 0, 0,
304  0, 0);
305  for (auto child : node.children_in_traversal_order) {
306  node_data.child_ids.push_back(child);
307  }
308  SetTreeData(node, tree_update);
309  tree_update.nodes.push_back(node_data);
310 }
311 
312 void AccessibilityBridge::SetRoleFromFlutterUpdate(ui::AXNodeData& node_data,
313  const SemanticsNode& node) {
314  FlutterSemanticsFlag flags = node.flags;
315  if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsButton) {
316  node_data.role = ax::mojom::Role::kButton;
317  return;
318  }
319  if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField &&
320  !(flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly)) {
321  node_data.role = ax::mojom::Role::kTextField;
322  return;
323  }
324  if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsHeader) {
325  node_data.role = ax::mojom::Role::kHeader;
326  return;
327  }
328  if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsImage) {
329  node_data.role = ax::mojom::Role::kImage;
330  return;
331  }
332  if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsLink) {
333  node_data.role = ax::mojom::Role::kLink;
334  return;
335  }
336 
337  if (flags & kFlutterSemanticsFlagIsInMutuallyExclusiveGroup &&
338  flags & kFlutterSemanticsFlagHasCheckedState) {
339  node_data.role = ax::mojom::Role::kRadioButton;
340  return;
341  }
342  if (flags & kFlutterSemanticsFlagHasCheckedState) {
343  node_data.role = ax::mojom::Role::kCheckBox;
344  return;
345  }
346  if (flags & kFlutterSemanticsFlagHasToggledState) {
347  node_data.role = ax::mojom::Role::kSwitch;
348  return;
349  }
350  if (flags & kFlutterSemanticsFlagIsSlider) {
351  node_data.role = ax::mojom::Role::kSlider;
352  return;
353  }
354  // If the state cannot be derived from the flutter flags, we fallback to group
355  // or static text.
356  if (node.children_in_traversal_order.empty()) {
357  node_data.role = ax::mojom::Role::kStaticText;
358  } else {
359  node_data.role = ax::mojom::Role::kGroup;
360  }
361 }
362 
363 void AccessibilityBridge::SetStateFromFlutterUpdate(ui::AXNodeData& node_data,
364  const SemanticsNode& node) {
365  FlutterSemanticsFlag flags = node.flags;
366  FlutterSemanticsAction actions = node.actions;
367  if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagHasExpandedState &&
368  flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsExpanded) {
369  node_data.AddState(ax::mojom::State::kExpanded);
370  } else if (flags &
371  FlutterSemanticsFlag::kFlutterSemanticsFlagHasExpandedState) {
372  node_data.AddState(ax::mojom::State::kCollapsed);
373  }
374  if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField &&
375  (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly) == 0) {
376  node_data.AddState(ax::mojom::State::kEditable);
377  }
378  if (node_data.role == ax::mojom::Role::kStaticText &&
379  (actions & kHasScrollingAction) == 0 && node.value.empty() &&
380  node.label.empty() && node.hint.empty()) {
381  node_data.AddState(ax::mojom::State::kIgnored);
382  } else {
383  // kFlutterSemanticsFlagIsFocusable means a keyboard focusable, it is
384  // different from semantics focusable.
385  // TODO(chunhtai): figure out whether something is not semantics focusable.
386  node_data.AddState(ax::mojom::State::kFocusable);
387  }
388 }
389 
390 void AccessibilityBridge::SetActionsFromFlutterUpdate(
391  ui::AXNodeData& node_data,
392  const SemanticsNode& node) {
393  FlutterSemanticsAction actions = node.actions;
394  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionTap) {
395  node_data.AddAction(ax::mojom::Action::kDoDefault);
396  }
397  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionScrollLeft) {
398  node_data.AddAction(ax::mojom::Action::kScrollLeft);
399  }
400  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionScrollRight) {
401  node_data.AddAction(ax::mojom::Action::kScrollRight);
402  }
403  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionScrollUp) {
404  node_data.AddAction(ax::mojom::Action::kScrollUp);
405  }
406  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionScrollDown) {
407  node_data.AddAction(ax::mojom::Action::kScrollDown);
408  }
409  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionIncrease) {
410  node_data.AddAction(ax::mojom::Action::kIncrement);
411  }
412  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionDecrease) {
413  node_data.AddAction(ax::mojom::Action::kDecrement);
414  }
415  // Every node has show on screen action.
416  node_data.AddAction(ax::mojom::Action::kScrollToMakeVisible);
417 
418  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionSetSelection) {
419  node_data.AddAction(ax::mojom::Action::kSetSelection);
420  }
421  if (actions & FlutterSemanticsAction::
422  kFlutterSemanticsActionDidGainAccessibilityFocus) {
423  node_data.AddAction(ax::mojom::Action::kSetAccessibilityFocus);
424  }
425  if (actions & FlutterSemanticsAction::
426  kFlutterSemanticsActionDidLoseAccessibilityFocus) {
427  node_data.AddAction(ax::mojom::Action::kClearAccessibilityFocus);
428  }
429  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionCustomAction) {
430  node_data.AddAction(ax::mojom::Action::kCustomAction);
431  }
432 }
433 
434 void AccessibilityBridge::SetBooleanAttributesFromFlutterUpdate(
435  ui::AXNodeData& node_data,
436  const SemanticsNode& node) {
437  FlutterSemanticsAction actions = node.actions;
438  FlutterSemanticsFlag flags = node.flags;
439  node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kScrollable,
440  actions & kHasScrollingAction);
441  node_data.AddBoolAttribute(
442  ax::mojom::BoolAttribute::kClickable,
443  actions & FlutterSemanticsAction::kFlutterSemanticsActionTap);
444  // TODO(chunhtai): figure out if there is a node that does not clip overflow.
445  node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kClipsChildren,
446  !node.children_in_traversal_order.empty());
447  node_data.AddBoolAttribute(
448  ax::mojom::BoolAttribute::kSelected,
449  flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsSelected);
450  node_data.AddBoolAttribute(
451  ax::mojom::BoolAttribute::kEditableRoot,
452  flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField &&
453  (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly) == 0);
454  // Mark nodes as line breaking so that screen readers don't
455  // merge all consecutive objects into one.
456  // TODO(schectman): When should a node have this attribute set?
457  // https://github.com/flutter/flutter/issues/118184
458  node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
459  true);
460 }
461 
462 void AccessibilityBridge::SetIntAttributesFromFlutterUpdate(
463  ui::AXNodeData& node_data,
464  const SemanticsNode& node) {
465  FlutterSemanticsFlag flags = node.flags;
466  node_data.AddIntAttribute(ax::mojom::IntAttribute::kTextDirection,
467  node.text_direction);
468 
469  int sel_start = node.text_selection_base;
470  int sel_end = node.text_selection_extent;
471  if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField &&
472  (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly) == 0 &&
473  !node.value.empty()) {
474  // By default the text field selection should be at the end.
475  sel_start = sel_start == -1 ? node.value.length() : sel_start;
476  sel_end = sel_end == -1 ? node.value.length() : sel_end;
477  }
478  node_data.AddIntAttribute(ax::mojom::IntAttribute::kTextSelStart, sel_start);
479  node_data.AddIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, sel_end);
480 
481  if (node_data.role == ax::mojom::Role::kRadioButton ||
482  node_data.role == ax::mojom::Role::kCheckBox) {
483  node_data.AddIntAttribute(
484  ax::mojom::IntAttribute::kCheckedState,
485  static_cast<int32_t>(
486  flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsCheckStateMixed
487  ? ax::mojom::CheckedState::kMixed
488  : flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsChecked
489  ? ax::mojom::CheckedState::kTrue
490  : ax::mojom::CheckedState::kFalse));
491  } else if (node_data.role == ax::mojom::Role::kSwitch) {
492  node_data.AddIntAttribute(
493  ax::mojom::IntAttribute::kCheckedState,
494  static_cast<int32_t>(
495  flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsToggled
496  ? ax::mojom::CheckedState::kTrue
497  : ax::mojom::CheckedState::kFalse));
498  }
499 }
500 
501 void AccessibilityBridge::SetIntListAttributesFromFlutterUpdate(
502  ui::AXNodeData& node_data,
503  const SemanticsNode& node) {
504  FlutterSemanticsAction actions = node.actions;
505  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionCustomAction) {
506  std::vector<int32_t> custom_action_ids;
507  for (size_t i = 0; i < node.custom_accessibility_actions.size(); i++) {
508  custom_action_ids.push_back(node.custom_accessibility_actions[i]);
509  }
510  node_data.AddIntListAttribute(ax::mojom::IntListAttribute::kCustomActionIds,
511  custom_action_ids);
512  }
513 }
514 
515 void AccessibilityBridge::SetStringListAttributesFromFlutterUpdate(
516  ui::AXNodeData& node_data,
517  const SemanticsNode& node) {
518  FlutterSemanticsAction actions = node.actions;
519  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionCustomAction) {
520  std::vector<std::string> custom_action_description;
521  for (size_t i = 0; i < node.custom_accessibility_actions.size(); i++) {
522  auto iter = pending_semantics_custom_action_updates_.find(
523  node.custom_accessibility_actions[i]);
524  BASE_DCHECK(iter != pending_semantics_custom_action_updates_.end());
525  custom_action_description.push_back(iter->second.label);
526  }
527  node_data.AddStringListAttribute(
528  ax::mojom::StringListAttribute::kCustomActionDescriptions,
529  custom_action_description);
530  }
531 }
532 
533 void AccessibilityBridge::SetNameFromFlutterUpdate(ui::AXNodeData& node_data,
534  const SemanticsNode& node) {
535  node_data.SetName(node.label);
536 }
537 
538 void AccessibilityBridge::SetValueFromFlutterUpdate(ui::AXNodeData& node_data,
539  const SemanticsNode& node) {
540  node_data.SetValue(node.value);
541 }
542 
543 void AccessibilityBridge::SetTooltipFromFlutterUpdate(
544  ui::AXNodeData& node_data,
545  const SemanticsNode& node) {
546  node_data.SetTooltip(node.tooltip);
547 }
548 
549 void AccessibilityBridge::SetTreeData(const SemanticsNode& node,
550  ui::AXTreeUpdate& tree_update) {
551  FlutterSemanticsFlag flags = node.flags;
552  // Set selection of the focused node if:
553  // 1. this text field has a valid selection
554  // 2. this text field doesn't have a valid selection but had selection stored
555  // in the tree.
556  if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField &&
557  flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsFocused) {
558  if (node.text_selection_base != -1) {
559  tree_update.tree_data.sel_anchor_object_id = node.id;
560  tree_update.tree_data.sel_anchor_offset = node.text_selection_base;
561  tree_update.tree_data.sel_focus_object_id = node.id;
562  tree_update.tree_data.sel_focus_offset = node.text_selection_extent;
563  tree_update.has_tree_data = true;
564  } else if (tree_update.tree_data.sel_anchor_object_id == node.id) {
565  tree_update.tree_data.sel_anchor_object_id = ui::AXNode::kInvalidAXID;
566  tree_update.tree_data.sel_anchor_offset = -1;
567  tree_update.tree_data.sel_focus_object_id = ui::AXNode::kInvalidAXID;
568  tree_update.tree_data.sel_focus_offset = -1;
569  tree_update.has_tree_data = true;
570  }
571  }
572 
573  if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsFocused &&
574  tree_update.tree_data.focus_id != node.id) {
575  tree_update.tree_data.focus_id = node.id;
576  tree_update.has_tree_data = true;
577  } else if ((flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsFocused) ==
578  0 &&
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  result.flags = flutter_node.flags;
591  result.actions = flutter_node.actions;
592  result.text_selection_base = flutter_node.text_selection_base;
593  result.text_selection_extent = flutter_node.text_selection_extent;
594  result.scroll_child_count = flutter_node.scroll_child_count;
595  result.scroll_index = flutter_node.scroll_index;
596  result.scroll_position = flutter_node.scroll_position;
597  result.scroll_extent_max = flutter_node.scroll_extent_max;
598  result.scroll_extent_min = flutter_node.scroll_extent_min;
599  result.elevation = flutter_node.elevation;
600  result.thickness = flutter_node.thickness;
601  if (flutter_node.label) {
602  result.label = std::string(flutter_node.label);
603  }
604  if (flutter_node.hint) {
605  result.hint = std::string(flutter_node.hint);
606  }
607  if (flutter_node.value) {
608  result.value = std::string(flutter_node.value);
609  }
610  if (flutter_node.increased_value) {
611  result.increased_value = std::string(flutter_node.increased_value);
612  }
613  if (flutter_node.decreased_value) {
614  result.decreased_value = std::string(flutter_node.decreased_value);
615  }
616  if (flutter_node.tooltip) {
617  result.tooltip = std::string(flutter_node.tooltip);
618  }
619  result.text_direction = flutter_node.text_direction;
620  result.rect = flutter_node.rect;
621  result.transform = flutter_node.transform;
622  if (flutter_node.child_count > 0) {
623  result.children_in_traversal_order = std::vector<int32_t>(
624  flutter_node.children_in_traversal_order,
625  flutter_node.children_in_traversal_order + flutter_node.child_count);
626  }
627  if (flutter_node.custom_accessibility_actions_count > 0) {
628  result.custom_accessibility_actions = std::vector<int32_t>(
629  flutter_node.custom_accessibility_actions,
630  flutter_node.custom_accessibility_actions +
631  flutter_node.custom_accessibility_actions_count);
632  }
633  return result;
634 }
635 
636 AccessibilityBridge::SemanticsCustomAction
637 AccessibilityBridge::FromFlutterSemanticsCustomAction(
638  const FlutterSemanticsCustomAction2& flutter_custom_action) {
639  SemanticsCustomAction result;
640  result.id = flutter_custom_action.id;
641  result.override_action = flutter_custom_action.override_action;
642  if (flutter_custom_action.label) {
643  result.label = std::string(flutter_custom_action.label);
644  }
645  if (flutter_custom_action.hint) {
646  result.hint = std::string(flutter_custom_action.hint);
647  }
648  return result;
649 }
650 
651 void AccessibilityBridge::SetLastFocusedId(AccessibilityNodeId node_id) {
652  if (last_focused_id_ != node_id) {
653  auto last_focused_child =
654  GetFlutterPlatformNodeDelegateFromID(last_focused_id_);
655  if (!last_focused_child.expired()) {
657  last_focused_id_,
658  FlutterSemanticsAction::
659  kFlutterSemanticsActionDidLoseAccessibilityFocus,
660  {});
661  }
662  last_focused_id_ = node_id;
663  }
664 }
665 
666 AccessibilityNodeId AccessibilityBridge::GetLastFocusedId() {
667  return last_focused_id_;
668 }
669 
670 gfx::NativeViewAccessible AccessibilityBridge::GetNativeAccessibleFromId(
671  AccessibilityNodeId id) {
672  auto platform_node_delegate = GetFlutterPlatformNodeDelegateFromID(id).lock();
673  if (!platform_node_delegate) {
674  return nullptr;
675  }
676  return platform_node_delegate->GetNativeViewAccessible();
677 }
678 
679 gfx::RectF AccessibilityBridge::RelativeToGlobalBounds(const ui::AXNode* node,
680  bool& offscreen,
681  bool clip_bounds) {
682  return tree_->RelativeToTreeBounds(node, gfx::RectF(), &offscreen,
683  clip_bounds);
684 }
685 
687  ui::AXTreeID tree_id,
688  ui::AXNode::AXID node_id) const {
689  return GetNodeFromTree(node_id);
690 }
691 
693  ui::AXNode::AXID node_id) const {
694  return tree_->GetFromId(node_id);
695 }
696 
697 ui::AXTreeID AccessibilityBridge::GetTreeID() const {
698  return tree_->GetAXTreeID();
699 }
700 
702  return ui::AXTreeIDUnknown();
703 }
704 
706  return tree_->root();
707 }
708 
710  return nullptr;
711 }
712 
713 ui::AXTree* AccessibilityBridge::GetTree() const {
714  return tree_.get();
715 }
716 
718  const ui::AXNode::AXID node_id) const {
719  auto platform_delegate_weak = GetFlutterPlatformNodeDelegateFromID(node_id);
720  auto platform_delegate = platform_delegate_weak.lock();
721  if (!platform_delegate) {
722  return nullptr;
723  }
724  return platform_delegate->GetPlatformNode();
725 }
726 
728  const ui::AXNode& node) const {
729  return GetPlatformNodeFromTree(node.id());
730 }
731 
732 ui::AXPlatformNodeDelegate* AccessibilityBridge::RootDelegate() const {
734  .lock()
735  .get();
736 }
737 
738 } // namespace flutter
std::unique_ptr< flutter::PlatformViewIOS > platform_view
ui::AXTree * GetTree() const override
ui::AXPlatformNodeDelegate * RootDelegate() const override
std::weak_ptr< FlutterPlatformNodeDelegate > GetFlutterPlatformNodeDelegateFromID(AccessibilityNodeId id) const
Get the flutter platform node delegate with the given id from this accessibility bridge....
virtual std::shared_ptr< FlutterPlatformNodeDelegate > CreateFlutterPlatformNodeDelegate()=0
Creates a platform specific FlutterPlatformNodeDelegate. Ownership passes to the caller....
void AddFlutterSemanticsNodeUpdate(const FlutterSemanticsNode2 &node)
Adds a semantics node update to the pending semantics update. Calling this method alone will NOT upda...
void AddFlutterSemanticsCustomActionUpdate(const FlutterSemanticsCustomAction2 &action)
Adds a custom semantics action update to the pending semantics update. Calling this method alone will...
ui::AXPlatformNode * GetPlatformNodeFromTree(const ui::AXNode::AXID node_id) const override
const ui::AXTreeData & GetAXTreeData() const
Get the ax tree data from this accessibility bridge. The tree data contains information such as the i...
ui::AXTreeID GetParentTreeID() const override
ui::AXNode * GetRootAsAXNode() const override
ui::AXNode * GetParentNodeFromParentTreeAsAXNode() const override
AccessibilityBridge()
Creates a new instance of a accessibility bridge.
virtual void OnAccessibilityEvent(ui::AXEventGenerator::TargetedEvent targeted_event)=0
Handle accessibility events generated due to accessibility tree changes. These events are needed to b...
ui::AXTreeID GetTreeID() const override
const std::vector< ui::AXEventGenerator::TargetedEvent > GetPendingEvents() const
Gets all pending accessibility events generated during semantics updates. This is useful when decidin...
void CommitUpdates()
Flushes the pending updates and applies them to this accessibility bridge. Calling this with no pendi...
ui::AXNode * GetNodeFromTree(const ui::AXTreeID tree_id, const ui::AXNode::AXID node_id) const override
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...
constexpr int kHasScrollingAction
ui::AXNode::AXID AccessibilityNodeId