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