| // 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. |
| |
| #include "flutter/shell/platform/windows/public/flutter_windows.h" |
| |
| #include <dxgi.h> |
| #include <wrl/client.h> |
| #include <thread> |
| |
| #include "flutter/fml/synchronization/count_down_latch.h" |
| #include "flutter/fml/synchronization/waitable_event.h" |
| #include "flutter/shell/platform/common/app_lifecycle_state.h" |
| #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" |
| #include "flutter/shell/platform/windows/egl/manager.h" |
| #include "flutter/shell/platform/windows/testing/engine_modifier.h" |
| #include "flutter/shell/platform/windows/testing/windows_test.h" |
| #include "flutter/shell/platform/windows/testing/windows_test_config_builder.h" |
| #include "flutter/shell/platform/windows/testing/windows_test_context.h" |
| #include "flutter/shell/platform/windows/windows_lifecycle_manager.h" |
| #include "flutter/testing/stream_capture.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "third_party/tonic/converter/dart_converter.h" |
| |
| namespace flutter { |
| namespace testing { |
| |
| namespace { |
| |
| // An EGL manager that initializes EGL but fails to create surfaces. |
| class HalfBrokenEGLManager : public egl::Manager { |
| public: |
| HalfBrokenEGLManager() : egl::Manager(/*enable_impeller = */ false) {} |
| |
| std::unique_ptr<egl::WindowSurface> |
| CreateWindowSurface(HWND hwnd, size_t width, size_t height) override { |
| return nullptr; |
| } |
| }; |
| |
| class MockWindowsLifecycleManager : public WindowsLifecycleManager { |
| public: |
| MockWindowsLifecycleManager(FlutterWindowsEngine* engine) |
| : WindowsLifecycleManager(engine) {} |
| |
| MOCK_METHOD(void, SetLifecycleState, (AppLifecycleState), (override)); |
| }; |
| |
| // Process the next win32 message if there is one. This can be used to |
| // pump the Windows platform thread task runner. |
| void PumpMessage() { |
| ::MSG msg; |
| if (::GetMessage(&msg, nullptr, 0, 0)) { |
| ::TranslateMessage(&msg); |
| ::DispatchMessage(&msg); |
| } |
| } |
| |
| } // namespace |
| |
| // Verify that we can fetch a texture registrar. |
| // Prevent regression: https://github.com/flutter/flutter/issues/86617 |
| TEST(WindowsNoFixtureTest, GetTextureRegistrar) { |
| FlutterDesktopEngineProperties properties = {}; |
| properties.assets_path = L""; |
| properties.icu_data_path = L"icudtl.dat"; |
| auto engine = FlutterDesktopEngineCreate(&properties); |
| ASSERT_NE(engine, nullptr); |
| auto texture_registrar = FlutterDesktopEngineGetTextureRegistrar(engine); |
| EXPECT_NE(texture_registrar, nullptr); |
| FlutterDesktopEngineDestroy(engine); |
| } |
| |
| // Verify we can successfully launch main(). |
| TEST_F(WindowsTest, LaunchMain) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| ViewControllerPtr controller{builder.Run()}; |
| ASSERT_NE(controller, nullptr); |
| } |
| |
| // Verify there is no unexpected output from launching main. |
| TEST_F(WindowsTest, LaunchMainHasNoOutput) { |
| // Replace stdout & stderr stream buffers with our own. |
| StreamCapture stdout_capture(&std::cout); |
| StreamCapture stderr_capture(&std::cerr); |
| |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| ViewControllerPtr controller{builder.Run()}; |
| ASSERT_NE(controller, nullptr); |
| |
| stdout_capture.Stop(); |
| stderr_capture.Stop(); |
| |
| // Verify stdout & stderr have no output. |
| EXPECT_TRUE(stdout_capture.GetOutput().empty()); |
| EXPECT_TRUE(stderr_capture.GetOutput().empty()); |
| } |
| |
| // Verify we can successfully launch a custom entry point. |
| TEST_F(WindowsTest, LaunchCustomEntrypoint) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| builder.SetDartEntrypoint("customEntrypoint"); |
| ViewControllerPtr controller{builder.Run()}; |
| ASSERT_NE(controller, nullptr); |
| } |
| |
| // Verify that engine launches with the custom entrypoint specified in the |
| // FlutterDesktopEngineRun parameter when no entrypoint is specified in |
| // FlutterDesktopEngineProperties.dart_entrypoint. |
| // |
| // TODO(cbracken): https://github.com/flutter/flutter/issues/109285 |
| TEST_F(WindowsTest, LaunchCustomEntrypointInEngineRunInvocation) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| EnginePtr engine{builder.InitializeEngine()}; |
| ASSERT_NE(engine, nullptr); |
| |
| ASSERT_TRUE(FlutterDesktopEngineRun(engine.get(), "customEntrypoint")); |
| } |
| |
| // Verify that the engine can launch in headless mode. |
| TEST_F(WindowsTest, LaunchHeadlessEngine) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| builder.SetDartEntrypoint("signalViewIds"); |
| EnginePtr engine{builder.RunHeadless()}; |
| ASSERT_NE(engine, nullptr); |
| |
| std::string view_ids; |
| fml::AutoResetWaitableEvent latch; |
| context.AddNativeFunction( |
| "SignalStringValue", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| auto handle = Dart_GetNativeArgument(args, 0); |
| ASSERT_FALSE(Dart_IsError(handle)); |
| view_ids = tonic::DartConverter<std::string>::FromDart(handle); |
| latch.Signal(); |
| })); |
| |
| ViewControllerPtr controller{builder.Run()}; |
| ASSERT_NE(controller, nullptr); |
| |
| // Verify a headless app has the implicit view. |
| latch.Wait(); |
| EXPECT_EQ(view_ids, "View IDs: [0]"); |
| } |
| |
| // Verify that the engine can return to headless mode. |
| TEST_F(WindowsTest, EngineCanTransitionToHeadless) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| EnginePtr engine{builder.RunHeadless()}; |
| ASSERT_NE(engine, nullptr); |
| |
| // Create and then destroy a view controller that does not own its engine. |
| // This causes the engine to transition back to headless mode. |
| { |
| FlutterDesktopViewControllerProperties properties = {}; |
| ViewControllerPtr controller{ |
| FlutterDesktopEngineCreateViewController(engine.get(), &properties)}; |
| |
| ASSERT_NE(controller, nullptr); |
| } |
| |
| // The engine is back in headless mode now. |
| ASSERT_NE(engine, nullptr); |
| |
| auto engine_ptr = reinterpret_cast<FlutterWindowsEngine*>(engine.get()); |
| ASSERT_TRUE(engine_ptr->running()); |
| } |
| |
| // Verify that accessibility features are initialized when a view is created. |
| TEST_F(WindowsTest, LaunchRefreshesAccessibility) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| EnginePtr engine{builder.InitializeEngine()}; |
| EngineModifier modifier{ |
| reinterpret_cast<FlutterWindowsEngine*>(engine.get())}; |
| |
| auto called = false; |
| modifier.embedder_api().UpdateAccessibilityFeatures = MOCK_ENGINE_PROC( |
| UpdateAccessibilityFeatures, ([&called](auto engine, auto flags) { |
| called = true; |
| return kSuccess; |
| })); |
| |
| ViewControllerPtr controller{ |
| FlutterDesktopViewControllerCreate(0, 0, engine.release())}; |
| |
| ASSERT_TRUE(called); |
| } |
| |
| // Verify that engine fails to launch when a conflicting entrypoint in |
| // FlutterDesktopEngineProperties.dart_entrypoint and the |
| // FlutterDesktopEngineRun parameter. |
| // |
| // TODO(cbracken): https://github.com/flutter/flutter/issues/109285 |
| TEST_F(WindowsTest, LaunchConflictingCustomEntrypoints) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| builder.SetDartEntrypoint("customEntrypoint"); |
| EnginePtr engine{builder.InitializeEngine()}; |
| ASSERT_NE(engine, nullptr); |
| |
| ASSERT_FALSE(FlutterDesktopEngineRun(engine.get(), "conflictingEntrypoint")); |
| } |
| |
| // Verify that native functions can be registered and resolved. |
| TEST_F(WindowsTest, VerifyNativeFunction) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| builder.SetDartEntrypoint("verifyNativeFunction"); |
| |
| fml::AutoResetWaitableEvent latch; |
| auto native_entry = |
| CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); }); |
| context.AddNativeFunction("Signal", native_entry); |
| |
| ViewControllerPtr controller{builder.Run()}; |
| ASSERT_NE(controller, nullptr); |
| |
| // Wait until signal has been called. |
| latch.Wait(); |
| } |
| |
| // Verify that native functions that pass parameters can be registered and |
| // resolved. |
| TEST_F(WindowsTest, VerifyNativeFunctionWithParameters) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| builder.SetDartEntrypoint("verifyNativeFunctionWithParameters"); |
| |
| bool bool_value = false; |
| fml::AutoResetWaitableEvent latch; |
| auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| auto handle = Dart_GetNativeBooleanArgument(args, 0, &bool_value); |
| ASSERT_FALSE(Dart_IsError(handle)); |
| latch.Signal(); |
| }); |
| context.AddNativeFunction("SignalBoolValue", native_entry); |
| |
| ViewControllerPtr controller{builder.Run()}; |
| ASSERT_NE(controller, nullptr); |
| |
| // Wait until signalBoolValue has been called. |
| latch.Wait(); |
| EXPECT_TRUE(bool_value); |
| } |
| |
| // Verify that Platform.executable returns the executable name. |
| TEST_F(WindowsTest, PlatformExecutable) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| builder.SetDartEntrypoint("readPlatformExecutable"); |
| |
| std::string executable_name; |
| fml::AutoResetWaitableEvent latch; |
| auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| auto handle = Dart_GetNativeArgument(args, 0); |
| ASSERT_FALSE(Dart_IsError(handle)); |
| executable_name = tonic::DartConverter<std::string>::FromDart(handle); |
| latch.Signal(); |
| }); |
| context.AddNativeFunction("SignalStringValue", native_entry); |
| |
| ViewControllerPtr controller{builder.Run()}; |
| ASSERT_NE(controller, nullptr); |
| |
| // Wait until signalStringValue has been called. |
| latch.Wait(); |
| EXPECT_EQ(executable_name, "flutter_windows_unittests.exe"); |
| } |
| |
| // Verify that native functions that return values can be registered and |
| // resolved. |
| TEST_F(WindowsTest, VerifyNativeFunctionWithReturn) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| builder.SetDartEntrypoint("verifyNativeFunctionWithReturn"); |
| |
| bool bool_value_to_return = true; |
| fml::CountDownLatch latch(2); |
| auto bool_return_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| Dart_SetBooleanReturnValue(args, bool_value_to_return); |
| latch.CountDown(); |
| }); |
| context.AddNativeFunction("SignalBoolReturn", bool_return_entry); |
| |
| bool bool_value_passed = false; |
| auto bool_pass_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| auto handle = Dart_GetNativeBooleanArgument(args, 0, &bool_value_passed); |
| ASSERT_FALSE(Dart_IsError(handle)); |
| latch.CountDown(); |
| }); |
| context.AddNativeFunction("SignalBoolValue", bool_pass_entry); |
| |
| ViewControllerPtr controller{builder.Run()}; |
| ASSERT_NE(controller, nullptr); |
| |
| // Wait until signalBoolReturn and signalBoolValue have been called. |
| latch.Wait(); |
| EXPECT_TRUE(bool_value_passed); |
| } |
| |
| // Verify the next frame callback is executed. |
| TEST_F(WindowsTest, NextFrameCallback) { |
| struct Captures { |
| fml::AutoResetWaitableEvent frame_scheduled_latch; |
| fml::AutoResetWaitableEvent frame_drawn_latch; |
| std::thread::id thread_id; |
| bool done = false; |
| }; |
| Captures captures; |
| |
| CreateNewThread("test_platform_thread")->PostTask([&]() { |
| captures.thread_id = std::this_thread::get_id(); |
| |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| builder.SetDartEntrypoint("drawHelloWorld"); |
| |
| auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| ASSERT_FALSE(captures.frame_drawn_latch.IsSignaledForTest()); |
| captures.frame_scheduled_latch.Signal(); |
| }); |
| context.AddNativeFunction("NotifyFirstFrameScheduled", native_entry); |
| |
| ViewControllerPtr controller{builder.Run()}; |
| ASSERT_NE(controller, nullptr); |
| |
| auto engine = FlutterDesktopViewControllerGetEngine(controller.get()); |
| |
| FlutterDesktopEngineSetNextFrameCallback( |
| engine, |
| [](void* user_data) { |
| auto captures = static_cast<Captures*>(user_data); |
| |
| ASSERT_TRUE(captures->frame_scheduled_latch.IsSignaledForTest()); |
| |
| // Callback should execute on platform thread. |
| ASSERT_EQ(std::this_thread::get_id(), captures->thread_id); |
| |
| // Signal the test passed and end the Windows message loop. |
| captures->done = true; |
| captures->frame_drawn_latch.Signal(); |
| }, |
| &captures); |
| |
| // Pump messages for the Windows platform task runner. |
| while (!captures.done) { |
| PumpMessage(); |
| } |
| }); |
| |
| captures.frame_drawn_latch.Wait(); |
| } |
| |
| // Verify the embedder ignores presents to the implicit view when there is no |
| // implicit view. |
| TEST_F(WindowsTest, PresentHeadless) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| builder.SetDartEntrypoint("renderImplicitView"); |
| |
| EnginePtr engine{builder.RunHeadless()}; |
| ASSERT_NE(engine, nullptr); |
| |
| bool done = false; |
| FlutterDesktopEngineSetNextFrameCallback( |
| engine.get(), |
| [](void* user_data) { |
| // This executes on the platform thread. |
| auto done = reinterpret_cast<std::atomic<bool>*>(user_data); |
| *done = true; |
| }, |
| &done); |
| |
| // This app is in headless mode, however, the engine assumes the implicit |
| // view always exists. Send window metrics for the implicit view, causing |
| // the engine to present to the implicit view. The embedder must not crash. |
| auto engine_ptr = reinterpret_cast<FlutterWindowsEngine*>(engine.get()); |
| FlutterWindowMetricsEvent metrics = {}; |
| metrics.struct_size = sizeof(FlutterWindowMetricsEvent); |
| metrics.width = 100; |
| metrics.height = 100; |
| metrics.pixel_ratio = 1.0; |
| metrics.view_id = kImplicitViewId; |
| engine_ptr->SendWindowMetricsEvent(metrics); |
| |
| // Pump messages for the Windows platform task runner. |
| while (!done) { |
| PumpMessage(); |
| } |
| } |
| |
| // Implicit view has the implicit view ID. |
| TEST_F(WindowsTest, GetViewId) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| ViewControllerPtr controller{builder.Run()}; |
| ASSERT_NE(controller, nullptr); |
| FlutterDesktopViewId view_id = |
| FlutterDesktopViewControllerGetViewId(controller.get()); |
| |
| ASSERT_EQ(view_id, static_cast<FlutterDesktopViewId>(kImplicitViewId)); |
| } |
| |
| TEST_F(WindowsTest, GetGraphicsAdapter) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| ViewControllerPtr controller{builder.Run()}; |
| ASSERT_NE(controller, nullptr); |
| auto view = FlutterDesktopViewControllerGetView(controller.get()); |
| |
| Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter; |
| dxgi_adapter = FlutterDesktopViewGetGraphicsAdapter(view); |
| ASSERT_NE(dxgi_adapter, nullptr); |
| DXGI_ADAPTER_DESC desc{}; |
| ASSERT_TRUE(SUCCEEDED(dxgi_adapter->GetDesc(&desc))); |
| } |
| |
| // Implicit view has the implicit view ID. |
| TEST_F(WindowsTest, PluginRegistrarGetImplicitView) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| ViewControllerPtr controller{builder.Run()}; |
| ASSERT_NE(controller, nullptr); |
| |
| FlutterDesktopEngineRef engine = |
| FlutterDesktopViewControllerGetEngine(controller.get()); |
| FlutterDesktopPluginRegistrarRef registrar = |
| FlutterDesktopEngineGetPluginRegistrar(engine, "foo_bar"); |
| FlutterDesktopViewRef implicit_view = |
| FlutterDesktopPluginRegistrarGetView(registrar); |
| |
| ASSERT_NE(implicit_view, nullptr); |
| } |
| |
| TEST_F(WindowsTest, PluginRegistrarGetView) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| ViewControllerPtr controller{builder.Run()}; |
| ASSERT_NE(controller, nullptr); |
| |
| FlutterDesktopEngineRef engine = |
| FlutterDesktopViewControllerGetEngine(controller.get()); |
| FlutterDesktopPluginRegistrarRef registrar = |
| FlutterDesktopEngineGetPluginRegistrar(engine, "foo_bar"); |
| |
| FlutterDesktopViewId view_id = |
| FlutterDesktopViewControllerGetViewId(controller.get()); |
| FlutterDesktopViewRef view = |
| FlutterDesktopPluginRegistrarGetViewById(registrar, view_id); |
| |
| FlutterDesktopViewRef view_123 = FlutterDesktopPluginRegistrarGetViewById( |
| registrar, static_cast<FlutterDesktopViewId>(123)); |
| |
| ASSERT_NE(view, nullptr); |
| ASSERT_EQ(view_123, nullptr); |
| } |
| |
| TEST_F(WindowsTest, PluginRegistrarGetViewHeadless) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| EnginePtr engine{builder.RunHeadless()}; |
| ASSERT_NE(engine, nullptr); |
| |
| FlutterDesktopPluginRegistrarRef registrar = |
| FlutterDesktopEngineGetPluginRegistrar(engine.get(), "foo_bar"); |
| |
| FlutterDesktopViewRef implicit_view = |
| FlutterDesktopPluginRegistrarGetView(registrar); |
| FlutterDesktopViewRef view_123 = FlutterDesktopPluginRegistrarGetViewById( |
| registrar, static_cast<FlutterDesktopViewId>(123)); |
| |
| ASSERT_EQ(implicit_view, nullptr); |
| ASSERT_EQ(view_123, nullptr); |
| } |
| |
| // Verify the app does not crash if EGL initializes successfully but |
| // the rendering surface cannot be created. |
| TEST_F(WindowsTest, SurfaceOptional) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| EnginePtr engine{builder.InitializeEngine()}; |
| EngineModifier modifier{ |
| reinterpret_cast<FlutterWindowsEngine*>(engine.get())}; |
| |
| auto egl_manager = std::make_unique<HalfBrokenEGLManager>(); |
| ASSERT_TRUE(egl_manager->IsValid()); |
| modifier.SetEGLManager(std::move(egl_manager)); |
| |
| ViewControllerPtr controller{ |
| FlutterDesktopViewControllerCreate(0, 0, engine.release())}; |
| |
| ASSERT_NE(controller, nullptr); |
| } |
| |
| // Verify the app produces the expected lifecycle events. |
| TEST_F(WindowsTest, Lifecycle) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| EnginePtr engine{builder.InitializeEngine()}; |
| auto windows_engine = reinterpret_cast<FlutterWindowsEngine*>(engine.get()); |
| EngineModifier modifier{windows_engine}; |
| |
| auto lifecycle_manager = |
| std::make_unique<MockWindowsLifecycleManager>(windows_engine); |
| auto lifecycle_manager_ptr = lifecycle_manager.get(); |
| modifier.SetLifecycleManager(std::move(lifecycle_manager)); |
| |
| EXPECT_CALL(*lifecycle_manager_ptr, |
| SetLifecycleState(AppLifecycleState::kResumed)) |
| .WillOnce([lifecycle_manager_ptr](AppLifecycleState state) { |
| lifecycle_manager_ptr->WindowsLifecycleManager::SetLifecycleState( |
| state); |
| }); |
| |
| EXPECT_CALL(*lifecycle_manager_ptr, |
| SetLifecycleState(AppLifecycleState::kHidden)) |
| .WillOnce([lifecycle_manager_ptr](AppLifecycleState state) { |
| lifecycle_manager_ptr->WindowsLifecycleManager::SetLifecycleState( |
| state); |
| }); |
| |
| // Create a controller. This launches the engine and sets the app lifecycle |
| // to the "resumed" state. |
| ViewControllerPtr controller{ |
| FlutterDesktopViewControllerCreate(0, 0, engine.release())}; |
| |
| FlutterDesktopViewRef view = |
| FlutterDesktopViewControllerGetView(controller.get()); |
| ASSERT_NE(view, nullptr); |
| |
| HWND hwnd = FlutterDesktopViewGetHWND(view); |
| ASSERT_NE(hwnd, nullptr); |
| |
| // Give the window a non-zero size to show it. This does not change the app |
| // lifecycle directly. However, destroying the view will now result in a |
| // "hidden" app lifecycle event. |
| ::MoveWindow(hwnd, /* X */ 0, /* Y */ 0, /* nWidth*/ 100, /* nHeight*/ 100, |
| /* bRepaint*/ false); |
| } |
| |
| TEST_F(WindowsTest, GetKeyboardStateHeadless) { |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| builder.SetDartEntrypoint("sendGetKeyboardState"); |
| |
| std::atomic<bool> done = false; |
| context.AddNativeFunction( |
| "SignalStringValue", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| auto handle = Dart_GetNativeArgument(args, 0); |
| ASSERT_FALSE(Dart_IsError(handle)); |
| auto value = tonic::DartConverter<std::string>::FromDart(handle); |
| EXPECT_EQ(value, "Success"); |
| done = true; |
| })); |
| |
| ViewControllerPtr controller{builder.Run()}; |
| ASSERT_NE(controller, nullptr); |
| |
| // Pump messages for the Windows platform task runner. |
| ::MSG msg; |
| while (!done) { |
| PumpMessage(); |
| } |
| } |
| |
| // Verify the embedder can add and remove views. |
| TEST_F(WindowsTest, AddRemoveView) { |
| std::mutex mutex; |
| std::string view_ids; |
| |
| auto& context = GetContext(); |
| WindowsConfigBuilder builder(context); |
| builder.SetDartEntrypoint("onMetricsChangedSignalViewIds"); |
| |
| fml::AutoResetWaitableEvent ready_latch; |
| context.AddNativeFunction( |
| "Signal", CREATE_NATIVE_ENTRY( |
| [&](Dart_NativeArguments args) { ready_latch.Signal(); })); |
| |
| context.AddNativeFunction( |
| "SignalStringValue", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| auto handle = Dart_GetNativeArgument(args, 0); |
| ASSERT_FALSE(Dart_IsError(handle)); |
| |
| std::scoped_lock lock{mutex}; |
| view_ids = tonic::DartConverter<std::string>::FromDart(handle); |
| })); |
| |
| // Create the implicit view. |
| ViewControllerPtr first_controller{builder.Run()}; |
| ASSERT_NE(first_controller, nullptr); |
| |
| ready_latch.Wait(); |
| |
| // Create a second view. |
| FlutterDesktopEngineRef engine = |
| FlutterDesktopViewControllerGetEngine(first_controller.get()); |
| FlutterDesktopViewControllerProperties properties = {}; |
| properties.width = 100; |
| properties.height = 100; |
| ViewControllerPtr second_controller{ |
| FlutterDesktopEngineCreateViewController(engine, &properties)}; |
| ASSERT_NE(second_controller, nullptr); |
| |
| // Pump messages for the Windows platform task runner until the view is added. |
| while (true) { |
| PumpMessage(); |
| std::scoped_lock lock{mutex}; |
| if (view_ids == "View IDs: [0, 1]") { |
| break; |
| } |
| } |
| |
| // Delete the second view and pump messages for the Windows platform task |
| // runner until the view is removed. |
| second_controller.reset(); |
| while (true) { |
| PumpMessage(); |
| std::scoped_lock lock{mutex}; |
| if (view_ids == "View IDs: [0]") { |
| break; |
| } |
| } |
| } |
| |
| } // namespace testing |
| } // namespace flutter |