| 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
5 #include "flutter/shell/platform/windows/public/flutter_windows.h"
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"
13 #include "flutter/shell/platform/common/app_lifecycle_state.h"
14 #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
15 #include "flutter/shell/platform/windows/egl/manager.h"
16 #include "flutter/shell/platform/windows/flutter_windows_view.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"
22 #include "flutter/shell/platform/windows/windows_lifecycle_manager.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);
74 FlutterDesktopEngineDestroy(engine);
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 {
164 FlutterDesktopViewControllerProperties properties = {};
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
346 FlutterDesktopEngineSetNextFrameCallback(
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;
382 FlutterDesktopEngineSetNextFrameCallback(
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 Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
443 dxgi_adapter = FlutterDesktopEngineGetGraphicsAdapter(engine);
444 ASSERT_NE(dxgi_adapter, nullptr);
445 DXGI_ADAPTER_DESC desc{};
446 ASSERT_TRUE(SUCCEEDED(dxgi_adapter->GetDesc(&desc)));
447 }
448
449 TEST_F(WindowsTest, GetGraphicsAdapterWithLowPowerPreference) {
450 std::optional<LUID> luid = egl::Manager::GetLowPowerGpuLuid();
451 if (!luid) {
452 GTEST_SKIP() << "Not able to find low power GPU, nothing to check.";
453 }
454
455 auto& context = GetContext();
456 WindowsConfigBuilder builder(context);
457 builder.SetGpuPreference(FlutterDesktopGpuPreference::LowPowerPreference);
458 ViewControllerPtr controller{builder.Run()};
459 ASSERT_NE(controller, nullptr);
460 auto view = FlutterDesktopViewControllerGetView(controller.get());
461
462 Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
463 dxgi_adapter = FlutterDesktopViewGetGraphicsAdapter(view);
464 ASSERT_NE(dxgi_adapter, nullptr);
465 DXGI_ADAPTER_DESC desc{};
466 ASSERT_TRUE(SUCCEEDED(dxgi_adapter->GetDesc(&desc)));
467 ASSERT_EQ(desc.AdapterLuid.HighPart, luid->HighPart);
468 ASSERT_EQ(desc.AdapterLuid.LowPart, luid->LowPart);
469 }
470
471 TEST_F(WindowsTest, GetGraphicsAdapterWithHighPerformancePreference) {
472 std::optional<LUID> luid = egl::Manager::GetHighPerformanceGpuLuid();
473 if (!luid) {
474 GTEST_SKIP() << "Not able to find high performance GPU, nothing to check.";
475 }
476
477 auto& context = GetContext();
478 WindowsConfigBuilder builder(context);
479 builder.SetGpuPreference(
480 FlutterDesktopGpuPreference::HighPerformancePreference);
481 ViewControllerPtr controller{builder.Run()};
482 ASSERT_NE(controller, nullptr);
483 auto view = FlutterDesktopViewControllerGetView(controller.get());
484
485 Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
486 dxgi_adapter = FlutterDesktopViewGetGraphicsAdapter(view);
487 ASSERT_NE(dxgi_adapter, nullptr);
488 DXGI_ADAPTER_DESC desc{};
489 ASSERT_TRUE(SUCCEEDED(dxgi_adapter->GetDesc(&desc)));
490 ASSERT_EQ(desc.AdapterLuid.HighPart, luid->HighPart);
491 ASSERT_EQ(desc.AdapterLuid.LowPart, luid->LowPart);
492 }
493
494 TEST_F(WindowsTest, GetEngineGraphicsAdapterWithLowPowerPreference) {
495 std::optional<LUID> luid = egl::Manager::GetLowPowerGpuLuid();
496 if (!luid) {
497 GTEST_SKIP() << "Not able to find low power GPU, nothing to check.";
498 }
499
500 auto& context = GetContext();
501 WindowsConfigBuilder builder(context);
502 builder.SetGpuPreference(FlutterDesktopGpuPreference::LowPowerPreference);
503 ViewControllerPtr controller{builder.Run()};
504 ASSERT_NE(controller, nullptr);
505 auto engine = FlutterDesktopViewControllerGetEngine(controller.get());
506
507 Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
508 dxgi_adapter = FlutterDesktopEngineGetGraphicsAdapter(engine);
509 ASSERT_NE(dxgi_adapter, nullptr);
510 DXGI_ADAPTER_DESC desc{};
511 ASSERT_TRUE(SUCCEEDED(dxgi_adapter->GetDesc(&desc)));
512 ASSERT_EQ(desc.AdapterLuid.HighPart, luid->HighPart);
513 ASSERT_EQ(desc.AdapterLuid.LowPart, luid->LowPart);
514 }
515
516 TEST_F(WindowsTest, GetEngineGraphicsAdapterWithHighPerformancePreference) {
517 std::optional<LUID> luid = egl::Manager::GetHighPerformanceGpuLuid();
518 if (!luid) {
519 GTEST_SKIP() << "Not able to find high performance GPU, nothing to check.";
520 }
521
522 auto& context = GetContext();
523 WindowsConfigBuilder builder(context);
524 builder.SetGpuPreference(
525 FlutterDesktopGpuPreference::HighPerformancePreference);
526 ViewControllerPtr controller{builder.Run()};
527 ASSERT_NE(controller, nullptr);
528 auto engine = FlutterDesktopViewControllerGetEngine(controller.get());
529
530 Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
531 dxgi_adapter = FlutterDesktopEngineGetGraphicsAdapter(engine);
532 ASSERT_NE(dxgi_adapter, nullptr);
533 DXGI_ADAPTER_DESC desc{};
534 ASSERT_TRUE(SUCCEEDED(dxgi_adapter->GetDesc(&desc)));
535 ASSERT_EQ(desc.AdapterLuid.HighPart, luid->HighPart);
536 ASSERT_EQ(desc.AdapterLuid.LowPart, luid->LowPart);
537 }
538
539 // Implicit view has the implicit view ID.
540 TEST_F(WindowsTest, PluginRegistrarGetImplicitView) {
541 auto& context = GetContext();
542 WindowsConfigBuilder builder(context);
543 ViewControllerPtr controller{builder.Run()};
544 ASSERT_NE(controller, nullptr);
545
546 FlutterDesktopEngineRef engine =
547 FlutterDesktopViewControllerGetEngine(controller.get());
548 FlutterDesktopPluginRegistrarRef registrar =
549 FlutterDesktopEngineGetPluginRegistrar(engine, "foo_bar");
550 FlutterDesktopViewRef implicit_view =
551 FlutterDesktopPluginRegistrarGetView(registrar);
552
553 ASSERT_NE(implicit_view, nullptr);
554 }
555
556 TEST_F(WindowsTest, PluginRegistrarGetView) {
557 auto& context = GetContext();
558 WindowsConfigBuilder builder(context);
559 ViewControllerPtr controller{builder.Run()};
560 ASSERT_NE(controller, nullptr);
561
562 FlutterDesktopEngineRef engine =
563 FlutterDesktopViewControllerGetEngine(controller.get());
564 FlutterDesktopPluginRegistrarRef registrar =
565 FlutterDesktopEngineGetPluginRegistrar(engine, "foo_bar");
566
567 FlutterDesktopViewId view_id =
568 FlutterDesktopViewControllerGetViewId(controller.get());
569 FlutterDesktopViewRef view =
570 FlutterDesktopPluginRegistrarGetViewById(registrar, view_id);
571
572 FlutterDesktopViewRef view_123 = FlutterDesktopPluginRegistrarGetViewById(
573 registrar, static_cast<FlutterDesktopViewId>(123));
574
575 ASSERT_NE(view, nullptr);
576 ASSERT_EQ(view_123, nullptr);
577 }
578
579 TEST_F(WindowsTest, PluginRegistrarGetViewHeadless) {
580 auto& context = GetContext();
581 WindowsConfigBuilder builder(context);
582 EnginePtr engine{builder.RunHeadless()};
583 ASSERT_NE(engine, nullptr);
584
585 FlutterDesktopPluginRegistrarRef registrar =
586 FlutterDesktopEngineGetPluginRegistrar(engine.get(), "foo_bar");
587
588 FlutterDesktopViewRef implicit_view =
589 FlutterDesktopPluginRegistrarGetView(registrar);
590 FlutterDesktopViewRef view_123 = FlutterDesktopPluginRegistrarGetViewById(
591 registrar, static_cast<FlutterDesktopViewId>(123));
592
593 ASSERT_EQ(implicit_view, nullptr);
594 ASSERT_EQ(view_123, nullptr);
595 }
596
597 // Verify the app does not crash if EGL initializes successfully but
598 // the rendering surface cannot be created.
599 TEST_F(WindowsTest, SurfaceOptional) {
600 auto& context = GetContext();
601 WindowsConfigBuilder builder(context);
602 EnginePtr engine{builder.InitializeEngine()};
603 EngineModifier modifier{
604 reinterpret_cast<FlutterWindowsEngine*>(engine.get())};
605
606 auto egl_manager = std::make_unique<HalfBrokenEGLManager>();
607 ASSERT_TRUE(egl_manager->IsValid());
608 modifier.SetEGLManager(std::move(egl_manager));
609
610 ViewControllerPtr controller{
611 FlutterDesktopViewControllerCreate(0, 0, engine.release())};
612
613 ASSERT_NE(controller, nullptr);
614 }
615
616 // Verify the app produces the expected lifecycle events.
617 TEST_F(WindowsTest, Lifecycle) {
618 auto& context = GetContext();
619 WindowsConfigBuilder builder(context);
620 EnginePtr engine{builder.InitializeEngine()};
621 auto windows_engine = reinterpret_cast<FlutterWindowsEngine*>(engine.get());
622 EngineModifier modifier{windows_engine};
623
624 auto lifecycle_manager =
625 std::make_unique<MockWindowsLifecycleManager>(windows_engine);
626 auto lifecycle_manager_ptr = lifecycle_manager.get();
627 modifier.SetLifecycleManager(std::move(lifecycle_manager));
628
629 EXPECT_CALL(*lifecycle_manager_ptr,
630 SetLifecycleState(AppLifecycleState::kInactive))
631 .WillOnce([lifecycle_manager_ptr](AppLifecycleState state) {
632 lifecycle_manager_ptr->WindowsLifecycleManager::SetLifecycleState(
633 state);
634 });
635
636 EXPECT_CALL(*lifecycle_manager_ptr,
637 SetLifecycleState(AppLifecycleState::kHidden))
638 .WillOnce([lifecycle_manager_ptr](AppLifecycleState state) {
639 lifecycle_manager_ptr->WindowsLifecycleManager::SetLifecycleState(
640 state);
641 });
642
643 FlutterDesktopViewControllerProperties properties = {0, 0};
644
645 // Create a controller. This launches the engine and sets the app lifecycle
646 // to the "resumed" state.
647 ViewControllerPtr controller{
648 FlutterDesktopEngineCreateViewController(engine.get(), &properties)};
649
650 FlutterDesktopViewRef view =
651 FlutterDesktopViewControllerGetView(controller.get());
652 ASSERT_NE(view, nullptr);
653
654 HWND hwnd = FlutterDesktopViewGetHWND(view);
655 ASSERT_NE(hwnd, nullptr);
656
657 // Give the window a non-zero size to show it. This does not change the app
658 // lifecycle directly. However, destroying the view will now result in a
659 // "hidden" app lifecycle event.
660 ::MoveWindow(hwnd, /* X */ 0, /* Y */ 0, /* nWidth*/ 100, /* nHeight*/ 100,
661 /* bRepaint*/ false);
662
663 while (lifecycle_manager_ptr->IsUpdateStateScheduled()) {
664 PumpMessage();
665 }
666
667 // Resets the view, simulating the window being hidden.
668 controller.reset();
669
670 while (lifecycle_manager_ptr->IsUpdateStateScheduled()) {
671 PumpMessage();
672 }
673 }
674
675 TEST_F(WindowsTest, GetKeyboardStateHeadless) {
676 auto& context = GetContext();
677 WindowsConfigBuilder builder(context);
678 builder.SetDartEntrypoint("sendGetKeyboardState");
679
680 std::atomic<bool> done = false;
681 context.AddNativeFunction(
682 "SignalStringValue", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
683 auto handle = Dart_GetNativeArgument(args, 0);
684 ASSERT_FALSE(Dart_IsError(handle));
685 auto value = tonic::DartConverter<std::string>::FromDart(handle);
686 EXPECT_EQ(value, "Success");
687 done = true;
688 }));
689
690 ViewControllerPtr controller{builder.Run()};
691 ASSERT_NE(controller, nullptr);
692
693 // Pump messages for the Windows platform task runner.
694 ::MSG msg;
695 while (!done) {
696 PumpMessage();
697 }
698 }
699
700 // Verify the embedder can add and remove views.
701 TEST_F(WindowsTest, AddRemoveView) {
702 std::mutex mutex;
703 std::string view_ids;
704
705 auto& context = GetContext();
706 WindowsConfigBuilder builder(context);
707 builder.SetDartEntrypoint("onMetricsChangedSignalViewIds");
708
709 bool ready = false;
710 context.AddNativeFunction(
711 "Signal",
712 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { ready = true; }));
713
714 context.AddNativeFunction(
715 "SignalStringValue", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
716 auto handle = Dart_GetNativeArgument(args, 0);
717 ASSERT_FALSE(Dart_IsError(handle));
718
719 std::scoped_lock lock{mutex};
720 view_ids = tonic::DartConverter<std::string>::FromDart(handle);
721 }));
722
723 // Create the implicit view.
724 ViewControllerPtr first_controller{builder.Run()};
725 ASSERT_NE(first_controller, nullptr);
726
727 while (!ready) {
728 PumpMessage();
729 }
730
731 // Create a second view.
732 FlutterDesktopEngineRef engine =
733 FlutterDesktopViewControllerGetEngine(first_controller.get());
734 FlutterDesktopViewControllerProperties properties = {};
735 properties.width = 100;
736 properties.height = 100;
737 ViewControllerPtr second_controller{
738 FlutterDesktopEngineCreateViewController(engine, &properties)};
739 ASSERT_NE(second_controller, nullptr);
740
741 // Pump messages for the Windows platform task runner until the view is added.
742 while (true) {
743 PumpMessage();
744 std::scoped_lock lock{mutex};
745 if (view_ids == "View IDs: [0, 1]") {
746 break;
747 }
748 }
749
750 // Delete the second view and pump messages for the Windows platform task
751 // runner until the view is removed.
752 second_controller.reset();
753 while (true) {
754 PumpMessage();
755 std::scoped_lock lock{mutex};
756 if (view_ids == "View IDs: [0]") {
757 break;
758 }
759 }
760 }
761
762 TEST_F(WindowsTest, EngineId) {
763 auto& context = GetContext();
764 WindowsConfigBuilder builder(context);
765 builder.SetDartEntrypoint("testEngineId");
766
767 std::optional<int64_t> engineId;
768 context.AddNativeFunction(
769 "NotifyEngineId", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
770 const auto argument = Dart_GetNativeArgument(args, 0);
771 if (!Dart_IsNull(argument)) {
772 const auto handle = tonic::DartConverter<int64_t>::FromDart(argument);
773 engineId = handle;
774 }
775 }));
776 // Create the implicit view.
777 ViewControllerPtr first_controller{builder.Run()};
778 ASSERT_NE(first_controller, nullptr);
779
780 while (!engineId.has_value()) {
781 PumpMessage();
782 }
783
784 auto engine = FlutterDesktopViewControllerGetEngine(first_controller.get());
785 EXPECT_EQ(engine, FlutterDesktopEngineForId(*engineId));
786 }
787
788 TEST_F(WindowsTest, EnableIAccessible) {
789 auto& context = GetContext();
790 WindowsConfigBuilder builder(context);
791 builder.SetAccessibilityMode(
792 FlutterDesktopAccessibilityMode::IAccessibleMode);
793 builder.SetDartEntrypoint("sendSemanticsTree");
794
795 // Setup: a signal for when we have send out all of our semantics updates
796 bool done = false;
797 auto native_entry =
798 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { done = true; });
799 context.AddNativeFunction("Signal", native_entry);
800
801 // Setup: Create a view
802 ViewControllerPtr controller{builder.Run()};
803 ASSERT_NE(controller, nullptr);
804
805 auto view = FlutterDesktopViewControllerGetView(controller.get());
806 ASSERT_NE(view, nullptr);
807
808 // Setup: UpdateSemanticsEnabled will trigger the semantics updates
809 // to get sent.
810 auto windows_view = reinterpret_cast<FlutterWindowsView*>(view);
811 windows_view->OnUpdateSemanticsEnabled(true);
812
813 while (!done) {
814 PumpMessage();
815 }
816
817 HWND hwnd = FlutterDesktopViewGetHWND(view);
818 ASSERT_NE(hwnd, nullptr);
819
820 LRESULT lres = SendMessage(hwnd, WM_GETOBJECT, 0, OBJID_CLIENT);
821 ASSERT_NE(lres, 0);
822
823 // In IAccessible mode, the object returned from WM_GETOBJECT should support
824 // IAccessible but not IAccessibleEx.
825 IAccessible* accessible = nullptr;
826 HRESULT hr = ObjectFromLresult(lres, IID_IAccessible, 0, (void**)&accessible);
827 ASSERT_TRUE(SUCCEEDED(hr));
828 ASSERT_NE(accessible, nullptr);
829
830 IAccessibleEx* accessible_ex = nullptr;
831 hr = ObjectFromLresult(lres, IID_IAccessibleEx, 0, (void**)&accessible_ex);
832 ASSERT_TRUE(FAILED(hr));
833 ASSERT_EQ(accessible_ex, nullptr);
834 }
835
836 TEST_F(WindowsTest, EnableIAccessibleEx) {
837 auto& context = GetContext();
838 WindowsConfigBuilder builder(context);
839 builder.SetAccessibilityMode(
840 FlutterDesktopAccessibilityMode::IAccessibleExMode);
841 builder.SetDartEntrypoint("sendSemanticsTree");
842
843 // Setup: a signal for when we have send out all of our semantics updates
844 bool done = false;
845 auto native_entry =
846 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { done = true; });
847 context.AddNativeFunction("Signal", native_entry);
848
849 // Setup: Create a view
850 ViewControllerPtr controller{builder.Run()};
851 ASSERT_NE(controller, nullptr);
852
853 auto view = FlutterDesktopViewControllerGetView(controller.get());
854 ASSERT_NE(view, nullptr);
855
856 // Setup: UpdateSemanticsEnabled will trigger the semantics updates
857 // to get sent.
858 auto windows_view = reinterpret_cast<FlutterWindowsView*>(view);
859 windows_view->OnUpdateSemanticsEnabled(true);
860
861 while (!done) {
862 PumpMessage();
863 }
864
865 HWND hwnd = FlutterDesktopViewGetHWND(view);
866 ASSERT_NE(hwnd, nullptr);
867
868 LRESULT lres = SendMessage(hwnd, WM_GETOBJECT, 0, OBJID_CLIENT);
869 ASSERT_NE(lres, 0);
870
871 // In IAccessibleEx mode, the object returned from WM_GETOBJECT should
872 // support IAccessibleEx.
873 IAccessibleEx* accessible_ex = nullptr;
874 HRESULT hr =
875 ObjectFromLresult(lres, IID_IAccessibleEx, 0, (void**)&accessible_ex);
876 ASSERT_TRUE(SUCCEEDED(hr));
877 ASSERT_NE(accessible_ex, nullptr);
878 accessible_ex->Release();
879 }
880
881 } // namespace testing
882 } // namespace flutter
app_lifecycle_state.h
flutter::FlutterWindowsEngine
Definition: flutter_windows_engine.h:92
flutter::FlutterWindowsEngine::UpdateAccessibilityFeatures
void UpdateAccessibilityFeatures()
Definition: flutter_windows_engine.cc:1021
flutter::FlutterWindowsView
Definition: flutter_windows_view.h:47
flutter::FlutterWindowsView::OnUpdateSemanticsEnabled
virtual void OnUpdateSemanticsEnabled(bool enabled) override
Definition: flutter_windows_view.cc:383
flutter::WindowsLifecycleManager::WindowsLifecycleManager
WindowsLifecycleManager(FlutterWindowsEngine *engine)
Definition: windows_lifecycle_manager.cc:16
flutter::WindowsLifecycleManager::SetLifecycleState
virtual void SetLifecycleState(AppLifecycleState state)
Definition: windows_lifecycle_manager.cc:195
flutter::egl::Manager::GetLowPowerGpuLuid
static std::optional< LUID > GetLowPowerGpuLuid()
Definition: manager.cc:376
flutter::egl::Manager::GetHighPerformanceGpuLuid
static std::optional< LUID > GetHighPerformanceGpuLuid()
Definition: manager.cc:380
flutter::testing::MockWindowsLifecycleManager::MOCK_METHOD
MOCK_METHOD(void, Quit,(std::optional< HWND >, std::optional< WPARAM >, std::optional< LPARAM >, UINT),(override))
flutter::testing::MockWindowsLifecycleManager::MockWindowsLifecycleManager
MockWindowsLifecycleManager(FlutterWindowsEngine *engine)
Definition: flutter_windows_engine_unittests.cc:885
FlutterDesktopEngineCreate
FlutterDesktopEngineRef FlutterDesktopEngineCreate(const FlutterDesktopEngineProperties *engine_properties)
Definition: flutter_windows.cc:184
FlutterDesktopEngineForId
FLUTTER_EXPORT FlutterDesktopEngineRef FlutterDesktopEngineForId(int64_t engine_id)
Definition: flutter_windows.cc:202
FlutterDesktopPluginRegistrarGetViewById
FlutterDesktopViewRef FlutterDesktopPluginRegistrarGetViewById(FlutterDesktopPluginRegistrarRef registrar, FlutterDesktopViewId view_id)
Definition: flutter_windows.cc:308
FlutterDesktopEngineGetPluginRegistrar
FlutterDesktopPluginRegistrarRef FlutterDesktopEngineGetPluginRegistrar(FlutterDesktopEngineRef engine, const char *plugin_name)
Definition: flutter_windows.cc:225
FlutterDesktopViewControllerCreate
FlutterDesktopViewControllerRef FlutterDesktopViewControllerCreate(int width, int height, FlutterDesktopEngineRef engine)
Definition: flutter_windows.cc:122
FlutterDesktopEngineDestroy
bool FlutterDesktopEngineDestroy(FlutterDesktopEngineRef engine_ref)
Definition: flutter_windows.cc:191
FlutterDesktopEngineGetTextureRegistrar
FlutterDesktopTextureRegistrarRef FlutterDesktopEngineGetTextureRegistrar(FlutterDesktopEngineRef engine)
Definition: flutter_windows.cc:240
FlutterDesktopViewGetGraphicsAdapter
IDXGIAdapter * FlutterDesktopViewGetGraphicsAdapter(FlutterDesktopViewRef view)
Definition: flutter_windows.cc:257
FlutterDesktopEngineGetGraphicsAdapter
IDXGIAdapter * FlutterDesktopEngineGetGraphicsAdapter(FlutterDesktopEngineRef engine)
Definition: flutter_windows.cc:262
FlutterDesktopViewControllerGetViewId
FlutterDesktopViewId FlutterDesktopViewControllerGetViewId(FlutterDesktopViewControllerRef ref)
Definition: flutter_windows.cc:142
FlutterDesktopViewControllerGetEngine
FlutterDesktopEngineRef FlutterDesktopViewControllerGetEngine(FlutterDesktopViewControllerRef ref)
Definition: flutter_windows.cc:148
FlutterDesktopEngineSetNextFrameCallback
void FlutterDesktopEngineSetNextFrameCallback(FlutterDesktopEngineRef engine, VoidCallback callback, void *user_data)
Definition: flutter_windows.cc:246
FlutterDesktopViewGetHWND
HWND FlutterDesktopViewGetHWND(FlutterDesktopViewRef view)
Definition: flutter_windows.cc:253
FlutterDesktopViewControllerGetView
FlutterDesktopViewRef FlutterDesktopViewControllerGetView(FlutterDesktopViewControllerRef ref)
Definition: flutter_windows.cc:154
FlutterDesktopPluginRegistrarGetView
FlutterDesktopViewRef FlutterDesktopPluginRegistrarGetView(FlutterDesktopPluginRegistrarRef registrar)
Definition: flutter_windows.cc:303
FlutterDesktopEngineCreateViewController
FlutterDesktopViewControllerRef FlutterDesktopEngineCreateViewController(FlutterDesktopEngineRef engine, const FlutterDesktopViewControllerProperties *properties)
Definition: flutter_windows.cc:129
FlutterDesktopEngineRun
bool FlutterDesktopEngineRun(FlutterDesktopEngineRef engine, const char *entry_point)
Definition: flutter_windows.cc:207
flutter_windows.h
FlutterDesktopEngineRef
struct FlutterDesktopEngine * FlutterDesktopEngineRef
Definition: flutter_windows.h:33
FlutterDesktopViewId
int64_t FlutterDesktopViewId
Definition: flutter_windows.h:36
FlutterDesktopViewRef
struct FlutterDesktopView * FlutterDesktopViewRef
Definition: flutter_windows.h:29
LowPowerPreference
@ LowPowerPreference
Definition: flutter_windows.h:45
HighPerformancePreference
@ HighPerformancePreference
Definition: flutter_windows.h:49
NoPreference
@ NoPreference
Definition: flutter_windows.h:41
IAccessibleMode
@ IAccessibleMode
Definition: flutter_windows.h:69
IAccessibleExMode
@ IAccessibleExMode
Definition: flutter_windows.h:71
flutter_windows_view.h
user_data
void * user_data
Definition: flutter_windows_view_unittests.cc:53
manager.h
flutter::testing::TEST_F
TEST_F(AccessibilityPluginTest, DirectAnnounceCall)
Definition: accessibility_plugin_unittests.cc:102
flutter::testing::TEST
TEST(AccessibilityBridgeWindows, GetParent)
Definition: accessibility_bridge_windows_unittests.cc:247
flutter
Definition: accessibility_bridge_windows.cc:11
flutter::GpuPreference
GpuPreference
Definition: dart_project.h:14
flutter::AppLifecycleState
AppLifecycleState
Definition: app_lifecycle_state.h:32
flutter::AppLifecycleState::kHidden
@ kHidden
flutter::AppLifecycleState::kInactive
@ kInactive
flutter::kImplicitViewId
constexpr FlutterViewId kImplicitViewId
Definition: flutter_windows_engine.h:56
flutter::AccessibilityMode::IAccessibleEx
@ IAccessibleEx
flutter::AccessibilityMode::IAccessible
@ IAccessible
FlutterDesktopEngineProperties
Definition: flutter_windows.h:75
FlutterDesktopEngineProperties::icu_data_path
const wchar_t * icu_data_path
Definition: flutter_windows.h:84
FlutterDesktopEngineProperties::assets_path
const wchar_t * assets_path
Definition: flutter_windows.h:79
FlutterDesktopPluginRegistrar
Definition: window_state.h:23
FlutterDesktopViewControllerProperties
Definition: flutter_windows_internal.h:18
FlutterDesktopViewControllerProperties::width
int width
Definition: flutter_windows_internal.h:20
FlutterDesktopViewControllerProperties::height
int height
Definition: flutter_windows_internal.h:23
windows_lifecycle_manager.h