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"
29 using ::testing::NiceMock;
38 class AccessibilityBridgeWindowsSpy :
public AccessibilityBridgeWindows {
42 explicit AccessibilityBridgeWindowsSpy(FlutterWindowsEngine* engine,
43 FlutterWindowsView* view)
44 : AccessibilityBridgeWindows(view) {}
46 void DispatchWinAccessibilityEvent(
47 std::shared_ptr<FlutterPlatformNodeDelegateWindows>
node_delegate,
52 void SetFocus(std::shared_ptr<FlutterPlatformNodeDelegateWindows>
58 dispatched_events_.clear();
59 focused_nodes_.clear();
62 const std::vector<MsaaEvent>& dispatched_events()
const {
63 return dispatched_events_;
66 const std::vector<int32_t> focused_nodes()
const {
67 std::vector<int32_t> ids;
68 std::transform(focused_nodes_.begin(), focused_nodes_.end(),
69 std::back_inserter(ids),
70 [](std::shared_ptr<FlutterPlatformNodeDelegate> node) {
71 return node->GetAXNode()->id();
77 std::weak_ptr<FlutterPlatformNodeDelegate> GetFocusedNode()
override {
78 return focused_nodes_.back();
82 std::vector<MsaaEvent> dispatched_events_;
83 std::vector<std::shared_ptr<FlutterPlatformNodeDelegate>> focused_nodes_;
85 FML_DISALLOW_COPY_AND_ASSIGN(AccessibilityBridgeWindowsSpy);
90 class FlutterWindowsViewSpy :
public FlutterWindowsView {
92 FlutterWindowsViewSpy(FlutterWindowsEngine* engine,
93 std::unique_ptr<WindowBindingHandler> handler)
101 virtual std::shared_ptr<AccessibilityBridgeWindows>
102 CreateAccessibilityBridge()
override {
103 return std::make_shared<AccessibilityBridgeWindowsSpy>(GetEngine(),
this);
107 FML_DISALLOW_COPY_AND_ASSIGN(FlutterWindowsViewSpy);
113 std::unique_ptr<FlutterWindowsEngine> GetTestEngine() {
115 properties.
assets_path = L
"C:\\foo\\flutter_assets";
119 FlutterProjectBundle project(properties);
120 auto engine = std::make_unique<FlutterWindowsEngine>(project);
122 EngineModifier modifier(engine.get());
123 modifier.embedder_api().UpdateSemanticsEnabled =
124 [](FLUTTER_API_SYMBOL(FlutterEngine) engine,
bool enabled) {
128 MockEmbedderApiForKeyboard(modifier,
129 std::make_shared<MockKeyResponseController>());
145 void PopulateAXTree(std::shared_ptr<AccessibilityBridge> bridge) {
147 FlutterSemanticsNode2 node0{
sizeof(FlutterSemanticsNode2), 0};
148 auto empty_flags = FlutterSemanticsFlags{};
149 std::vector<int32_t> node0_children{1, 2};
150 node0.child_count = node0_children.size();
151 node0.children_in_traversal_order = node0_children.data();
152 node0.children_in_hit_test_order = node0_children.data();
153 node0.flags2 = &empty_flags;
156 FlutterSemanticsNode2 node1{
sizeof(FlutterSemanticsNode2), 1};
157 node1.label =
"prefecture";
158 node1.value =
"Kyoto";
159 node1.flags2 = &empty_flags;
162 FlutterSemanticsNode2 node2{
sizeof(FlutterSemanticsNode2), 2};
163 std::vector<int32_t> node2_children{3, 4};
164 node2.child_count = node2_children.size();
165 node2.children_in_traversal_order = node2_children.data();
166 node2.children_in_hit_test_order = node2_children.data();
167 node2.flags2 = &empty_flags;
170 FlutterSemanticsNode2 node3{
sizeof(FlutterSemanticsNode2), 3};
171 node3.label =
"city";
173 node3.flags2 = &empty_flags;
176 FlutterSemanticsNode2 node4{
sizeof(FlutterSemanticsNode2), 4};
177 node4.flags2 = &empty_flags;
179 bridge->AddFlutterSemanticsNodeUpdate(node0);
180 bridge->AddFlutterSemanticsNodeUpdate(node1);
181 bridge->AddFlutterSemanticsNodeUpdate(node2);
182 bridge->AddFlutterSemanticsNodeUpdate(node3);
183 bridge->AddFlutterSemanticsNodeUpdate(node4);
184 bridge->CommitUpdates();
187 ui::AXNode* AXNodeFromID(std::shared_ptr<AccessibilityBridge> bridge,
189 auto node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(
id).lock();
193 std::shared_ptr<AccessibilityBridgeWindowsSpy> GetAccessibilityBridgeSpy(
194 FlutterWindowsView& view) {
195 return std::static_pointer_cast<AccessibilityBridgeWindowsSpy>(
196 view.accessibility_bridge().lock());
199 void ExpectWinEventFromAXEvent(int32_t node_id,
200 ui::AXEventGenerator::Event ax_event,
201 ax::mojom::Event expected_event) {
202 auto engine = GetTestEngine();
203 FlutterWindowsViewSpy view{
204 engine.get(), std::make_unique<NiceMock<MockWindowBindingHandler>>()};
205 EngineModifier modifier{engine.get()};
206 modifier.SetImplicitView(&view);
207 view.OnUpdateSemanticsEnabled(
true);
209 auto bridge = GetAccessibilityBridgeSpy(view);
210 PopulateAXTree(bridge);
212 bridge->ResetRecords();
213 bridge->OnAccessibilityEvent({AXNodeFromID(bridge, node_id),
214 {ax_event, ax::mojom::EventFrom::kNone, {}}});
215 ASSERT_EQ(bridge->dispatched_events().size(), 1);
216 EXPECT_EQ(bridge->dispatched_events()[0].event_type, expected_event);
219 void ExpectWinEventFromAXEventOnFocusNode(int32_t node_id,
220 ui::AXEventGenerator::Event ax_event,
221 ax::mojom::Event expected_event,
223 auto engine = GetTestEngine();
224 FlutterWindowsViewSpy view{
225 engine.get(), std::make_unique<NiceMock<MockWindowBindingHandler>>()};
226 EngineModifier modifier{engine.get()};
227 modifier.SetImplicitView(&view);
228 view.OnUpdateSemanticsEnabled(
true);
230 auto bridge = GetAccessibilityBridgeSpy(view);
231 PopulateAXTree(bridge);
233 bridge->ResetRecords();
234 auto focus_delegate =
235 bridge->GetFlutterPlatformNodeDelegateFromID(focus_id).lock();
236 bridge->SetFocus(std::static_pointer_cast<FlutterPlatformNodeDelegateWindows>(
238 bridge->OnAccessibilityEvent({AXNodeFromID(bridge, node_id),
239 {ax_event, ax::mojom::EventFrom::kNone, {}}});
240 ASSERT_EQ(bridge->dispatched_events().size(), 1);
241 EXPECT_EQ(bridge->dispatched_events()[0].event_type, expected_event);
242 EXPECT_EQ(bridge->dispatched_events()[0].node_delegate->GetAXNode()->id(),
249 auto engine = GetTestEngine();
250 FlutterWindowsViewSpy view{
251 engine.get(), std::make_unique<NiceMock<MockWindowBindingHandler>>()};
252 EngineModifier modifier{engine.get()};
253 modifier.SetImplicitView(&view);
254 view.OnUpdateSemanticsEnabled(
true);
256 auto bridge = view.accessibility_bridge().lock();
257 PopulateAXTree(bridge);
259 auto node0_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
260 auto node1_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock();
261 EXPECT_EQ(node0_delegate->GetNativeViewAccessible(),
262 node1_delegate->GetParent());
266 auto engine = GetTestEngine();
267 FlutterWindowsViewSpy view{
268 engine.get(), std::make_unique<NiceMock<MockWindowBindingHandler>>()};
269 EngineModifier modifier{engine.get()};
270 modifier.SetImplicitView(&view);
271 view.OnUpdateSemanticsEnabled(
true);
273 auto bridge = view.accessibility_bridge().lock();
274 PopulateAXTree(bridge);
276 auto node0_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
277 ASSERT_TRUE(node0_delegate->GetParent() ==
nullptr);
281 auto engine = GetTestEngine();
282 FlutterWindowsViewSpy view{
283 engine.get(), std::make_unique<NiceMock<MockWindowBindingHandler>>()};
284 EngineModifier modifier{engine.get()};
285 modifier.SetImplicitView(&view);
286 view.OnUpdateSemanticsEnabled(
true);
288 auto bridge = view.accessibility_bridge().lock();
289 PopulateAXTree(bridge);
291 FlutterSemanticsAction actual_action = kFlutterSemanticsActionTap;
292 modifier.embedder_api().SendSemanticsAction = MOCK_ENGINE_PROC(
295 const FlutterSendSemanticsActionInfo* info) {
296 actual_action = info->action;
302 EXPECT_EQ(actual_action, kFlutterSemanticsActionCopy);
306 ExpectWinEventFromAXEvent(0, ui::AXEventGenerator::Event::ALERT,
307 ax::mojom::Event::kAlert);
311 ExpectWinEventFromAXEvent(0, ui::AXEventGenerator::Event::CHILDREN_CHANGED,
312 ax::mojom::Event::kChildrenChanged);
316 auto engine = GetTestEngine();
317 FlutterWindowsViewSpy view{
318 engine.get(), std::make_unique<NiceMock<MockWindowBindingHandler>>()};
319 EngineModifier modifier{engine.get()};
320 modifier.SetImplicitView(&view);
321 view.OnUpdateSemanticsEnabled(
true);
323 auto bridge = GetAccessibilityBridgeSpy(view);
324 PopulateAXTree(bridge);
326 bridge->ResetRecords();
327 bridge->OnAccessibilityEvent({AXNodeFromID(bridge, 1),
328 {ui::AXEventGenerator::Event::FOCUS_CHANGED,
329 ax::mojom::EventFrom::kNone,
331 ASSERT_EQ(bridge->dispatched_events().size(), 1);
332 EXPECT_EQ(bridge->dispatched_events()[0].event_type,
333 ax::mojom::Event::kFocus);
335 ASSERT_EQ(bridge->focused_nodes().size(), 1);
336 EXPECT_EQ(bridge->focused_nodes()[0], 1);
341 ExpectWinEventFromAXEvent(4, ui::AXEventGenerator::Event::IGNORED_CHANGED,
342 ax::mojom::Event::kHide);
346 ExpectWinEventFromAXEvent(
347 1, ui::AXEventGenerator::Event::IMAGE_ANNOTATION_CHANGED,
348 ax::mojom::Event::kTextChanged);
352 ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::LIVE_REGION_CHANGED,
353 ax::mojom::Event::kLiveRegionChanged);
357 ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::NAME_CHANGED,
358 ax::mojom::Event::kTextChanged);
362 ExpectWinEventFromAXEvent(
363 1, ui::AXEventGenerator::Event::SCROLL_HORIZONTAL_POSITION_CHANGED,
364 ax::mojom::Event::kScrollPositionChanged);
368 ExpectWinEventFromAXEvent(
369 1, ui::AXEventGenerator::Event::SCROLL_VERTICAL_POSITION_CHANGED,
370 ax::mojom::Event::kScrollPositionChanged);
374 ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::SELECTED_CHANGED,
375 ax::mojom::Event::kValueChanged);
379 ExpectWinEventFromAXEvent(
380 2, ui::AXEventGenerator::Event::SELECTED_CHILDREN_CHANGED,
381 ax::mojom::Event::kSelectedChildrenChanged);
385 ExpectWinEventFromAXEvent(0, ui::AXEventGenerator::Event::SUBTREE_CREATED,
386 ax::mojom::Event::kShow);
390 ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::VALUE_CHANGED,
391 ax::mojom::Event::kValueChanged);
395 ExpectWinEventFromAXEvent(
396 1, ui::AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED,
397 ax::mojom::Event::kStateChanged);
401 ExpectWinEventFromAXEventOnFocusNode(
402 1, ui::AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED,
403 ax::mojom::Event::kDocumentSelectionChanged, 2);
std::shared_ptr< FlutterPlatformNodeDelegateWindows > node_delegate
ax::mojom::Event event_type
void DispatchAccessibilityAction(AccessibilityNodeId target, FlutterSemanticsAction action, fml::MallocMapping data) override
Dispatch accessibility action back to the Flutter framework. These actions are generated in the nativ...
void OnAccessibilityEvent(ui::AXEventGenerator::TargetedEvent targeted_event) override
Handle accessibility events generated due to accessibility tree changes. These events are needed to b...
TEST(AccessibilityBridgeWindows, GetParent)
constexpr FlutterViewId kImplicitViewId
const wchar_t * icu_data_path
const wchar_t * assets_path
FlutterDesktopImpellerSwitch impeller_switch
const wchar_t * aot_library_path