| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Allow access to fml::MessageLoop::GetCurrent() in order to flush platform |
| // thread tasks. |
| #define FML_USED_ON_EMBEDDER |
| |
| #include <functional> |
| |
| #include "flutter/fml/macros.h" |
| #include "flutter/fml/message_loop.h" |
| #include "flutter/fml/synchronization/waitable_event.h" |
| #include "flutter/lib/ui/semantics/semantics_node.h" |
| #include "flutter/shell/platform/embedder/embedder.h" |
| #include "flutter/shell/platform/embedder/tests/embedder_config_builder.h" |
| #include "flutter/testing/testing.h" |
| |
| // CREATE_NATIVE_ENTRY is leaky by design |
| // NOLINTBEGIN(clang-analyzer-core.StackAddressEscape) |
| |
| namespace flutter { |
| namespace testing { |
| |
| using EmbedderA11yTest = testing::EmbedderTest; |
| |
| TEST_F(EmbedderA11yTest, A11yTreeIsConsistent) { |
| auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); |
| |
| fml::AutoResetWaitableEvent latch; |
| |
| // Called by the Dart text fixture on the UI thread to signal that the C++ |
| // unittest should resume. |
| context.AddNativeCallback( |
| "SignalNativeTest", CREATE_NATIVE_ENTRY(([&latch](Dart_NativeArguments) { |
| latch.Signal(); |
| }))); |
| |
| // Called by test fixture on UI thread to pass data back to this test. |
| NativeEntry notify_semantics_enabled_callback; |
| context.AddNativeCallback( |
| "NotifySemanticsEnabled", |
| CREATE_NATIVE_ENTRY( |
| ([¬ify_semantics_enabled_callback](Dart_NativeArguments args) { |
| ASSERT_NE(notify_semantics_enabled_callback, nullptr); |
| notify_semantics_enabled_callback(args); |
| }))); |
| |
| NativeEntry notify_accessibility_features_callback; |
| context.AddNativeCallback( |
| "NotifyAccessibilityFeatures", |
| CREATE_NATIVE_ENTRY(( |
| [¬ify_accessibility_features_callback](Dart_NativeArguments args) { |
| ASSERT_NE(notify_accessibility_features_callback, nullptr); |
| notify_accessibility_features_callback(args); |
| }))); |
| |
| NativeEntry notify_semantics_action_callback; |
| context.AddNativeCallback( |
| "NotifySemanticsAction", |
| CREATE_NATIVE_ENTRY( |
| ([¬ify_semantics_action_callback](Dart_NativeArguments args) { |
| ASSERT_NE(notify_semantics_action_callback, nullptr); |
| notify_semantics_action_callback(args); |
| }))); |
| |
| EmbedderConfigBuilder builder(context); |
| builder.SetSoftwareRendererConfig(); |
| builder.SetDartEntrypoint("a11y_main"); |
| |
| auto engine = builder.LaunchEngine(); |
| ASSERT_TRUE(engine.is_valid()); |
| |
| // Wait for initial NotifySemanticsEnabled(false). |
| notify_semantics_enabled_callback = [&](Dart_NativeArguments args) { |
| bool enabled = true; |
| auto handle = Dart_GetNativeBooleanArgument(args, 0, &enabled); |
| ASSERT_FALSE(Dart_IsError(handle)); |
| ASSERT_FALSE(enabled); |
| latch.Signal(); |
| }; |
| latch.Wait(); |
| |
| // Prepare to NotifyAccessibilityFeatures call |
| fml::AutoResetWaitableEvent notify_features_latch; |
| notify_accessibility_features_callback = [&](Dart_NativeArguments args) { |
| bool enabled = true; |
| auto handle = Dart_GetNativeBooleanArgument(args, 0, &enabled); |
| ASSERT_FALSE(Dart_IsError(handle)); |
| ASSERT_FALSE(enabled); |
| notify_features_latch.Signal(); |
| }; |
| |
| // Enable semantics. Wait for NotifySemanticsEnabled(true). |
| notify_semantics_enabled_callback = [&](Dart_NativeArguments args) { |
| bool enabled = false; |
| auto handle = Dart_GetNativeBooleanArgument(args, 0, &enabled); |
| ASSERT_FALSE(Dart_IsError(handle)); |
| ASSERT_TRUE(enabled); |
| latch.Signal(); |
| }; |
| auto result = FlutterEngineUpdateSemanticsEnabled(engine.get(), true); |
| ASSERT_EQ(result, FlutterEngineResult::kSuccess); |
| latch.Wait(); |
| |
| // Wait for initial accessibility features (reduce_motion == false) |
| notify_features_latch.Wait(); |
| |
| // Set accessibility features: (reduce_motion == true) |
| notify_accessibility_features_callback = [&](Dart_NativeArguments args) { |
| bool enabled = false; |
| auto handle = Dart_GetNativeBooleanArgument(args, 0, &enabled); |
| ASSERT_FALSE(Dart_IsError(handle)); |
| ASSERT_TRUE(enabled); |
| latch.Signal(); |
| }; |
| result = FlutterEngineUpdateAccessibilityFeatures( |
| engine.get(), kFlutterAccessibilityFeatureReduceMotion); |
| ASSERT_EQ(result, FlutterEngineResult::kSuccess); |
| latch.Wait(); |
| |
| // Wait for UpdateSemantics callback on platform (current) thread. |
| int node_count = 0; |
| int node_batch_end_count = 0; |
| context.SetSemanticsNodeCallback( |
| [&node_count, &node_batch_end_count](const FlutterSemanticsNode* node) { |
| if (node->id == kFlutterSemanticsNodeIdBatchEnd) { |
| ++node_batch_end_count; |
| } else { |
| ++node_count; |
| ASSERT_EQ(1.0, node->transform.scaleX); |
| ASSERT_EQ(2.0, node->transform.skewX); |
| ASSERT_EQ(3.0, node->transform.transX); |
| ASSERT_EQ(4.0, node->transform.skewY); |
| ASSERT_EQ(5.0, node->transform.scaleY); |
| ASSERT_EQ(6.0, node->transform.transY); |
| ASSERT_EQ(7.0, node->transform.pers0); |
| ASSERT_EQ(8.0, node->transform.pers1); |
| ASSERT_EQ(9.0, node->transform.pers2); |
| |
| if (node->id == 128) { |
| ASSERT_EQ(0x3f3, node->platform_view_id); |
| } else { |
| ASSERT_EQ(0, node->platform_view_id); |
| } |
| } |
| }); |
| |
| int action_count = 0; |
| int action_batch_end_count = 0; |
| context.SetSemanticsCustomActionCallback( |
| [&action_count, |
| &action_batch_end_count](const FlutterSemanticsCustomAction* action) { |
| if (action->id == kFlutterSemanticsCustomActionIdBatchEnd) { |
| ++action_batch_end_count; |
| } else { |
| ++action_count; |
| } |
| }); |
| |
| latch.Wait(); |
| fml::MessageLoop::GetCurrent().RunExpiredTasksNow(); |
| ASSERT_EQ(4, node_count); |
| ASSERT_EQ(1, node_batch_end_count); |
| ASSERT_EQ(1, action_count); |
| ASSERT_EQ(1, action_batch_end_count); |
| |
| // Dispatch a tap to semantics node 42. Wait for NotifySemanticsAction. |
| notify_semantics_action_callback = [&](Dart_NativeArguments args) { |
| int64_t node_id = 0; |
| Dart_GetNativeIntegerArgument(args, 0, &node_id); |
| ASSERT_EQ(42, node_id); |
| |
| int64_t action_id; |
| auto handle = Dart_GetNativeIntegerArgument(args, 1, &action_id); |
| ASSERT_FALSE(Dart_IsError(handle)); |
| ASSERT_EQ(static_cast<int32_t>(flutter::SemanticsAction::kTap), action_id); |
| |
| Dart_Handle semantic_args = Dart_GetNativeArgument(args, 2); |
| int64_t data; |
| Dart_Handle dart_int = Dart_ListGetAt(semantic_args, 0); |
| Dart_IntegerToInt64(dart_int, &data); |
| ASSERT_EQ(2, data); |
| |
| dart_int = Dart_ListGetAt(semantic_args, 1); |
| Dart_IntegerToInt64(dart_int, &data); |
| ASSERT_EQ(1, data); |
| latch.Signal(); |
| }; |
| std::vector<uint8_t> bytes({2, 1}); |
| result = FlutterEngineDispatchSemanticsAction( |
| engine.get(), 42, kFlutterSemanticsActionTap, &bytes[0], bytes.size()); |
| ASSERT_EQ(result, FlutterEngineResult::kSuccess); |
| latch.Wait(); |
| |
| // Disable semantics. Wait for NotifySemanticsEnabled(false). |
| notify_semantics_enabled_callback = [&](Dart_NativeArguments args) { |
| bool enabled = true; |
| Dart_GetNativeBooleanArgument(args, 0, &enabled); |
| ASSERT_FALSE(enabled); |
| latch.Signal(); |
| }; |
| result = FlutterEngineUpdateSemanticsEnabled(engine.get(), false); |
| ASSERT_EQ(result, FlutterEngineResult::kSuccess); |
| latch.Wait(); |
| } |
| |
| } // namespace testing |
| } // namespace flutter |
| |
| // NOLINTEND(clang-analyzer-core.StackAddressEscape) |