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