Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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 "flutter/shell/platform/fuchsia/flutter/accessibility_bridge.h"
6
7#include <lib/inspect/cpp/inspector.h>
8#include <lib/zx/process.h>
9#include <zircon/status.h>
10#include <zircon/types.h>
11
12#include <algorithm>
13#include <deque>
14
15#include "flutter/fml/logging.h"
16#include "flutter/lib/ui/semantics/semantics_node.h"
17
18#include "../runtime/dart/utils/root_inspect_node.h"
19
20namespace flutter_runner {
21namespace {
22
23#if !FLUTTER_RELEASE
24static constexpr char kTreeDumpInspectRootName[] = "semantic_tree_root";
25
26// Converts flutter semantic node flags to a string representation.
27std::string NodeFlagsToString(const flutter::SemanticsNode& node) {
28 std::string output;
30 output += "kHasCheckedState|";
31 }
33 output += "kHasEnabledState|";
34 }
36 output += "kHasImplicitScrolling|";
37 }
39 output += "kHasToggledState|";
40 }
42 output += "kIsButton|";
43 }
45 output += "kIsChecked|";
46 }
48 output += "kIsEnabled|";
49 }
51 output += "kIsFocusable|";
52 }
54 output += "kIsFocused|";
55 }
57 output += "kIsHeader|";
58 }
60 output += "kIsHidden|";
61 }
63 output += "kIsImage|";
64 }
66 output += "kIsInMutuallyExclusiveGroup|";
67 }
69 output += "kIsKeyboardKey|";
70 }
72 output += "kIsLink|";
73 }
75 output += "kIsLiveRegion|";
76 }
78 output += "kIsObscured|";
79 }
81 output += "kIsReadOnly|";
82 }
84 output += "kIsSelected|";
85 }
87 output += "kIsSlider|";
88 }
90 output += "kIsTextField|";
91 }
93 output += "kIsToggled|";
94 }
96 output += "kNamesRoute|";
97 }
99 output += "kScopesRoute|";
100 }
101
102 return output;
103}
104
105// Converts flutter semantic node actions to a string representation.
106std::string NodeActionsToString(const flutter::SemanticsNode& node) {
107 std::string output;
108
110 output += "kCopy|";
111 }
113 output += "kCustomAction|";
114 }
116 output += "kCut|";
117 }
119 output += "kDecrease|";
120 }
122 output += "kDidGainAccessibilityFocus|";
123 }
125 output += "kDidLoseAccessibilityFocus|";
126 }
128 output += "kDismiss|";
129 }
131 output += "kIncrease|";
132 }
134 output += "kLongPress|";
135 }
136 if (node.HasAction(
138 output += "kMoveCursorBackwardByCharacter|";
139 }
141 output += "kMoveCursorBackwardByWord|";
142 }
144 output += "kMoveCursorForwardByCharacter|";
145 }
147 output += "kMoveCursorForwardByWord|";
148 }
150 output += "kPaste|";
151 }
153 output += "kScrollDown|";
154 }
156 output += "kScrollLeft|";
157 }
159 output += "kScrollRight|";
160 }
162 output += "kScrollUp|";
163 }
165 output += "kSetSelection|";
166 }
168 output += "kSetText|";
169 }
171 output += "kShowOnScreen|";
172 }
174 output += "kTap|";
175 }
176
177 return output;
178}
179
180// Returns a string representation of the flutter semantic node absolut
181// location.
182std::string NodeLocationToString(const SkRect& rect) {
183 auto min_x = rect.fLeft;
184 auto min_y = rect.fTop;
185 auto max_x = rect.fRight;
186 auto max_y = rect.fBottom;
187 std::string location =
188 "min(" + std::to_string(min_x) + ", " + std::to_string(min_y) + ") max(" +
189 std::to_string(max_x) + ", " + std::to_string(max_y) + ")";
190
191 return location;
192}
193
194// Returns a string representation of the node's different types of children.
195std::string NodeChildrenToString(const flutter::SemanticsNode& node) {
196 std::stringstream output;
197 if (!node.childrenInTraversalOrder.empty()) {
198 output << "children in traversal order:[";
199 for (const auto child_id : node.childrenInTraversalOrder) {
200 output << child_id << ", ";
201 }
202 output << "]\n";
203 }
204 if (!node.childrenInHitTestOrder.empty()) {
205 output << "children in hit test order:[";
206 for (const auto child_id : node.childrenInHitTestOrder) {
207 output << child_id << ", ";
208 }
209 output << ']';
210 }
211
212 return output.str();
213}
214#endif // !FLUTTER_RELEASE
215
216} // namespace
217
219 SetSemanticsEnabledCallback set_semantics_enabled_callback,
220 DispatchSemanticsActionCallback dispatch_semantics_action_callback,
221 fuchsia::accessibility::semantics::SemanticsManagerHandle semantics_manager,
222 fuchsia::ui::views::ViewRef view_ref,
223 inspect::Node inspect_node)
224 : set_semantics_enabled_callback_(
225 std::move(set_semantics_enabled_callback)),
226 dispatch_semantics_action_callback_(
227 std::move(dispatch_semantics_action_callback)),
228 binding_(this),
229 fuchsia_semantics_manager_(semantics_manager.Bind()),
230 atomic_updates_(std::make_shared<std::queue<FuchsiaAtomicUpdate>>()),
231 inspect_node_(std::move(inspect_node)) {
232 fuchsia_semantics_manager_.set_error_handler([](zx_status_t status) {
233 FML_LOG(ERROR) << "Flutter cannot connect to SemanticsManager with status: "
234 << zx_status_get_string(status) << ".";
235 });
236 fuchsia_semantics_manager_->RegisterViewForSemantics(
237 std::move(view_ref), binding_.NewBinding(), tree_ptr_.NewRequest());
238
239#if !FLUTTER_RELEASE
240 // The first argument to |CreateLazyValues| is the name of the lazy node, and
241 // will only be displayed if the callback used to generate the node's content
242 // fails. Therefore, we use an error message for this node name.
243 inspect_node_tree_dump_ =
244 inspect_node_.CreateLazyValues("dump_fail", [this]() {
245 inspect::Inspector inspector;
246 if (auto it = nodes_.find(kRootNodeId); it == nodes_.end()) {
247 inspector.GetRoot().CreateString(
248 "empty_tree", "this semantic tree is empty", &inspector);
249 } else {
250 FillInspectTree(
251 kRootNodeId, /*current_level=*/1,
252 inspector.GetRoot().CreateChild(kTreeDumpInspectRootName),
253 &inspector);
254 }
255 return fpromise::make_ok_promise(std::move(inspector));
256 });
257#endif // !FLUTTER_RELEASE
258}
259
261 return semantics_enabled_;
262}
263
265 semantics_enabled_ = enabled;
266 if (!enabled) {
267 nodes_.clear();
268 }
269}
270
271fuchsia::ui::gfx::BoundingBox AccessibilityBridge::GetNodeLocation(
272 const flutter::SemanticsNode& node) const {
273 fuchsia::ui::gfx::BoundingBox box;
274 box.min.x = node.rect.fLeft;
275 box.min.y = node.rect.fTop;
276 box.min.z = static_cast<float>(node.elevation);
277 box.max.x = node.rect.fRight;
278 box.max.y = node.rect.fBottom;
279 box.max.z = static_cast<float>(node.thickness);
280 return box;
281}
282
283fuchsia::ui::gfx::mat4 AccessibilityBridge::GetNodeTransform(
284 const flutter::SemanticsNode& node) const {
285 return ConvertSkiaTransformToMat4(node.transform);
286}
287
288fuchsia::ui::gfx::mat4 AccessibilityBridge::ConvertSkiaTransformToMat4(
289 const SkM44 transform) const {
290 fuchsia::ui::gfx::mat4 value;
291 float* m = value.matrix.data();
292 transform.getColMajor(m);
293 return value;
294}
295
296fuchsia::accessibility::semantics::Attributes
297AccessibilityBridge::GetNodeAttributes(const flutter::SemanticsNode& node,
298 size_t* added_size) const {
299 fuchsia::accessibility::semantics::Attributes attributes;
300 // TODO(MI4-2531): Don't truncate.
301 if (node.label.size() > fuchsia::accessibility::semantics::MAX_LABEL_SIZE) {
302 attributes.set_label(node.label.substr(
303 0, fuchsia::accessibility::semantics::MAX_LABEL_SIZE));
304 *added_size += fuchsia::accessibility::semantics::MAX_LABEL_SIZE;
305 } else {
306 attributes.set_label(node.label);
307 *added_size += node.label.size();
308 }
309
310 if (node.tooltip.size() > fuchsia::accessibility::semantics::MAX_LABEL_SIZE) {
311 attributes.set_secondary_label(node.tooltip.substr(
312 0, fuchsia::accessibility::semantics::MAX_LABEL_SIZE));
313 *added_size += fuchsia::accessibility::semantics::MAX_LABEL_SIZE;
314 } else {
315 attributes.set_secondary_label(node.tooltip);
316 *added_size += node.tooltip.size();
317 }
318
320 attributes.set_is_keyboard_key(true);
321 }
322
323 return attributes;
324}
325
326fuchsia::accessibility::semantics::States AccessibilityBridge::GetNodeStates(
327 const flutter::SemanticsNode& node,
328 size_t* additional_size) const {
329 fuchsia::accessibility::semantics::States states;
330 (*additional_size) += sizeof(fuchsia::accessibility::semantics::States);
331
332 // Set checked state.
334 states.set_checked_state(
335 fuchsia::accessibility::semantics::CheckedState::NONE);
336 } else {
337 states.set_checked_state(
339 ? fuchsia::accessibility::semantics::CheckedState::CHECKED
340 : fuchsia::accessibility::semantics::CheckedState::UNCHECKED);
341 }
342
343 // Set enabled state.
345 states.set_enabled_state(
347 ? fuchsia::accessibility::semantics::EnabledState::ENABLED
348 : fuchsia::accessibility::semantics::EnabledState::DISABLED);
349 }
350
351 // Set selected state.
352 states.set_selected(node.HasFlag(flutter::SemanticsFlags::kIsSelected));
353
354 // Flutter's definition of a hidden node is different from Fuchsia, so it must
355 // not be set here.
356
357 // Set value.
358 if (node.value.size() > fuchsia::accessibility::semantics::MAX_VALUE_SIZE) {
359 states.set_value(node.value.substr(
360 0, fuchsia::accessibility::semantics::MAX_VALUE_SIZE));
361 (*additional_size) += fuchsia::accessibility::semantics::MAX_VALUE_SIZE;
362 } else {
363 states.set_value(node.value);
364 (*additional_size) += node.value.size();
365 }
366
367 // Set toggled state.
369 states.set_toggled_state(
371 ? fuchsia::accessibility::semantics::ToggledState::ON
372 : fuchsia::accessibility::semantics::ToggledState::OFF);
373 }
374
375 return states;
376}
377
378std::vector<fuchsia::accessibility::semantics::Action>
379AccessibilityBridge::GetNodeActions(const flutter::SemanticsNode& node,
380 size_t* additional_size) const {
381 std::vector<fuchsia::accessibility::semantics::Action> node_actions;
382
384 node_actions.push_back(fuchsia::accessibility::semantics::Action::DEFAULT);
385 }
387 node_actions.push_back(
388 fuchsia::accessibility::semantics::Action::SECONDARY);
389 }
391 node_actions.push_back(
392 fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN);
393 }
395 node_actions.push_back(
396 fuchsia::accessibility::semantics::Action::INCREMENT);
397 }
399 node_actions.push_back(
400 fuchsia::accessibility::semantics::Action::DECREMENT);
401 }
402
403 *additional_size +=
404 node_actions.size() * sizeof(fuchsia::accessibility::semantics::Action);
405 return node_actions;
406}
407
408fuchsia::accessibility::semantics::Role AccessibilityBridge::GetNodeRole(
409 const flutter::SemanticsNode& node) const {
411 return fuchsia::accessibility::semantics::Role::BUTTON;
412 }
413
415 return fuchsia::accessibility::semantics::Role::TEXT_FIELD;
416 }
417
419 return fuchsia::accessibility::semantics::Role::LINK;
420 }
421
423 return fuchsia::accessibility::semantics::Role::SLIDER;
424 }
425
427 return fuchsia::accessibility::semantics::Role::HEADER;
428 }
430 return fuchsia::accessibility::semantics::Role::IMAGE;
431 }
432
433 // If a flutter node supports the kIncrease or kDecrease actions, it can be
434 // treated as a slider control by assistive technology. This is important
435 // because users have special gestures to deal with sliders, and Fuchsia API
436 // requires nodes that can receive this kind of action to be a slider control.
439 return fuchsia::accessibility::semantics::Role::SLIDER;
440 }
441
442 // If a flutter node has a checked state, then we assume it is either a
443 // checkbox or a radio button. We distinguish between checkboxes and
444 // radio buttons based on membership in a mutually exclusive group.
447 return fuchsia::accessibility::semantics::Role::RADIO_BUTTON;
448 } else {
449 return fuchsia::accessibility::semantics::Role::CHECK_BOX;
450 }
451 }
452
454 return fuchsia::accessibility::semantics::Role::TOGGLE_SWITCH;
455 }
456 return fuchsia::accessibility::semantics::Role::UNKNOWN;
457}
458
459std::unordered_set<int32_t> AccessibilityBridge::GetDescendants(
460 int32_t node_id) const {
461 std::unordered_set<int32_t> descendents;
462 std::deque<int32_t> to_process = {node_id};
463 while (!to_process.empty()) {
464 int32_t id = to_process.front();
465 to_process.pop_front();
466 descendents.emplace(id);
467
468 auto it = nodes_.find(id);
469 if (it != nodes_.end()) {
470 const auto& node = it->second.data;
471 for (const auto& child : node.childrenInHitTestOrder) {
472 if (descendents.find(child) == descendents.end()) {
473 to_process.push_back(child);
474 } else {
475 // This indicates either a cycle or a child with multiple parents.
476 // Flutter should never let this happen, but the engine API does not
477 // explicitly forbid it right now.
478 // TODO(http://fxbug.dev/75905): Crash flutter accessibility bridge
479 // when a cycle in the tree is found.
480 FML_LOG(ERROR) << "Semantics Node " << child
481 << " has already been listed as a child of another "
482 "node, ignoring for parent "
483 << id << ".";
484 }
485 }
486 }
487 }
488 return descendents;
489}
490
491// The only known usage of a negative number for a node ID is in the embedder
492// API as a sentinel value, which is not expected here. No valid producer of
493// nodes should give us a negative ID.
494static uint32_t FlutterIdToFuchsiaId(int32_t flutter_node_id) {
495 FML_DCHECK(flutter_node_id >= 0)
496 << "Unexpectedly received a negative semantics node ID.";
497 return static_cast<uint32_t>(flutter_node_id);
498}
499
500void AccessibilityBridge::PruneUnreachableNodes(
501 FuchsiaAtomicUpdate* atomic_update) {
502 const auto& reachable_nodes = GetDescendants(kRootNodeId);
503 auto iter = nodes_.begin();
504 while (iter != nodes_.end()) {
505 int32_t id = iter->first;
506 if (reachable_nodes.find(id) == reachable_nodes.end()) {
507 atomic_update->AddNodeDeletion(FlutterIdToFuchsiaId(id));
508 iter = nodes_.erase(iter);
509 } else {
510 iter++;
511 }
512 }
513}
514
515// TODO(FIDL-718) - remove this, handle the error instead in something like
516// set_error_handler.
517static void PrintNodeSizeError(uint32_t node_id) {
518 FML_LOG(ERROR) << "Semantics node with ID " << node_id
519 << " exceeded the maximum FIDL message size and may not "
520 "be delivered to the accessibility manager service.";
521}
522
525 float view_pixel_ratio) {
526 if (update.empty()) {
527 return;
528 }
529 FML_DCHECK(nodes_.find(kRootNodeId) != nodes_.end() ||
530 update.find(kRootNodeId) != update.end())
531 << "AccessibilityBridge received an update with out ever getting a root "
532 "node.";
533
534 FuchsiaAtomicUpdate atomic_update;
535 bool has_root_node_update = false;
536 // TODO(MI4-2498): Actions, Roles, hit test children, additional
537 // flags/states/attr
538
539 // TODO(MI4-1478): Support for partial updates for nodes > 64kb
540 // e.g. if a node has a long label or more than 64k children.
541 for (const auto& [flutter_node_id, flutter_node] : update) {
542 size_t this_node_size = sizeof(fuchsia::accessibility::semantics::Node);
543 // We handle root update separately in GetRootNodeUpdate.
544 // TODO(chunhtai): remove this special case after we remove the inverse
545 // view pixel ratio transformation in scenic view.
546 // TODO(http://fxbug.dev/75908): Investigate flutter a11y bridge refactor
547 // after removal of the inverse view pixel ratio transformation in scenic
548 // view).
549 if (flutter_node.id == kRootNodeId) {
550 root_flutter_semantics_node_ = flutter_node;
551 has_root_node_update = true;
552 continue;
553 }
554 // Store the nodes for later hit testing and logging.
555 nodes_[flutter_node.id].data = flutter_node;
556
557 fuchsia::accessibility::semantics::Node fuchsia_node;
558 std::vector<uint32_t> child_ids;
559 // Send the nodes in traversal order, so the manager can figure out
560 // traversal.
561 for (int32_t flutter_child_id : flutter_node.childrenInTraversalOrder) {
562 child_ids.push_back(FlutterIdToFuchsiaId(flutter_child_id));
563 }
564 // TODO(http://fxbug.dev/75910): check the usage of FlutterIdToFuchsiaId in
565 // the flutter accessibility bridge.
566 fuchsia_node.set_node_id(flutter_node.id)
567 .set_role(GetNodeRole(flutter_node))
568 .set_location(GetNodeLocation(flutter_node))
569 .set_transform(GetNodeTransform(flutter_node))
570 .set_attributes(GetNodeAttributes(flutter_node, &this_node_size))
571 .set_states(GetNodeStates(flutter_node, &this_node_size))
572 .set_actions(GetNodeActions(flutter_node, &this_node_size))
573 .set_child_ids(child_ids);
574 this_node_size +=
575 kNodeIdSize * flutter_node.childrenInTraversalOrder.size();
576
577 atomic_update.AddNodeUpdate(std::move(fuchsia_node), this_node_size);
578 }
579
580 // Handles root node update.
581 if (has_root_node_update || last_seen_view_pixel_ratio_ != view_pixel_ratio) {
582 last_seen_view_pixel_ratio_ = view_pixel_ratio;
583 size_t root_node_size;
584 fuchsia::accessibility::semantics::Node root_update =
585 GetRootNodeUpdate(root_node_size);
586 atomic_update.AddNodeUpdate(std::move(root_update), root_node_size);
587 }
588
589 PruneUnreachableNodes(&atomic_update);
590 UpdateScreenRects();
591
592 atomic_updates_->push(std::move(atomic_update));
593 if (atomic_updates_->size() == 1) {
594 // There were no commits in the queue, so send this one.
595 Apply(&atomic_updates_->front());
596 }
597}
598
599fuchsia::accessibility::semantics::Node AccessibilityBridge::GetRootNodeUpdate(
600 size_t& node_size) {
601 fuchsia::accessibility::semantics::Node root_fuchsia_node;
602 std::vector<uint32_t> child_ids;
603 node_size = sizeof(fuchsia::accessibility::semantics::Node);
604 for (int32_t flutter_child_id :
605 root_flutter_semantics_node_.childrenInTraversalOrder) {
606 child_ids.push_back(FlutterIdToFuchsiaId(flutter_child_id));
607 }
608 // Applies the inverse view pixel ratio transformation to the root node.
609 float inverse_view_pixel_ratio = 1.f / last_seen_view_pixel_ratio_;
610 SkM44 inverse_view_pixel_ratio_transform;
611 inverse_view_pixel_ratio_transform.setScale(inverse_view_pixel_ratio,
612 inverse_view_pixel_ratio, 1.f);
613
614 SkM44 result = root_flutter_semantics_node_.transform *
615 inverse_view_pixel_ratio_transform;
616 nodes_[root_flutter_semantics_node_.id].data = root_flutter_semantics_node_;
617
618 // TODO(http://fxbug.dev/75910): check the usage of FlutterIdToFuchsiaId in
619 // the flutter accessibility bridge.
620 root_fuchsia_node.set_node_id(root_flutter_semantics_node_.id)
621 .set_role(GetNodeRole(root_flutter_semantics_node_))
622 .set_location(GetNodeLocation(root_flutter_semantics_node_))
623 .set_transform(ConvertSkiaTransformToMat4(result))
624 .set_attributes(
625 GetNodeAttributes(root_flutter_semantics_node_, &node_size))
626 .set_states(GetNodeStates(root_flutter_semantics_node_, &node_size))
627 .set_actions(GetNodeActions(root_flutter_semantics_node_, &node_size))
628 .set_child_ids(child_ids);
629 node_size += kNodeIdSize *
630 root_flutter_semantics_node_.childrenInTraversalOrder.size();
631 return root_fuchsia_node;
632}
633
635 fuchsia::accessibility::semantics::SemanticEvent semantic_event;
636 fuchsia::accessibility::semantics::AnnounceEvent announce_event;
637 announce_event.set_message(message);
638 semantic_event.set_announce(std::move(announce_event));
639
640 tree_ptr_->SendSemanticEvent(std::move(semantic_event), []() {});
641}
642
643void AccessibilityBridge::UpdateScreenRects() {
644 std::unordered_set<int32_t> visited_nodes;
645
646 // The embedder applies a special pixel ratio transform to the root of the
647 // view, and the accessibility bridge applies the inverse of this transform
648 // to the root node. However, this transform is not persisted in the flutter
649 // representation of the root node, so we need to account for it explicitly
650 // here.
651 float inverse_view_pixel_ratio = 1.f / last_seen_view_pixel_ratio_;
652 SkM44 inverse_view_pixel_ratio_transform;
653 inverse_view_pixel_ratio_transform.setScale(inverse_view_pixel_ratio,
654 inverse_view_pixel_ratio, 1.f);
655
656 UpdateScreenRects(kRootNodeId, inverse_view_pixel_ratio_transform,
657 &visited_nodes);
658}
659
660void AccessibilityBridge::UpdateScreenRects(
661 int32_t node_id,
662 SkM44 parent_transform,
663 std::unordered_set<int32_t>* visited_nodes) {
664 auto it = nodes_.find(node_id);
665 if (it == nodes_.end()) {
666 FML_LOG(ERROR) << "UpdateScreenRects called on unknown node";
667 return;
668 }
669 auto& node = it->second;
670 const auto& current_transform = parent_transform * node.data.transform;
671
672 const auto& rect = node.data.rect;
673 SkV4 dst[2] = {
674 current_transform.map(rect.left(), rect.top(), 0, 1),
675 current_transform.map(rect.right(), rect.bottom(), 0, 1),
676 };
677 node.screen_rect.setLTRB(dst[0].x, dst[0].y, dst[1].x, dst[1].y);
678 node.screen_rect.sort();
679
680 visited_nodes->emplace(node_id);
681
682 for (uint32_t child_id : node.data.childrenInHitTestOrder) {
683 if (visited_nodes->find(child_id) == visited_nodes->end()) {
684 UpdateScreenRects(child_id, current_transform, visited_nodes);
685 }
686 }
687}
688
689std::optional<flutter::SemanticsAction>
690AccessibilityBridge::GetFlutterSemanticsAction(
691 fuchsia::accessibility::semantics::Action fuchsia_action,
692 uint32_t node_id) {
693 switch (fuchsia_action) {
694 // The default action associated with the element.
695 case fuchsia::accessibility::semantics::Action::DEFAULT:
697 // The secondary action associated with the element. This may correspond to
698 // a long press (touchscreens) or right click (mouse).
699 case fuchsia::accessibility::semantics::Action::SECONDARY:
701 // Set (input/non-accessibility) focus on this element.
702 case fuchsia::accessibility::semantics::Action::SET_FOCUS:
703 FML_LOG(WARNING)
704 << "Unsupported action SET_FOCUS sent for accessibility node "
705 << node_id;
706 return {};
707 // Set the element's value.
708 case fuchsia::accessibility::semantics::Action::SET_VALUE:
709 FML_LOG(WARNING)
710 << "Unsupported action SET_VALUE sent for accessibility node "
711 << node_id;
712 return {};
713 // Scroll node to make it visible.
714 case fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN:
716 case fuchsia::accessibility::semantics::Action::INCREMENT:
718 case fuchsia::accessibility::semantics::Action::DECREMENT:
720 default:
721 FML_LOG(WARNING) << "Unexpected action "
722 << static_cast<int32_t>(fuchsia_action)
723 << " sent for accessibility node " << node_id;
724 return {};
725 }
726}
727
728// |fuchsia::accessibility::semantics::SemanticListener|
730 uint32_t node_id,
731 fuchsia::accessibility::semantics::Action action,
732 fuchsia::accessibility::semantics::SemanticListener::
733 OnAccessibilityActionRequestedCallback callback) {
734 // TODO(http://fxbug.dev/75910): check the usage of FlutterIdToFuchsiaId in
735 // the flutter accessibility bridge.
736 if (nodes_.find(node_id) == nodes_.end()) {
737 FML_LOG(ERROR) << "Attempted to send accessibility action "
738 << static_cast<int32_t>(action)
739 << " to unknown node id: " << node_id;
740 callback(false);
741 return;
742 }
743
744 std::optional<flutter::SemanticsAction> flutter_action =
745 GetFlutterSemanticsAction(action, node_id);
746 if (!flutter_action.has_value()) {
747 callback(false);
748 return;
749 }
750 dispatch_semantics_action_callback_(static_cast<int32_t>(node_id),
751 flutter_action.value());
752 callback(true);
753}
754
755// |fuchsia::accessibility::semantics::SemanticListener|
757 fuchsia::math::PointF local_point,
758 fuchsia::accessibility::semantics::SemanticListener::HitTestCallback
759 callback) {
760 auto hit_node_id = GetHitNode(kRootNodeId, local_point.x, local_point.y);
761 FML_DCHECK(hit_node_id.has_value());
762 fuchsia::accessibility::semantics::Hit hit;
763 // TODO(http://fxbug.dev/75910): check the usage of FlutterIdToFuchsiaId in
764 // the flutter accessibility bridge.
765 hit.set_node_id(hit_node_id.value_or(kRootNodeId));
766 callback(std::move(hit));
767}
768
769std::optional<int32_t> AccessibilityBridge::GetHitNode(int32_t node_id,
770 float x,
771 float y) {
772 auto it = nodes_.find(node_id);
773 if (it == nodes_.end()) {
774 FML_LOG(ERROR) << "Attempted to hit test unknown node id: " << node_id;
775 return {};
776 }
777 auto const& node = it->second;
778 if (node.data.flags &
779 static_cast<int32_t>(flutter::SemanticsFlags::kIsHidden) || //
780 !node.screen_rect.contains(x, y)) {
781 return {};
782 }
783 for (int32_t child_id : node.data.childrenInHitTestOrder) {
784 auto candidate = GetHitNode(child_id, x, y);
785 if (candidate) {
786 return candidate;
787 }
788 }
789
790 if (IsFocusable(node.data)) {
791 return node_id;
792 }
793
794 return {};
795}
796
797bool AccessibilityBridge::IsFocusable(
798 const flutter::SemanticsNode& node) const {
800 return false;
801 }
802
804 return true;
805 }
806
807 // Always consider platform views focusable.
808 if (node.IsPlatformViewNode()) {
809 return true;
810 }
811
812 // Always consider actionable nodes focusable.
813 if (node.actions != 0) {
814 return true;
815 }
816
817 // Consider text nodes focusable.
818 return !node.label.empty() || !node.value.empty() || !node.hint.empty();
819}
820
821// |fuchsia::accessibility::semantics::SemanticListener|
823 bool enabled,
824 OnSemanticsModeChangedCallback callback) {
825 set_semantics_enabled_callback_(enabled);
826}
827
828#if !FLUTTER_RELEASE
829void AccessibilityBridge::FillInspectTree(int32_t flutter_node_id,
830 int32_t current_level,
831 inspect::Node inspect_node,
832 inspect::Inspector* inspector) const {
833 const auto it = nodes_.find(flutter_node_id);
834 if (it == nodes_.end()) {
835 inspect_node.CreateString(
836 "missing_child",
837 "This node has a parent in the semantic tree but has no value",
838 inspector);
839 inspector->emplace(std::move(inspect_node));
840 return;
841 }
842 const auto& semantic_node = it->second;
843 const auto& data = semantic_node.data;
844
845 inspect_node.CreateInt("id", data.id, inspector);
846
847 // Even with an empty label, we still want to create the property to
848 // explicetly show that it is empty.
849 inspect_node.CreateString("label", data.label, inspector);
850 if (!data.hint.empty()) {
851 inspect_node.CreateString("hint", data.hint, inspector);
852 }
853 if (!data.value.empty()) {
854 inspect_node.CreateString("value", data.value, inspector);
855 }
856 if (!data.increasedValue.empty()) {
857 inspect_node.CreateString("increased_value", data.increasedValue,
858 inspector);
859 }
860 if (!data.decreasedValue.empty()) {
861 inspect_node.CreateString("decreased_value", data.decreasedValue,
862 inspector);
863 }
864
865 if (data.textDirection) {
866 inspect_node.CreateString(
867 "text_direction", data.textDirection == 1 ? "RTL" : "LTR", inspector);
868 }
869
870 if (data.flags) {
871 inspect_node.CreateString("flags", NodeFlagsToString(data), inspector);
872 }
873 if (data.actions) {
874 inspect_node.CreateString("actions", NodeActionsToString(data), inspector);
875 }
876
877 inspect_node.CreateString(
878 "location", NodeLocationToString(semantic_node.screen_rect), inspector);
879 if (!data.childrenInTraversalOrder.empty() ||
880 !data.childrenInHitTestOrder.empty()) {
881 inspect_node.CreateString("children", NodeChildrenToString(data),
882 inspector);
883 }
884
885 inspect_node.CreateInt("current_level", current_level, inspector);
886
887 for (int32_t flutter_child_id : semantic_node.data.childrenInTraversalOrder) {
888 const auto inspect_name = "node_" + std::to_string(flutter_child_id);
889 FillInspectTree(flutter_child_id, current_level + 1,
890 inspect_node.CreateChild(inspect_name), inspector);
891 }
892
893 inspector->emplace(std::move(inspect_node));
894}
895#endif // !FLUTTER_RELEASE
896
897void AccessibilityBridge::Apply(FuchsiaAtomicUpdate* atomic_update) {
898 size_t begin = 0;
899 auto it = atomic_update->deletions.begin();
900
901 // Process up to kMaxDeletionsPerUpdate deletions at a time.
902 while (it != atomic_update->deletions.end()) {
903 std::vector<uint32_t> to_delete;
904 size_t end = std::min(atomic_update->deletions.size() - begin,
906 std::copy(std::make_move_iterator(it), std::make_move_iterator(it + end),
907 std::back_inserter(to_delete));
908 tree_ptr_->DeleteSemanticNodes(std::move(to_delete));
909 begin = end;
910 it += end;
911 }
912
913 std::vector<fuchsia::accessibility::semantics::Node> to_update;
914 size_t current_size = 0;
915 for (auto& node_and_size : atomic_update->updates) {
916 if (current_size + node_and_size.second > kMaxMessageSize) {
917 tree_ptr_->UpdateSemanticNodes(std::move(to_update));
918 current_size = 0;
919 to_update.clear();
920 }
921 current_size += node_and_size.second;
922 to_update.push_back(std::move(node_and_size.first));
923 }
924 if (!to_update.empty()) {
925 tree_ptr_->UpdateSemanticNodes(std::move(to_update));
926 }
927
928 // Commit this update and subsequent ones; for flow control wait for a
929 // response between each commit.
930 tree_ptr_->CommitUpdates(
931 [this, atomic_updates = std::weak_ptr<std::queue<FuchsiaAtomicUpdate>>(
932 atomic_updates_)]() {
933 auto atomic_updates_ptr = atomic_updates.lock();
934 if (!atomic_updates_ptr) {
935 // The queue no longer exists, which means that is no longer
936 // necessary.
937 return;
938 }
939 // Removes the update that just went through.
940 atomic_updates_ptr->pop();
941 if (!atomic_updates_ptr->empty()) {
942 Apply(&atomic_updates_ptr->front());
943 }
944 });
945
946 atomic_update->deletions.clear();
947 atomic_update->updates.clear();
948}
949
950void AccessibilityBridge::FuchsiaAtomicUpdate::AddNodeUpdate(
951 fuchsia::accessibility::semantics::Node node,
952 size_t size) {
953 if (size > kMaxMessageSize) {
954 // TODO(MI4-2531, FIDL-718): Remove this
955 // This is defensive. If, despite our best efforts, we ended up with a node
956 // that is larger than the max fidl size, we send no updates.
957 PrintNodeSizeError(node.node_id());
958 return;
959 }
960 updates.emplace_back(std::move(node), size);
961}
962
963void AccessibilityBridge::FuchsiaAtomicUpdate::AddNodeDeletion(uint32_t id) {
964 deletions.push_back(id);
965}
966} // namespace flutter_runner
Definition SkM44.h:150
SkM44 & setScale(SkScalar x, SkScalar y, SkScalar z=1)
Definition SkM44.h:309
void AddSemanticsNodeUpdate(const flutter::SemanticsNodeUpdates update, float view_pixel_ratio)
std::function< void(bool)> SetSemanticsEnabledCallback
void OnSemanticsModeChanged(bool enabled, OnSemanticsModeChangedCallback callback) override
void OnAccessibilityActionRequested(uint32_t node_id, fuchsia::accessibility::semantics::Action action, fuchsia::accessibility::semantics::SemanticListener::OnAccessibilityActionRequestedCallback callback) override
void HitTest(fuchsia::math::PointF local_point, fuchsia::accessibility::semantics::SemanticListener::HitTestCallback callback) override
static constexpr uint32_t kMaxMessageSize
void RequestAnnounce(const std::string message)
std::function< void(int32_t, flutter::SemanticsAction)> DispatchSemanticsActionCallback
AccessibilityBridge(SetSemanticsEnabledCallback set_semantics_enabled_callback, DispatchSemanticsActionCallback dispatch_semantics_action_callback, fuchsia::accessibility::semantics::SemanticsManagerHandle semantics_manager, fuchsia::ui::views::ViewRef view_ref, inspect::Node inspect_node)
static constexpr size_t kMaxDeletionsPerUpdate
static const char * begin(const StringSlice &s)
Definition editor.cpp:252
VkQueue queue
Definition main.cc:55
glong glong end
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
uint8_t value
GAsyncResult * result
#define FML_LOG(severity)
Definition logging.h:82
#define FML_DCHECK(condition)
Definition logging.h:103
Win32Message message
double y
double x
sk_sp< SkBlender > blender SkRect rect
Definition SkRecords.h:350
static uint32_t FlutterIdToFuchsiaId(int32_t flutter_node_id)
static void PrintNodeSizeError(uint32_t node_id)
std::unordered_map< int32_t, SemanticsNode > SemanticsNodeUpdates
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
Definition switches.h:41
dst
Definition cp.py:12
Definition ref_ptr.h:256
static SkColor4f transform(SkColor4f c, SkColorSpace *src, SkColorSpace *dst)
Definition p3.cpp:47
fidl::Binding< fuchsia::ui::composition::ChildViewWatcher > binding_
SkScalar fBottom
larger y-axis bounds
Definition extension.cpp:17
SkScalar fLeft
smaller x-axis bounds
Definition extension.cpp:14
SkScalar fRight
larger x-axis bounds
Definition extension.cpp:16
SkScalar fTop
smaller y-axis bounds
Definition extension.cpp:15
Definition SkM44.h:98
std::vector< int32_t > childrenInHitTestOrder
bool HasAction(SemanticsAction action) const
bool HasFlag(SemanticsFlags flag) const
bool IsPlatformViewNode() const
std::vector< int32_t > childrenInTraversalOrder
#define ERROR(message)