Flutter Engine
 
Loading...
Searching...
No Matches
embedder_a11y_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
5// Allow access to fml::MessageLoop::GetCurrent() in order to flush platform
6// thread tasks.
7#define FML_USED_ON_EMBEDDER
8
9#include <functional>
10
11#include "flutter/fml/macros.h"
19
20#include "gmock/gmock.h" // For EXPECT_THAT and matchers
21#include "gtest/gtest.h"
22
23// CREATE_NATIVE_ENTRY is leaky by design
24// NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
25
26namespace flutter {
27namespace testing {
28
30using ::testing::ElementsAre;
31
32#if !defined(OS_FUCHSIA) || (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG)
33constexpr static char kTooltip[] = "tooltip";
34#endif
35
36TEST_F(EmbedderTest, CannotProvideMultipleSemanticsCallbacks) {
37#if defined(OS_FUCHSIA) && (FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_DEBUG)
38 GTEST_SKIP() << "Dart_LoadELF is not implemented on Fuchsia.";
39#else
40 {
41 auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
42 EmbedderConfigBuilder builder(context);
43 builder.SetSurface(DlISize(1, 1));
45 [](const FlutterSemanticsUpdate* update, void* user_data) {};
47 [](const FlutterSemanticsUpdate2* update, void* user_data) {};
48 auto engine = builder.InitializeEngine();
49 ASSERT_FALSE(engine.is_valid());
50 engine.reset();
51 }
52
53 {
54 auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
55 EmbedderConfigBuilder builder(context);
56 builder.SetSurface(DlISize(1, 1));
58 [](const FlutterSemanticsUpdate2* update, void* user_data) {};
60 [](const FlutterSemanticsNode* update, void* user_data) {};
62 [](const FlutterSemanticsCustomAction* update, void* user_data) {};
63 auto engine = builder.InitializeEngine();
64 ASSERT_FALSE(engine.is_valid());
65 engine.reset();
66 }
67
68 {
69 auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
70 EmbedderConfigBuilder builder(context);
71 builder.SetSurface(DlISize(1, 1));
73 [](const FlutterSemanticsUpdate* update, void* user_data) {};
75 [](const FlutterSemanticsNode* update, void* user_data) {};
77 [](const FlutterSemanticsCustomAction* update, void* user_data) {};
78 auto engine = builder.InitializeEngine();
79 ASSERT_FALSE(engine.is_valid());
80 engine.reset();
81 }
82
83 {
84 auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
85 EmbedderConfigBuilder builder(context);
86 builder.SetSurface(DlISize(1, 1));
88 [](const FlutterSemanticsUpdate2* update, void* user_data) {};
90 [](const FlutterSemanticsUpdate* update, void* user_data) {};
92 [](const FlutterSemanticsNode* update, void* user_data) {};
94 [](const FlutterSemanticsCustomAction* update, void* user_data) {};
95 auto engine = builder.InitializeEngine();
96 ASSERT_FALSE(engine.is_valid());
97 engine.reset();
98 }
99#endif
100}
101
102TEST_F(EmbedderA11yTest, A11yTreeIsConsistentUsingV3Callbacks) {
103#if defined(OS_FUCHSIA) && (FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_DEBUG)
104 GTEST_SKIP() << "Dart_LoadELF is not implemented on Fuchsia.";
105#else
106 auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
107
108 fml::AutoResetWaitableEvent signal_native_latch;
109
110 // Called by the Dart text fixture on the UI thread to signal that the C++
111 // unittest should resume.
112 context.AddNativeCallback(
113 "SignalNativeTest",
114 CREATE_NATIVE_ENTRY(([&signal_native_latch](Dart_NativeArguments) {
115 signal_native_latch.Signal();
116 })));
117
118 // Called by test fixture on UI thread to pass data back to this test.
119 NativeEntry notify_semantics_enabled_callback;
120 context.AddNativeCallback(
121 "NotifySemanticsEnabled",
123 ([&notify_semantics_enabled_callback](Dart_NativeArguments args) {
124 ASSERT_NE(notify_semantics_enabled_callback, nullptr);
125 notify_semantics_enabled_callback(args);
126 })));
127
128 NativeEntry notify_accessibility_features_callback;
129 context.AddNativeCallback(
130 "NotifyAccessibilityFeatures",
132 [&notify_accessibility_features_callback](Dart_NativeArguments args) {
133 ASSERT_NE(notify_accessibility_features_callback, nullptr);
134 notify_accessibility_features_callback(args);
135 })));
136
137 NativeEntry notify_semantics_action_callback;
138 context.AddNativeCallback(
139 "NotifySemanticsAction",
141 ([&notify_semantics_action_callback](Dart_NativeArguments args) {
142 ASSERT_NE(notify_semantics_action_callback, nullptr);
143 notify_semantics_action_callback(args);
144 })));
145
146 fml::AutoResetWaitableEvent semantics_update_latch;
147 context.SetSemanticsUpdateCallback2(
148 [&](const FlutterSemanticsUpdate2* update) {
149 ASSERT_EQ(size_t(4), update->node_count);
150 ASSERT_EQ(size_t(1), update->custom_action_count);
151
152 for (size_t i = 0; i < update->node_count; i++) {
153 const FlutterSemanticsNode2* node = update->nodes[i];
154
155 ASSERT_EQ(1.0, node->transform.scaleX);
156 ASSERT_EQ(2.0, node->transform.skewX);
157 ASSERT_EQ(3.0, node->transform.transX);
158 ASSERT_EQ(4.0, node->transform.skewY);
159 ASSERT_EQ(5.0, node->transform.scaleY);
160 ASSERT_EQ(6.0, node->transform.transY);
161 ASSERT_EQ(7.0, node->transform.pers0);
162 ASSERT_EQ(8.0, node->transform.pers1);
163 ASSERT_EQ(9.0, node->transform.pers2);
164 ASSERT_EQ(std::strncmp(kTooltip, node->tooltip, sizeof(kTooltip) - 1),
165 0);
166 ASSERT_EQ(node->heading_level, 0);
167
168 if (node->id == 128) {
169 ASSERT_EQ(0x3f3, node->platform_view_id);
170 } else {
171 ASSERT_NE(kFlutterSemanticsNodeIdBatchEnd, node->id);
172 ASSERT_EQ(0, node->platform_view_id);
173 }
174 }
175
176 semantics_update_latch.Signal();
177 });
178
179 EmbedderConfigBuilder builder(context);
180 builder.SetSurface(DlISize(1, 1));
181 builder.SetDartEntrypoint("a11y_main");
182
183 auto engine = builder.LaunchEngine();
184 ASSERT_TRUE(engine.is_valid());
185
186 // 1: Wait for initial notifySemanticsEnabled(false).
187 fml::AutoResetWaitableEvent notify_semantics_enabled_latch;
188 notify_semantics_enabled_callback = [&](Dart_NativeArguments args) {
189 Dart_Handle exception = nullptr;
190 bool enabled =
192 ASSERT_FALSE(enabled);
193 notify_semantics_enabled_latch.Signal();
194 };
195 notify_semantics_enabled_latch.Wait();
196
197 // Prepare notifyAccessibilityFeatures callback.
198 fml::AutoResetWaitableEvent notify_features_latch;
199 notify_accessibility_features_callback = [&](Dart_NativeArguments args) {
200 Dart_Handle exception = nullptr;
201 bool enabled =
203 ASSERT_FALSE(enabled);
204 notify_features_latch.Signal();
205 };
206
207 // 2: Enable semantics. Wait for notifySemanticsEnabled(true).
208 fml::AutoResetWaitableEvent notify_semantics_enabled_latch_2;
209 notify_semantics_enabled_callback = [&](Dart_NativeArguments args) {
210 Dart_Handle exception = nullptr;
211 bool enabled =
213 ASSERT_TRUE(enabled);
214 notify_semantics_enabled_latch_2.Signal();
215 };
216 auto result = FlutterEngineUpdateSemanticsEnabled(engine.get(), true);
217 ASSERT_EQ(result, FlutterEngineResult::kSuccess);
218 notify_semantics_enabled_latch_2.Wait();
219
220 // 3: Wait for notifyAccessibilityFeatures (reduce_motion == false)
221 notify_features_latch.Wait();
222
223 // 4: Wait for notifyAccessibilityFeatures (reduce_motion == true)
224 fml::AutoResetWaitableEvent notify_features_latch_2;
225 notify_accessibility_features_callback = [&](Dart_NativeArguments args) {
226 Dart_Handle exception = nullptr;
227 bool enabled =
229 ASSERT_TRUE(enabled);
230 notify_features_latch_2.Signal();
231 };
234 ASSERT_EQ(result, FlutterEngineResult::kSuccess);
235 notify_features_latch_2.Wait();
236
237 // 5: Wait for UpdateSemantics callback on platform (current) thread.
238 signal_native_latch.Wait();
240 semantics_update_latch.Wait();
241
242 // 6: Dispatch a tap to semantics node 42. Wait for NotifySemanticsAction.
243 fml::AutoResetWaitableEvent notify_semantics_action_latch;
244 notify_semantics_action_callback = [&](Dart_NativeArguments args) {
245 Dart_Handle exception = nullptr;
246 int64_t node_id =
248 ASSERT_EQ(42, node_id);
249
250 int64_t action_id =
252 ASSERT_EQ(static_cast<int32_t>(flutter::SemanticsAction::kTap), action_id);
253
254 std::vector<int64_t> semantic_args =
256 exception);
257 ASSERT_THAT(semantic_args, ElementsAre(2, 1));
258 notify_semantics_action_latch.Signal();
259 };
260 std::vector<uint8_t> bytes({2, 1});
262 engine.get(), 42, kFlutterSemanticsActionTap, &bytes[0], bytes.size());
263 ASSERT_EQ(result, FlutterEngineResult::kSuccess);
264 notify_semantics_action_latch.Wait();
265
266 // 7: Disable semantics. Wait for NotifySemanticsEnabled(false).
267 fml::AutoResetWaitableEvent notify_semantics_enabled_latch_3;
268 notify_semantics_enabled_callback = [&](Dart_NativeArguments args) {
269 Dart_Handle exception = nullptr;
270 bool enabled =
272 ASSERT_FALSE(enabled);
273 notify_semantics_enabled_latch_3.Signal();
274 };
275 result = FlutterEngineUpdateSemanticsEnabled(engine.get(), false);
276 ASSERT_EQ(result, FlutterEngineResult::kSuccess);
277 notify_semantics_enabled_latch_3.Wait();
278#endif
279}
280
281TEST_F(EmbedderA11yTest, A11yStringAttributes) {
282#if defined(OS_FUCHSIA) && (FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_DEBUG)
283 GTEST_SKIP() << "Dart_LoadELF is not implemented on Fuchsia.";
284#else
285 auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
286
287 fml::AutoResetWaitableEvent signal_native_latch;
288
289 // Called by the Dart text fixture on the UI thread to signal that the C++
290 // unittest should resume.
291 context.AddNativeCallback(
292 "SignalNativeTest",
293 CREATE_NATIVE_ENTRY(([&signal_native_latch](Dart_NativeArguments) {
294 signal_native_latch.Signal();
295 })));
296
297 fml::AutoResetWaitableEvent semantics_update_latch;
298 context.SetSemanticsUpdateCallback2(
299 [&](const FlutterSemanticsUpdate2* update) {
300 ASSERT_EQ(update->node_count, size_t(1));
301 ASSERT_EQ(update->custom_action_count, size_t(0));
302
303 auto node = update->nodes[0];
304
305 // Verify identifier
306 {
307 ASSERT_EQ(std::string(node->identifier), "identifier");
308 }
309
310 // Verify label
311 {
312 ASSERT_EQ(std::string(node->label), "What is the meaning of life?");
313 ASSERT_EQ(node->label_attribute_count, size_t(2));
314
315 ASSERT_EQ(node->label_attributes[0]->start, size_t(0));
316 ASSERT_EQ(node->label_attributes[0]->end, size_t(28));
317 ASSERT_EQ(node->label_attributes[0]->type,
319 ASSERT_EQ(std::string(node->label_attributes[0]->locale->locale),
320 "en");
321
322 ASSERT_EQ(node->label_attributes[1]->start, size_t(0));
323 ASSERT_EQ(node->label_attributes[1]->end, size_t(1));
324 ASSERT_EQ(node->label_attributes[1]->type,
326 }
327
328 // Verify hint
329 {
330 ASSERT_EQ(std::string(node->hint), "It's a number");
331 ASSERT_EQ(node->hint_attribute_count, size_t(2));
332
333 ASSERT_EQ(node->hint_attributes[0]->start, size_t(0));
334 ASSERT_EQ(node->hint_attributes[0]->end, size_t(1));
335 ASSERT_EQ(node->hint_attributes[0]->type,
337 ASSERT_EQ(std::string(node->hint_attributes[0]->locale->locale),
338 "en");
339
340 ASSERT_EQ(node->hint_attributes[1]->start, size_t(2));
341 ASSERT_EQ(node->hint_attributes[1]->end, size_t(3));
342 ASSERT_EQ(node->hint_attributes[1]->type,
344 ASSERT_EQ(std::string(node->hint_attributes[1]->locale->locale),
345 "fr");
346 }
347
348 // Verify value
349 {
350 ASSERT_EQ(std::string(node->value), "42");
351 ASSERT_EQ(node->value_attribute_count, size_t(1));
352
353 ASSERT_EQ(node->value_attributes[0]->start, size_t(0));
354 ASSERT_EQ(node->value_attributes[0]->end, size_t(2));
355 ASSERT_EQ(node->value_attributes[0]->type,
357 ASSERT_EQ(std::string(node->value_attributes[0]->locale->locale),
358 "en-US");
359 }
360
361 // Verify increased value
362 {
363 ASSERT_EQ(std::string(node->increased_value), "43");
364 ASSERT_EQ(node->increased_value_attribute_count, size_t(2));
365
366 ASSERT_EQ(node->increased_value_attributes[0]->start, size_t(0));
367 ASSERT_EQ(node->increased_value_attributes[0]->end, size_t(1));
368 ASSERT_EQ(node->increased_value_attributes[0]->type,
370
371 ASSERT_EQ(node->increased_value_attributes[1]->start, size_t(1));
372 ASSERT_EQ(node->increased_value_attributes[1]->end, size_t(2));
373 ASSERT_EQ(node->increased_value_attributes[1]->type,
375 }
376
377 // Verify decreased value
378 {
379 ASSERT_EQ(std::string(node->decreased_value), "41");
380 ASSERT_EQ(node->decreased_value_attribute_count, size_t(0));
381 ASSERT_EQ(node->decreased_value_attributes, nullptr);
382 }
383
384 semantics_update_latch.Signal();
385 });
386
387 EmbedderConfigBuilder builder(context);
388 builder.SetSurface(DlISize(1, 1));
389 builder.SetDartEntrypoint("a11y_string_attributes");
390
391 auto engine = builder.LaunchEngine();
392 ASSERT_TRUE(engine.is_valid());
393
394 // 1: Enable semantics.
395 auto result = FlutterEngineUpdateSemanticsEnabled(engine.get(), true);
396 ASSERT_EQ(result, FlutterEngineResult::kSuccess);
397
398 // 2: Wait for semantics update callback on platform (current) thread.
399 signal_native_latch.Wait();
401 semantics_update_latch.Wait();
402#endif
403}
404
405TEST_F(EmbedderA11yTest, A11yTreeIsConsistentUsingV2Callbacks) {
406#if defined(OS_FUCHSIA) && (FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_DEBUG)
407 GTEST_SKIP() << "Dart_LoadELF is not implemented on Fuchsia.";
408#else
409 auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
410
411 fml::AutoResetWaitableEvent signal_native_latch;
412
413 // Called by the Dart text fixture on the UI thread to signal that the C++
414 // unittest should resume.
415 context.AddNativeCallback(
416 "SignalNativeTest",
417 CREATE_NATIVE_ENTRY(([&signal_native_latch](Dart_NativeArguments) {
418 signal_native_latch.Signal();
419 })));
420
421 // Called by test fixture on UI thread to pass data back to this test.
422 NativeEntry notify_semantics_enabled_callback;
423 context.AddNativeCallback(
424 "NotifySemanticsEnabled",
426 ([&notify_semantics_enabled_callback](Dart_NativeArguments args) {
427 ASSERT_NE(notify_semantics_enabled_callback, nullptr);
428 notify_semantics_enabled_callback(args);
429 })));
430
431 NativeEntry notify_accessibility_features_callback;
432 context.AddNativeCallback(
433 "NotifyAccessibilityFeatures",
435 [&notify_accessibility_features_callback](Dart_NativeArguments args) {
436 ASSERT_NE(notify_accessibility_features_callback, nullptr);
437 notify_accessibility_features_callback(args);
438 })));
439
440 NativeEntry notify_semantics_action_callback;
441 context.AddNativeCallback(
442 "NotifySemanticsAction",
444 ([&notify_semantics_action_callback](Dart_NativeArguments args) {
445 ASSERT_NE(notify_semantics_action_callback, nullptr);
446 notify_semantics_action_callback(args);
447 })));
448
449 fml::AutoResetWaitableEvent semantics_update_latch;
450 context.SetSemanticsUpdateCallback([&](const FlutterSemanticsUpdate* update) {
451 ASSERT_EQ(size_t(4), update->nodes_count);
452 ASSERT_EQ(size_t(1), update->custom_actions_count);
453
454 for (size_t i = 0; i < update->nodes_count; i++) {
455 const FlutterSemanticsNode* node = update->nodes + i;
456
457 ASSERT_EQ(1.0, node->transform.scaleX);
458 ASSERT_EQ(2.0, node->transform.skewX);
459 ASSERT_EQ(3.0, node->transform.transX);
460 ASSERT_EQ(4.0, node->transform.skewY);
461 ASSERT_EQ(5.0, node->transform.scaleY);
462 ASSERT_EQ(6.0, node->transform.transY);
463 ASSERT_EQ(7.0, node->transform.pers0);
464 ASSERT_EQ(8.0, node->transform.pers1);
465 ASSERT_EQ(9.0, node->transform.pers2);
466 ASSERT_EQ(std::strncmp(kTooltip, node->tooltip, sizeof(kTooltip) - 1), 0);
467 ASSERT_EQ(node->heading_level, 0);
468
469 if (node->id == 128) {
470 ASSERT_EQ(0x3f3, node->platform_view_id);
471 } else {
472 ASSERT_NE(kFlutterSemanticsNodeIdBatchEnd, node->id);
473 ASSERT_EQ(0, node->platform_view_id);
474 }
475 }
476
477 semantics_update_latch.Signal();
478 });
479
480 EmbedderConfigBuilder builder(context);
481 builder.SetSurface(DlISize(1, 1));
482 builder.SetDartEntrypoint("a11y_main");
483
484 auto engine = builder.LaunchEngine();
485 ASSERT_TRUE(engine.is_valid());
486
487 // 1: Wait for initial notifySemanticsEnabled(false).
488 fml::AutoResetWaitableEvent notify_semantics_enabled_latch;
489 notify_semantics_enabled_callback = [&](Dart_NativeArguments args) {
490 Dart_Handle exception = nullptr;
491 bool enabled =
493 ASSERT_FALSE(enabled);
494 notify_semantics_enabled_latch.Signal();
495 };
496 notify_semantics_enabled_latch.Wait();
497
498 // Prepare notifyAccessibilityFeatures callback.
499 fml::AutoResetWaitableEvent notify_features_latch;
500 notify_accessibility_features_callback = [&](Dart_NativeArguments args) {
501 Dart_Handle exception = nullptr;
502 bool enabled =
504 ASSERT_FALSE(enabled);
505 notify_features_latch.Signal();
506 };
507
508 // 2: Enable semantics. Wait for notifySemanticsEnabled(true).
509 fml::AutoResetWaitableEvent notify_semantics_enabled_latch_2;
510 notify_semantics_enabled_callback = [&](Dart_NativeArguments args) {
511 Dart_Handle exception = nullptr;
512 bool enabled =
514 ASSERT_TRUE(enabled);
515 notify_semantics_enabled_latch_2.Signal();
516 };
517 auto result = FlutterEngineUpdateSemanticsEnabled(engine.get(), true);
518 ASSERT_EQ(result, FlutterEngineResult::kSuccess);
519 notify_semantics_enabled_latch_2.Wait();
520
521 // 3: Wait for notifyAccessibilityFeatures (reduce_motion == false)
522 notify_features_latch.Wait();
523
524 // 4: Wait for notifyAccessibilityFeatures (reduce_motion == true)
525 fml::AutoResetWaitableEvent notify_features_latch_2;
526 notify_accessibility_features_callback = [&](Dart_NativeArguments args) {
527 Dart_Handle exception = nullptr;
528 bool enabled =
530 ASSERT_TRUE(enabled);
531 notify_features_latch_2.Signal();
532 };
535 ASSERT_EQ(result, FlutterEngineResult::kSuccess);
536 notify_features_latch_2.Wait();
537
538 // 5: Wait for UpdateSemantics callback on platform (current) thread.
539 signal_native_latch.Wait();
541 semantics_update_latch.Wait();
542
543 // 6: Dispatch a tap to semantics node 42. Wait for NotifySemanticsAction.
544 fml::AutoResetWaitableEvent notify_semantics_action_latch;
545 notify_semantics_action_callback = [&](Dart_NativeArguments args) {
546 Dart_Handle exception = nullptr;
547 int64_t node_id =
549 ASSERT_EQ(42, node_id);
550
551 int64_t action_id =
553 ASSERT_EQ(static_cast<int32_t>(flutter::SemanticsAction::kTap), action_id);
554
555 std::vector<int64_t> semantic_args =
557 exception);
558 ASSERT_THAT(semantic_args, ElementsAre(2, 1));
559 notify_semantics_action_latch.Signal();
560 };
561 std::vector<uint8_t> bytes({2, 1});
563 engine.get(), 42, kFlutterSemanticsActionTap, &bytes[0], bytes.size());
564 ASSERT_EQ(result, FlutterEngineResult::kSuccess);
565 notify_semantics_action_latch.Wait();
566
567 // 7: Disable semantics. Wait for NotifySemanticsEnabled(false).
568 fml::AutoResetWaitableEvent notify_semantics_enabled_latch_3;
569 notify_semantics_enabled_callback = [&](Dart_NativeArguments args) {
570 Dart_Handle exception = nullptr;
571 bool enabled =
573 ASSERT_FALSE(enabled);
574 notify_semantics_enabled_latch_3.Signal();
575 };
576 result = FlutterEngineUpdateSemanticsEnabled(engine.get(), false);
577 ASSERT_EQ(result, FlutterEngineResult::kSuccess);
578 notify_semantics_enabled_latch_3.Wait();
579#endif
580}
581
582TEST_F(EmbedderA11yTest, A11yTreeIsConsistentUsingV1Callbacks) {
583#if defined(OS_FUCHSIA) && (FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_DEBUG)
584 GTEST_SKIP() << "Dart_LoadELF is not implemented on Fuchsia.";
585#else
586 auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
587
588 fml::AutoResetWaitableEvent signal_native_latch;
589
590 // Called by the Dart text fixture on the UI thread to signal that the C++
591 // unittest should resume.
592 context.AddNativeCallback(
593 "SignalNativeTest",
594 CREATE_NATIVE_ENTRY(([&signal_native_latch](Dart_NativeArguments) {
595 signal_native_latch.Signal();
596 })));
597
598 // Called by test fixture on UI thread to pass data back to this test.
599 NativeEntry notify_semantics_enabled_callback;
600 context.AddNativeCallback(
601 "NotifySemanticsEnabled",
603 ([&notify_semantics_enabled_callback](Dart_NativeArguments args) {
604 ASSERT_NE(notify_semantics_enabled_callback, nullptr);
605 notify_semantics_enabled_callback(args);
606 })));
607
608 NativeEntry notify_accessibility_features_callback;
609 context.AddNativeCallback(
610 "NotifyAccessibilityFeatures",
612 [&notify_accessibility_features_callback](Dart_NativeArguments args) {
613 ASSERT_NE(notify_accessibility_features_callback, nullptr);
614 notify_accessibility_features_callback(args);
615 })));
616
617 NativeEntry notify_semantics_action_callback;
618 context.AddNativeCallback(
619 "NotifySemanticsAction",
621 ([&notify_semantics_action_callback](Dart_NativeArguments args) {
622 ASSERT_NE(notify_semantics_action_callback, nullptr);
623 notify_semantics_action_callback(args);
624 })));
625
626 fml::AutoResetWaitableEvent semantics_node_latch;
627 fml::AutoResetWaitableEvent semantics_action_latch;
628
629 int node_batch_end_count = 0;
630 int action_batch_end_count = 0;
631
632 int node_count = 0;
633 context.SetSemanticsNodeCallback([&](const FlutterSemanticsNode* node) {
634 if (node->id == kFlutterSemanticsNodeIdBatchEnd) {
635 ++node_batch_end_count;
636 semantics_node_latch.Signal();
637 } else {
638 // Batches should be completed after all nodes are received.
639 ASSERT_EQ(0, node_batch_end_count);
640 ASSERT_EQ(0, action_batch_end_count);
641
642 ++node_count;
643 ASSERT_EQ(1.0, node->transform.scaleX);
644 ASSERT_EQ(2.0, node->transform.skewX);
645 ASSERT_EQ(3.0, node->transform.transX);
646 ASSERT_EQ(4.0, node->transform.skewY);
647 ASSERT_EQ(5.0, node->transform.scaleY);
648 ASSERT_EQ(6.0, node->transform.transY);
649 ASSERT_EQ(7.0, node->transform.pers0);
650 ASSERT_EQ(8.0, node->transform.pers1);
651 ASSERT_EQ(9.0, node->transform.pers2);
652 ASSERT_EQ(std::strncmp(kTooltip, node->tooltip, sizeof(kTooltip) - 1), 0);
653
654 if (node->id == 128) {
655 ASSERT_EQ(0x3f3, node->platform_view_id);
656 } else {
657 ASSERT_EQ(0, node->platform_view_id);
658 }
659 }
660 });
661
662 int action_count = 0;
663 context.SetSemanticsCustomActionCallback(
666 ++action_batch_end_count;
667 semantics_action_latch.Signal();
668 } else {
669 // Batches should be completed after all actions are received.
670 ASSERT_EQ(0, node_batch_end_count);
671 ASSERT_EQ(0, action_batch_end_count);
672
673 ++action_count;
674 }
675 });
676
677 EmbedderConfigBuilder builder(context);
678 builder.SetSurface(DlISize(1, 1));
679 builder.SetDartEntrypoint("a11y_main");
680
681 auto engine = builder.LaunchEngine();
682 ASSERT_TRUE(engine.is_valid());
683
684 // 1: Wait for initial notifySemanticsEnabled(false).
685 fml::AutoResetWaitableEvent notify_semantics_enabled_latch;
686 notify_semantics_enabled_callback = [&](Dart_NativeArguments args) {
687 Dart_Handle exception = nullptr;
688 bool enabled =
690 ASSERT_FALSE(enabled);
691 notify_semantics_enabled_latch.Signal();
692 };
693 notify_semantics_enabled_latch.Wait();
694
695 // Prepare notifyAccessibilityFeatures callback.
696 fml::AutoResetWaitableEvent notify_features_latch;
697 notify_accessibility_features_callback = [&](Dart_NativeArguments args) {
698 Dart_Handle exception = nullptr;
699 bool enabled =
701 ASSERT_FALSE(enabled);
702 notify_features_latch.Signal();
703 };
704
705 // 2: Enable semantics. Wait for notifySemanticsEnabled(true).
706 fml::AutoResetWaitableEvent notify_semantics_enabled_latch_2;
707 notify_semantics_enabled_callback = [&](Dart_NativeArguments args) {
708 Dart_Handle exception = nullptr;
709 bool enabled =
711 ASSERT_TRUE(enabled);
712 notify_semantics_enabled_latch_2.Signal();
713 };
714 auto result = FlutterEngineUpdateSemanticsEnabled(engine.get(), true);
715 ASSERT_EQ(result, FlutterEngineResult::kSuccess);
716 notify_semantics_enabled_latch_2.Wait();
717
718 // 3: Wait for notifyAccessibilityFeatures (reduce_motion == false)
719 notify_features_latch.Wait();
720
721 // 4: Wait for notifyAccessibilityFeatures (reduce_motion == true)
722 fml::AutoResetWaitableEvent notify_features_latch_2;
723 notify_accessibility_features_callback = [&](Dart_NativeArguments args) {
724 Dart_Handle exception = nullptr;
725 bool enabled =
727 ASSERT_TRUE(enabled);
728 notify_features_latch_2.Signal();
729 };
732 ASSERT_EQ(result, FlutterEngineResult::kSuccess);
733 notify_features_latch_2.Wait();
734
735 // 5: Wait for UpdateSemantics callback on platform (current) thread.
736 signal_native_latch.Wait();
738 semantics_node_latch.Wait();
739 semantics_action_latch.Wait();
740 ASSERT_EQ(4, node_count);
741 ASSERT_EQ(1, node_batch_end_count);
742 ASSERT_EQ(1, action_count);
743 ASSERT_EQ(1, action_batch_end_count);
744
745 // 6: Dispatch a tap to semantics node 42. Wait for NotifySemanticsAction.
746 fml::AutoResetWaitableEvent notify_semantics_action_latch;
747 notify_semantics_action_callback = [&](Dart_NativeArguments args) {
748 Dart_Handle exception = nullptr;
749 int64_t node_id =
751 ASSERT_EQ(42, node_id);
752
753 int64_t action_id =
755 ASSERT_EQ(static_cast<int32_t>(flutter::SemanticsAction::kTap), action_id);
756
757 std::vector<int64_t> semantic_args =
759 exception);
760 ASSERT_THAT(semantic_args, ElementsAre(2, 1));
761 notify_semantics_action_latch.Signal();
762 };
763 std::vector<uint8_t> bytes({2, 1});
765 engine.get(), 42, kFlutterSemanticsActionTap, &bytes[0], bytes.size());
766 ASSERT_EQ(result, FlutterEngineResult::kSuccess);
767 notify_semantics_action_latch.Wait();
768
769 // 7: Disable semantics. Wait for NotifySemanticsEnabled(false).
770 fml::AutoResetWaitableEvent notify_semantics_enabled_latch_3;
771 notify_semantics_enabled_callback = [&](Dart_NativeArguments args) {
772 Dart_Handle exception = nullptr;
773 bool enabled =
775 ASSERT_FALSE(enabled);
776 notify_semantics_enabled_latch_3.Signal();
777 };
778 result = FlutterEngineUpdateSemanticsEnabled(engine.get(), false);
779 ASSERT_EQ(result, FlutterEngineResult::kSuccess);
780 notify_semantics_enabled_latch_3.Wait();
781#endif
782}
783
784TEST_F(EmbedderA11yTest, A11yTreesAreConsistentWithMultipleViews) {
785#if defined(OS_FUCHSIA) && (FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_DEBUG)
786 GTEST_SKIP() << "Dart_LoadELF is not implemented on Fuchsia.";
787#else
788 auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
789
790 fml::AutoResetWaitableEvent signal_native_latch;
791
792 // Called by the Dart text fixture on the UI thread to signal that the C++
793 // unittest should resume.
794 context.AddNativeCallback(
795 "SignalNativeTest",
796 CREATE_NATIVE_ENTRY(([&signal_native_latch](Dart_NativeArguments) {
797 signal_native_latch.Signal();
798 })));
799
800 // Called by test fixture on UI thread to pass data back to this test.
801 NativeEntry notify_semantics_enabled_callback;
802 context.AddNativeCallback(
803 "NotifySemanticsEnabled",
805 ([&notify_semantics_enabled_callback](Dart_NativeArguments args) {
806 ASSERT_NE(notify_semantics_enabled_callback, nullptr);
807 notify_semantics_enabled_callback(args);
808 })));
809
810 NativeEntry notify_accessibility_features_callback;
811 context.AddNativeCallback(
812 "NotifyAccessibilityFeatures",
814 [&notify_accessibility_features_callback](Dart_NativeArguments args) {
815 ASSERT_NE(notify_accessibility_features_callback, nullptr);
816 notify_accessibility_features_callback(args);
817 })));
818
819 int num_times_set_semantics_update_callback2_called = 0;
820 fml::AutoResetWaitableEvent semantics_update_latch;
821 context.SetSemanticsUpdateCallback2(
822 [&](const FlutterSemanticsUpdate2* update) {
823 num_times_set_semantics_update_callback2_called++;
824 ASSERT_EQ(size_t(1), update->node_count);
825
826 for (size_t i = 0; i < update->node_count; i++) {
827 const FlutterSemanticsNode2* node = update->nodes[i];
828
829 // The node ID should be the view_id + 1
830 ASSERT_EQ(node->id, update->view_id + 1);
831 ASSERT_EQ(1.0, node->transform.scaleX);
832 ASSERT_EQ(2.0, node->transform.skewX);
833 ASSERT_EQ(3.0, node->transform.transX);
834 ASSERT_EQ(4.0, node->transform.skewY);
835 ASSERT_EQ(5.0, node->transform.scaleY);
836 ASSERT_EQ(6.0, node->transform.transY);
837 ASSERT_EQ(7.0, node->transform.pers0);
838 ASSERT_EQ(8.0, node->transform.pers1);
839 ASSERT_EQ(9.0, node->transform.pers2);
840 ASSERT_EQ(std::strncmp(kTooltip, node->tooltip, sizeof(kTooltip) - 1),
841 0);
842 }
843
844 if (num_times_set_semantics_update_callback2_called == 3) {
845 semantics_update_latch.Signal();
846 }
847 });
848
849 EmbedderConfigBuilder builder(context);
850 builder.SetSurface(DlISize(1, 1));
851 builder.SetDartEntrypoint("a11y_main_multi_view");
852
853 auto engine = builder.LaunchEngine();
854 ASSERT_TRUE(engine.is_valid());
855
856 // 1: Wait for initial notifySemanticsEnabled(false).
857 fml::AutoResetWaitableEvent notify_semantics_enabled_latch;
858 notify_semantics_enabled_callback = [&](Dart_NativeArguments args) {
859 Dart_Handle exception = nullptr;
860 bool enabled =
862 ASSERT_FALSE(enabled);
863 notify_semantics_enabled_latch.Signal();
864 };
865 notify_semantics_enabled_latch.Wait();
866
867 const int64_t first_view_id = 1;
868 const int64_t second_view_id = 2;
869
870 // 2. Add the first view and wait for the add view callback.
871 FlutterWindowMetricsEvent window_metrics_event;
872 window_metrics_event.struct_size = sizeof(FlutterWindowMetricsEvent);
873 window_metrics_event.width = 100;
874 window_metrics_event.height = 100;
875 window_metrics_event.pixel_ratio = 1.0;
876 window_metrics_event.left = 0;
877 window_metrics_event.top = 0;
878 window_metrics_event.physical_view_inset_top = 0.0;
879 window_metrics_event.physical_view_inset_right = 0.0;
880 window_metrics_event.physical_view_inset_bottom = 0.0;
881 window_metrics_event.physical_view_inset_left = 0.0;
882 window_metrics_event.display_id = 0;
883 window_metrics_event.view_id = first_view_id;
884
885 FlutterAddViewInfo add_view_info;
886 add_view_info.struct_size = sizeof(FlutterAddViewInfo);
887 add_view_info.view_id = first_view_id;
888 add_view_info.view_metrics = &window_metrics_event;
889 fml::AutoResetWaitableEvent notify_add_view_latch;
890 add_view_info.user_data = &notify_add_view_latch;
891 add_view_info.add_view_callback = [](const FlutterAddViewResult* result) {
892 EXPECT_TRUE(result->added);
893 auto latch =
894 reinterpret_cast<fml::AutoResetWaitableEvent*>(result->user_data);
895 latch->Signal();
896 };
897 FlutterEngineAddView(engine.get(), &add_view_info);
898 notify_add_view_latch.Wait();
899
900 // 3. Add the second view and wait for the add view callback.
901 add_view_info.view_id = second_view_id;
902 window_metrics_event.view_id = second_view_id;
903 add_view_info.add_view_callback = [](const FlutterAddViewResult* result) {
904 EXPECT_TRUE(result->added);
905 auto latch =
906 reinterpret_cast<fml::AutoResetWaitableEvent*>(result->user_data);
907 latch->Signal();
908 };
909 FlutterEngineAddView(engine.get(), &add_view_info);
910 notify_add_view_latch.Wait();
911
912 // Prepare notifyAccessibilityFeatures callback.
913 fml::AutoResetWaitableEvent notify_features_latch;
914 notify_accessibility_features_callback = [&](Dart_NativeArguments args) {
915 Dart_Handle exception = nullptr;
916 bool enabled =
918 ASSERT_FALSE(enabled);
919 notify_features_latch.Signal();
920 };
921
922 // 4: Enable semantics. Wait for notifySemanticsEnabled(true).
923 fml::AutoResetWaitableEvent notify_semantics_enabled_latch_2;
924 notify_semantics_enabled_callback = [&](Dart_NativeArguments args) {
925 Dart_Handle exception = nullptr;
926 bool enabled =
928 ASSERT_TRUE(enabled);
929 notify_semantics_enabled_latch_2.Signal();
930 };
931 auto result = FlutterEngineUpdateSemanticsEnabled(engine.get(), true);
932 ASSERT_EQ(result, FlutterEngineResult::kSuccess);
933 notify_semantics_enabled_latch_2.Wait();
934
935 // 5: Wait for notifyAccessibilityFeatures (reduce_motion == false)
936 notify_features_latch.Wait();
937
938 // 6: Wait for UpdateSemantics callback on platform (current) thread.
939 // for all pending updates. Expect that it is called 3 times (once for
940 // the implicit view and two more times for the views that were manually
941 // added).
942 signal_native_latch.Wait();
944 semantics_update_latch.Wait();
945 EXPECT_EQ(num_times_set_semantics_update_callback2_called, 3);
946
947 // 7: Disable semantics. Wait for NotifySemanticsEnabled(false).
948 fml::AutoResetWaitableEvent notify_semantics_enabled_latch_3;
949 notify_semantics_enabled_callback = [&](Dart_NativeArguments args) {
950 Dart_Handle exception = nullptr;
951 bool enabled =
953 ASSERT_FALSE(enabled);
954 notify_semantics_enabled_latch_3.Signal();
955 };
956 result = FlutterEngineUpdateSemanticsEnabled(engine.get(), false);
957 ASSERT_EQ(result, FlutterEngineResult::kSuccess);
958 notify_semantics_enabled_latch_3.Wait();
959#endif
960}
961
962} // namespace testing
963} // namespace flutter
964
965// NOLINTEND(clang-analyzer-core.StackAddressEscape)
static FML_EMBEDDER_ONLY MessageLoop & GetCurrent()
void RunExpiredTasksNow()
const int32_t kFlutterSemanticsNodeIdBatchEnd
Definition embedder.cc:107
const int32_t kFlutterSemanticsCustomActionIdBatchEnd
Definition embedder.cc:108
FlutterEngineResult FlutterEngineDispatchSemanticsAction(FLUTTER_API_SYMBOL(FlutterEngine) engine, uint64_t node_id, FlutterSemanticsAction action, const uint8_t *data, size_t data_length)
Dispatch a semantics action to the specified semantics node in the implicit view.
Definition embedder.cc:3196
FLUTTER_EXPORT FlutterEngineResult FlutterEngineAddView(FLUTTER_API_SYMBOL(FlutterEngine) engine, const FlutterAddViewInfo *info)
Adds a view.
Definition embedder.cc:2500
FlutterEngineResult FlutterEngineUpdateAccessibilityFeatures(FLUTTER_API_SYMBOL(FlutterEngine) engine, FlutterAccessibilityFeature flags)
Sets additional accessibility features.
Definition embedder.cc:3182
FlutterEngineResult FlutterEngineUpdateSemanticsEnabled(FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled)
Enable or disable accessibility semantics.
Definition embedder.cc:3168
@ kFlutterAccessibilityFeatureReduceMotion
Definition embedder.h:103
@ kSuccess
Definition embedder.h:73
@ kFlutterSemanticsActionTap
Definition embedder.h:118
@ kSpellOut
Definition embedder.h:1474
@ kLocale
Definition embedder.h:1476
FlutterEngine engine
Definition main.cc:84
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
TEST_F(DisplayListTest, Defaults)
static constexpr char kTooltip[]
std::function< void(Dart_NativeArguments)> NativeEntry
impeller::ISize32 DlISize
FlutterAddViewCallback add_view_callback
Definition embedder.h:1117
FlutterViewId view_id
The identifier for the view to add. This must be unique.
Definition embedder.h:1097
const FlutterWindowMetricsEvent * view_metrics
Definition embedder.h:1102
FlutterUpdateSemanticsCallback update_semantics_callback
Definition embedder.h:2737
FlutterUpdateSemanticsCallback2 update_semantics_callback2
Definition embedder.h:2749
FlutterUpdateSemanticsCustomActionCallback update_semantics_custom_action_callback
Definition embedder.h:2585
FlutterUpdateSemanticsNodeCallback update_semantics_node_callback
Definition embedder.h:2564
const char * tooltip
A textual tooltip attached to the node.
Definition embedder.h:1677
FlutterPlatformViewIdentifier platform_view_id
Definition embedder.h:1675
int32_t id
The unique identifier for this node.
Definition embedder.h:1613
FlutterTransformation transform
Definition embedder.h:1661
FlutterTransformation transform
Definition embedder.h:1578
const char * tooltip
A textual tooltip attached to the node.
Definition embedder.h:1594
FlutterPlatformViewIdentifier platform_view_id
Definition embedder.h:1592
int32_t id
The unique identifier for this node.
Definition embedder.h:1534
A batch of updates to semantics nodes and custom actions.
Definition embedder.h:1796
size_t node_count
The number of semantics node updates.
Definition embedder.h:1800
size_t custom_action_count
The number of semantics custom action updates.
Definition embedder.h:1804
FlutterSemanticsNode2 ** nodes
Definition embedder.h:1802
FlutterViewId view_id
Definition embedder.h:1809
FlutterSemanticsNode * nodes
Definition embedder.h:1788
size_t nodes_count
The number of semantics node updates.
Definition embedder.h:1786
size_t custom_actions_count
The number of semantics custom action updates.
Definition embedder.h:1790
double transY
vertical translation
Definition embedder.h:400
double pers2
perspective scale factor
Definition embedder.h:406
double skewX
horizontal skew factor
Definition embedder.h:392
double pers0
input x-axis perspective factor
Definition embedder.h:402
double scaleX
horizontal scale factor
Definition embedder.h:390
double skewY
vertical skew factor
Definition embedder.h:396
double scaleY
vertical scale factor
Definition embedder.h:398
double pers1
input y-axis perspective factor
Definition embedder.h:404
double transX
horizontal translation
Definition embedder.h:394
FlutterEngineDisplayId display_id
The identifier of the display the view is rendering on.
Definition embedder.h:1067
size_t struct_size
The size of this struct. Must be sizeof(FlutterWindowMetricsEvent).
Definition embedder.h:1047
size_t height
Physical height of the window.
Definition embedder.h:1051
size_t left
Horizontal physical location of the left side of the window on the screen.
Definition embedder.h:1055
double physical_view_inset_left
Left inset of window.
Definition embedder.h:1065
int64_t view_id
The view that this event is describing.
Definition embedder.h:1069
double pixel_ratio
Scale factor for the physical screen.
Definition embedder.h:1053
double physical_view_inset_top
Top inset of window.
Definition embedder.h:1059
double physical_view_inset_bottom
Bottom inset of window.
Definition embedder.h:1063
size_t top
Vertical physical location of the top of the window on the screen.
Definition embedder.h:1057
double physical_view_inset_right
Right inset of window.
Definition embedder.h:1061
size_t width
Physical width of the window.
Definition embedder.h:1049
#define CREATE_NATIVE_ENTRY(native_entry)