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
628TEST_F(WindowManagerTest, TooltipWindowUpdatesPositionOnViewSizeChange) {
629 IsolateScope isolate_scope(isolate());
630
631 const int64_t parent_view_id =
633 engine_id(), regular_creation_request());
634 const HWND parent_window_handle =
636 engine_id(), parent_view_id);
637
638 // Track the child size passed to the callback
639 static int callback_count = 0;
640 static int last_width = 0;
641 static int last_height = 0;
642
643 auto position_callback = [](const WindowSize& child_size,
644 const WindowRect& parent_rect,
645 const WindowRect& output_rect) -> WindowRect* {
646 callback_count++;
647 last_width = child_size.width;
648 last_height = child_size.height;
649
650 // Use malloc since the caller will use free()
651 WindowRect* rect = static_cast<WindowRect*>(malloc(sizeof(WindowRect)));
652 rect->left = parent_rect.left + callback_count * 5;
653 rect->top = parent_rect.top + callback_count * 5;
654 rect->width = child_size.width;
655 rect->height = child_size.height;
656 return rect;
657 };
658
659 TooltipWindowCreationRequest creation_request{
661 .view_min_width = 100,
662 .view_min_height = 50,
663 .view_max_width = 300,
664 .view_max_height = 200},
665 .parent = parent_window_handle,
666 .get_position_callback = position_callback};
667
668 // Reset callback tracking
669 callback_count = 0;
670 last_width = 0;
671 last_height = 0;
672
673 const int64_t tooltip_view_id =
675 engine_id(), &creation_request);
676
677 HWND tooltip_window_handle =
679 engine_id(), tooltip_view_id);
680
681 // Get the view associated with the tooltip window
683 engine()->GetViewFromTopLevelWindow(tooltip_window_handle);
684 ASSERT_NE(view, nullptr);
685
686 // Get initial position
687 RECT initial_rect;
688 GetWindowRect(tooltip_window_handle, &initial_rect);
689 int initial_callback_count = callback_count;
690
691 // Simulate a frame being generated with new dimensions
692 // This should trigger DidUpdateViewSize which calls UpdatePosition
693 view->OnFrameGenerated(150, 100);
694
695 // Process any pending tasks to ensure the callback is executed
696 engine()->task_runner()->ProcessTasks();
697
698 // Verify the callback was called again with the new dimensions
699 EXPECT_GT(callback_count, initial_callback_count);
700 EXPECT_EQ(last_width, 150);
701 EXPECT_EQ(last_height, 100);
702
703 // Get new position and verify it changed
704 RECT new_rect;
705 GetWindowRect(tooltip_window_handle, &new_rect);
706
707 // The position should have changed due to our callback logic
708 // (we offset by callback_count * 5)
709 EXPECT_NE(initial_rect.left, new_rect.left);
710 EXPECT_NE(initial_rect.top, new_rect.top);
711}
712
713TEST_F(WindowManagerTest, CreatePopupWindow) {
714 IsolateScope isolate_scope(isolate());
715
716 const int64_t parent_view_id =
718 engine_id(), regular_creation_request());
719 const HWND parent_window_handle =
721 engine_id(), parent_view_id);
722
723 auto position_callback = [](const WindowSize& child_size,
724 const WindowRect& parent_rect,
725 const WindowRect& output_rect) -> WindowRect* {
726 WindowRect* rect = static_cast<WindowRect*>(malloc(sizeof(WindowRect)));
727 rect->left = parent_rect.left + 10;
728 rect->top = parent_rect.top + 10;
729 rect->width = child_size.width;
730 rect->height = child_size.height;
731 return rect;
732 };
733
734 PopupWindowCreationRequest creation_request{
736 .view_min_width = 100,
737 .view_min_height = 50,
738 .view_max_width = 300,
739 .view_max_height = 200},
740 .parent = parent_window_handle,
741 .get_position_callback = position_callback};
742
743 const int64_t popup_view_id =
745 &creation_request);
746
747 EXPECT_NE(popup_view_id, -1);
748 HWND popup_window_handle =
750 engine_id(), popup_view_id);
751 EXPECT_NE(popup_window_handle, nullptr);
752}
753
754TEST_F(WindowManagerTest, PopupWindowHasNoActivateStyle) {
755 IsolateScope isolate_scope(isolate());
756
757 const int64_t parent_view_id =
759 engine_id(), regular_creation_request());
760 const HWND parent_window_handle =
762 engine_id(), parent_view_id);
763
764 auto position_callback = [](const WindowSize& child_size,
765 const WindowRect& parent_rect,
766 const WindowRect& output_rect) -> WindowRect* {
767 WindowRect* rect = static_cast<WindowRect*>(malloc(sizeof(WindowRect)));
768 rect->left = parent_rect.left + 10;
769 rect->top = parent_rect.top + 10;
770 rect->width = child_size.width;
771 rect->height = child_size.height;
772 return rect;
773 };
774
775 PopupWindowCreationRequest creation_request{
777 .view_min_width = 100,
778 .view_min_height = 50,
779 .view_max_width = 300,
780 .view_max_height = 200},
781 .parent = parent_window_handle,
782 .get_position_callback = position_callback};
783
784 const int64_t popup_view_id =
786 &creation_request);
787
788 HWND popup_window_handle =
790 engine_id(), popup_view_id);
791
792 DWORD ex_style = GetWindowLong(popup_window_handle, GWL_EXSTYLE);
793 EXPECT_TRUE(ex_style & WS_EX_NOACTIVATE);
794}
795
796TEST_F(WindowManagerTest, PopupWindowDoesNotStealFocus) {
797 IsolateScope isolate_scope(isolate());
798
799 const int64_t parent_view_id =
801 engine_id(), regular_creation_request());
802 const HWND parent_window_handle =
804 engine_id(), parent_view_id);
805
806 // Give focus to the parent window
807 SetFocus(parent_window_handle);
808 HWND focused_before = GetFocus();
809
810 auto position_callback = [](const WindowSize& child_size,
811 const WindowRect& parent_rect,
812 const WindowRect& output_rect) -> WindowRect* {
813 WindowRect* rect = static_cast<WindowRect*>(malloc(sizeof(WindowRect)));
814 rect->left = parent_rect.left + 10;
815 rect->top = parent_rect.top + 10;
816 rect->width = child_size.width;
817 rect->height = child_size.height;
818 return rect;
819 };
820
821 PopupWindowCreationRequest creation_request{
823 .view_min_width = 100,
824 .view_min_height = 50,
825 .view_max_width = 300,
826 .view_max_height = 200},
827 .parent = parent_window_handle,
828 .get_position_callback = position_callback};
829
830 const int64_t popup_view_id =
832 &creation_request);
833
834 HWND popup_window_handle =
836 engine_id(), popup_view_id);
837
838 // Verify focus remains with the parent window
839 HWND focused_after = GetFocus();
840 EXPECT_EQ(focused_before, focused_after);
841 EXPECT_NE(focused_after, popup_window_handle);
842}
843
844TEST_F(WindowManagerTest, PopupWindowUpdatesPositionOnViewSizeChange) {
845 IsolateScope isolate_scope(isolate());
846
847 const int64_t parent_view_id =
849 engine_id(), regular_creation_request());
850 const HWND parent_window_handle =
852 engine_id(), parent_view_id);
853
854 // Track the child size passed to the callback
855 static int callback_count = 0;
856 static int last_width = 0;
857 static int last_height = 0;
858
859 auto position_callback = [](const WindowSize& child_size,
860 const WindowRect& parent_rect,
861 const WindowRect& output_rect) -> WindowRect* {
862 callback_count++;
863 last_width = child_size.width;
864 last_height = child_size.height;
865
866 // Use malloc since the caller will use free()
867 WindowRect* rect = static_cast<WindowRect*>(malloc(sizeof(WindowRect)));
868 rect->left = parent_rect.left + callback_count * 5;
869 rect->top = parent_rect.top + callback_count * 5;
870 rect->width = child_size.width;
871 rect->height = child_size.height;
872 return rect;
873 };
874
875 PopupWindowCreationRequest creation_request{
877 .view_min_width = 100,
878 .view_min_height = 50,
879 .view_max_width = 300,
880 .view_max_height = 200},
881 .parent = parent_window_handle,
882 .get_position_callback = position_callback};
883
884 // Reset callback tracking
885 callback_count = 0;
886 last_width = 0;
887 last_height = 0;
888
889 const int64_t popup_view_id =
891 &creation_request);
892
893 HWND popup_window_handle =
895 engine_id(), popup_view_id);
896
897 // Get the view associated with the popup window
899 engine()->GetViewFromTopLevelWindow(popup_window_handle);
900 ASSERT_NE(view, nullptr);
901
902 // Get initial position
903 RECT initial_rect;
904 GetWindowRect(popup_window_handle, &initial_rect);
905 int initial_callback_count = callback_count;
906
907 // Simulate a frame being generated with new dimensions
908 // This should trigger DidUpdateViewSize which calls UpdatePosition
909 view->OnFrameGenerated(150, 100);
910
911 // Process any pending tasks to ensure the callback is executed
912 engine()->task_runner()->ProcessTasks();
913
914 // Verify the callback was called again with the new dimensions
915 EXPECT_GT(callback_count, initial_callback_count);
916 EXPECT_EQ(last_width, 150);
917 EXPECT_EQ(last_height, 100);
918
919 // Get new position and verify it changed
920 RECT new_rect;
921 GetWindowRect(popup_window_handle, &new_rect);
922
923 // The position should have changed due to our callback logic
924 // (we offset by callback_count * 5)
925 EXPECT_NE(initial_rect.left, new_rect.left);
926 EXPECT_NE(initial_rect.top, new_rect.top);
927}
928
929TEST_F(WindowManagerTest, CreateRegularWindowSizedToContent) {
930 IsolateScope isolate_scope(isolate());
931
932 RegularWindowCreationRequest creation_request{
934 .preferred_constraints = {.has_view_constraints = false},
935 .title = L"Sized To Content",
936 .sized_to_content = true,
937 .resizable = false};
938
939 const int64_t view_id =
941 engine_id(), &creation_request);
942 EXPECT_GE(view_id, 0);
943}
944
945TEST_F(WindowManagerTest, RegularWindowSizedToContentResizesToContent) {
946 IsolateScope isolate_scope(isolate());
947
948 RegularWindowCreationRequest creation_request{
950 .preferred_constraints = {.has_view_constraints = false},
951 .title = L"Sized To Content",
952 .sized_to_content = true,
953 .resizable = false};
954
955 const int64_t view_id =
957 engine_id(), &creation_request);
958 const HWND window_handle =
960 view_id);
961
962 FlutterWindowsView* view = engine()->GetViewFromTopLevelWindow(window_handle);
963 ASSERT_NE(view, nullptr);
964
965 view->OnFrameGenerated(300, 200);
966 engine()->task_runner()->ProcessTasks();
967
970 EXPECT_EQ(size.width, 300);
971 EXPECT_EQ(size.height, 200);
972}
973
974TEST_F(WindowManagerTest,
975 RegularWindowSizedToContentNonResizableHasNoThickFrame) {
976 IsolateScope isolate_scope(isolate());
977
978 RegularWindowCreationRequest creation_request{
980 .preferred_constraints = {.has_view_constraints = false},
981 .title = L"Sized To Content Non-Resizable",
982 .sized_to_content = true,
983 .resizable = false};
984
985 const int64_t view_id =
987 engine_id(), &creation_request);
988 const HWND window_handle =
990 view_id);
991
992 const LONG style = GetWindowLong(window_handle, GWL_STYLE);
993 EXPECT_EQ(style & WS_THICKFRAME, 0L);
994 EXPECT_EQ(style & WS_MAXIMIZEBOX, 0L);
995}
996
997TEST_F(WindowManagerTest, RegularWindowSizedToContentResizableHasThickFrame) {
998 IsolateScope isolate_scope(isolate());
999
1000 RegularWindowCreationRequest creation_request{
1002 .preferred_constraints = {.has_view_constraints = false},
1003 .title = L"Sized To Content Resizable",
1004 .sized_to_content = true,
1005 .resizable = true};
1006
1007 const int64_t view_id =
1009 engine_id(), &creation_request);
1010 const HWND window_handle =
1012 view_id);
1013
1014 const LONG style = GetWindowLong(window_handle, GWL_STYLE);
1015 EXPECT_NE(style & WS_THICKFRAME, 0L);
1016 EXPECT_NE(style & WS_MAXIMIZEBOX, 0L);
1017}
1018
1019TEST_F(WindowManagerTest,
1020 RegularWindowSizedToContentResizableStopsTrackingAfterFirstFrame) {
1021 IsolateScope isolate_scope(isolate());
1022
1023 RegularWindowCreationRequest creation_request{
1025 .preferred_constraints = {.has_view_constraints = false},
1026 .title = L"Sized To Content Resizable",
1027 .sized_to_content = true,
1028 .resizable = true};
1029
1030 const int64_t view_id =
1032 engine_id(), &creation_request);
1033 const HWND window_handle =
1035 view_id);
1036
1037 FlutterWindowsView* view = engine()->GetViewFromTopLevelWindow(window_handle);
1038 ASSERT_NE(view, nullptr);
1039
1040 view->OnFrameGenerated(300, 200);
1041 engine()->task_runner()->ProcessTasks();
1042
1043 EXPECT_FALSE(view->IsSizedToContent());
1044}
1045
1046TEST_F(WindowManagerTest, CreateModelessDialogSizedToContent) {
1047 IsolateScope isolate_scope(isolate());
1048
1049 DialogWindowCreationRequest creation_request{
1051 .preferred_constraints = {.has_view_constraints = false},
1052 .title = L"Modeless Dialog Sized To Content",
1053 .parent_or_null = nullptr,
1054 .sized_to_content = true,
1055 .resizable = false};
1056
1057 const int64_t view_id =
1059 engine_id(), &creation_request);
1060 EXPECT_GE(view_id, 0);
1061}
1062
1063TEST_F(WindowManagerTest, CreateModalDialogSizedToContent) {
1064 IsolateScope isolate_scope(isolate());
1065
1066 const int64_t parent_view_id =
1068 engine_id(), regular_creation_request());
1069 const HWND parent_window_handle =
1071 engine_id(), parent_view_id);
1072
1073 DialogWindowCreationRequest creation_request{
1075 .preferred_constraints = {.has_view_constraints = false},
1076 .title = L"Modal Dialog Sized To Content",
1077 .parent_or_null = parent_window_handle,
1078 .sized_to_content = true,
1079 .resizable = false};
1080
1081 const int64_t view_id =
1083 engine_id(), &creation_request);
1084 EXPECT_GE(view_id, 0);
1085}
1086
1087TEST_F(WindowManagerTest, DialogWindowSizedToContentResizesToContent) {
1088 IsolateScope isolate_scope(isolate());
1089
1090 DialogWindowCreationRequest creation_request{
1092 .preferred_constraints = {.has_view_constraints = false},
1093 .title = L"Dialog Sized To Content",
1094 .parent_or_null = nullptr,
1095 .sized_to_content = true,
1096 .resizable = false};
1097
1098 const int64_t view_id =
1100 engine_id(), &creation_request);
1101 const HWND window_handle =
1103 view_id);
1104
1105 FlutterWindowsView* view = engine()->GetViewFromTopLevelWindow(window_handle);
1106 ASSERT_NE(view, nullptr);
1107
1108 view->OnFrameGenerated(300, 200);
1109 engine()->task_runner()->ProcessTasks();
1110
1113 EXPECT_EQ(size.width, 300);
1114 EXPECT_EQ(size.height, 200);
1115}
1116
1117TEST_F(WindowManagerTest,
1118 DialogWindowSizedToContentNonResizableHasNoThickFrame) {
1119 IsolateScope isolate_scope(isolate());
1120
1121 DialogWindowCreationRequest creation_request{
1123 .preferred_constraints = {.has_view_constraints = false},
1124 .title = L"Dialog Sized To Content Non-Resizable",
1125 .parent_or_null = nullptr,
1126 .sized_to_content = true,
1127 .resizable = false};
1128
1129 const int64_t view_id =
1131 engine_id(), &creation_request);
1132 const HWND window_handle =
1134 view_id);
1135
1136 const LONG style = GetWindowLong(window_handle, GWL_STYLE);
1137 EXPECT_EQ(style & WS_THICKFRAME, 0L);
1138}
1139
1140} // namespace testing
1141} // 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