5#include "flutter/shell/platform/fuchsia/flutter/accessibility_bridge.h"
7#include <lib/inspect/cpp/inspector.h>
8#include <lib/zx/process.h>
9#include <zircon/status.h>
10#include <zircon/types.h>
15#include "flutter/fml/logging.h"
16#include "flutter/lib/ui/semantics/semantics_node.h"
18#include "../runtime/dart/utils/root_inspect_node.h"
24static constexpr char kTreeDumpInspectRootName[] =
"semantic_tree_root";
30 output +=
"kHasCheckedState|";
33 output +=
"kHasEnabledState|";
36 output +=
"kHasImplicitScrolling|";
39 output +=
"kHasToggledState|";
66 output +=
"kIsInMutuallyExclusiveGroup|";
69 output +=
"kIsKeyboardKey|";
75 output +=
"kIsLiveRegion|";
113 output +=
"kCustomAction|";
122 output +=
"kDidGainAccessibilityFocus|";
125 output +=
"kDidLoseAccessibilityFocus|";
138 output +=
"kMoveCursorBackwardByCharacter|";
141 output +=
"kMoveCursorBackwardByWord|";
144 output +=
"kMoveCursorForwardByCharacter|";
147 output +=
"kMoveCursorForwardByWord|";
159 output +=
"kScrollRight|";
165 output +=
"kSetSelection|";
171 output +=
"kShowOnScreen|";
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 =
201 output <<
"children in traversal order:[";
203 output << child_id <<
", ";
208 output <<
"children in hit test order:[";
210 output << child_id <<
", ";
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)),
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) <<
".";
239 fuchsia_semantics_manager_->RegisterViewForSemantics(
240 std::move(view_ref), binding_.NewBinding(), tree_ptr_.NewRequest());
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);
255 inspector.GetRoot().CreateChild(kTreeDumpInspectRootName),
258 return fpromise::make_ok_promise(std::move(inspector));
264 return semantics_enabled_;
268 semantics_enabled_ = enabled;
274fuchsia::ui::gfx::BoundingBox AccessibilityBridge::GetNodeLocation(
276 fuchsia::ui::gfx::BoundingBox box;
279 box.min.z =
static_cast<float>(node.
elevation);
282 box.max.z =
static_cast<float>(node.
thickness);
286fuchsia::ui::gfx::mat4 AccessibilityBridge::GetNodeTransform(
288 return ConvertSkiaTransformToMat4(node.
transform);
291fuchsia::ui::gfx::mat4 AccessibilityBridge::ConvertSkiaTransformToMat4(
293 fuchsia::ui::gfx::mat4
value;
294 float*
m =
value.matrix.data();
299fuchsia::accessibility::semantics::Attributes
301 size_t* added_size)
const {
302 fuchsia::accessibility::semantics::Attributes attributes;
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;
309 attributes.set_label(node.
label);
310 *added_size += node.
label.size();
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;
318 attributes.set_secondary_label(node.
tooltip);
319 *added_size += node.
tooltip.size();
323 attributes.set_is_keyboard_key(
true);
329fuchsia::accessibility::semantics::States AccessibilityBridge::GetNodeStates(
331 size_t* additional_size)
const {
332 fuchsia::accessibility::semantics::States states;
333 (*additional_size) +=
sizeof(fuchsia::accessibility::semantics::States);
337 states.set_checked_state(
338 fuchsia::accessibility::semantics::CheckedState::NONE);
340 states.set_checked_state(
342 ? fuchsia::accessibility::semantics::CheckedState::CHECKED
343 : fuchsia::accessibility::semantics::CheckedState::UNCHECKED);
348 states.set_enabled_state(
350 ? fuchsia::accessibility::semantics::EnabledState::ENABLED
351 : fuchsia::accessibility::semantics::EnabledState::DISABLED);
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;
366 states.set_value(node.
value);
367 (*additional_size) += node.
value.size();
372 states.set_toggled_state(
374 ? fuchsia::accessibility::semantics::ToggledState::ON
375 : fuchsia::accessibility::semantics::ToggledState::OFF);
381std::vector<fuchsia::accessibility::semantics::Action>
383 size_t* additional_size)
const {
384 std::vector<fuchsia::accessibility::semantics::Action> node_actions;
387 node_actions.push_back(fuchsia::accessibility::semantics::Action::DEFAULT);
390 node_actions.push_back(
391 fuchsia::accessibility::semantics::Action::SECONDARY);
394 node_actions.push_back(
395 fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN);
398 node_actions.push_back(
399 fuchsia::accessibility::semantics::Action::INCREMENT);
402 node_actions.push_back(
403 fuchsia::accessibility::semantics::Action::DECREMENT);
414 return fuchsia::accessibility::semantics::Role::BUTTON;
418 return fuchsia::accessibility::semantics::Role::TEXT_FIELD;
422 return fuchsia::accessibility::semantics::Role::LINK;
426 return fuchsia::accessibility::semantics::Role::SLIDER;
433 return fuchsia::accessibility::semantics::Role::IMAGE;
442 return fuchsia::accessibility::semantics::Role::SLIDER;
450 return fuchsia::accessibility::semantics::Role::RADIO_BUTTON;
452 return fuchsia::accessibility::semantics::Role::CHECK_BOX;
457 return fuchsia::accessibility::semantics::Role::TOGGLE_SWITCH;
459 return fuchsia::accessibility::semantics::Role::UNKNOWN;
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);
471 auto it = nodes_.find(
id);
472 if (it != nodes_.end()) {
473 const auto& node = it->second.data;
475 if (descendents.find(child) == descendents.end()) {
476 to_process.push_back(child);
484 <<
" has already been listed as a child of another "
485 "node, ignoring for parent "
499 <<
"Unexpectedly received a negative semantics node ID.";
500 return static_cast<uint32_t
>(flutter_node_id);
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()) {
511 iter = nodes_.erase(iter);
522 <<
" exceeded the maximum FIDL message size and may not "
523 "be delivered to the accessibility manager service.";
528 float view_pixel_ratio) {
532 FML_DCHECK(nodes_.find(kRootNodeId) != nodes_.end() ||
534 <<
"AccessibilityBridge received an update with out ever getting a root "
537 FuchsiaAtomicUpdate atomic_update;
538 bool has_root_node_update =
false;
544 for (
const auto& [flutter_node_id, flutter_node] :
update) {
545 size_t this_node_size =
sizeof(fuchsia::accessibility::semantics::Node);
552 if (flutter_node.id == kRootNodeId) {
553 root_flutter_semantics_node_ = flutter_node;
554 has_root_node_update =
true;
558 nodes_[flutter_node.id].data = flutter_node;
560 fuchsia::accessibility::semantics::Node fuchsia_node;
561 std::vector<uint32_t> child_ids;
564 for (int32_t flutter_child_id : flutter_node.childrenInTraversalOrder) {
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);
578 kNodeIdSize * flutter_node.childrenInTraversalOrder.size();
580 atomic_update.AddNodeUpdate(std::move(fuchsia_node), this_node_size);
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);
592 PruneUnreachableNodes(&atomic_update);
595 atomic_updates_->push(std::move(atomic_update));
596 if (atomic_updates_->size() == 1) {
598 Apply(&atomic_updates_->front());
602fuchsia::accessibility::semantics::Node AccessibilityBridge::GetRootNodeUpdate(
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 :
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);
618 inverse_view_pixel_ratio_transform;
619 nodes_[root_flutter_semantics_node_.
id].data = root_flutter_semantics_node_;
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))
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);
634 return root_fuchsia_node;
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));
643 tree_ptr_->SendSemanticEvent(std::move(semantic_event), []() {});
646void AccessibilityBridge::UpdateScreenRects() {
647 std::unordered_set<int32_t> visited_nodes;
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);
659 UpdateScreenRects(kRootNodeId, inverse_view_pixel_ratio_transform,
663void AccessibilityBridge::UpdateScreenRects(
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";
672 auto& node = it->second;
673 const auto& current_transform = parent_transform * node.data.
transform;
677 current_transform.map(
rect.left(),
rect.top(), 0, 1),
678 current_transform.map(
rect.right(),
rect.bottom(), 0, 1),
681 node.screen_rect.sort();
683 visited_nodes->emplace(node_id);
686 if (visited_nodes->find(child_id) == visited_nodes->end()) {
687 UpdateScreenRects(child_id, current_transform, visited_nodes);
692std::optional<flutter::SemanticsAction>
693AccessibilityBridge::GetFlutterSemanticsAction(
696 switch (fuchsia_action) {
698 case fuchsia::accessibility::semantics::Action::DEFAULT:
702 case fuchsia::accessibility::semantics::Action::SECONDARY:
705 case fuchsia::accessibility::semantics::Action::SET_FOCUS:
707 <<
"Unsupported action SET_FOCUS sent for accessibility node "
711 case fuchsia::accessibility::semantics::Action::SET_VALUE:
713 <<
"Unsupported action SET_VALUE sent for accessibility node "
717 case fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN:
719 case fuchsia::accessibility::semantics::Action::INCREMENT:
721 case fuchsia::accessibility::semantics::Action::DECREMENT:
724 FML_LOG(WARNING) <<
"Unexpected action "
725 <<
static_cast<int32_t
>(fuchsia_action)
726 <<
" sent for accessibility node " << node_id;
735 fuchsia::accessibility::semantics::SemanticListener::
736 OnAccessibilityActionRequestedCallback
callback) {
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;
747 std::optional<flutter::SemanticsAction> flutter_action =
748 GetFlutterSemanticsAction(
action, node_id);
749 if (!flutter_action.has_value()) {
753 dispatch_semantics_action_callback_(
static_cast<int32_t
>(node_id),
754 flutter_action.value());
760 fuchsia::math::PointF local_point,
761 fuchsia::accessibility::semantics::SemanticListener::HitTestCallback
763 auto hit_node_id = GetHitNode(kRootNodeId, local_point.x, local_point.y);
765 fuchsia::accessibility::semantics::Hit hit;
768 hit.set_node_id(hit_node_id.value_or(kRootNodeId));
772std::optional<int32_t> AccessibilityBridge::GetHitNode(int32_t node_id,
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;
780 auto const& node = it->second;
781 if (node.data.
flags &
783 !node.screen_rect.contains(
x,
y)) {
787 auto candidate = GetHitNode(child_id,
x,
y);
793 if (IsFocusable(node.data)) {
800bool AccessibilityBridge::IsFocusable(
821 return !node.
label.empty() || !node.
value.empty() || !node.
hint.empty();
825void AccessibilityBridge::OnSemanticsModeChanged(
827 OnSemanticsModeChangedCallback
callback) {
828 set_semantics_enabled_callback_(enabled);
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(
840 "This node has a parent in the semantic tree but has no value",
842 inspector->emplace(std::move(inspect_node));
845 const auto& semantic_node = it->second;
846 const auto&
data = semantic_node.data;
848 inspect_node.CreateInt(
"id",
data.id, inspector);
852 inspect_node.CreateString(
"label",
data.label, inspector);
853 if (!
data.hint.empty()) {
854 inspect_node.CreateString(
"hint",
data.hint, inspector);
856 if (!
data.value.empty()) {
857 inspect_node.CreateString(
"value",
data.value, inspector);
859 if (!
data.increasedValue.empty()) {
860 inspect_node.CreateString(
"increased_value",
data.increasedValue,
863 if (!
data.decreasedValue.empty()) {
864 inspect_node.CreateString(
"decreased_value",
data.decreasedValue,
868 if (
data.textDirection) {
869 inspect_node.CreateString(
870 "text_direction",
data.textDirection == 1 ?
"RTL" :
"LTR", inspector);
874 inspect_node.CreateString(
"flags", NodeFlagsToString(
data), inspector);
877 inspect_node.CreateString(
"actions", NodeActionsToString(
data), inspector);
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),
888 inspect_node.CreateInt(
"current_level", current_level, inspector);
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);
896 inspector->emplace(std::move(inspect_node));
900void AccessibilityBridge::Apply(FuchsiaAtomicUpdate* atomic_update) {
902 auto it = atomic_update->deletions.begin();
905 while (it != atomic_update->deletions.end()) {
906 std::vector<uint32_t> to_delete;
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));
916 std::vector<fuchsia::accessibility::semantics::Node> to_update;
917 size_t current_size = 0;
918 for (
auto& node_and_size : atomic_update->updates) {
920 tree_ptr_->UpdateSemanticNodes(std::move(to_update));
924 current_size += node_and_size.second;
925 to_update.push_back(std::move(node_and_size.first));
927 if (!to_update.empty()) {
928 tree_ptr_->UpdateSemanticNodes(std::move(to_update));
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) {
943 atomic_updates_ptr->pop();
944 if (!atomic_updates_ptr->empty()) {
945 Apply(&atomic_updates_ptr->front());
949 atomic_update->deletions.clear();
950 atomic_update->updates.clear();
953void AccessibilityBridge::FuchsiaAtomicUpdate::AddNodeUpdate(
954 fuchsia::accessibility::semantics::Node node,
956 if (
size > kMaxMessageSize) {
963 updates.emplace_back(std::move(node),
size);
966void AccessibilityBridge::FuchsiaAtomicUpdate::AddNodeDeletion(uint32_t
id) {
967 deletions.push_back(
id);
static void copy(void *dst, const uint8_t *src, int width, int bpp, int deltaSrc, int offset, const SkPMColor ctable[])
SkM44 & setScale(SkScalar x, SkScalar y, SkScalar z=1)
void AddSemanticsNodeUpdate(const flutter::SemanticsNodeUpdates update, float view_pixel_ratio)
std::function< void(bool)> SetSemanticsEnabledCallback
void SetSemanticsEnabled(bool enabled)
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 size_t kNodeIdSize
static constexpr uint32_t kMaxMessageSize
bool GetSemanticsEnabled() const
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)
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
#define FML_LOG(severity)
#define FML_DCHECK(condition)
static float min(float r, float g, float b)
sk_sp< SkBlender > blender SkRect rect
static uint32_t FlutterIdToFuchsiaId(int32_t flutter_node_id)
static void PrintNodeSizeError(uint32_t node_id)
@ kIsInMutuallyExclusiveGroup
std::unordered_map< int32_t, SemanticsNode > SemanticsNodeUpdates
@ kMoveCursorBackwardByWord
@ kMoveCursorBackwardByCharacter
@ kMoveCursorForwardByWord
@ kMoveCursorForwardByCharacter
@ kDidLoseAccessibilityFocus
@ kDidGainAccessibilityFocus
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
static bool Bind(PassBindingsCacheMTL &pass, ShaderStage stage, size_t bind_index, const BufferView &view)
static SkString to_string(int n)
static SkColor4f transform(SkColor4f c, SkColorSpace *src, SkColorSpace *dst)
static constexpr const char HEADER[]
SkScalar fBottom
larger y-axis bounds
SkScalar fLeft
smaller x-axis bounds
SkScalar fRight
larger x-axis bounds
SkScalar fTop
smaller y-axis bounds
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