From 49a1fd89618932982dc05072cfbc6a36cc524bd8 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Thu, 2 Dec 2021 17:01:25 -0800 Subject: [PATCH] Win32: Implement DispatchAccessibilityAction Implements DispatchAccessibilityAction: a pass-through method that forwards to the engine, which calls through the Embedder API back to the framework. Issue: https://github.com/flutter/flutter/issues/77838 --- ci/licenses_golden/licenses_flutter | 1 + shell/platform/windows/BUILD.gn | 1 + .../accessibility_bridge_delegate_win32.cc | 2 +- ...ibility_bridge_delegate_win32_unittests.cc | 128 ++++++++++++++++++ 4 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 shell/platform/windows/accessibility_bridge_delegate_win32_unittests.cc diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 901669237596c..d43c84dc242e4 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1698,6 +1698,7 @@ FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_view.h FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/flutter_linux.h FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_delegate_win32.cc FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_delegate_win32.h +FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_delegate_win32_unittests.cc FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_delegate_winuwp.cc FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_delegate_winuwp.h FILE: ../../../flutter/shell/platform/windows/angle_surface_manager.cc diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index 5b5f468865e65..e5f99df322d2f 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -251,6 +251,7 @@ executable("flutter_windows_unittests") { } else { sources += [ # TODO move first two tests to common once above TODO's unblocked. + "accessibility_bridge_delegate_win32_unittests.cc", "dpi_utils_win32_unittests.cc", "flutter_project_bundle_unittests.cc", "flutter_window_win32_unittests.cc", diff --git a/shell/platform/windows/accessibility_bridge_delegate_win32.cc b/shell/platform/windows/accessibility_bridge_delegate_win32.cc index cb9361747bce3..7dcf838af9497 100644 --- a/shell/platform/windows/accessibility_bridge_delegate_win32.cc +++ b/shell/platform/windows/accessibility_bridge_delegate_win32.cc @@ -25,7 +25,7 @@ void AccessibilityBridgeDelegateWin32::DispatchAccessibilityAction( AccessibilityNodeId target, FlutterSemanticsAction action, fml::MallocMapping data) { - // TODO(cbracken): https://github.com/flutter/flutter/issues/77838 + engine_->DispatchSemanticsAction(target, action, std::move(data)); } std::shared_ptr diff --git a/shell/platform/windows/accessibility_bridge_delegate_win32_unittests.cc b/shell/platform/windows/accessibility_bridge_delegate_win32_unittests.cc new file mode 100644 index 0000000000000..296ce154b769e --- /dev/null +++ b/shell/platform/windows/accessibility_bridge_delegate_win32_unittests.cc @@ -0,0 +1,128 @@ +// 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/shell/platform/windows/accessibility_bridge_delegate_win32.h" + +#include +#include +#include + +#include + +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" +#include "flutter/shell/platform/windows/flutter_platform_node_delegate_win32.h" +#include "flutter/shell/platform/windows/flutter_windows_engine.h" +#include "flutter/shell/platform/windows/flutter_windows_view.h" +#include "flutter/shell/platform/windows/testing/engine_modifier.h" +#include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h" +#include "flutter/shell/platform/windows/testing/test_keyboard.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +namespace { + +// Returns an engine instance configured with dummy project path values, and +// overridden methods for sending platform messages, so that the engine can +// respond as if the framework were connected. +std::unique_ptr GetTestEngine() { + FlutterDesktopEngineProperties properties = {}; + properties.assets_path = L"C:\\foo\\flutter_assets"; + properties.icu_data_path = L"C:\\foo\\icudtl.dat"; + properties.aot_library_path = L"C:\\foo\\aot.so"; + FlutterProjectBundle project(properties); + auto engine = std::make_unique(project); + + EngineModifier modifier(engine.get()); + modifier.embedder_api().UpdateSemanticsEnabled = + [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) { + return kSuccess; + }; + MockEmbedderApiForKeyboard( + modifier, [] { return false; }, + [](const FlutterKeyEvent* event) { return false; }); + + engine->RunWithEntrypoint(nullptr); + return engine; +} + +// Populates the AXTree associated with the specified bridge with test data. +// +// node0 +// / \ +// node1 node2 +// / \ +// node3 node4 +// +// node0 and node2 are grouping nodes. node1 and node2 are static text nodes. +// node4 is a static text node with no text, and hence has the "ignored" state. +void PopulateAXTree(std::shared_ptr bridge) { + // Add node 0: root. + FlutterSemanticsNode node0{sizeof(FlutterSemanticsNode), 0}; + std::vector node0_children{1, 2}; + node0.child_count = node0_children.size(); + node0.children_in_traversal_order = node0_children.data(); + node0.children_in_hit_test_order = node0_children.data(); + + // Add node 1: text child of node 0. + FlutterSemanticsNode node1{sizeof(FlutterSemanticsNode), 1}; + node1.label = "prefecture"; + node1.value = "Kyoto"; + + // Add node 2: subtree child of node 0. + FlutterSemanticsNode node2{sizeof(FlutterSemanticsNode), 2}; + std::vector node2_children{3, 4}; + node2.child_count = node2_children.size(); + node2.children_in_traversal_order = node2_children.data(); + node2.children_in_hit_test_order = node2_children.data(); + + // Add node 3: text child of node 2. + FlutterSemanticsNode node3{sizeof(FlutterSemanticsNode), 3}; + node3.label = "city"; + node3.value = "Uji"; + + // Add node 4: text child (with no text) of node 2. + FlutterSemanticsNode node4{sizeof(FlutterSemanticsNode), 4}; + + bridge->AddFlutterSemanticsNodeUpdate(&node0); + bridge->AddFlutterSemanticsNodeUpdate(&node1); + bridge->AddFlutterSemanticsNodeUpdate(&node2); + bridge->AddFlutterSemanticsNodeUpdate(&node3); + bridge->AddFlutterSemanticsNodeUpdate(&node4); + bridge->CommitUpdates(); +} + +} // namespace + +TEST(AccessibilityBridgeDelegateWin32, DispatchAccessibilityAction) { + auto window_binding_handler = + std::make_unique<::testing::NiceMock>(); + FlutterWindowsView view(std::move(window_binding_handler)); + view.SetEngine(GetTestEngine()); + view.OnUpdateSemanticsEnabled(true); + + auto bridge = view.GetEngine()->accessibility_bridge().lock(); + PopulateAXTree(bridge); + + FlutterSemanticsAction actual_action = kFlutterSemanticsActionTap; + EngineModifier modifier(view.GetEngine()); + modifier.embedder_api().DispatchSemanticsAction = MOCK_ENGINE_PROC( + DispatchSemanticsAction, + ([&actual_action](FLUTTER_API_SYMBOL(FlutterEngine) engine, uint64_t id, + FlutterSemanticsAction action, const uint8_t* data, + size_t data_length) { + actual_action = action; + return kSuccess; + })); + + AccessibilityBridgeDelegateWin32 delegate(view.GetEngine()); + delegate.DispatchAccessibilityAction(1, kFlutterSemanticsActionCopy, {}); + EXPECT_EQ(actual_action, kFlutterSemanticsActionCopy); +} + +} // namespace testing +} // namespace flutter