Flutter Windows 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
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  const FlutterSemanticsFlags* flags = node.flags;
315  FML_DCHECK(flags) << "SemanticsNode::flags must not be null";
316  if (flags->is_button) {
317  node_data.role = ax::mojom::Role::kButton;
318  return;
319  }
320  if (flags->is_text_field && !flags->is_read_only) {
321  node_data.role = ax::mojom::Role::kTextField;
322  return;
323  }
324  if (flags->is_header) {
325  node_data.role = ax::mojom::Role::kHeader;
326  return;
327  }
328  if (flags->is_image) {
329  node_data.role = ax::mojom::Role::kImage;
330  return;
331  }
332  if (flags->is_link) {
333  node_data.role = ax::mojom::Role::kLink;
334  return;
335  }
336 
337  if (flags->is_in_mutually_exclusive_group &&
338  flags->is_checked != FlutterCheckState::kFlutterCheckStateNone) {
339  node_data.role = ax::mojom::Role::kRadioButton;
340  return;
341  }
342  if (flags->is_checked != FlutterCheckState::kFlutterCheckStateNone) {
343  node_data.role = ax::mojom::Role::kCheckBox;
344  return;
345  }
346  if (flags->is_toggled != FlutterTristate::kFlutterTristateNone) {
347  node_data.role = ax::mojom::Role::kSwitch;
348  return;
349  }
350  if (flags->is_slider) {
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  const FlutterSemanticsFlags* flags = node.flags;
366  FlutterSemanticsAction actions = node.actions;
367  if (flags->is_expanded == FlutterTristate::kFlutterTristateTrue) {
368  node_data.AddState(ax::mojom::State::kExpanded);
369  } else if (flags->is_expanded == FlutterTristate::kFlutterTristateFalse) {
370  node_data.AddState(ax::mojom::State::kCollapsed);
371  }
372  if (flags->is_text_field && !flags->is_read_only) {
373  node_data.AddState(ax::mojom::State::kEditable);
374  }
375  if (node_data.role == ax::mojom::Role::kStaticText &&
376  (actions & kHasScrollingAction) == 0 && node.value.empty() &&
377  node.label.empty() && node.hint.empty()) {
378  node_data.AddState(ax::mojom::State::kIgnored);
379  } else {
380  // kFlutterSemanticsFlagIsFocusable means a keyboard focusable, it is
381  // different from semantics focusable.
382  // TODO(chunhtai): figure out whether something is not semantics focusable.
383  node_data.AddState(ax::mojom::State::kFocusable);
384  }
385 }
386 
387 void AccessibilityBridge::SetActionsFromFlutterUpdate(
388  ui::AXNodeData& node_data,
389  const SemanticsNode& node) {
390  FlutterSemanticsAction actions = node.actions;
391  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionTap) {
392  node_data.AddAction(ax::mojom::Action::kDoDefault);
393  }
394  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionScrollLeft) {
395  node_data.AddAction(ax::mojom::Action::kScrollLeft);
396  }
397  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionScrollRight) {
398  node_data.AddAction(ax::mojom::Action::kScrollRight);
399  }
400  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionScrollUp) {
401  node_data.AddAction(ax::mojom::Action::kScrollUp);
402  }
403  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionScrollDown) {
404  node_data.AddAction(ax::mojom::Action::kScrollDown);
405  }
406  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionIncrease) {
407  node_data.AddAction(ax::mojom::Action::kIncrement);
408  }
409  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionDecrease) {
410  node_data.AddAction(ax::mojom::Action::kDecrement);
411  }
412  // Every node has show on screen action.
413  node_data.AddAction(ax::mojom::Action::kScrollToMakeVisible);
414 
415  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionSetSelection) {
416  node_data.AddAction(ax::mojom::Action::kSetSelection);
417  }
418  if (actions & FlutterSemanticsAction::
419  kFlutterSemanticsActionDidGainAccessibilityFocus) {
420  node_data.AddAction(ax::mojom::Action::kSetAccessibilityFocus);
421  }
422  if (actions & FlutterSemanticsAction::
423  kFlutterSemanticsActionDidLoseAccessibilityFocus) {
424  node_data.AddAction(ax::mojom::Action::kClearAccessibilityFocus);
425  }
426  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionCustomAction) {
427  node_data.AddAction(ax::mojom::Action::kCustomAction);
428  }
429 }
430 
431 void AccessibilityBridge::SetBooleanAttributesFromFlutterUpdate(
432  ui::AXNodeData& node_data,
433  const SemanticsNode& node) {
434  FlutterSemanticsAction actions = node.actions;
435  const FlutterSemanticsFlags* flags = node.flags;
436  node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kScrollable,
437  actions & kHasScrollingAction);
438  node_data.AddBoolAttribute(
439  ax::mojom::BoolAttribute::kClickable,
440  actions & FlutterSemanticsAction::kFlutterSemanticsActionTap);
441  // TODO(chunhtai): figure out if there is a node that does not clip overflow.
442  node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kClipsChildren,
443  !node.children_in_traversal_order.empty());
444  node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected,
445  flags->is_selected);
446  node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot,
447  flags->is_text_field && !flags->is_read_only);
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);
454 }
455 
456 void AccessibilityBridge::SetIntAttributesFromFlutterUpdate(
457  ui::AXNodeData& node_data,
458  const SemanticsNode& node) {
459  const FlutterSemanticsFlags* flags = node.flags;
460  node_data.AddIntAttribute(ax::mojom::IntAttribute::kTextDirection,
461  node.text_direction);
462 
463  int sel_start = node.text_selection_base;
464  int sel_end = node.text_selection_extent;
465  if (flags->is_text_field && !flags->is_read_only && !node.value.empty()) {
466  // By default the text field selection should be at the end.
467  sel_start = sel_start == -1 ? node.value.length() : sel_start;
468  sel_end = sel_end == -1 ? node.value.length() : sel_end;
469  }
470  node_data.AddIntAttribute(ax::mojom::IntAttribute::kTextSelStart, sel_start);
471  node_data.AddIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, sel_end);
472 
473  if (node_data.role == ax::mojom::Role::kRadioButton ||
474  node_data.role == ax::mojom::Role::kCheckBox) {
475  node_data.AddIntAttribute(
476  ax::mojom::IntAttribute::kCheckedState,
477  static_cast<int32_t>(
478  (flags->is_checked == FlutterCheckState::kFlutterCheckStateMixed)
479  ? ax::mojom::CheckedState::kMixed
480  : (flags->is_checked == FlutterCheckState::kFlutterCheckStateTrue)
481  ? ax::mojom::CheckedState::kTrue
482  : ax::mojom::CheckedState::kFalse));
483  } else if (node_data.role == ax::mojom::Role::kSwitch) {
484  node_data.AddIntAttribute(
485  ax::mojom::IntAttribute::kCheckedState,
486  static_cast<int32_t>(
487  (flags->is_toggled == FlutterTristate::kFlutterTristateTrue)
488  ? ax::mojom::CheckedState::kTrue
489  : ax::mojom::CheckedState::kFalse));
490  }
491 }
492 
493 void AccessibilityBridge::SetIntListAttributesFromFlutterUpdate(
494  ui::AXNodeData& node_data,
495  const SemanticsNode& node) {
496  FlutterSemanticsAction actions = node.actions;
497  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionCustomAction) {
498  std::vector<int32_t> custom_action_ids;
499  custom_action_ids.reserve(node.custom_accessibility_actions.size());
500  for (size_t i = 0; i < node.custom_accessibility_actions.size(); i++) {
501  custom_action_ids.push_back(node.custom_accessibility_actions[i]);
502  }
503  node_data.AddIntListAttribute(ax::mojom::IntListAttribute::kCustomActionIds,
504  custom_action_ids);
505  }
506 }
507 
508 void AccessibilityBridge::SetStringListAttributesFromFlutterUpdate(
509  ui::AXNodeData& node_data,
510  const SemanticsNode& node) {
511  FlutterSemanticsAction actions = node.actions;
512  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionCustomAction) {
513  std::vector<std::string> custom_action_description;
514  for (size_t i = 0; i < node.custom_accessibility_actions.size(); i++) {
515  auto iter = pending_semantics_custom_action_updates_.find(
516  node.custom_accessibility_actions[i]);
517  BASE_DCHECK(iter != pending_semantics_custom_action_updates_.end());
518  custom_action_description.push_back(iter->second.label);
519  }
520  node_data.AddStringListAttribute(
521  ax::mojom::StringListAttribute::kCustomActionDescriptions,
522  custom_action_description);
523  }
524 }
525 
526 void AccessibilityBridge::SetNameFromFlutterUpdate(ui::AXNodeData& node_data,
527  const SemanticsNode& node) {
528  node_data.SetName(node.label);
529 }
530 
531 void AccessibilityBridge::SetValueFromFlutterUpdate(ui::AXNodeData& node_data,
532  const SemanticsNode& node) {
533  node_data.SetValue(node.value);
534 }
535 
536 void AccessibilityBridge::SetTooltipFromFlutterUpdate(
537  ui::AXNodeData& node_data,
538  const SemanticsNode& node) {
539  node_data.SetTooltip(node.tooltip);
540 }
541 
542 void AccessibilityBridge::SetTreeData(const SemanticsNode& node,
543  ui::AXTreeUpdate& tree_update) {
544  const FlutterSemanticsFlags* flags = node.flags;
545  // Set selection of the focused node if:
546  // 1. this text field has a valid selection
547  // 2. this text field doesn't have a valid selection but had selection stored
548  // in the tree.
549  if (flags->is_text_field &&
550  flags->is_focused == FlutterTristate::kFlutterTristateTrue) {
551  if (node.text_selection_base != -1) {
552  tree_update.tree_data.sel_anchor_object_id = node.id;
553  tree_update.tree_data.sel_anchor_offset = node.text_selection_base;
554  tree_update.tree_data.sel_focus_object_id = node.id;
555  tree_update.tree_data.sel_focus_offset = node.text_selection_extent;
556  tree_update.has_tree_data = true;
557  } else if (tree_update.tree_data.sel_anchor_object_id == node.id) {
558  tree_update.tree_data.sel_anchor_object_id = ui::AXNode::kInvalidAXID;
559  tree_update.tree_data.sel_anchor_offset = -1;
560  tree_update.tree_data.sel_focus_object_id = ui::AXNode::kInvalidAXID;
561  tree_update.tree_data.sel_focus_offset = -1;
562  tree_update.has_tree_data = true;
563  }
564  }
565 
566  if (flags->is_focused == FlutterTristate::kFlutterTristateTrue &&
567  tree_update.tree_data.focus_id != node.id) {
568  tree_update.tree_data.focus_id = node.id;
569  tree_update.has_tree_data = true;
570  } else if (flags->is_focused != FlutterTristate::kFlutterTristateTrue &&
571  tree_update.tree_data.focus_id == node.id) {
572  tree_update.tree_data.focus_id = ui::AXNode::kInvalidAXID;
573  tree_update.has_tree_data = true;
574  }
575 }
576 
577 AccessibilityBridge::SemanticsNode
578 AccessibilityBridge::FromFlutterSemanticsNode(
579  const FlutterSemanticsNode2& flutter_node) {
580  SemanticsNode result;
581  result.id = flutter_node.id;
582  FML_DCHECK(flutter_node.flags2)
583  << "FlutterSemanticsNode2::flags2 must not be null";
584 
585  result.flags = flutter_node.flags2;
586  result.actions = flutter_node.actions;
587  result.heading_level = flutter_node.heading_level;
588  result.text_selection_base = flutter_node.text_selection_base;
589  result.text_selection_extent = flutter_node.text_selection_extent;
590  result.scroll_child_count = flutter_node.scroll_child_count;
591  result.scroll_index = flutter_node.scroll_index;
592  result.scroll_position = flutter_node.scroll_position;
593  result.scroll_extent_max = flutter_node.scroll_extent_max;
594  result.scroll_extent_min = flutter_node.scroll_extent_min;
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) {
617  result.children_in_traversal_order = std::vector<int32_t>(
618  flutter_node.children_in_traversal_order,
619  flutter_node.children_in_traversal_order + flutter_node.child_count);
620  }
621  if (flutter_node.custom_accessibility_actions_count > 0) {
622  result.custom_accessibility_actions = std::vector<int32_t>(
623  flutter_node.custom_accessibility_actions,
624  flutter_node.custom_accessibility_actions +
625  flutter_node.custom_accessibility_actions_count);
626  }
627  return result;
628 }
629 
630 AccessibilityBridge::SemanticsCustomAction
631 AccessibilityBridge::FromFlutterSemanticsCustomAction(
632  const FlutterSemanticsCustomAction2& flutter_custom_action) {
633  SemanticsCustomAction result;
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);
638  }
639  if (flutter_custom_action.hint) {
640  result.hint = std::string(flutter_custom_action.hint);
641  }
642  return result;
643 }
644 
645 void AccessibilityBridge::SetLastFocusedId(AccessibilityNodeId node_id) {
646  if (last_focused_id_ != node_id) {
647  auto last_focused_child =
648  GetFlutterPlatformNodeDelegateFromID(last_focused_id_);
649  if (!last_focused_child.expired()) {
651  last_focused_id_,
652  FlutterSemanticsAction::
653  kFlutterSemanticsActionDidLoseAccessibilityFocus,
654  {});
655  }
656  last_focused_id_ = node_id;
657  }
658 }
659 
660 AccessibilityNodeId AccessibilityBridge::GetLastFocusedId() {
661  return last_focused_id_;
662 }
663 
664 gfx::NativeViewAccessible AccessibilityBridge::GetNativeAccessibleFromId(
665  AccessibilityNodeId id) {
666  auto platform_node_delegate = GetFlutterPlatformNodeDelegateFromID(id).lock();
667  if (!platform_node_delegate) {
668  return nullptr;
669  }
670  return platform_node_delegate->GetNativeViewAccessible();
671 }
672 
673 gfx::RectF AccessibilityBridge::RelativeToGlobalBounds(const ui::AXNode* node,
674  bool& offscreen,
675  bool clip_bounds) {
676  return tree_->RelativeToTreeBounds(node, gfx::RectF(), &offscreen,
677  clip_bounds);
678 }
679 
681  ui::AXTreeID tree_id,
682  ui::AXNode::AXID node_id) const {
683  return GetNodeFromTree(node_id);
684 }
685 
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 
696  return ui::AXTreeIDUnknown();
697 }
698 
700  return tree_->root();
701 }
702 
704  return nullptr;
705 }
706 
707 ui::AXTree* AccessibilityBridge::GetTree() const {
708  return tree_.get();
709 }
710 
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 
722  const ui::AXNode& node) const {
723  return GetPlatformNodeFromTree(node.id());
724 }
725 
726 ui::AXPlatformNodeDelegate* AccessibilityBridge::RootDelegate() const {
728  .lock()
729  .get();
730 }
731 
732 } // namespace flutter
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