|
| 1 | +// Copyright 2019 The Fuchsia 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 <zircon/status.h> |
| 8 | +#include <zircon/types.h> |
| 9 | + |
| 10 | +#include <deque> |
| 11 | + |
| 12 | +#include "flutter/fml/logging.h" |
| 13 | +#include "flutter/lib/ui/semantics/semantics_node.h" |
| 14 | + |
| 15 | +namespace flutter_runner { |
| 16 | +AccessibilityBridge::AccessibilityBridge( |
| 17 | + const std::shared_ptr<sys::ServiceDirectory> services, |
| 18 | + fuchsia::ui::views::ViewRef view_ref) |
| 19 | + : binding_(this) { |
| 20 | + services->Connect(fuchsia::accessibility::semantics::SemanticsManager::Name_, |
| 21 | + fuchsia_semantics_manager_.NewRequest().TakeChannel()); |
| 22 | + fuchsia_semantics_manager_.set_error_handler([](zx_status_t status) { |
| 23 | + FML_LOG(ERROR) << "Flutter cannot connect to SemanticsManager with status: " |
| 24 | + << zx_status_get_string(status) << "."; |
| 25 | + }); |
| 26 | + fidl::InterfaceHandle< |
| 27 | + fuchsia::accessibility::semantics::SemanticActionListener> |
| 28 | + listener_handle; |
| 29 | + binding_.Bind(listener_handle.NewRequest()); |
| 30 | + fuchsia_semantics_manager_->RegisterView( |
| 31 | + std::move(view_ref), std::move(listener_handle), tree_ptr_.NewRequest()); |
| 32 | +} |
| 33 | + |
| 34 | +bool AccessibilityBridge::GetSemanticsEnabled() const { |
| 35 | + return semantics_enabled_; |
| 36 | +} |
| 37 | + |
| 38 | +void AccessibilityBridge::SetSemanticsEnabled(bool enabled) { |
| 39 | + semantics_enabled_ = enabled; |
| 40 | + if (!enabled) { |
| 41 | + nodes_.clear(); |
| 42 | + } |
| 43 | +} |
| 44 | + |
| 45 | +fuchsia::ui::gfx::BoundingBox AccessibilityBridge::GetNodeLocation( |
| 46 | + const flutter::SemanticsNode& node) const { |
| 47 | + fuchsia::ui::gfx::BoundingBox box; |
| 48 | + box.min.x = node.rect.fLeft; |
| 49 | + box.min.y = node.rect.fTop; |
| 50 | + box.min.z = static_cast<float>(node.elevation); |
| 51 | + box.max.x = node.rect.fRight; |
| 52 | + box.max.y = node.rect.fBottom; |
| 53 | + box.max.z = static_cast<float>(node.thickness); |
| 54 | + return box; |
| 55 | +} |
| 56 | + |
| 57 | +fuchsia::ui::gfx::mat4 AccessibilityBridge::GetNodeTransform( |
| 58 | + const flutter::SemanticsNode& node) const { |
| 59 | + fuchsia::ui::gfx::mat4 value; |
| 60 | + float* m = value.matrix.data(); |
| 61 | + node.transform.asColMajorf(m); |
| 62 | + return value; |
| 63 | +} |
| 64 | + |
| 65 | +fuchsia::accessibility::semantics::Attributes |
| 66 | +AccessibilityBridge::GetNodeAttributes(const flutter::SemanticsNode& node, |
| 67 | + size_t* added_size) const { |
| 68 | + fuchsia::accessibility::semantics::Attributes attributes; |
| 69 | + // TODO(MI4-2531): Don't truncate. |
| 70 | + if (node.label.size() > fuchsia::accessibility::semantics::MAX_LABEL_SIZE) { |
| 71 | + attributes.set_label(node.label.substr( |
| 72 | + 0, fuchsia::accessibility::semantics::MAX_LABEL_SIZE)); |
| 73 | + *added_size += fuchsia::accessibility::semantics::MAX_LABEL_SIZE; |
| 74 | + } else { |
| 75 | + attributes.set_label(node.label); |
| 76 | + *added_size += node.label.size(); |
| 77 | + } |
| 78 | + |
| 79 | + return attributes; |
| 80 | +} |
| 81 | + |
| 82 | +fuchsia::accessibility::semantics::States AccessibilityBridge::GetNodeStates( |
| 83 | + const flutter::SemanticsNode& node) const { |
| 84 | + fuchsia::accessibility::semantics::States states; |
| 85 | + if (node.HasFlag(flutter::SemanticsFlags::kHasCheckedState)) { |
| 86 | + states.set_checked(node.HasFlag(flutter::SemanticsFlags::kIsChecked)); |
| 87 | + } |
| 88 | + return states; |
| 89 | +} |
| 90 | + |
| 91 | +std::unordered_set<int32_t> AccessibilityBridge::GetDescendants( |
| 92 | + int32_t node_id) const { |
| 93 | + std::unordered_set<int32_t> descendents; |
| 94 | + std::deque<int32_t> to_process = {node_id}; |
| 95 | + while (!to_process.empty()) { |
| 96 | + int32_t id = to_process.front(); |
| 97 | + to_process.pop_front(); |
| 98 | + descendents.emplace(id); |
| 99 | + |
| 100 | + auto it = nodes_.find(id); |
| 101 | + if (it != nodes_.end()) { |
| 102 | + auto const& children = it->second; |
| 103 | + for (const auto& child : children) { |
| 104 | + if (descendents.find(child) == descendents.end()) { |
| 105 | + to_process.push_back(child); |
| 106 | + } else { |
| 107 | + // This indicates either a cycle or a child with multiple parents. |
| 108 | + // Flutter should never let this happen, but the engine API does not |
| 109 | + // explicitly forbid it right now. |
| 110 | + FML_LOG(ERROR) << "Semantics Node " << child |
| 111 | + << " has already been listed as a child of another " |
| 112 | + "node, ignoring for parent " |
| 113 | + << id << "."; |
| 114 | + } |
| 115 | + } |
| 116 | + } |
| 117 | + } |
| 118 | + return descendents; |
| 119 | +} |
| 120 | + |
| 121 | +// The only known usage of a negative number for a node ID is in the embedder |
| 122 | +// API as a sentinel value, which is not expected here. No valid producer of |
| 123 | +// nodes should give us a negative ID. |
| 124 | +static uint32_t FlutterIdToFuchsiaId(int32_t flutter_node_id) { |
| 125 | + FML_DCHECK(flutter_node_id >= 0) |
| 126 | + << "Unexpectedly recieved a negative semantics node ID."; |
| 127 | + return static_cast<uint32_t>(flutter_node_id); |
| 128 | +} |
| 129 | + |
| 130 | +void AccessibilityBridge::PruneUnreachableNodes() { |
| 131 | + const auto& reachable_nodes = GetDescendants(kRootNodeId); |
| 132 | + std::vector<uint32_t> nodes_to_remove; |
| 133 | + auto iter = nodes_.begin(); |
| 134 | + while (iter != nodes_.end()) { |
| 135 | + int32_t id = iter->first; |
| 136 | + if (reachable_nodes.find(id) == reachable_nodes.end()) { |
| 137 | + // TODO(MI4-2531): This shouldn't be strictly necessary at this level. |
| 138 | + if (sizeof(nodes_to_remove) + (nodes_to_remove.size() * kNodeIdSize) >= |
| 139 | + kMaxMessageSize) { |
| 140 | + tree_ptr_->DeleteSemanticNodes(std::move(nodes_to_remove)); |
| 141 | + nodes_to_remove.clear(); |
| 142 | + } |
| 143 | + nodes_to_remove.push_back(FlutterIdToFuchsiaId(id)); |
| 144 | + iter = nodes_.erase(iter); |
| 145 | + } else { |
| 146 | + iter++; |
| 147 | + } |
| 148 | + } |
| 149 | + if (!nodes_to_remove.empty()) { |
| 150 | + tree_ptr_->DeleteSemanticNodes(std::move(nodes_to_remove)); |
| 151 | + } |
| 152 | +} |
| 153 | + |
| 154 | +// TODO(FIDL-718) - remove this, handle the error instead in something like |
| 155 | +// set_error_handler. |
| 156 | +static void PrintNodeSizeError(uint32_t node_id) { |
| 157 | + FML_LOG(ERROR) << "Semantics node with ID " << node_id |
| 158 | + << " exceeded the maximum FIDL message size and may not " |
| 159 | + "be delivered to the accessibility manager service."; |
| 160 | +} |
| 161 | + |
| 162 | +void AccessibilityBridge::AddSemanticsNodeUpdate( |
| 163 | + const flutter::SemanticsNodeUpdates update) { |
| 164 | + if (update.empty()) { |
| 165 | + return; |
| 166 | + } |
| 167 | + FML_DCHECK(nodes_.find(kRootNodeId) != nodes_.end() || |
| 168 | + update.find(kRootNodeId) != update.end()) |
| 169 | + << "AccessibilityBridge received an update with out ever getting a root " |
| 170 | + "node."; |
| 171 | + |
| 172 | + std::vector<fuchsia::accessibility::semantics::Node> nodes; |
| 173 | + size_t current_size = 0; |
| 174 | + |
| 175 | + // TODO(MI4-2498): Actions, Roles, hit test children, additional |
| 176 | + // flags/states/attr |
| 177 | + |
| 178 | + // TODO(MI4-1478): Support for partial updates for nodes > 64kb |
| 179 | + // e.g. if a node has a long label or more than 64k children. |
| 180 | + for (const auto& value : update) { |
| 181 | + size_t this_node_size = sizeof(fuchsia::accessibility::semantics::Node); |
| 182 | + const auto& flutter_node = value.second; |
| 183 | + nodes_[flutter_node.id] = |
| 184 | + std::vector<int32_t>(flutter_node.childrenInTraversalOrder); |
| 185 | + fuchsia::accessibility::semantics::Node fuchsia_node; |
| 186 | + std::vector<uint32_t> child_ids; |
| 187 | + for (int32_t flutter_child_id : flutter_node.childrenInTraversalOrder) { |
| 188 | + child_ids.push_back(FlutterIdToFuchsiaId(flutter_child_id)); |
| 189 | + } |
| 190 | + fuchsia_node.set_node_id(flutter_node.id) |
| 191 | + .set_location(GetNodeLocation(flutter_node)) |
| 192 | + .set_transform(GetNodeTransform(flutter_node)) |
| 193 | + .set_attributes(GetNodeAttributes(flutter_node, &this_node_size)) |
| 194 | + .set_states(GetNodeStates(flutter_node)) |
| 195 | + .set_child_ids(child_ids); |
| 196 | + this_node_size += |
| 197 | + kNodeIdSize * flutter_node.childrenInTraversalOrder.size(); |
| 198 | + |
| 199 | + // TODO(MI4-2531, FIDL-718): Remove this |
| 200 | + // This is defensive. If, despite our best efforts, we ended up with a node |
| 201 | + // that is larger than the max fidl size, we send no updates. |
| 202 | + if (this_node_size >= kMaxMessageSize) { |
| 203 | + PrintNodeSizeError(flutter_node.id); |
| 204 | + return; |
| 205 | + } |
| 206 | + |
| 207 | + current_size += this_node_size; |
| 208 | + |
| 209 | + // If we would exceed the max FIDL message size by appending this node, |
| 210 | + // we should delete/update/commit now. |
| 211 | + if (current_size >= kMaxMessageSize) { |
| 212 | + tree_ptr_->UpdateSemanticNodes(std::move(nodes)); |
| 213 | + nodes.clear(); |
| 214 | + current_size = this_node_size; |
| 215 | + } |
| 216 | + nodes.push_back(std::move(fuchsia_node)); |
| 217 | + } |
| 218 | + |
| 219 | + if (current_size > kMaxMessageSize) { |
| 220 | + PrintNodeSizeError(nodes.back().node_id()); |
| 221 | + } |
| 222 | + |
| 223 | + PruneUnreachableNodes(); |
| 224 | + |
| 225 | + tree_ptr_->UpdateSemanticNodes(std::move(nodes)); |
| 226 | + tree_ptr_->Commit(); |
| 227 | +} |
| 228 | + |
| 229 | +// |fuchsia::accessibility::semantics::SemanticActionListener| |
| 230 | +void AccessibilityBridge::OnAccessibilityActionRequested( |
| 231 | + uint32_t node_id, |
| 232 | + fuchsia::accessibility::semantics::Action action, |
| 233 | + fuchsia::accessibility::semantics::SemanticActionListener:: |
| 234 | + OnAccessibilityActionRequestedCallback callback) {} |
| 235 | + |
| 236 | +// |fuchsia::accessibility::semantics::SemanticActionListener| |
| 237 | +void AccessibilityBridge::HitTest( |
| 238 | + fuchsia::math::PointF local_point, |
| 239 | + fuchsia::accessibility::semantics::SemanticActionListener::HitTestCallback |
| 240 | + callback) {} |
| 241 | + |
| 242 | +} // namespace flutter_runner |
0 commit comments