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"
17 #include "flutter/shell/platform/windows/testing/engine_modifier.h"
18 #include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h"
19 #include "flutter/shell/platform/windows/testing/windows_test.h"
20 #include "flutter/shell/platform/windows/testing/windows_test_config_builder.h"
21 #include "flutter/shell/platform/windows/testing/windows_test_context.h"
23 #include "flutter/testing/stream_capture.h"
24 #include "gmock/gmock.h"
25 #include "gtest/gtest.h"
26 #include "third_party/tonic/converter/dart_converter.h"
27 
28 namespace flutter {
29 namespace testing {
30 
31 namespace {
32 
33 // An EGL manager that initializes EGL but fails to create surfaces.
34 class HalfBrokenEGLManager : public egl::Manager {
35  public:
36  HalfBrokenEGLManager() : egl::Manager(egl::GpuPreference::NoPreference) {}
37 
38  std::unique_ptr<egl::WindowSurface>
39  CreateWindowSurface(HWND hwnd, size_t width, size_t height) override {
40  return nullptr;
41  }
42 };
43 
44 class MockWindowsLifecycleManager : public WindowsLifecycleManager {
45  public:
46  MockWindowsLifecycleManager(FlutterWindowsEngine* engine)
47  : WindowsLifecycleManager(engine) {}
48 
49  MOCK_METHOD(void, SetLifecycleState, (AppLifecycleState), (override));
50 };
51 
52 // Process the next win32 message if there is one. This can be used to
53 // pump the Windows platform thread task runner.
54 void PumpMessage() {
55  ::MSG msg;
56  if (::GetMessage(&msg, nullptr, 0, 0)) {
57  ::TranslateMessage(&msg);
58  ::DispatchMessage(&msg);
59  }
60 }
61 
62 } // namespace
63 
64 // Verify that we can fetch a texture registrar.
65 // Prevent regression: https://github.com/flutter/flutter/issues/86617
66 TEST(WindowsNoFixtureTest, GetTextureRegistrar) {
67  FlutterDesktopEngineProperties properties = {};
68  properties.assets_path = L"";
69  properties.icu_data_path = L"icudtl.dat";
70  properties.impeller_switch = DefaultImpeller;
71  auto engine = FlutterDesktopEngineCreate(&properties);
72  ASSERT_NE(engine, nullptr);
73  auto texture_registrar = FlutterDesktopEngineGetTextureRegistrar(engine);
74  EXPECT_NE(texture_registrar, nullptr);
76 }
77 
78 // Verify we can successfully launch main().
79 TEST_F(WindowsTest, LaunchMain) {
80  auto& context = GetContext();
81  WindowsConfigBuilder builder(context);
82  ViewControllerPtr controller{builder.Run()};
83  ASSERT_NE(controller, nullptr);
84 }
85 
86 // Verify there is no unexpected output from launching main.
87 TEST_F(WindowsTest, LaunchMainHasNoOutput) {
88  // Replace stderr stream buffer with our own. (stdout may contain expected
89  // output printed by Dart, such as the Dart VM service startup message)
90  StreamCapture stderr_capture(&std::cerr);
91 
92  auto& context = GetContext();
93  WindowsConfigBuilder builder(context);
94  ViewControllerPtr controller{builder.Run()};
95  ASSERT_NE(controller, nullptr);
96 
97  stderr_capture.Stop();
98 
99  // Verify stderr has no output.
100  EXPECT_TRUE(stderr_capture.GetOutput().empty());
101 }
102 
103 // Verify we can successfully launch a custom entry point.
104 TEST_F(WindowsTest, LaunchCustomEntrypoint) {
105  auto& context = GetContext();
106  WindowsConfigBuilder builder(context);
107  builder.SetDartEntrypoint("customEntrypoint");
108  ViewControllerPtr controller{builder.Run()};
109  ASSERT_NE(controller, nullptr);
110 }
111 
112 // Verify that engine launches with the custom entrypoint specified in the
113 // FlutterDesktopEngineRun parameter when no entrypoint is specified in
114 // FlutterDesktopEngineProperties.dart_entrypoint.
115 //
116 // TODO(cbracken): https://github.com/flutter/flutter/issues/109285
117 TEST_F(WindowsTest, LaunchCustomEntrypointInEngineRunInvocation) {
118  auto& context = GetContext();
119  WindowsConfigBuilder builder(context);
120  EnginePtr engine{builder.InitializeEngine()};
121  ASSERT_NE(engine, nullptr);
122 
123  ASSERT_TRUE(FlutterDesktopEngineRun(engine.get(), "customEntrypoint"));
124 }
125 
126 // Verify that the engine can launch in headless mode.
127 TEST_F(WindowsTest, LaunchHeadlessEngine) {
128  auto& context = GetContext();
129  WindowsConfigBuilder builder(context);
130  builder.SetDartEntrypoint("signalViewIds");
131  EnginePtr engine{builder.RunHeadless()};
132  ASSERT_NE(engine, nullptr);
133 
134  std::string view_ids;
135  bool signaled = false;
136  context.AddNativeFunction(
137  "SignalStringValue", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
138  auto handle = Dart_GetNativeArgument(args, 0);
139  ASSERT_FALSE(Dart_IsError(handle));
140  view_ids = tonic::DartConverter<std::string>::FromDart(handle);
141  signaled = true;
142  }));
143 
144  ViewControllerPtr controller{builder.Run()};
145  ASSERT_NE(controller, nullptr);
146 
147  while (!signaled) {
148  PumpMessage();
149  }
150 
151  // Verify a headless app has the implicit view.
152  EXPECT_EQ(view_ids, "View IDs: [0]");
153 }
154 
155 // Verify that the engine can return to headless mode.
156 TEST_F(WindowsTest, EngineCanTransitionToHeadless) {
157  auto& context = GetContext();
158  WindowsConfigBuilder builder(context);
159  EnginePtr engine{builder.RunHeadless()};
160  ASSERT_NE(engine, nullptr);
161 
162  // Create and then destroy a view controller that does not own its engine.
163  // This causes the engine to transition back to headless mode.
164  {
166  ViewControllerPtr controller{
167  FlutterDesktopEngineCreateViewController(engine.get(), &properties)};
168 
169  ASSERT_NE(controller, nullptr);
170  }
171 
172  // The engine is back in headless mode now.
173  ASSERT_NE(engine, nullptr);
174 
175  auto engine_ptr = reinterpret_cast<FlutterWindowsEngine*>(engine.get());
176  ASSERT_TRUE(engine_ptr->running());
177 }
178 
179 // Verify that accessibility features are initialized when a view is created.
180 TEST_F(WindowsTest, LaunchRefreshesAccessibility) {
181  auto& context = GetContext();
182  WindowsConfigBuilder builder(context);
183  EnginePtr engine{builder.InitializeEngine()};
184  EngineModifier modifier{
185  reinterpret_cast<FlutterWindowsEngine*>(engine.get())};
186 
187  auto called = false;
188  modifier.embedder_api().UpdateAccessibilityFeatures = MOCK_ENGINE_PROC(
189  UpdateAccessibilityFeatures, ([&called](auto engine, auto flags) {
190  called = true;
191  return kSuccess;
192  }));
193 
194  ViewControllerPtr controller{
195  FlutterDesktopViewControllerCreate(0, 0, engine.release())};
196 
197  ASSERT_TRUE(called);
198 }
199 
200 // Verify that engine fails to launch when a conflicting entrypoint in
201 // FlutterDesktopEngineProperties.dart_entrypoint and the
202 // FlutterDesktopEngineRun parameter.
203 //
204 // TODO(cbracken): https://github.com/flutter/flutter/issues/109285
205 TEST_F(WindowsTest, LaunchConflictingCustomEntrypoints) {
206  auto& context = GetContext();
207  WindowsConfigBuilder builder(context);
208  builder.SetDartEntrypoint("customEntrypoint");
209  EnginePtr engine{builder.InitializeEngine()};
210  ASSERT_NE(engine, nullptr);
211 
212  ASSERT_FALSE(FlutterDesktopEngineRun(engine.get(), "conflictingEntrypoint"));
213 }
214 
215 // Verify that native functions can be registered and resolved.
216 TEST_F(WindowsTest, VerifyNativeFunction) {
217  auto& context = GetContext();
218  WindowsConfigBuilder builder(context);
219  builder.SetDartEntrypoint("verifyNativeFunction");
220 
221  bool signaled = false;
222  auto native_entry =
223  CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { signaled = true; });
224  context.AddNativeFunction("Signal", native_entry);
225 
226  ViewControllerPtr controller{builder.Run()};
227  ASSERT_NE(controller, nullptr);
228 
229  // Wait until signal has been called.
230  while (!signaled) {
231  PumpMessage();
232  }
233 }
234 
235 // Verify that native functions that pass parameters can be registered and
236 // resolved.
237 TEST_F(WindowsTest, VerifyNativeFunctionWithParameters) {
238  auto& context = GetContext();
239  WindowsConfigBuilder builder(context);
240  builder.SetDartEntrypoint("verifyNativeFunctionWithParameters");
241 
242  bool bool_value = false;
243  bool signaled = false;
244  auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
245  auto handle = Dart_GetNativeBooleanArgument(args, 0, &bool_value);
246  ASSERT_FALSE(Dart_IsError(handle));
247  signaled = true;
248  });
249  context.AddNativeFunction("SignalBoolValue", native_entry);
250 
251  ViewControllerPtr controller{builder.Run()};
252  ASSERT_NE(controller, nullptr);
253 
254  // Wait until signalBoolValue has been called.
255  while (!signaled) {
256  PumpMessage();
257  }
258  EXPECT_TRUE(bool_value);
259 }
260 
261 // Verify that Platform.executable returns the executable name.
262 TEST_F(WindowsTest, PlatformExecutable) {
263  auto& context = GetContext();
264  WindowsConfigBuilder builder(context);
265  builder.SetDartEntrypoint("readPlatformExecutable");
266 
267  std::string executable_name;
268  bool signaled = false;
269  auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
270  auto handle = Dart_GetNativeArgument(args, 0);
271  ASSERT_FALSE(Dart_IsError(handle));
272  executable_name = tonic::DartConverter<std::string>::FromDart(handle);
273  signaled = true;
274  });
275  context.AddNativeFunction("SignalStringValue", native_entry);
276 
277  ViewControllerPtr controller{builder.Run()};
278  ASSERT_NE(controller, nullptr);
279 
280  // Wait until signalStringValue has been called.
281  while (!signaled) {
282  PumpMessage();
283  }
284  EXPECT_EQ(executable_name, "flutter_windows_unittests.exe");
285 }
286 
287 // Verify that native functions that return values can be registered and
288 // resolved.
289 TEST_F(WindowsTest, VerifyNativeFunctionWithReturn) {
290  auto& context = GetContext();
291  WindowsConfigBuilder builder(context);
292  builder.SetDartEntrypoint("verifyNativeFunctionWithReturn");
293 
294  bool bool_value_to_return = true;
295  int count = 2;
296  auto bool_return_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
297  Dart_SetBooleanReturnValue(args, bool_value_to_return);
298  --count;
299  });
300  context.AddNativeFunction("SignalBoolReturn", bool_return_entry);
301 
302  bool bool_value_passed = false;
303  auto bool_pass_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
304  auto handle = Dart_GetNativeBooleanArgument(args, 0, &bool_value_passed);
305  ASSERT_FALSE(Dart_IsError(handle));
306  --count;
307  });
308  context.AddNativeFunction("SignalBoolValue", bool_pass_entry);
309 
310  ViewControllerPtr controller{builder.Run()};
311  ASSERT_NE(controller, nullptr);
312 
313  // Wait until signalBoolReturn and signalBoolValue have been called.
314  while (count > 0) {
315  PumpMessage();
316  }
317  EXPECT_TRUE(bool_value_passed);
318 }
319 
320 // Verify the next frame callback is executed.
321 TEST_F(WindowsTest, NextFrameCallback) {
322  struct Captures {
323  fml::AutoResetWaitableEvent frame_scheduled_latch;
324  std::thread::id thread_id;
325  bool done = false;
326  };
327  Captures captures;
328 
329  auto platform_thread = std::make_unique<fml::Thread>("test_platform_thread");
330  platform_thread->GetTaskRunner()->PostTask([&]() {
331  captures.thread_id = std::this_thread::get_id();
332 
333  auto& context = GetContext();
334  WindowsConfigBuilder builder(context);
335  builder.SetDartEntrypoint("drawHelloWorld");
336 
337  auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
338  captures.frame_scheduled_latch.Signal();
339  });
340  context.AddNativeFunction("NotifyFirstFrameScheduled", native_entry);
341 
342  ViewControllerPtr controller{builder.Run()};
343  EXPECT_NE(controller, nullptr);
344 
345  auto engine = FlutterDesktopViewControllerGetEngine(controller.get());
346 
348  engine,
349  [](void* user_data) {
350  auto captures = static_cast<Captures*>(user_data);
351 
352  EXPECT_TRUE(captures->frame_scheduled_latch.IsSignaledForTest());
353 
354  // Callback should execute on platform thread.
355  EXPECT_EQ(std::this_thread::get_id(), captures->thread_id);
356 
357  // Signal the test passed and end the Windows message loop.
358  captures->done = true;
359  },
360  &captures);
361 
362  // Pump messages for the Windows platform task runner.
363  while (!captures.done) {
364  PumpMessage();
365  }
366  });
367 
368  // Wait for the platform thread to exit.
369  platform_thread->Join();
370 }
371 
372 // Verify the embedder ignores presents to the implicit view when there is no
373 // implicit view.
374 TEST_F(WindowsTest, PresentHeadless) {
375  auto& context = GetContext();
376  WindowsConfigBuilder builder(context);
377  builder.SetDartEntrypoint("renderImplicitView");
378 
379  EnginePtr engine{builder.RunHeadless()};
380  ASSERT_NE(engine, nullptr);
381 
382  bool done = false;
384  engine.get(),
385  [](void* user_data) {
386  // This executes on the platform thread.
387  auto done = reinterpret_cast<std::atomic<bool>*>(user_data);
388  *done = true;
389  },
390  &done);
391 
392  // This app is in headless mode, however, the engine assumes the implicit
393  // view always exists. Send window metrics for the implicit view, causing
394  // the engine to present to the implicit view. The embedder must not crash.
395  auto engine_ptr = reinterpret_cast<FlutterWindowsEngine*>(engine.get());
396  FlutterWindowMetricsEvent metrics = {};
397  metrics.struct_size = sizeof(FlutterWindowMetricsEvent);
398  metrics.width = 100;
399  metrics.height = 100;
400  metrics.pixel_ratio = 1.0;
401  metrics.view_id = kImplicitViewId;
402  engine_ptr->SendWindowMetricsEvent(metrics);
403 
404  // Pump messages for the Windows platform task runner.
405  while (!done) {
406  PumpMessage();
407  }
408 }
409 
410 // Verify IsPlatformThread returns true on the platform thread.
411 TEST_F(WindowsTest, IsPlatformThread) {
412  auto& context = GetContext();
413  WindowsConfigBuilder builder(context);
414 
415  EnginePtr engine{builder.RunHeadless()};
416  ASSERT_NE(engine, nullptr);
417 
418  EXPECT_TRUE(FlutterDesktopEngineIsPlatformThread(engine.get()));
419 }
420 
421 // Verify IsPlatformThread returns false on a background thread.
422 TEST_F(WindowsTest, IsNotPlatformThread) {
423  auto& context = GetContext();
424  WindowsConfigBuilder builder(context);
425 
426  EnginePtr engine{builder.RunHeadless()};
427  ASSERT_NE(engine, nullptr);
428 
429  bool result = true;
430  std::thread background(
431  [&]() { result = FlutterDesktopEngineIsPlatformThread(engine.get()); });
432  background.join();
433 
434  EXPECT_FALSE(result);
435 }
436 
437 // Verify a task can be posted to the platform thread while on the platform
438 // thread.
439 TEST_F(WindowsTest, PostPlatformThreadTaskFromPlatformThread) {
440  auto& context = GetContext();
441  WindowsConfigBuilder builder(context);
442 
443  EnginePtr engine{builder.RunHeadless()};
444  ASSERT_NE(engine, nullptr);
445 
446  struct Captures {
447  std::thread::id thread_id;
448  bool done = false;
449  } captures;
450 
452  engine.get(),
453  [](void* user_data) {
454  auto captures = static_cast<Captures*>(user_data);
455  captures->thread_id = std::this_thread::get_id();
456  captures->done = true;
457  },
458  /*on_cancel=*/nullptr, &captures);
459 
460  while (!captures.done) {
461  PumpMessage();
462  }
463 
464  EXPECT_EQ(captures.thread_id, std::this_thread::get_id());
465 }
466 
467 // Verify a task can be posted to the platform thread while on a background
468 // thread.
469 TEST_F(WindowsTest, PostPlatformThreadTaskFromBackgroundThread) {
470  auto& context = GetContext();
471  WindowsConfigBuilder builder(context);
472 
473  EnginePtr engine{builder.RunHeadless()};
474  ASSERT_NE(engine, nullptr);
475 
476  std::mutex background_thread_id_mutex;
477  struct Captures {
478  std::thread::id background_thread_id;
479  std::thread::id platform_thread_id;
480  bool done = false;
481  } captures;
482 
483  std::thread background([&]() {
484  {
485  std::scoped_lock lock{background_thread_id_mutex};
486  captures.background_thread_id = std::this_thread::get_id();
487  }
488 
490  engine.get(),
491  [](void* user_data) {
492  auto captures = static_cast<Captures*>(user_data);
493  captures->platform_thread_id = std::this_thread::get_id();
494  captures->done = true;
495  },
496  /*on_cancel=*/nullptr, &captures);
497  });
498  background.join();
499 
500  while (!captures.done) {
501  PumpMessage();
502  }
503 
504  std::scoped_lock lock{background_thread_id_mutex};
505  EXPECT_NE(captures.background_thread_id, std::thread::id{});
506  EXPECT_NE(captures.background_thread_id, captures.platform_thread_id);
507  EXPECT_EQ(captures.platform_thread_id, std::this_thread::get_id());
508 }
509 
510 // Verify that destroying the engine after posting a task invokes the cancel
511 // callback instead of the task callback.
512 TEST_F(WindowsTest, PostPlatformThreadTaskCancelledOnEngineDestroy) {
513  auto& context = GetContext();
514  WindowsConfigBuilder builder(context);
515 
516  EnginePtr engine{builder.RunHeadless()};
517  ASSERT_NE(engine, nullptr);
518 
519  struct Captures {
520  bool callback_called = false;
521  bool cancel_called = false;
522  } captures;
523 
525  engine.get(),
526  [](void* user_data) {
527  static_cast<Captures*>(user_data)->callback_called = true;
528  },
529  [](void* user_data) {
530  static_cast<Captures*>(user_data)->cancel_called = true;
531  },
532  &captures);
533 
534  // Destroy the engine before the task has a chance to run. The cancel
535  // callback should be called and the task callback should not.
536  engine.reset();
537 
538  EXPECT_FALSE(captures.callback_called);
539  EXPECT_TRUE(captures.cancel_called);
540 }
541 
542 // Implicit view has the implicit view ID.
543 TEST_F(WindowsTest, GetViewId) {
544  auto& context = GetContext();
545  WindowsConfigBuilder builder(context);
546  ViewControllerPtr controller{builder.Run()};
547  ASSERT_NE(controller, nullptr);
548  FlutterDesktopViewId view_id =
549  FlutterDesktopViewControllerGetViewId(controller.get());
550 
551  ASSERT_EQ(view_id, static_cast<FlutterDesktopViewId>(kImplicitViewId));
552 }
553 
554 TEST_F(WindowsTest, GetGraphicsAdapter) {
555  auto& context = GetContext();
556  WindowsConfigBuilder builder(context);
557  ViewControllerPtr controller{builder.Run()};
558  ASSERT_NE(controller, nullptr);
559  auto view = FlutterDesktopViewControllerGetView(controller.get());
560 
561  Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
562  dxgi_adapter = FlutterDesktopViewGetGraphicsAdapter(view);
563  ASSERT_NE(dxgi_adapter, nullptr);
564  DXGI_ADAPTER_DESC desc{};
565  ASSERT_TRUE(SUCCEEDED(dxgi_adapter->GetDesc(&desc)));
566 }
567 
568 TEST_F(WindowsTest, GetEngineGraphicsAdapter) {
569  auto& context = GetContext();
570  WindowsConfigBuilder builder(context);
571  ViewControllerPtr controller{builder.Run()};
572  ASSERT_NE(controller, nullptr);
573  auto engine = FlutterDesktopViewControllerGetEngine(controller.get());
574 
575  // Can't use smart pointer because the result is not a real COM object.
576  IDXGIAdapter* dxgi_adapter;
577  ASSERT_TRUE(FlutterDesktopEngineGetGraphicsAdapter(engine, &dxgi_adapter));
578  ASSERT_NE(dxgi_adapter, nullptr);
579  DXGI_ADAPTER_DESC desc{};
580  ASSERT_TRUE(SUCCEEDED(dxgi_adapter->GetDesc(&desc)));
581 }
582 
583 TEST_F(WindowsTest, GetGraphicsAdapterWithLowPowerPreference) {
584  std::optional<LUID> luid = egl::Manager::GetLowPowerGpuLuid();
585  if (!luid) {
586  GTEST_SKIP() << "Not able to find low power GPU, nothing to check.";
587  }
588 
589  auto& context = GetContext();
590  WindowsConfigBuilder builder(context);
591  builder.SetGpuPreference(FlutterDesktopGpuPreference::LowPowerPreference);
592  ViewControllerPtr controller{builder.Run()};
593  ASSERT_NE(controller, nullptr);
594  auto view = FlutterDesktopViewControllerGetView(controller.get());
595 
596  Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
597  dxgi_adapter = FlutterDesktopViewGetGraphicsAdapter(view);
598  ASSERT_NE(dxgi_adapter, nullptr);
599  DXGI_ADAPTER_DESC desc{};
600  ASSERT_TRUE(SUCCEEDED(dxgi_adapter->GetDesc(&desc)));
601  ASSERT_EQ(desc.AdapterLuid.HighPart, luid->HighPart);
602  ASSERT_EQ(desc.AdapterLuid.LowPart, luid->LowPart);
603 }
604 
605 TEST_F(WindowsTest, GetGraphicsAdapterWithHighPerformancePreference) {
606  std::optional<LUID> luid = egl::Manager::GetHighPerformanceGpuLuid();
607  if (!luid) {
608  GTEST_SKIP() << "Not able to find high performance GPU, nothing to check.";
609  }
610 
611  auto& context = GetContext();
612  WindowsConfigBuilder builder(context);
613  builder.SetGpuPreference(
615  ViewControllerPtr controller{builder.Run()};
616  ASSERT_NE(controller, nullptr);
617  auto view = FlutterDesktopViewControllerGetView(controller.get());
618 
619  Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
620  dxgi_adapter = FlutterDesktopViewGetGraphicsAdapter(view);
621  ASSERT_NE(dxgi_adapter, nullptr);
622  DXGI_ADAPTER_DESC desc{};
623  ASSERT_TRUE(SUCCEEDED(dxgi_adapter->GetDesc(&desc)));
624  ASSERT_EQ(desc.AdapterLuid.HighPart, luid->HighPart);
625  ASSERT_EQ(desc.AdapterLuid.LowPart, luid->LowPart);
626 }
627 
628 TEST_F(WindowsTest, GetEngineGraphicsAdapterWithLowPowerPreference) {
629  std::optional<LUID> luid = egl::Manager::GetLowPowerGpuLuid();
630  if (!luid) {
631  GTEST_SKIP() << "Not able to find low power GPU, nothing to check.";
632  }
633 
634  auto& context = GetContext();
635  WindowsConfigBuilder builder(context);
636  builder.SetGpuPreference(FlutterDesktopGpuPreference::LowPowerPreference);
637  ViewControllerPtr controller{builder.Run()};
638  ASSERT_NE(controller, nullptr);
639  auto engine = FlutterDesktopViewControllerGetEngine(controller.get());
640 
641  // Can't use smart pointer because the result is not a real COM object.
642  IDXGIAdapter* dxgi_adapter;
643  ASSERT_TRUE(FlutterDesktopEngineGetGraphicsAdapter(engine, &dxgi_adapter));
644  ASSERT_NE(dxgi_adapter, nullptr);
645  DXGI_ADAPTER_DESC desc{};
646  ASSERT_TRUE(SUCCEEDED(dxgi_adapter->GetDesc(&desc)));
647  ASSERT_EQ(desc.AdapterLuid.HighPart, luid->HighPart);
648  ASSERT_EQ(desc.AdapterLuid.LowPart, luid->LowPart);
649 }
650 
651 TEST_F(WindowsTest, GetEngineGraphicsAdapterWithHighPerformancePreference) {
652  std::optional<LUID> luid = egl::Manager::GetHighPerformanceGpuLuid();
653  if (!luid) {
654  GTEST_SKIP() << "Not able to find high performance GPU, nothing to check.";
655  }
656 
657  auto& context = GetContext();
658  WindowsConfigBuilder builder(context);
659  builder.SetGpuPreference(
661  ViewControllerPtr controller{builder.Run()};
662  ASSERT_NE(controller, nullptr);
663  auto engine = FlutterDesktopViewControllerGetEngine(controller.get());
664 
665  // Can't use smart pointer because the result is not a real COM object.
666  IDXGIAdapter* dxgi_adapter;
667  ASSERT_TRUE(FlutterDesktopEngineGetGraphicsAdapter(engine, &dxgi_adapter));
668  ASSERT_NE(dxgi_adapter, nullptr);
669  DXGI_ADAPTER_DESC desc{};
670  ASSERT_TRUE(SUCCEEDED(dxgi_adapter->GetDesc(&desc)));
671  ASSERT_EQ(desc.AdapterLuid.HighPart, luid->HighPart);
672  ASSERT_EQ(desc.AdapterLuid.LowPart, luid->LowPart);
673 }
674 
675 // Implicit view has the implicit view ID.
676 TEST_F(WindowsTest, PluginRegistrarGetImplicitView) {
677  auto& context = GetContext();
678  WindowsConfigBuilder builder(context);
679  ViewControllerPtr controller{builder.Run()};
680  ASSERT_NE(controller, nullptr);
681 
682  FlutterDesktopEngineRef engine =
683  FlutterDesktopViewControllerGetEngine(controller.get());
685  FlutterDesktopEngineGetPluginRegistrar(engine, "foo_bar");
686  FlutterDesktopViewRef implicit_view =
688 
689  ASSERT_NE(implicit_view, nullptr);
690 }
691 
692 TEST_F(WindowsTest, PluginRegistrarGetView) {
693  auto& context = GetContext();
694  WindowsConfigBuilder builder(context);
695  ViewControllerPtr controller{builder.Run()};
696  ASSERT_NE(controller, nullptr);
697 
698  FlutterDesktopEngineRef engine =
699  FlutterDesktopViewControllerGetEngine(controller.get());
701  FlutterDesktopEngineGetPluginRegistrar(engine, "foo_bar");
702 
703  FlutterDesktopViewId view_id =
704  FlutterDesktopViewControllerGetViewId(controller.get());
705  FlutterDesktopViewRef view =
706  FlutterDesktopPluginRegistrarGetViewById(registrar, view_id);
707 
709  registrar, static_cast<FlutterDesktopViewId>(123));
710 
711  ASSERT_NE(view, nullptr);
712  ASSERT_EQ(view_123, nullptr);
713 }
714 
715 TEST_F(WindowsTest, PluginRegistrarGetViewHeadless) {
716  auto& context = GetContext();
717  WindowsConfigBuilder builder(context);
718  EnginePtr engine{builder.RunHeadless()};
719  ASSERT_NE(engine, nullptr);
720 
722  FlutterDesktopEngineGetPluginRegistrar(engine.get(), "foo_bar");
723 
724  FlutterDesktopViewRef implicit_view =
727  registrar, static_cast<FlutterDesktopViewId>(123));
728 
729  ASSERT_EQ(implicit_view, nullptr);
730  ASSERT_EQ(view_123, nullptr);
731 }
732 
733 // Verify the app does not crash if EGL initializes successfully but
734 // the rendering surface cannot be created.
735 TEST_F(WindowsTest, SurfaceOptional) {
736  auto& context = GetContext();
737  WindowsConfigBuilder builder(context);
738  EnginePtr engine{builder.InitializeEngine()};
739  EngineModifier modifier{
740  reinterpret_cast<FlutterWindowsEngine*>(engine.get())};
741 
742  auto egl_manager = std::make_unique<HalfBrokenEGLManager>();
743  ASSERT_TRUE(egl_manager->IsValid());
744  modifier.SetEGLManager(std::move(egl_manager));
745 
746  ViewControllerPtr controller{
747  FlutterDesktopViewControllerCreate(0, 0, engine.release())};
748 
749  ASSERT_NE(controller, nullptr);
750 }
751 
752 // Verify the app produces the expected lifecycle events.
753 TEST_F(WindowsTest, Lifecycle) {
754  auto& context = GetContext();
755  WindowsConfigBuilder builder(context);
756  EnginePtr engine{builder.InitializeEngine()};
757  auto windows_engine = reinterpret_cast<FlutterWindowsEngine*>(engine.get());
758  EngineModifier modifier{windows_engine};
759 
760  auto lifecycle_manager =
761  std::make_unique<MockWindowsLifecycleManager>(windows_engine);
762  auto lifecycle_manager_ptr = lifecycle_manager.get();
763  modifier.SetLifecycleManager(std::move(lifecycle_manager));
764 
765  EXPECT_CALL(*lifecycle_manager_ptr,
766  SetLifecycleState(AppLifecycleState::kInactive))
767  .WillOnce([lifecycle_manager_ptr](AppLifecycleState state) {
768  lifecycle_manager_ptr->WindowsLifecycleManager::SetLifecycleState(
769  state);
770  });
771 
772  EXPECT_CALL(*lifecycle_manager_ptr,
773  SetLifecycleState(AppLifecycleState::kHidden))
774  .WillOnce([lifecycle_manager_ptr](AppLifecycleState state) {
775  lifecycle_manager_ptr->WindowsLifecycleManager::SetLifecycleState(
776  state);
777  });
778 
779  FlutterDesktopViewControllerProperties properties = {0, 0};
780 
781  // Create a controller. This launches the engine and sets the app lifecycle
782  // to the "resumed" state.
783  ViewControllerPtr controller{
784  FlutterDesktopEngineCreateViewController(engine.get(), &properties)};
785 
786  FlutterDesktopViewRef view =
787  FlutterDesktopViewControllerGetView(controller.get());
788  ASSERT_NE(view, nullptr);
789 
790  HWND hwnd = FlutterDesktopViewGetHWND(view);
791  ASSERT_NE(hwnd, nullptr);
792 
793  // Give the window a non-zero size to show it. This does not change the app
794  // lifecycle directly. However, destroying the view will now result in a
795  // "hidden" app lifecycle event.
796  ::MoveWindow(hwnd, /* X */ 0, /* Y */ 0, /* nWidth*/ 100, /* nHeight*/ 100,
797  /* bRepaint*/ false);
798 
799  while (lifecycle_manager_ptr->IsUpdateStateScheduled()) {
800  PumpMessage();
801  }
802 
803  // Resets the view, simulating the window being hidden.
804  controller.reset();
805 
806  while (lifecycle_manager_ptr->IsUpdateStateScheduled()) {
807  PumpMessage();
808  }
809 }
810 
811 TEST_F(WindowsTest, GetKeyboardStateHeadless) {
812  auto& context = GetContext();
813  WindowsConfigBuilder builder(context);
814  builder.SetDartEntrypoint("sendGetKeyboardState");
815 
816  std::atomic<bool> done = false;
817  context.AddNativeFunction(
818  "SignalStringValue", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
819  auto handle = Dart_GetNativeArgument(args, 0);
820  ASSERT_FALSE(Dart_IsError(handle));
821  auto value = tonic::DartConverter<std::string>::FromDart(handle);
822  EXPECT_EQ(value, "Success");
823  done = true;
824  }));
825 
826  ViewControllerPtr controller{builder.Run()};
827  ASSERT_NE(controller, nullptr);
828 
829  // Pump messages for the Windows platform task runner.
830  ::MSG msg;
831  while (!done) {
832  PumpMessage();
833  }
834 }
835 
836 // Verify the embedder can add and remove views.
837 TEST_F(WindowsTest, AddRemoveView) {
838  std::mutex mutex;
839  std::string view_ids;
840 
841  auto& context = GetContext();
842  WindowsConfigBuilder builder(context);
843  builder.SetDartEntrypoint("onMetricsChangedSignalViewIds");
844 
845  bool ready = false;
846  context.AddNativeFunction(
847  "Signal",
848  CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { ready = true; }));
849 
850  context.AddNativeFunction(
851  "SignalStringValue", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
852  auto handle = Dart_GetNativeArgument(args, 0);
853  ASSERT_FALSE(Dart_IsError(handle));
854 
855  std::scoped_lock lock{mutex};
856  view_ids = tonic::DartConverter<std::string>::FromDart(handle);
857  }));
858 
859  // Create the implicit view.
860  ViewControllerPtr first_controller{builder.Run()};
861  ASSERT_NE(first_controller, nullptr);
862 
863  while (!ready) {
864  PumpMessage();
865  }
866 
867  // Create a second view.
868  FlutterDesktopEngineRef engine =
869  FlutterDesktopViewControllerGetEngine(first_controller.get());
871  properties.width = 100;
872  properties.height = 100;
873  ViewControllerPtr second_controller{
874  FlutterDesktopEngineCreateViewController(engine, &properties)};
875  ASSERT_NE(second_controller, nullptr);
876 
877  // Pump messages for the Windows platform task runner until the view is added.
878  while (true) {
879  PumpMessage();
880  std::scoped_lock lock{mutex};
881  if (view_ids == "View IDs: [0, 1]") {
882  break;
883  }
884  }
885 
886  // Delete the second view and pump messages for the Windows platform task
887  // runner until the view is removed.
888  second_controller.reset();
889  while (true) {
890  PumpMessage();
891  std::scoped_lock lock{mutex};
892  if (view_ids == "View IDs: [0]") {
893  break;
894  }
895  }
896 }
897 
898 TEST_F(WindowsTest, EngineId) {
899  auto& context = GetContext();
900  WindowsConfigBuilder builder(context);
901  builder.SetDartEntrypoint("testEngineId");
902 
903  std::optional<int64_t> engineId;
904  context.AddNativeFunction(
905  "NotifyEngineId", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
906  const auto argument = Dart_GetNativeArgument(args, 0);
907  if (!Dart_IsNull(argument)) {
908  const auto handle = tonic::DartConverter<int64_t>::FromDart(argument);
909  engineId = handle;
910  }
911  }));
912  // Create the implicit view.
913  ViewControllerPtr first_controller{builder.Run()};
914  ASSERT_NE(first_controller, nullptr);
915 
916  while (!engineId.has_value()) {
917  PumpMessage();
918  }
919 
920  auto engine = FlutterDesktopViewControllerGetEngine(first_controller.get());
921  EXPECT_EQ(engine, FlutterDesktopEngineForId(*engineId));
922 }
923 
924 TEST_F(WindowsTest, EnableIAccessible) {
925  auto& context = GetContext();
926  WindowsConfigBuilder builder(context);
927  builder.SetAccessibilityMode(
929  builder.SetDartEntrypoint("sendSemanticsTree");
930 
931  // Setup: a signal for when we have send out all of our semantics updates
932  bool done = false;
933  auto native_entry =
934  CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { done = true; });
935  context.AddNativeFunction("Signal", native_entry);
936 
937  // Setup: Create a view
938  ViewControllerPtr controller{builder.Run()};
939  ASSERT_NE(controller, nullptr);
940 
941  auto view = FlutterDesktopViewControllerGetView(controller.get());
942  ASSERT_NE(view, nullptr);
943 
944  // Setup: UpdateSemanticsEnabled will trigger the semantics updates
945  // to get sent.
946  auto windows_view = reinterpret_cast<FlutterWindowsView*>(view);
947  windows_view->OnUpdateSemanticsEnabled(true);
948 
949  while (!done) {
950  PumpMessage();
951  }
952 
953  HWND hwnd = FlutterDesktopViewGetHWND(view);
954  ASSERT_NE(hwnd, nullptr);
955 
956  LRESULT lres = SendMessage(hwnd, WM_GETOBJECT, 0, OBJID_CLIENT);
957  ASSERT_NE(lres, 0);
958 
959  // In IAccessible mode, the object returned from WM_GETOBJECT should support
960  // IAccessible but not IAccessibleEx.
961  IAccessible* accessible = nullptr;
962  HRESULT hr = ObjectFromLresult(lres, IID_IAccessible, 0, (void**)&accessible);
963  ASSERT_TRUE(SUCCEEDED(hr));
964  ASSERT_NE(accessible, nullptr);
965 
966  IAccessibleEx* accessible_ex = nullptr;
967  hr = ObjectFromLresult(lres, IID_IAccessibleEx, 0, (void**)&accessible_ex);
968  ASSERT_TRUE(FAILED(hr));
969  ASSERT_EQ(accessible_ex, nullptr);
970 }
971 
972 TEST_F(WindowsTest, EnableIAccessibleEx) {
973  auto& context = GetContext();
974  WindowsConfigBuilder builder(context);
975  builder.SetAccessibilityMode(
977  builder.SetDartEntrypoint("sendSemanticsTree");
978 
979  // Setup: a signal for when we have send out all of our semantics updates
980  bool done = false;
981  auto native_entry =
982  CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { done = true; });
983  context.AddNativeFunction("Signal", native_entry);
984 
985  // Setup: Create a view
986  ViewControllerPtr controller{builder.Run()};
987  ASSERT_NE(controller, nullptr);
988 
989  auto view = FlutterDesktopViewControllerGetView(controller.get());
990  ASSERT_NE(view, nullptr);
991 
992  // Setup: UpdateSemanticsEnabled will trigger the semantics updates
993  // to get sent.
994  auto windows_view = reinterpret_cast<FlutterWindowsView*>(view);
995  windows_view->OnUpdateSemanticsEnabled(true);
996 
997  while (!done) {
998  PumpMessage();
999  }
1000 
1001  HWND hwnd = FlutterDesktopViewGetHWND(view);
1002  ASSERT_NE(hwnd, nullptr);
1003 
1004  LRESULT lres = SendMessage(hwnd, WM_GETOBJECT, 0, OBJID_CLIENT);
1005  ASSERT_NE(lres, 0);
1006 
1007  // In IAccessibleEx mode, the object returned from WM_GETOBJECT should
1008  // support IAccessibleEx.
1009  IAccessibleEx* accessible_ex = nullptr;
1010  HRESULT hr =
1011  ObjectFromLresult(lres, IID_IAccessibleEx, 0, (void**)&accessible_ex);
1012  ASSERT_TRUE(SUCCEEDED(hr));
1013  ASSERT_NE(accessible_ex, nullptr);
1014  accessible_ex->Release();
1015 }
1016 
1017 } // namespace testing
1018 } // namespace flutter
virtual void OnUpdateSemanticsEnabled(bool enabled) override
WindowsLifecycleManager(FlutterWindowsEngine *engine)
virtual void SetLifecycleState(AppLifecycleState state)
static std::optional< LUID > GetLowPowerGpuLuid()
Definition: manager.cc:376
static std::optional< LUID > GetHighPerformanceGpuLuid()
Definition: manager.cc:380
MOCK_METHOD(void, Quit,(std::optional< HWND >, std::optional< WPARAM >, std::optional< LPARAM >, UINT),(override))
bool FlutterDesktopEngineGetGraphicsAdapter(FlutterDesktopEngineRef engine, IDXGIAdapter **adapter_out)
FlutterDesktopEngineRef FlutterDesktopEngineCreate(const FlutterDesktopEngineProperties *engine_properties)
FLUTTER_EXPORT FlutterDesktopEngineRef FlutterDesktopEngineForId(int64_t engine_id)
FlutterDesktopViewRef FlutterDesktopPluginRegistrarGetViewById(FlutterDesktopPluginRegistrarRef registrar, FlutterDesktopViewId view_id)
FlutterDesktopPluginRegistrarRef FlutterDesktopEngineGetPluginRegistrar(FlutterDesktopEngineRef engine, const char *plugin_name)
bool FlutterDesktopEngineIsPlatformThread(FlutterDesktopEngineRef engine)
FlutterDesktopViewControllerRef FlutterDesktopViewControllerCreate(int width, int height, FlutterDesktopEngineRef engine)
bool FlutterDesktopEngineDestroy(FlutterDesktopEngineRef engine_ref)
void FlutterDesktopEnginePostPlatformThreadTask(FlutterDesktopEngineRef engine, VoidCallback callback, VoidCallback on_cancel, void *user_data)
FlutterDesktopTextureRegistrarRef FlutterDesktopEngineGetTextureRegistrar(FlutterDesktopEngineRef engine)
IDXGIAdapter * FlutterDesktopViewGetGraphicsAdapter(FlutterDesktopViewRef view)
FlutterDesktopViewId FlutterDesktopViewControllerGetViewId(FlutterDesktopViewControllerRef ref)
FlutterDesktopEngineRef FlutterDesktopViewControllerGetEngine(FlutterDesktopViewControllerRef ref)
void FlutterDesktopEngineSetNextFrameCallback(FlutterDesktopEngineRef engine, VoidCallback callback, void *user_data)
HWND FlutterDesktopViewGetHWND(FlutterDesktopViewRef view)
FlutterDesktopViewRef FlutterDesktopViewControllerGetView(FlutterDesktopViewControllerRef ref)
FlutterDesktopViewRef FlutterDesktopPluginRegistrarGetView(FlutterDesktopPluginRegistrarRef registrar)
FlutterDesktopViewControllerRef FlutterDesktopEngineCreateViewController(FlutterDesktopEngineRef engine, const FlutterDesktopViewControllerProperties *properties)
bool FlutterDesktopEngineRun(FlutterDesktopEngineRef engine, const char *entry_point)
struct FlutterDesktopEngine * FlutterDesktopEngineRef
int64_t FlutterDesktopViewId
struct FlutterDesktopView * FlutterDesktopViewRef
@ LowPowerPreference
@ HighPerformancePreference
@ NoPreference
@ DefaultImpeller
@ IAccessibleMode
@ IAccessibleExMode
TEST_F(AccessibilityPluginTest, DirectAnnounceCall)
TEST(AccessibilityBridgeWindows, GetParent)
constexpr FlutterViewId kImplicitViewId
FlutterDesktopImpellerSwitch impeller_switch