Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
accessibility_bridge_windows_unittests.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
6
7#include <comdef.h>
8#include <comutil.h>
9#include <oleacc.h>
10
11#include <vector>
12
13#include "flutter/fml/macros.h"
22#include "gmock/gmock.h"
23#include "gtest/gtest.h"
24
25namespace flutter {
26namespace testing {
27
28namespace {
29using ::testing::NiceMock;
30
31// A structure representing a Win32 MSAA event targeting a specified node.
32struct MsaaEvent {
33 std::shared_ptr<FlutterPlatformNodeDelegateWindows> node_delegate;
35};
36
37// Accessibility bridge delegate that captures events dispatched to the OS.
38class AccessibilityBridgeWindowsSpy : public AccessibilityBridgeWindows {
39 public:
41
42 explicit AccessibilityBridgeWindowsSpy(FlutterWindowsEngine* engine,
43 FlutterWindowsView* view)
44 : AccessibilityBridgeWindows(view) {}
45
46 void DispatchWinAccessibilityEvent(
47 std::shared_ptr<FlutterPlatformNodeDelegateWindows> node_delegate,
49 dispatched_events_.push_back({node_delegate, event_type});
50 }
51
52 void SetFocus(std::shared_ptr<FlutterPlatformNodeDelegateWindows>
53 node_delegate) override {
54 focused_nodes_.push_back(std::move(node_delegate));
55 }
56
57 void ResetRecords() {
58 dispatched_events_.clear();
59 focused_nodes_.clear();
60 }
61
62 const std::vector<MsaaEvent>& dispatched_events() const {
63 return dispatched_events_;
64 }
65
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();
72 });
73 return ids;
74 }
75
76 protected:
77 std::weak_ptr<FlutterPlatformNodeDelegate> GetFocusedNode() override {
78 return focused_nodes_.back();
79 }
80
81 private:
82 std::vector<MsaaEvent> dispatched_events_;
83 std::vector<std::shared_ptr<FlutterPlatformNodeDelegate>> focused_nodes_;
84
85 FML_DISALLOW_COPY_AND_ASSIGN(AccessibilityBridgeWindowsSpy);
86};
87
88// A FlutterWindowsView whose accessibility bridge is an
89// AccessibilityBridgeWindowsSpy.
90class FlutterWindowsViewSpy : public FlutterWindowsView {
91 public:
92 FlutterWindowsViewSpy(FlutterWindowsEngine* engine,
93 std::unique_ptr<WindowBindingHandler> handler)
94 : FlutterWindowsView(kImplicitViewId,
95 engine,
96 std::move(handler),
97 false,
98 BoxConstraints()) {}
99
100 protected:
101 virtual std::shared_ptr<AccessibilityBridgeWindows>
102 CreateAccessibilityBridge() override {
103 return std::make_shared<AccessibilityBridgeWindowsSpy>(GetEngine(), this);
104 }
105
106 private:
107 FML_DISALLOW_COPY_AND_ASSIGN(FlutterWindowsViewSpy);
108};
109
110// Returns an engine instance configured with dummy project path values, and
111// overridden methods for sending platform messages, so that the engine can
112// respond as if the framework were connected.
113std::unique_ptr<FlutterWindowsEngine> GetTestEngine() {
114 FlutterDesktopEngineProperties properties = {};
115 properties.assets_path = L"C:\\foo\\flutter_assets";
116 properties.icu_data_path = L"C:\\foo\\icudtl.dat";
117 properties.aot_library_path = L"C:\\foo\\aot.so";
118 properties.impeller_switch = DefaultImpeller;
119 FlutterProjectBundle project(properties);
120 auto engine = std::make_unique<FlutterWindowsEngine>(project);
121
122 EngineModifier modifier(engine.get());
123 modifier.embedder_api().UpdateSemanticsEnabled =
124 [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
125 return kSuccess;
126 };
127
129 std::make_shared<MockKeyResponseController>());
130
131 engine->Run();
132 return engine;
133}
134
135// Populates the AXTree associated with the specified bridge with test data.
136//
137// node0
138// / \
139// node1 node2
140// / \
141// node3 node4
142//
143// node0 and node2 are grouping nodes. node1 and node2 are static text nodes.
144// node4 is a static text node with no text, and hence has the "ignored" state.
145void PopulateAXTree(std::shared_ptr<AccessibilityBridge> bridge) {
146 // Add node 0: root.
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;
154
155 // Add node 1: text child of node 0.
157 node1.label = "prefecture";
158 node1.value = "Kyoto";
159 node1.flags2 = &empty_flags;
160
161 // Add node 2: subtree child of node 0.
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;
168
169 // Add node 3: text child of node 2.
171 node3.label = "city";
172 node3.value = "Uji";
173 node3.flags2 = &empty_flags;
174
175 // Add node 4: text child (with no text) of node 2.
177 node4.flags2 = &empty_flags;
178
179 bridge->AddFlutterSemanticsNodeUpdate(node0);
180 bridge->AddFlutterSemanticsNodeUpdate(node1);
181 bridge->AddFlutterSemanticsNodeUpdate(node2);
182 bridge->AddFlutterSemanticsNodeUpdate(node3);
183 bridge->AddFlutterSemanticsNodeUpdate(node4);
184 bridge->CommitUpdates();
185}
186
187ui::AXNode* AXNodeFromID(std::shared_ptr<AccessibilityBridge> bridge,
188 int32_t id) {
189 auto node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(id).lock();
190 return node_delegate ? node_delegate->GetAXNode() : nullptr;
191}
192
193std::shared_ptr<AccessibilityBridgeWindowsSpy> GetAccessibilityBridgeSpy(
194 FlutterWindowsView& view) {
195 return std::static_pointer_cast<AccessibilityBridgeWindowsSpy>(
196 view.accessibility_bridge().lock());
197}
198
199void ExpectWinEventFromAXEvent(int32_t node_id,
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);
208
209 auto bridge = GetAccessibilityBridgeSpy(view);
210 PopulateAXTree(bridge);
211
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);
217}
218
219void ExpectWinEventFromAXEventOnFocusNode(int32_t node_id,
221 ax::mojom::Event expected_event,
222 int32_t focus_id) {
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);
229
230 auto bridge = GetAccessibilityBridgeSpy(view);
231 PopulateAXTree(bridge);
232
233 bridge->ResetRecords();
234 auto focus_delegate =
235 bridge->GetFlutterPlatformNodeDelegateFromID(focus_id).lock();
236 bridge->SetFocus(std::static_pointer_cast<FlutterPlatformNodeDelegateWindows>(
237 focus_delegate));
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(),
243 focus_id);
244}
245
246} // namespace
247
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);
255
256 auto bridge = view.accessibility_bridge().lock();
257 PopulateAXTree(bridge);
258
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());
263}
264
265TEST(AccessibilityBridgeWindows, GetParentOnRootRetunsNullptr) {
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);
272
273 auto bridge = view.accessibility_bridge().lock();
274 PopulateAXTree(bridge);
275
276 auto node0_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
277 ASSERT_TRUE(node0_delegate->GetParent() == nullptr);
278}
279
280TEST(AccessibilityBridgeWindows, DispatchAccessibilityAction) {
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);
287
288 auto bridge = view.accessibility_bridge().lock();
289 PopulateAXTree(bridge);
290
292 modifier.embedder_api().SendSemanticsAction = MOCK_ENGINE_PROC(
293 SendSemanticsAction,
294 ([&actual_action](FLUTTER_API_SYMBOL(FlutterEngine) engine,
295 const FlutterSendSemanticsActionInfo* info) {
296 actual_action = info->action;
297 return kSuccess;
298 }));
299
302 EXPECT_EQ(actual_action, kFlutterSemanticsActionCopy);
303}
304
305TEST(AccessibilityBridgeWindows, OnAccessibilityEventAlert) {
306 ExpectWinEventFromAXEvent(0, ui::AXEventGenerator::Event::ALERT,
308}
309
310TEST(AccessibilityBridgeWindows, OnAccessibilityEventChildrenChanged) {
311 ExpectWinEventFromAXEvent(0, ui::AXEventGenerator::Event::CHILDREN_CHANGED,
313}
314
315TEST(AccessibilityBridgeWindows, OnAccessibilityEventFocusChanged) {
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);
322
323 auto bridge = GetAccessibilityBridgeSpy(view);
324 PopulateAXTree(bridge);
325
326 bridge->ResetRecords();
327 bridge->OnAccessibilityEvent({AXNodeFromID(bridge, 1),
330 {}}});
331 ASSERT_EQ(bridge->dispatched_events().size(), 1);
332 EXPECT_EQ(bridge->dispatched_events()[0].event_type,
334
335 ASSERT_EQ(bridge->focused_nodes().size(), 1);
336 EXPECT_EQ(bridge->focused_nodes()[0], 1);
337}
338
339TEST(AccessibilityBridgeWindows, OnAccessibilityEventIgnoredChanged) {
340 // Static test nodes with no text, hint, or scrollability are ignored.
341 ExpectWinEventFromAXEvent(4, ui::AXEventGenerator::Event::IGNORED_CHANGED,
343}
344
345TEST(AccessibilityBridgeWindows, OnAccessibilityImageAnnotationChanged) {
346 ExpectWinEventFromAXEvent(
349}
350
351TEST(AccessibilityBridgeWindows, OnAccessibilityLiveRegionChanged) {
352 ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::LIVE_REGION_CHANGED,
354}
355
356TEST(AccessibilityBridgeWindows, OnAccessibilityNameChanged) {
357 ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::NAME_CHANGED,
359}
360
366
367TEST(AccessibilityBridgeWindows, OnAccessibilityVScrollPosChanged) {
368 ExpectWinEventFromAXEvent(
371}
372
373TEST(AccessibilityBridgeWindows, OnAccessibilitySelectedChanged) {
374 ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::SELECTED_CHANGED,
376}
377
378TEST(AccessibilityBridgeWindows, OnAccessibilitySelectedChildrenChanged) {
379 ExpectWinEventFromAXEvent(
382}
383
384TEST(AccessibilityBridgeWindows, OnAccessibilitySubtreeCreated) {
385 ExpectWinEventFromAXEvent(0, ui::AXEventGenerator::Event::SUBTREE_CREATED,
387}
388
389TEST(AccessibilityBridgeWindows, OnAccessibilityValueChanged) {
390 ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::VALUE_CHANGED,
392}
393
394TEST(AccessibilityBridgeWindows, OnAccessibilityStateChanged) {
395 ExpectWinEventFromAXEvent(
398}
399
400TEST(AccessibilityBridgeWindows, OnDocumentSelectionChanged) {
401 ExpectWinEventFromAXEventOnFocusNode(
404}
405
406} // namespace testing
407} // namespace flutter
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...
#define FLUTTER_API_SYMBOL(symbol)
Definition embedder.h:67
FlutterSemanticsAction
Definition embedder.h:122
@ kFlutterSemanticsActionCopy
Copy the current selection to the clipboard.
Definition embedder.h:155
@ kFlutterSemanticsActionTap
Definition embedder.h:125
FlutterEngine engine
Definition main.cc:84
FlView * view
const gchar FlBinaryMessengerMessageHandler handler
@ DefaultImpeller
#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition macros.h:27
void MockEmbedderApiForKeyboard(EngineModifier &modifier, std::shared_ptr< MockKeyResponseController > response_controller)
TEST(NativeAssetsManagerTest, NoAvailableAssets)
constexpr int64_t kImplicitViewId
Definition ref_ptr.h:261
#define MOCK_ENGINE_PROC(proc, mock_impl)
FlutterDesktopImpellerSwitch impeller_switch
size_t child_count
The number of children this node has.
Definition embedder.h:1718
const char * label
A textual description of the node.
Definition embedder.h:1698
FlutterSemanticsFlags * flags2
Definition embedder.h:1760
FlutterSemanticsAction action
The semantics action.
Definition embedder.h:2839