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(egl::GpuPreference::NoPreference) {}
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 // Process the next win32 message if there is one. This can be used to
51 // pump the Windows platform thread task runner.
52 void PumpMessage() {
53  ::MSG msg;
54  if (::GetMessage(&msg, nullptr, 0, 0)) {
55  ::TranslateMessage(&msg);
56  ::DispatchMessage(&msg);
57  }
58 }
59 
60 } // namespace
61 
62 // Verify that we can fetch a texture registrar.
63 // Prevent regression: https://github.com/flutter/flutter/issues/86617
64 TEST(WindowsNoFixtureTest, GetTextureRegistrar) {
65  FlutterDesktopEngineProperties properties = {};
66  properties.assets_path = L"";
67  properties.icu_data_path = L"icudtl.dat";
68  auto engine = FlutterDesktopEngineCreate(&properties);
69  ASSERT_NE(engine, nullptr);
70  auto texture_registrar = FlutterDesktopEngineGetTextureRegistrar(engine);
71  EXPECT_NE(texture_registrar, nullptr);
73 }
74 
75 // Verify we can successfully launch main().
76 TEST_F(WindowsTest, LaunchMain) {
77  auto& context = GetContext();
78  WindowsConfigBuilder builder(context);
79  ViewControllerPtr controller{builder.Run()};
80  ASSERT_NE(controller, nullptr);
81 }
82 
83 // Verify there is no unexpected output from launching main.
84 TEST_F(WindowsTest, LaunchMainHasNoOutput) {
85  // Replace stderr stream buffer with our own. (stdout may contain expected
86  // output printed by Dart, such as the Dart VM service startup message)
87  StreamCapture stderr_capture(&std::cerr);
88 
89  auto& context = GetContext();
90  WindowsConfigBuilder builder(context);
91  ViewControllerPtr controller{builder.Run()};
92  ASSERT_NE(controller, nullptr);
93 
94  stderr_capture.Stop();
95 
96  // Verify stderr has no output.
97  EXPECT_TRUE(stderr_capture.GetOutput().empty());
98 }
99 
100 // Verify we can successfully launch a custom entry point.
101 TEST_F(WindowsTest, LaunchCustomEntrypoint) {
102  auto& context = GetContext();
103  WindowsConfigBuilder builder(context);
104  builder.SetDartEntrypoint("customEntrypoint");
105  ViewControllerPtr controller{builder.Run()};
106  ASSERT_NE(controller, nullptr);
107 }
108 
109 // Verify that engine launches with the custom entrypoint specified in the
110 // FlutterDesktopEngineRun parameter when no entrypoint is specified in
111 // FlutterDesktopEngineProperties.dart_entrypoint.
112 //
113 // TODO(cbracken): https://github.com/flutter/flutter/issues/109285
114 TEST_F(WindowsTest, LaunchCustomEntrypointInEngineRunInvocation) {
115  auto& context = GetContext();
116  WindowsConfigBuilder builder(context);
117  EnginePtr engine{builder.InitializeEngine()};
118  ASSERT_NE(engine, nullptr);
119 
120  ASSERT_TRUE(FlutterDesktopEngineRun(engine.get(), "customEntrypoint"));
121 }
122 
123 // Verify that the engine can launch in headless mode.
124 TEST_F(WindowsTest, LaunchHeadlessEngine) {
125  auto& context = GetContext();
126  WindowsConfigBuilder builder(context);
127  builder.SetDartEntrypoint("signalViewIds");
128  EnginePtr engine{builder.RunHeadless()};
129  ASSERT_NE(engine, nullptr);
130 
131  std::string view_ids;
132  bool signaled = false;
133  context.AddNativeFunction(
134  "SignalStringValue", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
135  auto handle = Dart_GetNativeArgument(args, 0);
136  ASSERT_FALSE(Dart_IsError(handle));
137  view_ids = tonic::DartConverter<std::string>::FromDart(handle);
138  signaled = true;
139  }));
140 
141  ViewControllerPtr controller{builder.Run()};
142  ASSERT_NE(controller, nullptr);
143 
144  while (!signaled) {
145  PumpMessage();
146  }
147 
148  // Verify a headless app has the implicit view.
149  EXPECT_EQ(view_ids, "View IDs: [0]");
150 }
151 
152 // Verify that the engine can return to headless mode.
153 TEST_F(WindowsTest, EngineCanTransitionToHeadless) {
154  auto& context = GetContext();
155  WindowsConfigBuilder builder(context);
156  EnginePtr engine{builder.RunHeadless()};
157  ASSERT_NE(engine, nullptr);
158 
159  // Create and then destroy a view controller that does not own its engine.
160  // This causes the engine to transition back to headless mode.
161  {
163  ViewControllerPtr controller{
164  FlutterDesktopEngineCreateViewController(engine.get(), &properties)};
165 
166  ASSERT_NE(controller, nullptr);
167  }
168 
169  // The engine is back in headless mode now.
170  ASSERT_NE(engine, nullptr);
171 
172  auto engine_ptr = reinterpret_cast<FlutterWindowsEngine*>(engine.get());
173  ASSERT_TRUE(engine_ptr->running());
174 }
175 
176 // Verify that accessibility features are initialized when a view is created.
177 TEST_F(WindowsTest, LaunchRefreshesAccessibility) {
178  auto& context = GetContext();
179  WindowsConfigBuilder builder(context);
180  EnginePtr engine{builder.InitializeEngine()};
181  EngineModifier modifier{
182  reinterpret_cast<FlutterWindowsEngine*>(engine.get())};
183 
184  auto called = false;
185  modifier.embedder_api().UpdateAccessibilityFeatures = MOCK_ENGINE_PROC(
186  UpdateAccessibilityFeatures, ([&called](auto engine, auto flags) {
187  called = true;
188  return kSuccess;
189  }));
190 
191  ViewControllerPtr controller{
192  FlutterDesktopViewControllerCreate(0, 0, engine.release())};
193 
194  ASSERT_TRUE(called);
195 }
196 
197 // Verify that engine fails to launch when a conflicting entrypoint in
198 // FlutterDesktopEngineProperties.dart_entrypoint and the
199 // FlutterDesktopEngineRun parameter.
200 //
201 // TODO(cbracken): https://github.com/flutter/flutter/issues/109285
202 TEST_F(WindowsTest, LaunchConflictingCustomEntrypoints) {
203  auto& context = GetContext();
204  WindowsConfigBuilder builder(context);
205  builder.SetDartEntrypoint("customEntrypoint");
206  EnginePtr engine{builder.InitializeEngine()};
207  ASSERT_NE(engine, nullptr);
208 
209  ASSERT_FALSE(FlutterDesktopEngineRun(engine.get(), "conflictingEntrypoint"));
210 }
211 
212 // Verify that native functions can be registered and resolved.
213 TEST_F(WindowsTest, VerifyNativeFunction) {
214  auto& context = GetContext();
215  WindowsConfigBuilder builder(context);
216  builder.SetDartEntrypoint("verifyNativeFunction");
217 
218  bool signaled = false;
219  auto native_entry =
220  CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { signaled = true; });
221  context.AddNativeFunction("Signal", native_entry);
222 
223  ViewControllerPtr controller{builder.Run()};
224  ASSERT_NE(controller, nullptr);
225 
226  // Wait until signal has been called.
227  while (!signaled) {
228  PumpMessage();
229  }
230 }
231 
232 // Verify that native functions that pass parameters can be registered and
233 // resolved.
234 TEST_F(WindowsTest, VerifyNativeFunctionWithParameters) {
235  auto& context = GetContext();
236  WindowsConfigBuilder builder(context);
237  builder.SetDartEntrypoint("verifyNativeFunctionWithParameters");
238 
239  bool bool_value = false;
240  bool signaled = false;
241  auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
242  auto handle = Dart_GetNativeBooleanArgument(args, 0, &bool_value);
243  ASSERT_FALSE(Dart_IsError(handle));
244  signaled = true;
245  });
246  context.AddNativeFunction("SignalBoolValue", native_entry);
247 
248  ViewControllerPtr controller{builder.Run()};
249  ASSERT_NE(controller, nullptr);
250 
251  // Wait until signalBoolValue has been called.
252  while (!signaled) {
253  PumpMessage();
254  }
255  EXPECT_TRUE(bool_value);
256 }
257 
258 // Verify that Platform.executable returns the executable name.
259 TEST_F(WindowsTest, PlatformExecutable) {
260  auto& context = GetContext();
261  WindowsConfigBuilder builder(context);
262  builder.SetDartEntrypoint("readPlatformExecutable");
263 
264  std::string executable_name;
265  bool signaled = false;
266  auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
267  auto handle = Dart_GetNativeArgument(args, 0);
268  ASSERT_FALSE(Dart_IsError(handle));
269  executable_name = tonic::DartConverter<std::string>::FromDart(handle);
270  signaled = true;
271  });
272  context.AddNativeFunction("SignalStringValue", native_entry);
273 
274  ViewControllerPtr controller{builder.Run()};
275  ASSERT_NE(controller, nullptr);
276 
277  // Wait until signalStringValue has been called.
278  while (!signaled) {
279  PumpMessage();
280  }
281  EXPECT_EQ(executable_name, "flutter_windows_unittests.exe");
282 }
283 
284 // Verify that native functions that return values can be registered and
285 // resolved.
286 TEST_F(WindowsTest, VerifyNativeFunctionWithReturn) {
287  auto& context = GetContext();
288  WindowsConfigBuilder builder(context);
289  builder.SetDartEntrypoint("verifyNativeFunctionWithReturn");
290 
291  bool bool_value_to_return = true;
292  int count = 2;
293  auto bool_return_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
294  Dart_SetBooleanReturnValue(args, bool_value_to_return);
295  --count;
296  });
297  context.AddNativeFunction("SignalBoolReturn", bool_return_entry);
298 
299  bool bool_value_passed = false;
300  auto bool_pass_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
301  auto handle = Dart_GetNativeBooleanArgument(args, 0, &bool_value_passed);
302  ASSERT_FALSE(Dart_IsError(handle));
303  --count;
304  });
305  context.AddNativeFunction("SignalBoolValue", bool_pass_entry);
306 
307  ViewControllerPtr controller{builder.Run()};
308  ASSERT_NE(controller, nullptr);
309 
310  // Wait until signalBoolReturn and signalBoolValue have been called.
311  while (count > 0) {
312  PumpMessage();
313  }
314  EXPECT_TRUE(bool_value_passed);
315 }
316 
317 // Verify the next frame callback is executed.
318 TEST_F(WindowsTest, NextFrameCallback) {
319  struct Captures {
320  fml::AutoResetWaitableEvent frame_scheduled_latch;
321  std::thread::id thread_id;
322  bool done = false;
323  };
324  Captures captures;
325 
326  auto platform_thread = std::make_unique<fml::Thread>("test_platform_thread");
327  platform_thread->GetTaskRunner()->PostTask([&]() {
328  captures.thread_id = std::this_thread::get_id();
329 
330  auto& context = GetContext();
331  WindowsConfigBuilder builder(context);
332  builder.SetDartEntrypoint("drawHelloWorld");
333 
334  auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
335  captures.frame_scheduled_latch.Signal();
336  });
337  context.AddNativeFunction("NotifyFirstFrameScheduled", native_entry);
338 
339  ViewControllerPtr controller{builder.Run()};
340  EXPECT_NE(controller, nullptr);
341 
342  auto engine = FlutterDesktopViewControllerGetEngine(controller.get());
343 
345  engine,
346  [](void* user_data) {
347  auto captures = static_cast<Captures*>(user_data);
348 
349  EXPECT_TRUE(captures->frame_scheduled_latch.IsSignaledForTest());
350 
351  // Callback should execute on platform thread.
352  EXPECT_EQ(std::this_thread::get_id(), captures->thread_id);
353 
354  // Signal the test passed and end the Windows message loop.
355  captures->done = true;
356  },
357  &captures);
358 
359  // Pump messages for the Windows platform task runner.
360  while (!captures.done) {
361  PumpMessage();
362  }
363  });
364 
365  // Wait for the platform thread to exit.
366  platform_thread->Join();
367 }
368 
369 // Verify the embedder ignores presents to the implicit view when there is no
370 // implicit view.
371 TEST_F(WindowsTest, PresentHeadless) {
372  auto& context = GetContext();
373  WindowsConfigBuilder builder(context);
374  builder.SetDartEntrypoint("renderImplicitView");
375 
376  EnginePtr engine{builder.RunHeadless()};
377  ASSERT_NE(engine, nullptr);
378 
379  bool done = false;
381  engine.get(),
382  [](void* user_data) {
383  // This executes on the platform thread.
384  auto done = reinterpret_cast<std::atomic<bool>*>(user_data);
385  *done = true;
386  },
387  &done);
388 
389  // This app is in headless mode, however, the engine assumes the implicit
390  // view always exists. Send window metrics for the implicit view, causing
391  // the engine to present to the implicit view. The embedder must not crash.
392  auto engine_ptr = reinterpret_cast<FlutterWindowsEngine*>(engine.get());
393  FlutterWindowMetricsEvent metrics = {};
394  metrics.struct_size = sizeof(FlutterWindowMetricsEvent);
395  metrics.width = 100;
396  metrics.height = 100;
397  metrics.pixel_ratio = 1.0;
398  metrics.view_id = kImplicitViewId;
399  engine_ptr->SendWindowMetricsEvent(metrics);
400 
401  // Pump messages for the Windows platform task runner.
402  while (!done) {
403  PumpMessage();
404  }
405 }
406 
407 // Implicit view has the implicit view ID.
408 TEST_F(WindowsTest, GetViewId) {
409  auto& context = GetContext();
410  WindowsConfigBuilder builder(context);
411  ViewControllerPtr controller{builder.Run()};
412  ASSERT_NE(controller, nullptr);
413  FlutterDesktopViewId view_id =
414  FlutterDesktopViewControllerGetViewId(controller.get());
415 
416  ASSERT_EQ(view_id, static_cast<FlutterDesktopViewId>(kImplicitViewId));
417 }
418 
419 TEST_F(WindowsTest, GetGraphicsAdapter) {
420  auto& context = GetContext();
421  WindowsConfigBuilder builder(context);
422  ViewControllerPtr controller{builder.Run()};
423  ASSERT_NE(controller, nullptr);
424  auto view = FlutterDesktopViewControllerGetView(controller.get());
425 
426  Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
427  dxgi_adapter = FlutterDesktopViewGetGraphicsAdapter(view);
428  ASSERT_NE(dxgi_adapter, nullptr);
429  DXGI_ADAPTER_DESC desc{};
430  ASSERT_TRUE(SUCCEEDED(dxgi_adapter->GetDesc(&desc)));
431 }
432 
433 TEST_F(WindowsTest, GetGraphicsAdapterWithLowPowerPreference) {
434  std::optional<LUID> luid = egl::Manager::GetLowPowerGpuLuid();
435  if (!luid) {
436  GTEST_SKIP() << "Not able to find low power GPU, nothing to check.";
437  }
438 
439  auto& context = GetContext();
440  WindowsConfigBuilder builder(context);
441  builder.SetGpuPreference(FlutterDesktopGpuPreference::LowPowerPreference);
442  ViewControllerPtr controller{builder.Run()};
443  ASSERT_NE(controller, nullptr);
444  auto view = FlutterDesktopViewControllerGetView(controller.get());
445 
446  Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
447  dxgi_adapter = FlutterDesktopViewGetGraphicsAdapter(view);
448  ASSERT_NE(dxgi_adapter, nullptr);
449  DXGI_ADAPTER_DESC desc{};
450  ASSERT_TRUE(SUCCEEDED(dxgi_adapter->GetDesc(&desc)));
451  ASSERT_EQ(desc.AdapterLuid.HighPart, luid->HighPart);
452  ASSERT_EQ(desc.AdapterLuid.LowPart, luid->LowPart);
453 }
454 
455 TEST_F(WindowsTest, GetGraphicsAdapterWithHighPerformancePreference) {
456  std::optional<LUID> luid = egl::Manager::GetHighPerformanceGpuLuid();
457  if (!luid) {
458  GTEST_SKIP() << "Not able to find high performance GPU, nothing to check.";
459  }
460 
461  auto& context = GetContext();
462  WindowsConfigBuilder builder(context);
463  builder.SetGpuPreference(
465  ViewControllerPtr controller{builder.Run()};
466  ASSERT_NE(controller, nullptr);
467  auto view = FlutterDesktopViewControllerGetView(controller.get());
468 
469  Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
470  dxgi_adapter = FlutterDesktopViewGetGraphicsAdapter(view);
471  ASSERT_NE(dxgi_adapter, nullptr);
472  DXGI_ADAPTER_DESC desc{};
473  ASSERT_TRUE(SUCCEEDED(dxgi_adapter->GetDesc(&desc)));
474  ASSERT_EQ(desc.AdapterLuid.HighPart, luid->HighPart);
475  ASSERT_EQ(desc.AdapterLuid.LowPart, luid->LowPart);
476 }
477 
478 // Implicit view has the implicit view ID.
479 TEST_F(WindowsTest, PluginRegistrarGetImplicitView) {
480  auto& context = GetContext();
481  WindowsConfigBuilder builder(context);
482  ViewControllerPtr controller{builder.Run()};
483  ASSERT_NE(controller, nullptr);
484 
485  FlutterDesktopEngineRef engine =
486  FlutterDesktopViewControllerGetEngine(controller.get());
488  FlutterDesktopEngineGetPluginRegistrar(engine, "foo_bar");
489  FlutterDesktopViewRef implicit_view =
491 
492  ASSERT_NE(implicit_view, nullptr);
493 }
494 
495 TEST_F(WindowsTest, PluginRegistrarGetView) {
496  auto& context = GetContext();
497  WindowsConfigBuilder builder(context);
498  ViewControllerPtr controller{builder.Run()};
499  ASSERT_NE(controller, nullptr);
500 
501  FlutterDesktopEngineRef engine =
502  FlutterDesktopViewControllerGetEngine(controller.get());
504  FlutterDesktopEngineGetPluginRegistrar(engine, "foo_bar");
505 
506  FlutterDesktopViewId view_id =
507  FlutterDesktopViewControllerGetViewId(controller.get());
508  FlutterDesktopViewRef view =
509  FlutterDesktopPluginRegistrarGetViewById(registrar, view_id);
510 
512  registrar, static_cast<FlutterDesktopViewId>(123));
513 
514  ASSERT_NE(view, nullptr);
515  ASSERT_EQ(view_123, nullptr);
516 }
517 
518 TEST_F(WindowsTest, PluginRegistrarGetViewHeadless) {
519  auto& context = GetContext();
520  WindowsConfigBuilder builder(context);
521  EnginePtr engine{builder.RunHeadless()};
522  ASSERT_NE(engine, nullptr);
523 
525  FlutterDesktopEngineGetPluginRegistrar(engine.get(), "foo_bar");
526 
527  FlutterDesktopViewRef implicit_view =
530  registrar, static_cast<FlutterDesktopViewId>(123));
531 
532  ASSERT_EQ(implicit_view, nullptr);
533  ASSERT_EQ(view_123, nullptr);
534 }
535 
536 // Verify the app does not crash if EGL initializes successfully but
537 // the rendering surface cannot be created.
538 TEST_F(WindowsTest, SurfaceOptional) {
539  auto& context = GetContext();
540  WindowsConfigBuilder builder(context);
541  EnginePtr engine{builder.InitializeEngine()};
542  EngineModifier modifier{
543  reinterpret_cast<FlutterWindowsEngine*>(engine.get())};
544 
545  auto egl_manager = std::make_unique<HalfBrokenEGLManager>();
546  ASSERT_TRUE(egl_manager->IsValid());
547  modifier.SetEGLManager(std::move(egl_manager));
548 
549  ViewControllerPtr controller{
550  FlutterDesktopViewControllerCreate(0, 0, engine.release())};
551 
552  ASSERT_NE(controller, nullptr);
553 }
554 
555 // Verify the app produces the expected lifecycle events.
556 TEST_F(WindowsTest, Lifecycle) {
557  auto& context = GetContext();
558  WindowsConfigBuilder builder(context);
559  EnginePtr engine{builder.InitializeEngine()};
560  auto windows_engine = reinterpret_cast<FlutterWindowsEngine*>(engine.get());
561  EngineModifier modifier{windows_engine};
562 
563  auto lifecycle_manager =
564  std::make_unique<MockWindowsLifecycleManager>(windows_engine);
565  auto lifecycle_manager_ptr = lifecycle_manager.get();
566  modifier.SetLifecycleManager(std::move(lifecycle_manager));
567 
568  EXPECT_CALL(*lifecycle_manager_ptr,
569  SetLifecycleState(AppLifecycleState::kInactive))
570  .WillOnce([lifecycle_manager_ptr](AppLifecycleState state) {
571  lifecycle_manager_ptr->WindowsLifecycleManager::SetLifecycleState(
572  state);
573  });
574 
575  EXPECT_CALL(*lifecycle_manager_ptr,
576  SetLifecycleState(AppLifecycleState::kHidden))
577  .WillOnce([lifecycle_manager_ptr](AppLifecycleState state) {
578  lifecycle_manager_ptr->WindowsLifecycleManager::SetLifecycleState(
579  state);
580  });
581 
582  FlutterDesktopViewControllerProperties properties = {0, 0};
583 
584  // Create a controller. This launches the engine and sets the app lifecycle
585  // to the "resumed" state.
586  ViewControllerPtr controller{
587  FlutterDesktopEngineCreateViewController(engine.get(), &properties)};
588 
589  FlutterDesktopViewRef view =
590  FlutterDesktopViewControllerGetView(controller.get());
591  ASSERT_NE(view, nullptr);
592 
593  HWND hwnd = FlutterDesktopViewGetHWND(view);
594  ASSERT_NE(hwnd, nullptr);
595 
596  // Give the window a non-zero size to show it. This does not change the app
597  // lifecycle directly. However, destroying the view will now result in a
598  // "hidden" app lifecycle event.
599  ::MoveWindow(hwnd, /* X */ 0, /* Y */ 0, /* nWidth*/ 100, /* nHeight*/ 100,
600  /* bRepaint*/ false);
601 
602  while (lifecycle_manager_ptr->IsUpdateStateScheduled()) {
603  PumpMessage();
604  }
605 
606  // Resets the view, simulating the window being hidden.
607  controller.reset();
608 
609  while (lifecycle_manager_ptr->IsUpdateStateScheduled()) {
610  PumpMessage();
611  }
612 }
613 
614 TEST_F(WindowsTest, GetKeyboardStateHeadless) {
615  auto& context = GetContext();
616  WindowsConfigBuilder builder(context);
617  builder.SetDartEntrypoint("sendGetKeyboardState");
618 
619  std::atomic<bool> done = false;
620  context.AddNativeFunction(
621  "SignalStringValue", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
622  auto handle = Dart_GetNativeArgument(args, 0);
623  ASSERT_FALSE(Dart_IsError(handle));
624  auto value = tonic::DartConverter<std::string>::FromDart(handle);
625  EXPECT_EQ(value, "Success");
626  done = true;
627  }));
628 
629  ViewControllerPtr controller{builder.Run()};
630  ASSERT_NE(controller, nullptr);
631 
632  // Pump messages for the Windows platform task runner.
633  ::MSG msg;
634  while (!done) {
635  PumpMessage();
636  }
637 }
638 
639 // Verify the embedder can add and remove views.
640 TEST_F(WindowsTest, AddRemoveView) {
641  std::mutex mutex;
642  std::string view_ids;
643 
644  auto& context = GetContext();
645  WindowsConfigBuilder builder(context);
646  builder.SetDartEntrypoint("onMetricsChangedSignalViewIds");
647 
648  bool ready = false;
649  context.AddNativeFunction(
650  "Signal",
651  CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { ready = true; }));
652 
653  context.AddNativeFunction(
654  "SignalStringValue", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
655  auto handle = Dart_GetNativeArgument(args, 0);
656  ASSERT_FALSE(Dart_IsError(handle));
657 
658  std::scoped_lock lock{mutex};
659  view_ids = tonic::DartConverter<std::string>::FromDart(handle);
660  }));
661 
662  // Create the implicit view.
663  ViewControllerPtr first_controller{builder.Run()};
664  ASSERT_NE(first_controller, nullptr);
665 
666  while (!ready) {
667  PumpMessage();
668  }
669 
670  // Create a second view.
671  FlutterDesktopEngineRef engine =
672  FlutterDesktopViewControllerGetEngine(first_controller.get());
674  properties.width = 100;
675  properties.height = 100;
676  ViewControllerPtr second_controller{
677  FlutterDesktopEngineCreateViewController(engine, &properties)};
678  ASSERT_NE(second_controller, nullptr);
679 
680  // Pump messages for the Windows platform task runner until the view is added.
681  while (true) {
682  PumpMessage();
683  std::scoped_lock lock{mutex};
684  if (view_ids == "View IDs: [0, 1]") {
685  break;
686  }
687  }
688 
689  // Delete the second view and pump messages for the Windows platform task
690  // runner until the view is removed.
691  second_controller.reset();
692  while (true) {
693  PumpMessage();
694  std::scoped_lock lock{mutex};
695  if (view_ids == "View IDs: [0]") {
696  break;
697  }
698  }
699 }
700 
701 TEST_F(WindowsTest, EngineId) {
702  auto& context = GetContext();
703  WindowsConfigBuilder builder(context);
704  builder.SetDartEntrypoint("testEngineId");
705 
706  std::optional<int64_t> engineId;
707  context.AddNativeFunction(
708  "NotifyEngineId", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
709  const auto argument = Dart_GetNativeArgument(args, 0);
710  if (!Dart_IsNull(argument)) {
711  const auto handle = tonic::DartConverter<int64_t>::FromDart(argument);
712  engineId = handle;
713  }
714  }));
715  // Create the implicit view.
716  ViewControllerPtr first_controller{builder.Run()};
717  ASSERT_NE(first_controller, nullptr);
718 
719  while (!engineId.has_value()) {
720  PumpMessage();
721  }
722 
723  auto engine = FlutterDesktopViewControllerGetEngine(first_controller.get());
724  EXPECT_EQ(engine, FlutterDesktopEngineForId(*engineId));
725 }
726 
727 } // namespace testing
728 } // namespace flutter
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))
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)
FlutterDesktopViewControllerRef FlutterDesktopViewControllerCreate(int width, int height, FlutterDesktopEngineRef engine)
bool FlutterDesktopEngineDestroy(FlutterDesktopEngineRef engine_ref)
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
TEST_F(AccessibilityPluginTest, DirectAnnounceCall)
TEST(AccessibilityBridgeWindows, GetParent)
constexpr FlutterViewId kImplicitViewId