Flutter Linux 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  for (size_t i = 0; i < node.custom_accessibility_actions.size(); i++) {
500  custom_action_ids.push_back(node.custom_accessibility_actions[i]);
501  }
502  node_data.AddIntListAttribute(ax::mojom::IntListAttribute::kCustomActionIds,
503  custom_action_ids);
504  }
505 }
506 
507 void AccessibilityBridge::SetStringListAttributesFromFlutterUpdate(
508  ui::AXNodeData& node_data,
509  const SemanticsNode& node) {
510  FlutterSemanticsAction actions = node.actions;
511  if (actions & FlutterSemanticsAction::kFlutterSemanticsActionCustomAction) {
512  std::vector<std::string> custom_action_description;
513  for (size_t i = 0; i < node.custom_accessibility_actions.size(); i++) {
514  auto iter = pending_semantics_custom_action_updates_.find(
515  node.custom_accessibility_actions[i]);
516  BASE_DCHECK(iter != pending_semantics_custom_action_updates_.end());
517  custom_action_description.push_back(iter->second.label);
518  }
519  node_data.AddStringListAttribute(
520  ax::mojom::StringListAttribute::kCustomActionDescriptions,
521  custom_action_description);
522  }
523 }
524 
525 void AccessibilityBridge::SetNameFromFlutterUpdate(ui::AXNodeData& node_data,
526  const SemanticsNode& node) {
527  node_data.SetName(node.label);
528 }
529 
530 void AccessibilityBridge::SetValueFromFlutterUpdate(ui::AXNodeData& node_data,
531  const SemanticsNode& node) {
532  node_data.SetValue(node.value);
533 }
534 
535 void AccessibilityBridge::SetTooltipFromFlutterUpdate(
536  ui::AXNodeData& node_data,
537  const SemanticsNode& node) {
538  node_data.SetTooltip(node.tooltip);
539 }
540 
541 void AccessibilityBridge::SetTreeData(const SemanticsNode& node,
542  ui::AXTreeUpdate& tree_update) {
543  const FlutterSemanticsFlags* flags = node.flags;
544  // Set selection of the focused node if:
545  // 1. this text field has a valid selection
546  // 2. this text field doesn't have a valid selection but had selection stored
547  // in the tree.
548  if (flags->is_text_field &&
549  flags->is_focused == FlutterTristate::kFlutterTristateTrue) {
550  if (node.text_selection_base != -1) {
551  tree_update.tree_data.sel_anchor_object_id = node.id;
552  tree_update.tree_data.sel_anchor_offset = node.text_selection_base;
553  tree_update.tree_data.sel_focus_object_id = node.id;
554  tree_update.tree_data.sel_focus_offset = node.text_selection_extent;
555  tree_update.has_tree_data = true;
556  } else if (tree_update.tree_data.sel_anchor_object_id == node.id) {
557  tree_update.tree_data.sel_anchor_object_id = ui::AXNode::kInvalidAXID;
558  tree_update.tree_data.sel_anchor_offset = -1;
559  tree_update.tree_data.sel_focus_object_id = ui::AXNode::kInvalidAXID;
560  tree_update.tree_data.sel_focus_offset = -1;
561  tree_update.has_tree_data = true;
562  }
563  }
564 
565  if (flags->is_focused == FlutterTristate::kFlutterTristateTrue &&
566  tree_update.tree_data.focus_id != node.id) {
567  tree_update.tree_data.focus_id = node.id;
568  tree_update.has_tree_data = true;
569  } else if (flags->is_focused != FlutterTristate::kFlutterTristateTrue &&
570  tree_update.tree_data.focus_id == node.id) {
571  tree_update.tree_data.focus_id = ui::AXNode::kInvalidAXID;
572  tree_update.has_tree_data = true;
573  }
574 }
575 
576 AccessibilityBridge::SemanticsNode
577 AccessibilityBridge::FromFlutterSemanticsNode(
578  const FlutterSemanticsNode2& flutter_node) {
579  SemanticsNode result;
580  result.id = flutter_node.id;
581  FML_DCHECK(flutter_node.flags2)
582  << "FlutterSemanticsNode2::flags2 must not be null";
583 
584  result.flags = flutter_node.flags2;
585  result.actions = flutter_node.actions;
586  result.text_selection_base = flutter_node.text_selection_base;
587  result.text_selection_extent = flutter_node.text_selection_extent;
588  result.scroll_child_count = flutter_node.scroll_child_count;
589  result.scroll_index = flutter_node.scroll_index;
590  result.scroll_position = flutter_node.scroll_position;
591  result.scroll_extent_max = flutter_node.scroll_extent_max;
592  result.scroll_extent_min = flutter_node.scroll_extent_min;
593  if (flutter_node.label) {
594  result.label = std::string(flutter_node.label);
595  }
596  if (flutter_node.hint) {
597  result.hint = std::string(flutter_node.hint);
598  }
599  if (flutter_node.value) {
600  result.value = std::string(flutter_node.value);
601  }
602  if (flutter_node.increased_value) {
603  result.increased_value = std::string(flutter_node.increased_value);
604  }
605  if (flutter_node.decreased_value) {
606  result.decreased_value = std::string(flutter_node.decreased_value);
607  }
608  if (flutter_node.tooltip) {
609  result.tooltip = std::string(flutter_node.tooltip);
610  }
611  result.text_direction = flutter_node.text_direction;
612  result.rect = flutter_node.rect;
613  result.transform = flutter_node.transform;
614  if (flutter_node.child_count > 0) {
615  result.children_in_traversal_order = std::vector<int32_t>(
616  flutter_node.children_in_traversal_order,
617  flutter_node.children_in_traversal_order + flutter_node.child_count);
618  }
619  if (flutter_node.custom_accessibility_actions_count > 0) {
620  result.custom_accessibility_actions = std::vector<int32_t>(
621  flutter_node.custom_accessibility_actions,
622  flutter_node.custom_accessibility_actions +
623  flutter_node.custom_accessibility_actions_count);
624  }
625  return result;
626 }
627 
628 AccessibilityBridge::SemanticsCustomAction
629 AccessibilityBridge::FromFlutterSemanticsCustomAction(
630  const FlutterSemanticsCustomAction2& flutter_custom_action) {
631  SemanticsCustomAction result;
632  result.id = flutter_custom_action.id;
633  result.override_action = flutter_custom_action.override_action;
634  if (flutter_custom_action.label) {
635  result.label = std::string(flutter_custom_action.label);
636  }
637  if (flutter_custom_action.hint) {
638  result.hint = std::string(flutter_custom_action.hint);
639  }
640  return result;
641 }
642 
643 void AccessibilityBridge::SetLastFocusedId(AccessibilityNodeId node_id) {
644  if (last_focused_id_ != node_id) {
645  auto last_focused_child =
646  GetFlutterPlatformNodeDelegateFromID(last_focused_id_);
647  if (!last_focused_child.expired()) {
649  last_focused_id_,
650  FlutterSemanticsAction::
651  kFlutterSemanticsActionDidLoseAccessibilityFocus,
652  {});
653  }
654  last_focused_id_ = node_id;
655  }
656 }
657 
658 AccessibilityNodeId AccessibilityBridge::GetLastFocusedId() {
659  return last_focused_id_;
660 }
661 
662 gfx::NativeViewAccessible AccessibilityBridge::GetNativeAccessibleFromId(
663  AccessibilityNodeId id) {
664  auto platform_node_delegate = GetFlutterPlatformNodeDelegateFromID(id).lock();
665  if (!platform_node_delegate) {
666  return nullptr;
667  }
668  return platform_node_delegate->GetNativeViewAccessible();
669 }
670 
671 gfx::RectF AccessibilityBridge::RelativeToGlobalBounds(const ui::AXNode* node,
672  bool& offscreen,
673  bool clip_bounds) {
674  return tree_->RelativeToTreeBounds(node, gfx::RectF(), &offscreen,
675  clip_bounds);
676 }
677 
679  ui::AXTreeID tree_id,
680  ui::AXNode::AXID node_id) const {
681  return GetNodeFromTree(node_id);
682 }
683 
685  ui::AXNode::AXID node_id) const {
686  return tree_->GetFromId(node_id);
687 }
688 
689 ui::AXTreeID AccessibilityBridge::GetTreeID() const {
690  return tree_->GetAXTreeID();
691 }
692 
694  return ui::AXTreeIDUnknown();
695 }
696 
698  return tree_->root();
699 }
700 
702  return nullptr;
703 }
704 
705 ui::AXTree* AccessibilityBridge::GetTree() const {
706  return tree_.get();
707 }
708 
710  const ui::AXNode::AXID node_id) const {
711  auto platform_delegate_weak = GetFlutterPlatformNodeDelegateFromID(node_id);
712  auto platform_delegate = platform_delegate_weak.lock();
713  if (!platform_delegate) {
714  return nullptr;
715  }
716  return platform_delegate->GetPlatformNode();
717 }
718 
720  const ui::AXNode& node) const {
721  return GetPlatformNodeFromTree(node.id());
722 }
723 
724 ui::AXPlatformNodeDelegate* AccessibilityBridge::RootDelegate() const {
726  .lock()
727  .get();
728 }
729 
730 } // 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...
FlutterSemanticsAction action
const uint8_t uint32_t uint32_t GError ** error
uint32_t * target
constexpr int kHasScrollingAction
ui::AXNode::AXID AccessibilityNodeId