Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
window_manager_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
13#include "gmock/gmock.h"
14#include "gtest/gtest.h"
15
16namespace flutter {
17namespace testing {
18
19namespace {
20
21using ::testing::NiceMock;
22using ::testing::Return;
23
24// Builds a mock |WindowSurface| whose lifecycle operations all succeed. Used to
25// keep the EGL surface lifecycle deterministic in tests so that resize paths
26// (e.g. |FlutterWindowsView::OnFrameGenerated|) do not perform real, flaky
27// cross-thread ANGLE/D3D calls against an actual GPU surface.
28std::unique_ptr<egl::MockWindowSurface> CreateMockWindowSurface() {
29 auto surface = std::make_unique<NiceMock<egl::MockWindowSurface>>();
30 ON_CALL(*surface, IsValid).WillByDefault(Return(true));
31 ON_CALL(*surface, MakeCurrent).WillByDefault(Return(true));
32 ON_CALL(*surface, SetVSyncEnabled).WillByDefault(Return(true));
33 ON_CALL(*surface, Destroy).WillByDefault(Return(true));
34 return surface;
35}
36
37class WindowManagerTest : public WindowsTest {
38 public:
39 WindowManagerTest() = default;
40 virtual ~WindowManagerTest() = default;
41
42 protected:
43 void SetUp() override {
44 auto& context = GetContext();
45 FlutterWindowsEngineBuilder builder(context);
46 ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
47
48 engine_ = builder.Build();
49 ASSERT_TRUE(engine_);
50
51 engine_->SetRootIsolateCreateCallback(context.GetRootIsolateCallback());
52 ASSERT_TRUE(engine_->Run("testWindowController"));
53
54 bool signalled = false;
55 context.AddNativeFunction(
56 "Signal", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
57 isolate_ = flutter::Isolate::Current();
58 signalled = true;
59 }));
60 while (!signalled) {
61 engine_->task_runner()->ProcessTasks();
62 }
63
64 // Replace the engine's EGL manager with a permissive mock so that EGL
65 // surface creation and resizing are deterministic and never issue real,
66 // cross-thread GPU calls. These tests do not exercise real EGL rendering,
67 // and without this, tests that drive |FlutterWindowsView::OnFrameGenerated|
68 // from the test thread race the engine's raster thread for the EGL context,
69 // intermittently producing an EGL_BAD_ACCESS and a crash. Installed here,
70 // before any windows (and thus render surfaces) are created.
71 auto egl_manager = std::make_unique<NiceMock<egl::MockManager>>();
72 ON_CALL(*egl_manager, CreateWindowSurface)
73 .WillByDefault(
74 [](HWND, size_t, size_t) { return CreateMockWindowSurface(); });
75 ON_CALL(*egl_manager, render_context)
76 .WillByDefault(Return(&mock_egl_context_));
77 ON_CALL(mock_egl_context_, ClearCurrent).WillByDefault(Return(true));
78 ON_CALL(mock_egl_context_, MakeCurrent).WillByDefault(Return(true));
79 EngineModifier{engine_.get()}.SetEGLManager(std::move(egl_manager));
80 }
81
82 void TearDown() override { engine_->Stop(); }
83
84 int64_t engine_id() { return reinterpret_cast<int64_t>(engine_.get()); }
85 flutter::Isolate& isolate() { return *isolate_; }
86 RegularWindowCreationRequest* regular_creation_request() {
87 return &regular_creation_request_;
88 }
89 FlutterWindowsEngine* engine() { return engine_.get(); }
90
91 private:
92 std::unique_ptr<FlutterWindowsEngine> engine_;
93 NiceMock<egl::MockContext> mock_egl_context_;
94 std::optional<flutter::Isolate> isolate_;
95 RegularWindowCreationRequest regular_creation_request_{
96 .preferred_size =
97 {
98 .has_preferred_view_size = true,
99 .preferred_view_width = 800,
100 .preferred_view_height = 600,
101 },
102 };
103
104 FML_DISALLOW_COPY_AND_ASSIGN(WindowManagerTest);
105};
106
107} // namespace
108
109TEST_F(WindowManagerTest, WindowingInitialize) {
110 IsolateScope isolate_scope(isolate());
111
112 static bool received_message = false;
113 WindowingInitRequest init_request{
114 .on_message = [](WindowsMessage* message) { received_message = true; }};
115
116 InternalFlutterWindows_WindowManager_Initialize(engine_id(), &init_request);
117 const int64_t view_id =
119 engine_id(), regular_creation_request());
121 engine_id(), view_id));
122
123 EXPECT_TRUE(received_message);
124}
125
126TEST_F(WindowManagerTest, CreateRegularWindow) {
127 IsolateScope isolate_scope(isolate());
128
129 const int64_t view_id =
131 engine_id(), regular_creation_request());
132 EXPECT_EQ(view_id, 0);
133}
134
135TEST_F(WindowManagerTest, GetWindowHandle) {
136 IsolateScope isolate_scope(isolate());
137
138 const int64_t view_id =
140 engine_id(), regular_creation_request());
141 const HWND window_handle =
143 view_id);
144 EXPECT_NE(window_handle, nullptr);
145}
146
147TEST_F(WindowManagerTest, GetWindowSize) {
148 IsolateScope isolate_scope(isolate());
149
150 const int64_t view_id =
152 engine_id(), regular_creation_request());
153 const HWND window_handle =
155 view_id);
156
159
160 EXPECT_EQ(size.width,
161 regular_creation_request()->preferred_size.preferred_view_width);
162 EXPECT_EQ(size.height,
163 regular_creation_request()->preferred_size.preferred_view_height);
164}
165
166TEST_F(WindowManagerTest, SetWindowSize) {
167 IsolateScope isolate_scope(isolate());
168
169 const int64_t view_id =
171 engine_id(), regular_creation_request());
172 const HWND window_handle =
174 view_id);
175
176 WindowSizeRequest requestedSize{
177
179 .preferred_view_width = 640,
180 .preferred_view_height = 480,
181 };
183 &requestedSize);
184
185 ActualWindowSize actual_size =
187 EXPECT_EQ(actual_size.width, 640);
188 EXPECT_EQ(actual_size.height, 480);
189}
190
191TEST_F(WindowManagerTest, CanConstrainByMinimiumSize) {
192 IsolateScope isolate_scope(isolate());
193
194 const int64_t view_id =
196 engine_id(), regular_creation_request());
197 const HWND window_handle =
199 view_id);
200 WindowConstraints constraints{.has_view_constraints = true,
201 .view_min_width = 900,
202 .view_min_height = 700,
203 .view_max_width = 10000,
204 .view_max_height = 10000};
206 &constraints);
207
208 ActualWindowSize actual_size =
210 EXPECT_EQ(actual_size.width, 900);
211 EXPECT_EQ(actual_size.height, 700);
212}
213
214TEST_F(WindowManagerTest, CanConstrainByMaximumSize) {
215 IsolateScope isolate_scope(isolate());
216
217 const int64_t view_id =
219 engine_id(), regular_creation_request());
220 const HWND window_handle =
222 view_id);
223 WindowConstraints constraints{.has_view_constraints = true,
224 .view_min_width = 0,
225 .view_min_height = 0,
226 .view_max_width = 500,
227 .view_max_height = 500};
229 &constraints);
230
231 ActualWindowSize actual_size =
233 EXPECT_EQ(actual_size.width, 500);
234 EXPECT_EQ(actual_size.height, 500);
235}
236
237TEST_F(WindowManagerTest, CanFullscreenWindow) {
238 IsolateScope isolate_scope(isolate());
239
240 const int64_t view_id =
242 engine_id(), regular_creation_request());
243 const HWND window_handle =
245 view_id);
246
247 FullscreenRequest request{.fullscreen = true, .has_display_id = false};
249
250 int screen_width = GetSystemMetrics(SM_CXSCREEN);
251 int screen_height = GetSystemMetrics(SM_CYSCREEN);
252 ActualWindowSize actual_size =
254 EXPECT_EQ(actual_size.width, screen_width);
255 EXPECT_EQ(actual_size.height, screen_height);
256 EXPECT_TRUE(
258}
259
260TEST_F(WindowManagerTest, CanUnfullscreenWindow) {
261 IsolateScope isolate_scope(isolate());
262
263 const int64_t view_id =
265 engine_id(), regular_creation_request());
266 const HWND window_handle =
268 view_id);
269
270 FullscreenRequest request{.fullscreen = true, .has_display_id = false};
272
273 request.fullscreen = false;
275
276 ActualWindowSize actual_size =
278 EXPECT_EQ(actual_size.width, 800);
279 EXPECT_EQ(actual_size.height, 600);
280 EXPECT_FALSE(
282}
283
284TEST_F(WindowManagerTest, CanSetWindowSizeWhileFullscreen) {
285 IsolateScope isolate_scope(isolate());
286
287 const int64_t view_id =
289 engine_id(), regular_creation_request());
290 const HWND window_handle =
292 view_id);
293
294 FullscreenRequest request{.fullscreen = true, .has_display_id = false};
296
297 WindowSizeRequest requestedSize{
298
300 .preferred_view_width = 500,
301 .preferred_view_height = 500,
302 };
304 &requestedSize);
305
306 request.fullscreen = false;
308
309 ActualWindowSize actual_size =
311 EXPECT_EQ(actual_size.width, 500);
312 EXPECT_EQ(actual_size.height, 500);
313}
314
315TEST_F(WindowManagerTest, CanSetWindowConstraintsWhileFullscreen) {
316 IsolateScope isolate_scope(isolate());
317
318 const int64_t view_id =
320 engine_id(), regular_creation_request());
321 const HWND window_handle =
323 view_id);
324
325 FullscreenRequest request{.fullscreen = true, .has_display_id = false};
327
328 WindowConstraints constraints{.has_view_constraints = true,
329 .view_min_width = 0,
330 .view_min_height = 0,
331 .view_max_width = 500,
332 .view_max_height = 500};
334 &constraints);
335
336 request.fullscreen = false;
338
339 ActualWindowSize actual_size =
341 EXPECT_EQ(actual_size.width, 500);
342 EXPECT_EQ(actual_size.height, 500);
343}
344
345TEST_F(WindowManagerTest, CreateModelessDialogWindow) {
346 IsolateScope isolate_scope(isolate());
347 DialogWindowCreationRequest creation_request{
349 .preferred_view_width = 800,
350 .preferred_view_height = 600},
351 .preferred_constraints = {.has_view_constraints = false},
352 .title = L"Hello World",
353 .parent_or_null = nullptr};
354 const int64_t view_id =
356 engine_id(), &creation_request);
357 EXPECT_EQ(view_id, 0);
358}
359
360TEST_F(WindowManagerTest, CreateModalDialogWindow) {
361 IsolateScope isolate_scope(isolate());
362
363 const int64_t parent_view_id =
365 engine_id(), regular_creation_request());
366 const HWND parent_window_handle =
368 engine_id(), parent_view_id);
369
370 DialogWindowCreationRequest creation_request{
372 {
374 .preferred_view_width = 800,
375 .preferred_view_height = 600,
376 },
377 .preferred_constraints = {.has_view_constraints = false},
378 .title = L"Hello World",
379 .parent_or_null = parent_window_handle};
380
381 const int64_t view_id =
383 engine_id(), &creation_request);
384 EXPECT_EQ(view_id, 1);
385
386 const HWND window_handle =
388 view_id);
389 HostWindow* host_window = HostWindow::GetThisFromHandle(window_handle);
390 EXPECT_EQ(host_window->GetOwnerWindow()->GetWindowHandle(),
391 parent_window_handle);
392}
393
394TEST_F(WindowManagerTest, DeactivatedRegularWindowDoesNotReactivateItself) {
395 IsolateScope isolate_scope(isolate());
396
397 // Two independent top-level regular windows (regular windows have no Win32
398 // owner). |window_handle| is deactivated while |active_window_handle| holds
399 // activation.
400 const int64_t window_view_id =
402 engine_id(), regular_creation_request());
403 const HWND window_handle =
405 engine_id(), window_view_id);
406
407 const int64_t active_view_id =
409 engine_id(), regular_creation_request());
410 const HWND active_window_handle =
412 engine_id(), active_view_id);
413
414 SetActiveWindow(active_window_handle);
415 ASSERT_EQ(GetActiveWindow(), active_window_handle);
416
417 // A window receiving WM_ACTIVATE with WA_INACTIVE must not focus its own
418 // content: SetFocus activates the parent of the focused window (the window
419 // itself), which would reactivate the deactivated window, pull it back to the
420 // top of the z-order, and take activation away from the active window.
421 SendMessage(window_handle, WM_ACTIVATE, MAKEWPARAM(WA_INACTIVE, 0), 0);
422
423 EXPECT_EQ(GetActiveWindow(), active_window_handle);
424}
425
426TEST_F(WindowManagerTest, DialogCanNeverBeFullscreen) {
427 IsolateScope isolate_scope(isolate());
428
429 DialogWindowCreationRequest creation_request{
431 .preferred_view_width = 800,
432 .preferred_view_height = 600},
433 .preferred_constraints = {.has_view_constraints = false},
434 .title = L"Hello World",
435 .parent_or_null = nullptr};
436
437 const int64_t view_id =
439 engine_id(), &creation_request);
440 const HWND window_handle =
442 view_id);
443
444 FullscreenRequest request{.fullscreen = true, .has_display_id = false};
446 EXPECT_FALSE(
448}
449
450TEST_F(WindowManagerTest, CreateTooltipWindow) {
451 IsolateScope isolate_scope(isolate());
452
453 const int64_t parent_view_id =
455 engine_id(), regular_creation_request());
456 const HWND parent_window_handle =
458 engine_id(), parent_view_id);
459
460 auto position_callback = [](const WindowSize& child_size,
461 const WindowRect& parent_rect,
462 const WindowRect& output_rect) -> WindowRect* {
463 WindowRect* rect = static_cast<WindowRect*>(malloc(sizeof(WindowRect)));
464 rect->left = parent_rect.left + 10;
465 rect->top = parent_rect.top + 10;
466 rect->width = child_size.width;
467 rect->height = child_size.height;
468 return rect;
469 };
470
471 TooltipWindowCreationRequest creation_request{
473 .view_min_width = 100,
474 .view_min_height = 50,
475 .view_max_width = 300,
476 .view_max_height = 200},
477 .parent = parent_window_handle,
478 .get_position_callback = position_callback};
479
480 const int64_t tooltip_view_id =
482 engine_id(), &creation_request);
483
484 EXPECT_NE(tooltip_view_id, -1);
485 HWND tooltip_window_handle =
487 engine_id(), tooltip_view_id);
488 EXPECT_NE(tooltip_window_handle, nullptr);
489}
490
491TEST_F(WindowManagerTest, TooltipWindowHasNoActivateStyle) {
492 IsolateScope isolate_scope(isolate());
493
494 const int64_t parent_view_id =
496 engine_id(), regular_creation_request());
497 const HWND parent_window_handle =
499 engine_id(), parent_view_id);
500
501 auto position_callback = [](const WindowSize& child_size,
502 const WindowRect& parent_rect,
503 const WindowRect& output_rect) -> WindowRect* {
504 WindowRect* rect = static_cast<WindowRect*>(malloc(sizeof(WindowRect)));
505 rect->left = parent_rect.left + 10;
506 rect->top = parent_rect.top + 10;
507 rect->width = child_size.width;
508 rect->height = child_size.height;
509 return rect;
510 };
511
512 TooltipWindowCreationRequest creation_request{
514 .view_min_width = 100,
515 .view_min_height = 50,
516 .view_max_width = 300,
517 .view_max_height = 200},
518 .parent = parent_window_handle,
519 .get_position_callback = position_callback};
520
521 const int64_t tooltip_view_id =
523 engine_id(), &creation_request);
524
525 HWND tooltip_window_handle =
527 engine_id(), tooltip_view_id);
528
529 DWORD ex_style = GetWindowLong(tooltip_window_handle, GWL_EXSTYLE);
530 EXPECT_TRUE(ex_style & WS_EX_NOACTIVATE);
531}
532
533TEST_F(WindowManagerTest, TooltipWindowDoesNotStealFocus) {
534 IsolateScope isolate_scope(isolate());
535
536 const int64_t parent_view_id =
538 engine_id(), regular_creation_request());
539 const HWND parent_window_handle =
541 engine_id(), parent_view_id);
542
543 // Give focus to the parent window
544 SetFocus(parent_window_handle);
545 HWND focused_before = GetFocus();
546
547 auto position_callback = [](const WindowSize& child_size,
548 const WindowRect& parent_rect,
549 const WindowRect& output_rect) -> WindowRect* {
550 WindowRect* rect = static_cast<WindowRect*>(malloc(sizeof(WindowRect)));
551 rect->left = parent_rect.left + 10;
552 rect->top = parent_rect.top + 10;
553 rect->width = child_size.width;
554 rect->height = child_size.height;
555 return rect;
556 };
557
558 TooltipWindowCreationRequest creation_request{
560 .view_min_width = 100,
561 .view_min_height = 50,
562 .view_max_width = 300,
563 .view_max_height = 200},
564 .parent = parent_window_handle,
565 .get_position_callback = position_callback};
566
567 const int64_t tooltip_view_id =
569 engine_id(), &creation_request);
570
571 HWND tooltip_window_handle =
573 engine_id(), tooltip_view_id);
574
575 // Verify focus remains with the parent window
576 HWND focused_after = GetFocus();
577 EXPECT_EQ(focused_before, focused_after);
578 EXPECT_NE(focused_after, tooltip_window_handle);
579}
580
581TEST_F(WindowManagerTest, TooltipWindowReturnsNoActivateOnMouseClick) {
582 IsolateScope isolate_scope(isolate());
583
584 const int64_t parent_view_id =
586 engine_id(), regular_creation_request());
587 const HWND parent_window_handle =
589 engine_id(), parent_view_id);
590
591 auto position_callback = [](const WindowSize& child_size,
592 const WindowRect& parent_rect,
593 const WindowRect& output_rect) -> WindowRect* {
594 WindowRect* rect = static_cast<WindowRect*>(malloc(sizeof(WindowRect)));
595 rect->left = parent_rect.left + 10;
596 rect->top = parent_rect.top + 10;
597 rect->width = child_size.width;
598 rect->height = child_size.height;
599 return rect;
600 };
601
602 TooltipWindowCreationRequest creation_request{
604 .view_min_width = 100,
605 .view_min_height = 50,
606 .view_max_width = 300,
607 .view_max_height = 200},
608 .parent = parent_window_handle,
609 .get_position_callback = position_callback};
610
611 const int64_t tooltip_view_id =
613 engine_id(), &creation_request);
614
615 HWND tooltip_window_handle =
617 engine_id(), tooltip_view_id);
618
619 // Send WM_MOUSEACTIVATE message to the tooltip window
620 LRESULT result = SendMessage(tooltip_window_handle, WM_MOUSEACTIVATE,
621 reinterpret_cast<WPARAM>(parent_window_handle),
622 MAKELPARAM(HTCLIENT, WM_LBUTTONDOWN));
623
624 // Verify the tooltip returns MA_NOACTIVATE
625 EXPECT_EQ(result, MA_NOACTIVATE);
626}
627
628// TODO(team-windows): Fix flakes. See:
629// https://github.com/flutter/flutter/issues/177172
630TEST_F(WindowManagerTest,
631 DISABLED_TooltipWindowUpdatesPositionOnViewSizeChange) {
632 IsolateScope isolate_scope(isolate());
633
634 const int64_t parent_view_id =
636 engine_id(), regular_creation_request());
637 const HWND parent_window_handle =
639 engine_id(), parent_view_id);
640
641 // Track the child size passed to the callback
642 static int callback_count = 0;
643 static int last_width = 0;
644 static int last_height = 0;
645
646 auto position_callback = [](const WindowSize& child_size,
647 const WindowRect& parent_rect,
648 const WindowRect& output_rect) -> WindowRect* {
649 callback_count++;
650 last_width = child_size.width;
651 last_height = child_size.height;
652
653 // Use malloc since the caller will use free()
654 WindowRect* rect = static_cast<WindowRect*>(malloc(sizeof(WindowRect)));
655 rect->left = parent_rect.left + callback_count * 5;
656 rect->top = parent_rect.top + callback_count * 5;
657 rect->width = child_size.width;
658 rect->height = child_size.height;
659 return rect;
660 };
661
662 TooltipWindowCreationRequest creation_request{
664 .view_min_width = 100,
665 .view_min_height = 50,
666 .view_max_width = 300,
667 .view_max_height = 200},
668 .parent = parent_window_handle,
669 .get_position_callback = position_callback};
670
671 // Reset callback tracking
672 callback_count = 0;
673 last_width = 0;
674 last_height = 0;
675
676 const int64_t tooltip_view_id =
678 engine_id(), &creation_request);
679
680 HWND tooltip_window_handle =
682 engine_id(), tooltip_view_id);
683
684 // Get the view associated with the tooltip window
686 engine()->GetViewFromTopLevelWindow(tooltip_window_handle);
687 ASSERT_NE(view, nullptr);
688
689 // Get initial position
690 RECT initial_rect;
691 GetWindowRect(tooltip_window_handle, &initial_rect);
692 int initial_callback_count = callback_count;
693
694 // Simulate a frame being generated with new dimensions
695 // This should trigger DidUpdateViewSize which calls UpdatePosition
696 view->OnFrameGenerated(150, 100);
697
698 // Process any pending tasks to ensure the callback is executed
699 engine()->task_runner()->ProcessTasks();
700
701 // Verify the callback was called again with the new dimensions
702 EXPECT_GT(callback_count, initial_callback_count);
703 EXPECT_EQ(last_width, 150);
704 EXPECT_EQ(last_height, 100);
705
706 // Get new position and verify it changed
707 RECT new_rect;
708 GetWindowRect(tooltip_window_handle, &new_rect);
709
710 // The position should have changed due to our callback logic
711 // (we offset by callback_count * 5)
712 EXPECT_NE(initial_rect.left, new_rect.left);
713 EXPECT_NE(initial_rect.top, new_rect.top);
714}
715
716TEST_F(WindowManagerTest, CreatePopupWindow) {
717 IsolateScope isolate_scope(isolate());
718
719 const int64_t parent_view_id =
721 engine_id(), regular_creation_request());
722 const HWND parent_window_handle =
724 engine_id(), parent_view_id);
725
726 auto position_callback = [](const WindowSize& child_size,
727 const WindowRect& parent_rect,
728 const WindowRect& output_rect) -> WindowRect* {
729 WindowRect* rect = static_cast<WindowRect*>(malloc(sizeof(WindowRect)));
730 rect->left = parent_rect.left + 10;
731 rect->top = parent_rect.top + 10;
732 rect->width = child_size.width;
733 rect->height = child_size.height;
734 return rect;
735 };
736
737 PopupWindowCreationRequest creation_request{
739 .view_min_width = 100,
740 .view_min_height = 50,
741 .view_max_width = 300,
742 .view_max_height = 200},
743 .parent = parent_window_handle,
744 .get_position_callback = position_callback};
745
746 const int64_t popup_view_id =
748 &creation_request);
749
750 EXPECT_NE(popup_view_id, -1);
751 HWND popup_window_handle =
753 engine_id(), popup_view_id);
754 EXPECT_NE(popup_window_handle, nullptr);
755}
756
757TEST_F(WindowManagerTest, PopupWindowHasNoActivateStyle) {
758 IsolateScope isolate_scope(isolate());
759
760 const int64_t parent_view_id =
762 engine_id(), regular_creation_request());
763 const HWND parent_window_handle =
765 engine_id(), parent_view_id);
766
767 auto position_callback = [](const WindowSize& child_size,
768 const WindowRect& parent_rect,
769 const WindowRect& output_rect) -> WindowRect* {
770 WindowRect* rect = static_cast<WindowRect*>(malloc(sizeof(WindowRect)));
771 rect->left = parent_rect.left + 10;
772 rect->top = parent_rect.top + 10;
773 rect->width = child_size.width;
774 rect->height = child_size.height;
775 return rect;
776 };
777
778 PopupWindowCreationRequest creation_request{
780 .view_min_width = 100,
781 .view_min_height = 50,
782 .view_max_width = 300,
783 .view_max_height = 200},
784 .parent = parent_window_handle,
785 .get_position_callback = position_callback};
786
787 const int64_t popup_view_id =
789 &creation_request);
790
791 HWND popup_window_handle =
793 engine_id(), popup_view_id);
794
795 DWORD ex_style = GetWindowLong(popup_window_handle, GWL_EXSTYLE);
796 EXPECT_TRUE(ex_style & WS_EX_NOACTIVATE);
797}
798
799TEST_F(WindowManagerTest, PopupWindowDoesNotStealFocus) {
800 IsolateScope isolate_scope(isolate());
801
802 const int64_t parent_view_id =
804 engine_id(), regular_creation_request());
805 const HWND parent_window_handle =
807 engine_id(), parent_view_id);
808
809 // Give focus to the parent window
810 SetFocus(parent_window_handle);
811 HWND focused_before = GetFocus();
812
813 auto position_callback = [](const WindowSize& child_size,
814 const WindowRect& parent_rect,
815 const WindowRect& output_rect) -> WindowRect* {
816 WindowRect* rect = static_cast<WindowRect*>(malloc(sizeof(WindowRect)));
817 rect->left = parent_rect.left + 10;
818 rect->top = parent_rect.top + 10;
819 rect->width = child_size.width;
820 rect->height = child_size.height;
821 return rect;
822 };
823
824 PopupWindowCreationRequest creation_request{
826 .view_min_width = 100,
827 .view_min_height = 50,
828 .view_max_width = 300,
829 .view_max_height = 200},
830 .parent = parent_window_handle,
831 .get_position_callback = position_callback};
832
833 const int64_t popup_view_id =
835 &creation_request);
836
837 HWND popup_window_handle =
839 engine_id(), popup_view_id);
840
841 // Verify focus remains with the parent window
842 HWND focused_after = GetFocus();
843 EXPECT_EQ(focused_before, focused_after);
844 EXPECT_NE(focused_after, popup_window_handle);
845}
846
847// TODO(team-windows): Fix flakes. See:
848// https://github.com/flutter/flutter/issues/177172
849TEST_F(WindowManagerTest, DISABLED_PopupWindowUpdatesPositionOnViewSizeChange) {
850 IsolateScope isolate_scope(isolate());
851
852 const int64_t parent_view_id =
854 engine_id(), regular_creation_request());
855 const HWND parent_window_handle =
857 engine_id(), parent_view_id);
858
859 // Track the child size passed to the callback
860 static int callback_count = 0;
861 static int last_width = 0;
862 static int last_height = 0;
863
864 auto position_callback = [](const WindowSize& child_size,
865 const WindowRect& parent_rect,
866 const WindowRect& output_rect) -> WindowRect* {
867 callback_count++;
868 last_width = child_size.width;
869 last_height = child_size.height;
870
871 // Use malloc since the caller will use free()
872 WindowRect* rect = static_cast<WindowRect*>(malloc(sizeof(WindowRect)));
873 rect->left = parent_rect.left + callback_count * 5;
874 rect->top = parent_rect.top + callback_count * 5;
875 rect->width = child_size.width;
876 rect->height = child_size.height;
877 return rect;
878 };
879
880 PopupWindowCreationRequest creation_request{
882 .view_min_width = 100,
883 .view_min_height = 50,
884 .view_max_width = 300,
885 .view_max_height = 200},
886 .parent = parent_window_handle,
887 .get_position_callback = position_callback};
888
889 // Reset callback tracking
890 callback_count = 0;
891 last_width = 0;
892 last_height = 0;
893
894 const int64_t popup_view_id =
896 &creation_request);
897
898 HWND popup_window_handle =
900 engine_id(), popup_view_id);
901
902 // Get the view associated with the popup window
904 engine()->GetViewFromTopLevelWindow(popup_window_handle);
905 ASSERT_NE(view, nullptr);
906
907 // Get initial position
908 RECT initial_rect;
909 GetWindowRect(popup_window_handle, &initial_rect);
910 int initial_callback_count = callback_count;
911
912 // Simulate a frame being generated with new dimensions
913 // This should trigger DidUpdateViewSize which calls UpdatePosition
914 view->OnFrameGenerated(150, 100);
915
916 // Process any pending tasks to ensure the callback is executed
917 engine()->task_runner()->ProcessTasks();
918
919 // Verify the callback was called again with the new dimensions
920 EXPECT_GT(callback_count, initial_callback_count);
921 EXPECT_EQ(last_width, 150);
922 EXPECT_EQ(last_height, 100);
923
924 // Get new position and verify it changed
925 RECT new_rect;
926 GetWindowRect(popup_window_handle, &new_rect);
927
928 // The position should have changed due to our callback logic
929 // (we offset by callback_count * 5)
930 EXPECT_NE(initial_rect.left, new_rect.left);
931 EXPECT_NE(initial_rect.top, new_rect.top);
932}
933
934TEST_F(WindowManagerTest, CreateRegularWindowSizedToContent) {
935 IsolateScope isolate_scope(isolate());
936
937 RegularWindowCreationRequest creation_request{
939 .preferred_constraints = {.has_view_constraints = false},
940 .title = L"Sized To Content",
941 .sized_to_content = true,
942 .resizable = false};
943
944 const int64_t view_id =
946 engine_id(), &creation_request);
947 EXPECT_GE(view_id, 0);
948}
949
950TEST_F(WindowManagerTest, RegularWindowSizedToContentResizesToContent) {
951 IsolateScope isolate_scope(isolate());
952
953 RegularWindowCreationRequest creation_request{
955 .preferred_constraints = {.has_view_constraints = false},
956 .title = L"Sized To Content",
957 .sized_to_content = true,
958 .resizable = false};
959
960 const int64_t view_id =
962 engine_id(), &creation_request);
963 const HWND window_handle =
965 view_id);
966
967 FlutterWindowsView* view = engine()->GetViewFromTopLevelWindow(window_handle);
968 ASSERT_NE(view, nullptr);
969
970 view->OnFrameGenerated(300, 200);
971 engine()->task_runner()->ProcessTasks();
972
975 EXPECT_EQ(size.width, 300);
976 EXPECT_EQ(size.height, 200);
977}
978
979TEST_F(WindowManagerTest,
980 RegularWindowSizedToContentNonResizableHasNoThickFrame) {
981 IsolateScope isolate_scope(isolate());
982
983 RegularWindowCreationRequest creation_request{
985 .preferred_constraints = {.has_view_constraints = false},
986 .title = L"Sized To Content Non-Resizable",
987 .sized_to_content = true,
988 .resizable = false};
989
990 const int64_t view_id =
992 engine_id(), &creation_request);
993 const HWND window_handle =
995 view_id);
996
997 const LONG style = GetWindowLong(window_handle, GWL_STYLE);
998 EXPECT_EQ(style & WS_THICKFRAME, 0L);
999 EXPECT_EQ(style & WS_MAXIMIZEBOX, 0L);
1000}
1001
1002TEST_F(WindowManagerTest, RegularWindowSizedToContentResizableHasThickFrame) {
1003 IsolateScope isolate_scope(isolate());
1004
1005 RegularWindowCreationRequest creation_request{
1007 .preferred_constraints = {.has_view_constraints = false},
1008 .title = L"Sized To Content Resizable",
1009 .sized_to_content = true,
1010 .resizable = true};
1011
1012 const int64_t view_id =
1014 engine_id(), &creation_request);
1015 const HWND window_handle =
1017 view_id);
1018
1019 const LONG style = GetWindowLong(window_handle, GWL_STYLE);
1020 EXPECT_NE(style & WS_THICKFRAME, 0L);
1021 EXPECT_NE(style & WS_MAXIMIZEBOX, 0L);
1022}
1023
1024TEST_F(WindowManagerTest,
1025 RegularWindowSizedToContentResizableStopsTrackingAfterFirstFrame) {
1026 IsolateScope isolate_scope(isolate());
1027
1028 RegularWindowCreationRequest creation_request{
1030 .preferred_constraints = {.has_view_constraints = false},
1031 .title = L"Sized To Content Resizable",
1032 .sized_to_content = true,
1033 .resizable = true};
1034
1035 const int64_t view_id =
1037 engine_id(), &creation_request);
1038 const HWND window_handle =
1040 view_id);
1041
1042 FlutterWindowsView* view = engine()->GetViewFromTopLevelWindow(window_handle);
1043 ASSERT_NE(view, nullptr);
1044
1045 view->OnFrameGenerated(300, 200);
1046 engine()->task_runner()->ProcessTasks();
1047
1048 EXPECT_FALSE(view->IsSizedToContent());
1049}
1050
1051TEST_F(WindowManagerTest, CreateModelessDialogSizedToContent) {
1052 IsolateScope isolate_scope(isolate());
1053
1054 DialogWindowCreationRequest creation_request{
1056 .preferred_constraints = {.has_view_constraints = false},
1057 .title = L"Modeless Dialog Sized To Content",
1058 .parent_or_null = nullptr,
1059 .sized_to_content = true,
1060 .resizable = false};
1061
1062 const int64_t view_id =
1064 engine_id(), &creation_request);
1065 EXPECT_GE(view_id, 0);
1066}
1067
1068TEST_F(WindowManagerTest, CreateModalDialogSizedToContent) {
1069 IsolateScope isolate_scope(isolate());
1070
1071 const int64_t parent_view_id =
1073 engine_id(), regular_creation_request());
1074 const HWND parent_window_handle =
1076 engine_id(), parent_view_id);
1077
1078 DialogWindowCreationRequest creation_request{
1080 .preferred_constraints = {.has_view_constraints = false},
1081 .title = L"Modal Dialog Sized To Content",
1082 .parent_or_null = parent_window_handle,
1083 .sized_to_content = true,
1084 .resizable = false};
1085
1086 const int64_t view_id =
1088 engine_id(), &creation_request);
1089 EXPECT_GE(view_id, 0);
1090}
1091
1092TEST_F(WindowManagerTest, DialogWindowSizedToContentResizesToContent) {
1093 IsolateScope isolate_scope(isolate());
1094
1095 DialogWindowCreationRequest creation_request{
1097 .preferred_constraints = {.has_view_constraints = false},
1098 .title = L"Dialog Sized To Content",
1099 .parent_or_null = nullptr,
1100 .sized_to_content = true,
1101 .resizable = false};
1102
1103 const int64_t view_id =
1105 engine_id(), &creation_request);
1106 const HWND window_handle =
1108 view_id);
1109
1110 FlutterWindowsView* view = engine()->GetViewFromTopLevelWindow(window_handle);
1111 ASSERT_NE(view, nullptr);
1112
1113 view->OnFrameGenerated(300, 200);
1114 engine()->task_runner()->ProcessTasks();
1115
1118 EXPECT_EQ(size.width, 300);
1119 EXPECT_EQ(size.height, 200);
1120}
1121
1122TEST_F(WindowManagerTest,
1123 DialogWindowSizedToContentNonResizableHasNoThickFrame) {
1124 IsolateScope isolate_scope(isolate());
1125
1126 DialogWindowCreationRequest creation_request{
1128 .preferred_constraints = {.has_view_constraints = false},
1129 .title = L"Dialog Sized To Content Non-Resizable",
1130 .parent_or_null = nullptr,
1131 .sized_to_content = true,
1132 .resizable = false};
1133
1134 const int64_t view_id =
1136 engine_id(), &creation_request);
1137 const HWND window_handle =
1139 view_id);
1140
1141 const LONG style = GetWindowLong(window_handle, GWL_STYLE);
1142 EXPECT_EQ(style & WS_THICKFRAME, 0L);
1143}
1144
1145} // namespace testing
1146} // namespace flutter
HWND GetWindowHandle() const
HostWindow * GetOwnerWindow() const
static HostWindow * GetThisFromHandle(HWND hwnd)
static Isolate Current()
FlutterEngine engine
Definition main.cc:84
VkSurfaceKHR surface
Definition main.cc:65
FlView * view
const char * message
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
G_BEGIN_DECLS FlutterViewId view_id
#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition macros.h:27
TEST_F(DisplayListTest, Defaults)
it will be possible to load the file into Perfetto s trace viewer use test Running tests that layout and measure text will not yield consistent results across various platforms Enabling this option will make font resolution default to the Ahem test font on all disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
std::shared_ptr< ContextGLES > context
void(* on_message)(WindowsMessage *)
#define CREATE_NATIVE_ENTRY(native_entry)
void InternalFlutterWindows_WindowManager_Initialize(int64_t engine_id, const flutter::WindowingInitRequest *request)
void InternalFlutterWindows_WindowManager_SetWindowConstraints(HWND hwnd, const flutter::WindowConstraints *constraints)
bool InternalFlutterWindows_WindowManager_GetFullscreen(HWND hwnd)
void InternalFlutterWindows_WindowManager_SetWindowSize(HWND hwnd, const flutter::WindowSizeRequest *size)
FlutterViewId InternalFlutterWindows_WindowManager_CreateRegularWindow(int64_t engine_id, const flutter::RegularWindowCreationRequest *request)
FLUTTER_EXPORT FlutterViewId InternalFlutterWindows_WindowManager_CreateDialogWindow(int64_t engine_id, const flutter::DialogWindowCreationRequest *request)
void InternalFlutterWindows_WindowManager_SetFullscreen(HWND hwnd, const flutter::FullscreenRequest *request)
flutter::ActualWindowSize InternalFlutterWindows_WindowManager_GetWindowContentSize(HWND hwnd)
FLUTTER_EXPORT FlutterViewId InternalFlutterWindows_WindowManager_CreateTooltipWindow(int64_t engine_id, const flutter::TooltipWindowCreationRequest *request)
FLUTTER_EXPORT FlutterViewId InternalFlutterWindows_WindowManager_CreatePopupWindow(int64_t engine_id, const flutter::PopupWindowCreationRequest *request)
HWND InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(int64_t engine_id, FlutterViewId view_id)
long LONG
LONG_PTR LRESULT
#define SendMessage
UINT_PTR WPARAM
unsigned long DWORD