Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
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
24#include "gmock/gmock.h"
25#include "gtest/gtest.h"
27
28namespace flutter {
29namespace testing {
30
31namespace {
32
33// An EGL manager that initializes EGL but fails to create surfaces.
34class 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
44class MockWindowsLifecycleManager : public WindowsLifecycleManager {
45 public:
46 MockWindowsLifecycleManager(FlutterWindowsEngine* engine)
48
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.
54void PumpMessage() {
55 ::MSG msg;
56 if (::GetMessage(&msg, nullptr, 0, 0)) {
57 ::TranslateMessage(&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
66TEST(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);
73 EXPECT_NE(texture_registrar, nullptr);
75}
76
77// Verify we can successfully launch main().
78TEST_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.
86TEST_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.
103TEST_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
116TEST_F(WindowsTest, LaunchCustomEntrypointInEngineRunInvocation) {
117 auto& context = GetContext();
118 WindowsConfigBuilder builder(context);
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.
126TEST_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));
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.
155TEST_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{
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.
179TEST_F(WindowsTest, LaunchRefreshesAccessibility) {
180 auto& context = GetContext();
181 WindowsConfigBuilder builder(context);
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{
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
204TEST_F(WindowsTest, LaunchConflictingCustomEntrypoints) {
205 auto& context = GetContext();
206 WindowsConfigBuilder builder(context);
207 builder.SetDartEntrypoint("customEntrypoint");
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.
215TEST_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.
236TEST_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.
261TEST_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.
288TEST_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.
320TEST_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.
373TEST_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.
410TEST_F(WindowsTest, GetViewId) {
411 auto& context = GetContext();
412 WindowsConfigBuilder builder(context);
413 ViewControllerPtr controller{builder.Run()};
414 ASSERT_NE(controller, nullptr);
417
418 ASSERT_EQ(view_id, static_cast<FlutterDesktopViewId>(kImplicitViewId));
419}
420
421TEST_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;
430 ASSERT_NE(dxgi_adapter, nullptr);
431 DXGI_ADAPTER_DESC desc{};
432 ASSERT_TRUE(SUCCEEDED(dxgi_adapter->GetDesc(&desc)));
433}
434
435TEST_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
450TEST_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);
459 ViewControllerPtr controller{builder.Run()};
460 ASSERT_NE(controller, nullptr);
461 auto view = FlutterDesktopViewControllerGetView(controller.get());
462
463 Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
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
472TEST_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;
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
495TEST_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);
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
518TEST_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.
543TEST_F(WindowsTest, PluginRegistrarGetImplicitView) {
544 auto& context = GetContext();
545 WindowsConfigBuilder builder(context);
546 ViewControllerPtr controller{builder.Run()};
547 ASSERT_NE(controller, nullptr);
548
553 FlutterDesktopViewRef implicit_view =
555
556 ASSERT_NE(implicit_view, nullptr);
557}
558
559TEST_F(WindowsTest, PluginRegistrarGetView) {
560 auto& context = GetContext();
561 WindowsConfigBuilder builder(context);
562 ViewControllerPtr controller{builder.Run()};
563 ASSERT_NE(controller, nullptr);
564
569
574
576 registrar, static_cast<FlutterDesktopViewId>(123));
577
578 ASSERT_NE(view, nullptr);
579 ASSERT_EQ(view_123, nullptr);
580}
581
582TEST_F(WindowsTest, PluginRegistrarGetViewHeadless) {
583 auto& context = GetContext();
584 WindowsConfigBuilder builder(context);
585 EnginePtr engine{builder.RunHeadless()};
586 ASSERT_NE(engine, nullptr);
587
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.
602TEST_F(WindowsTest, SurfaceOptional) {
603 auto& context = GetContext();
604 WindowsConfigBuilder builder(context);
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{
615
616 ASSERT_NE(controller, nullptr);
617}
618
619// Verify the app produces the expected lifecycle events.
620TEST_F(WindowsTest, Lifecycle) {
621 auto& context = GetContext();
622 WindowsConfigBuilder builder(context);
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{
652
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
678TEST_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));
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.
704TEST_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};
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.
736 FlutterDesktopViewControllerGetEngine(first_controller.get());
738 properties.width = 100;
739 properties.height = 100;
740 ViewControllerPtr second_controller{
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
765TEST_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
791TEST_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
839TEST_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))
void SetAccessibilityMode(FlutterDesktopAccessibilityMode accessibility_mode)
void SetDartEntrypoint(std::string_view entrypoint)
void SetGpuPreference(FlutterDesktopGpuPreference gpu_preference)
int32_t value
FlutterEngine engine
Definition main.cc:84
FlViewAccessible * accessible
FlView * view
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
G_BEGIN_DECLS FlutterViewId view_id
FLUTTER_EXPORT FlutterDesktopEngineRef FlutterDesktopEngineForId(int64_t engine_id)
FlutterDesktopViewControllerRef FlutterDesktopEngineCreateViewController(FlutterDesktopEngineRef engine, const FlutterDesktopViewControllerProperties *properties)
int64_t FlutterDesktopViewId
struct FlutterDesktopView * FlutterDesktopViewRef
@ LowPowerPreference
@ HighPerformancePreference
@ IAccessibleMode
@ IAccessibleExMode
G_BEGIN_DECLS FlTextureRegistrar * texture_registrar
TEST_F(DisplayListTest, Defaults)
std::unique_ptr< FlutterDesktopEngine, EngineDeleter > EnginePtr
std::unique_ptr< FlutterDesktopViewController, ViewControllerDeleter > ViewControllerPtr
TEST(NativeAssetsManagerTest, NoAvailableAssets)
constexpr int64_t kImplicitViewId
#define MOCK_ENGINE_PROC(proc, mock_impl)
int32_t height
int32_t width
size_t struct_size
The size of this struct. Must be sizeof(FlutterWindowMetricsEvent).
Definition embedder.h:1054
size_t height
Physical height of the window.
Definition embedder.h:1058
int64_t view_id
The view that this event is describing.
Definition embedder.h:1076
double pixel_ratio
Scale factor for the physical screen.
Definition embedder.h:1060
size_t width
Physical width of the window.
Definition embedder.h:1056
HWND FlutterDesktopViewGetHWND(FlutterDesktopViewRef controller)
bool FlutterDesktopEngineGetGraphicsAdapter(FlutterDesktopEngineRef engine, IDXGIAdapter **adapter_out)
FlutterDesktopEngineRef FlutterDesktopEngineCreate(const FlutterDesktopEngineProperties *engine_properties)
FlutterDesktopViewId FlutterDesktopViewControllerGetViewId(FlutterDesktopViewControllerRef controller)
FlutterDesktopPluginRegistrarRef FlutterDesktopEngineGetPluginRegistrar(FlutterDesktopEngineRef engine, const char *plugin_name)
FlutterDesktopViewControllerRef FlutterDesktopViewControllerCreate(int width, int height, FlutterDesktopEngineRef engine)
bool FlutterDesktopEngineDestroy(FlutterDesktopEngineRef engine_ref)
FlutterDesktopEngineRef FlutterDesktopViewControllerGetEngine(FlutterDesktopViewControllerRef controller)
FlutterDesktopViewRef FlutterDesktopViewControllerGetView(FlutterDesktopViewControllerRef controller)
FlutterDesktopTextureRegistrarRef FlutterDesktopEngineGetTextureRegistrar(FlutterDesktopEngineRef engine)
FlutterDesktopViewRef FlutterDesktopPluginRegistrarGetView(FlutterDesktopPluginRegistrarRef controller)
IDXGIAdapter * FlutterDesktopViewGetGraphicsAdapter(FlutterDesktopViewRef view)
void FlutterDesktopEngineSetNextFrameCallback(FlutterDesktopEngineRef engine, VoidCallback callback, void *user_data)
FlutterDesktopViewRef FlutterDesktopPluginRegistrarGetViewById(FlutterDesktopPluginRegistrarRef controller, FlutterDesktopViewId view_id)
bool FlutterDesktopEngineRun(FlutterDesktopEngineRef engine, const char *entry_point)
#define CREATE_NATIVE_ENTRY(native_entry)
#define SUCCEEDED(hr)
struct tagMSG MSG
LONG_PTR LRESULT
#define GetMessage
#define SendMessage
#define FAILED(hr)
#define DispatchMessage