13 #include "flutter/fml/macros.h"
14 #include "flutter/shell/platform/embedder/embedder.h"
15 #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
19 #include "flutter/shell/platform/windows/testing/engine_modifier.h"
20 #include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h"
21 #include "flutter/shell/platform/windows/testing/test_keyboard.h"
22 #include "gmock/gmock.h"
23 #include "gtest/gtest.h"
37 class AccessibilityBridgeWindowsSpy :
public AccessibilityBridgeWindows {
41 explicit AccessibilityBridgeWindowsSpy(FlutterWindowsEngine* engine,
42 FlutterWindowsView* view)
43 : AccessibilityBridgeWindows(view) {}
45 void DispatchWinAccessibilityEvent(
46 std::shared_ptr<FlutterPlatformNodeDelegateWindows>
node_delegate,
51 void SetFocus(std::shared_ptr<FlutterPlatformNodeDelegateWindows>
57 dispatched_events_.clear();
58 focused_nodes_.clear();
61 const std::vector<MsaaEvent>& dispatched_events()
const {
62 return dispatched_events_;
65 const std::vector<int32_t> focused_nodes()
const {
66 std::vector<int32_t> ids;
67 std::transform(focused_nodes_.begin(), focused_nodes_.end(),
68 std::back_inserter(ids),
69 [](std::shared_ptr<FlutterPlatformNodeDelegate> node) {
70 return node->GetAXNode()->id();
76 std::weak_ptr<FlutterPlatformNodeDelegate> GetFocusedNode()
override {
77 return focused_nodes_.back();
81 std::vector<MsaaEvent> dispatched_events_;
82 std::vector<std::shared_ptr<FlutterPlatformNodeDelegate>> focused_nodes_;
84 FML_DISALLOW_COPY_AND_ASSIGN(AccessibilityBridgeWindowsSpy);
89 class FlutterWindowsViewSpy :
public FlutterWindowsView {
91 explicit FlutterWindowsViewSpy(std::unique_ptr<WindowBindingHandler> handler)
92 : FlutterWindowsView(std::move(handler)) {}
95 virtual std::shared_ptr<AccessibilityBridgeWindows>
96 CreateAccessibilityBridge()
override {
97 return std::make_shared<AccessibilityBridgeWindowsSpy>(GetEngine(),
this);
101 FML_DISALLOW_COPY_AND_ASSIGN(FlutterWindowsViewSpy);
107 std::unique_ptr<FlutterWindowsEngine> GetTestEngine() {
109 properties.
assets_path = L
"C:\\foo\\flutter_assets";
112 FlutterProjectBundle project(properties);
113 auto engine = std::make_unique<FlutterWindowsEngine>(project);
115 EngineModifier modifier(engine.get());
116 modifier.embedder_api().UpdateSemanticsEnabled =
117 [](FLUTTER_API_SYMBOL(FlutterEngine) engine,
bool enabled) {
121 MockEmbedderApiForKeyboard(modifier,
122 std::make_shared<MockKeyResponseController>());
138 void PopulateAXTree(std::shared_ptr<AccessibilityBridge> bridge) {
140 FlutterSemanticsNode2 node0{
sizeof(FlutterSemanticsNode2), 0};
141 std::vector<int32_t> node0_children{1, 2};
142 node0.child_count = node0_children.size();
143 node0.children_in_traversal_order = node0_children.data();
144 node0.children_in_hit_test_order = node0_children.data();
147 FlutterSemanticsNode2 node1{
sizeof(FlutterSemanticsNode2), 1};
148 node1.label =
"prefecture";
149 node1.value =
"Kyoto";
152 FlutterSemanticsNode2 node2{
sizeof(FlutterSemanticsNode2), 2};
153 std::vector<int32_t> node2_children{3, 4};
154 node2.child_count = node2_children.size();
155 node2.children_in_traversal_order = node2_children.data();
156 node2.children_in_hit_test_order = node2_children.data();
159 FlutterSemanticsNode2 node3{
sizeof(FlutterSemanticsNode2), 3};
160 node3.label =
"city";
164 FlutterSemanticsNode2 node4{
sizeof(FlutterSemanticsNode2), 4};
166 bridge->AddFlutterSemanticsNodeUpdate(node0);
167 bridge->AddFlutterSemanticsNodeUpdate(node1);
168 bridge->AddFlutterSemanticsNodeUpdate(node2);
169 bridge->AddFlutterSemanticsNodeUpdate(node3);
170 bridge->AddFlutterSemanticsNodeUpdate(node4);
171 bridge->CommitUpdates();
174 ui::AXNode* AXNodeFromID(std::shared_ptr<AccessibilityBridge> bridge,
176 auto node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(
id).lock();
180 std::shared_ptr<AccessibilityBridgeWindowsSpy> GetAccessibilityBridgeSpy(
181 FlutterWindowsView& view) {
182 return std::static_pointer_cast<AccessibilityBridgeWindowsSpy>(
183 view.accessibility_bridge().lock());
186 void ExpectWinEventFromAXEvent(int32_t node_id,
187 ui::AXEventGenerator::Event ax_event,
188 ax::mojom::Event expected_event) {
189 auto window_binding_handler =
190 std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
191 FlutterWindowsViewSpy view(std::move(window_binding_handler));
192 view.SetEngine(GetTestEngine());
193 view.OnUpdateSemanticsEnabled(
true);
195 auto bridge = GetAccessibilityBridgeSpy(view);
196 PopulateAXTree(bridge);
198 bridge->ResetRecords();
199 bridge->OnAccessibilityEvent({AXNodeFromID(bridge, node_id),
200 {ax_event, ax::mojom::EventFrom::kNone, {}}});
201 ASSERT_EQ(bridge->dispatched_events().size(), 1);
202 EXPECT_EQ(bridge->dispatched_events()[0].event_type, expected_event);
205 void ExpectWinEventFromAXEventOnFocusNode(int32_t node_id,
206 ui::AXEventGenerator::Event ax_event,
207 ax::mojom::Event expected_event,
209 auto window_binding_handler =
210 std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
211 FlutterWindowsViewSpy view(std::move(window_binding_handler));
212 view.SetEngine(GetTestEngine());
213 view.OnUpdateSemanticsEnabled(
true);
215 auto bridge = GetAccessibilityBridgeSpy(view);
216 PopulateAXTree(bridge);
218 bridge->ResetRecords();
219 auto focus_delegate =
220 bridge->GetFlutterPlatformNodeDelegateFromID(focus_id).lock();
221 bridge->SetFocus(std::static_pointer_cast<FlutterPlatformNodeDelegateWindows>(
223 bridge->OnAccessibilityEvent({AXNodeFromID(bridge, node_id),
224 {ax_event, ax::mojom::EventFrom::kNone, {}}});
225 ASSERT_EQ(bridge->dispatched_events().size(), 1);
226 EXPECT_EQ(bridge->dispatched_events()[0].event_type, expected_event);
227 EXPECT_EQ(bridge->dispatched_events()[0].node_delegate->GetAXNode()->id(),
234 auto window_binding_handler =
235 std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
236 FlutterWindowsViewSpy view(std::move(window_binding_handler));
237 view.SetEngine(GetTestEngine());
238 view.OnUpdateSemanticsEnabled(
true);
240 auto bridge = view.accessibility_bridge().lock();
241 PopulateAXTree(bridge);
243 auto node0_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
244 auto node1_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock();
245 EXPECT_EQ(node0_delegate->GetNativeViewAccessible(),
246 node1_delegate->GetParent());
250 auto window_binding_handler =
251 std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
252 FlutterWindowsViewSpy view(std::move(window_binding_handler));
253 view.SetEngine(GetTestEngine());
254 view.OnUpdateSemanticsEnabled(
true);
256 auto bridge = view.accessibility_bridge().lock();
257 PopulateAXTree(bridge);
259 auto node0_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
260 ASSERT_TRUE(node0_delegate->GetParent() ==
nullptr);
264 auto window_binding_handler =
265 std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
266 FlutterWindowsViewSpy view(std::move(window_binding_handler));
267 view.SetEngine(GetTestEngine());
268 view.OnUpdateSemanticsEnabled(
true);
270 auto bridge = view.accessibility_bridge().lock();
271 PopulateAXTree(bridge);
273 FlutterSemanticsAction actual_action = kFlutterSemanticsActionTap;
274 EngineModifier modifier(view.GetEngine());
275 modifier.embedder_api().DispatchSemanticsAction = MOCK_ENGINE_PROC(
276 DispatchSemanticsAction,
277 ([&actual_action](FLUTTER_API_SYMBOL(
FlutterEngine) engine, uint64_t
id,
278 FlutterSemanticsAction
action,
const uint8_t* data,
279 size_t data_length) {
286 EXPECT_EQ(actual_action, kFlutterSemanticsActionCopy);
290 ExpectWinEventFromAXEvent(0, ui::AXEventGenerator::Event::ALERT,
291 ax::mojom::Event::kAlert);
295 ExpectWinEventFromAXEvent(0, ui::AXEventGenerator::Event::CHILDREN_CHANGED,
296 ax::mojom::Event::kChildrenChanged);
300 auto window_binding_handler =
301 std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
302 FlutterWindowsViewSpy view(std::move(window_binding_handler));
303 view.SetEngine(GetTestEngine());
304 view.OnUpdateSemanticsEnabled(
true);
306 auto bridge = GetAccessibilityBridgeSpy(view);
307 PopulateAXTree(bridge);
309 bridge->ResetRecords();
310 bridge->OnAccessibilityEvent({AXNodeFromID(bridge, 1),
311 {ui::AXEventGenerator::Event::FOCUS_CHANGED,
312 ax::mojom::EventFrom::kNone,
314 ASSERT_EQ(bridge->dispatched_events().size(), 1);
315 EXPECT_EQ(bridge->dispatched_events()[0].event_type,
316 ax::mojom::Event::kFocus);
318 ASSERT_EQ(bridge->focused_nodes().size(), 1);
319 EXPECT_EQ(bridge->focused_nodes()[0], 1);
324 ExpectWinEventFromAXEvent(4, ui::AXEventGenerator::Event::IGNORED_CHANGED,
325 ax::mojom::Event::kHide);
329 ExpectWinEventFromAXEvent(
330 1, ui::AXEventGenerator::Event::IMAGE_ANNOTATION_CHANGED,
331 ax::mojom::Event::kTextChanged);
335 ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::LIVE_REGION_CHANGED,
336 ax::mojom::Event::kLiveRegionChanged);
340 ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::NAME_CHANGED,
341 ax::mojom::Event::kTextChanged);
345 ExpectWinEventFromAXEvent(
346 1, ui::AXEventGenerator::Event::SCROLL_HORIZONTAL_POSITION_CHANGED,
347 ax::mojom::Event::kScrollPositionChanged);
351 ExpectWinEventFromAXEvent(
352 1, ui::AXEventGenerator::Event::SCROLL_VERTICAL_POSITION_CHANGED,
353 ax::mojom::Event::kScrollPositionChanged);
357 ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::SELECTED_CHANGED,
358 ax::mojom::Event::kValueChanged);
362 ExpectWinEventFromAXEvent(
363 2, ui::AXEventGenerator::Event::SELECTED_CHILDREN_CHANGED,
364 ax::mojom::Event::kSelectedChildrenChanged);
368 ExpectWinEventFromAXEvent(0, ui::AXEventGenerator::Event::SUBTREE_CREATED,
369 ax::mojom::Event::kShow);
373 ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::VALUE_CHANGED,
374 ax::mojom::Event::kValueChanged);
378 ExpectWinEventFromAXEvent(
379 1, ui::AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED,
380 ax::mojom::Event::kStateChanged);
384 ExpectWinEventFromAXEventOnFocusNode(
385 1, ui::AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED,
386 ax::mojom::Event::kDocumentSelectionChanged, 2);