Flutter Windows Embedder
flutter_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 <dxgi.h>
8 #include <wrl/client.h>
9 #include <thread>
10 
11 #include "flutter/fml/synchronization/count_down_latch.h"
12 #include "flutter/fml/synchronization/waitable_event.h"
14 #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
16 #include "flutter/shell/platform/windows/testing/engine_modifier.h"
17 #include "flutter/shell/platform/windows/testing/windows_test.h"
18 #include "flutter/shell/platform/windows/testing/windows_test_config_builder.h"
19 #include "flutter/shell/platform/windows/testing/windows_test_context.h"
21 #include "flutter/testing/stream_capture.h"
22 #include "gmock/gmock.h"
23 #include "gtest/gtest.h"
24 #include "third_party/tonic/converter/dart_converter.h"
25 
26 namespace flutter {
27 namespace testing {
28 
29 namespace {
30 
31 // An EGL manager that initializes EGL but fails to create surfaces.
32 class HalfBrokenEGLManager : public egl::Manager {
33  public:
34  HalfBrokenEGLManager() : egl::Manager(/*enable_impeller = */ false) {}
35 
36  std::unique_ptr<egl::WindowSurface>
37  CreateWindowSurface(HWND hwnd, size_t width, size_t height) override {
38  return nullptr;
39  }
40 };
41 
42 class MockWindowsLifecycleManager : public WindowsLifecycleManager {
43  public:
44  MockWindowsLifecycleManager(FlutterWindowsEngine* engine)
45  : WindowsLifecycleManager(engine) {}
46 
47  MOCK_METHOD(void, SetLifecycleState, (AppLifecycleState), (override));
48 };
49 
50 } // namespace
51 
52 // Verify that we can fetch a texture registrar.
53 // Prevent regression: https://github.com/flutter/flutter/issues/86617
54 TEST(WindowsNoFixtureTest, GetTextureRegistrar) {
55  FlutterDesktopEngineProperties properties = {};
56  properties.assets_path = L"";
57  properties.icu_data_path = L"icudtl.dat";
58  auto engine = FlutterDesktopEngineCreate(&properties);
59  ASSERT_NE(engine, nullptr);
60  auto texture_registrar = FlutterDesktopEngineGetTextureRegistrar(engine);
61  EXPECT_NE(texture_registrar, nullptr);
63 }
64 
65 // Verify we can successfully launch main().
66 TEST_F(WindowsTest, LaunchMain) {
67  auto& context = GetContext();
68  WindowsConfigBuilder builder(context);
69  ViewControllerPtr controller{builder.Run()};
70  ASSERT_NE(controller, nullptr);
71 }
72 
73 // Verify there is no unexpected output from launching main.
74 TEST_F(WindowsTest, LaunchMainHasNoOutput) {
75  // Replace stdout & stderr stream buffers with our own.
76  StreamCapture stdout_capture(&std::cout);
77  StreamCapture stderr_capture(&std::cerr);
78 
79  auto& context = GetContext();
80  WindowsConfigBuilder builder(context);
81  ViewControllerPtr controller{builder.Run()};
82  ASSERT_NE(controller, nullptr);
83 
84  stdout_capture.Stop();
85  stderr_capture.Stop();
86 
87  // Verify stdout & stderr have no output.
88  EXPECT_TRUE(stdout_capture.GetOutput().empty());
89  EXPECT_TRUE(stderr_capture.GetOutput().empty());
90 }
91 
92 // Verify we can successfully launch a custom entry point.
93 TEST_F(WindowsTest, LaunchCustomEntrypoint) {
94  auto& context = GetContext();
95  WindowsConfigBuilder builder(context);
96  builder.SetDartEntrypoint("customEntrypoint");
97  ViewControllerPtr controller{builder.Run()};
98  ASSERT_NE(controller, nullptr);
99 }
100 
101 // Verify that engine launches with the custom entrypoint specified in the
102 // FlutterDesktopEngineRun parameter when no entrypoint is specified in
103 // FlutterDesktopEngineProperties.dart_entrypoint.
104 //
105 // TODO(cbracken): https://github.com/flutter/flutter/issues/109285
106 TEST_F(WindowsTest, LaunchCustomEntrypointInEngineRunInvocation) {
107  auto& context = GetContext();
108  WindowsConfigBuilder builder(context);
109  EnginePtr engine{builder.InitializeEngine()};
110  ASSERT_NE(engine, nullptr);
111 
112  ASSERT_TRUE(FlutterDesktopEngineRun(engine.get(), "customEntrypoint"));
113 }
114 
115 // Verify that the engine can launch in headless mode.
116 TEST_F(WindowsTest, LaunchHeadlessEngine) {
117  auto& context = GetContext();
118  WindowsConfigBuilder builder(context);
119  EnginePtr engine{builder.RunHeadless()};
120  ASSERT_NE(engine, nullptr);
121 }
122 
123 // Verify that accessibility features are initialized when a view is created.
124 TEST_F(WindowsTest, LaunchRefreshesAccessibility) {
125  auto& context = GetContext();
126  WindowsConfigBuilder builder(context);
127  EnginePtr engine{builder.InitializeEngine()};
128  EngineModifier modifier{
129  reinterpret_cast<FlutterWindowsEngine*>(engine.get())};
130 
131  auto called = false;
132  modifier.embedder_api().UpdateAccessibilityFeatures = MOCK_ENGINE_PROC(
133  UpdateAccessibilityFeatures, ([&called](auto engine, auto flags) {
134  called = true;
135  return kSuccess;
136  }));
137 
138  ViewControllerPtr controller{
139  FlutterDesktopViewControllerCreate(0, 0, engine.release())};
140 
141  ASSERT_TRUE(called);
142 }
143 
144 // Verify that engine fails to launch when a conflicting entrypoint in
145 // FlutterDesktopEngineProperties.dart_entrypoint and the
146 // FlutterDesktopEngineRun parameter.
147 //
148 // TODO(cbracken): https://github.com/flutter/flutter/issues/109285
149 TEST_F(WindowsTest, LaunchConflictingCustomEntrypoints) {
150  auto& context = GetContext();
151  WindowsConfigBuilder builder(context);
152  builder.SetDartEntrypoint("customEntrypoint");
153  EnginePtr engine{builder.InitializeEngine()};
154  ASSERT_NE(engine, nullptr);
155 
156  ASSERT_FALSE(FlutterDesktopEngineRun(engine.get(), "conflictingEntrypoint"));
157 }
158 
159 // Verify that native functions can be registered and resolved.
160 TEST_F(WindowsTest, VerifyNativeFunction) {
161  auto& context = GetContext();
162  WindowsConfigBuilder builder(context);
163  builder.SetDartEntrypoint("verifyNativeFunction");
164 
165  fml::AutoResetWaitableEvent latch;
166  auto native_entry =
167  CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); });
168  context.AddNativeFunction("Signal", native_entry);
169 
170  ViewControllerPtr controller{builder.Run()};
171  ASSERT_NE(controller, nullptr);
172 
173  // Wait until signal has been called.
174  latch.Wait();
175 }
176 
177 // Verify that native functions that pass parameters can be registered and
178 // resolved.
179 TEST_F(WindowsTest, VerifyNativeFunctionWithParameters) {
180  auto& context = GetContext();
181  WindowsConfigBuilder builder(context);
182  builder.SetDartEntrypoint("verifyNativeFunctionWithParameters");
183 
184  bool bool_value = false;
185  fml::AutoResetWaitableEvent latch;
186  auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
187  auto handle = Dart_GetNativeBooleanArgument(args, 0, &bool_value);
188  ASSERT_FALSE(Dart_IsError(handle));
189  latch.Signal();
190  });
191  context.AddNativeFunction("SignalBoolValue", native_entry);
192 
193  ViewControllerPtr controller{builder.Run()};
194  ASSERT_NE(controller, nullptr);
195 
196  // Wait until signalBoolValue has been called.
197  latch.Wait();
198  EXPECT_TRUE(bool_value);
199 }
200 
201 // Verify that Platform.executable returns the executable name.
202 TEST_F(WindowsTest, PlatformExecutable) {
203  auto& context = GetContext();
204  WindowsConfigBuilder builder(context);
205  builder.SetDartEntrypoint("readPlatformExecutable");
206 
207  std::string executable_name;
208  fml::AutoResetWaitableEvent latch;
209  auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
210  auto handle = Dart_GetNativeArgument(args, 0);
211  ASSERT_FALSE(Dart_IsError(handle));
212  executable_name = tonic::DartConverter<std::string>::FromDart(handle);
213  latch.Signal();
214  });
215  context.AddNativeFunction("SignalStringValue", native_entry);
216 
217  ViewControllerPtr controller{builder.Run()};
218  ASSERT_NE(controller, nullptr);
219 
220  // Wait until signalStringValue has been called.
221  latch.Wait();
222  EXPECT_EQ(executable_name, "flutter_windows_unittests.exe");
223 }
224 
225 // Verify that native functions that return values can be registered and
226 // resolved.
227 TEST_F(WindowsTest, VerifyNativeFunctionWithReturn) {
228  auto& context = GetContext();
229  WindowsConfigBuilder builder(context);
230  builder.SetDartEntrypoint("verifyNativeFunctionWithReturn");
231 
232  bool bool_value_to_return = true;
233  fml::CountDownLatch latch(2);
234  auto bool_return_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
235  Dart_SetBooleanReturnValue(args, bool_value_to_return);
236  latch.CountDown();
237  });
238  context.AddNativeFunction("SignalBoolReturn", bool_return_entry);
239 
240  bool bool_value_passed = false;
241  auto bool_pass_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
242  auto handle = Dart_GetNativeBooleanArgument(args, 0, &bool_value_passed);
243  ASSERT_FALSE(Dart_IsError(handle));
244  latch.CountDown();
245  });
246  context.AddNativeFunction("SignalBoolValue", bool_pass_entry);
247 
248  ViewControllerPtr controller{builder.Run()};
249  ASSERT_NE(controller, nullptr);
250 
251  // Wait until signalBoolReturn and signalBoolValue have been called.
252  latch.Wait();
253  EXPECT_TRUE(bool_value_passed);
254 }
255 
256 // Verify the next frame callback is executed.
257 TEST_F(WindowsTest, NextFrameCallback) {
258  struct Captures {
259  fml::AutoResetWaitableEvent frame_scheduled_latch;
260  fml::AutoResetWaitableEvent frame_drawn_latch;
261  std::thread::id thread_id;
262  };
263  Captures captures;
264 
265  CreateNewThread("test_platform_thread")->PostTask([&]() {
266  captures.thread_id = std::this_thread::get_id();
267 
268  auto& context = GetContext();
269  WindowsConfigBuilder builder(context);
270  builder.SetDartEntrypoint("drawHelloWorld");
271 
272  auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
273  ASSERT_FALSE(captures.frame_drawn_latch.IsSignaledForTest());
274  captures.frame_scheduled_latch.Signal();
275  });
276  context.AddNativeFunction("NotifyFirstFrameScheduled", native_entry);
277 
278  ViewControllerPtr controller{builder.Run()};
279  ASSERT_NE(controller, nullptr);
280 
281  auto engine = FlutterDesktopViewControllerGetEngine(controller.get());
282 
284  engine,
285  [](void* user_data) {
286  auto captures = static_cast<Captures*>(user_data);
287 
288  ASSERT_TRUE(captures->frame_scheduled_latch.IsSignaledForTest());
289 
290  // Callback should execute on platform thread.
291  ASSERT_EQ(std::this_thread::get_id(), captures->thread_id);
292 
293  // Signal the test passed and end the Windows message loop.
294  captures->frame_drawn_latch.Signal();
295  ::PostQuitMessage(0);
296  },
297  &captures);
298 
299  // Pump messages for the Windows platform task runner.
300  ::MSG msg;
301  while (::GetMessage(&msg, nullptr, 0, 0)) {
302  ::TranslateMessage(&msg);
303  ::DispatchMessage(&msg);
304  }
305  });
306 
307  captures.frame_drawn_latch.Wait();
308 }
309 
310 // Implicit view has the implicit view ID.
311 TEST_F(WindowsTest, GetViewId) {
312  auto& context = GetContext();
313  WindowsConfigBuilder builder(context);
314  ViewControllerPtr controller{builder.Run()};
315  ASSERT_NE(controller, nullptr);
316  FlutterDesktopViewId view_id =
317  FlutterDesktopViewControllerGetViewId(controller.get());
318 
319  ASSERT_EQ(view_id, static_cast<FlutterDesktopViewId>(kImplicitViewId));
320 }
321 
322 TEST_F(WindowsTest, GetGraphicsAdapter) {
323  auto& context = GetContext();
324  WindowsConfigBuilder builder(context);
325  ViewControllerPtr controller{builder.Run()};
326  ASSERT_NE(controller, nullptr);
327  auto view = FlutterDesktopViewControllerGetView(controller.get());
328 
329  Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
330  dxgi_adapter = FlutterDesktopViewGetGraphicsAdapter(view);
331  ASSERT_NE(dxgi_adapter, nullptr);
332  DXGI_ADAPTER_DESC desc{};
333  ASSERT_TRUE(SUCCEEDED(dxgi_adapter->GetDesc(&desc)));
334 }
335 
336 // Verify the app does not crash if EGL initializes successfully but
337 // the rendering surface cannot be created.
338 TEST_F(WindowsTest, SurfaceOptional) {
339  auto& context = GetContext();
340  WindowsConfigBuilder builder(context);
341  EnginePtr engine{builder.InitializeEngine()};
342  EngineModifier modifier{
343  reinterpret_cast<FlutterWindowsEngine*>(engine.get())};
344 
345  auto egl_manager = std::make_unique<HalfBrokenEGLManager>();
346  ASSERT_TRUE(egl_manager->IsValid());
347  modifier.SetEGLManager(std::move(egl_manager));
348 
349  ViewControllerPtr controller{
350  FlutterDesktopViewControllerCreate(0, 0, engine.release())};
351 
352  ASSERT_NE(controller, nullptr);
353 }
354 
355 // Verify the app produces the expected lifecycle events.
356 TEST_F(WindowsTest, Lifecycle) {
357  auto& context = GetContext();
358  WindowsConfigBuilder builder(context);
359  EnginePtr engine{builder.InitializeEngine()};
360  auto windows_engine = reinterpret_cast<FlutterWindowsEngine*>(engine.get());
361  EngineModifier modifier{windows_engine};
362 
363  auto lifecycle_manager =
364  std::make_unique<MockWindowsLifecycleManager>(windows_engine);
365  auto lifecycle_manager_ptr = lifecycle_manager.get();
366  modifier.SetLifecycleManager(std::move(lifecycle_manager));
367 
368  EXPECT_CALL(*lifecycle_manager_ptr,
369  SetLifecycleState(AppLifecycleState::kResumed))
370  .WillOnce([lifecycle_manager_ptr](AppLifecycleState state) {
371  lifecycle_manager_ptr->WindowsLifecycleManager::SetLifecycleState(
372  state);
373  });
374 
375  EXPECT_CALL(*lifecycle_manager_ptr,
376  SetLifecycleState(AppLifecycleState::kHidden))
377  .WillOnce([lifecycle_manager_ptr](AppLifecycleState state) {
378  lifecycle_manager_ptr->WindowsLifecycleManager::SetLifecycleState(
379  state);
380  });
381 
382  // Create a controller. This launches the engine and sets the app lifecycle
383  // to the "resumed" state.
384  ViewControllerPtr controller{
385  FlutterDesktopViewControllerCreate(0, 0, engine.release())};
386 
387  FlutterDesktopViewRef view =
388  FlutterDesktopViewControllerGetView(controller.get());
389  ASSERT_NE(view, nullptr);
390 
391  HWND hwnd = FlutterDesktopViewGetHWND(view);
392  ASSERT_NE(hwnd, nullptr);
393 
394  // Give the window a non-zero size to show it. This does not change the app
395  // lifecycle directly. However, destroying the view will now result in a
396  // "hidden" app lifecycle event.
397  ::MoveWindow(hwnd, /* X */ 0, /* Y */ 0, /* nWidth*/ 100, /* nHeight*/ 100,
398  /* bRepaint*/ false);
399 }
400 
401 } // namespace testing
402 } // namespace flutter
flutter::kImplicitViewId
constexpr FlutterViewId kImplicitViewId
Definition: flutter_windows_engine.h:54
FlutterDesktopViewGetHWND
HWND FlutterDesktopViewGetHWND(FlutterDesktopViewRef view)
Definition: flutter_windows.cc:218
flutter::AppLifecycleState::kHidden
@ kHidden
FlutterDesktopViewControllerGetView
FlutterDesktopViewRef FlutterDesktopViewControllerGetView(FlutterDesktopViewControllerRef ref)
Definition: flutter_windows.cc:125
FlutterDesktopEngineGetTextureRegistrar
FlutterDesktopTextureRegistrarRef FlutterDesktopEngineGetTextureRegistrar(FlutterDesktopEngineRef engine)
Definition: flutter_windows.cc:205
FlutterDesktopEngineProperties
Definition: flutter_windows.h:39
windows_lifecycle_manager.h
FlutterDesktopViewId
int64_t FlutterDesktopViewId
Definition: flutter_windows.h:36
user_data
void * user_data
Definition: flutter_windows_view_unittests.cc:52
flutter::FlutterWindowsEngine
Definition: flutter_windows_engine.h:89
FlutterDesktopEngineProperties::icu_data_path
const wchar_t * icu_data_path
Definition: flutter_windows.h:48
flutter::FlutterWindowsEngine::UpdateAccessibilityFeatures
void UpdateAccessibilityFeatures()
Definition: flutter_windows_engine.cc:795
FlutterDesktopViewControllerGetViewId
FlutterDesktopViewId FlutterDesktopViewControllerGetViewId(FlutterDesktopViewControllerRef ref)
Definition: flutter_windows.cc:113
flutter::testing::MockWindowsLifecycleManager::MockWindowsLifecycleManager
MockWindowsLifecycleManager(FlutterWindowsEngine *engine)
Definition: flutter_windows_engine_unittests.cc:751
app_lifecycle_state.h
FlutterDesktopViewRef
struct FlutterDesktopView * FlutterDesktopViewRef
Definition: flutter_windows.h:29
flutter::WindowsLifecycleManager::SetLifecycleState
virtual void SetLifecycleState(AppLifecycleState state)
Definition: windows_lifecycle_manager.cc:195
flutter::WindowsLifecycleManager::WindowsLifecycleManager
WindowsLifecycleManager(FlutterWindowsEngine *engine)
Definition: windows_lifecycle_manager.cc:16
flutter
Definition: accessibility_bridge_windows.cc:11
FlutterDesktopEngineDestroy
bool FlutterDesktopEngineDestroy(FlutterDesktopEngineRef engine_ref)
Definition: flutter_windows.cc:162
manager.h
FlutterDesktopEngineCreate
FlutterDesktopEngineRef FlutterDesktopEngineCreate(const FlutterDesktopEngineProperties *engine_properties)
Definition: flutter_windows.cc:155
FlutterDesktopViewControllerGetEngine
FlutterDesktopEngineRef FlutterDesktopViewControllerGetEngine(FlutterDesktopViewControllerRef ref)
Definition: flutter_windows.cc:119
flutter::AppLifecycleState::kResumed
@ kResumed
flutter_windows.h
flutter::testing::TEST
TEST(AccessibilityBridgeWindows, GetParent)
Definition: accessibility_bridge_windows_unittests.cc:237
flutter::AppLifecycleState
AppLifecycleState
Definition: app_lifecycle_state.h:32
FlutterDesktopEngineSetNextFrameCallback
void FlutterDesktopEngineSetNextFrameCallback(FlutterDesktopEngineRef engine, VoidCallback callback, void *user_data)
Definition: flutter_windows.cc:211
flutter::testing::MockWindowsLifecycleManager::MOCK_METHOD
MOCK_METHOD(void, Quit,(std::optional< HWND >, std::optional< WPARAM >, std::optional< LPARAM >, UINT),(override))
flutter::testing::TEST_F
TEST_F(CompositorOpenGLTest, CreateBackingStore)
Definition: compositor_opengl_unittests.cc:127
FlutterDesktopViewGetGraphicsAdapter
IDXGIAdapter * FlutterDesktopViewGetGraphicsAdapter(FlutterDesktopViewRef view)
Definition: flutter_windows.cc:222
FlutterDesktopEngineProperties::assets_path
const wchar_t * assets_path
Definition: flutter_windows.h:43
FlutterDesktopEngineRun
bool FlutterDesktopEngineRun(FlutterDesktopEngineRef engine, const char *entry_point)
Definition: flutter_windows.cc:172
FlutterDesktopViewControllerCreate
FlutterDesktopViewControllerRef FlutterDesktopViewControllerCreate(int width, int height, FlutterDesktopEngineRef engine_ref)
Definition: flutter_windows.cc:75