From 85ceaaf7d2dc9f244fcfb4769a41fd3befe0e248 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Thu, 7 Jan 2021 10:26:24 -0800 Subject: [PATCH 01/19] Implements accessibility bridge in common library --- shell/platform/common/cpp/BUILD.gn | 25 + .../common/cpp/accessibility_bridge.cc | 522 ++++++++++++++++++ .../common/cpp/accessibility_bridge.h | 232 ++++++++ .../cpp/accessibility_bridge_unittests.cc | 193 +++++++ .../common/cpp/flutter_accessibility.cc | 110 ++++ .../common/cpp/flutter_accessibility.h | 79 +++ .../cpp/flutter_accessibility_unittests.cc | 47 ++ .../common/cpp/test_accessibility_bridge.cc | 26 + .../common/cpp/test_accessibility_bridge.h | 29 + 9 files changed, 1263 insertions(+) create mode 100644 shell/platform/common/cpp/accessibility_bridge.cc create mode 100644 shell/platform/common/cpp/accessibility_bridge.h create mode 100644 shell/platform/common/cpp/accessibility_bridge_unittests.cc create mode 100644 shell/platform/common/cpp/flutter_accessibility.cc create mode 100644 shell/platform/common/cpp/flutter_accessibility.h create mode 100644 shell/platform/common/cpp/flutter_accessibility_unittests.cc create mode 100644 shell/platform/common/cpp/test_accessibility_bridge.cc create mode 100644 shell/platform/common/cpp/test_accessibility_bridge.h diff --git a/shell/platform/common/cpp/BUILD.gn b/shell/platform/common/cpp/BUILD.gn index 36c9760c419c2..8b04764c019cb 100644 --- a/shell/platform/common/cpp/BUILD.gn +++ b/shell/platform/common/cpp/BUILD.gn @@ -68,6 +68,26 @@ source_set("common_cpp_switches") { ] } +source_set("common_cpp_accessibility") { + public = [ + "accessibility_bridge.h", + "flutter_accessibility.h", + ] + + sources = [ + "accessibility_bridge.cc", + "flutter_accessibility.cc", + ] + + public_configs = + [ "//flutter/third_party/accessibility:accessibility_config" ] + + public_deps = [ + "//flutter/shell/platform/embedder:embedder_as_internal_library", + "//flutter/third_party/accessibility", + ] +} + source_set("common_cpp") { public = [ "incoming_message_dispatcher.h", @@ -139,15 +159,20 @@ if (enable_unittests) { testonly = true sources = [ + "accessibility_bridge_unittests.cc", "engine_switches_unittests.cc", + "flutter_accessibility_unittests.cc", "json_message_codec_unittests.cc", "json_method_codec_unittests.cc", + "test_accessibility_bridge.cc", + "test_accessibility_bridge.h", "text_input_model_unittests.cc", "text_range_unittests.cc", ] deps = [ ":common_cpp", + ":common_cpp_accessibility", ":common_cpp_fixtures", ":common_cpp_input", ":common_cpp_switches", diff --git a/shell/platform/common/cpp/accessibility_bridge.cc b/shell/platform/common/cpp/accessibility_bridge.cc new file mode 100644 index 0000000000000..e92f52a884380 --- /dev/null +++ b/shell/platform/common/cpp/accessibility_bridge.cc @@ -0,0 +1,522 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "accessibility_bridge.h" + +#include +#include + +#include "flutter/third_party/accessibility/ax/ax_tree_update.h" +#include "flutter/third_party/accessibility/base/logging.h" + +namespace ui { // namespace + +constexpr int khasScrollingAction = + FlutterSemanticsAction::kFlutterSemanticsActionScrollLeft | + FlutterSemanticsAction::kFlutterSemanticsActionScrollRight | + FlutterSemanticsAction::kFlutterSemanticsActionScrollUp | + FlutterSemanticsAction::kFlutterSemanticsActionScrollDown; + +// AccessibilityBridge +AccessibilityBridge::AccessibilityBridge(std::unique_ptr delegate, + void* user_data) + : delegate_(std::move(delegate)), + tree_(std::make_unique()), + event_generator_(tree_.get()) { + user_data_ = user_data; + tree_->AddObserver((ui::AXTreeObserver*)this); +} + +AccessibilityBridge::~AccessibilityBridge() { + event_generator_.ReleaseTree(); + tree_->RemoveObserver((ui::AXTreeObserver*)this); +} + +void AccessibilityBridge::AddFlutterSemanticsNodeUpdate( + const FlutterSemanticsNode* node) { + _pending_semantics_node_updates[node->id] = FromFlutterSemanticsNode(node); +} + +void AccessibilityBridge::AddFlutterSemanticsCustomActionUpdate( + const FlutterSemanticsCustomAction* action) { + _pending_semantics_custom_action_updates[action->id] = + FromFlutterSemanticsCustomAction(action); +} + +void AccessibilityBridge::CommitUpdates() { + AXTreeUpdate update{.tree_data = GetAXTree()->data()}; + // Figure out update order, AXTree only accepts update in tree order, where + // parent node must come before the child node in AXTreeUpdate.nodes. + // We start with picking a random node and turn the entire subtree into a + // list. We pick another node from the remaining update, and keep doing so + // until the update map is empty. We then concatenate the lists in the + // reversed order, this guarantees parent updates always come before child + // updates. + std::vector> results; + while (!_pending_semantics_node_updates.empty()) { + auto begin = _pending_semantics_node_updates.begin(); + SemanticsNode target = begin->second; + std::vector sub_tree_list; + GetSubTreeList(target, sub_tree_list); + results.push_back(sub_tree_list); + _pending_semantics_node_updates.erase(begin); + } + + for (size_t i = results.size(); i > 0; i--) { + for (SemanticsNode node : results[i - 1]) { + ConvertFluterUpdate(node, update); + } + } + + tree_->Unserialize(update); + _pending_semantics_node_updates.clear(); + _pending_semantics_custom_action_updates.clear(); + + std::string error = tree_->error(); + if (!error.empty()) { + BASE_LOG() << "Failed to update AXTree, error: " << error; + return; + } + // Handles accessibility events as the result of the semantics update. + for (const auto& targeted_event : event_generator_) { + FlutterAccessibility* event_target = + GetFlutterAccessibilityFromID(targeted_event.node->id()); + if (!event_target) + continue; + + delegate_->OnAccessibilityEvent(targeted_event, this); + } + event_generator_.ClearEvents(); +} + +void* AccessibilityBridge::GetUserData() { + return user_data_; +} + +AccessibilityBridge::AccessibilityBridgeDelegate* AccessibilityBridge::GetDelegate() { + return delegate_.get(); +} + +ui::AXTree* AccessibilityBridge::GetAXTree() { + return tree_.get(); +} + +AXEventGenerator* AccessibilityBridge::GetEventGenerator() { + return &event_generator_; +} + +FlutterAccessibility* AccessibilityBridge::GetFlutterAccessibilityFromID( + int32_t id) const { + const auto iter = id_wrapper_map_.find(id); + if (iter != id_wrapper_map_.end()) + return iter->second; + + return nullptr; +} + +void AccessibilityBridge::SetFocusedNode(int32_t node_id) { + if (last_focused_node_ != node_id) { + FlutterAccessibility* last_focused_child = + GetFlutterAccessibilityFromID(last_focused_node_); + if (last_focused_child) { + delegate_->DispatchAccessibilityAction( + last_focused_node_, + FlutterSemanticsAction:: + kFlutterSemanticsActionDidLoseAccessibilityFocus, + nullptr, 0); + } + last_focused_node_ = node_id; + } +} + +int32_t AccessibilityBridge::GetLastFocusedNode() { + return last_focused_node_; +} + +void AccessibilityBridge::OnNodeWillBeDeleted(ui::AXTree* tree, + ui::AXNode* node) {} + +void AccessibilityBridge::OnSubtreeWillBeDeleted(ui::AXTree* tree, + ui::AXNode* node) {} + +void AccessibilityBridge::OnNodeReparented(ui::AXTree* tree, ui::AXNode* node) { +} + +void AccessibilityBridge::OnRoleChanged(ui::AXTree* tree, + ui::AXNode* node, + ax::mojom::Role old_role, + ax::mojom::Role new_role) {} + +void AccessibilityBridge::OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) { + BASE_DCHECK(node); + FlutterAccessibility* wrapper = FlutterAccessibility::Create(); + id_wrapper_map_[node->id()] = wrapper; + wrapper->Init(this, node); +} + +void AccessibilityBridge::OnNodeDeleted(ui::AXTree* tree, int32_t node_id) { + BASE_DCHECK(node_id != ui::AXNode::kInvalidAXID); + if (FlutterAccessibility* wrapper = GetFlutterAccessibilityFromID(node_id)) { + id_wrapper_map_.erase(node_id); + delete wrapper; + } +} + +void AccessibilityBridge::OnAtomicUpdateFinished( + ui::AXTree* tree, + bool root_changed, + const std::vector& changes) { + // The Flutter semantics update does not include child->parent relationship + // We have to update the relative bound offset container id here in order + // to calculate the screen bound correctly. + for (const auto& change : changes) { + ui::AXNode* node = change.node; + const AXNodeData& data = node->data(); + int32_t offset_container_id = -1; + if (node->parent()) { + offset_container_id = node->parent()->id(); + } + node->SetLocation(offset_container_id, data.relative_bounds.bounds, + data.relative_bounds.transform.get()); + } +} + +// Private method. +void AccessibilityBridge::GetSubTreeList(SemanticsNode target, + std::vector& result) { + result.push_back(target); + for (int32_t child : target.children_in_traversal_order) { + auto iter = _pending_semantics_node_updates.find(child); + if (iter != _pending_semantics_node_updates.end()) { + SemanticsNode node = iter->second; + GetSubTreeList(node, result); + _pending_semantics_node_updates.erase(iter); + } + } +} + +void AccessibilityBridge::ConvertFluterUpdate(const SemanticsNode& node, + AXTreeUpdate& tree_update) { + AXNodeData node_data; + node_data.id = node.id; + SetRoleFromFlutterUpdate(node_data, node); + SetStateFromFlutterUpdate(node_data, node); + SetActionsFromFlutterUpdate(node_data, node); + SetBooleanAttributesFromFlutterUpdate(node_data, node); + SetIntAttributesFromFlutterUpdate(node_data, node); + SetIntListAttributesFromFlutterUpdate(node_data, node); + SetStringListAttributesFromFlutterUpdate(node_data, node); + SetNameFromFlutterUpdate(node_data, node); + SetValueFromFlutterUpdate(node_data, node); + node_data.relative_bounds.bounds.SetRect(node.rect.left, node.rect.top, + node.rect.right - node.rect.left, node.rect.bottom - node.rect.top); + node_data.relative_bounds.transform = std::make_unique( + node.transform.scaleX, node.transform.skewX, node.transform.transX, 0, + node.transform.skewY, node.transform.scaleY, node.transform.transY, 0, + node.transform.pers0, node.transform.pers1, node.transform.pers2, 0, + 0, 0, 0, 0); + for (auto child : node.children_in_traversal_order) { + node_data.child_ids.push_back(child); + } + SetTreeData(node, tree_update); + tree_update.nodes.push_back(node_data); +} + +void AccessibilityBridge::SetRoleFromFlutterUpdate(AXNodeData& node_data, + const SemanticsNode& node) { + FlutterSemanticsFlag flags = node.flags; + if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsButton) { + node_data.role = ax::mojom::Role::kButton; + return; + } + if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField) { + node_data.role = ax::mojom::Role::kTextField; + return; + } + if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsHeader) { + node_data.role = ax::mojom::Role::kHeader; + return; + } + if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsImage) { + node_data.role = ax::mojom::Role::kImage; + return; + } + if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsLink) { + node_data.role = ax::mojom::Role::kLink; + return; + } + + if (flags & kFlutterSemanticsFlagIsInMutuallyExclusiveGroup && + flags & kFlutterSemanticsFlagHasCheckedState) { + node_data.role = ax::mojom::Role::kRadioButton; + return; + } + if (flags & kFlutterSemanticsFlagHasCheckedState) { + node_data.role = ax::mojom::Role::kCheckBox; + return; + } + // If the state cannot be derived from the flutter flags, we fallback to group + // or static text. + if (node.children_in_traversal_order.size() == 0) { + node_data.role = ax::mojom::Role::kStaticText; + } else { + node_data.role = ax::mojom::Role::kGroup; + } +} + +void AccessibilityBridge::SetStateFromFlutterUpdate(AXNodeData& node_data, + const SemanticsNode& node) { + FlutterSemanticsFlag flags = node.flags; + FlutterSemanticsAction actions = node.actions; + if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField && + (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly) == 0) { + node_data.AddState(ax::mojom::State::kEditable); + } + if (node_data.role == ax::mojom::Role::kStaticText && + (actions & khasScrollingAction) == 0 && node.value.empty() && + node.label.empty() && node.hint.empty()) { + node_data.AddState(ax::mojom::State::kIgnored); + } else { + // kFlutterSemanticsFlagIsFocusable means a keyboard focusable, it is + // different from semantics focusable. + // TODO(chunhtai): figure out whether something is not semantics focusable. + node_data.AddState(ax::mojom::State::kFocusable); + } +} + +void AccessibilityBridge::SetActionsFromFlutterUpdate( + AXNodeData& node_data, + const SemanticsNode& node) { + FlutterSemanticsAction actions = node.actions; + if (actions & FlutterSemanticsAction::kFlutterSemanticsActionTap) { + node_data.AddAction(ax::mojom::Action::kDoDefault); + } + if (actions & FlutterSemanticsAction::kFlutterSemanticsActionScrollLeft) { + node_data.AddAction(ax::mojom::Action::kScrollLeft); + } + if (actions & FlutterSemanticsAction::kFlutterSemanticsActionScrollRight) { + node_data.AddAction(ax::mojom::Action::kScrollRight); + } + if (actions & FlutterSemanticsAction::kFlutterSemanticsActionScrollUp) { + node_data.AddAction(ax::mojom::Action::kScrollUp); + } + if (actions & FlutterSemanticsAction::kFlutterSemanticsActionScrollDown) { + node_data.AddAction(ax::mojom::Action::kScrollDown); + } + if (actions & FlutterSemanticsAction::kFlutterSemanticsActionIncrease) { + node_data.AddAction(ax::mojom::Action::kIncrement); + } + if (actions & FlutterSemanticsAction::kFlutterSemanticsActionDecrease) { + node_data.AddAction(ax::mojom::Action::kDecrement); + } + // Every node has show on screen action. + node_data.AddAction(ax::mojom::Action::kScrollToMakeVisible); + + if (actions & FlutterSemanticsAction::kFlutterSemanticsActionSetSelection) { + node_data.AddAction(ax::mojom::Action::kSetSelection); + } + if (actions & FlutterSemanticsAction:: + kFlutterSemanticsActionDidGainAccessibilityFocus) { + node_data.AddAction(ax::mojom::Action::kSetAccessibilityFocus); + } + if (actions & FlutterSemanticsAction:: + kFlutterSemanticsActionDidLoseAccessibilityFocus) { + node_data.AddAction(ax::mojom::Action::kClearAccessibilityFocus); + } + if (actions & FlutterSemanticsAction::kFlutterSemanticsActionCustomAction) { + node_data.AddAction(ax::mojom::Action::kCustomAction); + } +} + +void AccessibilityBridge::SetBooleanAttributesFromFlutterUpdate( + AXNodeData& node_data, + const SemanticsNode& node) { + FlutterSemanticsAction actions = node.actions; + FlutterSemanticsFlag flags = node.flags; + node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kScrollable, + actions & khasScrollingAction); + node_data.AddBoolAttribute( + ax::mojom::BoolAttribute::kClickable, + actions & FlutterSemanticsAction::kFlutterSemanticsActionTap); + node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kClipsChildren, + actions & khasScrollingAction && + node.children_in_traversal_order.size() != 0); + node_data.AddBoolAttribute( + ax::mojom::BoolAttribute::kSelected, + flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsSelected); + node_data.AddBoolAttribute( + ax::mojom::BoolAttribute::kEditableRoot, + flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField && + (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly) > 0); +} + +void AccessibilityBridge::SetIntAttributesFromFlutterUpdate( + AXNodeData& node_data, + const SemanticsNode& node) { + FlutterSemanticsFlag flags = node.flags; + node_data.AddIntAttribute(ax::mojom::IntAttribute::kTextDirection, node.text_direction); + + int sel_start = node.text_selection_base; + int sel_end = node.text_selection_extent; + if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField && + (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly) == 0 && + !node.value.empty()) { + // By default the text field selection should be at the end. + sel_start = sel_start == -1 ? node.value.length() : sel_start; + sel_end = sel_end == -1 ? node.value.length() : sel_end; + } + node_data.AddIntAttribute(ax::mojom::IntAttribute::kTextSelStart, sel_start); + node_data.AddIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, sel_end); + + if (node_data.role == ax::mojom::Role::kRadioButton) { + node_data.AddIntAttribute( + ax::mojom::IntAttribute::kCheckedState, + static_cast( + flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsChecked + ? ax::mojom::CheckedState::kTrue + : ax::mojom::CheckedState::kFalse)); + } +} + +void AccessibilityBridge::SetIntListAttributesFromFlutterUpdate( + AXNodeData& node_data, + const SemanticsNode& node) { + FlutterSemanticsAction actions = node.actions; + if (actions & FlutterSemanticsAction::kFlutterSemanticsActionCustomAction) { + std::vector custom_action_ids; + for (size_t i = 0; i < node.custom_accessibility_actions.size(); i++) { + custom_action_ids.push_back(node.custom_accessibility_actions[i]); + } + node_data.AddIntListAttribute(ax::mojom::IntListAttribute::kCustomActionIds, + custom_action_ids); + } +} + +void AccessibilityBridge::SetStringListAttributesFromFlutterUpdate( + AXNodeData& node_data, + const SemanticsNode& node) { + FlutterSemanticsAction actions = node.actions; + if (actions & FlutterSemanticsAction::kFlutterSemanticsActionCustomAction) { + std::vector custom_action_description; + for (size_t i = 0; i < node.custom_accessibility_actions.size(); i++) { + auto iter = _pending_semantics_custom_action_updates.find( + node.custom_accessibility_actions[i]); + BASE_DCHECK(iter != _pending_semantics_custom_action_updates.end()); + custom_action_description.push_back(iter->second.label); + } + node_data.AddStringListAttribute( + ax::mojom::StringListAttribute::kCustomActionDescriptions, + custom_action_description); + } +} + +void AccessibilityBridge::SetNameFromFlutterUpdate(AXNodeData& node_data, + const SemanticsNode& node) { + node_data.SetName(node.label); +} + +void AccessibilityBridge::SetValueFromFlutterUpdate(AXNodeData& node_data, + const SemanticsNode& node) { + node_data.SetValue(node.value); +} + +void AccessibilityBridge::SetTreeData(const SemanticsNode& node, + AXTreeUpdate& tree_update) { + FlutterSemanticsFlag flags = node.flags; + // Set selection if: + // 1. this text field has a valid selection + // 2. this text field doesn't have a valid selection but had selection stored + // in the tree. + if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField) { + if (node.text_selection_base != -1) { + tree_update.tree_data.sel_anchor_object_id = node.id; + tree_update.tree_data.sel_anchor_offset = node.text_selection_base; + tree_update.tree_data.sel_focus_object_id = node.id; + tree_update.tree_data.sel_focus_offset = node.text_selection_extent; + tree_update.has_tree_data = true; + } else if (tree_update.tree_data.sel_anchor_object_id == node.id) { + tree_update.tree_data.sel_anchor_object_id = AXNode::kInvalidAXID; + tree_update.tree_data.sel_anchor_offset = -1; + tree_update.tree_data.sel_focus_object_id = AXNode::kInvalidAXID; + tree_update.tree_data.sel_focus_offset = -1; + tree_update.has_tree_data = true; + } + } + + if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsFocused && + tree_update.tree_data.focus_id != node.id) { + tree_update.tree_data.focus_id = node.id; + tree_update.has_tree_data = true; + } else if ((flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsFocused) == + 0 && + tree_update.tree_data.focus_id == node.id) { + tree_update.tree_data.focus_id = AXNode::kInvalidAXID; + tree_update.has_tree_data = true; + } +} + +AccessibilityBridge::SemanticsNode +AccessibilityBridge::FromFlutterSemanticsNode( + const FlutterSemanticsNode* flutter_node) { + SemanticsNode result; + result.id = flutter_node->id; + result.flags = flutter_node->flags; + result.actions = flutter_node->actions; + result.text_selection_base = flutter_node->text_selection_base; + result.text_selection_extent = flutter_node->text_selection_extent; + result.scroll_child_count = flutter_node->scroll_child_count; + result.scroll_index = flutter_node->scroll_index; + result.scroll_position = flutter_node->scroll_position; + result.scroll_extent_max = flutter_node->scroll_extent_max; + result.scroll_extent_min = flutter_node->scroll_extent_min; + result.elevation = flutter_node->elevation; + result.thickness = flutter_node->thickness; + if (flutter_node->label) { + result.label = std::string(flutter_node->label); + } + if (flutter_node->hint) { + result.hint = std::string(flutter_node->hint); + } + if (flutter_node->value) { + result.value = std::string(flutter_node->value); + } + if (flutter_node->increased_value) { + result.increased_value = std::string(flutter_node->increased_value); + } + if (flutter_node->decreased_value) { + result.decreased_value = std::string(flutter_node->decreased_value); + } + result.text_direction = flutter_node->text_direction; + result.rect = flutter_node->rect; + result.transform = flutter_node->transform; + if (flutter_node->child_count > 0) { + result.children_in_traversal_order = std::vector( + flutter_node->children_in_traversal_order, + flutter_node->children_in_traversal_order + flutter_node->child_count); + } + if (flutter_node->custom_accessibility_actions_count > 0) { + result.custom_accessibility_actions = std::vector( + flutter_node->custom_accessibility_actions, + flutter_node->custom_accessibility_actions + + flutter_node->custom_accessibility_actions_count); + } + return result; +} + +AccessibilityBridge::SemanticsCustomAction +AccessibilityBridge::FromFlutterSemanticsCustomAction( + const FlutterSemanticsCustomAction* flutter_custom_action) { + SemanticsCustomAction result; + result.id = flutter_custom_action->id; + result.override_action = flutter_custom_action->override_action; + if (flutter_custom_action->label) { + result.label = std::string(flutter_custom_action->label); + } + if (flutter_custom_action->hint) { + result.hint = std::string(flutter_custom_action->hint); + } + return result; +} + +} // namespace ui diff --git a/shell/platform/common/cpp/accessibility_bridge.h b/shell/platform/common/cpp/accessibility_bridge.h new file mode 100644 index 0000000000000..c4d7a755122e9 --- /dev/null +++ b/shell/platform/common/cpp/accessibility_bridge.h @@ -0,0 +1,232 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_ACCESSIBILITY_BRIDGE_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_ACCESSIBILITY_BRIDGE_H_ + +#include + +#include "flutter/shell/platform/embedder/embedder.h" + +#include "flutter/third_party/accessibility/ax/ax_event_generator.h" +#include "flutter/third_party/accessibility/ax/ax_tree.h" +#include "flutter/third_party/accessibility/ax/ax_tree_observer.h" +#include "flutter/third_party/accessibility/ax/platform/ax_platform_node_delegate.h" + +#include "flutter_accessibility.h" + +namespace ui { + +//------------------------------------------------------------------------------ +/// Use this class to maintain an accessibility tree. This class consumes +/// semantics updates from the embedder API and produces an accessibility tree +/// in the native format. +/// +/// To use this class, you must provide your own implementation of +/// FlutterAccessibility and AccessibilityBridgeDelegate. +class AccessibilityBridge : public AXTreeObserver { + public: + //------------------------------------------------------------------------------ + /// Delegate to handle accessibility event and route accessibility action + /// back to the Flutter framework. + /// + /// The accessibility events are generated when accessibility tree changes. + /// These events must be sent to the native accessibility system through + /// the native API for the system to pick up the changes + /// (e.g. NSAccessibilityPostNotification in MacOS). + /// + /// The accessibility actions are generated by the native accessibility system + /// when users interacted with the assistive technologies. Those actions needed + /// to be sent to the Flutter framework. + class AccessibilityBridgeDelegate { + public: + virtual ~AccessibilityBridgeDelegate() = default; + //------------------------------------------------------------------------------ + /// @brief Handle accessibility events generated due to accessibility tree + /// changes. + /// + /// @param[in] targeted_event The object that contains both the generated + /// event and the event target. + /// @param[in] bridge The pointer to the accessibility bridge that + /// can be used for querying the accessibility + /// information at the time the event is fired. + virtual void OnAccessibilityEvent( + AXEventGenerator::TargetedEvent targeted_event, AccessibilityBridge* bridge) = 0; + + //------------------------------------------------------------------------------ + /// @brief Dispatch accessibility action back to the Flutter framework + /// + /// @param[in] target The semantics node id of the action target. + /// @param[in] action The generated flutter semantics action. + /// @param[in] data Additional data associated with the action. + /// @param[in] data_size The length of the additional data. + virtual void DispatchAccessibilityAction(uint16_t target, + FlutterSemanticsAction action, + uint8_t* data, + size_t data_size) = 0; + }; + //------------------------------------------------------------------------------ + /// @brief Creates a new instance of a accessibility bridge. + /// + /// @param[in] user_data A custom pointer to the data of your + /// choice. This pointer can be retrieve later + /// through GetUserData(). + AccessibilityBridge(std::unique_ptr delegate, + void* user_data); + ~AccessibilityBridge(); + + //------------------------------------------------------------------------------ + /// @brief Adds a semantics node update to the pending semantics update. + /// Calling this method alone will NOT update the semantics tree. + /// To flush the pending updates, call the CommitUpdates(). + /// + /// @param[in] node A pointer to the semantics node update. + void AddFlutterSemanticsNodeUpdate(const FlutterSemanticsNode* node); + + //------------------------------------------------------------------------------ + /// @brief Adds a custom semantics action update to the pending semantics + /// update. Calling this method alone will NOT update the + /// semantics tree. To flush the pending updates, call the + /// CommitUpdates(). + /// + /// @param[in] action A pointer to the custom semantics action + /// update. + void AddFlutterSemanticsCustomActionUpdate( + const FlutterSemanticsCustomAction* action); + + //------------------------------------------------------------------------------ + /// @brief Flushes the pending updates and applies them to this + /// accessibility bridge. + void CommitUpdates(); + + //------------------------------------------------------------------------------ + /// @brief Get the underlying AXTree. + AXTree* GetAXTree(); + + //------------------------------------------------------------------------------ + /// @brief The event generator of this accessibility bridge. It contains + /// the pending accessibility events generated as a result of a + /// semantics update. + AXEventGenerator* GetEventGenerator(); + + //------------------------------------------------------------------------------ + /// @brief Get the user data. + void* GetUserData(); + + //------------------------------------------------------------------------------ + /// @brief Get the accessibility bridge delegate. + AccessibilityBridgeDelegate* GetDelegate(); + + //------------------------------------------------------------------------------ + /// @brief Get the flutter accessibility node with the given id from this + /// accessibility bridge. + /// + /// @param[in] id The id of the flutter accessibility node you want + /// to retrieve. + FlutterAccessibility* GetFlutterAccessibilityFromID(int32_t id) const; + + //------------------------------------------------------------------------------ + /// @brief Update the currently focused flutter accessibility node. + /// + /// @param[in] id The id of the currently focused flutter + /// accessibility node. + void SetFocusedNode(int32_t node_id); + + //------------------------------------------------------------------------------ + /// @brief Get the last focused node. + int32_t GetLastFocusedNode(); + + // AXTreeObserver implementation. + void OnNodeWillBeDeleted(AXTree* tree, AXNode* node) override; + void OnSubtreeWillBeDeleted(AXTree* tree, AXNode* node) override; + void OnNodeCreated(AXTree* tree, AXNode* node) override; + void OnNodeDeleted(AXTree* tree, int32_t node_id) override; + void OnNodeReparented(AXTree* tree, AXNode* node) override; + void OnRoleChanged(AXTree* tree, + AXNode* node, + ax::mojom::Role old_role, + ax::mojom::Role new_role) override; + void OnAtomicUpdateFinished( + AXTree* tree, + bool root_changed, + const std::vector& changes) override; + + std::unique_ptr delegate_; + private: + // See FlutterSemanticsNode in embedder.h + typedef struct { + int32_t id; + FlutterSemanticsFlag flags; + FlutterSemanticsAction actions; + int32_t text_selection_base; + int32_t text_selection_extent; + int32_t scroll_child_count; + int32_t scroll_index; + double scroll_position; + double scroll_extent_max; + double scroll_extent_min; + double elevation; + double thickness; + std::string label; + std::string hint; + std::string value; + std::string increased_value; + std::string decreased_value; + FlutterTextDirection text_direction; + FlutterRect rect; + FlutterTransformation transform; + std::vector children_in_traversal_order; + std::vector custom_accessibility_actions; + } SemanticsNode; + + // See FlutterSemanticsCustomAction in embedder.h + typedef struct { + int32_t id; + FlutterSemanticsAction override_action; + std::string label; + std::string hint; + } SemanticsCustomAction; + + std::unordered_map id_wrapper_map_; + std::unique_ptr tree_; + AXEventGenerator event_generator_; + std::unordered_map _pending_semantics_node_updates; + std::unordered_map + _pending_semantics_custom_action_updates; + int32_t last_focused_node_ = AXNode::kInvalidAXID; + void* user_data_; + + void InitAXTree(const AXTreeUpdate& initial_state); + void GetSubTreeList(SemanticsNode target, std::vector& result); + void ConvertFluterUpdate(const SemanticsNode& node, + AXTreeUpdate& tree_update); + void SetRoleFromFlutterUpdate(AXNodeData& node_data, + const SemanticsNode& node); + void SetStateFromFlutterUpdate(AXNodeData& node_data, + const SemanticsNode& node); + void SetActionsFromFlutterUpdate(AXNodeData& node_data, + const SemanticsNode& node); + void SetBooleanAttributesFromFlutterUpdate(AXNodeData& node_data, + const SemanticsNode& node); + void SetIntAttributesFromFlutterUpdate(AXNodeData& node_data, + const SemanticsNode& node); + void SetIntListAttributesFromFlutterUpdate(AXNodeData& node_data, + const SemanticsNode& node); + void SetStringListAttributesFromFlutterUpdate(AXNodeData& node_data, + const SemanticsNode& node); + void SetNameFromFlutterUpdate(AXNodeData& node_data, + const SemanticsNode& node); + void SetValueFromFlutterUpdate(AXNodeData& node_data, + const SemanticsNode& node); + void SetTreeData(const SemanticsNode& node, AXTreeUpdate& tree_update); + SemanticsNode FromFlutterSemanticsNode( + const FlutterSemanticsNode* flutter_node); + SemanticsCustomAction FromFlutterSemanticsCustomAction( + const FlutterSemanticsCustomAction* flutter_custom_action); + BASE_DISALLOW_COPY_AND_ASSIGN(AccessibilityBridge); +}; + +} // namespace ui + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_ACCESSIBILITY_BRIDGE_H_ diff --git a/shell/platform/common/cpp/accessibility_bridge_unittests.cc b/shell/platform/common/cpp/accessibility_bridge_unittests.cc new file mode 100644 index 0000000000000..84928b053ab33 --- /dev/null +++ b/shell/platform/common/cpp/accessibility_bridge_unittests.cc @@ -0,0 +1,193 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "accessibility_bridge.h" + +#include "gtest/gtest.h" + +#include "test_accessibility_bridge.h" + +namespace ui { +namespace testing { + +TEST(AccessibilityBridgeTest, basicTest) { + AccessibilityBridge bridge(std::make_unique(), nullptr); + FlutterSemanticsNode root; + root.id = 0; + root.label = "root"; + root.hint = ""; + root.value = ""; + root.increased_value = ""; + root.decreased_value = ""; + root.child_count = 2; + int32_t children[] = {1, 2}; + root.children_in_traversal_order = children; + root.custom_accessibility_actions_count = 0; + bridge.AddFlutterSemanticsNodeUpdate(&root); + + FlutterSemanticsNode child1; + child1.id = 1; + child1.label = "child 1"; + child1.hint = ""; + child1.value = ""; + child1.increased_value = ""; + child1.decreased_value = ""; + child1.child_count = 0; + child1.custom_accessibility_actions_count = 0; + bridge.AddFlutterSemanticsNodeUpdate(&child1); + + FlutterSemanticsNode child2; + child2.id = 2; + child2.label = "child 2"; + child2.hint = ""; + child2.value = ""; + child2.increased_value = ""; + child2.decreased_value = ""; + child2.child_count = 0; + child2.custom_accessibility_actions_count = 0; + bridge.AddFlutterSemanticsNodeUpdate(&child2); + + bridge.CommitUpdates(); + + FlutterAccessibility* root_node = bridge.GetFlutterAccessibilityFromID(0); + FlutterAccessibility* child1_node = bridge.GetFlutterAccessibilityFromID(1); + FlutterAccessibility* child2_node = bridge.GetFlutterAccessibilityFromID(2); + ASSERT_EQ(root_node->GetChildCount(), 2); + ASSERT_EQ(root_node->GetData().child_ids[0], 1); + ASSERT_EQ(root_node->GetData().child_ids[1], 2); + ASSERT_EQ(root_node->GetName(), "root"); + + ASSERT_EQ(child1_node->GetChildCount(), 0); + ASSERT_EQ(child1_node->GetName(), "child 1"); + + ASSERT_EQ(child2_node->GetChildCount(), 0); + ASSERT_EQ(child2_node->GetName(), "child 2"); +} + +TEST(AccessibilityBridgeTest, canFireChildrenChangedCorrectly) { + AccessibilityBridge bridge(std::make_unique(), nullptr); + TestAccessibilityBridgeDelegate* delegate = (TestAccessibilityBridgeDelegate*)bridge.GetDelegate(); + FlutterSemanticsNode root; + root.id = 0; + root.flags = static_cast(0); + root.actions = static_cast(0); + root.text_selection_base = -1; + root.text_selection_extent = -1; + root.label = "root"; + root.hint = ""; + root.value = ""; + root.increased_value = ""; + root.decreased_value = ""; + root.child_count = 1; + int32_t children[] = {1}; + root.children_in_traversal_order = children; + root.custom_accessibility_actions_count = 0; + bridge.AddFlutterSemanticsNodeUpdate(&root); + + FlutterSemanticsNode child1; + child1.id = 1; + child1.flags = static_cast(0); + child1.actions = static_cast(0); + child1.text_selection_base = -1; + child1.text_selection_extent = -1; + child1.label = "child 1"; + child1.hint = ""; + child1.value = ""; + child1.increased_value = ""; + child1.decreased_value = ""; + child1.child_count = 0; + child1.custom_accessibility_actions_count = 0; + bridge.AddFlutterSemanticsNodeUpdate(&child1); + + bridge.CommitUpdates(); + + FlutterAccessibility* root_node = bridge.GetFlutterAccessibilityFromID(0); + FlutterAccessibility* child1_node = bridge.GetFlutterAccessibilityFromID(1); + ASSERT_EQ(root_node->GetChildCount(), 1); + ASSERT_EQ(root_node->GetData().child_ids[0], 1); + ASSERT_EQ(root_node->GetName(), "root"); + + ASSERT_EQ(child1_node->GetChildCount(), 0); + ASSERT_EQ(child1_node->GetName(), "child 1"); + delegate->accessibilitiy_events.clear(); + + // Add a child to root. + root.child_count = 2; + int32_t new_children[] = {1, 2}; + root.children_in_traversal_order = new_children; + bridge.AddFlutterSemanticsNodeUpdate(&root); + + FlutterSemanticsNode child2; + child2.id = 2; + child2.flags = static_cast(0); + child2.actions = static_cast(0); + child2.text_selection_base = -1; + child2.text_selection_extent = -1; + child2.label = "child 2"; + child2.hint = ""; + child2.value = ""; + child2.increased_value = ""; + child2.decreased_value = ""; + child2.child_count = 0; + child2.custom_accessibility_actions_count = 0; + bridge.AddFlutterSemanticsNodeUpdate(&child2); + + bridge.CommitUpdates(); + + root_node = bridge.GetFlutterAccessibilityFromID(0); + + ASSERT_EQ(root_node->GetChildCount(), 2); + ASSERT_EQ(root_node->GetData().child_ids[0], 1); + ASSERT_EQ(root_node->GetData().child_ids[1], 2); + ASSERT_EQ(delegate->accessibilitiy_events.size(), size_t{2}); + ASSERT_EQ(delegate->accessibilitiy_events[0].event_params.event, + AXEventGenerator::Event::CHILDREN_CHANGED); + ASSERT_EQ(delegate->accessibilitiy_events[1].event_params.event, + AXEventGenerator::Event::SUBTREE_CREATED); +} + +TEST(AccessibilityBridgeTest, canHandleSelectionChangeCorrect) { + AccessibilityBridge bridge(std::make_unique(), nullptr); + FlutterSemanticsNode root; + root.id = 0; + root.flags = FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField; + root.actions = static_cast(0); + root.text_selection_base = -1; + root.text_selection_extent = -1; + root.label = "root"; + root.hint = ""; + root.value = ""; + root.increased_value = ""; + root.decreased_value = ""; + root.child_count = 0; + root.custom_accessibility_actions_count = 0; + bridge.AddFlutterSemanticsNodeUpdate(&root); + + bridge.CommitUpdates(); + + TestAccessibilityBridgeDelegate* delegate = (TestAccessibilityBridgeDelegate*)bridge.GetDelegate(); + AXTree* tree = bridge.GetAXTree(); + ASSERT_EQ(tree->data().sel_anchor_object_id, AXNode::kInvalidAXID); + delegate->accessibilitiy_events.clear(); + + // Update the selection. + root.text_selection_base = 0; + root.text_selection_extent = 5; + bridge.AddFlutterSemanticsNodeUpdate(&root); + + bridge.CommitUpdates(); + + ASSERT_EQ(tree->data().sel_anchor_object_id, 0); + ASSERT_EQ(tree->data().sel_anchor_offset, 0); + ASSERT_EQ(tree->data().sel_focus_object_id, 0); + ASSERT_EQ(tree->data().sel_focus_offset, 5); + ASSERT_EQ(delegate->accessibilitiy_events.size(), size_t{2}); + ASSERT_EQ(delegate->accessibilitiy_events[0].event_params.event, + AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED); + ASSERT_EQ(delegate->accessibilitiy_events[1].event_params.event, + AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED); +} + +} // namespace testing +} // namespace ui diff --git a/shell/platform/common/cpp/flutter_accessibility.cc b/shell/platform/common/cpp/flutter_accessibility.cc new file mode 100644 index 0000000000000..05ac854a5a86f --- /dev/null +++ b/shell/platform/common/cpp/flutter_accessibility.cc @@ -0,0 +1,110 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter_accessibility.h" + +#include "flutter/third_party/accessibility/ax/ax_action_data.h" +#include "flutter/third_party/accessibility/gfx/geometry/rect_conversions.h" + +#include "accessibility_bridge.h" + +namespace ui { + +FlutterAccessibility::FlutterAccessibility() = default; + +FlutterAccessibility::~FlutterAccessibility() = default; + +void FlutterAccessibility::Init(AccessibilityBridge* bridge, AXNode* node) { + bridge_ = bridge; + ax_node_ = node; +} + +AccessibilityBridge* FlutterAccessibility::GetBridge() const { + return bridge_; +} + +AXNode* FlutterAccessibility::GetAXNode() const { + return ax_node_; +} + +bool FlutterAccessibility::AccessibilityPerformAction( + const AXActionData& data) { + int32_t target = GetAXNode()->id(); + switch (data.action) { + case ax::mojom::Action::kDoDefault: + bridge_->GetDelegate()->DispatchAccessibilityAction( + target, FlutterSemanticsAction::kFlutterSemanticsActionTap, nullptr, + 0); + return true; + case ax::mojom::Action::kFocus: + bridge_->SetFocusedNode(target); + bridge_->GetDelegate()->DispatchAccessibilityAction( + target, + FlutterSemanticsAction:: + kFlutterSemanticsActionDidGainAccessibilityFocus, + nullptr, 0); + return true; + case ax::mojom::Action::kScrollToMakeVisible: + bridge_->GetDelegate()->DispatchAccessibilityAction( + target, FlutterSemanticsAction::kFlutterSemanticsActionShowOnScreen, + nullptr, 0); + return true; + // TODO(chunhtai): support more actions. + default: + return false; + } + return false; +} + +const AXNodeData& FlutterAccessibility::GetData() const { + return GetAXNode()->data(); +} + +gfx::NativeViewAccessible FlutterAccessibility::GetParent() { + if (!GetAXNode()->parent()) { + return nullptr; + } + return GetBridge() + ->GetFlutterAccessibilityFromID(GetAXNode()->parent()->id()) + ->GetNativeViewAccessible(); +} + +gfx::NativeViewAccessible FlutterAccessibility::GetFocus() { + int32_t focused_node = GetBridge()->GetLastFocusedNode(); + if (focused_node == AXNode::kInvalidAXID) { + return nullptr; + } + FlutterAccessibility* focus = + GetBridge()->GetFlutterAccessibilityFromID(focused_node); + if (!focus) + return nullptr; + return GetBridge() + ->GetFlutterAccessibilityFromID(focused_node) + ->GetNativeViewAccessible(); +} + +int FlutterAccessibility::GetChildCount() const { + return static_cast(GetAXNode()->GetUnignoredChildCount()); +} + +gfx::NativeViewAccessible FlutterAccessibility::ChildAtIndex(int index) { + int32_t child = GetAXNode()->GetUnignoredChildAtIndex(index)->id(); + return GetBridge() + ->GetFlutterAccessibilityFromID(child) + ->GetNativeViewAccessible(); +} + +gfx::Rect FlutterAccessibility::GetBoundsRect(const AXCoordinateSystem coordinate_system, + const AXClippingBehavior clipping_behavior, + AXOffscreenResult* offscreen_result) const { + // TODO(chunhtai): consider screen dpr. + const bool clip_bounds = clipping_behavior == AXClippingBehavior::kClipped; + bool offscreen = false; + return gfx::ToEnclosingRect( + GetBridge()->GetAXTree()->RelativeToTreeBounds( + GetAXNode(), GetData().relative_bounds.bounds, &offscreen, clip_bounds) + ); +} + +} // namespace ui diff --git a/shell/platform/common/cpp/flutter_accessibility.h b/shell/platform/common/cpp/flutter_accessibility.h new file mode 100644 index 0000000000000..98abc4565f6e6 --- /dev/null +++ b/shell/platform/common/cpp/flutter_accessibility.h @@ -0,0 +1,79 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_FLUTTER_ACCESSIBILITY_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_FLUTTER_ACCESSIBILITY_H_ + +#include "flutter/shell/platform/embedder/embedder.h" + +#include "flutter/third_party/accessibility/ax/ax_event_generator.h" +#include "flutter/third_party/accessibility/ax/platform/ax_platform_node_delegate_base.h" + +namespace ui { + +class AccessibilityBridge; + +//------------------------------------------------------------------------------ +/// The accessibility node to be used in accessibility bridge. This class is +/// responsible for providing native accessibility object with appropriate +/// information, such as accessibility label/value/bounds. +/// +/// While most methods have default implementations and are ready to be used +/// as-is, the subclasses must override the GetNativeViewAccessible to return +/// native accessibility objects. To do that, subclasses should create and +/// maintain AXPlatformNode[s] which delegate their accessibility attributes to +/// this class. +/// +/// For desktop platforms, subclasses also need to override the GetBoundsRect +/// to apply window-to-screen transform. +/// +/// Lastly, each platform needs to implement the FlutterAccessibility::Create +/// static method to inject its sublcass into accessibility bridge. +class FlutterAccessibility : public AXPlatformNodeDelegateBase { + public: + //------------------------------------------------------------------------------ + /// @brief Creates a platform specific FlutterAccessibility. Ownership + /// passes to the caller. This method will be called by + /// accessibility bridge when it creates accessibility node. Each + /// platform needs to implement this method in order to inject its + /// subclass into the accessibility bridge. + static FlutterAccessibility* Create(); + + FlutterAccessibility(); + ~FlutterAccessibility() override; + + //------------------------------------------------------------------------------ + /// @brief Gets the accessibility bridge to which this accessibility node + /// belongs. + AccessibilityBridge* GetBridge() const; + + //------------------------------------------------------------------------------ + /// @brief Gets the underlying ax node for this accessibility node. + AXNode* GetAXNode() const; + + // AXPlatformNodeDelegateBase override; + const AXNodeData& GetData() const override; + bool AccessibilityPerformAction(const AXActionData& data) override; + gfx::NativeViewAccessible GetParent() override; + gfx::NativeViewAccessible GetFocus() override; + int GetChildCount() const override; + gfx::NativeViewAccessible ChildAtIndex(int index) override; + gfx::Rect GetBoundsRect(const AXCoordinateSystem coordinate_system, + const AXClippingBehavior clipping_behavior, + AXOffscreenResult* offscreen_result) const override; + //------------------------------------------------------------------------------ + /// @brief Called only once, immediately after construction. The + /// constructor doesn't take any arguments because in the Windows + /// subclass we use a special function to construct a COM object. + /// Subclasses must call super. + virtual void Init(AccessibilityBridge* bridge, AXNode* node); + + private: + AXNode* ax_node_; + AccessibilityBridge* bridge_; +}; + +} // namespace ui + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_FLUTTER_ACCESSIBILITY_H_ diff --git a/shell/platform/common/cpp/flutter_accessibility_unittests.cc b/shell/platform/common/cpp/flutter_accessibility_unittests.cc new file mode 100644 index 0000000000000..b699d38d224b7 --- /dev/null +++ b/shell/platform/common/cpp/flutter_accessibility_unittests.cc @@ -0,0 +1,47 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter_accessibility.h" + +#include "flutter/third_party/accessibility/ax/ax_action_data.h" +#include "gtest/gtest.h" + +#include "test_accessibility_bridge.h" + +namespace ui { +namespace testing { + +TEST(FlutterAccessibilityTest, canPerfomActions) { + // Set up a flutter accessibility node. + FlutterAccessibility* accessibility = FlutterAccessibility::Create(); + AccessibilityBridge bridge(std::make_unique(), nullptr); + TestAccessibilityBridgeDelegate* delegate = (TestAccessibilityBridgeDelegate*)bridge.GetDelegate(); + + AXNode ax_node(bridge.GetAXTree(), 0, -1, -1); + accessibility->Init(&bridge, &ax_node); + + // Performs an AXAction. + AXActionData action_data; + action_data.action = ax::mojom::Action::kDoDefault; + accessibility->AccessibilityPerformAction(action_data); + ASSERT_EQ(delegate->performed_actions.size(), size_t{1}); + ASSERT_EQ(delegate->performed_actions[0], + FlutterSemanticsAction::kFlutterSemanticsActionTap); + + action_data.action = ax::mojom::Action::kFocus; + accessibility->AccessibilityPerformAction(action_data); + ASSERT_EQ(delegate->performed_actions.size(), size_t{2}); + ASSERT_EQ( + delegate->performed_actions[1], + FlutterSemanticsAction::kFlutterSemanticsActionDidGainAccessibilityFocus); + + action_data.action = ax::mojom::Action::kScrollToMakeVisible; + accessibility->AccessibilityPerformAction(action_data); + ASSERT_EQ(delegate->performed_actions.size(), size_t{3}); + ASSERT_EQ(delegate->performed_actions[2], + FlutterSemanticsAction::kFlutterSemanticsActionShowOnScreen); +} + +} // namespace testing +} // namespace ui diff --git a/shell/platform/common/cpp/test_accessibility_bridge.cc b/shell/platform/common/cpp/test_accessibility_bridge.cc new file mode 100644 index 0000000000000..61c024bf835ea --- /dev/null +++ b/shell/platform/common/cpp/test_accessibility_bridge.cc @@ -0,0 +1,26 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "test_accessibility_bridge.h" + +namespace ui { + +FlutterAccessibility* FlutterAccessibility::Create() { + return new FlutterAccessibility(); +}; + +void TestAccessibilityBridgeDelegate::OnAccessibilityEvent( + AXEventGenerator::TargetedEvent targeted_event, AccessibilityBridge* bridge) { + accessibilitiy_events.push_back(targeted_event); +} + +void TestAccessibilityBridgeDelegate::DispatchAccessibilityAction( + uint16_t target, + FlutterSemanticsAction action, + uint8_t* data, + size_t data_size) { + performed_actions.push_back(action); +} + +} // namespace ui diff --git a/shell/platform/common/cpp/test_accessibility_bridge.h b/shell/platform/common/cpp/test_accessibility_bridge.h new file mode 100644 index 0000000000000..44e73df71cc02 --- /dev/null +++ b/shell/platform/common/cpp/test_accessibility_bridge.h @@ -0,0 +1,29 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_TEST_ACCESSIBILITY_BRIDGE_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_TEST_ACCESSIBILITY_BRIDGE_H_ + +#include "accessibility_bridge.h" + +namespace ui { + +class TestAccessibilityBridgeDelegate : public AccessibilityBridge::AccessibilityBridgeDelegate { + public: + TestAccessibilityBridgeDelegate() = default; + + void OnAccessibilityEvent( + AXEventGenerator::TargetedEvent targeted_event, AccessibilityBridge* bridge) override; + void DispatchAccessibilityAction(uint16_t target, + FlutterSemanticsAction action, + uint8_t* data, + size_t data_size) override; + + std::vector accessibilitiy_events; + std::vector performed_actions; +}; + +} // namespace ui + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_TEST_ACCESSIBILITY_BRIDGE_H_ From 5e2d29bc03467b4568dd973845d73dc0ac0ae5d2 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Thu, 7 Jan 2021 10:32:18 -0800 Subject: [PATCH 02/19] format --- .../common/cpp/accessibility_bridge.cc | 18 ++++++----- .../common/cpp/accessibility_bridge.h | 30 ++++++++++++------- .../cpp/accessibility_bridge_unittests.cc | 15 ++++++---- .../common/cpp/flutter_accessibility.cc | 13 ++++---- .../common/cpp/flutter_accessibility.h | 4 +-- .../cpp/flutter_accessibility_unittests.cc | 6 ++-- .../common/cpp/test_accessibility_bridge.cc | 11 +++---- .../common/cpp/test_accessibility_bridge.h | 13 ++++---- 8 files changed, 65 insertions(+), 45 deletions(-) diff --git a/shell/platform/common/cpp/accessibility_bridge.cc b/shell/platform/common/cpp/accessibility_bridge.cc index e92f52a884380..6117c71e59fd7 100644 --- a/shell/platform/common/cpp/accessibility_bridge.cc +++ b/shell/platform/common/cpp/accessibility_bridge.cc @@ -19,8 +19,9 @@ constexpr int khasScrollingAction = FlutterSemanticsAction::kFlutterSemanticsActionScrollDown; // AccessibilityBridge -AccessibilityBridge::AccessibilityBridge(std::unique_ptr delegate, - void* user_data) +AccessibilityBridge::AccessibilityBridge( + std::unique_ptr delegate, + void* user_data) : delegate_(std::move(delegate)), tree_(std::make_unique()), event_generator_(tree_.get()) { @@ -94,7 +95,8 @@ void* AccessibilityBridge::GetUserData() { return user_data_; } -AccessibilityBridge::AccessibilityBridgeDelegate* AccessibilityBridge::GetDelegate() { +AccessibilityBridge::AccessibilityBridgeDelegate* +AccessibilityBridge::GetDelegate() { return delegate_.get(); } @@ -210,12 +212,13 @@ void AccessibilityBridge::ConvertFluterUpdate(const SemanticsNode& node, SetNameFromFlutterUpdate(node_data, node); SetValueFromFlutterUpdate(node_data, node); node_data.relative_bounds.bounds.SetRect(node.rect.left, node.rect.top, - node.rect.right - node.rect.left, node.rect.bottom - node.rect.top); + node.rect.right - node.rect.left, + node.rect.bottom - node.rect.top); node_data.relative_bounds.transform = std::make_unique( node.transform.scaleX, node.transform.skewX, node.transform.transX, 0, node.transform.skewY, node.transform.scaleY, node.transform.transY, 0, - node.transform.pers0, node.transform.pers1, node.transform.pers2, 0, - 0, 0, 0, 0); + node.transform.pers0, node.transform.pers1, node.transform.pers2, 0, 0, 0, + 0, 0); for (auto child : node.children_in_traversal_order) { node_data.child_ids.push_back(child); } @@ -355,7 +358,8 @@ void AccessibilityBridge::SetIntAttributesFromFlutterUpdate( AXNodeData& node_data, const SemanticsNode& node) { FlutterSemanticsFlag flags = node.flags; - node_data.AddIntAttribute(ax::mojom::IntAttribute::kTextDirection, node.text_direction); + node_data.AddIntAttribute(ax::mojom::IntAttribute::kTextDirection, + node.text_direction); int sel_start = node.text_selection_base; int sel_end = node.text_selection_extent; diff --git a/shell/platform/common/cpp/accessibility_bridge.h b/shell/platform/common/cpp/accessibility_bridge.h index c4d7a755122e9..ea05b672d5fca 100644 --- a/shell/platform/common/cpp/accessibility_bridge.h +++ b/shell/platform/common/cpp/accessibility_bridge.h @@ -37,29 +37,36 @@ class AccessibilityBridge : public AXTreeObserver { /// (e.g. NSAccessibilityPostNotification in MacOS). /// /// The accessibility actions are generated by the native accessibility system - /// when users interacted with the assistive technologies. Those actions needed - /// to be sent to the Flutter framework. + /// when users interacted with the assistive technologies. Those actions + /// needed to be sent to the Flutter framework. class AccessibilityBridgeDelegate { public: virtual ~AccessibilityBridgeDelegate() = default; //------------------------------------------------------------------------------ - /// @brief Handle accessibility events generated due to accessibility tree + /// @brief Handle accessibility events generated due to accessibility + /// tree /// changes. /// - /// @param[in] targeted_event The object that contains both the generated + /// @param[in] targeted_event The object that contains both the + /// generated /// event and the event target. - /// @param[in] bridge The pointer to the accessibility bridge that - /// can be used for querying the accessibility - /// information at the time the event is fired. + /// @param[in] bridge The pointer to the accessibility bridge + /// that + /// can be used for querying the + /// accessibility information at the time + /// the event is fired. virtual void OnAccessibilityEvent( - AXEventGenerator::TargetedEvent targeted_event, AccessibilityBridge* bridge) = 0; + AXEventGenerator::TargetedEvent targeted_event, + AccessibilityBridge* bridge) = 0; //------------------------------------------------------------------------------ /// @brief Dispatch accessibility action back to the Flutter framework /// - /// @param[in] target The semantics node id of the action target. + /// @param[in] target The semantics node id of the action + /// target. /// @param[in] action The generated flutter semantics action. - /// @param[in] data Additional data associated with the action. + /// @param[in] data Additional data associated with the + /// action. /// @param[in] data_size The length of the additional data. virtual void DispatchAccessibilityAction(uint16_t target, FlutterSemanticsAction action, @@ -104,7 +111,7 @@ class AccessibilityBridge : public AXTreeObserver { /// @brief Get the underlying AXTree. AXTree* GetAXTree(); - //------------------------------------------------------------------------------ + //------------------------------------------------------------------------------ /// @brief The event generator of this accessibility bridge. It contains /// the pending accessibility events generated as a result of a /// semantics update. @@ -153,6 +160,7 @@ class AccessibilityBridge : public AXTreeObserver { const std::vector& changes) override; std::unique_ptr delegate_; + private: // See FlutterSemanticsNode in embedder.h typedef struct { diff --git a/shell/platform/common/cpp/accessibility_bridge_unittests.cc b/shell/platform/common/cpp/accessibility_bridge_unittests.cc index 84928b053ab33..c06c5f12fab12 100644 --- a/shell/platform/common/cpp/accessibility_bridge_unittests.cc +++ b/shell/platform/common/cpp/accessibility_bridge_unittests.cc @@ -12,7 +12,8 @@ namespace ui { namespace testing { TEST(AccessibilityBridgeTest, basicTest) { - AccessibilityBridge bridge(std::make_unique(), nullptr); + AccessibilityBridge bridge( + std::make_unique(), nullptr); FlutterSemanticsNode root; root.id = 0; root.label = "root"; @@ -66,8 +67,10 @@ TEST(AccessibilityBridgeTest, basicTest) { } TEST(AccessibilityBridgeTest, canFireChildrenChangedCorrectly) { - AccessibilityBridge bridge(std::make_unique(), nullptr); - TestAccessibilityBridgeDelegate* delegate = (TestAccessibilityBridgeDelegate*)bridge.GetDelegate(); + AccessibilityBridge bridge( + std::make_unique(), nullptr); + TestAccessibilityBridgeDelegate* delegate = + (TestAccessibilityBridgeDelegate*)bridge.GetDelegate(); FlutterSemanticsNode root; root.id = 0; root.flags = static_cast(0); @@ -148,7 +151,8 @@ TEST(AccessibilityBridgeTest, canFireChildrenChangedCorrectly) { } TEST(AccessibilityBridgeTest, canHandleSelectionChangeCorrect) { - AccessibilityBridge bridge(std::make_unique(), nullptr); + AccessibilityBridge bridge( + std::make_unique(), nullptr); FlutterSemanticsNode root; root.id = 0; root.flags = FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField; @@ -166,7 +170,8 @@ TEST(AccessibilityBridgeTest, canHandleSelectionChangeCorrect) { bridge.CommitUpdates(); - TestAccessibilityBridgeDelegate* delegate = (TestAccessibilityBridgeDelegate*)bridge.GetDelegate(); + TestAccessibilityBridgeDelegate* delegate = + (TestAccessibilityBridgeDelegate*)bridge.GetDelegate(); AXTree* tree = bridge.GetAXTree(); ASSERT_EQ(tree->data().sel_anchor_object_id, AXNode::kInvalidAXID); delegate->accessibilitiy_events.clear(); diff --git a/shell/platform/common/cpp/flutter_accessibility.cc b/shell/platform/common/cpp/flutter_accessibility.cc index 05ac854a5a86f..34c52ee986e24 100644 --- a/shell/platform/common/cpp/flutter_accessibility.cc +++ b/shell/platform/common/cpp/flutter_accessibility.cc @@ -95,16 +95,15 @@ gfx::NativeViewAccessible FlutterAccessibility::ChildAtIndex(int index) { ->GetNativeViewAccessible(); } -gfx::Rect FlutterAccessibility::GetBoundsRect(const AXCoordinateSystem coordinate_system, - const AXClippingBehavior clipping_behavior, - AXOffscreenResult* offscreen_result) const { +gfx::Rect FlutterAccessibility::GetBoundsRect( + const AXCoordinateSystem coordinate_system, + const AXClippingBehavior clipping_behavior, + AXOffscreenResult* offscreen_result) const { // TODO(chunhtai): consider screen dpr. const bool clip_bounds = clipping_behavior == AXClippingBehavior::kClipped; bool offscreen = false; - return gfx::ToEnclosingRect( - GetBridge()->GetAXTree()->RelativeToTreeBounds( - GetAXNode(), GetData().relative_bounds.bounds, &offscreen, clip_bounds) - ); + return gfx::ToEnclosingRect(GetBridge()->GetAXTree()->RelativeToTreeBounds( + GetAXNode(), GetData().relative_bounds.bounds, &offscreen, clip_bounds)); } } // namespace ui diff --git a/shell/platform/common/cpp/flutter_accessibility.h b/shell/platform/common/cpp/flutter_accessibility.h index 98abc4565f6e6..4ef634d16a009 100644 --- a/shell/platform/common/cpp/flutter_accessibility.h +++ b/shell/platform/common/cpp/flutter_accessibility.h @@ -60,8 +60,8 @@ class FlutterAccessibility : public AXPlatformNodeDelegateBase { int GetChildCount() const override; gfx::NativeViewAccessible ChildAtIndex(int index) override; gfx::Rect GetBoundsRect(const AXCoordinateSystem coordinate_system, - const AXClippingBehavior clipping_behavior, - AXOffscreenResult* offscreen_result) const override; + const AXClippingBehavior clipping_behavior, + AXOffscreenResult* offscreen_result) const override; //------------------------------------------------------------------------------ /// @brief Called only once, immediately after construction. The /// constructor doesn't take any arguments because in the Windows diff --git a/shell/platform/common/cpp/flutter_accessibility_unittests.cc b/shell/platform/common/cpp/flutter_accessibility_unittests.cc index b699d38d224b7..61f3bbf17ec3f 100644 --- a/shell/platform/common/cpp/flutter_accessibility_unittests.cc +++ b/shell/platform/common/cpp/flutter_accessibility_unittests.cc @@ -15,8 +15,10 @@ namespace testing { TEST(FlutterAccessibilityTest, canPerfomActions) { // Set up a flutter accessibility node. FlutterAccessibility* accessibility = FlutterAccessibility::Create(); - AccessibilityBridge bridge(std::make_unique(), nullptr); - TestAccessibilityBridgeDelegate* delegate = (TestAccessibilityBridgeDelegate*)bridge.GetDelegate(); + AccessibilityBridge bridge( + std::make_unique(), nullptr); + TestAccessibilityBridgeDelegate* delegate = + (TestAccessibilityBridgeDelegate*)bridge.GetDelegate(); AXNode ax_node(bridge.GetAXTree(), 0, -1, -1); accessibility->Init(&bridge, &ax_node); diff --git a/shell/platform/common/cpp/test_accessibility_bridge.cc b/shell/platform/common/cpp/test_accessibility_bridge.cc index 61c024bf835ea..48085a7eb3e3e 100644 --- a/shell/platform/common/cpp/test_accessibility_bridge.cc +++ b/shell/platform/common/cpp/test_accessibility_bridge.cc @@ -11,15 +11,16 @@ FlutterAccessibility* FlutterAccessibility::Create() { }; void TestAccessibilityBridgeDelegate::OnAccessibilityEvent( - AXEventGenerator::TargetedEvent targeted_event, AccessibilityBridge* bridge) { + AXEventGenerator::TargetedEvent targeted_event, + AccessibilityBridge* bridge) { accessibilitiy_events.push_back(targeted_event); } void TestAccessibilityBridgeDelegate::DispatchAccessibilityAction( - uint16_t target, - FlutterSemanticsAction action, - uint8_t* data, - size_t data_size) { + uint16_t target, + FlutterSemanticsAction action, + uint8_t* data, + size_t data_size) { performed_actions.push_back(action); } diff --git a/shell/platform/common/cpp/test_accessibility_bridge.h b/shell/platform/common/cpp/test_accessibility_bridge.h index 44e73df71cc02..bc687be447531 100644 --- a/shell/platform/common/cpp/test_accessibility_bridge.h +++ b/shell/platform/common/cpp/test_accessibility_bridge.h @@ -9,16 +9,17 @@ namespace ui { -class TestAccessibilityBridgeDelegate : public AccessibilityBridge::AccessibilityBridgeDelegate { +class TestAccessibilityBridgeDelegate + : public AccessibilityBridge::AccessibilityBridgeDelegate { public: TestAccessibilityBridgeDelegate() = default; - void OnAccessibilityEvent( - AXEventGenerator::TargetedEvent targeted_event, AccessibilityBridge* bridge) override; + void OnAccessibilityEvent(AXEventGenerator::TargetedEvent targeted_event, + AccessibilityBridge* bridge) override; void DispatchAccessibilityAction(uint16_t target, - FlutterSemanticsAction action, - uint8_t* data, - size_t data_size) override; + FlutterSemanticsAction action, + uint8_t* data, + size_t data_size) override; std::vector accessibilitiy_events; std::vector performed_actions; From 22b143419997eaa23deae08ad10419a1bb54b17f Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Thu, 7 Jan 2021 12:05:38 -0800 Subject: [PATCH 03/19] add more tests --- .../common/cpp/accessibility_bridge.cc | 4 +- .../cpp/accessibility_bridge_unittests.cc | 63 ++++----- .../common/cpp/flutter_accessibility.cc | 7 +- .../cpp/flutter_accessibility_unittests.cc | 122 +++++++++++++++++- 4 files changed, 156 insertions(+), 40 deletions(-) diff --git a/shell/platform/common/cpp/accessibility_bridge.cc b/shell/platform/common/cpp/accessibility_bridge.cc index 6117c71e59fd7..5011411b5ee3d 100644 --- a/shell/platform/common/cpp/accessibility_bridge.cc +++ b/shell/platform/common/cpp/accessibility_bridge.cc @@ -342,9 +342,9 @@ void AccessibilityBridge::SetBooleanAttributesFromFlutterUpdate( node_data.AddBoolAttribute( ax::mojom::BoolAttribute::kClickable, actions & FlutterSemanticsAction::kFlutterSemanticsActionTap); + // TODO(chunhtai): figure out if there is a node that does not clip overflow. node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kClipsChildren, - actions & khasScrollingAction && - node.children_in_traversal_order.size() != 0); + node.children_in_traversal_order.size() != 0); node_data.AddBoolAttribute( ax::mojom::BoolAttribute::kSelected, flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsSelected); diff --git a/shell/platform/common/cpp/accessibility_bridge_unittests.cc b/shell/platform/common/cpp/accessibility_bridge_unittests.cc index c06c5f12fab12..65650edb6114f 100644 --- a/shell/platform/common/cpp/accessibility_bridge_unittests.cc +++ b/shell/platform/common/cpp/accessibility_bridge_unittests.cc @@ -54,16 +54,16 @@ TEST(AccessibilityBridgeTest, basicTest) { FlutterAccessibility* root_node = bridge.GetFlutterAccessibilityFromID(0); FlutterAccessibility* child1_node = bridge.GetFlutterAccessibilityFromID(1); FlutterAccessibility* child2_node = bridge.GetFlutterAccessibilityFromID(2); - ASSERT_EQ(root_node->GetChildCount(), 2); - ASSERT_EQ(root_node->GetData().child_ids[0], 1); - ASSERT_EQ(root_node->GetData().child_ids[1], 2); - ASSERT_EQ(root_node->GetName(), "root"); + EXPECT_EQ(root_node->GetChildCount(), 2); + EXPECT_EQ(root_node->GetData().child_ids[0], 1); + EXPECT_EQ(root_node->GetData().child_ids[1], 2); + EXPECT_EQ(root_node->GetName(), "root"); - ASSERT_EQ(child1_node->GetChildCount(), 0); - ASSERT_EQ(child1_node->GetName(), "child 1"); + EXPECT_EQ(child1_node->GetChildCount(), 0); + EXPECT_EQ(child1_node->GetName(), "child 1"); - ASSERT_EQ(child2_node->GetChildCount(), 0); - ASSERT_EQ(child2_node->GetName(), "child 2"); + EXPECT_EQ(child2_node->GetChildCount(), 0); + EXPECT_EQ(child2_node->GetName(), "child 2"); } TEST(AccessibilityBridgeTest, canFireChildrenChangedCorrectly) { @@ -107,12 +107,12 @@ TEST(AccessibilityBridgeTest, canFireChildrenChangedCorrectly) { FlutterAccessibility* root_node = bridge.GetFlutterAccessibilityFromID(0); FlutterAccessibility* child1_node = bridge.GetFlutterAccessibilityFromID(1); - ASSERT_EQ(root_node->GetChildCount(), 1); - ASSERT_EQ(root_node->GetData().child_ids[0], 1); - ASSERT_EQ(root_node->GetName(), "root"); + EXPECT_EQ(root_node->GetChildCount(), 1); + EXPECT_EQ(root_node->GetData().child_ids[0], 1); + EXPECT_EQ(root_node->GetName(), "root"); - ASSERT_EQ(child1_node->GetChildCount(), 0); - ASSERT_EQ(child1_node->GetName(), "child 1"); + EXPECT_EQ(child1_node->GetChildCount(), 0); + EXPECT_EQ(child1_node->GetName(), "child 1"); delegate->accessibilitiy_events.clear(); // Add a child to root. @@ -140,17 +140,20 @@ TEST(AccessibilityBridgeTest, canFireChildrenChangedCorrectly) { root_node = bridge.GetFlutterAccessibilityFromID(0); - ASSERT_EQ(root_node->GetChildCount(), 2); - ASSERT_EQ(root_node->GetData().child_ids[0], 1); - ASSERT_EQ(root_node->GetData().child_ids[1], 2); - ASSERT_EQ(delegate->accessibilitiy_events.size(), size_t{2}); - ASSERT_EQ(delegate->accessibilitiy_events[0].event_params.event, - AXEventGenerator::Event::CHILDREN_CHANGED); - ASSERT_EQ(delegate->accessibilitiy_events[1].event_params.event, - AXEventGenerator::Event::SUBTREE_CREATED); + EXPECT_EQ(root_node->GetChildCount(), 2); + EXPECT_EQ(root_node->GetData().child_ids[0], 1); + EXPECT_EQ(root_node->GetData().child_ids[1], 2); + EXPECT_EQ(delegate->accessibilitiy_events.size(), size_t{2}); + std::set actual_event; + actual_event.insert(delegate->accessibilitiy_events[0].event_params.event); + actual_event.insert(delegate->accessibilitiy_events[1].event_params.event); + EXPECT_NE(actual_event.find(AXEventGenerator::Event::CHILDREN_CHANGED), + actual_event.end()); + EXPECT_NE(actual_event.find(AXEventGenerator::Event::SUBTREE_CREATED), + actual_event.end()); } -TEST(AccessibilityBridgeTest, canHandleSelectionChangeCorrect) { +TEST(AccessibilityBridgeTest, canHandleSelectionChangeCorrectly) { AccessibilityBridge bridge( std::make_unique(), nullptr); FlutterSemanticsNode root; @@ -173,7 +176,7 @@ TEST(AccessibilityBridgeTest, canHandleSelectionChangeCorrect) { TestAccessibilityBridgeDelegate* delegate = (TestAccessibilityBridgeDelegate*)bridge.GetDelegate(); AXTree* tree = bridge.GetAXTree(); - ASSERT_EQ(tree->data().sel_anchor_object_id, AXNode::kInvalidAXID); + EXPECT_EQ(tree->data().sel_anchor_object_id, AXNode::kInvalidAXID); delegate->accessibilitiy_events.clear(); // Update the selection. @@ -183,14 +186,14 @@ TEST(AccessibilityBridgeTest, canHandleSelectionChangeCorrect) { bridge.CommitUpdates(); - ASSERT_EQ(tree->data().sel_anchor_object_id, 0); - ASSERT_EQ(tree->data().sel_anchor_offset, 0); - ASSERT_EQ(tree->data().sel_focus_object_id, 0); - ASSERT_EQ(tree->data().sel_focus_offset, 5); - ASSERT_EQ(delegate->accessibilitiy_events.size(), size_t{2}); - ASSERT_EQ(delegate->accessibilitiy_events[0].event_params.event, + EXPECT_EQ(tree->data().sel_anchor_object_id, 0); + EXPECT_EQ(tree->data().sel_anchor_offset, 0); + EXPECT_EQ(tree->data().sel_focus_object_id, 0); + EXPECT_EQ(tree->data().sel_focus_offset, 5); + EXPECT_EQ(delegate->accessibilitiy_events.size(), size_t{2}); + EXPECT_EQ(delegate->accessibilitiy_events[0].event_params.event, AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED); - ASSERT_EQ(delegate->accessibilitiy_events[1].event_params.event, + EXPECT_EQ(delegate->accessibilitiy_events[1].event_params.event, AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED); } diff --git a/shell/platform/common/cpp/flutter_accessibility.cc b/shell/platform/common/cpp/flutter_accessibility.cc index 34c52ee986e24..777bc78e4d743 100644 --- a/shell/platform/common/cpp/flutter_accessibility.cc +++ b/shell/platform/common/cpp/flutter_accessibility.cc @@ -102,8 +102,11 @@ gfx::Rect FlutterAccessibility::GetBoundsRect( // TODO(chunhtai): consider screen dpr. const bool clip_bounds = clipping_behavior == AXClippingBehavior::kClipped; bool offscreen = false; - return gfx::ToEnclosingRect(GetBridge()->GetAXTree()->RelativeToTreeBounds( - GetAXNode(), GetData().relative_bounds.bounds, &offscreen, clip_bounds)); + gfx::RectF bounds = GetBridge()->GetAXTree()->RelativeToTreeBounds( + GetAXNode(), gfx::RectF(), &offscreen, clip_bounds); + *offscreen_result = + offscreen ? AXOffscreenResult::kOffscreen : AXOffscreenResult::kOnscreen; + return gfx::ToEnclosingRect(bounds); } } // namespace ui diff --git a/shell/platform/common/cpp/flutter_accessibility_unittests.cc b/shell/platform/common/cpp/flutter_accessibility_unittests.cc index 61f3bbf17ec3f..f3f0765b69c25 100644 --- a/shell/platform/common/cpp/flutter_accessibility_unittests.cc +++ b/shell/platform/common/cpp/flutter_accessibility_unittests.cc @@ -27,23 +27,133 @@ TEST(FlutterAccessibilityTest, canPerfomActions) { AXActionData action_data; action_data.action = ax::mojom::Action::kDoDefault; accessibility->AccessibilityPerformAction(action_data); - ASSERT_EQ(delegate->performed_actions.size(), size_t{1}); - ASSERT_EQ(delegate->performed_actions[0], + EXPECT_EQ(delegate->performed_actions.size(), size_t{1}); + EXPECT_EQ(delegate->performed_actions[0], FlutterSemanticsAction::kFlutterSemanticsActionTap); action_data.action = ax::mojom::Action::kFocus; accessibility->AccessibilityPerformAction(action_data); - ASSERT_EQ(delegate->performed_actions.size(), size_t{2}); - ASSERT_EQ( + EXPECT_EQ(delegate->performed_actions.size(), size_t{2}); + EXPECT_EQ( delegate->performed_actions[1], FlutterSemanticsAction::kFlutterSemanticsActionDidGainAccessibilityFocus); action_data.action = ax::mojom::Action::kScrollToMakeVisible; accessibility->AccessibilityPerformAction(action_data); - ASSERT_EQ(delegate->performed_actions.size(), size_t{3}); - ASSERT_EQ(delegate->performed_actions[2], + EXPECT_EQ(delegate->performed_actions.size(), size_t{3}); + EXPECT_EQ(delegate->performed_actions[2], FlutterSemanticsAction::kFlutterSemanticsActionShowOnScreen); } +TEST(FlutterAccessibilityTest, canGetBridge) { + // Set up a flutter accessibility node. + FlutterAccessibility* accessibility = FlutterAccessibility::Create(); + AccessibilityBridge bridge( + std::make_unique(), nullptr); + + AXNode ax_node(bridge.GetAXTree(), 0, -1, -1); + accessibility->Init(&bridge, &ax_node); + + EXPECT_EQ(accessibility->GetBridge(), &bridge); +} + +TEST(FlutterAccessibilityTest, canGetAXNode) { + // Set up a flutter accessibility node. + FlutterAccessibility* accessibility = FlutterAccessibility::Create(); + AccessibilityBridge bridge( + std::make_unique(), nullptr); + + AXNode ax_node(bridge.GetAXTree(), 0, -1, -1); + accessibility->Init(&bridge, &ax_node); + + EXPECT_EQ(accessibility->GetAXNode(), &ax_node); +} + +TEST(FlutterAccessibilityTest, canCalculateBoundsCorrectly) { + AccessibilityBridge bridge( + std::make_unique(), nullptr); + FlutterSemanticsNode root; + root.id = 0; + root.label = "root"; + root.hint = ""; + root.value = ""; + root.increased_value = ""; + root.decreased_value = ""; + root.child_count = 1; + int32_t children[] = {1}; + root.children_in_traversal_order = children; + root.custom_accessibility_actions_count = 0; + root.rect = {0, 0, 100, 100}; // LTRB + root.transform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + bridge.AddFlutterSemanticsNodeUpdate(&root); + + FlutterSemanticsNode child1; + child1.id = 1; + child1.label = "child 1"; + child1.hint = ""; + child1.value = ""; + child1.increased_value = ""; + child1.decreased_value = ""; + child1.child_count = 0; + child1.custom_accessibility_actions_count = 0; + child1.rect = {0, 0, 50, 50}; // LTRB + child1.transform = {0.5, 0, 0, 0, 0.5, 0, 0, 0, 1}; + bridge.AddFlutterSemanticsNodeUpdate(&child1); + + bridge.CommitUpdates(); + FlutterAccessibility* child1_node = bridge.GetFlutterAccessibilityFromID(1); + AXOffscreenResult result; + gfx::Rect bounds = child1_node->GetBoundsRect( + AXCoordinateSystem::kScreenDIPs, AXClippingBehavior::kClipped, &result); + EXPECT_EQ(bounds.x(), 0); + EXPECT_EQ(bounds.y(), 0); + EXPECT_EQ(bounds.width(), 25); + EXPECT_EQ(bounds.height(), 25); + EXPECT_EQ(result, AXOffscreenResult::kOnscreen); +} + +TEST(FlutterAccessibilityTest, canCalculateOffScreenBoundsCorrectly) { + AccessibilityBridge bridge( + std::make_unique(), nullptr); + FlutterSemanticsNode root; + root.id = 0; + root.label = "root"; + root.hint = ""; + root.value = ""; + root.increased_value = ""; + root.decreased_value = ""; + root.child_count = 1; + int32_t children[] = {1}; + root.children_in_traversal_order = children; + root.custom_accessibility_actions_count = 0; + root.rect = {0, 0, 100, 100}; // LTRB + root.transform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + bridge.AddFlutterSemanticsNodeUpdate(&root); + + FlutterSemanticsNode child1; + child1.id = 1; + child1.label = "child 1"; + child1.hint = ""; + child1.value = ""; + child1.increased_value = ""; + child1.decreased_value = ""; + child1.child_count = 0; + child1.custom_accessibility_actions_count = 0; + child1.rect = {90, 90, 100, 100}; // LTRB + child1.transform = {2, 0, 0, 0, 2, 0, 0, 0, 1}; + bridge.AddFlutterSemanticsNodeUpdate(&child1); + + bridge.CommitUpdates(); + FlutterAccessibility* child1_node = bridge.GetFlutterAccessibilityFromID(1); + AXOffscreenResult result; + gfx::Rect bounds = child1_node->GetBoundsRect( + AXCoordinateSystem::kScreenDIPs, AXClippingBehavior::kUnclipped, &result); + EXPECT_EQ(bounds.x(), 180); + EXPECT_EQ(bounds.y(), 180); + EXPECT_EQ(bounds.width(), 20); + EXPECT_EQ(bounds.height(), 20); + EXPECT_EQ(result, AXOffscreenResult::kOffscreen); +} + } // namespace testing } // namespace ui From 3584cf1cf68a6af7e14b5266b332d4907314e0b0 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Thu, 7 Jan 2021 12:53:11 -0800 Subject: [PATCH 04/19] fix license --- ci/licenses_golden/licenses_flutter | 8 ++++++++ shell/platform/common/cpp/flutter_accessibility.cc | 6 ++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 5f784be07af1e..e2328976d299e 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -868,6 +868,9 @@ FILE: ../../../flutter/shell/platform/android/surface/android_surface_mock.cc FILE: ../../../flutter/shell/platform/android/surface/android_surface_mock.h FILE: ../../../flutter/shell/platform/android/vsync_waiter_android.cc FILE: ../../../flutter/shell/platform/android/vsync_waiter_android.h +FILE: ../../../flutter/shell/platform/common/cpp/accessibility_bridge.cc +FILE: ../../../flutter/shell/platform/common/cpp/accessibility_bridge.h +FILE: ../../../flutter/shell/platform/common/cpp/accessibility_bridge_unittests.cc FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/basic_message_channel_unittests.cc FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/binary_messenger_impl.h FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/byte_buffer_streams.h @@ -909,6 +912,9 @@ FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/texture_registra FILE: ../../../flutter/shell/platform/common/cpp/engine_switches.cc FILE: ../../../flutter/shell/platform/common/cpp/engine_switches.h FILE: ../../../flutter/shell/platform/common/cpp/engine_switches_unittests.cc +FILE: ../../../flutter/shell/platform/common/cpp/flutter_accessibility.cc +FILE: ../../../flutter/shell/platform/common/cpp/flutter_accessibility.h +FILE: ../../../flutter/shell/platform/common/cpp/flutter_accessibility_unittests.cc FILE: ../../../flutter/shell/platform/common/cpp/incoming_message_dispatcher.cc FILE: ../../../flutter/shell/platform/common/cpp/incoming_message_dispatcher.h FILE: ../../../flutter/shell/platform/common/cpp/json_message_codec.cc @@ -924,6 +930,8 @@ FILE: ../../../flutter/shell/platform/common/cpp/public/flutter_export.h FILE: ../../../flutter/shell/platform/common/cpp/public/flutter_messenger.h FILE: ../../../flutter/shell/platform/common/cpp/public/flutter_plugin_registrar.h FILE: ../../../flutter/shell/platform/common/cpp/public/flutter_texture_registrar.h +FILE: ../../../flutter/shell/platform/common/cpp/test_accessibility_bridge.cc +FILE: ../../../flutter/shell/platform/common/cpp/test_accessibility_bridge.h FILE: ../../../flutter/shell/platform/common/cpp/text_input_model.cc FILE: ../../../flutter/shell/platform/common/cpp/text_input_model.h FILE: ../../../flutter/shell/platform/common/cpp/text_input_model_unittests.cc diff --git a/shell/platform/common/cpp/flutter_accessibility.cc b/shell/platform/common/cpp/flutter_accessibility.cc index 777bc78e4d743..e8f24aee8a883 100644 --- a/shell/platform/common/cpp/flutter_accessibility.cc +++ b/shell/platform/common/cpp/flutter_accessibility.cc @@ -104,8 +104,10 @@ gfx::Rect FlutterAccessibility::GetBoundsRect( bool offscreen = false; gfx::RectF bounds = GetBridge()->GetAXTree()->RelativeToTreeBounds( GetAXNode(), gfx::RectF(), &offscreen, clip_bounds); - *offscreen_result = - offscreen ? AXOffscreenResult::kOffscreen : AXOffscreenResult::kOnscreen; + if (offscreen_result != nullptr) { + *offscreen_result = offscreen ? AXOffscreenResult::kOffscreen + : AXOffscreenResult::kOnscreen; + } return gfx::ToEnclosingRect(bounds); } From d357439e891d9951f39af86fcc8586f72aab535a Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Thu, 7 Jan 2021 13:16:32 -0800 Subject: [PATCH 05/19] rename build file --- third_party/accessibility/{build.gn => BUILD.gn} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename third_party/accessibility/{build.gn => BUILD.gn} (100%) diff --git a/third_party/accessibility/build.gn b/third_party/accessibility/BUILD.gn similarity index 100% rename from third_party/accessibility/build.gn rename to third_party/accessibility/BUILD.gn From 343aaba4e3f0c629a2abebedbd1bb30ac6b95205 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Thu, 7 Jan 2021 14:18:45 -0800 Subject: [PATCH 06/19] fix build --- shell/platform/common/cpp/BUILD.gn | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/shell/platform/common/cpp/BUILD.gn b/shell/platform/common/cpp/BUILD.gn index 8b04764c019cb..cb6678c2209ea 100644 --- a/shell/platform/common/cpp/BUILD.gn +++ b/shell/platform/common/cpp/BUILD.gn @@ -159,9 +159,7 @@ if (enable_unittests) { testonly = true sources = [ - "accessibility_bridge_unittests.cc", "engine_switches_unittests.cc", - "flutter_accessibility_unittests.cc", "json_message_codec_unittests.cc", "json_method_codec_unittests.cc", "test_accessibility_bridge.cc", @@ -172,7 +170,6 @@ if (enable_unittests) { deps = [ ":common_cpp", - ":common_cpp_accessibility", ":common_cpp_fixtures", ":common_cpp_input", ":common_cpp_switches", @@ -181,6 +178,16 @@ if (enable_unittests) { "//flutter/testing", ] + # The accessibility bridge only supports MacOS for now. + if (is_mac) { + sources += [ + "accessibility_bridge_unittests.cc", + "flutter_accessibility_unittests.cc", + ] + + deps += [":common_cpp_accessibility"] + } + public_configs = [ "//flutter:config" ] } } From f4cbd2271d13e76868e35548850c2ecc03f3e409 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Thu, 7 Jan 2021 14:21:56 -0800 Subject: [PATCH 07/19] fix build take two --- shell/platform/common/cpp/BUILD.gn | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/common/cpp/BUILD.gn b/shell/platform/common/cpp/BUILD.gn index cb6678c2209ea..776f8a0fdaee5 100644 --- a/shell/platform/common/cpp/BUILD.gn +++ b/shell/platform/common/cpp/BUILD.gn @@ -162,8 +162,6 @@ if (enable_unittests) { "engine_switches_unittests.cc", "json_message_codec_unittests.cc", "json_method_codec_unittests.cc", - "test_accessibility_bridge.cc", - "test_accessibility_bridge.h", "text_input_model_unittests.cc", "text_range_unittests.cc", ] @@ -183,6 +181,8 @@ if (enable_unittests) { sources += [ "accessibility_bridge_unittests.cc", "flutter_accessibility_unittests.cc", + "test_accessibility_bridge.cc", + "test_accessibility_bridge.h", ] deps += [":common_cpp_accessibility"] From ad5f7b1f51f77fcb85ca23f08b90272e04507343 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Thu, 7 Jan 2021 14:25:41 -0800 Subject: [PATCH 08/19] fix format --- shell/platform/common/cpp/BUILD.gn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/common/cpp/BUILD.gn b/shell/platform/common/cpp/BUILD.gn index 776f8a0fdaee5..3fbde2e61a742 100644 --- a/shell/platform/common/cpp/BUILD.gn +++ b/shell/platform/common/cpp/BUILD.gn @@ -185,7 +185,7 @@ if (enable_unittests) { "test_accessibility_bridge.h", ] - deps += [":common_cpp_accessibility"] + deps += [ ":common_cpp_accessibility" ] } public_configs = [ "//flutter:config" ] From 45ceb3c805517d81d09c68a24dd6e01c1047bbfa Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 11 Jan 2021 11:02:15 -0800 Subject: [PATCH 09/19] smart pointer --- .../common/cpp/accessibility_bridge.cc | 136 +++++++++--------- .../common/cpp/accessibility_bridge.h | 135 ++++++++--------- .../cpp/accessibility_bridge_unittests.cc | 54 +++---- .../common/cpp/flutter_accessibility.cc | 68 ++++----- .../common/cpp/flutter_accessibility.h | 33 ++--- .../cpp/flutter_accessibility_unittests.cc | 102 +++++++++---- .../common/cpp/test_accessibility_bridge.cc | 13 +- .../common/cpp/test_accessibility_bridge.h | 11 +- 8 files changed, 293 insertions(+), 259 deletions(-) diff --git a/shell/platform/common/cpp/accessibility_bridge.cc b/shell/platform/common/cpp/accessibility_bridge.cc index 5011411b5ee3d..e175b9a0f2ca4 100644 --- a/shell/platform/common/cpp/accessibility_bridge.cc +++ b/shell/platform/common/cpp/accessibility_bridge.cc @@ -10,9 +10,9 @@ #include "flutter/third_party/accessibility/ax/ax_tree_update.h" #include "flutter/third_party/accessibility/base/logging.h" -namespace ui { // namespace +namespace flutter { // namespace -constexpr int khasScrollingAction = +constexpr int kHasScrollingAction = FlutterSemanticsAction::kFlutterSemanticsActionScrollLeft | FlutterSemanticsAction::kFlutterSemanticsActionScrollRight | FlutterSemanticsAction::kFlutterSemanticsActionScrollUp | @@ -20,18 +20,15 @@ constexpr int khasScrollingAction = // AccessibilityBridge AccessibilityBridge::AccessibilityBridge( - std::unique_ptr delegate, - void* user_data) - : delegate_(std::move(delegate)), - tree_(std::make_unique()), - event_generator_(tree_.get()) { - user_data_ = user_data; - tree_->AddObserver((ui::AXTreeObserver*)this); + std::unique_ptr delegate) + : delegate_(std::move(delegate)) { + event_generator_.SetTree(&tree_); + tree_.AddObserver((ui::AXTreeObserver*)this); } AccessibilityBridge::~AccessibilityBridge() { event_generator_.ReleaseTree(); - tree_->RemoveObserver((ui::AXTreeObserver*)this); + tree_.RemoveObserver((ui::AXTreeObserver*)this); } void AccessibilityBridge::AddFlutterSemanticsNodeUpdate( @@ -46,14 +43,14 @@ void AccessibilityBridge::AddFlutterSemanticsCustomActionUpdate( } void AccessibilityBridge::CommitUpdates() { - AXTreeUpdate update{.tree_data = GetAXTree()->data()}; - // Figure out update order, AXTree only accepts update in tree order, where - // parent node must come before the child node in AXTreeUpdate.nodes. - // We start with picking a random node and turn the entire subtree into a - // list. We pick another node from the remaining update, and keep doing so - // until the update map is empty. We then concatenate the lists in the - // reversed order, this guarantees parent updates always come before child - // updates. + ui::AXTreeUpdate update{.tree_data = tree_.data()}; + // Figure out update order, ui::AXTree only accepts update in tree order, + // where parent node must come before the child node in + // ui::AXTreeUpdate.nodes. We start with picking a random node and turn the + // entire subtree into a list. We pick another node from the remaining update, + // and keep doing so until the update map is empty. We then concatenate the + // lists in the reversed order, this guarantees parent updates always come + // before child updates. std::vector> results; while (!_pending_semantics_node_updates.empty()) { auto begin = _pending_semantics_node_updates.begin(); @@ -70,20 +67,20 @@ void AccessibilityBridge::CommitUpdates() { } } - tree_->Unserialize(update); + tree_.Unserialize(update); _pending_semantics_node_updates.clear(); _pending_semantics_custom_action_updates.clear(); - std::string error = tree_->error(); + std::string error = tree_.error(); if (!error.empty()) { - BASE_LOG() << "Failed to update AXTree, error: " << error; + BASE_LOG() << "Failed to update ui::AXTree, error: " << error; return; } // Handles accessibility events as the result of the semantics update. for (const auto& targeted_event : event_generator_) { - FlutterAccessibility* event_target = + auto event_target = GetFlutterAccessibilityFromID(targeted_event.node->id()); - if (!event_target) + if (event_target.expired()) continue; delegate_->OnAccessibilityEvent(targeted_event, this); @@ -91,37 +88,19 @@ void AccessibilityBridge::CommitUpdates() { event_generator_.ClearEvents(); } -void* AccessibilityBridge::GetUserData() { - return user_data_; -} - -AccessibilityBridge::AccessibilityBridgeDelegate* -AccessibilityBridge::GetDelegate() { - return delegate_.get(); -} - -ui::AXTree* AccessibilityBridge::GetAXTree() { - return tree_.get(); -} - -AXEventGenerator* AccessibilityBridge::GetEventGenerator() { - return &event_generator_; -} - -FlutterAccessibility* AccessibilityBridge::GetFlutterAccessibilityFromID( - int32_t id) const { +std::weak_ptr +AccessibilityBridge::GetFlutterAccessibilityFromID(int32_t id) const { const auto iter = id_wrapper_map_.find(id); if (iter != id_wrapper_map_.end()) return iter->second; - return nullptr; + return std::weak_ptr(); } void AccessibilityBridge::SetFocusedNode(int32_t node_id) { if (last_focused_node_ != node_id) { - FlutterAccessibility* last_focused_child = - GetFlutterAccessibilityFromID(last_focused_node_); - if (last_focused_child) { + auto last_focused_child = GetFlutterAccessibilityFromID(last_focused_node_); + if (!last_focused_child.expired()) { delegate_->DispatchAccessibilityAction( last_focused_node_, FlutterSemanticsAction:: @@ -136,6 +115,10 @@ int32_t AccessibilityBridge::GetLastFocusedNode() { return last_focused_node_; } +const ui::AXTreeData& AccessibilityBridge::GetAXTreeData() const { + return tree_.data(); +} + void AccessibilityBridge::OnNodeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) {} @@ -152,16 +135,14 @@ void AccessibilityBridge::OnRoleChanged(ui::AXTree* tree, void AccessibilityBridge::OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) { BASE_DCHECK(node); - FlutterAccessibility* wrapper = FlutterAccessibility::Create(); - id_wrapper_map_[node->id()] = wrapper; - wrapper->Init(this, node); + id_wrapper_map_[node->id()] = delegate_->CreateFlutterAccessibility(); + id_wrapper_map_[node->id()]->Init(this, node); } void AccessibilityBridge::OnNodeDeleted(ui::AXTree* tree, int32_t node_id) { BASE_DCHECK(node_id != ui::AXNode::kInvalidAXID); - if (FlutterAccessibility* wrapper = GetFlutterAccessibilityFromID(node_id)) { + if (id_wrapper_map_.find(node_id) != id_wrapper_map_.end()) { id_wrapper_map_.erase(node_id); - delete wrapper; } } @@ -174,7 +155,7 @@ void AccessibilityBridge::OnAtomicUpdateFinished( // to calculate the screen bound correctly. for (const auto& change : changes) { ui::AXNode* node = change.node; - const AXNodeData& data = node->data(); + const ui::AXNodeData& data = node->data(); int32_t offset_container_id = -1; if (node->parent()) { offset_container_id = node->parent()->id(); @@ -199,8 +180,8 @@ void AccessibilityBridge::GetSubTreeList(SemanticsNode target, } void AccessibilityBridge::ConvertFluterUpdate(const SemanticsNode& node, - AXTreeUpdate& tree_update) { - AXNodeData node_data; + ui::AXTreeUpdate& tree_update) { + ui::AXNodeData node_data; node_data.id = node.id; SetRoleFromFlutterUpdate(node_data, node); SetStateFromFlutterUpdate(node_data, node); @@ -226,7 +207,7 @@ void AccessibilityBridge::ConvertFluterUpdate(const SemanticsNode& node, tree_update.nodes.push_back(node_data); } -void AccessibilityBridge::SetRoleFromFlutterUpdate(AXNodeData& node_data, +void AccessibilityBridge::SetRoleFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node) { FlutterSemanticsFlag flags = node.flags; if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsButton) { @@ -268,7 +249,7 @@ void AccessibilityBridge::SetRoleFromFlutterUpdate(AXNodeData& node_data, } } -void AccessibilityBridge::SetStateFromFlutterUpdate(AXNodeData& node_data, +void AccessibilityBridge::SetStateFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node) { FlutterSemanticsFlag flags = node.flags; FlutterSemanticsAction actions = node.actions; @@ -277,7 +258,7 @@ void AccessibilityBridge::SetStateFromFlutterUpdate(AXNodeData& node_data, node_data.AddState(ax::mojom::State::kEditable); } if (node_data.role == ax::mojom::Role::kStaticText && - (actions & khasScrollingAction) == 0 && node.value.empty() && + (actions & kHasScrollingAction) == 0 && node.value.empty() && node.label.empty() && node.hint.empty()) { node_data.AddState(ax::mojom::State::kIgnored); } else { @@ -289,7 +270,7 @@ void AccessibilityBridge::SetStateFromFlutterUpdate(AXNodeData& node_data, } void AccessibilityBridge::SetActionsFromFlutterUpdate( - AXNodeData& node_data, + ui::AXNodeData& node_data, const SemanticsNode& node) { FlutterSemanticsAction actions = node.actions; if (actions & FlutterSemanticsAction::kFlutterSemanticsActionTap) { @@ -333,12 +314,12 @@ void AccessibilityBridge::SetActionsFromFlutterUpdate( } void AccessibilityBridge::SetBooleanAttributesFromFlutterUpdate( - AXNodeData& node_data, + ui::AXNodeData& node_data, const SemanticsNode& node) { FlutterSemanticsAction actions = node.actions; FlutterSemanticsFlag flags = node.flags; node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kScrollable, - actions & khasScrollingAction); + actions & kHasScrollingAction); node_data.AddBoolAttribute( ax::mojom::BoolAttribute::kClickable, actions & FlutterSemanticsAction::kFlutterSemanticsActionTap); @@ -355,7 +336,7 @@ void AccessibilityBridge::SetBooleanAttributesFromFlutterUpdate( } void AccessibilityBridge::SetIntAttributesFromFlutterUpdate( - AXNodeData& node_data, + ui::AXNodeData& node_data, const SemanticsNode& node) { FlutterSemanticsFlag flags = node.flags; node_data.AddIntAttribute(ax::mojom::IntAttribute::kTextDirection, @@ -384,7 +365,7 @@ void AccessibilityBridge::SetIntAttributesFromFlutterUpdate( } void AccessibilityBridge::SetIntListAttributesFromFlutterUpdate( - AXNodeData& node_data, + ui::AXNodeData& node_data, const SemanticsNode& node) { FlutterSemanticsAction actions = node.actions; if (actions & FlutterSemanticsAction::kFlutterSemanticsActionCustomAction) { @@ -398,7 +379,7 @@ void AccessibilityBridge::SetIntListAttributesFromFlutterUpdate( } void AccessibilityBridge::SetStringListAttributesFromFlutterUpdate( - AXNodeData& node_data, + ui::AXNodeData& node_data, const SemanticsNode& node) { FlutterSemanticsAction actions = node.actions; if (actions & FlutterSemanticsAction::kFlutterSemanticsActionCustomAction) { @@ -415,18 +396,18 @@ void AccessibilityBridge::SetStringListAttributesFromFlutterUpdate( } } -void AccessibilityBridge::SetNameFromFlutterUpdate(AXNodeData& node_data, +void AccessibilityBridge::SetNameFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node) { node_data.SetName(node.label); } -void AccessibilityBridge::SetValueFromFlutterUpdate(AXNodeData& node_data, +void AccessibilityBridge::SetValueFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node) { node_data.SetValue(node.value); } void AccessibilityBridge::SetTreeData(const SemanticsNode& node, - AXTreeUpdate& tree_update) { + ui::AXTreeUpdate& tree_update) { FlutterSemanticsFlag flags = node.flags; // Set selection if: // 1. this text field has a valid selection @@ -440,9 +421,9 @@ void AccessibilityBridge::SetTreeData(const SemanticsNode& node, tree_update.tree_data.sel_focus_offset = node.text_selection_extent; tree_update.has_tree_data = true; } else if (tree_update.tree_data.sel_anchor_object_id == node.id) { - tree_update.tree_data.sel_anchor_object_id = AXNode::kInvalidAXID; + tree_update.tree_data.sel_anchor_object_id = ui::AXNode::kInvalidAXID; tree_update.tree_data.sel_anchor_offset = -1; - tree_update.tree_data.sel_focus_object_id = AXNode::kInvalidAXID; + tree_update.tree_data.sel_focus_object_id = ui::AXNode::kInvalidAXID; tree_update.tree_data.sel_focus_offset = -1; tree_update.has_tree_data = true; } @@ -455,7 +436,7 @@ void AccessibilityBridge::SetTreeData(const SemanticsNode& node, } else if ((flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsFocused) == 0 && tree_update.tree_data.focus_id == node.id) { - tree_update.tree_data.focus_id = AXNode::kInvalidAXID; + tree_update.tree_data.focus_id = ui::AXNode::kInvalidAXID; tree_update.has_tree_data = true; } } @@ -523,4 +504,19 @@ AccessibilityBridge::FromFlutterSemanticsCustomAction( return result; } -} // namespace ui +gfx::RectF AccessibilityBridge::RelativeToGlobalBounds(ui::AXNode* node, + bool* offscreen, + bool clip_bounds) { + return tree_.RelativeToTreeBounds(node, gfx::RectF(), offscreen, clip_bounds); +} + +void AccessibilityBridge::DispatchAccessibilityAction( + uint16_t target, + FlutterSemanticsAction action, + std::unique_ptr data, + size_t data_size) { + delegate_->DispatchAccessibilityAction(target, action, std::move(data), + data_size); +} + +} // namespace flutter diff --git a/shell/platform/common/cpp/accessibility_bridge.h b/shell/platform/common/cpp/accessibility_bridge.h index ea05b672d5fca..6d9c7aa0b6d3e 100644 --- a/shell/platform/common/cpp/accessibility_bridge.h +++ b/shell/platform/common/cpp/accessibility_bridge.h @@ -16,7 +16,7 @@ #include "flutter_accessibility.h" -namespace ui { +namespace flutter { //------------------------------------------------------------------------------ /// Use this class to maintain an accessibility tree. This class consumes @@ -25,9 +25,9 @@ namespace ui { /// /// To use this class, you must provide your own implementation of /// FlutterAccessibility and AccessibilityBridgeDelegate. -class AccessibilityBridge : public AXTreeObserver { +class AccessibilityBridge : public ui::AXTreeObserver { public: - //------------------------------------------------------------------------------ + //----------------------------------------------------------------------------- /// Delegate to handle accessibility event and route accessibility action /// back to the Flutter framework. /// @@ -42,45 +42,50 @@ class AccessibilityBridge : public AXTreeObserver { class AccessibilityBridgeDelegate { public: virtual ~AccessibilityBridgeDelegate() = default; - //------------------------------------------------------------------------------ + //--------------------------------------------------------------------------- /// @brief Handle accessibility events generated due to accessibility - /// tree - /// changes. + /// tree changes. /// /// @param[in] targeted_event The object that contains both the - /// generated - /// event and the event target. + /// generated event and the event target. /// @param[in] bridge The pointer to the accessibility bridge - /// that - /// can be used for querying the + /// that can be used for querying the /// accessibility information at the time /// the event is fired. virtual void OnAccessibilityEvent( - AXEventGenerator::TargetedEvent targeted_event, + ui::AXEventGenerator::TargetedEvent targeted_event, AccessibilityBridge* bridge) = 0; - //------------------------------------------------------------------------------ + //--------------------------------------------------------------------------- /// @brief Dispatch accessibility action back to the Flutter framework /// /// @param[in] target The semantics node id of the action - /// target. + /// target. /// @param[in] action The generated flutter semantics action. /// @param[in] data Additional data associated with the - /// action. + /// action. /// @param[in] data_size The length of the additional data. virtual void DispatchAccessibilityAction(uint16_t target, FlutterSemanticsAction action, - uint8_t* data, + std::unique_ptr data, size_t data_size) = 0; + + //--------------------------------------------------------------------------- + /// @brief Creates a platform specific FlutterAccessibility. Ownership + /// passes to the caller. This method will be called by + /// accessibility bridge when it creates accessibility node. + /// Each platform needs to implement this method in order to + /// inject its subclass into the accessibility bridge. + virtual std::unique_ptr + CreateFlutterAccessibility() = 0; }; - //------------------------------------------------------------------------------ + //----------------------------------------------------------------------------- /// @brief Creates a new instance of a accessibility bridge. /// /// @param[in] user_data A custom pointer to the data of your /// choice. This pointer can be retrieve later /// through GetUserData(). - AccessibilityBridge(std::unique_ptr delegate, - void* user_data); + AccessibilityBridge(std::unique_ptr delegate); ~AccessibilityBridge(); //------------------------------------------------------------------------------ @@ -107,31 +112,14 @@ class AccessibilityBridge : public AXTreeObserver { /// accessibility bridge. void CommitUpdates(); - //------------------------------------------------------------------------------ - /// @brief Get the underlying AXTree. - AXTree* GetAXTree(); - - //------------------------------------------------------------------------------ - /// @brief The event generator of this accessibility bridge. It contains - /// the pending accessibility events generated as a result of a - /// semantics update. - AXEventGenerator* GetEventGenerator(); - - //------------------------------------------------------------------------------ - /// @brief Get the user data. - void* GetUserData(); - - //------------------------------------------------------------------------------ - /// @brief Get the accessibility bridge delegate. - AccessibilityBridgeDelegate* GetDelegate(); - //------------------------------------------------------------------------------ /// @brief Get the flutter accessibility node with the given id from this /// accessibility bridge. /// /// @param[in] id The id of the flutter accessibility node you want /// to retrieve. - FlutterAccessibility* GetFlutterAccessibilityFromID(int32_t id) const; + std::weak_ptr GetFlutterAccessibilityFromID( + int32_t id) const; //------------------------------------------------------------------------------ /// @brief Update the currently focused flutter accessibility node. @@ -144,22 +132,24 @@ class AccessibilityBridge : public AXTreeObserver { /// @brief Get the last focused node. int32_t GetLastFocusedNode(); - // AXTreeObserver implementation. - void OnNodeWillBeDeleted(AXTree* tree, AXNode* node) override; - void OnSubtreeWillBeDeleted(AXTree* tree, AXNode* node) override; - void OnNodeCreated(AXTree* tree, AXNode* node) override; - void OnNodeDeleted(AXTree* tree, int32_t node_id) override; - void OnNodeReparented(AXTree* tree, AXNode* node) override; - void OnRoleChanged(AXTree* tree, - AXNode* node, + //------------------------------------------------------------------------------ + /// @brief Get the ax tree data. + const ui::AXTreeData& GetAXTreeData() const; + + // ui::AXTreeObserver implementation. + void OnNodeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override; + void OnSubtreeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override; + void OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) override; + void OnNodeDeleted(ui::AXTree* tree, int32_t node_id) override; + void OnNodeReparented(ui::AXTree* tree, ui::AXNode* node) override; + void OnRoleChanged(ui::AXTree* tree, + ui::AXNode* node, ax::mojom::Role old_role, ax::mojom::Role new_role) override; void OnAtomicUpdateFinished( - AXTree* tree, + ui::AXTree* tree, bool root_changed, - const std::vector& changes) override; - - std::unique_ptr delegate_; + const std::vector& changes) override; private: // See FlutterSemanticsNode in embedder.h @@ -196,45 +186,56 @@ class AccessibilityBridge : public AXTreeObserver { std::string hint; } SemanticsCustomAction; - std::unordered_map id_wrapper_map_; - std::unique_ptr tree_; - AXEventGenerator event_generator_; + std::unordered_map> + id_wrapper_map_; + ui::AXTree tree_; + ui::AXEventGenerator event_generator_; std::unordered_map _pending_semantics_node_updates; std::unordered_map _pending_semantics_custom_action_updates; - int32_t last_focused_node_ = AXNode::kInvalidAXID; - void* user_data_; + int32_t last_focused_node_ = ui::AXNode::kInvalidAXID; + std::unique_ptr delegate_; - void InitAXTree(const AXTreeUpdate& initial_state); + void InitAXTree(const ui::AXTreeUpdate& initial_state); void GetSubTreeList(SemanticsNode target, std::vector& result); void ConvertFluterUpdate(const SemanticsNode& node, - AXTreeUpdate& tree_update); - void SetRoleFromFlutterUpdate(AXNodeData& node_data, + ui::AXTreeUpdate& tree_update); + void SetRoleFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); - void SetStateFromFlutterUpdate(AXNodeData& node_data, + void SetStateFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); - void SetActionsFromFlutterUpdate(AXNodeData& node_data, + void SetActionsFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); - void SetBooleanAttributesFromFlutterUpdate(AXNodeData& node_data, + void SetBooleanAttributesFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); - void SetIntAttributesFromFlutterUpdate(AXNodeData& node_data, + void SetIntAttributesFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); - void SetIntListAttributesFromFlutterUpdate(AXNodeData& node_data, + void SetIntListAttributesFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); - void SetStringListAttributesFromFlutterUpdate(AXNodeData& node_data, + void SetStringListAttributesFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); - void SetNameFromFlutterUpdate(AXNodeData& node_data, + void SetNameFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); - void SetValueFromFlutterUpdate(AXNodeData& node_data, + void SetValueFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); - void SetTreeData(const SemanticsNode& node, AXTreeUpdate& tree_update); + void SetTreeData(const SemanticsNode& node, ui::AXTreeUpdate& tree_update); SemanticsNode FromFlutterSemanticsNode( const FlutterSemanticsNode* flutter_node); SemanticsCustomAction FromFlutterSemanticsCustomAction( const FlutterSemanticsCustomAction* flutter_custom_action); + void DispatchAccessibilityAction(uint16_t target, + FlutterSemanticsAction action, + std::unique_ptr data, + size_t data_size); + gfx::RectF RelativeToGlobalBounds(ui::AXNode* node, + bool* offscreen, + bool clip_bounds); + + friend class FlutterAccessibility; + BASE_DISALLOW_COPY_AND_ASSIGN(AccessibilityBridge); }; -} // namespace ui +} // namespace flutter #endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_ACCESSIBILITY_BRIDGE_H_ diff --git a/shell/platform/common/cpp/accessibility_bridge_unittests.cc b/shell/platform/common/cpp/accessibility_bridge_unittests.cc index 65650edb6114f..f009d89286bc1 100644 --- a/shell/platform/common/cpp/accessibility_bridge_unittests.cc +++ b/shell/platform/common/cpp/accessibility_bridge_unittests.cc @@ -8,12 +8,12 @@ #include "test_accessibility_bridge.h" -namespace ui { +namespace flutter { namespace testing { TEST(AccessibilityBridgeTest, basicTest) { AccessibilityBridge bridge( - std::make_unique(), nullptr); + std::make_unique()); FlutterSemanticsNode root; root.id = 0; root.label = "root"; @@ -51,9 +51,9 @@ TEST(AccessibilityBridgeTest, basicTest) { bridge.CommitUpdates(); - FlutterAccessibility* root_node = bridge.GetFlutterAccessibilityFromID(0); - FlutterAccessibility* child1_node = bridge.GetFlutterAccessibilityFromID(1); - FlutterAccessibility* child2_node = bridge.GetFlutterAccessibilityFromID(2); + auto root_node = bridge.GetFlutterAccessibilityFromID(0).lock(); + auto child1_node = bridge.GetFlutterAccessibilityFromID(1).lock(); + auto child2_node = bridge.GetFlutterAccessibilityFromID(2).lock(); EXPECT_EQ(root_node->GetChildCount(), 2); EXPECT_EQ(root_node->GetData().child_ids[0], 1); EXPECT_EQ(root_node->GetData().child_ids[1], 2); @@ -67,10 +67,10 @@ TEST(AccessibilityBridgeTest, basicTest) { } TEST(AccessibilityBridgeTest, canFireChildrenChangedCorrectly) { - AccessibilityBridge bridge( - std::make_unique(), nullptr); TestAccessibilityBridgeDelegate* delegate = - (TestAccessibilityBridgeDelegate*)bridge.GetDelegate(); + new TestAccessibilityBridgeDelegate(); + std::unique_ptr ptr(delegate); + AccessibilityBridge bridge(std::move(ptr)); FlutterSemanticsNode root; root.id = 0; root.flags = static_cast(0); @@ -105,8 +105,8 @@ TEST(AccessibilityBridgeTest, canFireChildrenChangedCorrectly) { bridge.CommitUpdates(); - FlutterAccessibility* root_node = bridge.GetFlutterAccessibilityFromID(0); - FlutterAccessibility* child1_node = bridge.GetFlutterAccessibilityFromID(1); + auto root_node = bridge.GetFlutterAccessibilityFromID(0).lock(); + auto child1_node = bridge.GetFlutterAccessibilityFromID(1).lock(); EXPECT_EQ(root_node->GetChildCount(), 1); EXPECT_EQ(root_node->GetData().child_ids[0], 1); EXPECT_EQ(root_node->GetName(), "root"); @@ -138,24 +138,26 @@ TEST(AccessibilityBridgeTest, canFireChildrenChangedCorrectly) { bridge.CommitUpdates(); - root_node = bridge.GetFlutterAccessibilityFromID(0); + root_node = bridge.GetFlutterAccessibilityFromID(0).lock(); EXPECT_EQ(root_node->GetChildCount(), 2); EXPECT_EQ(root_node->GetData().child_ids[0], 1); EXPECT_EQ(root_node->GetData().child_ids[1], 2); EXPECT_EQ(delegate->accessibilitiy_events.size(), size_t{2}); - std::set actual_event; + std::set actual_event; actual_event.insert(delegate->accessibilitiy_events[0].event_params.event); actual_event.insert(delegate->accessibilitiy_events[1].event_params.event); - EXPECT_NE(actual_event.find(AXEventGenerator::Event::CHILDREN_CHANGED), + EXPECT_NE(actual_event.find(ui::AXEventGenerator::Event::CHILDREN_CHANGED), actual_event.end()); - EXPECT_NE(actual_event.find(AXEventGenerator::Event::SUBTREE_CREATED), + EXPECT_NE(actual_event.find(ui::AXEventGenerator::Event::SUBTREE_CREATED), actual_event.end()); } TEST(AccessibilityBridgeTest, canHandleSelectionChangeCorrectly) { - AccessibilityBridge bridge( - std::make_unique(), nullptr); + TestAccessibilityBridgeDelegate* delegate = + new TestAccessibilityBridgeDelegate(); + std::unique_ptr ptr(delegate); + AccessibilityBridge bridge(std::move(ptr)); FlutterSemanticsNode root; root.id = 0; root.flags = FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField; @@ -173,10 +175,8 @@ TEST(AccessibilityBridgeTest, canHandleSelectionChangeCorrectly) { bridge.CommitUpdates(); - TestAccessibilityBridgeDelegate* delegate = - (TestAccessibilityBridgeDelegate*)bridge.GetDelegate(); - AXTree* tree = bridge.GetAXTree(); - EXPECT_EQ(tree->data().sel_anchor_object_id, AXNode::kInvalidAXID); + const ui::AXTreeData& tree = bridge.GetAXTreeData(); + EXPECT_EQ(tree.sel_anchor_object_id, ui::AXNode::kInvalidAXID); delegate->accessibilitiy_events.clear(); // Update the selection. @@ -186,16 +186,16 @@ TEST(AccessibilityBridgeTest, canHandleSelectionChangeCorrectly) { bridge.CommitUpdates(); - EXPECT_EQ(tree->data().sel_anchor_object_id, 0); - EXPECT_EQ(tree->data().sel_anchor_offset, 0); - EXPECT_EQ(tree->data().sel_focus_object_id, 0); - EXPECT_EQ(tree->data().sel_focus_offset, 5); + EXPECT_EQ(tree.sel_anchor_object_id, 0); + EXPECT_EQ(tree.sel_anchor_offset, 0); + EXPECT_EQ(tree.sel_focus_object_id, 0); + EXPECT_EQ(tree.sel_focus_offset, 5); EXPECT_EQ(delegate->accessibilitiy_events.size(), size_t{2}); EXPECT_EQ(delegate->accessibilitiy_events[0].event_params.event, - AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED); + ui::AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED); EXPECT_EQ(delegate->accessibilitiy_events[1].event_params.event, - AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED); + ui::AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED); } } // namespace testing -} // namespace ui +} // namespace flutter diff --git a/shell/platform/common/cpp/flutter_accessibility.cc b/shell/platform/common/cpp/flutter_accessibility.cc index e8f24aee8a883..99fad3f133f28 100644 --- a/shell/platform/common/cpp/flutter_accessibility.cc +++ b/shell/platform/common/cpp/flutter_accessibility.cc @@ -9,13 +9,13 @@ #include "accessibility_bridge.h" -namespace ui { +namespace flutter { FlutterAccessibility::FlutterAccessibility() = default; FlutterAccessibility::~FlutterAccessibility() = default; -void FlutterAccessibility::Init(AccessibilityBridge* bridge, AXNode* node) { +void FlutterAccessibility::Init(AccessibilityBridge* bridge, ui::AXNode* node) { bridge_ = bridge; ax_node_ = node; } @@ -24,31 +24,31 @@ AccessibilityBridge* FlutterAccessibility::GetBridge() const { return bridge_; } -AXNode* FlutterAccessibility::GetAXNode() const { +ui::AXNode* FlutterAccessibility::GetAXNode() const { return ax_node_; } bool FlutterAccessibility::AccessibilityPerformAction( - const AXActionData& data) { + const ui::AXActionData& data) { int32_t target = GetAXNode()->id(); switch (data.action) { case ax::mojom::Action::kDoDefault: - bridge_->GetDelegate()->DispatchAccessibilityAction( - target, FlutterSemanticsAction::kFlutterSemanticsActionTap, nullptr, + bridge_->DispatchAccessibilityAction( + target, FlutterSemanticsAction::kFlutterSemanticsActionTap, {nullptr}, 0); return true; case ax::mojom::Action::kFocus: bridge_->SetFocusedNode(target); - bridge_->GetDelegate()->DispatchAccessibilityAction( + bridge_->DispatchAccessibilityAction( target, FlutterSemanticsAction:: kFlutterSemanticsActionDidGainAccessibilityFocus, - nullptr, 0); + {nullptr}, 0); return true; case ax::mojom::Action::kScrollToMakeVisible: - bridge_->GetDelegate()->DispatchAccessibilityAction( + bridge_->DispatchAccessibilityAction( target, FlutterSemanticsAction::kFlutterSemanticsActionShowOnScreen, - nullptr, 0); + {nullptr}, 0); return true; // TODO(chunhtai): support more actions. default: @@ -57,7 +57,7 @@ bool FlutterAccessibility::AccessibilityPerformAction( return false; } -const AXNodeData& FlutterAccessibility::GetData() const { +const ui::AXNodeData& FlutterAccessibility::GetData() const { return GetAXNode()->data(); } @@ -65,23 +65,24 @@ gfx::NativeViewAccessible FlutterAccessibility::GetParent() { if (!GetAXNode()->parent()) { return nullptr; } - return GetBridge() - ->GetFlutterAccessibilityFromID(GetAXNode()->parent()->id()) - ->GetNativeViewAccessible(); + std::shared_ptr accessibility = + bridge_->GetFlutterAccessibilityFromID(GetAXNode()->parent()->id()) + .lock(); + BASE_CHECK(accessibility); + return accessibility->GetNativeViewAccessible(); } gfx::NativeViewAccessible FlutterAccessibility::GetFocus() { - int32_t focused_node = GetBridge()->GetLastFocusedNode(); - if (focused_node == AXNode::kInvalidAXID) { + int32_t focused_node = bridge_->GetLastFocusedNode(); + if (focused_node == ui::AXNode::kInvalidAXID) { return nullptr; } - FlutterAccessibility* focus = - GetBridge()->GetFlutterAccessibilityFromID(focused_node); - if (!focus) + std::weak_ptr focus = + bridge_->GetFlutterAccessibilityFromID(focused_node); + auto focus_ptr = focus.lock(); + if (!focus_ptr) return nullptr; - return GetBridge() - ->GetFlutterAccessibilityFromID(focused_node) - ->GetNativeViewAccessible(); + return focus_ptr->GetNativeViewAccessible(); } int FlutterAccessibility::GetChildCount() const { @@ -90,25 +91,26 @@ int FlutterAccessibility::GetChildCount() const { gfx::NativeViewAccessible FlutterAccessibility::ChildAtIndex(int index) { int32_t child = GetAXNode()->GetUnignoredChildAtIndex(index)->id(); - return GetBridge() - ->GetFlutterAccessibilityFromID(child) + return bridge_->GetFlutterAccessibilityFromID(child) + .lock() ->GetNativeViewAccessible(); } gfx::Rect FlutterAccessibility::GetBoundsRect( - const AXCoordinateSystem coordinate_system, - const AXClippingBehavior clipping_behavior, - AXOffscreenResult* offscreen_result) const { + const ui::AXCoordinateSystem coordinate_system, + const ui::AXClippingBehavior clipping_behavior, + ui::AXOffscreenResult* offscreen_result) const { // TODO(chunhtai): consider screen dpr. - const bool clip_bounds = clipping_behavior == AXClippingBehavior::kClipped; + const bool clip_bounds = + clipping_behavior == ui::AXClippingBehavior::kClipped; bool offscreen = false; - gfx::RectF bounds = GetBridge()->GetAXTree()->RelativeToTreeBounds( - GetAXNode(), gfx::RectF(), &offscreen, clip_bounds); + gfx::RectF bounds = + bridge_->RelativeToGlobalBounds(GetAXNode(), &offscreen, clip_bounds); if (offscreen_result != nullptr) { - *offscreen_result = offscreen ? AXOffscreenResult::kOffscreen - : AXOffscreenResult::kOnscreen; + *offscreen_result = offscreen ? ui::AXOffscreenResult::kOffscreen + : ui::AXOffscreenResult::kOnscreen; } return gfx::ToEnclosingRect(bounds); } -} // namespace ui +} // namespace flutter diff --git a/shell/platform/common/cpp/flutter_accessibility.h b/shell/platform/common/cpp/flutter_accessibility.h index 4ef634d16a009..0aa6bbb067a7c 100644 --- a/shell/platform/common/cpp/flutter_accessibility.h +++ b/shell/platform/common/cpp/flutter_accessibility.h @@ -10,7 +10,7 @@ #include "flutter/third_party/accessibility/ax/ax_event_generator.h" #include "flutter/third_party/accessibility/ax/platform/ax_platform_node_delegate_base.h" -namespace ui { +namespace flutter { class AccessibilityBridge; @@ -30,16 +30,8 @@ class AccessibilityBridge; /// /// Lastly, each platform needs to implement the FlutterAccessibility::Create /// static method to inject its sublcass into accessibility bridge. -class FlutterAccessibility : public AXPlatformNodeDelegateBase { +class FlutterAccessibility : public ui::AXPlatformNodeDelegateBase { public: - //------------------------------------------------------------------------------ - /// @brief Creates a platform specific FlutterAccessibility. Ownership - /// passes to the caller. This method will be called by - /// accessibility bridge when it creates accessibility node. Each - /// platform needs to implement this method in order to inject its - /// subclass into the accessibility bridge. - static FlutterAccessibility* Create(); - FlutterAccessibility(); ~FlutterAccessibility() override; @@ -50,30 +42,31 @@ class FlutterAccessibility : public AXPlatformNodeDelegateBase { //------------------------------------------------------------------------------ /// @brief Gets the underlying ax node for this accessibility node. - AXNode* GetAXNode() const; + ui::AXNode* GetAXNode() const; - // AXPlatformNodeDelegateBase override; - const AXNodeData& GetData() const override; - bool AccessibilityPerformAction(const AXActionData& data) override; + // ui::AXPlatformNodeDelegateBase override; + const ui::AXNodeData& GetData() const override; + bool AccessibilityPerformAction(const ui::AXActionData& data) override; gfx::NativeViewAccessible GetParent() override; gfx::NativeViewAccessible GetFocus() override; int GetChildCount() const override; gfx::NativeViewAccessible ChildAtIndex(int index) override; - gfx::Rect GetBoundsRect(const AXCoordinateSystem coordinate_system, - const AXClippingBehavior clipping_behavior, - AXOffscreenResult* offscreen_result) const override; + gfx::Rect GetBoundsRect( + const ui::AXCoordinateSystem coordinate_system, + const ui::AXClippingBehavior clipping_behavior, + ui::AXOffscreenResult* offscreen_result) const override; //------------------------------------------------------------------------------ /// @brief Called only once, immediately after construction. The /// constructor doesn't take any arguments because in the Windows /// subclass we use a special function to construct a COM object. /// Subclasses must call super. - virtual void Init(AccessibilityBridge* bridge, AXNode* node); + virtual void Init(AccessibilityBridge* bridge, ui::AXNode* node); private: - AXNode* ax_node_; + ui::AXNode* ax_node_; AccessibilityBridge* bridge_; }; -} // namespace ui +} // namespace flutter #endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_FLUTTER_ACCESSIBILITY_H_ diff --git a/shell/platform/common/cpp/flutter_accessibility_unittests.cc b/shell/platform/common/cpp/flutter_accessibility_unittests.cc index f3f0765b69c25..de2d7d8329ca0 100644 --- a/shell/platform/common/cpp/flutter_accessibility_unittests.cc +++ b/shell/platform/common/cpp/flutter_accessibility_unittests.cc @@ -9,22 +9,34 @@ #include "test_accessibility_bridge.h" -namespace ui { +namespace flutter { namespace testing { TEST(FlutterAccessibilityTest, canPerfomActions) { - // Set up a flutter accessibility node. - FlutterAccessibility* accessibility = FlutterAccessibility::Create(); - AccessibilityBridge bridge( - std::make_unique(), nullptr); TestAccessibilityBridgeDelegate* delegate = - (TestAccessibilityBridgeDelegate*)bridge.GetDelegate(); + new TestAccessibilityBridgeDelegate(); + std::unique_ptr ptr(delegate); + AccessibilityBridge bridge(std::move(ptr)); + FlutterSemanticsNode root; + root.id = 0; + root.flags = FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField; + root.actions = static_cast(0); + root.text_selection_base = -1; + root.text_selection_extent = -1; + root.label = "root"; + root.hint = ""; + root.value = ""; + root.increased_value = ""; + root.decreased_value = ""; + root.child_count = 0; + root.custom_accessibility_actions_count = 0; + bridge.AddFlutterSemanticsNodeUpdate(&root); - AXNode ax_node(bridge.GetAXTree(), 0, -1, -1); - accessibility->Init(&bridge, &ax_node); + bridge.CommitUpdates(); + auto accessibility = bridge.GetFlutterAccessibilityFromID(0).lock(); // Performs an AXAction. - AXActionData action_data; + ui::AXActionData action_data; action_data.action = ax::mojom::Action::kDoDefault; accessibility->AccessibilityPerformAction(action_data); EXPECT_EQ(delegate->performed_actions.size(), size_t{1}); @@ -47,31 +59,57 @@ TEST(FlutterAccessibilityTest, canPerfomActions) { TEST(FlutterAccessibilityTest, canGetBridge) { // Set up a flutter accessibility node. - FlutterAccessibility* accessibility = FlutterAccessibility::Create(); AccessibilityBridge bridge( - std::make_unique(), nullptr); + std::make_unique()); + FlutterSemanticsNode root; + root.id = 0; + root.flags = FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField; + root.actions = static_cast(0); + root.text_selection_base = -1; + root.text_selection_extent = -1; + root.label = "root"; + root.hint = ""; + root.value = ""; + root.increased_value = ""; + root.decreased_value = ""; + root.child_count = 0; + root.custom_accessibility_actions_count = 0; + bridge.AddFlutterSemanticsNodeUpdate(&root); - AXNode ax_node(bridge.GetAXTree(), 0, -1, -1); - accessibility->Init(&bridge, &ax_node); + bridge.CommitUpdates(); + auto accessibility = bridge.GetFlutterAccessibilityFromID(0).lock(); EXPECT_EQ(accessibility->GetBridge(), &bridge); } TEST(FlutterAccessibilityTest, canGetAXNode) { // Set up a flutter accessibility node. - FlutterAccessibility* accessibility = FlutterAccessibility::Create(); AccessibilityBridge bridge( - std::make_unique(), nullptr); + std::make_unique()); + FlutterSemanticsNode root; + root.id = 0; + root.flags = FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField; + root.actions = static_cast(0); + root.text_selection_base = -1; + root.text_selection_extent = -1; + root.label = "root"; + root.hint = ""; + root.value = ""; + root.increased_value = ""; + root.decreased_value = ""; + root.child_count = 0; + root.custom_accessibility_actions_count = 0; + bridge.AddFlutterSemanticsNodeUpdate(&root); - AXNode ax_node(bridge.GetAXTree(), 0, -1, -1); - accessibility->Init(&bridge, &ax_node); + bridge.CommitUpdates(); - EXPECT_EQ(accessibility->GetAXNode(), &ax_node); + auto accessibility = bridge.GetFlutterAccessibilityFromID(0).lock(); + EXPECT_EQ(accessibility->GetAXNode()->data().id, 0); } TEST(FlutterAccessibilityTest, canCalculateBoundsCorrectly) { AccessibilityBridge bridge( - std::make_unique(), nullptr); + std::make_unique()); FlutterSemanticsNode root; root.id = 0; root.label = "root"; @@ -101,20 +139,21 @@ TEST(FlutterAccessibilityTest, canCalculateBoundsCorrectly) { bridge.AddFlutterSemanticsNodeUpdate(&child1); bridge.CommitUpdates(); - FlutterAccessibility* child1_node = bridge.GetFlutterAccessibilityFromID(1); - AXOffscreenResult result; - gfx::Rect bounds = child1_node->GetBoundsRect( - AXCoordinateSystem::kScreenDIPs, AXClippingBehavior::kClipped, &result); + auto child1_node = bridge.GetFlutterAccessibilityFromID(1).lock(); + ui::AXOffscreenResult result; + gfx::Rect bounds = + child1_node->GetBoundsRect(ui::AXCoordinateSystem::kScreenDIPs, + ui::AXClippingBehavior::kClipped, &result); EXPECT_EQ(bounds.x(), 0); EXPECT_EQ(bounds.y(), 0); EXPECT_EQ(bounds.width(), 25); EXPECT_EQ(bounds.height(), 25); - EXPECT_EQ(result, AXOffscreenResult::kOnscreen); + EXPECT_EQ(result, ui::AXOffscreenResult::kOnscreen); } TEST(FlutterAccessibilityTest, canCalculateOffScreenBoundsCorrectly) { AccessibilityBridge bridge( - std::make_unique(), nullptr); + std::make_unique()); FlutterSemanticsNode root; root.id = 0; root.label = "root"; @@ -144,16 +183,17 @@ TEST(FlutterAccessibilityTest, canCalculateOffScreenBoundsCorrectly) { bridge.AddFlutterSemanticsNodeUpdate(&child1); bridge.CommitUpdates(); - FlutterAccessibility* child1_node = bridge.GetFlutterAccessibilityFromID(1); - AXOffscreenResult result; - gfx::Rect bounds = child1_node->GetBoundsRect( - AXCoordinateSystem::kScreenDIPs, AXClippingBehavior::kUnclipped, &result); + auto child1_node = bridge.GetFlutterAccessibilityFromID(1).lock(); + ui::AXOffscreenResult result; + gfx::Rect bounds = + child1_node->GetBoundsRect(ui::AXCoordinateSystem::kScreenDIPs, + ui::AXClippingBehavior::kUnclipped, &result); EXPECT_EQ(bounds.x(), 180); EXPECT_EQ(bounds.y(), 180); EXPECT_EQ(bounds.width(), 20); EXPECT_EQ(bounds.height(), 20); - EXPECT_EQ(result, AXOffscreenResult::kOffscreen); + EXPECT_EQ(result, ui::AXOffscreenResult::kOffscreen); } } // namespace testing -} // namespace ui +} // namespace flutter diff --git a/shell/platform/common/cpp/test_accessibility_bridge.cc b/shell/platform/common/cpp/test_accessibility_bridge.cc index 48085a7eb3e3e..dd4916e9fe7fd 100644 --- a/shell/platform/common/cpp/test_accessibility_bridge.cc +++ b/shell/platform/common/cpp/test_accessibility_bridge.cc @@ -4,14 +4,15 @@ #include "test_accessibility_bridge.h" -namespace ui { +namespace flutter { -FlutterAccessibility* FlutterAccessibility::Create() { - return new FlutterAccessibility(); +std::unique_ptr +TestAccessibilityBridgeDelegate::CreateFlutterAccessibility() { + return std::make_unique(); }; void TestAccessibilityBridgeDelegate::OnAccessibilityEvent( - AXEventGenerator::TargetedEvent targeted_event, + ui::AXEventGenerator::TargetedEvent targeted_event, AccessibilityBridge* bridge) { accessibilitiy_events.push_back(targeted_event); } @@ -19,9 +20,9 @@ void TestAccessibilityBridgeDelegate::OnAccessibilityEvent( void TestAccessibilityBridgeDelegate::DispatchAccessibilityAction( uint16_t target, FlutterSemanticsAction action, - uint8_t* data, + std::unique_ptr data, size_t data_size) { performed_actions.push_back(action); } -} // namespace ui +} // namespace flutter diff --git a/shell/platform/common/cpp/test_accessibility_bridge.h b/shell/platform/common/cpp/test_accessibility_bridge.h index bc687be447531..5d6a76ea2b74a 100644 --- a/shell/platform/common/cpp/test_accessibility_bridge.h +++ b/shell/platform/common/cpp/test_accessibility_bridge.h @@ -7,24 +7,25 @@ #include "accessibility_bridge.h" -namespace ui { +namespace flutter { class TestAccessibilityBridgeDelegate : public AccessibilityBridge::AccessibilityBridgeDelegate { public: TestAccessibilityBridgeDelegate() = default; - void OnAccessibilityEvent(AXEventGenerator::TargetedEvent targeted_event, + void OnAccessibilityEvent(ui::AXEventGenerator::TargetedEvent targeted_event, AccessibilityBridge* bridge) override; void DispatchAccessibilityAction(uint16_t target, FlutterSemanticsAction action, - uint8_t* data, + std::unique_ptr data, size_t data_size) override; + std::unique_ptr CreateFlutterAccessibility(); - std::vector accessibilitiy_events; + std::vector accessibilitiy_events; std::vector performed_actions; }; -} // namespace ui +} // namespace flutter #endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_TEST_ACCESSIBILITY_BRIDGE_H_ From 3e80a541bba90e16839114efb6ad4ada3dc65b0a Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 11 Jan 2021 14:21:15 -0800 Subject: [PATCH 10/19] update --- ci/licenses_golden/licenses_flutter | 6 +- shell/platform/common/cpp/BUILD.gn | 6 +- .../common/cpp/accessibility_bridge.cc | 22 +++---- .../common/cpp/accessibility_bridge.h | 59 +++++++++++-------- .../cpp/accessibility_bridge_unittests.cc | 12 ++-- ...y.cc => flutter_platform_node_delegate.cc} | 42 ++++++------- ...ity.h => flutter_platform_node_delegate.h} | 20 +++---- ...utter_platform_node_delegate_unittests.cc} | 12 ++-- .../common/cpp/test_accessibility_bridge.cc | 9 ++- .../common/cpp/test_accessibility_bridge.h | 7 +-- 10 files changed, 100 insertions(+), 95 deletions(-) rename shell/platform/common/cpp/{flutter_accessibility.cc => flutter_platform_node_delegate.cc} (64%) rename shell/platform/common/cpp/{flutter_accessibility.h => flutter_platform_node_delegate.h} (78%) rename shell/platform/common/cpp/{flutter_accessibility_unittests.cc => flutter_platform_node_delegate_unittests.cc} (93%) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index e2328976d299e..674c3c741454c 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -912,9 +912,9 @@ FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/texture_registra FILE: ../../../flutter/shell/platform/common/cpp/engine_switches.cc FILE: ../../../flutter/shell/platform/common/cpp/engine_switches.h FILE: ../../../flutter/shell/platform/common/cpp/engine_switches_unittests.cc -FILE: ../../../flutter/shell/platform/common/cpp/flutter_accessibility.cc -FILE: ../../../flutter/shell/platform/common/cpp/flutter_accessibility.h -FILE: ../../../flutter/shell/platform/common/cpp/flutter_accessibility_unittests.cc +FILE: ../../../flutter/shell/platform/common/cpp/flutter_platform_node_delegate.cc +FILE: ../../../flutter/shell/platform/common/cpp/flutter_platform_node_delegate.h +FILE: ../../../flutter/shell/platform/common/cpp/flutter_platform_node_delegate_unittests.cc FILE: ../../../flutter/shell/platform/common/cpp/incoming_message_dispatcher.cc FILE: ../../../flutter/shell/platform/common/cpp/incoming_message_dispatcher.h FILE: ../../../flutter/shell/platform/common/cpp/json_message_codec.cc diff --git a/shell/platform/common/cpp/BUILD.gn b/shell/platform/common/cpp/BUILD.gn index 3fbde2e61a742..148c39ad91709 100644 --- a/shell/platform/common/cpp/BUILD.gn +++ b/shell/platform/common/cpp/BUILD.gn @@ -71,12 +71,12 @@ source_set("common_cpp_switches") { source_set("common_cpp_accessibility") { public = [ "accessibility_bridge.h", - "flutter_accessibility.h", + "flutter_platform_node_delegate.h", ] sources = [ "accessibility_bridge.cc", - "flutter_accessibility.cc", + "flutter_platform_node_delegate.cc", ] public_configs = @@ -180,7 +180,7 @@ if (enable_unittests) { if (is_mac) { sources += [ "accessibility_bridge_unittests.cc", - "flutter_accessibility_unittests.cc", + "flutter_platform_node_delegate_unittests.cc", "test_accessibility_bridge.cc", "test_accessibility_bridge.h", ] diff --git a/shell/platform/common/cpp/accessibility_bridge.cc b/shell/platform/common/cpp/accessibility_bridge.cc index e175b9a0f2ca4..969163a59fa0a 100644 --- a/shell/platform/common/cpp/accessibility_bridge.cc +++ b/shell/platform/common/cpp/accessibility_bridge.cc @@ -79,27 +79,27 @@ void AccessibilityBridge::CommitUpdates() { // Handles accessibility events as the result of the semantics update. for (const auto& targeted_event : event_generator_) { auto event_target = - GetFlutterAccessibilityFromID(targeted_event.node->id()); + GetFlutterPlatformNodeDelegateFromID(targeted_event.node->id()); if (event_target.expired()) continue; - delegate_->OnAccessibilityEvent(targeted_event, this); + delegate_->OnAccessibilityEvent(targeted_event); } event_generator_.ClearEvents(); } -std::weak_ptr -AccessibilityBridge::GetFlutterAccessibilityFromID(int32_t id) const { +std::weak_ptr +AccessibilityBridge::GetFlutterPlatformNodeDelegateFromID(ui::AXNode::AXID id) const { const auto iter = id_wrapper_map_.find(id); if (iter != id_wrapper_map_.end()) return iter->second; - return std::weak_ptr(); + return std::weak_ptr(); } -void AccessibilityBridge::SetFocusedNode(int32_t node_id) { +void AccessibilityBridge::SetFocusedNode(ui::AXNode::AXID node_id) { if (last_focused_node_ != node_id) { - auto last_focused_child = GetFlutterAccessibilityFromID(last_focused_node_); + auto last_focused_child = GetFlutterPlatformNodeDelegateFromID(last_focused_node_); if (!last_focused_child.expired()) { delegate_->DispatchAccessibilityAction( last_focused_node_, @@ -111,7 +111,7 @@ void AccessibilityBridge::SetFocusedNode(int32_t node_id) { } } -int32_t AccessibilityBridge::GetLastFocusedNode() { +ui::AXNode::AXID AccessibilityBridge::GetLastFocusedNode() { return last_focused_node_; } @@ -139,7 +139,7 @@ void AccessibilityBridge::OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) { id_wrapper_map_[node->id()]->Init(this, node); } -void AccessibilityBridge::OnNodeDeleted(ui::AXTree* tree, int32_t node_id) { +void AccessibilityBridge::OnNodeDeleted(ui::AXTree* tree, ui::AXNode::AXID node_id) { BASE_DCHECK(node_id != ui::AXNode::kInvalidAXID); if (id_wrapper_map_.find(node_id) != id_wrapper_map_.end()) { id_wrapper_map_.erase(node_id); @@ -156,7 +156,7 @@ void AccessibilityBridge::OnAtomicUpdateFinished( for (const auto& change : changes) { ui::AXNode* node = change.node; const ui::AXNodeData& data = node->data(); - int32_t offset_container_id = -1; + ui::AXNode::AXID offset_container_id = -1; if (node->parent()) { offset_container_id = node->parent()->id(); } @@ -511,7 +511,7 @@ gfx::RectF AccessibilityBridge::RelativeToGlobalBounds(ui::AXNode* node, } void AccessibilityBridge::DispatchAccessibilityAction( - uint16_t target, + ui::AXNode::AXID target, FlutterSemanticsAction action, std::unique_ptr data, size_t data_size) { diff --git a/shell/platform/common/cpp/accessibility_bridge.h b/shell/platform/common/cpp/accessibility_bridge.h index 6d9c7aa0b6d3e..c0ebc07f88d17 100644 --- a/shell/platform/common/cpp/accessibility_bridge.h +++ b/shell/platform/common/cpp/accessibility_bridge.h @@ -14,7 +14,7 @@ #include "flutter/third_party/accessibility/ax/ax_tree_observer.h" #include "flutter/third_party/accessibility/ax/platform/ax_platform_node_delegate.h" -#include "flutter_accessibility.h" +#include "flutter_platform_node_delegate.h" namespace flutter { @@ -24,7 +24,7 @@ namespace flutter { /// in the native format. /// /// To use this class, you must provide your own implementation of -/// FlutterAccessibility and AccessibilityBridgeDelegate. +/// FlutterPlatformNodeDelegate and AccessibilityBridgeDelegate. class AccessibilityBridge : public ui::AXTreeObserver { public: //----------------------------------------------------------------------------- @@ -44,20 +44,22 @@ class AccessibilityBridge : public ui::AXTreeObserver { virtual ~AccessibilityBridgeDelegate() = default; //--------------------------------------------------------------------------- /// @brief Handle accessibility events generated due to accessibility - /// tree changes. + /// tree changes. These events are generated in accessibility + /// bridge and needed to be sent to native accessibility system. + /// See ui::AXEventGenerator::Event for possible events. /// /// @param[in] targeted_event The object that contains both the /// generated event and the event target. - /// @param[in] bridge The pointer to the accessibility bridge - /// that can be used for querying the - /// accessibility information at the time - /// the event is fired. virtual void OnAccessibilityEvent( - ui::AXEventGenerator::TargetedEvent targeted_event, - AccessibilityBridge* bridge) = 0; + ui::AXEventGenerator::TargetedEvent targeted_event) = 0; //--------------------------------------------------------------------------- - /// @brief Dispatch accessibility action back to the Flutter framework + /// @brief Dispatch accessibility action back to the Flutter framework. + /// These actions are generated in the native accessibility + /// system when users interact with the assistive technologies. + /// For example, a + /// FlutterSemanticsAction::kFlutterSemanticsActionTap is + /// fired when user click or touch the screen. /// /// @param[in] target The semantics node id of the action /// target. @@ -65,18 +67,18 @@ class AccessibilityBridge : public ui::AXTreeObserver { /// @param[in] data Additional data associated with the /// action. /// @param[in] data_size The length of the additional data. - virtual void DispatchAccessibilityAction(uint16_t target, + virtual void DispatchAccessibilityAction(ui::AXNode::AXID target, FlutterSemanticsAction action, std::unique_ptr data, size_t data_size) = 0; //--------------------------------------------------------------------------- - /// @brief Creates a platform specific FlutterAccessibility. Ownership - /// passes to the caller. This method will be called by + /// @brief Creates a platform specific FlutterPlatformNodeDelegate. + /// Ownership passes to the caller. This method will be called by /// accessibility bridge when it creates accessibility node. /// Each platform needs to implement this method in order to /// inject its subclass into the accessibility bridge. - virtual std::unique_ptr + virtual std::unique_ptr CreateFlutterAccessibility() = 0; }; //----------------------------------------------------------------------------- @@ -109,28 +111,33 @@ class AccessibilityBridge : public ui::AXTreeObserver { //------------------------------------------------------------------------------ /// @brief Flushes the pending updates and applies them to this - /// accessibility bridge. + /// accessibility bridge. Calling this with no pending updates does + /// nothing, and callers should call this method at the end of an + /// automic batch to avoid leaving the tree in a unstable state. + /// For example if a node reparents from A to B, callers should + /// only call this method when both removal from A and addition + /// to B are in the pending updates. void CommitUpdates(); //------------------------------------------------------------------------------ - /// @brief Get the flutter accessibility node with the given id from this - /// accessibility bridge. + /// @brief Get the flutter platform node delegate with the given id from + /// this accessibility bridge. /// /// @param[in] id The id of the flutter accessibility node you want /// to retrieve. - std::weak_ptr GetFlutterAccessibilityFromID( - int32_t id) const; + std::weak_ptr GetFlutterPlatformNodeDelegateFromID( + ui::AXNode::AXID id) const; //------------------------------------------------------------------------------ /// @brief Update the currently focused flutter accessibility node. /// /// @param[in] id The id of the currently focused flutter /// accessibility node. - void SetFocusedNode(int32_t node_id); + void SetFocusedNode(ui::AXNode::AXID node_id); //------------------------------------------------------------------------------ /// @brief Get the last focused node. - int32_t GetLastFocusedNode(); + ui::AXNode::AXID GetLastFocusedNode(); //------------------------------------------------------------------------------ /// @brief Get the ax tree data. @@ -140,7 +147,7 @@ class AccessibilityBridge : public ui::AXTreeObserver { void OnNodeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override; void OnSubtreeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override; void OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) override; - void OnNodeDeleted(ui::AXTree* tree, int32_t node_id) override; + void OnNodeDeleted(ui::AXTree* tree, ui::AXNode::AXID node_id) override; void OnNodeReparented(ui::AXTree* tree, ui::AXNode* node) override; void OnRoleChanged(ui::AXTree* tree, ui::AXNode* node, @@ -186,14 +193,14 @@ class AccessibilityBridge : public ui::AXTreeObserver { std::string hint; } SemanticsCustomAction; - std::unordered_map> + std::unordered_map> id_wrapper_map_; ui::AXTree tree_; ui::AXEventGenerator event_generator_; std::unordered_map _pending_semantics_node_updates; std::unordered_map _pending_semantics_custom_action_updates; - int32_t last_focused_node_ = ui::AXNode::kInvalidAXID; + ui::AXNode::AXID last_focused_node_ = ui::AXNode::kInvalidAXID; std::unique_ptr delegate_; void InitAXTree(const ui::AXTreeUpdate& initial_state); @@ -223,7 +230,7 @@ class AccessibilityBridge : public ui::AXTreeObserver { const FlutterSemanticsNode* flutter_node); SemanticsCustomAction FromFlutterSemanticsCustomAction( const FlutterSemanticsCustomAction* flutter_custom_action); - void DispatchAccessibilityAction(uint16_t target, + void DispatchAccessibilityAction(ui::AXNode::AXID target, FlutterSemanticsAction action, std::unique_ptr data, size_t data_size); @@ -231,7 +238,7 @@ class AccessibilityBridge : public ui::AXTreeObserver { bool* offscreen, bool clip_bounds); - friend class FlutterAccessibility; + friend class FlutterPlatformNodeDelegate; BASE_DISALLOW_COPY_AND_ASSIGN(AccessibilityBridge); }; diff --git a/shell/platform/common/cpp/accessibility_bridge_unittests.cc b/shell/platform/common/cpp/accessibility_bridge_unittests.cc index f009d89286bc1..6e49bd0673b2e 100644 --- a/shell/platform/common/cpp/accessibility_bridge_unittests.cc +++ b/shell/platform/common/cpp/accessibility_bridge_unittests.cc @@ -51,9 +51,9 @@ TEST(AccessibilityBridgeTest, basicTest) { bridge.CommitUpdates(); - auto root_node = bridge.GetFlutterAccessibilityFromID(0).lock(); - auto child1_node = bridge.GetFlutterAccessibilityFromID(1).lock(); - auto child2_node = bridge.GetFlutterAccessibilityFromID(2).lock(); + auto root_node = bridge.GetFlutterPlatformNodeDelegateFromID(0).lock(); + auto child1_node = bridge.GetFlutterPlatformNodeDelegateFromID(1).lock(); + auto child2_node = bridge.GetFlutterPlatformNodeDelegateFromID(2).lock(); EXPECT_EQ(root_node->GetChildCount(), 2); EXPECT_EQ(root_node->GetData().child_ids[0], 1); EXPECT_EQ(root_node->GetData().child_ids[1], 2); @@ -105,8 +105,8 @@ TEST(AccessibilityBridgeTest, canFireChildrenChangedCorrectly) { bridge.CommitUpdates(); - auto root_node = bridge.GetFlutterAccessibilityFromID(0).lock(); - auto child1_node = bridge.GetFlutterAccessibilityFromID(1).lock(); + auto root_node = bridge.GetFlutterPlatformNodeDelegateFromID(0).lock(); + auto child1_node = bridge.GetFlutterPlatformNodeDelegateFromID(1).lock(); EXPECT_EQ(root_node->GetChildCount(), 1); EXPECT_EQ(root_node->GetData().child_ids[0], 1); EXPECT_EQ(root_node->GetName(), "root"); @@ -138,7 +138,7 @@ TEST(AccessibilityBridgeTest, canFireChildrenChangedCorrectly) { bridge.CommitUpdates(); - root_node = bridge.GetFlutterAccessibilityFromID(0).lock(); + root_node = bridge.GetFlutterPlatformNodeDelegateFromID(0).lock(); EXPECT_EQ(root_node->GetChildCount(), 2); EXPECT_EQ(root_node->GetData().child_ids[0], 1); diff --git a/shell/platform/common/cpp/flutter_accessibility.cc b/shell/platform/common/cpp/flutter_platform_node_delegate.cc similarity index 64% rename from shell/platform/common/cpp/flutter_accessibility.cc rename to shell/platform/common/cpp/flutter_platform_node_delegate.cc index 99fad3f133f28..acea91c843af5 100644 --- a/shell/platform/common/cpp/flutter_accessibility.cc +++ b/shell/platform/common/cpp/flutter_platform_node_delegate.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "flutter_accessibility.h" +#include "flutter_platform_node_delegate.h" #include "flutter/third_party/accessibility/ax/ax_action_data.h" #include "flutter/third_party/accessibility/gfx/geometry/rect_conversions.h" @@ -11,26 +11,26 @@ namespace flutter { -FlutterAccessibility::FlutterAccessibility() = default; +FlutterPlatformNodeDelegate::FlutterPlatformNodeDelegate() = default; -FlutterAccessibility::~FlutterAccessibility() = default; +FlutterPlatformNodeDelegate::~FlutterPlatformNodeDelegate() = default; -void FlutterAccessibility::Init(AccessibilityBridge* bridge, ui::AXNode* node) { +void FlutterPlatformNodeDelegate::Init(AccessibilityBridge* bridge, ui::AXNode* node) { bridge_ = bridge; ax_node_ = node; } -AccessibilityBridge* FlutterAccessibility::GetBridge() const { +AccessibilityBridge* FlutterPlatformNodeDelegate::GetBridge() const { return bridge_; } -ui::AXNode* FlutterAccessibility::GetAXNode() const { +ui::AXNode* FlutterPlatformNodeDelegate::GetAXNode() const { return ax_node_; } -bool FlutterAccessibility::AccessibilityPerformAction( +bool FlutterPlatformNodeDelegate::AccessibilityPerformAction( const ui::AXActionData& data) { - int32_t target = GetAXNode()->id(); + ui::AXNode::AXID target = GetAXNode()->id(); switch (data.action) { case ax::mojom::Action::kDoDefault: bridge_->DispatchAccessibilityAction( @@ -57,46 +57,46 @@ bool FlutterAccessibility::AccessibilityPerformAction( return false; } -const ui::AXNodeData& FlutterAccessibility::GetData() const { +const ui::AXNodeData& FlutterPlatformNodeDelegate::GetData() const { return GetAXNode()->data(); } -gfx::NativeViewAccessible FlutterAccessibility::GetParent() { +gfx::NativeViewAccessible FlutterPlatformNodeDelegate::GetParent() { if (!GetAXNode()->parent()) { return nullptr; } - std::shared_ptr accessibility = - bridge_->GetFlutterAccessibilityFromID(GetAXNode()->parent()->id()) + std::shared_ptr accessibility = + bridge_->GetFlutterPlatformNodeDelegateFromID(GetAXNode()->parent()->id()) .lock(); BASE_CHECK(accessibility); return accessibility->GetNativeViewAccessible(); } -gfx::NativeViewAccessible FlutterAccessibility::GetFocus() { - int32_t focused_node = bridge_->GetLastFocusedNode(); +gfx::NativeViewAccessible FlutterPlatformNodeDelegate::GetFocus() { + ui::AXNode::AXID focused_node = bridge_->GetLastFocusedNode(); if (focused_node == ui::AXNode::kInvalidAXID) { return nullptr; } - std::weak_ptr focus = - bridge_->GetFlutterAccessibilityFromID(focused_node); + std::weak_ptr focus = + bridge_->GetFlutterPlatformNodeDelegateFromID(focused_node); auto focus_ptr = focus.lock(); if (!focus_ptr) return nullptr; return focus_ptr->GetNativeViewAccessible(); } -int FlutterAccessibility::GetChildCount() const { +int FlutterPlatformNodeDelegate::GetChildCount() const { return static_cast(GetAXNode()->GetUnignoredChildCount()); } -gfx::NativeViewAccessible FlutterAccessibility::ChildAtIndex(int index) { - int32_t child = GetAXNode()->GetUnignoredChildAtIndex(index)->id(); - return bridge_->GetFlutterAccessibilityFromID(child) +gfx::NativeViewAccessible FlutterPlatformNodeDelegate::ChildAtIndex(int index) { + ui::AXNode::AXID child = GetAXNode()->GetUnignoredChildAtIndex(index)->id(); + return bridge_->GetFlutterPlatformNodeDelegateFromID(child) .lock() ->GetNativeViewAccessible(); } -gfx::Rect FlutterAccessibility::GetBoundsRect( +gfx::Rect FlutterPlatformNodeDelegate::GetBoundsRect( const ui::AXCoordinateSystem coordinate_system, const ui::AXClippingBehavior clipping_behavior, ui::AXOffscreenResult* offscreen_result) const { diff --git a/shell/platform/common/cpp/flutter_accessibility.h b/shell/platform/common/cpp/flutter_platform_node_delegate.h similarity index 78% rename from shell/platform/common/cpp/flutter_accessibility.h rename to shell/platform/common/cpp/flutter_platform_node_delegate.h index 0aa6bbb067a7c..4e7f8ea662d2a 100644 --- a/shell/platform/common/cpp/flutter_accessibility.h +++ b/shell/platform/common/cpp/flutter_platform_node_delegate.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_FLUTTER_ACCESSIBILITY_H_ -#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_FLUTTER_ACCESSIBILITY_H_ +#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_FLUTTER_PLATFORM_NODE_DELEGATE_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_FLUTTER_PLATFORM_NODE_DELEGATE_H_ #include "flutter/shell/platform/embedder/embedder.h" @@ -15,9 +15,9 @@ namespace flutter { class AccessibilityBridge; //------------------------------------------------------------------------------ -/// The accessibility node to be used in accessibility bridge. This class is -/// responsible for providing native accessibility object with appropriate -/// information, such as accessibility label/value/bounds. +/// The accessibility node delegate to be used in accessibility bridge. This +/// class is responsible for providing native accessibility object with +/// appropriate information, such as accessibility label/value/bounds. /// /// While most methods have default implementations and are ready to be used /// as-is, the subclasses must override the GetNativeViewAccessible to return @@ -28,12 +28,12 @@ class AccessibilityBridge; /// For desktop platforms, subclasses also need to override the GetBoundsRect /// to apply window-to-screen transform. /// -/// Lastly, each platform needs to implement the FlutterAccessibility::Create +/// Lastly, each platform needs to implement the FlutterPlatformNodeDelegate::Create /// static method to inject its sublcass into accessibility bridge. -class FlutterAccessibility : public ui::AXPlatformNodeDelegateBase { +class FlutterPlatformNodeDelegate : public ui::AXPlatformNodeDelegateBase { public: - FlutterAccessibility(); - ~FlutterAccessibility() override; + FlutterPlatformNodeDelegate(); + ~FlutterPlatformNodeDelegate() override; //------------------------------------------------------------------------------ /// @brief Gets the accessibility bridge to which this accessibility node @@ -69,4 +69,4 @@ class FlutterAccessibility : public ui::AXPlatformNodeDelegateBase { } // namespace flutter -#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_FLUTTER_ACCESSIBILITY_H_ +#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_FLUTTER_PLATFORM_NODE_DELEGATE_H_ diff --git a/shell/platform/common/cpp/flutter_accessibility_unittests.cc b/shell/platform/common/cpp/flutter_platform_node_delegate_unittests.cc similarity index 93% rename from shell/platform/common/cpp/flutter_accessibility_unittests.cc rename to shell/platform/common/cpp/flutter_platform_node_delegate_unittests.cc index de2d7d8329ca0..c22251c8df487 100644 --- a/shell/platform/common/cpp/flutter_accessibility_unittests.cc +++ b/shell/platform/common/cpp/flutter_platform_node_delegate_unittests.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "flutter_accessibility.h" +#include "flutter_platform_node_delegate.h" #include "flutter/third_party/accessibility/ax/ax_action_data.h" #include "gtest/gtest.h" @@ -34,7 +34,7 @@ TEST(FlutterAccessibilityTest, canPerfomActions) { bridge.CommitUpdates(); - auto accessibility = bridge.GetFlutterAccessibilityFromID(0).lock(); + auto accessibility = bridge.GetFlutterPlatformNodeDelegateFromID(0).lock(); // Performs an AXAction. ui::AXActionData action_data; action_data.action = ax::mojom::Action::kDoDefault; @@ -78,7 +78,7 @@ TEST(FlutterAccessibilityTest, canGetBridge) { bridge.CommitUpdates(); - auto accessibility = bridge.GetFlutterAccessibilityFromID(0).lock(); + auto accessibility = bridge.GetFlutterPlatformNodeDelegateFromID(0).lock(); EXPECT_EQ(accessibility->GetBridge(), &bridge); } @@ -103,7 +103,7 @@ TEST(FlutterAccessibilityTest, canGetAXNode) { bridge.CommitUpdates(); - auto accessibility = bridge.GetFlutterAccessibilityFromID(0).lock(); + auto accessibility = bridge.GetFlutterPlatformNodeDelegateFromID(0).lock(); EXPECT_EQ(accessibility->GetAXNode()->data().id, 0); } @@ -139,7 +139,7 @@ TEST(FlutterAccessibilityTest, canCalculateBoundsCorrectly) { bridge.AddFlutterSemanticsNodeUpdate(&child1); bridge.CommitUpdates(); - auto child1_node = bridge.GetFlutterAccessibilityFromID(1).lock(); + auto child1_node = bridge.GetFlutterPlatformNodeDelegateFromID(1).lock(); ui::AXOffscreenResult result; gfx::Rect bounds = child1_node->GetBoundsRect(ui::AXCoordinateSystem::kScreenDIPs, @@ -183,7 +183,7 @@ TEST(FlutterAccessibilityTest, canCalculateOffScreenBoundsCorrectly) { bridge.AddFlutterSemanticsNodeUpdate(&child1); bridge.CommitUpdates(); - auto child1_node = bridge.GetFlutterAccessibilityFromID(1).lock(); + auto child1_node = bridge.GetFlutterPlatformNodeDelegateFromID(1).lock(); ui::AXOffscreenResult result; gfx::Rect bounds = child1_node->GetBoundsRect(ui::AXCoordinateSystem::kScreenDIPs, diff --git a/shell/platform/common/cpp/test_accessibility_bridge.cc b/shell/platform/common/cpp/test_accessibility_bridge.cc index dd4916e9fe7fd..c592119132071 100644 --- a/shell/platform/common/cpp/test_accessibility_bridge.cc +++ b/shell/platform/common/cpp/test_accessibility_bridge.cc @@ -6,19 +6,18 @@ namespace flutter { -std::unique_ptr +std::unique_ptr TestAccessibilityBridgeDelegate::CreateFlutterAccessibility() { - return std::make_unique(); + return std::make_unique(); }; void TestAccessibilityBridgeDelegate::OnAccessibilityEvent( - ui::AXEventGenerator::TargetedEvent targeted_event, - AccessibilityBridge* bridge) { + ui::AXEventGenerator::TargetedEvent targeted_event) { accessibilitiy_events.push_back(targeted_event); } void TestAccessibilityBridgeDelegate::DispatchAccessibilityAction( - uint16_t target, + ui::AXNode::AXID target, FlutterSemanticsAction action, std::unique_ptr data, size_t data_size) { diff --git a/shell/platform/common/cpp/test_accessibility_bridge.h b/shell/platform/common/cpp/test_accessibility_bridge.h index 5d6a76ea2b74a..6e24b461cf180 100644 --- a/shell/platform/common/cpp/test_accessibility_bridge.h +++ b/shell/platform/common/cpp/test_accessibility_bridge.h @@ -14,13 +14,12 @@ class TestAccessibilityBridgeDelegate public: TestAccessibilityBridgeDelegate() = default; - void OnAccessibilityEvent(ui::AXEventGenerator::TargetedEvent targeted_event, - AccessibilityBridge* bridge) override; - void DispatchAccessibilityAction(uint16_t target, + void OnAccessibilityEvent(ui::AXEventGenerator::TargetedEvent targeted_event) override; + void DispatchAccessibilityAction(ui::AXNode::AXID target, FlutterSemanticsAction action, std::unique_ptr data, size_t data_size) override; - std::unique_ptr CreateFlutterAccessibility(); + std::unique_ptr CreateFlutterAccessibility(); std::vector accessibilitiy_events; std::vector performed_actions; From 262d47dc5ef6efe727502cf6f4ff5423ce2c3088 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 11 Jan 2021 15:07:10 -0800 Subject: [PATCH 11/19] update comments --- .../common/cpp/accessibility_bridge.cc | 9 ++-- .../common/cpp/accessibility_bridge.h | 48 +++++++++++++------ .../cpp/flutter_platform_node_delegate.cc | 3 +- .../cpp/flutter_platform_node_delegate.h | 3 -- .../common/cpp/test_accessibility_bridge.h | 3 +- 5 files changed, 43 insertions(+), 23 deletions(-) diff --git a/shell/platform/common/cpp/accessibility_bridge.cc b/shell/platform/common/cpp/accessibility_bridge.cc index 969163a59fa0a..ae5c3a842f34b 100644 --- a/shell/platform/common/cpp/accessibility_bridge.cc +++ b/shell/platform/common/cpp/accessibility_bridge.cc @@ -89,7 +89,8 @@ void AccessibilityBridge::CommitUpdates() { } std::weak_ptr -AccessibilityBridge::GetFlutterPlatformNodeDelegateFromID(ui::AXNode::AXID id) const { +AccessibilityBridge::GetFlutterPlatformNodeDelegateFromID( + ui::AXNode::AXID id) const { const auto iter = id_wrapper_map_.find(id); if (iter != id_wrapper_map_.end()) return iter->second; @@ -99,7 +100,8 @@ AccessibilityBridge::GetFlutterPlatformNodeDelegateFromID(ui::AXNode::AXID id) c void AccessibilityBridge::SetFocusedNode(ui::AXNode::AXID node_id) { if (last_focused_node_ != node_id) { - auto last_focused_child = GetFlutterPlatformNodeDelegateFromID(last_focused_node_); + auto last_focused_child = + GetFlutterPlatformNodeDelegateFromID(last_focused_node_); if (!last_focused_child.expired()) { delegate_->DispatchAccessibilityAction( last_focused_node_, @@ -139,7 +141,8 @@ void AccessibilityBridge::OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) { id_wrapper_map_[node->id()]->Init(this, node); } -void AccessibilityBridge::OnNodeDeleted(ui::AXTree* tree, ui::AXNode::AXID node_id) { +void AccessibilityBridge::OnNodeDeleted(ui::AXTree* tree, + ui::AXNode::AXID node_id) { BASE_DCHECK(node_id != ui::AXNode::kInvalidAXID); if (id_wrapper_map_.find(node_id) != id_wrapper_map_.end()) { id_wrapper_map_.erase(node_id); diff --git a/shell/platform/common/cpp/accessibility_bridge.h b/shell/platform/common/cpp/accessibility_bridge.h index c0ebc07f88d17..3cd400e32de4c 100644 --- a/shell/platform/common/cpp/accessibility_bridge.h +++ b/shell/platform/common/cpp/accessibility_bridge.h @@ -23,13 +23,26 @@ namespace flutter { /// semantics updates from the embedder API and produces an accessibility tree /// in the native format. /// +/// The bridge creates an AXTree to hold the semantics data that comes from +/// Flutter semantics updates. The tree holds AXNode[s] which contain the +/// semantics information for semantics node. The AXTree ressemble the Flutter +/// semantics tree in the Flutter framework. The bridge also uses +/// FlutterPlatformNodeDelegate to wrap each AXNode in order to provide +/// an accessibility tree in the native format. +/// +/// This class takes in a AccessibilityBridgeDelegate instance and is in charge +/// of its lifecycle. The delegate are used to handle the accessibility events +/// and actions. +/// /// To use this class, you must provide your own implementation of /// FlutterPlatformNodeDelegate and AccessibilityBridgeDelegate. class AccessibilityBridge : public ui::AXTreeObserver { public: //----------------------------------------------------------------------------- - /// Delegate to handle accessibility event and route accessibility action - /// back to the Flutter framework. + /// Delegate to handle requests from the accessibility bridge. The requests + /// include sending accessibility event to native accessibility system, + /// routing accessibility action to the Flutter framework, and creating + /// platform specific FlutterPlatformNodeDelegate. /// /// The accessibility events are generated when accessibility tree changes. /// These events must be sent to the native accessibility system through @@ -39,6 +52,10 @@ class AccessibilityBridge : public ui::AXTreeObserver { /// The accessibility actions are generated by the native accessibility system /// when users interacted with the assistive technologies. Those actions /// needed to be sent to the Flutter framework. + /// + /// Each platform needs to implement the FlutterPlatformNodeDelegate and + /// returns its platform specific instance of FlutterPlatformNodeDelegate + /// in this delegate. class AccessibilityBridgeDelegate { public: virtual ~AccessibilityBridgeDelegate() = default; @@ -74,10 +91,10 @@ class AccessibilityBridge : public ui::AXTreeObserver { //--------------------------------------------------------------------------- /// @brief Creates a platform specific FlutterPlatformNodeDelegate. - /// Ownership passes to the caller. This method will be called by - /// accessibility bridge when it creates accessibility node. - /// Each platform needs to implement this method in order to - /// inject its subclass into the accessibility bridge. + /// Ownership passes to the caller. This method will be called + /// by accessibility bridge whenever a new AXNode is created in + /// AXTree. Each platform needs to implement this method in + /// order to inject its subclass into the accessibility bridge. virtual std::unique_ptr CreateFlutterAccessibility() = 0; }; @@ -111,12 +128,12 @@ class AccessibilityBridge : public ui::AXTreeObserver { //------------------------------------------------------------------------------ /// @brief Flushes the pending updates and applies them to this - /// accessibility bridge. Calling this with no pending updates does - /// nothing, and callers should call this method at the end of an - /// automic batch to avoid leaving the tree in a unstable state. - /// For example if a node reparents from A to B, callers should - /// only call this method when both removal from A and addition - /// to B are in the pending updates. + /// accessibility bridge. Calling this with no pending updates + /// does nothing, and callers should call this method at the end + /// of an automic batch to avoid leaving the tree in a unstable + /// state. For example if a node reparents from A to B, callers + /// should only call this method when both removal from A and + /// addition to B are in the pending updates. void CommitUpdates(); //------------------------------------------------------------------------------ @@ -125,8 +142,8 @@ class AccessibilityBridge : public ui::AXTreeObserver { /// /// @param[in] id The id of the flutter accessibility node you want /// to retrieve. - std::weak_ptr GetFlutterPlatformNodeDelegateFromID( - ui::AXNode::AXID id) const; + std::weak_ptr + GetFlutterPlatformNodeDelegateFromID(ui::AXNode::AXID id) const; //------------------------------------------------------------------------------ /// @brief Update the currently focused flutter accessibility node. @@ -193,7 +210,8 @@ class AccessibilityBridge : public ui::AXTreeObserver { std::string hint; } SemanticsCustomAction; - std::unordered_map> + std::unordered_map> id_wrapper_map_; ui::AXTree tree_; ui::AXEventGenerator event_generator_; diff --git a/shell/platform/common/cpp/flutter_platform_node_delegate.cc b/shell/platform/common/cpp/flutter_platform_node_delegate.cc index acea91c843af5..4d216ee7a0284 100644 --- a/shell/platform/common/cpp/flutter_platform_node_delegate.cc +++ b/shell/platform/common/cpp/flutter_platform_node_delegate.cc @@ -15,7 +15,8 @@ FlutterPlatformNodeDelegate::FlutterPlatformNodeDelegate() = default; FlutterPlatformNodeDelegate::~FlutterPlatformNodeDelegate() = default; -void FlutterPlatformNodeDelegate::Init(AccessibilityBridge* bridge, ui::AXNode* node) { +void FlutterPlatformNodeDelegate::Init(AccessibilityBridge* bridge, + ui::AXNode* node) { bridge_ = bridge; ax_node_ = node; } diff --git a/shell/platform/common/cpp/flutter_platform_node_delegate.h b/shell/platform/common/cpp/flutter_platform_node_delegate.h index 4e7f8ea662d2a..b0adb0c114472 100644 --- a/shell/platform/common/cpp/flutter_platform_node_delegate.h +++ b/shell/platform/common/cpp/flutter_platform_node_delegate.h @@ -27,9 +27,6 @@ class AccessibilityBridge; /// /// For desktop platforms, subclasses also need to override the GetBoundsRect /// to apply window-to-screen transform. -/// -/// Lastly, each platform needs to implement the FlutterPlatformNodeDelegate::Create -/// static method to inject its sublcass into accessibility bridge. class FlutterPlatformNodeDelegate : public ui::AXPlatformNodeDelegateBase { public: FlutterPlatformNodeDelegate(); diff --git a/shell/platform/common/cpp/test_accessibility_bridge.h b/shell/platform/common/cpp/test_accessibility_bridge.h index 6e24b461cf180..63df0f992f5e7 100644 --- a/shell/platform/common/cpp/test_accessibility_bridge.h +++ b/shell/platform/common/cpp/test_accessibility_bridge.h @@ -14,7 +14,8 @@ class TestAccessibilityBridgeDelegate public: TestAccessibilityBridgeDelegate() = default; - void OnAccessibilityEvent(ui::AXEventGenerator::TargetedEvent targeted_event) override; + void OnAccessibilityEvent( + ui::AXEventGenerator::TargetedEvent targeted_event) override; void DispatchAccessibilityAction(ui::AXNode::AXID target, FlutterSemanticsAction action, std::unique_ptr data, From 077a7c79770b2846b6cefb41e7d653f19c87fb07 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 11 Jan 2021 16:34:03 -0800 Subject: [PATCH 12/19] fix lint --- shell/platform/common/cpp/accessibility_bridge.cc | 10 ++++++---- third_party/accessibility/gfx/native_widget_types.h | 7 +++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/shell/platform/common/cpp/accessibility_bridge.cc b/shell/platform/common/cpp/accessibility_bridge.cc index ae5c3a842f34b..989e97fbd603c 100644 --- a/shell/platform/common/cpp/accessibility_bridge.cc +++ b/shell/platform/common/cpp/accessibility_bridge.cc @@ -23,12 +23,12 @@ AccessibilityBridge::AccessibilityBridge( std::unique_ptr delegate) : delegate_(std::move(delegate)) { event_generator_.SetTree(&tree_); - tree_.AddObserver((ui::AXTreeObserver*)this); + tree_.AddObserver(static_cast(this)); } AccessibilityBridge::~AccessibilityBridge() { event_generator_.ReleaseTree(); - tree_.RemoveObserver((ui::AXTreeObserver*)this); + tree_.RemoveObserver(static_cast(this)); } void AccessibilityBridge::AddFlutterSemanticsNodeUpdate( @@ -80,8 +80,9 @@ void AccessibilityBridge::CommitUpdates() { for (const auto& targeted_event : event_generator_) { auto event_target = GetFlutterPlatformNodeDelegateFromID(targeted_event.node->id()); - if (event_target.expired()) + if (event_target.expired()) { continue; + } delegate_->OnAccessibilityEvent(targeted_event); } @@ -92,8 +93,9 @@ std::weak_ptr AccessibilityBridge::GetFlutterPlatformNodeDelegateFromID( ui::AXNode::AXID id) const { const auto iter = id_wrapper_map_.find(id); - if (iter != id_wrapper_map_.end()) + if (iter != id_wrapper_map_.end()) { return iter->second; + } return std::weak_ptr(); } diff --git a/third_party/accessibility/gfx/native_widget_types.h b/third_party/accessibility/gfx/native_widget_types.h index 8c5479755dd42..622e723c6bba5 100644 --- a/third_party/accessibility/gfx/native_widget_types.h +++ b/third_party/accessibility/gfx/native_widget_types.h @@ -192,7 +192,8 @@ typedef base::android::ScopedJavaGlobalRef NativeEvent; constexpr NativeView kNullNativeView = nullptr; constexpr NativeWindow kNullNativeWindow = nullptr; #else -#error Unknown build environment. +// for unknown platform. +typedef void* NativeCursor; #endif #if defined(OS_WIN) @@ -246,7 +247,9 @@ constexpr AcceleratedWidget kNullAcceleratedWidget = 0; typedef uint32_t AcceleratedWidget; constexpr AcceleratedWidget kNullAcceleratedWidget = 0; #else -#error unknown platform +// for unknown platform. +typedef void* AcceleratedWidget; +constexpr AcceleratedWidget kNullAcceleratedWidget= nullptr; #endif } // namespace gfx From 67b6457ffa81b3046fa129fc690a838db23c849c Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 11 Jan 2021 16:49:28 -0800 Subject: [PATCH 13/19] format --- third_party/accessibility/gfx/native_widget_types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/accessibility/gfx/native_widget_types.h b/third_party/accessibility/gfx/native_widget_types.h index 622e723c6bba5..1fcd30ec88137 100644 --- a/third_party/accessibility/gfx/native_widget_types.h +++ b/third_party/accessibility/gfx/native_widget_types.h @@ -249,7 +249,7 @@ constexpr AcceleratedWidget kNullAcceleratedWidget = 0; #else // for unknown platform. typedef void* AcceleratedWidget; -constexpr AcceleratedWidget kNullAcceleratedWidget= nullptr; +constexpr AcceleratedWidget kNullAcceleratedWidget = nullptr; #endif } // namespace gfx From 1f4662539f3fe42c213ccc55538dbb6ecf560558 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Tue, 12 Jan 2021 11:07:52 -0800 Subject: [PATCH 14/19] rename flutter accessibility --- shell/platform/common/cpp/accessibility_bridge.cc | 2 +- shell/platform/common/cpp/accessibility_bridge.h | 2 +- .../cpp/flutter_platform_node_delegate_unittests.cc | 10 +++++----- shell/platform/common/cpp/test_accessibility_bridge.cc | 2 +- shell/platform/common/cpp/test_accessibility_bridge.h | 3 ++- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/shell/platform/common/cpp/accessibility_bridge.cc b/shell/platform/common/cpp/accessibility_bridge.cc index 989e97fbd603c..822af9fd15a79 100644 --- a/shell/platform/common/cpp/accessibility_bridge.cc +++ b/shell/platform/common/cpp/accessibility_bridge.cc @@ -139,7 +139,7 @@ void AccessibilityBridge::OnRoleChanged(ui::AXTree* tree, void AccessibilityBridge::OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) { BASE_DCHECK(node); - id_wrapper_map_[node->id()] = delegate_->CreateFlutterAccessibility(); + id_wrapper_map_[node->id()] = delegate_->CreateFlutterPlatformNodeDelegate(); id_wrapper_map_[node->id()]->Init(this, node); } diff --git a/shell/platform/common/cpp/accessibility_bridge.h b/shell/platform/common/cpp/accessibility_bridge.h index 3cd400e32de4c..4ae8efbe6fd40 100644 --- a/shell/platform/common/cpp/accessibility_bridge.h +++ b/shell/platform/common/cpp/accessibility_bridge.h @@ -96,7 +96,7 @@ class AccessibilityBridge : public ui::AXTreeObserver { /// AXTree. Each platform needs to implement this method in /// order to inject its subclass into the accessibility bridge. virtual std::unique_ptr - CreateFlutterAccessibility() = 0; + CreateFlutterPlatformNodeDelegate() = 0; }; //----------------------------------------------------------------------------- /// @brief Creates a new instance of a accessibility bridge. diff --git a/shell/platform/common/cpp/flutter_platform_node_delegate_unittests.cc b/shell/platform/common/cpp/flutter_platform_node_delegate_unittests.cc index c22251c8df487..1939c420fd074 100644 --- a/shell/platform/common/cpp/flutter_platform_node_delegate_unittests.cc +++ b/shell/platform/common/cpp/flutter_platform_node_delegate_unittests.cc @@ -12,7 +12,7 @@ namespace flutter { namespace testing { -TEST(FlutterAccessibilityTest, canPerfomActions) { +TEST(FlutterPlatformNodeDelegateTest, canPerfomActions) { TestAccessibilityBridgeDelegate* delegate = new TestAccessibilityBridgeDelegate(); std::unique_ptr ptr(delegate); @@ -57,7 +57,7 @@ TEST(FlutterAccessibilityTest, canPerfomActions) { FlutterSemanticsAction::kFlutterSemanticsActionShowOnScreen); } -TEST(FlutterAccessibilityTest, canGetBridge) { +TEST(FlutterPlatformNodeDelegateTest, canGetBridge) { // Set up a flutter accessibility node. AccessibilityBridge bridge( std::make_unique()); @@ -82,7 +82,7 @@ TEST(FlutterAccessibilityTest, canGetBridge) { EXPECT_EQ(accessibility->GetBridge(), &bridge); } -TEST(FlutterAccessibilityTest, canGetAXNode) { +TEST(FlutterPlatformNodeDelegateTest, canGetAXNode) { // Set up a flutter accessibility node. AccessibilityBridge bridge( std::make_unique()); @@ -107,7 +107,7 @@ TEST(FlutterAccessibilityTest, canGetAXNode) { EXPECT_EQ(accessibility->GetAXNode()->data().id, 0); } -TEST(FlutterAccessibilityTest, canCalculateBoundsCorrectly) { +TEST(FlutterPlatformNodeDelegateTest, canCalculateBoundsCorrectly) { AccessibilityBridge bridge( std::make_unique()); FlutterSemanticsNode root; @@ -151,7 +151,7 @@ TEST(FlutterAccessibilityTest, canCalculateBoundsCorrectly) { EXPECT_EQ(result, ui::AXOffscreenResult::kOnscreen); } -TEST(FlutterAccessibilityTest, canCalculateOffScreenBoundsCorrectly) { +TEST(FlutterPlatformNodeDelegateTest, canCalculateOffScreenBoundsCorrectly) { AccessibilityBridge bridge( std::make_unique()); FlutterSemanticsNode root; diff --git a/shell/platform/common/cpp/test_accessibility_bridge.cc b/shell/platform/common/cpp/test_accessibility_bridge.cc index c592119132071..fb32107007f53 100644 --- a/shell/platform/common/cpp/test_accessibility_bridge.cc +++ b/shell/platform/common/cpp/test_accessibility_bridge.cc @@ -7,7 +7,7 @@ namespace flutter { std::unique_ptr -TestAccessibilityBridgeDelegate::CreateFlutterAccessibility() { +TestAccessibilityBridgeDelegate::CreateFlutterPlatformNodeDelegate() { return std::make_unique(); }; diff --git a/shell/platform/common/cpp/test_accessibility_bridge.h b/shell/platform/common/cpp/test_accessibility_bridge.h index 63df0f992f5e7..79e67ffbe950a 100644 --- a/shell/platform/common/cpp/test_accessibility_bridge.h +++ b/shell/platform/common/cpp/test_accessibility_bridge.h @@ -20,7 +20,8 @@ class TestAccessibilityBridgeDelegate FlutterSemanticsAction action, std::unique_ptr data, size_t data_size) override; - std::unique_ptr CreateFlutterAccessibility(); + std::unique_ptr + CreateFlutterPlatformNodeDelegate(); std::vector accessibilitiy_events; std::vector performed_actions; From 8fdeffb31afd00400bf8770abc5156f2fe48b9dd Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Tue, 12 Jan 2021 15:56:40 -0800 Subject: [PATCH 15/19] fixes selectable text --- shell/platform/common/cpp/accessibility_bridge.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shell/platform/common/cpp/accessibility_bridge.cc b/shell/platform/common/cpp/accessibility_bridge.cc index 822af9fd15a79..e48d82cec8a9f 100644 --- a/shell/platform/common/cpp/accessibility_bridge.cc +++ b/shell/platform/common/cpp/accessibility_bridge.cc @@ -219,7 +219,8 @@ void AccessibilityBridge::SetRoleFromFlutterUpdate(ui::AXNodeData& node_data, node_data.role = ax::mojom::Role::kButton; return; } - if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField) { + if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField && + !(flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly)) { node_data.role = ax::mojom::Role::kTextField; return; } From 8d31ac764d5271dd03f178e08a8a533947b71f12 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Wed, 13 Jan 2021 15:04:53 -0800 Subject: [PATCH 16/19] update --- .../common/cpp/accessibility_bridge.cc | 68 +++++++----- .../common/cpp/accessibility_bridge.h | 105 ++++++++++-------- .../cpp/flutter_platform_node_delegate.cc | 51 +++------ .../cpp/flutter_platform_node_delegate.h | 100 ++++++++++++++--- ...lutter_platform_node_delegate_unittests.cc | 27 +---- .../common/cpp/test_accessibility_bridge.cc | 5 +- .../common/cpp/test_accessibility_bridge.h | 5 +- 7 files changed, 205 insertions(+), 156 deletions(-) diff --git a/shell/platform/common/cpp/accessibility_bridge.cc b/shell/platform/common/cpp/accessibility_bridge.cc index e48d82cec8a9f..73f79a640dcaf 100644 --- a/shell/platform/common/cpp/accessibility_bridge.cc +++ b/shell/platform/common/cpp/accessibility_bridge.cc @@ -91,7 +91,7 @@ void AccessibilityBridge::CommitUpdates() { std::weak_ptr AccessibilityBridge::GetFlutterPlatformNodeDelegateFromID( - ui::AXNode::AXID id) const { + AccessibilityNodeId id) const { const auto iter = id_wrapper_map_.find(id); if (iter != id_wrapper_map_.end()) { return iter->second; @@ -100,25 +100,6 @@ AccessibilityBridge::GetFlutterPlatformNodeDelegateFromID( return std::weak_ptr(); } -void AccessibilityBridge::SetFocusedNode(ui::AXNode::AXID node_id) { - if (last_focused_node_ != node_id) { - auto last_focused_child = - GetFlutterPlatformNodeDelegateFromID(last_focused_node_); - if (!last_focused_child.expired()) { - delegate_->DispatchAccessibilityAction( - last_focused_node_, - FlutterSemanticsAction:: - kFlutterSemanticsActionDidLoseAccessibilityFocus, - nullptr, 0); - } - last_focused_node_ = node_id; - } -} - -ui::AXNode::AXID AccessibilityBridge::GetLastFocusedNode() { - return last_focused_node_; -} - const ui::AXTreeData& AccessibilityBridge::GetAXTreeData() const { return tree_.data(); } @@ -144,7 +125,7 @@ void AccessibilityBridge::OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) { } void AccessibilityBridge::OnNodeDeleted(ui::AXTree* tree, - ui::AXNode::AXID node_id) { + AccessibilityNodeId node_id) { BASE_DCHECK(node_id != ui::AXNode::kInvalidAXID); if (id_wrapper_map_.find(node_id) != id_wrapper_map_.end()) { id_wrapper_map_.erase(node_id); @@ -161,7 +142,7 @@ void AccessibilityBridge::OnAtomicUpdateFinished( for (const auto& change : changes) { ui::AXNode* node = change.node; const ui::AXNodeData& data = node->data(); - ui::AXNode::AXID offset_container_id = -1; + AccessibilityNodeId offset_container_id = -1; if (node->parent()) { offset_container_id = node->parent()->id(); } @@ -510,19 +491,46 @@ AccessibilityBridge::FromFlutterSemanticsCustomAction( return result; } -gfx::RectF AccessibilityBridge::RelativeToGlobalBounds(ui::AXNode* node, - bool* offscreen, +void AccessibilityBridge::SetLastFocusedId(AccessibilityNodeId node_id) { + if (last_focused_id_ != node_id) { + auto last_focused_child = + GetFlutterPlatformNodeDelegateFromID(last_focused_id_); + if (!last_focused_child.expired()) { + delegate_->DispatchAccessibilityAction( + last_focused_id_, + FlutterSemanticsAction:: + kFlutterSemanticsActionDidLoseAccessibilityFocus, + {}); + } + last_focused_id_ = node_id; + } +} + +AccessibilityNodeId AccessibilityBridge::GetLastFocusedId() { + return last_focused_id_; +} + +gfx::NativeViewAccessible AccessibilityBridge::GetNativeAccessibleFromId( + AccessibilityNodeId id) { + auto platform_node_delegate = GetFlutterPlatformNodeDelegateFromID(id).lock(); + if (!platform_node_delegate) { + return nullptr; + } + return platform_node_delegate->GetNativeViewAccessible(); +} + +gfx::RectF AccessibilityBridge::RelativeToGlobalBounds(const ui::AXNode* node, + bool& offscreen, bool clip_bounds) { - return tree_.RelativeToTreeBounds(node, gfx::RectF(), offscreen, clip_bounds); + return tree_.RelativeToTreeBounds(node, gfx::RectF(), &offscreen, + clip_bounds); } void AccessibilityBridge::DispatchAccessibilityAction( - ui::AXNode::AXID target, + AccessibilityNodeId target, FlutterSemanticsAction action, - std::unique_ptr data, - size_t data_size) { - delegate_->DispatchAccessibilityAction(target, action, std::move(data), - data_size); + std::vector data) { + delegate_->DispatchAccessibilityAction(target, action, data); } } // namespace flutter diff --git a/shell/platform/common/cpp/accessibility_bridge.h b/shell/platform/common/cpp/accessibility_bridge.h index 4ae8efbe6fd40..e2a3bca4ade6f 100644 --- a/shell/platform/common/cpp/accessibility_bridge.h +++ b/shell/platform/common/cpp/accessibility_bridge.h @@ -36,7 +36,8 @@ namespace flutter { /// /// To use this class, you must provide your own implementation of /// FlutterPlatformNodeDelegate and AccessibilityBridgeDelegate. -class AccessibilityBridge : public ui::AXTreeObserver { +class AccessibilityBridge : private ui::AXTreeObserver, + private FlutterPlatformNodeDelegate::OwnerBridge { public: //----------------------------------------------------------------------------- /// Delegate to handle requests from the accessibility bridge. The requests @@ -83,11 +84,9 @@ class AccessibilityBridge : public ui::AXTreeObserver { /// @param[in] action The generated flutter semantics action. /// @param[in] data Additional data associated with the /// action. - /// @param[in] data_size The length of the additional data. - virtual void DispatchAccessibilityAction(ui::AXNode::AXID target, + virtual void DispatchAccessibilityAction(AccessibilityNodeId target, FlutterSemanticsAction action, - std::unique_ptr data, - size_t data_size) = 0; + std::vector data) = 0; //--------------------------------------------------------------------------- /// @brief Creates a platform specific FlutterPlatformNodeDelegate. @@ -122,7 +121,7 @@ class AccessibilityBridge : public ui::AXTreeObserver { /// CommitUpdates(). /// /// @param[in] action A pointer to the custom semantics action - /// update. + /// update. void AddFlutterSemanticsCustomActionUpdate( const FlutterSemanticsCustomAction* action); @@ -130,7 +129,7 @@ class AccessibilityBridge : public ui::AXTreeObserver { /// @brief Flushes the pending updates and applies them to this /// accessibility bridge. Calling this with no pending updates /// does nothing, and callers should call this method at the end - /// of an automic batch to avoid leaving the tree in a unstable + /// of an atomic batch to avoid leaving the tree in a unstable /// state. For example if a node reparents from A to B, callers /// should only call this method when both removal from A and /// addition to B are in the pending updates. @@ -138,43 +137,21 @@ class AccessibilityBridge : public ui::AXTreeObserver { //------------------------------------------------------------------------------ /// @brief Get the flutter platform node delegate with the given id from - /// this accessibility bridge. + /// this accessibility bridge. Returns expired weak_ptr if the + /// delegate associated with the id does not exist or has been + /// removed from the accessibility tree. /// /// @param[in] id The id of the flutter accessibility node you want /// to retrieve. std::weak_ptr - GetFlutterPlatformNodeDelegateFromID(ui::AXNode::AXID id) const; + GetFlutterPlatformNodeDelegateFromID(AccessibilityNodeId id) const; //------------------------------------------------------------------------------ - /// @brief Update the currently focused flutter accessibility node. - /// - /// @param[in] id The id of the currently focused flutter - /// accessibility node. - void SetFocusedNode(ui::AXNode::AXID node_id); - - //------------------------------------------------------------------------------ - /// @brief Get the last focused node. - ui::AXNode::AXID GetLastFocusedNode(); - - //------------------------------------------------------------------------------ - /// @brief Get the ax tree data. + /// @brief Get the ax tree data from this accessibility bridge. The tree + /// data contains information such as the id of the node that + /// has the keyboard focus or the text selection range. const ui::AXTreeData& GetAXTreeData() const; - // ui::AXTreeObserver implementation. - void OnNodeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override; - void OnSubtreeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override; - void OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) override; - void OnNodeDeleted(ui::AXTree* tree, ui::AXNode::AXID node_id) override; - void OnNodeReparented(ui::AXTree* tree, ui::AXNode* node) override; - void OnRoleChanged(ui::AXTree* tree, - ui::AXNode* node, - ax::mojom::Role old_role, - ax::mojom::Role new_role) override; - void OnAtomicUpdateFinished( - ui::AXTree* tree, - bool root_changed, - const std::vector& changes) override; - private: // See FlutterSemanticsNode in embedder.h typedef struct { @@ -210,7 +187,7 @@ class AccessibilityBridge : public ui::AXTreeObserver { std::string hint; } SemanticsCustomAction; - std::unordered_map> id_wrapper_map_; ui::AXTree tree_; @@ -218,7 +195,7 @@ class AccessibilityBridge : public ui::AXTreeObserver { std::unordered_map _pending_semantics_node_updates; std::unordered_map _pending_semantics_custom_action_updates; - ui::AXNode::AXID last_focused_node_ = ui::AXNode::kInvalidAXID; + AccessibilityNodeId last_focused_id_ = ui::AXNode::kInvalidAXID; std::unique_ptr delegate_; void InitAXTree(const ui::AXTreeUpdate& initial_state); @@ -248,15 +225,53 @@ class AccessibilityBridge : public ui::AXTreeObserver { const FlutterSemanticsNode* flutter_node); SemanticsCustomAction FromFlutterSemanticsCustomAction( const FlutterSemanticsCustomAction* flutter_custom_action); - void DispatchAccessibilityAction(ui::AXNode::AXID target, + + // |AXTreeObserver| + void OnNodeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override; + + // |AXTreeObserver| + void OnSubtreeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override; + + // |AXTreeObserver| + void OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) override; + + // |AXTreeObserver| + void OnNodeDeleted(ui::AXTree* tree, AccessibilityNodeId node_id) override; + + // |AXTreeObserver| + void OnNodeReparented(ui::AXTree* tree, ui::AXNode* node) override; + + // |AXTreeObserver| + void OnRoleChanged(ui::AXTree* tree, + ui::AXNode* node, + ax::mojom::Role old_role, + ax::mojom::Role new_role) override; + + // |AXTreeObserver| + void OnAtomicUpdateFinished( + ui::AXTree* tree, + bool root_changed, + const std::vector& changes) override; + + // |FlutterPlatformNodeDelegate::OwnerBridge| + void SetLastFocusedId(AccessibilityNodeId node_id) override; + + // |FlutterPlatformNodeDelegate::OwnerBridge| + AccessibilityNodeId GetLastFocusedId() override; + + // |FlutterPlatformNodeDelegate::OwnerBridge| + gfx::NativeViewAccessible GetNativeAccessibleFromId( + AccessibilityNodeId id) override; + + // |FlutterPlatformNodeDelegate::OwnerBridge| + void DispatchAccessibilityAction(AccessibilityNodeId target, FlutterSemanticsAction action, - std::unique_ptr data, - size_t data_size); - gfx::RectF RelativeToGlobalBounds(ui::AXNode* node, - bool* offscreen, - bool clip_bounds); + std::vector data) override; - friend class FlutterPlatformNodeDelegate; + // |FlutterPlatformNodeDelegate::OwnerBridge| + gfx::RectF RelativeToGlobalBounds(const ui::AXNode* node, + bool& offscreen, + bool clip_bounds) override; BASE_DISALLOW_COPY_AND_ASSIGN(AccessibilityBridge); }; diff --git a/shell/platform/common/cpp/flutter_platform_node_delegate.cc b/shell/platform/common/cpp/flutter_platform_node_delegate.cc index 4d216ee7a0284..7a9f047ccc2ee 100644 --- a/shell/platform/common/cpp/flutter_platform_node_delegate.cc +++ b/shell/platform/common/cpp/flutter_platform_node_delegate.cc @@ -7,49 +7,41 @@ #include "flutter/third_party/accessibility/ax/ax_action_data.h" #include "flutter/third_party/accessibility/gfx/geometry/rect_conversions.h" -#include "accessibility_bridge.h" - namespace flutter { FlutterPlatformNodeDelegate::FlutterPlatformNodeDelegate() = default; FlutterPlatformNodeDelegate::~FlutterPlatformNodeDelegate() = default; -void FlutterPlatformNodeDelegate::Init(AccessibilityBridge* bridge, - ui::AXNode* node) { +void FlutterPlatformNodeDelegate::Init(OwnerBridge* bridge, ui::AXNode* node) { bridge_ = bridge; ax_node_ = node; } -AccessibilityBridge* FlutterPlatformNodeDelegate::GetBridge() const { - return bridge_; -} - ui::AXNode* FlutterPlatformNodeDelegate::GetAXNode() const { return ax_node_; } bool FlutterPlatformNodeDelegate::AccessibilityPerformAction( const ui::AXActionData& data) { - ui::AXNode::AXID target = GetAXNode()->id(); + AccessibilityNodeId target = ax_node_->id(); switch (data.action) { case ax::mojom::Action::kDoDefault: bridge_->DispatchAccessibilityAction( - target, FlutterSemanticsAction::kFlutterSemanticsActionTap, {nullptr}, - 0); + target, FlutterSemanticsAction::kFlutterSemanticsActionTap, {}); return true; case ax::mojom::Action::kFocus: - bridge_->SetFocusedNode(target); + bridge_->SetLastFocusedId(target); bridge_->DispatchAccessibilityAction( target, FlutterSemanticsAction:: kFlutterSemanticsActionDidGainAccessibilityFocus, - {nullptr}, 0); + {}); return true; case ax::mojom::Action::kScrollToMakeVisible: bridge_->DispatchAccessibilityAction( target, FlutterSemanticsAction::kFlutterSemanticsActionShowOnScreen, - {nullptr}, 0); + {}); return true; // TODO(chunhtai): support more actions. default: @@ -59,42 +51,31 @@ bool FlutterPlatformNodeDelegate::AccessibilityPerformAction( } const ui::AXNodeData& FlutterPlatformNodeDelegate::GetData() const { - return GetAXNode()->data(); + return ax_node_->data(); } gfx::NativeViewAccessible FlutterPlatformNodeDelegate::GetParent() { - if (!GetAXNode()->parent()) { + if (!ax_node_->parent()) { return nullptr; } - std::shared_ptr accessibility = - bridge_->GetFlutterPlatformNodeDelegateFromID(GetAXNode()->parent()->id()) - .lock(); - BASE_CHECK(accessibility); - return accessibility->GetNativeViewAccessible(); + return bridge_->GetNativeAccessibleFromId(ax_node_->parent()->id()); } gfx::NativeViewAccessible FlutterPlatformNodeDelegate::GetFocus() { - ui::AXNode::AXID focused_node = bridge_->GetLastFocusedNode(); - if (focused_node == ui::AXNode::kInvalidAXID) { + AccessibilityNodeId last_focused = bridge_->GetLastFocusedId(); + if (last_focused == ui::AXNode::kInvalidAXID) { return nullptr; } - std::weak_ptr focus = - bridge_->GetFlutterPlatformNodeDelegateFromID(focused_node); - auto focus_ptr = focus.lock(); - if (!focus_ptr) - return nullptr; - return focus_ptr->GetNativeViewAccessible(); + return bridge_->GetNativeAccessibleFromId(last_focused); } int FlutterPlatformNodeDelegate::GetChildCount() const { - return static_cast(GetAXNode()->GetUnignoredChildCount()); + return static_cast(ax_node_->GetUnignoredChildCount()); } gfx::NativeViewAccessible FlutterPlatformNodeDelegate::ChildAtIndex(int index) { - ui::AXNode::AXID child = GetAXNode()->GetUnignoredChildAtIndex(index)->id(); - return bridge_->GetFlutterPlatformNodeDelegateFromID(child) - .lock() - ->GetNativeViewAccessible(); + AccessibilityNodeId child = ax_node_->GetUnignoredChildAtIndex(index)->id(); + return bridge_->GetNativeAccessibleFromId(child); } gfx::Rect FlutterPlatformNodeDelegate::GetBoundsRect( @@ -106,7 +87,7 @@ gfx::Rect FlutterPlatformNodeDelegate::GetBoundsRect( clipping_behavior == ui::AXClippingBehavior::kClipped; bool offscreen = false; gfx::RectF bounds = - bridge_->RelativeToGlobalBounds(GetAXNode(), &offscreen, clip_bounds); + bridge_->RelativeToGlobalBounds(ax_node_, offscreen, clip_bounds); if (offscreen_result != nullptr) { *offscreen_result = offscreen ? ui::AXOffscreenResult::kOffscreen : ui::AXOffscreenResult::kOnscreen; diff --git a/shell/platform/common/cpp/flutter_platform_node_delegate.h b/shell/platform/common/cpp/flutter_platform_node_delegate.h index b0adb0c114472..5a40c09a35d38 100644 --- a/shell/platform/common/cpp/flutter_platform_node_delegate.h +++ b/shell/platform/common/cpp/flutter_platform_node_delegate.h @@ -12,10 +12,10 @@ namespace flutter { -class AccessibilityBridge; +typedef ui::AXNode::AXID AccessibilityNodeId; //------------------------------------------------------------------------------ -/// The accessibility node delegate to be used in accessibility bridge. This +/// The platform node delegate to be used in accessibility bridge. This /// class is responsible for providing native accessibility object with /// appropriate information, such as accessibility label/value/bounds. /// @@ -29,39 +29,111 @@ class AccessibilityBridge; /// to apply window-to-screen transform. class FlutterPlatformNodeDelegate : public ui::AXPlatformNodeDelegateBase { public: - FlutterPlatformNodeDelegate(); - ~FlutterPlatformNodeDelegate() override; + //---------------------------------------------------------------------------- + /// The required interface to be able to own the flutter platform node + /// delegate. + class OwnerBridge { + public: + ~OwnerBridge() = default; - //------------------------------------------------------------------------------ - /// @brief Gets the accessibility bridge to which this accessibility node - /// belongs. - AccessibilityBridge* GetBridge() const; + protected: + friend class FlutterPlatformNodeDelegate; - //------------------------------------------------------------------------------ - /// @brief Gets the underlying ax node for this accessibility node. - ui::AXNode* GetAXNode() const; + //--------------------------------------------------------------------------- + /// @brief Dispatch accessibility action back to the Flutter framework. + /// These actions are generated in the native accessibility + /// system when users interact with the assistive technologies. + /// For example, a + /// FlutterSemanticsAction::kFlutterSemanticsActionTap is + /// fired when user click or touch the screen. + /// + /// @param[in] target The semantics node id of the action + /// target. + /// @param[in] action The generated flutter semantics action. + /// @param[in] data Additional data associated with the + /// action. + virtual void DispatchAccessibilityAction(AccessibilityNodeId target, + FlutterSemanticsAction action, + std::vector data) = 0; + + //--------------------------------------------------------------------------- + /// @brief Get the native accessibility node with the given id. + /// + /// @param[in] id The id of the native accessibility node you + /// want to retrieve. + virtual gfx::NativeViewAccessible GetNativeAccessibleFromId( + AccessibilityNodeId id) = 0; - // ui::AXPlatformNodeDelegateBase override; + //--------------------------------------------------------------------------- + /// @brief Get the last id of the node that received accessibility + /// focus. + virtual AccessibilityNodeId GetLastFocusedId() = 0; + + //--------------------------------------------------------------------------- + /// @brief Update the id of the node that is currently foucsed by the + /// native accessibility system. + /// + /// @param[in] node_id The id of the focused node. + virtual void SetLastFocusedId(AccessibilityNodeId node_id) = 0; + + //--------------------------------------------------------------------------- + /// @brief Gets the rectangular bounds of the ax node relative to + /// global coordinate + /// + /// @param[in] node The ax node to look up. + /// @param[in] offscreen the bool reference to hold the result whether + /// the ax node is outside of its ancestors' bounds. + /// @param[in] clip_bounds whether to clip the result if the ax node cannot + /// be fully contained in its ancestors' bounds. + virtual gfx::RectF RelativeToGlobalBounds(const ui::AXNode* node, + bool& offscreen, + bool clip_bounds) = 0; + }; + + FlutterPlatformNodeDelegate(); + + // |ui::AXPlatformNodeDelegateBase| + ~FlutterPlatformNodeDelegate() override; + + // |ui::AXPlatformNodeDelegateBase| const ui::AXNodeData& GetData() const override; + + // |ui::AXPlatformNodeDelegateBase| bool AccessibilityPerformAction(const ui::AXActionData& data) override; + + // |ui::AXPlatformNodeDelegateBase| gfx::NativeViewAccessible GetParent() override; + + // |ui::AXPlatformNodeDelegateBase| gfx::NativeViewAccessible GetFocus() override; + + // |ui::AXPlatformNodeDelegateBase| int GetChildCount() const override; + + // |ui::AXPlatformNodeDelegateBase| gfx::NativeViewAccessible ChildAtIndex(int index) override; + + // |ui::AXPlatformNodeDelegateBase| gfx::Rect GetBoundsRect( const ui::AXCoordinateSystem coordinate_system, const ui::AXClippingBehavior clipping_behavior, ui::AXOffscreenResult* offscreen_result) const override; + //------------------------------------------------------------------------------ /// @brief Called only once, immediately after construction. The /// constructor doesn't take any arguments because in the Windows /// subclass we use a special function to construct a COM object. /// Subclasses must call super. - virtual void Init(AccessibilityBridge* bridge, ui::AXNode* node); + virtual void Init(OwnerBridge* bridge, ui::AXNode* node); + + protected: + //------------------------------------------------------------------------------ + /// @brief Gets the underlying ax node for this accessibility node. + ui::AXNode* GetAXNode() const; private: ui::AXNode* ax_node_; - AccessibilityBridge* bridge_; + OwnerBridge* bridge_; }; } // namespace flutter diff --git a/shell/platform/common/cpp/flutter_platform_node_delegate_unittests.cc b/shell/platform/common/cpp/flutter_platform_node_delegate_unittests.cc index 1939c420fd074..219017113ea77 100644 --- a/shell/platform/common/cpp/flutter_platform_node_delegate_unittests.cc +++ b/shell/platform/common/cpp/flutter_platform_node_delegate_unittests.cc @@ -57,31 +57,6 @@ TEST(FlutterPlatformNodeDelegateTest, canPerfomActions) { FlutterSemanticsAction::kFlutterSemanticsActionShowOnScreen); } -TEST(FlutterPlatformNodeDelegateTest, canGetBridge) { - // Set up a flutter accessibility node. - AccessibilityBridge bridge( - std::make_unique()); - FlutterSemanticsNode root; - root.id = 0; - root.flags = FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField; - root.actions = static_cast(0); - root.text_selection_base = -1; - root.text_selection_extent = -1; - root.label = "root"; - root.hint = ""; - root.value = ""; - root.increased_value = ""; - root.decreased_value = ""; - root.child_count = 0; - root.custom_accessibility_actions_count = 0; - bridge.AddFlutterSemanticsNodeUpdate(&root); - - bridge.CommitUpdates(); - - auto accessibility = bridge.GetFlutterPlatformNodeDelegateFromID(0).lock(); - EXPECT_EQ(accessibility->GetBridge(), &bridge); -} - TEST(FlutterPlatformNodeDelegateTest, canGetAXNode) { // Set up a flutter accessibility node. AccessibilityBridge bridge( @@ -104,7 +79,7 @@ TEST(FlutterPlatformNodeDelegateTest, canGetAXNode) { bridge.CommitUpdates(); auto accessibility = bridge.GetFlutterPlatformNodeDelegateFromID(0).lock(); - EXPECT_EQ(accessibility->GetAXNode()->data().id, 0); + EXPECT_EQ(accessibility->GetData().id, 0); } TEST(FlutterPlatformNodeDelegateTest, canCalculateBoundsCorrectly) { diff --git a/shell/platform/common/cpp/test_accessibility_bridge.cc b/shell/platform/common/cpp/test_accessibility_bridge.cc index fb32107007f53..22f88ea1f3289 100644 --- a/shell/platform/common/cpp/test_accessibility_bridge.cc +++ b/shell/platform/common/cpp/test_accessibility_bridge.cc @@ -17,10 +17,9 @@ void TestAccessibilityBridgeDelegate::OnAccessibilityEvent( } void TestAccessibilityBridgeDelegate::DispatchAccessibilityAction( - ui::AXNode::AXID target, + AccessibilityNodeId target, FlutterSemanticsAction action, - std::unique_ptr data, - size_t data_size) { + std::vector data) { performed_actions.push_back(action); } diff --git a/shell/platform/common/cpp/test_accessibility_bridge.h b/shell/platform/common/cpp/test_accessibility_bridge.h index 79e67ffbe950a..e0ec2b434df35 100644 --- a/shell/platform/common/cpp/test_accessibility_bridge.h +++ b/shell/platform/common/cpp/test_accessibility_bridge.h @@ -16,10 +16,9 @@ class TestAccessibilityBridgeDelegate void OnAccessibilityEvent( ui::AXEventGenerator::TargetedEvent targeted_event) override; - void DispatchAccessibilityAction(ui::AXNode::AXID target, + void DispatchAccessibilityAction(AccessibilityNodeId target, FlutterSemanticsAction action, - std::unique_ptr data, - size_t data_size) override; + std::vector data) override; std::unique_ptr CreateFlutterPlatformNodeDelegate(); From 1dafe8ff4ed63c940a279c2e6304840ad0f1a5c0 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Wed, 20 Jan 2021 10:25:43 -0800 Subject: [PATCH 17/19] addressing comments --- .../common/cpp/accessibility_bridge.cc | 29 +++++----- .../common/cpp/accessibility_bridge.h | 10 ++-- .../cpp/accessibility_bridge_unittests.cc | 53 ++++++++++--------- .../cpp/flutter_platform_node_delegate.cc | 8 +-- .../cpp/flutter_platform_node_delegate.h | 4 +- ...lutter_platform_node_delegate_unittests.cc | 46 ++++++++-------- .../accessibility/gfx/native_widget_types.h | 14 +++-- 7 files changed, 92 insertions(+), 72 deletions(-) diff --git a/shell/platform/common/cpp/accessibility_bridge.cc b/shell/platform/common/cpp/accessibility_bridge.cc index 73f79a640dcaf..20e42914bbfaf 100644 --- a/shell/platform/common/cpp/accessibility_bridge.cc +++ b/shell/platform/common/cpp/accessibility_bridge.cc @@ -33,12 +33,12 @@ AccessibilityBridge::~AccessibilityBridge() { void AccessibilityBridge::AddFlutterSemanticsNodeUpdate( const FlutterSemanticsNode* node) { - _pending_semantics_node_updates[node->id] = FromFlutterSemanticsNode(node); + pending_semantics_node_updates_[node->id] = FromFlutterSemanticsNode(node); } void AccessibilityBridge::AddFlutterSemanticsCustomActionUpdate( const FlutterSemanticsCustomAction* action) { - _pending_semantics_custom_action_updates[action->id] = + pending_semantics_custom_action_updates_[action->id] = FromFlutterSemanticsCustomAction(action); } @@ -52,13 +52,13 @@ void AccessibilityBridge::CommitUpdates() { // lists in the reversed order, this guarantees parent updates always come // before child updates. std::vector> results; - while (!_pending_semantics_node_updates.empty()) { - auto begin = _pending_semantics_node_updates.begin(); + while (!pending_semantics_node_updates_.empty()) { + auto begin = pending_semantics_node_updates_.begin(); SemanticsNode target = begin->second; std::vector sub_tree_list; GetSubTreeList(target, sub_tree_list); results.push_back(sub_tree_list); - _pending_semantics_node_updates.erase(begin); + pending_semantics_node_updates_.erase(begin); } for (size_t i = results.size(); i > 0; i--) { @@ -68,8 +68,8 @@ void AccessibilityBridge::CommitUpdates() { } tree_.Unserialize(update); - _pending_semantics_node_updates.clear(); - _pending_semantics_custom_action_updates.clear(); + pending_semantics_node_updates_.clear(); + pending_semantics_custom_action_updates_.clear(); std::string error = tree_.error(); if (!error.empty()) { @@ -121,7 +121,10 @@ void AccessibilityBridge::OnRoleChanged(ui::AXTree* tree, void AccessibilityBridge::OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) { BASE_DCHECK(node); id_wrapper_map_[node->id()] = delegate_->CreateFlutterPlatformNodeDelegate(); - id_wrapper_map_[node->id()]->Init(this, node); + id_wrapper_map_[node->id()]->Init( + std::static_pointer_cast( + shared_from_this()), + node); } void AccessibilityBridge::OnNodeDeleted(ui::AXTree* tree, @@ -156,11 +159,11 @@ void AccessibilityBridge::GetSubTreeList(SemanticsNode target, std::vector& result) { result.push_back(target); for (int32_t child : target.children_in_traversal_order) { - auto iter = _pending_semantics_node_updates.find(child); - if (iter != _pending_semantics_node_updates.end()) { + auto iter = pending_semantics_node_updates_.find(child); + if (iter != pending_semantics_node_updates_.end()) { SemanticsNode node = iter->second; GetSubTreeList(node, result); - _pending_semantics_node_updates.erase(iter); + pending_semantics_node_updates_.erase(iter); } } } @@ -372,9 +375,9 @@ void AccessibilityBridge::SetStringListAttributesFromFlutterUpdate( if (actions & FlutterSemanticsAction::kFlutterSemanticsActionCustomAction) { std::vector custom_action_description; for (size_t i = 0; i < node.custom_accessibility_actions.size(); i++) { - auto iter = _pending_semantics_custom_action_updates.find( + auto iter = pending_semantics_custom_action_updates_.find( node.custom_accessibility_actions[i]); - BASE_DCHECK(iter != _pending_semantics_custom_action_updates.end()); + BASE_DCHECK(iter != pending_semantics_custom_action_updates_.end()); custom_action_description.push_back(iter->second.label); } node_data.AddStringListAttribute( diff --git a/shell/platform/common/cpp/accessibility_bridge.h b/shell/platform/common/cpp/accessibility_bridge.h index e2a3bca4ade6f..f2f2bfd381bc6 100644 --- a/shell/platform/common/cpp/accessibility_bridge.h +++ b/shell/platform/common/cpp/accessibility_bridge.h @@ -36,8 +36,10 @@ namespace flutter { /// /// To use this class, you must provide your own implementation of /// FlutterPlatformNodeDelegate and AccessibilityBridgeDelegate. -class AccessibilityBridge : private ui::AXTreeObserver, - private FlutterPlatformNodeDelegate::OwnerBridge { +class AccessibilityBridge + : public std::enable_shared_from_this, + public FlutterPlatformNodeDelegate::OwnerBridge, + private ui::AXTreeObserver { public: //----------------------------------------------------------------------------- /// Delegate to handle requests from the accessibility bridge. The requests @@ -192,9 +194,9 @@ class AccessibilityBridge : private ui::AXTreeObserver, id_wrapper_map_; ui::AXTree tree_; ui::AXEventGenerator event_generator_; - std::unordered_map _pending_semantics_node_updates; + std::unordered_map pending_semantics_node_updates_; std::unordered_map - _pending_semantics_custom_action_updates; + pending_semantics_custom_action_updates_; AccessibilityNodeId last_focused_id_ = ui::AXNode::kInvalidAXID; std::unique_ptr delegate_; diff --git a/shell/platform/common/cpp/accessibility_bridge_unittests.cc b/shell/platform/common/cpp/accessibility_bridge_unittests.cc index 6e49bd0673b2e..90d50a571fcd5 100644 --- a/shell/platform/common/cpp/accessibility_bridge_unittests.cc +++ b/shell/platform/common/cpp/accessibility_bridge_unittests.cc @@ -12,8 +12,9 @@ namespace flutter { namespace testing { TEST(AccessibilityBridgeTest, basicTest) { - AccessibilityBridge bridge( - std::make_unique()); + std::shared_ptr bridge = + std::make_shared( + std::make_unique()); FlutterSemanticsNode root; root.id = 0; root.label = "root"; @@ -25,7 +26,7 @@ TEST(AccessibilityBridgeTest, basicTest) { int32_t children[] = {1, 2}; root.children_in_traversal_order = children; root.custom_accessibility_actions_count = 0; - bridge.AddFlutterSemanticsNodeUpdate(&root); + bridge->AddFlutterSemanticsNodeUpdate(&root); FlutterSemanticsNode child1; child1.id = 1; @@ -36,7 +37,7 @@ TEST(AccessibilityBridgeTest, basicTest) { child1.decreased_value = ""; child1.child_count = 0; child1.custom_accessibility_actions_count = 0; - bridge.AddFlutterSemanticsNodeUpdate(&child1); + bridge->AddFlutterSemanticsNodeUpdate(&child1); FlutterSemanticsNode child2; child2.id = 2; @@ -47,13 +48,13 @@ TEST(AccessibilityBridgeTest, basicTest) { child2.decreased_value = ""; child2.child_count = 0; child2.custom_accessibility_actions_count = 0; - bridge.AddFlutterSemanticsNodeUpdate(&child2); + bridge->AddFlutterSemanticsNodeUpdate(&child2); - bridge.CommitUpdates(); + bridge->CommitUpdates(); - auto root_node = bridge.GetFlutterPlatformNodeDelegateFromID(0).lock(); - auto child1_node = bridge.GetFlutterPlatformNodeDelegateFromID(1).lock(); - auto child2_node = bridge.GetFlutterPlatformNodeDelegateFromID(2).lock(); + auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock(); + auto child1_node = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock(); + auto child2_node = bridge->GetFlutterPlatformNodeDelegateFromID(2).lock(); EXPECT_EQ(root_node->GetChildCount(), 2); EXPECT_EQ(root_node->GetData().child_ids[0], 1); EXPECT_EQ(root_node->GetData().child_ids[1], 2); @@ -70,7 +71,8 @@ TEST(AccessibilityBridgeTest, canFireChildrenChangedCorrectly) { TestAccessibilityBridgeDelegate* delegate = new TestAccessibilityBridgeDelegate(); std::unique_ptr ptr(delegate); - AccessibilityBridge bridge(std::move(ptr)); + std::shared_ptr bridge = + std::make_shared(std::move(ptr)); FlutterSemanticsNode root; root.id = 0; root.flags = static_cast(0); @@ -86,7 +88,7 @@ TEST(AccessibilityBridgeTest, canFireChildrenChangedCorrectly) { int32_t children[] = {1}; root.children_in_traversal_order = children; root.custom_accessibility_actions_count = 0; - bridge.AddFlutterSemanticsNodeUpdate(&root); + bridge->AddFlutterSemanticsNodeUpdate(&root); FlutterSemanticsNode child1; child1.id = 1; @@ -101,12 +103,12 @@ TEST(AccessibilityBridgeTest, canFireChildrenChangedCorrectly) { child1.decreased_value = ""; child1.child_count = 0; child1.custom_accessibility_actions_count = 0; - bridge.AddFlutterSemanticsNodeUpdate(&child1); + bridge->AddFlutterSemanticsNodeUpdate(&child1); - bridge.CommitUpdates(); + bridge->CommitUpdates(); - auto root_node = bridge.GetFlutterPlatformNodeDelegateFromID(0).lock(); - auto child1_node = bridge.GetFlutterPlatformNodeDelegateFromID(1).lock(); + auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock(); + auto child1_node = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock(); EXPECT_EQ(root_node->GetChildCount(), 1); EXPECT_EQ(root_node->GetData().child_ids[0], 1); EXPECT_EQ(root_node->GetName(), "root"); @@ -119,7 +121,7 @@ TEST(AccessibilityBridgeTest, canFireChildrenChangedCorrectly) { root.child_count = 2; int32_t new_children[] = {1, 2}; root.children_in_traversal_order = new_children; - bridge.AddFlutterSemanticsNodeUpdate(&root); + bridge->AddFlutterSemanticsNodeUpdate(&root); FlutterSemanticsNode child2; child2.id = 2; @@ -134,11 +136,11 @@ TEST(AccessibilityBridgeTest, canFireChildrenChangedCorrectly) { child2.decreased_value = ""; child2.child_count = 0; child2.custom_accessibility_actions_count = 0; - bridge.AddFlutterSemanticsNodeUpdate(&child2); + bridge->AddFlutterSemanticsNodeUpdate(&child2); - bridge.CommitUpdates(); + bridge->CommitUpdates(); - root_node = bridge.GetFlutterPlatformNodeDelegateFromID(0).lock(); + root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock(); EXPECT_EQ(root_node->GetChildCount(), 2); EXPECT_EQ(root_node->GetData().child_ids[0], 1); @@ -157,7 +159,8 @@ TEST(AccessibilityBridgeTest, canHandleSelectionChangeCorrectly) { TestAccessibilityBridgeDelegate* delegate = new TestAccessibilityBridgeDelegate(); std::unique_ptr ptr(delegate); - AccessibilityBridge bridge(std::move(ptr)); + std::shared_ptr bridge = + std::make_shared(std::move(ptr)); FlutterSemanticsNode root; root.id = 0; root.flags = FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField; @@ -171,20 +174,20 @@ TEST(AccessibilityBridgeTest, canHandleSelectionChangeCorrectly) { root.decreased_value = ""; root.child_count = 0; root.custom_accessibility_actions_count = 0; - bridge.AddFlutterSemanticsNodeUpdate(&root); + bridge->AddFlutterSemanticsNodeUpdate(&root); - bridge.CommitUpdates(); + bridge->CommitUpdates(); - const ui::AXTreeData& tree = bridge.GetAXTreeData(); + const ui::AXTreeData& tree = bridge->GetAXTreeData(); EXPECT_EQ(tree.sel_anchor_object_id, ui::AXNode::kInvalidAXID); delegate->accessibilitiy_events.clear(); // Update the selection. root.text_selection_base = 0; root.text_selection_extent = 5; - bridge.AddFlutterSemanticsNodeUpdate(&root); + bridge->AddFlutterSemanticsNodeUpdate(&root); - bridge.CommitUpdates(); + bridge->CommitUpdates(); EXPECT_EQ(tree.sel_anchor_object_id, 0); EXPECT_EQ(tree.sel_anchor_offset, 0); diff --git a/shell/platform/common/cpp/flutter_platform_node_delegate.cc b/shell/platform/common/cpp/flutter_platform_node_delegate.cc index 7a9f047ccc2ee..d1f8da97c7641 100644 --- a/shell/platform/common/cpp/flutter_platform_node_delegate.cc +++ b/shell/platform/common/cpp/flutter_platform_node_delegate.cc @@ -13,8 +13,9 @@ FlutterPlatformNodeDelegate::FlutterPlatformNodeDelegate() = default; FlutterPlatformNodeDelegate::~FlutterPlatformNodeDelegate() = default; -void FlutterPlatformNodeDelegate::Init(OwnerBridge* bridge, ui::AXNode* node) { - bridge_ = bridge; +void FlutterPlatformNodeDelegate::Init(std::shared_ptr bridge, + ui::AXNode* node) { + bridge_ = std::move(bridge); ax_node_ = node; } @@ -82,7 +83,8 @@ gfx::Rect FlutterPlatformNodeDelegate::GetBoundsRect( const ui::AXCoordinateSystem coordinate_system, const ui::AXClippingBehavior clipping_behavior, ui::AXOffscreenResult* offscreen_result) const { - // TODO(chunhtai): consider screen dpr. + // TODO(chunhtai): We need to apply screen dpr in here. + // https://github.com/flutter/flutter/issues/74283 const bool clip_bounds = clipping_behavior == ui::AXClippingBehavior::kClipped; bool offscreen = false; diff --git a/shell/platform/common/cpp/flutter_platform_node_delegate.h b/shell/platform/common/cpp/flutter_platform_node_delegate.h index 5a40c09a35d38..8fdc0a507fc83 100644 --- a/shell/platform/common/cpp/flutter_platform_node_delegate.h +++ b/shell/platform/common/cpp/flutter_platform_node_delegate.h @@ -124,7 +124,7 @@ class FlutterPlatformNodeDelegate : public ui::AXPlatformNodeDelegateBase { /// constructor doesn't take any arguments because in the Windows /// subclass we use a special function to construct a COM object. /// Subclasses must call super. - virtual void Init(OwnerBridge* bridge, ui::AXNode* node); + virtual void Init(std::shared_ptr bridge, ui::AXNode* node); protected: //------------------------------------------------------------------------------ @@ -133,7 +133,7 @@ class FlutterPlatformNodeDelegate : public ui::AXPlatformNodeDelegateBase { private: ui::AXNode* ax_node_; - OwnerBridge* bridge_; + std::shared_ptr bridge_; }; } // namespace flutter diff --git a/shell/platform/common/cpp/flutter_platform_node_delegate_unittests.cc b/shell/platform/common/cpp/flutter_platform_node_delegate_unittests.cc index 219017113ea77..d4753265f4b07 100644 --- a/shell/platform/common/cpp/flutter_platform_node_delegate_unittests.cc +++ b/shell/platform/common/cpp/flutter_platform_node_delegate_unittests.cc @@ -16,7 +16,8 @@ TEST(FlutterPlatformNodeDelegateTest, canPerfomActions) { TestAccessibilityBridgeDelegate* delegate = new TestAccessibilityBridgeDelegate(); std::unique_ptr ptr(delegate); - AccessibilityBridge bridge(std::move(ptr)); + std::shared_ptr bridge = + std::make_shared(std::move(ptr)); FlutterSemanticsNode root; root.id = 0; root.flags = FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField; @@ -30,11 +31,11 @@ TEST(FlutterPlatformNodeDelegateTest, canPerfomActions) { root.decreased_value = ""; root.child_count = 0; root.custom_accessibility_actions_count = 0; - bridge.AddFlutterSemanticsNodeUpdate(&root); + bridge->AddFlutterSemanticsNodeUpdate(&root); - bridge.CommitUpdates(); + bridge->CommitUpdates(); - auto accessibility = bridge.GetFlutterPlatformNodeDelegateFromID(0).lock(); + auto accessibility = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock(); // Performs an AXAction. ui::AXActionData action_data; action_data.action = ax::mojom::Action::kDoDefault; @@ -59,8 +60,9 @@ TEST(FlutterPlatformNodeDelegateTest, canPerfomActions) { TEST(FlutterPlatformNodeDelegateTest, canGetAXNode) { // Set up a flutter accessibility node. - AccessibilityBridge bridge( - std::make_unique()); + std::shared_ptr bridge = + std::make_shared( + std::make_unique()); FlutterSemanticsNode root; root.id = 0; root.flags = FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField; @@ -74,17 +76,18 @@ TEST(FlutterPlatformNodeDelegateTest, canGetAXNode) { root.decreased_value = ""; root.child_count = 0; root.custom_accessibility_actions_count = 0; - bridge.AddFlutterSemanticsNodeUpdate(&root); + bridge->AddFlutterSemanticsNodeUpdate(&root); - bridge.CommitUpdates(); + bridge->CommitUpdates(); - auto accessibility = bridge.GetFlutterPlatformNodeDelegateFromID(0).lock(); + auto accessibility = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock(); EXPECT_EQ(accessibility->GetData().id, 0); } TEST(FlutterPlatformNodeDelegateTest, canCalculateBoundsCorrectly) { - AccessibilityBridge bridge( - std::make_unique()); + std::shared_ptr bridge = + std::make_shared( + std::make_unique()); FlutterSemanticsNode root; root.id = 0; root.label = "root"; @@ -98,7 +101,7 @@ TEST(FlutterPlatformNodeDelegateTest, canCalculateBoundsCorrectly) { root.custom_accessibility_actions_count = 0; root.rect = {0, 0, 100, 100}; // LTRB root.transform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; - bridge.AddFlutterSemanticsNodeUpdate(&root); + bridge->AddFlutterSemanticsNodeUpdate(&root); FlutterSemanticsNode child1; child1.id = 1; @@ -111,10 +114,10 @@ TEST(FlutterPlatformNodeDelegateTest, canCalculateBoundsCorrectly) { child1.custom_accessibility_actions_count = 0; child1.rect = {0, 0, 50, 50}; // LTRB child1.transform = {0.5, 0, 0, 0, 0.5, 0, 0, 0, 1}; - bridge.AddFlutterSemanticsNodeUpdate(&child1); + bridge->AddFlutterSemanticsNodeUpdate(&child1); - bridge.CommitUpdates(); - auto child1_node = bridge.GetFlutterPlatformNodeDelegateFromID(1).lock(); + bridge->CommitUpdates(); + auto child1_node = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock(); ui::AXOffscreenResult result; gfx::Rect bounds = child1_node->GetBoundsRect(ui::AXCoordinateSystem::kScreenDIPs, @@ -127,8 +130,9 @@ TEST(FlutterPlatformNodeDelegateTest, canCalculateBoundsCorrectly) { } TEST(FlutterPlatformNodeDelegateTest, canCalculateOffScreenBoundsCorrectly) { - AccessibilityBridge bridge( - std::make_unique()); + std::shared_ptr bridge = + std::make_shared( + std::make_unique()); FlutterSemanticsNode root; root.id = 0; root.label = "root"; @@ -142,7 +146,7 @@ TEST(FlutterPlatformNodeDelegateTest, canCalculateOffScreenBoundsCorrectly) { root.custom_accessibility_actions_count = 0; root.rect = {0, 0, 100, 100}; // LTRB root.transform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; - bridge.AddFlutterSemanticsNodeUpdate(&root); + bridge->AddFlutterSemanticsNodeUpdate(&root); FlutterSemanticsNode child1; child1.id = 1; @@ -155,10 +159,10 @@ TEST(FlutterPlatformNodeDelegateTest, canCalculateOffScreenBoundsCorrectly) { child1.custom_accessibility_actions_count = 0; child1.rect = {90, 90, 100, 100}; // LTRB child1.transform = {2, 0, 0, 0, 2, 0, 0, 0, 1}; - bridge.AddFlutterSemanticsNodeUpdate(&child1); + bridge->AddFlutterSemanticsNodeUpdate(&child1); - bridge.CommitUpdates(); - auto child1_node = bridge.GetFlutterPlatformNodeDelegateFromID(1).lock(); + bridge->CommitUpdates(); + auto child1_node = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock(); ui::AXOffscreenResult result; gfx::Rect bounds = child1_node->GetBoundsRect(ui::AXCoordinateSystem::kScreenDIPs, diff --git a/third_party/accessibility/gfx/native_widget_types.h b/third_party/accessibility/gfx/native_widget_types.h index 1fcd30ec88137..287689be82af9 100644 --- a/third_party/accessibility/gfx/native_widget_types.h +++ b/third_party/accessibility/gfx/native_widget_types.h @@ -191,9 +191,12 @@ typedef ui::WindowAndroid* NativeWindow; typedef base::android::ScopedJavaGlobalRef NativeEvent; constexpr NativeView kNullNativeView = nullptr; constexpr NativeWindow kNullNativeWindow = nullptr; -#else -// for unknown platform. +#elif defined(OS_LINUX) +// TODO(chunhtai): Figures out what is the correct type for Linux +// https://github.com/flutter/flutter/issues/74270 typedef void* NativeCursor; +#else +#error Unknown build environment. #endif #if defined(OS_WIN) @@ -246,10 +249,13 @@ constexpr AcceleratedWidget kNullAcceleratedWidget = 0; #elif defined(USE_OZONE) || defined(USE_X11) typedef uint32_t AcceleratedWidget; constexpr AcceleratedWidget kNullAcceleratedWidget = 0; -#else -// for unknown platform. +#elif defined(OS_LINUX) +// TODO(chunhtai): Figure out what the correct type is for the Linux. +// https://github.com/flutter/flutter/issues/74270 typedef void* AcceleratedWidget; constexpr AcceleratedWidget kNullAcceleratedWidget = nullptr; +#else +#error unknown platform #endif } // namespace gfx From 7d4f9b80c9b386dae44a23f62bf80179a4b79804 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Wed, 20 Jan 2021 10:37:55 -0800 Subject: [PATCH 18/19] update comments --- shell/platform/common/cpp/flutter_platform_node_delegate.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shell/platform/common/cpp/flutter_platform_node_delegate.h b/shell/platform/common/cpp/flutter_platform_node_delegate.h index 8fdc0a507fc83..37ec42af4b3f4 100644 --- a/shell/platform/common/cpp/flutter_platform_node_delegate.h +++ b/shell/platform/common/cpp/flutter_platform_node_delegate.h @@ -27,6 +27,9 @@ typedef ui::AXNode::AXID AccessibilityNodeId; /// /// For desktop platforms, subclasses also need to override the GetBoundsRect /// to apply window-to-screen transform. +/// +/// This class transforms bounds assuming the device pixel ratio is 1.0. See +/// the https://github.com/flutter/flutter/issues/74283 for more information. class FlutterPlatformNodeDelegate : public ui::AXPlatformNodeDelegateBase { public: //---------------------------------------------------------------------------- From 83af5d2d4509adde309d5bbf1719fc0fe619c1f5 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Wed, 20 Jan 2021 13:10:04 -0800 Subject: [PATCH 19/19] switch to weak ptr --- .../cpp/flutter_platform_node_delegate.cc | 32 ++++++++++++------- .../cpp/flutter_platform_node_delegate.h | 4 +-- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/shell/platform/common/cpp/flutter_platform_node_delegate.cc b/shell/platform/common/cpp/flutter_platform_node_delegate.cc index d1f8da97c7641..9eff09cc19350 100644 --- a/shell/platform/common/cpp/flutter_platform_node_delegate.cc +++ b/shell/platform/common/cpp/flutter_platform_node_delegate.cc @@ -13,9 +13,9 @@ FlutterPlatformNodeDelegate::FlutterPlatformNodeDelegate() = default; FlutterPlatformNodeDelegate::~FlutterPlatformNodeDelegate() = default; -void FlutterPlatformNodeDelegate::Init(std::shared_ptr bridge, +void FlutterPlatformNodeDelegate::Init(std::weak_ptr bridge, ui::AXNode* node) { - bridge_ = std::move(bridge); + bridge_ = bridge; ax_node_ = node; } @@ -26,21 +26,23 @@ ui::AXNode* FlutterPlatformNodeDelegate::GetAXNode() const { bool FlutterPlatformNodeDelegate::AccessibilityPerformAction( const ui::AXActionData& data) { AccessibilityNodeId target = ax_node_->id(); + auto bridge_ptr = bridge_.lock(); + BASE_DCHECK(bridge_ptr); switch (data.action) { case ax::mojom::Action::kDoDefault: - bridge_->DispatchAccessibilityAction( + bridge_ptr->DispatchAccessibilityAction( target, FlutterSemanticsAction::kFlutterSemanticsActionTap, {}); return true; case ax::mojom::Action::kFocus: - bridge_->SetLastFocusedId(target); - bridge_->DispatchAccessibilityAction( + bridge_ptr->SetLastFocusedId(target); + bridge_ptr->DispatchAccessibilityAction( target, FlutterSemanticsAction:: kFlutterSemanticsActionDidGainAccessibilityFocus, {}); return true; case ax::mojom::Action::kScrollToMakeVisible: - bridge_->DispatchAccessibilityAction( + bridge_ptr->DispatchAccessibilityAction( target, FlutterSemanticsAction::kFlutterSemanticsActionShowOnScreen, {}); return true; @@ -59,15 +61,19 @@ gfx::NativeViewAccessible FlutterPlatformNodeDelegate::GetParent() { if (!ax_node_->parent()) { return nullptr; } - return bridge_->GetNativeAccessibleFromId(ax_node_->parent()->id()); + auto bridge_ptr = bridge_.lock(); + BASE_DCHECK(bridge_ptr); + return bridge_ptr->GetNativeAccessibleFromId(ax_node_->parent()->id()); } gfx::NativeViewAccessible FlutterPlatformNodeDelegate::GetFocus() { - AccessibilityNodeId last_focused = bridge_->GetLastFocusedId(); + auto bridge_ptr = bridge_.lock(); + BASE_DCHECK(bridge_ptr); + AccessibilityNodeId last_focused = bridge_ptr->GetLastFocusedId(); if (last_focused == ui::AXNode::kInvalidAXID) { return nullptr; } - return bridge_->GetNativeAccessibleFromId(last_focused); + return bridge_ptr->GetNativeAccessibleFromId(last_focused); } int FlutterPlatformNodeDelegate::GetChildCount() const { @@ -75,21 +81,25 @@ int FlutterPlatformNodeDelegate::GetChildCount() const { } gfx::NativeViewAccessible FlutterPlatformNodeDelegate::ChildAtIndex(int index) { + auto bridge_ptr = bridge_.lock(); + BASE_DCHECK(bridge_ptr); AccessibilityNodeId child = ax_node_->GetUnignoredChildAtIndex(index)->id(); - return bridge_->GetNativeAccessibleFromId(child); + return bridge_ptr->GetNativeAccessibleFromId(child); } gfx::Rect FlutterPlatformNodeDelegate::GetBoundsRect( const ui::AXCoordinateSystem coordinate_system, const ui::AXClippingBehavior clipping_behavior, ui::AXOffscreenResult* offscreen_result) const { + auto bridge_ptr = bridge_.lock(); + BASE_DCHECK(bridge_ptr); // TODO(chunhtai): We need to apply screen dpr in here. // https://github.com/flutter/flutter/issues/74283 const bool clip_bounds = clipping_behavior == ui::AXClippingBehavior::kClipped; bool offscreen = false; gfx::RectF bounds = - bridge_->RelativeToGlobalBounds(ax_node_, offscreen, clip_bounds); + bridge_ptr->RelativeToGlobalBounds(ax_node_, offscreen, clip_bounds); if (offscreen_result != nullptr) { *offscreen_result = offscreen ? ui::AXOffscreenResult::kOffscreen : ui::AXOffscreenResult::kOnscreen; diff --git a/shell/platform/common/cpp/flutter_platform_node_delegate.h b/shell/platform/common/cpp/flutter_platform_node_delegate.h index 37ec42af4b3f4..26d757c8119c1 100644 --- a/shell/platform/common/cpp/flutter_platform_node_delegate.h +++ b/shell/platform/common/cpp/flutter_platform_node_delegate.h @@ -127,7 +127,7 @@ class FlutterPlatformNodeDelegate : public ui::AXPlatformNodeDelegateBase { /// constructor doesn't take any arguments because in the Windows /// subclass we use a special function to construct a COM object. /// Subclasses must call super. - virtual void Init(std::shared_ptr bridge, ui::AXNode* node); + virtual void Init(std::weak_ptr bridge, ui::AXNode* node); protected: //------------------------------------------------------------------------------ @@ -136,7 +136,7 @@ class FlutterPlatformNodeDelegate : public ui::AXPlatformNodeDelegateBase { private: ui::AXNode* ax_node_; - std::shared_ptr bridge_; + std::weak_ptr bridge_; }; } // namespace flutter