Flutter Engine
The Flutter Engine
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 }
177 output += "kFocus|";
178 }
179
180 return output;
181}
182
183// Returns a string representation of the flutter semantic node absolut
184// location.
185std::string NodeLocationToString(const SkRect& rect) {
186 auto min_x = rect.fLeft;
187 auto min_y = rect.fTop;
188 auto max_x = rect.fRight;
189 auto max_y = rect.fBottom;
190 std::string location =
191 "min(" + std::to_string(min_x) + ", " + std::to_string(min_y) + ") max(" +
192 std::to_string(max_x) + ", " + std::to_string(max_y) + ")";
193
194 return location;
195}
196
197// Returns a string representation of the node's different types of children.
198std::string NodeChildrenToString(const flutter::SemanticsNode& node) {
199 std::stringstream output;
200 if (!node.childrenInTraversalOrder.empty()) {
201 output << "children in traversal order:[";
202 for (const auto child_id : node.childrenInTraversalOrder) {
203 output << child_id << ", ";
204 }
205 output << "]\n";
206 }
207 if (!node.childrenInHitTestOrder.empty()) {
208 output << "children in hit test order:[";
209 for (const auto child_id : node.childrenInHitTestOrder) {
210 output << child_id << ", ";
211 }
212 output << ']';
213 }
214
215 return output.str();
216}
217#endif // !FLUTTER_RELEASE
218
219} // namespace
220
222 SetSemanticsEnabledCallback set_semantics_enabled_callback,
223 DispatchSemanticsActionCallback dispatch_semantics_action_callback,
224 fuchsia::accessibility::semantics::SemanticsManagerHandle semantics_manager,
225 fuchsia::ui::views::ViewRef view_ref,
226 inspect::Node inspect_node)
227 : set_semantics_enabled_callback_(
228 std::move(set_semantics_enabled_callback)),
229 dispatch_semantics_action_callback_(
230 std::move(dispatch_semantics_action_callback)),
231 binding_(this),
232 fuchsia_semantics_manager_(semantics_manager.Bind()),
233 atomic_updates_(std::make_shared<std::queue<FuchsiaAtomicUpdate>>()),
234 inspect_node_(std::move(inspect_node)) {
235 fuchsia_semantics_manager_.set_error_handler([](zx_status_t status) {
236 FML_LOG(ERROR) << "Flutter cannot connect to SemanticsManager with status: "
237 << zx_status_get_string(status) << ".";
238 });
239 fuchsia_semantics_manager_->RegisterViewForSemantics(
240 std::move(view_ref), binding_.NewBinding(), tree_ptr_.NewRequest());
241
242#if !FLUTTER_RELEASE
243 // The first argument to |CreateLazyValues| is the name of the lazy node, and
244 // will only be displayed if the callback used to generate the node's content
245 // fails. Therefore, we use an error message for this node name.
246 inspect_node_tree_dump_ =
247 inspect_node_.CreateLazyValues("dump_fail", [this]() {
248 inspect::Inspector inspector;
249 if (auto it = nodes_.find(kRootNodeId); it == nodes_.end()) {
250 inspector.GetRoot().CreateString(
251 "empty_tree", "this semantic tree is empty", &inspector);
252 } else {
253 FillInspectTree(
254 kRootNodeId, /*current_level=*/1,
255 inspector.GetRoot().CreateChild(kTreeDumpInspectRootName),
256 &inspector);
257 }
258 return fpromise::make_ok_promise(std::move(inspector));
259 });
260#endif // !FLUTTER_RELEASE
261}
262
264 return semantics_enabled_;
265}
266
268 semantics_enabled_ = enabled;
269 if (!enabled) {
270 nodes_.clear();
271 }
272}
273
274fuchsia::ui::gfx::BoundingBox AccessibilityBridge::GetNodeLocation(
275 const flutter::SemanticsNode& node) const {
276 fuchsia::ui::gfx::BoundingBox box;
277 box.min.x = node.rect.fLeft;
278 box.min.y = node.rect.fTop;
279 box.min.z = static_cast<float>(node.elevation);
280 box.max.x = node.rect.fRight;
281 box.max.y = node.rect.fBottom;
282 box.max.z = static_cast<float>(node.thickness);
283 return box;
284}
285
286fuchsia::ui::gfx::mat4 AccessibilityBridge::GetNodeTransform(
287 const flutter::SemanticsNode& node) const {
288 return ConvertSkiaTransformToMat4(node.transform);
289}
290
291fuchsia::ui::gfx::mat4 AccessibilityBridge::ConvertSkiaTransformToMat4(
292 const SkM44 transform) const {
293 fuchsia::ui::gfx::mat4 value;
294 float* m = value.matrix.data();
295 transform.getColMajor(m);
296 return value;
297}
298
299fuchsia::accessibility::semantics::Attributes
300AccessibilityBridge::GetNodeAttributes(const flutter::SemanticsNode& node,
301 size_t* added_size) const {
302 fuchsia::accessibility::semantics::Attributes attributes;
303 // TODO(MI4-2531): Don't truncate.
304 if (node.label.size() > fuchsia::accessibility::semantics::MAX_LABEL_SIZE) {
305 attributes.set_label(node.label.substr(
306 0, fuchsia::accessibility::semantics::MAX_LABEL_SIZE));
307 *added_size += fuchsia::accessibility::semantics::MAX_LABEL_SIZE;
308 } else {
309 attributes.set_label(node.label);
310 *added_size += node.label.size();
311 }
312
313 if (node.tooltip.size() > fuchsia::accessibility::semantics::MAX_LABEL_SIZE) {
314 attributes.set_secondary_label(node.tooltip.substr(
315 0, fuchsia::accessibility::semantics::MAX_LABEL_SIZE));
316 *added_size += fuchsia::accessibility::semantics::MAX_LABEL_SIZE;
317 } else {
318 attributes.set_secondary_label(node.tooltip);
319 *added_size += node.tooltip.size();
320 }
321
323 attributes.set_is_keyboard_key(true);
324 }
325
326 return attributes;
327}
328
329fuchsia::accessibility::semantics::States AccessibilityBridge::GetNodeStates(
330 const flutter::SemanticsNode& node,
331 size_t* additional_size) const {
332 fuchsia::accessibility::semantics::States states;
333 (*additional_size) += sizeof(fuchsia::accessibility::semantics::States);
334
335 // Set checked state.
337 states.set_checked_state(
338 fuchsia::accessibility::semantics::CheckedState::NONE);
339 } else {
340 states.set_checked_state(
342 ? fuchsia::accessibility::semantics::CheckedState::CHECKED
343 : fuchsia::accessibility::semantics::CheckedState::UNCHECKED);
344 }
345
346 // Set enabled state.
348 states.set_enabled_state(
350 ? fuchsia::accessibility::semantics::EnabledState::ENABLED
351 : fuchsia::accessibility::semantics::EnabledState::DISABLED);
352 }
353
354 // Set selected state.
355 states.set_selected(node.HasFlag(flutter::SemanticsFlags::kIsSelected));
356
357 // Flutter's definition of a hidden node is different from Fuchsia, so it must
358 // not be set here.
359
360 // Set value.
361 if (node.value.size() > fuchsia::accessibility::semantics::MAX_VALUE_SIZE) {
362 states.set_value(node.value.substr(
363 0, fuchsia::accessibility::semantics::MAX_VALUE_SIZE));
364 (*additional_size) += fuchsia::accessibility::semantics::MAX_VALUE_SIZE;
365 } else {
366 states.set_value(node.value);
367 (*additional_size) += node.value.size();
368 }
369
370 // Set toggled state.
372 states.set_toggled_state(
374 ? fuchsia::accessibility::semantics::ToggledState::ON
375 : fuchsia::accessibility::semantics::ToggledState::OFF);
376 }
377
378 return states;
379}
380
381std::vector<fuchsia::accessibility::semantics::Action>
382AccessibilityBridge::GetNodeActions(const flutter::SemanticsNode& node,
383 size_t* additional_size) const {
384 std::vector<fuchsia::accessibility::semantics::Action> node_actions;
385
387 node_actions.push_back(fuchsia::accessibility::semantics::Action::DEFAULT);
388 }
390 node_actions.push_back(
391 fuchsia::accessibility::semantics::Action::SECONDARY);
392 }
394 node_actions.push_back(
395 fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN);
396 }
398 node_actions.push_back(
399 fuchsia::accessibility::semantics::Action::INCREMENT);
400 }
402 node_actions.push_back(
403 fuchsia::accessibility::semantics::Action::DECREMENT);
404 }
405
406 *additional_size +=
407 node_actions.size() * sizeof(fuchsia::accessibility::semantics::Action);
408 return node_actions;
409}
410
411fuchsia::accessibility::semantics::Role AccessibilityBridge::GetNodeRole(
412 const flutter::SemanticsNode& node) const {
414 return fuchsia::accessibility::semantics::Role::BUTTON;
415 }
416
418 return fuchsia::accessibility::semantics::Role::TEXT_FIELD;
419 }
420
422 return fuchsia::accessibility::semantics::Role::LINK;
423 }
424
426 return fuchsia::accessibility::semantics::Role::SLIDER;
427 }
428
431 }
433 return fuchsia::accessibility::semantics::Role::IMAGE;
434 }
435
436 // If a flutter node supports the kIncrease or kDecrease actions, it can be
437 // treated as a slider control by assistive technology. This is important
438 // because users have special gestures to deal with sliders, and Fuchsia API
439 // requires nodes that can receive this kind of action to be a slider control.
442 return fuchsia::accessibility::semantics::Role::SLIDER;
443 }
444
445 // If a flutter node has a checked state, then we assume it is either a
446 // checkbox or a radio button. We distinguish between checkboxes and
447 // radio buttons based on membership in a mutually exclusive group.
450 return fuchsia::accessibility::semantics::Role::RADIO_BUTTON;
451 } else {
452 return fuchsia::accessibility::semantics::Role::CHECK_BOX;
453 }
454 }
455
457 return fuchsia::accessibility::semantics::Role::TOGGLE_SWITCH;
458 }
459 return fuchsia::accessibility::semantics::Role::UNKNOWN;
460}
461
462std::unordered_set<int32_t> AccessibilityBridge::GetDescendants(
463 int32_t node_id) const {
464 std::unordered_set<int32_t> descendents;
465 std::deque<int32_t> to_process = {node_id};
466 while (!to_process.empty()) {
467 int32_t id = to_process.front();
468 to_process.pop_front();
469 descendents.emplace(id);
470
471 auto it = nodes_.find(id);
472 if (it != nodes_.end()) {
473 const auto& node = it->second.data;
474 for (const auto& child : node.childrenInHitTestOrder) {
475 if (descendents.find(child) == descendents.end()) {
476 to_process.push_back(child);
477 } else {
478 // This indicates either a cycle or a child with multiple parents.
479 // Flutter should never let this happen, but the engine API does not
480 // explicitly forbid it right now.
481 // TODO(http://fxbug.dev/75905): Crash flutter accessibility bridge
482 // when a cycle in the tree is found.
483 FML_LOG(ERROR) << "Semantics Node " << child
484 << " has already been listed as a child of another "
485 "node, ignoring for parent "
486 << id << ".";
487 }
488 }
489 }
490 }
491 return descendents;
492}
493
494// The only known usage of a negative number for a node ID is in the embedder
495// API as a sentinel value, which is not expected here. No valid producer of
496// nodes should give us a negative ID.
497static uint32_t FlutterIdToFuchsiaId(int32_t flutter_node_id) {
498 FML_DCHECK(flutter_node_id >= 0)
499 << "Unexpectedly received a negative semantics node ID.";
500 return static_cast<uint32_t>(flutter_node_id);
501}
502
503void AccessibilityBridge::PruneUnreachableNodes(
504 FuchsiaAtomicUpdate* atomic_update) {
505 const auto& reachable_nodes = GetDescendants(kRootNodeId);
506 auto iter = nodes_.begin();
507 while (iter != nodes_.end()) {
508 int32_t id = iter->first;
509 if (reachable_nodes.find(id) == reachable_nodes.end()) {
510 atomic_update->AddNodeDeletion(FlutterIdToFuchsiaId(id));
511 iter = nodes_.erase(iter);
512 } else {
513 iter++;
514 }
515 }
516}
517
518// TODO(FIDL-718) - remove this, handle the error instead in something like
519// set_error_handler.
520static void PrintNodeSizeError(uint32_t node_id) {
521 FML_LOG(ERROR) << "Semantics node with ID " << node_id
522 << " exceeded the maximum FIDL message size and may not "
523 "be delivered to the accessibility manager service.";
524}
525
528 float view_pixel_ratio) {
529 if (update.empty()) {
530 return;
531 }
532 FML_DCHECK(nodes_.find(kRootNodeId) != nodes_.end() ||
533 update.find(kRootNodeId) != update.end())
534 << "AccessibilityBridge received an update with out ever getting a root "
535 "node.";
536
537 FuchsiaAtomicUpdate atomic_update;
538 bool has_root_node_update = false;
539 // TODO(MI4-2498): Actions, Roles, hit test children, additional
540 // flags/states/attr
541
542 // TODO(MI4-1478): Support for partial updates for nodes > 64kb
543 // e.g. if a node has a long label or more than 64k children.
544 for (const auto& [flutter_node_id, flutter_node] : update) {
545 size_t this_node_size = sizeof(fuchsia::accessibility::semantics::Node);
546 // We handle root update separately in GetRootNodeUpdate.
547 // TODO(chunhtai): remove this special case after we remove the inverse
548 // view pixel ratio transformation in scenic view.
549 // TODO(http://fxbug.dev/75908): Investigate flutter a11y bridge refactor
550 // after removal of the inverse view pixel ratio transformation in scenic
551 // view).
552 if (flutter_node.id == kRootNodeId) {
553 root_flutter_semantics_node_ = flutter_node;
554 has_root_node_update = true;
555 continue;
556 }
557 // Store the nodes for later hit testing and logging.
558 nodes_[flutter_node.id].data = flutter_node;
559
560 fuchsia::accessibility::semantics::Node fuchsia_node;
561 std::vector<uint32_t> child_ids;
562 // Send the nodes in traversal order, so the manager can figure out
563 // traversal.
564 for (int32_t flutter_child_id : flutter_node.childrenInTraversalOrder) {
565 child_ids.push_back(FlutterIdToFuchsiaId(flutter_child_id));
566 }
567 // TODO(http://fxbug.dev/75910): check the usage of FlutterIdToFuchsiaId in
568 // the flutter accessibility bridge.
569 fuchsia_node.set_node_id(flutter_node.id)
570 .set_role(GetNodeRole(flutter_node))
571 .set_location(GetNodeLocation(flutter_node))
572 .set_transform(GetNodeTransform(flutter_node))
573 .set_attributes(GetNodeAttributes(flutter_node, &this_node_size))
574 .set_states(GetNodeStates(flutter_node, &this_node_size))
575 .set_actions(GetNodeActions(flutter_node, &this_node_size))
576 .set_child_ids(child_ids);
577 this_node_size +=
578 kNodeIdSize * flutter_node.childrenInTraversalOrder.size();
579
580 atomic_update.AddNodeUpdate(std::move(fuchsia_node), this_node_size);
581 }
582
583 // Handles root node update.
584 if (has_root_node_update || last_seen_view_pixel_ratio_ != view_pixel_ratio) {
585 last_seen_view_pixel_ratio_ = view_pixel_ratio;
586 size_t root_node_size;
587 fuchsia::accessibility::semantics::Node root_update =
588 GetRootNodeUpdate(root_node_size);
589 atomic_update.AddNodeUpdate(std::move(root_update), root_node_size);
590 }
591
592 PruneUnreachableNodes(&atomic_update);
593 UpdateScreenRects();
594
595 atomic_updates_->push(std::move(atomic_update));
596 if (atomic_updates_->size() == 1) {
597 // There were no commits in the queue, so send this one.
598 Apply(&atomic_updates_->front());
599 }
600}
601
602fuchsia::accessibility::semantics::Node AccessibilityBridge::GetRootNodeUpdate(
603 size_t& node_size) {
604 fuchsia::accessibility::semantics::Node root_fuchsia_node;
605 std::vector<uint32_t> child_ids;
606 node_size = sizeof(fuchsia::accessibility::semantics::Node);
607 for (int32_t flutter_child_id :
608 root_flutter_semantics_node_.childrenInTraversalOrder) {
609 child_ids.push_back(FlutterIdToFuchsiaId(flutter_child_id));
610 }
611 // Applies the inverse view pixel ratio transformation to the root node.
612 float inverse_view_pixel_ratio = 1.f / last_seen_view_pixel_ratio_;
613 SkM44 inverse_view_pixel_ratio_transform;
614 inverse_view_pixel_ratio_transform.setScale(inverse_view_pixel_ratio,
615 inverse_view_pixel_ratio, 1.f);
616
617 SkM44 result = root_flutter_semantics_node_.transform *
618 inverse_view_pixel_ratio_transform;
619 nodes_[root_flutter_semantics_node_.id].data = root_flutter_semantics_node_;
620
621 // TODO(http://fxbug.dev/75910): check the usage of FlutterIdToFuchsiaId in
622 // the flutter accessibility bridge.
623 root_fuchsia_node.set_node_id(root_flutter_semantics_node_.id)
624 .set_role(GetNodeRole(root_flutter_semantics_node_))
625 .set_location(GetNodeLocation(root_flutter_semantics_node_))
626 .set_transform(ConvertSkiaTransformToMat4(result))
627 .set_attributes(
628 GetNodeAttributes(root_flutter_semantics_node_, &node_size))
629 .set_states(GetNodeStates(root_flutter_semantics_node_, &node_size))
630 .set_actions(GetNodeActions(root_flutter_semantics_node_, &node_size))
631 .set_child_ids(child_ids);
632 node_size += kNodeIdSize *
633 root_flutter_semantics_node_.childrenInTraversalOrder.size();
634 return root_fuchsia_node;
635}
636
638 fuchsia::accessibility::semantics::SemanticEvent semantic_event;
639 fuchsia::accessibility::semantics::AnnounceEvent announce_event;
640 announce_event.set_message(message);
641 semantic_event.set_announce(std::move(announce_event));
642
643 tree_ptr_->SendSemanticEvent(std::move(semantic_event), []() {});
644}
645
646void AccessibilityBridge::UpdateScreenRects() {
647 std::unordered_set<int32_t> visited_nodes;
648
649 // The embedder applies a special pixel ratio transform to the root of the
650 // view, and the accessibility bridge applies the inverse of this transform
651 // to the root node. However, this transform is not persisted in the flutter
652 // representation of the root node, so we need to account for it explicitly
653 // here.
654 float inverse_view_pixel_ratio = 1.f / last_seen_view_pixel_ratio_;
655 SkM44 inverse_view_pixel_ratio_transform;
656 inverse_view_pixel_ratio_transform.setScale(inverse_view_pixel_ratio,
657 inverse_view_pixel_ratio, 1.f);
658
659 UpdateScreenRects(kRootNodeId, inverse_view_pixel_ratio_transform,
660 &visited_nodes);
661}
662
663void AccessibilityBridge::UpdateScreenRects(
664 int32_t node_id,
665 SkM44 parent_transform,
666 std::unordered_set<int32_t>* visited_nodes) {
667 auto it = nodes_.find(node_id);
668 if (it == nodes_.end()) {
669 FML_LOG(ERROR) << "UpdateScreenRects called on unknown node";
670 return;
671 }
672 auto& node = it->second;
673 const auto& current_transform = parent_transform * node.data.transform;
674
675 const auto& rect = node.data.rect;
676 SkV4 dst[2] = {
677 current_transform.map(rect.left(), rect.top(), 0, 1),
678 current_transform.map(rect.right(), rect.bottom(), 0, 1),
679 };
680 node.screen_rect.setLTRB(dst[0].x, dst[0].y, dst[1].x, dst[1].y);
681 node.screen_rect.sort();
682
683 visited_nodes->emplace(node_id);
684
685 for (uint32_t child_id : node.data.childrenInHitTestOrder) {
686 if (visited_nodes->find(child_id) == visited_nodes->end()) {
687 UpdateScreenRects(child_id, current_transform, visited_nodes);
688 }
689 }
690}
691
692std::optional<flutter::SemanticsAction>
693AccessibilityBridge::GetFlutterSemanticsAction(
695 uint32_t node_id) {
696 switch (fuchsia_action) {
697 // The default action associated with the element.
698 case fuchsia::accessibility::semantics::Action::DEFAULT:
700 // The secondary action associated with the element. This may correspond to
701 // a long press (touchscreens) or right click (mouse).
702 case fuchsia::accessibility::semantics::Action::SECONDARY:
704 // Set (input/non-accessibility) focus on this element.
705 case fuchsia::accessibility::semantics::Action::SET_FOCUS:
706 FML_LOG(WARNING)
707 << "Unsupported action SET_FOCUS sent for accessibility node "
708 << node_id;
709 return {};
710 // Set the element's value.
711 case fuchsia::accessibility::semantics::Action::SET_VALUE:
712 FML_LOG(WARNING)
713 << "Unsupported action SET_VALUE sent for accessibility node "
714 << node_id;
715 return {};
716 // Scroll node to make it visible.
717 case fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN:
719 case fuchsia::accessibility::semantics::Action::INCREMENT:
721 case fuchsia::accessibility::semantics::Action::DECREMENT:
723 default:
724 FML_LOG(WARNING) << "Unexpected action "
725 << static_cast<int32_t>(fuchsia_action)
726 << " sent for accessibility node " << node_id;
727 return {};
728 }
729}
730
731// |fuchsia::accessibility::semantics::SemanticListener|
733 uint32_t node_id,
735 fuchsia::accessibility::semantics::SemanticListener::
736 OnAccessibilityActionRequestedCallback callback) {
737 // TODO(http://fxbug.dev/75910): check the usage of FlutterIdToFuchsiaId in
738 // the flutter accessibility bridge.
739 if (nodes_.find(node_id) == nodes_.end()) {
740 FML_LOG(ERROR) << "Attempted to send accessibility action "
741 << static_cast<int32_t>(action)
742 << " to unknown node id: " << node_id;
743 callback(false);
744 return;
745 }
746
747 std::optional<flutter::SemanticsAction> flutter_action =
748 GetFlutterSemanticsAction(action, node_id);
749 if (!flutter_action.has_value()) {
750 callback(false);
751 return;
752 }
753 dispatch_semantics_action_callback_(static_cast<int32_t>(node_id),
754 flutter_action.value());
755 callback(true);
756}
757
758// |fuchsia::accessibility::semantics::SemanticListener|
760 fuchsia::math::PointF local_point,
761 fuchsia::accessibility::semantics::SemanticListener::HitTestCallback
762 callback) {
763 auto hit_node_id = GetHitNode(kRootNodeId, local_point.x, local_point.y);
764 FML_DCHECK(hit_node_id.has_value());
765 fuchsia::accessibility::semantics::Hit hit;
766 // TODO(http://fxbug.dev/75910): check the usage of FlutterIdToFuchsiaId in
767 // the flutter accessibility bridge.
768 hit.set_node_id(hit_node_id.value_or(kRootNodeId));
769 callback(std::move(hit));
770}
771
772std::optional<int32_t> AccessibilityBridge::GetHitNode(int32_t node_id,
773 float x,
774 float y) {
775 auto it = nodes_.find(node_id);
776 if (it == nodes_.end()) {
777 FML_LOG(ERROR) << "Attempted to hit test unknown node id: " << node_id;
778 return {};
779 }
780 auto const& node = it->second;
781 if (node.data.flags &
782 static_cast<int32_t>(flutter::SemanticsFlags::kIsHidden) || //
783 !node.screen_rect.contains(x, y)) {
784 return {};
785 }
786 for (int32_t child_id : node.data.childrenInHitTestOrder) {
787 auto candidate = GetHitNode(child_id, x, y);
788 if (candidate) {
789 return candidate;
790 }
791 }
792
793 if (IsFocusable(node.data)) {
794 return node_id;
795 }
796
797 return {};
798}
799
800bool AccessibilityBridge::IsFocusable(
801 const flutter::SemanticsNode& node) const {
803 return false;
804 }
805
807 return true;
808 }
809
810 // Always consider platform views focusable.
811 if (node.IsPlatformViewNode()) {
812 return true;
813 }
814
815 // Always consider actionable nodes focusable.
816 if (node.actions != 0) {
817 return true;
818 }
819
820 // Consider text nodes focusable.
821 return !node.label.empty() || !node.value.empty() || !node.hint.empty();
822}
823
824// |fuchsia::accessibility::semantics::SemanticListener|
825void AccessibilityBridge::OnSemanticsModeChanged(
826 bool enabled,
827 OnSemanticsModeChangedCallback callback) {
828 set_semantics_enabled_callback_(enabled);
829}
830
831#if !FLUTTER_RELEASE
832void AccessibilityBridge::FillInspectTree(int32_t flutter_node_id,
833 int32_t current_level,
834 inspect::Node inspect_node,
835 inspect::Inspector* inspector) const {
836 const auto it = nodes_.find(flutter_node_id);
837 if (it == nodes_.end()) {
838 inspect_node.CreateString(
839 "missing_child",
840 "This node has a parent in the semantic tree but has no value",
841 inspector);
842 inspector->emplace(std::move(inspect_node));
843 return;
844 }
845 const auto& semantic_node = it->second;
846 const auto& data = semantic_node.data;
847
848 inspect_node.CreateInt("id", data.id, inspector);
849
850 // Even with an empty label, we still want to create the property to
851 // explicetly show that it is empty.
852 inspect_node.CreateString("label", data.label, inspector);
853 if (!data.hint.empty()) {
854 inspect_node.CreateString("hint", data.hint, inspector);
855 }
856 if (!data.value.empty()) {
857 inspect_node.CreateString("value", data.value, inspector);
858 }
859 if (!data.increasedValue.empty()) {
860 inspect_node.CreateString("increased_value", data.increasedValue,
861 inspector);
862 }
863 if (!data.decreasedValue.empty()) {
864 inspect_node.CreateString("decreased_value", data.decreasedValue,
865 inspector);
866 }
867
868 if (data.textDirection) {
869 inspect_node.CreateString(
870 "text_direction", data.textDirection == 1 ? "RTL" : "LTR", inspector);
871 }
872
873 if (data.flags) {
874 inspect_node.CreateString("flags", NodeFlagsToString(data), inspector);
875 }
876 if (data.actions) {
877 inspect_node.CreateString("actions", NodeActionsToString(data), inspector);
878 }
879
880 inspect_node.CreateString(
881 "location", NodeLocationToString(semantic_node.screen_rect), inspector);
882 if (!data.childrenInTraversalOrder.empty() ||
883 !data.childrenInHitTestOrder.empty()) {
884 inspect_node.CreateString("children", NodeChildrenToString(data),
885 inspector);
886 }
887
888 inspect_node.CreateInt("current_level", current_level, inspector);
889
890 for (int32_t flutter_child_id : semantic_node.data.childrenInTraversalOrder) {
891 const auto inspect_name = "node_" + std::to_string(flutter_child_id);
892 FillInspectTree(flutter_child_id, current_level + 1,
893 inspect_node.CreateChild(inspect_name), inspector);
894 }
895
896 inspector->emplace(std::move(inspect_node));
897}
898#endif // !FLUTTER_RELEASE
899
900void AccessibilityBridge::Apply(FuchsiaAtomicUpdate* atomic_update) {
901 size_t begin = 0;
902 auto it = atomic_update->deletions.begin();
903
904 // Process up to kMaxDeletionsPerUpdate deletions at a time.
905 while (it != atomic_update->deletions.end()) {
906 std::vector<uint32_t> to_delete;
907 size_t end = std::min(atomic_update->deletions.size() - begin,
909 std::copy(std::make_move_iterator(it), std::make_move_iterator(it + end),
910 std::back_inserter(to_delete));
911 tree_ptr_->DeleteSemanticNodes(std::move(to_delete));
912 begin = end;
913 it += end;
914 }
915
916 std::vector<fuchsia::accessibility::semantics::Node> to_update;
917 size_t current_size = 0;
918 for (auto& node_and_size : atomic_update->updates) {
919 if (current_size + node_and_size.second > kMaxMessageSize) {
920 tree_ptr_->UpdateSemanticNodes(std::move(to_update));
921 current_size = 0;
922 to_update.clear();
923 }
924 current_size += node_and_size.second;
925 to_update.push_back(std::move(node_and_size.first));
926 }
927 if (!to_update.empty()) {
928 tree_ptr_->UpdateSemanticNodes(std::move(to_update));
929 }
930
931 // Commit this update and subsequent ones; for flow control wait for a
932 // response between each commit.
933 tree_ptr_->CommitUpdates(
934 [this, atomic_updates = std::weak_ptr<std::queue<FuchsiaAtomicUpdate>>(
935 atomic_updates_)]() {
936 auto atomic_updates_ptr = atomic_updates.lock();
937 if (!atomic_updates_ptr) {
938 // The queue no longer exists, which means that is no longer
939 // necessary.
940 return;
941 }
942 // Removes the update that just went through.
943 atomic_updates_ptr->pop();
944 if (!atomic_updates_ptr->empty()) {
945 Apply(&atomic_updates_ptr->front());
946 }
947 });
948
949 atomic_update->deletions.clear();
950 atomic_update->updates.clear();
951}
952
953void AccessibilityBridge::FuchsiaAtomicUpdate::AddNodeUpdate(
954 fuchsia::accessibility::semantics::Node node,
955 size_t size) {
956 if (size > kMaxMessageSize) {
957 // TODO(MI4-2531, FIDL-718): Remove this
958 // This is defensive. If, despite our best efforts, we ended up with a node
959 // that is larger than the max fidl size, we send no updates.
960 PrintNodeSizeError(node.node_id());
961 return;
962 }
963 updates.emplace_back(std::move(node), size);
964}
965
966void AccessibilityBridge::FuchsiaAtomicUpdate::AddNodeDeletion(uint32_t id) {
967 deletions.push_back(id);
968}
969} // namespace flutter_runner
static void copy(void *dst, const uint8_t *src, int width, int bpp, int deltaSrc, int offset, const SkPMColor ctable[])
Definition: SkSwizzler.cpp:31
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 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
static float min(float r, float g, float b)
Definition: hsl.cpp:48
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
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
dst
Definition: cp.py:12
static bool Bind(PassBindingsCacheMTL &pass, ShaderStage stage, size_t bind_index, const BufferView &view)
Definition: ref_ptr.h:256
Definition: update.py:1
static SkString to_string(int n)
Definition: nanobench.cpp:119
static SkColor4f transform(SkColor4f c, SkColorSpace *src, SkColorSpace *dst)
Definition: p3.cpp:47
fidl::Binding< fuchsia::ui::composition::ChildViewWatcher > binding_
static constexpr const char HEADER[]
Definition: Main.cpp:30
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
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63
#define ERROR(message)
Definition: elf_loader.cc:260