Flutter Windows Embedder
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 
6 #include "flutter/shell/platform/windows/testing/flutter_windows_engine_builder.h"
7 #include "flutter/shell/platform/windows/testing/windows_test.h"
9 #include "gtest/gtest.h"
10 
11 namespace flutter {
12 namespace testing {
13 
14 namespace {
15 
16 class WindowManagerTest : public WindowsTest {
17  public:
18  WindowManagerTest() = default;
19  virtual ~WindowManagerTest() = default;
20 
21  protected:
22  void SetUp() override {
23  auto& context = GetContext();
24  FlutterWindowsEngineBuilder builder(context);
25  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
26 
27  engine_ = builder.Build();
28  ASSERT_TRUE(engine_);
29 
30  engine_->SetRootIsolateCreateCallback(context.GetRootIsolateCallback());
31  ASSERT_TRUE(engine_->Run("testWindowController"));
32 
33  bool signalled = false;
34  context.AddNativeFunction(
35  "Signal", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
36  isolate_ = flutter::Isolate::Current();
37  signalled = true;
38  }));
39  while (!signalled) {
40  engine_->task_runner()->ProcessTasks();
41  }
42  }
43 
44  void TearDown() override { engine_->Stop(); }
45 
46  int64_t engine_id() { return reinterpret_cast<int64_t>(engine_.get()); }
47  flutter::Isolate& isolate() { return *isolate_; }
48  RegularWindowCreationRequest* regular_creation_request() {
49  return &regular_creation_request_;
50  }
51  FlutterWindowsEngine* engine() { return engine_.get(); }
52 
53  private:
54  std::unique_ptr<FlutterWindowsEngine> engine_;
55  std::optional<flutter::Isolate> isolate_;
56  RegularWindowCreationRequest regular_creation_request_{
57  .preferred_size =
58  {
59  .has_preferred_view_size = true,
60  .preferred_view_width = 800,
61  .preferred_view_height = 600,
62  },
63  };
64 
65  FML_DISALLOW_COPY_AND_ASSIGN(WindowManagerTest);
66 };
67 
68 } // namespace
69 
70 TEST_F(WindowManagerTest, WindowingInitialize) {
71  IsolateScope isolate_scope(isolate());
72 
73  static bool received_message = false;
74  WindowingInitRequest init_request{
75  .on_message = [](WindowsMessage* message) { received_message = true; }};
76 
77  InternalFlutterWindows_WindowManager_Initialize(engine_id(), &init_request);
78  const int64_t view_id =
80  engine_id(), regular_creation_request());
82  engine_id(), view_id));
83 
84  EXPECT_TRUE(received_message);
85 }
86 
87 TEST_F(WindowManagerTest, CreateRegularWindow) {
88  IsolateScope isolate_scope(isolate());
89 
90  const int64_t view_id =
92  engine_id(), regular_creation_request());
93  EXPECT_EQ(view_id, 0);
94 }
95 
96 TEST_F(WindowManagerTest, GetWindowHandle) {
97  IsolateScope isolate_scope(isolate());
98 
99  const int64_t view_id =
101  engine_id(), regular_creation_request());
102  const HWND window_handle =
104  view_id);
105  EXPECT_NE(window_handle, nullptr);
106 }
107 
108 TEST_F(WindowManagerTest, GetWindowSize) {
109  IsolateScope isolate_scope(isolate());
110 
111  const int64_t view_id =
113  engine_id(), regular_creation_request());
114  const HWND window_handle =
116  view_id);
117 
118  ActualWindowSize size =
120 
121  EXPECT_EQ(size.width,
122  regular_creation_request()->preferred_size.preferred_view_width);
123  EXPECT_EQ(size.height,
124  regular_creation_request()->preferred_size.preferred_view_height);
125 }
126 
127 TEST_F(WindowManagerTest, SetWindowSize) {
128  IsolateScope isolate_scope(isolate());
129 
130  const int64_t view_id =
132  engine_id(), regular_creation_request());
133  const HWND window_handle =
135  view_id);
136 
137  WindowSizeRequest requestedSize{
138 
139  .has_preferred_view_size = true,
140  .preferred_view_width = 640,
141  .preferred_view_height = 480,
142  };
144  &requestedSize);
145 
146  ActualWindowSize actual_size =
148  EXPECT_EQ(actual_size.width, 640);
149  EXPECT_EQ(actual_size.height, 480);
150 }
151 
152 TEST_F(WindowManagerTest, CanConstrainByMinimiumSize) {
153  IsolateScope isolate_scope(isolate());
154 
155  const int64_t view_id =
157  engine_id(), regular_creation_request());
158  const HWND window_handle =
160  view_id);
161  WindowConstraints constraints{.has_view_constraints = true,
162  .view_min_width = 900,
163  .view_min_height = 700,
164  .view_max_width = 10000,
165  .view_max_height = 10000};
167  &constraints);
168 
169  ActualWindowSize actual_size =
171  EXPECT_EQ(actual_size.width, 900);
172  EXPECT_EQ(actual_size.height, 700);
173 }
174 
175 TEST_F(WindowManagerTest, CanConstrainByMaximumSize) {
176  IsolateScope isolate_scope(isolate());
177 
178  const int64_t view_id =
180  engine_id(), regular_creation_request());
181  const HWND window_handle =
183  view_id);
184  WindowConstraints constraints{.has_view_constraints = true,
185  .view_min_width = 0,
186  .view_min_height = 0,
187  .view_max_width = 500,
188  .view_max_height = 500};
190  &constraints);
191 
192  ActualWindowSize actual_size =
194  EXPECT_EQ(actual_size.width, 500);
195  EXPECT_EQ(actual_size.height, 500);
196 }
197 
198 TEST_F(WindowManagerTest, CanFullscreenWindow) {
199  IsolateScope isolate_scope(isolate());
200 
201  const int64_t view_id =
203  engine_id(), regular_creation_request());
204  const HWND window_handle =
206  view_id);
207 
208  FullscreenRequest request{.fullscreen = true, .has_display_id = false};
210 
211  int screen_width = GetSystemMetrics(SM_CXSCREEN);
212  int screen_height = GetSystemMetrics(SM_CYSCREEN);
213  ActualWindowSize actual_size =
215  EXPECT_EQ(actual_size.width, screen_width);
216  EXPECT_EQ(actual_size.height, screen_height);
217  EXPECT_TRUE(
219 }
220 
221 TEST_F(WindowManagerTest, CanUnfullscreenWindow) {
222  IsolateScope isolate_scope(isolate());
223 
224  const int64_t view_id =
226  engine_id(), regular_creation_request());
227  const HWND window_handle =
229  view_id);
230 
231  FullscreenRequest request{.fullscreen = true, .has_display_id = false};
233 
234  request.fullscreen = false;
236 
237  ActualWindowSize actual_size =
239  EXPECT_EQ(actual_size.width, 800);
240  EXPECT_EQ(actual_size.height, 600);
241  EXPECT_FALSE(
243 }
244 
245 TEST_F(WindowManagerTest, CanSetWindowSizeWhileFullscreen) {
246  IsolateScope isolate_scope(isolate());
247 
248  const int64_t view_id =
250  engine_id(), regular_creation_request());
251  const HWND window_handle =
253  view_id);
254 
255  FullscreenRequest request{.fullscreen = true, .has_display_id = false};
257 
258  WindowSizeRequest requestedSize{
259 
260  .has_preferred_view_size = true,
261  .preferred_view_width = 500,
262  .preferred_view_height = 500,
263  };
265  &requestedSize);
266 
267  request.fullscreen = false;
269 
270  ActualWindowSize actual_size =
272  EXPECT_EQ(actual_size.width, 500);
273  EXPECT_EQ(actual_size.height, 500);
274 }
275 
276 TEST_F(WindowManagerTest, CanSetWindowConstraintsWhileFullscreen) {
277  IsolateScope isolate_scope(isolate());
278 
279  const int64_t view_id =
281  engine_id(), regular_creation_request());
282  const HWND window_handle =
284  view_id);
285 
286  FullscreenRequest request{.fullscreen = true, .has_display_id = false};
288 
289  WindowConstraints constraints{.has_view_constraints = true,
290  .view_min_width = 0,
291  .view_min_height = 0,
292  .view_max_width = 500,
293  .view_max_height = 500};
295  &constraints);
296 
297  request.fullscreen = false;
299 
300  ActualWindowSize actual_size =
302  EXPECT_EQ(actual_size.width, 500);
303  EXPECT_EQ(actual_size.height, 500);
304 }
305 
306 TEST_F(WindowManagerTest, CreateModelessDialogWindow) {
307  IsolateScope isolate_scope(isolate());
308  DialogWindowCreationRequest creation_request{
310  .preferred_view_width = 800,
311  .preferred_view_height = 600},
312  .preferred_constraints = {.has_view_constraints = false},
313  .title = L"Hello World",
314  .parent_or_null = nullptr};
315  const int64_t view_id =
317  engine_id(), &creation_request);
318  EXPECT_EQ(view_id, 0);
319 }
320 
321 TEST_F(WindowManagerTest, CreateModalDialogWindow) {
322  IsolateScope isolate_scope(isolate());
323 
324  const int64_t parent_view_id =
326  engine_id(), regular_creation_request());
327  const HWND parent_window_handle =
329  engine_id(), parent_view_id);
330 
331  DialogWindowCreationRequest creation_request{
332  .preferred_size =
333  {
334  .has_preferred_view_size = true,
335  .preferred_view_width = 800,
336  .preferred_view_height = 600,
337  },
338  .preferred_constraints = {.has_view_constraints = false},
339  .title = L"Hello World",
340  .parent_or_null = parent_window_handle};
341 
342  const int64_t view_id =
344  engine_id(), &creation_request);
345  EXPECT_EQ(view_id, 1);
346 
347  const HWND window_handle =
349  view_id);
350  HostWindow* host_window = HostWindow::GetThisFromHandle(window_handle);
351  EXPECT_EQ(host_window->GetOwnerWindow()->GetWindowHandle(),
352  parent_window_handle);
353 }
354 
355 TEST_F(WindowManagerTest, DialogCanNeverBeFullscreen) {
356  IsolateScope isolate_scope(isolate());
357 
358  DialogWindowCreationRequest creation_request{
360  .preferred_view_width = 800,
361  .preferred_view_height = 600},
362  .preferred_constraints = {.has_view_constraints = false},
363  .title = L"Hello World",
364  .parent_or_null = nullptr};
365 
366  const int64_t view_id =
368  engine_id(), &creation_request);
369  const HWND window_handle =
371  view_id);
372 
373  FullscreenRequest request{.fullscreen = true, .has_display_id = false};
375  EXPECT_FALSE(
377 }
378 
379 TEST_F(WindowManagerTest, CreateTooltipWindow) {
380  IsolateScope isolate_scope(isolate());
381 
382  const int64_t parent_view_id =
384  engine_id(), regular_creation_request());
385  const HWND parent_window_handle =
387  engine_id(), parent_view_id);
388 
389  auto position_callback = [](const WindowSize& child_size,
390  const WindowRect& parent_rect,
391  const WindowRect& output_rect) -> WindowRect* {
392  WindowRect* rect = static_cast<WindowRect*>(malloc(sizeof(WindowRect)));
393  rect->left = parent_rect.left + 10;
394  rect->top = parent_rect.top + 10;
395  rect->width = child_size.width;
396  rect->height = child_size.height;
397  return rect;
398  };
399 
400  TooltipWindowCreationRequest creation_request{
402  .view_min_width = 100,
403  .view_min_height = 50,
404  .view_max_width = 300,
405  .view_max_height = 200},
406  .parent = parent_window_handle,
407  .get_position_callback = position_callback};
408 
409  const int64_t tooltip_view_id =
411  engine_id(), &creation_request);
412 
413  EXPECT_NE(tooltip_view_id, -1);
414  HWND tooltip_window_handle =
416  engine_id(), tooltip_view_id);
417  EXPECT_NE(tooltip_window_handle, nullptr);
418 }
419 
420 TEST_F(WindowManagerTest, TooltipWindowHasNoActivateStyle) {
421  IsolateScope isolate_scope(isolate());
422 
423  const int64_t parent_view_id =
425  engine_id(), regular_creation_request());
426  const HWND parent_window_handle =
428  engine_id(), parent_view_id);
429 
430  auto position_callback = [](const WindowSize& child_size,
431  const WindowRect& parent_rect,
432  const WindowRect& output_rect) -> WindowRect* {
433  WindowRect* rect = static_cast<WindowRect*>(malloc(sizeof(WindowRect)));
434  rect->left = parent_rect.left + 10;
435  rect->top = parent_rect.top + 10;
436  rect->width = child_size.width;
437  rect->height = child_size.height;
438  return rect;
439  };
440 
441  TooltipWindowCreationRequest creation_request{
443  .view_min_width = 100,
444  .view_min_height = 50,
445  .view_max_width = 300,
446  .view_max_height = 200},
447  .parent = parent_window_handle,
448  .get_position_callback = position_callback};
449 
450  const int64_t tooltip_view_id =
452  engine_id(), &creation_request);
453 
454  HWND tooltip_window_handle =
456  engine_id(), tooltip_view_id);
457 
458  DWORD ex_style = GetWindowLong(tooltip_window_handle, GWL_EXSTYLE);
459  EXPECT_TRUE(ex_style & WS_EX_NOACTIVATE);
460 }
461 
462 TEST_F(WindowManagerTest, TooltipWindowDoesNotStealFocus) {
463  IsolateScope isolate_scope(isolate());
464 
465  const int64_t parent_view_id =
467  engine_id(), regular_creation_request());
468  const HWND parent_window_handle =
470  engine_id(), parent_view_id);
471 
472  // Give focus to the parent window
473  SetFocus(parent_window_handle);
474  HWND focused_before = GetFocus();
475 
476  auto position_callback = [](const WindowSize& child_size,
477  const WindowRect& parent_rect,
478  const WindowRect& output_rect) -> WindowRect* {
479  WindowRect* rect = static_cast<WindowRect*>(malloc(sizeof(WindowRect)));
480  rect->left = parent_rect.left + 10;
481  rect->top = parent_rect.top + 10;
482  rect->width = child_size.width;
483  rect->height = child_size.height;
484  return rect;
485  };
486 
487  TooltipWindowCreationRequest creation_request{
489  .view_min_width = 100,
490  .view_min_height = 50,
491  .view_max_width = 300,
492  .view_max_height = 200},
493  .parent = parent_window_handle,
494  .get_position_callback = position_callback};
495 
496  const int64_t tooltip_view_id =
498  engine_id(), &creation_request);
499 
500  HWND tooltip_window_handle =
502  engine_id(), tooltip_view_id);
503 
504  // Verify focus remains with the parent window
505  HWND focused_after = GetFocus();
506  EXPECT_EQ(focused_before, focused_after);
507  EXPECT_NE(focused_after, tooltip_window_handle);
508 }
509 
510 TEST_F(WindowManagerTest, TooltipWindowReturnsNoActivateOnMouseClick) {
511  IsolateScope isolate_scope(isolate());
512 
513  const int64_t parent_view_id =
515  engine_id(), regular_creation_request());
516  const HWND parent_window_handle =
518  engine_id(), parent_view_id);
519 
520  auto position_callback = [](const WindowSize& child_size,
521  const WindowRect& parent_rect,
522  const WindowRect& output_rect) -> WindowRect* {
523  WindowRect* rect = static_cast<WindowRect*>(malloc(sizeof(WindowRect)));
524  rect->left = parent_rect.left + 10;
525  rect->top = parent_rect.top + 10;
526  rect->width = child_size.width;
527  rect->height = child_size.height;
528  return rect;
529  };
530 
531  TooltipWindowCreationRequest creation_request{
533  .view_min_width = 100,
534  .view_min_height = 50,
535  .view_max_width = 300,
536  .view_max_height = 200},
537  .parent = parent_window_handle,
538  .get_position_callback = position_callback};
539 
540  const int64_t tooltip_view_id =
542  engine_id(), &creation_request);
543 
544  HWND tooltip_window_handle =
546  engine_id(), tooltip_view_id);
547 
548  // Send WM_MOUSEACTIVATE message to the tooltip window
549  LRESULT result = SendMessage(tooltip_window_handle, WM_MOUSEACTIVATE,
550  reinterpret_cast<WPARAM>(parent_window_handle),
551  MAKELPARAM(HTCLIENT, WM_LBUTTONDOWN));
552 
553  // Verify the tooltip returns MA_NOACTIVATE
554  EXPECT_EQ(result, MA_NOACTIVATE);
555 }
556 
557 TEST_F(WindowManagerTest, TooltipWindowUpdatesPositionOnViewSizeChange) {
558  IsolateScope isolate_scope(isolate());
559 
560  const int64_t parent_view_id =
562  engine_id(), regular_creation_request());
563  const HWND parent_window_handle =
565  engine_id(), parent_view_id);
566 
567  // Track the child size passed to the callback
568  static int callback_count = 0;
569  static int last_width = 0;
570  static int last_height = 0;
571 
572  auto position_callback = [](const WindowSize& child_size,
573  const WindowRect& parent_rect,
574  const WindowRect& output_rect) -> WindowRect* {
575  callback_count++;
576  last_width = child_size.width;
577  last_height = child_size.height;
578 
579  // Use malloc since the caller will use free()
580  WindowRect* rect = static_cast<WindowRect*>(malloc(sizeof(WindowRect)));
581  rect->left = parent_rect.left + callback_count * 5;
582  rect->top = parent_rect.top + callback_count * 5;
583  rect->width = child_size.width;
584  rect->height = child_size.height;
585  return rect;
586  };
587 
588  TooltipWindowCreationRequest creation_request{
590  .view_min_width = 100,
591  .view_min_height = 50,
592  .view_max_width = 300,
593  .view_max_height = 200},
594  .is_sized_to_content = true,
595  .parent = parent_window_handle,
596  .get_position_callback = position_callback};
597 
598  // Reset callback tracking
599  callback_count = 0;
600  last_width = 0;
601  last_height = 0;
602 
603  const int64_t tooltip_view_id =
605  engine_id(), &creation_request);
606 
607  HWND tooltip_window_handle =
609  engine_id(), tooltip_view_id);
610 
611  // Get the view associated with the tooltip window
612  FlutterWindowsView* view =
613  engine()->GetViewFromTopLevelWindow(tooltip_window_handle);
614  ASSERT_NE(view, nullptr);
615 
616  // Get initial position
617  RECT initial_rect;
618  GetWindowRect(tooltip_window_handle, &initial_rect);
619  int initial_callback_count = callback_count;
620 
621  // Simulate a frame being generated with new dimensions
622  // This should trigger DidUpdateViewSize which calls UpdatePosition
623  view->OnFrameGenerated(150, 100);
624 
625  // Process any pending tasks to ensure the callback is executed
626  engine()->task_runner()->ProcessTasks();
627 
628  // Verify the callback was called again with the new dimensions
629  EXPECT_GT(callback_count, initial_callback_count);
630  EXPECT_EQ(last_width, 150);
631  EXPECT_EQ(last_height, 100);
632 
633  // Get new position and verify it changed
634  RECT new_rect;
635  GetWindowRect(tooltip_window_handle, &new_rect);
636 
637  // The position should have changed due to our callback logic
638  // (we offset by callback_count * 5)
639  EXPECT_NE(initial_rect.left, new_rect.left);
640  EXPECT_NE(initial_rect.top, new_rect.top);
641 }
642 
643 } // namespace testing
644 } // namespace flutter
bool OnFrameGenerated(size_t width, size_t height)
HWND GetWindowHandle() const
Definition: host_window.cc:350
HostWindow * GetOwnerWindow() const
Definition: host_window.cc:829
static HostWindow * GetThisFromHandle(HWND hwnd)
Definition: host_window.cc:335
static Isolate Current()
Definition: isolate_scope.cc:9
Win32Message message
TEST_F(AccessibilityPluginTest, DirectAnnounceCall)
void(* on_message)(WindowsMessage *)
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)
HWND InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(int64_t engine_id, FlutterViewId view_id)