Flutter Engine
The Flutter Engine
AccessibilityBridgeMacTest.mm
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
5#import "flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.h"
6
7#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h"
8#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
9#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
10#include "flutter/testing/autoreleasepool_test.h"
11#include "flutter/testing/testing.h"
12
13namespace flutter::testing {
14
15namespace {
16
17class AccessibilityBridgeMacSpy : public AccessibilityBridgeMac {
18 public:
20
21 AccessibilityBridgeMacSpy(__weak FlutterEngine* flutter_engine,
22 __weak FlutterViewController* view_controller)
23 : AccessibilityBridgeMac(flutter_engine, view_controller) {}
24
25 std::unordered_map<std::string, gfx::NativeViewAccessible> actual_notifications;
26
27 private:
28 void DispatchMacOSNotification(gfx::NativeViewAccessible native_node,
29 NSAccessibilityNotificationName mac_notification) override {
30 actual_notifications[[mac_notification UTF8String]] = native_node;
31 }
32};
33
34} // namespace
35} // namespace flutter::testing
36
38- (std::shared_ptr<flutter::AccessibilityBridgeMac>)createAccessibilityBridgeWithEngine:
39 (nonnull FlutterEngine*)engine;
40@end
41
43- (std::shared_ptr<flutter::AccessibilityBridgeMac>)createAccessibilityBridgeWithEngine:
44 (nonnull FlutterEngine*)engine {
45 return std::make_shared<flutter::testing::AccessibilityBridgeMacSpy>(engine, self);
46}
47@end
48
49namespace flutter::testing {
50
51namespace {
52
53// Returns an engine configured for the text fixture resource configuration.
54FlutterViewController* CreateTestViewController() {
55 NSString* fixtures = @(testing::GetFixturesPath());
56 FlutterDartProject* project = [[FlutterDartProject alloc]
57 initWithAssetsPath:fixtures
58 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
59 return [[AccessibilityBridgeTestViewController alloc] initWithProject:project];
60}
61
62// Test fixture that instantiates and re-uses a single NSWindow across multiple tests.
63//
64// Works around: http://www.openradar.me/FB13291861
65class AccessibilityBridgeMacWindowTest : public AutoreleasePoolTest {
66 public:
67 AccessibilityBridgeMacWindowTest() {
68 if (!gWindow_) {
69 gWindow_ = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
70 styleMask:NSBorderlessWindowMask
71 backing:NSBackingStoreBuffered
72 defer:NO];
73 }
74 }
75
76 NSWindow* GetWindow() const { return gWindow_; }
77
78 private:
79 static NSWindow* gWindow_;
80 FML_DISALLOW_COPY_AND_ASSIGN(AccessibilityBridgeMacWindowTest);
81};
82
83NSWindow* AccessibilityBridgeMacWindowTest::gWindow_ = nil;
84
85// Test-specific name for AutoreleasePoolTest fixture.
86using AccessibilityBridgeMacTest = AutoreleasePoolTest;
87
88} // namespace
89
90TEST_F(AccessibilityBridgeMacWindowTest, SendsAccessibilityCreateNotificationFlutterViewWindow) {
91 FlutterViewController* viewController = CreateTestViewController();
93 NSWindow* expectedTarget = GetWindow();
94 expectedTarget.contentView = viewController.view;
95
96 // Setting up bridge so that the AccessibilityBridgeMacDelegateSpy
97 // can query semantics information from.
99 auto bridge = std::static_pointer_cast<AccessibilityBridgeMacSpy>(
102 root.id = 0;
103 root.flags = static_cast<FlutterSemanticsFlag>(0);
104 root.actions = static_cast<FlutterSemanticsAction>(0);
105 root.text_selection_base = -1;
106 root.text_selection_extent = -1;
107 root.label = "root";
108 root.hint = "";
109 root.value = "";
110 root.increased_value = "";
111 root.decreased_value = "";
112 root.tooltip = "";
113 root.child_count = 0;
114 root.custom_accessibility_actions_count = 0;
115 bridge->AddFlutterSemanticsNodeUpdate(root);
116
117 bridge->CommitUpdates();
118 auto platform_node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
119
120 // Creates a targeted event.
121 ui::AXTree tree;
122 ui::AXNode ax_node(&tree, nullptr, 0, 0);
123 ui::AXNodeData node_data;
124 node_data.id = 0;
125 ax_node.SetData(node_data);
126 std::vector<ui::AXEventIntent> intent;
129 ui::AXEventGenerator::TargetedEvent targeted_event(&ax_node, event_params);
130
131 bridge->OnAccessibilityEvent(targeted_event);
132
133 ASSERT_EQ(bridge->actual_notifications.size(), 1u);
134 auto target = bridge->actual_notifications.find([NSAccessibilityCreatedNotification UTF8String]);
135 ASSERT_NE(target, bridge->actual_notifications.end());
136 EXPECT_EQ(target->second, expectedTarget);
137 [engine shutDownEngine];
138}
139
140// Flutter used to assume that the accessibility root had ID 0.
141// In a multi-view world, each view has its own accessibility root
142// with a globally unique node ID.
143//
144// node1
145// |
146// node2
147TEST_F(AccessibilityBridgeMacWindowTest, NonZeroRootNodeId) {
148 FlutterViewController* viewController = CreateTestViewController();
150 NSWindow* expectedTarget = GetWindow();
151 expectedTarget.contentView = viewController.view;
152
153 // Setting up bridge so that the AccessibilityBridgeMacDelegateSpy
154 // can query semantics information from.
156 auto bridge = std::static_pointer_cast<AccessibilityBridgeMacSpy>(
158
160 std::vector<int32_t> node1_children{2};
161 node1.id = 1;
162 node1.flags = static_cast<FlutterSemanticsFlag>(0);
163 node1.actions = static_cast<FlutterSemanticsAction>(0);
164 node1.text_selection_base = -1;
165 node1.text_selection_extent = -1;
166 node1.label = "node1";
167 node1.hint = "";
168 node1.value = "";
169 node1.increased_value = "";
170 node1.decreased_value = "";
171 node1.tooltip = "";
172 node1.child_count = node1_children.size();
173 node1.children_in_traversal_order = node1_children.data();
174 node1.children_in_hit_test_order = node1_children.data();
176
178 node2.id = 2;
179 node2.flags = static_cast<FlutterSemanticsFlag>(0);
180 node2.actions = static_cast<FlutterSemanticsAction>(0);
181 node2.text_selection_base = -1;
182 node2.text_selection_extent = -1;
183 node2.label = "node2";
184 node2.hint = "";
185 node2.value = "";
186 node2.increased_value = "";
187 node2.decreased_value = "";
188 node2.tooltip = "";
189 node2.child_count = 0;
191
192 bridge->AddFlutterSemanticsNodeUpdate(node1);
193 bridge->AddFlutterSemanticsNodeUpdate(node2);
194 bridge->CommitUpdates();
195
196 // Look up the root node delegate.
197 auto root_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock();
198 ASSERT_TRUE(root_delegate);
199 ASSERT_EQ(root_delegate->GetChildCount(), 1);
200
201 // Look up the child node delegate.
202 auto child_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(2).lock();
203 ASSERT_TRUE(child_delegate);
204 ASSERT_EQ(child_delegate->GetChildCount(), 0);
205
206 // Ensure a node with ID 0 does not exist.
207 auto invalid_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
208 ASSERT_FALSE(invalid_delegate);
209
210 [engine shutDownEngine];
211}
212
213TEST_F(AccessibilityBridgeMacTest, DoesNotSendAccessibilityCreateNotificationWhenHeadless) {
214 FlutterViewController* viewController = CreateTestViewController();
216
217 // Setting up bridge so that the AccessibilityBridgeMacDelegateSpy
218 // can query semantics information from.
220 auto bridge = std::static_pointer_cast<AccessibilityBridgeMacSpy>(
223 root.id = 0;
224 root.flags = static_cast<FlutterSemanticsFlag>(0);
225 root.actions = static_cast<FlutterSemanticsAction>(0);
226 root.text_selection_base = -1;
227 root.text_selection_extent = -1;
228 root.label = "root";
229 root.hint = "";
230 root.value = "";
231 root.increased_value = "";
232 root.decreased_value = "";
233 root.tooltip = "";
234 root.child_count = 0;
235 root.custom_accessibility_actions_count = 0;
236 bridge->AddFlutterSemanticsNodeUpdate(root);
237
238 bridge->CommitUpdates();
239 auto platform_node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
240
241 // Creates a targeted event.
242 ui::AXTree tree;
243 ui::AXNode ax_node(&tree, nullptr, 0, 0);
244 ui::AXNodeData node_data;
245 node_data.id = 0;
246 ax_node.SetData(node_data);
247 std::vector<ui::AXEventIntent> intent;
250 ui::AXEventGenerator::TargetedEvent targeted_event(&ax_node, event_params);
251
252 bridge->OnAccessibilityEvent(targeted_event);
253
254 // Does not send any notification if the engine is headless.
255 EXPECT_EQ(bridge->actual_notifications.size(), 0u);
256 [engine shutDownEngine];
257}
258
259TEST_F(AccessibilityBridgeMacTest, DoesNotSendAccessibilityCreateNotificationWhenNoWindow) {
260 FlutterViewController* viewController = CreateTestViewController();
262
263 // Setting up bridge so that the AccessibilityBridgeMacDelegateSpy
264 // can query semantics information from.
266 auto bridge = std::static_pointer_cast<AccessibilityBridgeMacSpy>(
269 root.id = 0;
270 root.flags = static_cast<FlutterSemanticsFlag>(0);
271 root.actions = static_cast<FlutterSemanticsAction>(0);
272 root.text_selection_base = -1;
273 root.text_selection_extent = -1;
274 root.label = "root";
275 root.hint = "";
276 root.value = "";
277 root.increased_value = "";
278 root.decreased_value = "";
279 root.tooltip = "";
280 root.child_count = 0;
281 root.custom_accessibility_actions_count = 0;
282 bridge->AddFlutterSemanticsNodeUpdate(root);
283
284 bridge->CommitUpdates();
285 auto platform_node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
286
287 // Creates a targeted event.
288 ui::AXTree tree;
289 ui::AXNode ax_node(&tree, nullptr, 0, 0);
290 ui::AXNodeData node_data;
291 node_data.id = 0;
292 ax_node.SetData(node_data);
293 std::vector<ui::AXEventIntent> intent;
296 ui::AXEventGenerator::TargetedEvent targeted_event(&ax_node, event_params);
297
298 bridge->OnAccessibilityEvent(targeted_event);
299
300 // Does not send any notification if the flutter view is not attached to a NSWindow.
301 EXPECT_EQ(bridge->actual_notifications.size(), 0u);
302 [engine shutDownEngine];
303}
304
305} // namespace flutter::testing
std::unordered_map< std::string, gfx::NativeViewAccessible > actual_notifications
void OnAccessibilityEvent(ui::AXEventGenerator::TargetedEvent targeted_event) override
Handle accessibility events generated due to accessibility tree changes. These events are needed to b...
void SetData(const AXNodeData &src)
Definition: ax_node.cc:373
FlutterSemanticsAction
Definition: embedder.h:113
FlutterSemanticsFlag
Definition: embedder.h:172
FlutterEngine engine
Definition: main.cc:68
uint32_t * target
#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition: macros.h:27
std::weak_ptr< flutter::AccessibilityBridgeMac > accessibilityBridge()
FlutterViewController * viewController
TEST_F(DisplayListTest, Defaults)
const char * GetFixturesPath()
Returns the directory containing the test fixture for the target if this target has fixtures configur...
UnimplementedNativeViewAccessible * NativeViewAccessible
string root
Definition: scale_cpu.py:20
Definition: ref_ptr.h:256
const char * increased_value
Definition: embedder.h:1370
const char * tooltip
A textual tooltip attached to the node.
Definition: embedder.h:1397
size_t custom_accessibility_actions_count
The number of custom accessibility action associated with this node.
Definition: embedder.h:1389
const int32_t * children_in_traversal_order
Array of child node IDs in traversal order. Has length child_count.
Definition: embedder.h:1385
const int32_t * children_in_hit_test_order
Array of child node IDs in hit test order. Has length child_count.
Definition: embedder.h:1387
int32_t text_selection_extent
The position at which the text selection terminates.
Definition: embedder.h:1345
FlutterSemanticsAction actions
The set of semantics actions applicable to this node.
Definition: embedder.h:1341
int32_t id
The unique identifier for this node.
Definition: embedder.h:1337
size_t child_count
The number of children this node has.
Definition: embedder.h:1383
const char * decreased_value
Definition: embedder.h:1373
const char * label
A textual description of the node.
Definition: embedder.h:1363
int32_t text_selection_base
The position at which the text selection originates.
Definition: embedder.h:1343
const char * hint
A brief description of the result of performing an action on the node.
Definition: embedder.h:1365
const char * value
A textual description of the current value of the node.
Definition: embedder.h:1367
FlutterSemanticsFlag flags
The set of semantics flags associated with this node.
Definition: embedder.h:1339