Flutter Windows Embedder
flutter_window_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 
5 #include "flutter/fml/macros.h"
7 #include "flutter/shell/platform/windows/testing/flutter_windows_engine_builder.h"
8 #include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h"
9 #include "flutter/shell/platform/windows/testing/mock_window_binding_handler_delegate.h"
10 #include "flutter/shell/platform/windows/testing/windows_test.h"
11 #include "flutter/shell/platform/windows/testing/wm_builders.h"
12 
13 #include "gmock/gmock.h"
14 #include "gtest/gtest.h"
15 
16 namespace flutter {
17 namespace testing {
18 
19 using ::testing::_;
20 using ::testing::AnyNumber;
21 using ::testing::Eq;
22 using ::testing::Invoke;
23 using ::testing::Return;
24 
25 namespace {
26 static constexpr int32_t kDefaultPointerDeviceId = 0;
27 
28 class MockFlutterWindow : public FlutterWindow {
29  public:
30  MockFlutterWindow(bool reset_view_on_exit = true)
31  : reset_view_on_exit_(reset_view_on_exit) {
32  ON_CALL(*this, GetDpiScale())
33  .WillByDefault(Return(this->FlutterWindow::GetDpiScale()));
34  }
35  virtual ~MockFlutterWindow() {
36  if (reset_view_on_exit_) {
37  SetView(nullptr);
38  }
39  }
40 
41  // Wrapper for GetCurrentDPI() which is a protected method.
42  UINT GetDpi() { return GetCurrentDPI(); }
43 
44  // Simulates a WindowProc message from the OS.
45  LRESULT InjectWindowMessage(UINT const message,
46  WPARAM const wparam,
47  LPARAM const lparam) {
48  return HandleMessage(message, wparam, lparam);
49  }
50 
51  MOCK_METHOD(void, OnDpiScale, (unsigned int), (override));
52  MOCK_METHOD(void, OnResize, (unsigned int, unsigned int), (override));
53  MOCK_METHOD(void,
54  OnPointerMove,
55  (double, double, FlutterPointerDeviceKind, int32_t, int),
56  (override));
57  MOCK_METHOD(void,
58  OnPointerDown,
59  (double, double, FlutterPointerDeviceKind, int32_t, UINT),
60  (override));
61  MOCK_METHOD(void,
62  OnPointerUp,
63  (double, double, FlutterPointerDeviceKind, int32_t, UINT),
64  (override));
65  MOCK_METHOD(void,
66  OnPointerLeave,
67  (double, double, FlutterPointerDeviceKind, int32_t),
68  (override));
69  MOCK_METHOD(float, GetScrollOffsetMultiplier, (), (override));
70  MOCK_METHOD(float, GetDpiScale, (), (override));
71  MOCK_METHOD(void, UpdateCursorRect, (const Rect&), (override));
72  MOCK_METHOD(void, OnResetImeComposing, (), (override));
73  MOCK_METHOD(UINT, Win32DispatchMessage, (UINT, WPARAM, LPARAM), (override));
74  MOCK_METHOD(BOOL, Win32PeekMessage, (LPMSG, UINT, UINT, UINT), (override));
75  MOCK_METHOD(uint32_t, Win32MapVkToChar, (uint32_t), (override));
76  MOCK_METHOD(HWND, GetWindowHandle, (), (override));
77  MOCK_METHOD(ui::AXFragmentRootDelegateWin*,
78  GetAxFragmentRootDelegate,
79  (),
80  (override));
81  MOCK_METHOD(void, OnWindowStateEvent, (WindowStateEvent), (override));
82 
83  protected:
84  // |KeyboardManager::WindowDelegate|
85  LRESULT Win32DefWindowProc(HWND hWnd,
86  UINT Msg,
87  WPARAM wParam,
88  LPARAM lParam) override {
89  return kWmResultDefault;
90  }
91 
92  private:
93  bool reset_view_on_exit_;
94  FML_DISALLOW_COPY_AND_ASSIGN(MockFlutterWindow);
95 };
96 
97 class MockFlutterWindowsView : public FlutterWindowsView {
98  public:
99  MockFlutterWindowsView(FlutterWindowsEngine* engine,
100  std::unique_ptr<WindowBindingHandler> window_binding)
102  engine,
103  std::move(window_binding),
104  false,
105  BoxConstraints()) {}
107 
108  MOCK_METHOD(void,
109  NotifyWinEventWrapper,
110  (ui::AXPlatformNodeWin*, ax::mojom::Event),
111  (override));
112 
113  private:
114  FML_DISALLOW_COPY_AND_ASSIGN(MockFlutterWindowsView);
115 };
116 
117 class FlutterWindowTest : public WindowsTest {};
118 
119 } // namespace
120 
121 TEST_F(FlutterWindowTest, CreateDestroy) {
122  std::unique_ptr<FlutterWindowsEngine> engine =
123  FlutterWindowsEngineBuilder{GetContext()}.Build();
124  FlutterWindow window(800, 600, engine->display_manager());
125  ASSERT_TRUE(TRUE);
126 }
127 
128 TEST_F(FlutterWindowTest, OnBitmapSurfaceUpdated) {
129  std::unique_ptr<FlutterWindowsEngine> engine =
130  FlutterWindowsEngineBuilder{GetContext()}.Build();
131  FlutterWindow win32window(100, 100, engine->display_manager());
132  int old_handle_count = GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS);
133 
134  constexpr size_t row_bytes = 100 * 4;
135  constexpr size_t height = 100;
136  std::array<char, row_bytes * height> allocation;
137  win32window.OnBitmapSurfaceUpdated(allocation.data(), row_bytes, height);
138 
139  int new_handle_count = GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS);
140  // Check GDI resources leak
141  EXPECT_EQ(old_handle_count, new_handle_count);
142 }
143 
144 // Tests that composing rect updates are transformed from Flutter logical
145 // coordinates to device coordinates and passed to the text input manager
146 // when the DPI scale is 100% (96 DPI).
147 TEST_F(FlutterWindowTest, OnCursorRectUpdatedRegularDPI) {
148  MockFlutterWindow win32window;
149  EXPECT_CALL(win32window, GetDpiScale()).WillOnce(Return(1.0));
150 
151  Rect cursor_rect(Point(10, 20), Size(30, 40));
152  EXPECT_CALL(win32window, UpdateCursorRect(cursor_rect)).Times(1);
153 
154  win32window.OnCursorRectUpdated(cursor_rect);
155 }
156 
157 // Tests that composing rect updates are transformed from Flutter logical
158 // coordinates to device coordinates and passed to the text input manager
159 // when the DPI scale is 150% (144 DPI).
160 TEST_F(FlutterWindowTest, OnCursorRectUpdatedHighDPI) {
161  MockFlutterWindow win32window;
162  EXPECT_CALL(win32window, GetDpiScale()).WillOnce(Return(1.5));
163 
164  Rect expected_cursor_rect(Point(15, 30), Size(45, 60));
165  EXPECT_CALL(win32window, UpdateCursorRect(expected_cursor_rect)).Times(1);
166 
167  Rect cursor_rect(Point(10, 20), Size(30, 40));
168  win32window.OnCursorRectUpdated(cursor_rect);
169 }
170 
171 TEST_F(FlutterWindowTest, OnPointerStarSendsDeviceType) {
172  std::unique_ptr<FlutterWindowsEngine> engine =
173  FlutterWindowsEngineBuilder{GetContext()}.Build();
174  FlutterWindow win32window(100, 100, engine->display_manager());
175  MockWindowBindingHandlerDelegate delegate;
176  EXPECT_CALL(delegate, OnWindowStateEvent).Times(AnyNumber());
177  win32window.SetView(&delegate);
178 
179  // Move
180  EXPECT_CALL(delegate,
181  OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindMouse,
182  kDefaultPointerDeviceId, 0))
183  .Times(1);
184  EXPECT_CALL(delegate,
185  OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindTouch,
186  kDefaultPointerDeviceId, 0))
187  .Times(1);
188  EXPECT_CALL(delegate,
189  OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindStylus,
190  kDefaultPointerDeviceId, 0))
191  .Times(1);
192 
193  // Down
194  EXPECT_CALL(
195  delegate,
196  OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindMouse,
197  kDefaultPointerDeviceId, kFlutterPointerButtonMousePrimary))
198  .Times(1);
199  EXPECT_CALL(
200  delegate,
201  OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindTouch,
202  kDefaultPointerDeviceId, kFlutterPointerButtonMousePrimary))
203  .Times(1);
204  EXPECT_CALL(
205  delegate,
206  OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindStylus,
207  kDefaultPointerDeviceId, kFlutterPointerButtonMousePrimary))
208  .Times(1);
209 
210  // Up
211  EXPECT_CALL(delegate, OnPointerUp(10.0, 10.0, kFlutterPointerDeviceKindMouse,
212  kDefaultPointerDeviceId,
213  kFlutterPointerButtonMousePrimary))
214  .Times(1);
215  EXPECT_CALL(delegate, OnPointerUp(10.0, 10.0, kFlutterPointerDeviceKindTouch,
216  kDefaultPointerDeviceId,
217  kFlutterPointerButtonMousePrimary))
218  .Times(1);
219  EXPECT_CALL(delegate, OnPointerUp(10.0, 10.0, kFlutterPointerDeviceKindStylus,
220  kDefaultPointerDeviceId,
221  kFlutterPointerButtonMousePrimary))
222  .Times(1);
223 
224  // Leave
225  EXPECT_CALL(delegate,
226  OnPointerLeave(10.0, 10.0, kFlutterPointerDeviceKindMouse,
227  kDefaultPointerDeviceId))
228  .Times(1);
229  EXPECT_CALL(delegate,
230  OnPointerLeave(10.0, 10.0, kFlutterPointerDeviceKindTouch,
231  kDefaultPointerDeviceId))
232  .Times(1);
233  EXPECT_CALL(delegate,
234  OnPointerLeave(10.0, 10.0, kFlutterPointerDeviceKindStylus,
235  kDefaultPointerDeviceId))
236  .Times(1);
237 
238  win32window.OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindMouse,
239  kDefaultPointerDeviceId, 0);
240  win32window.OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindMouse,
241  kDefaultPointerDeviceId, WM_LBUTTONDOWN);
242  win32window.OnPointerUp(10.0, 10.0, kFlutterPointerDeviceKindMouse,
243  kDefaultPointerDeviceId, WM_LBUTTONDOWN);
244  win32window.OnPointerLeave(10.0, 10.0, kFlutterPointerDeviceKindMouse,
245  kDefaultPointerDeviceId);
246 
247  // Touch
248  win32window.OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindTouch,
249  kDefaultPointerDeviceId, 0);
250  win32window.OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindTouch,
251  kDefaultPointerDeviceId, WM_LBUTTONDOWN);
252  win32window.OnPointerUp(10.0, 10.0, kFlutterPointerDeviceKindTouch,
253  kDefaultPointerDeviceId, WM_LBUTTONDOWN);
254  win32window.OnPointerLeave(10.0, 10.0, kFlutterPointerDeviceKindTouch,
255  kDefaultPointerDeviceId);
256 
257  // Pen
258  win32window.OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindStylus,
259  kDefaultPointerDeviceId, 0);
260  win32window.OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindStylus,
261  kDefaultPointerDeviceId, WM_LBUTTONDOWN);
262  win32window.OnPointerUp(10.0, 10.0, kFlutterPointerDeviceKindStylus,
263  kDefaultPointerDeviceId, WM_LBUTTONDOWN);
264  win32window.OnPointerLeave(10.0, 10.0, kFlutterPointerDeviceKindStylus,
265  kDefaultPointerDeviceId);
266 
267  // Destruction of win32window sends a HIDE update. In situ, the window is
268  // owned by the delegate, and so is destructed first. Not so here.
269  win32window.SetView(nullptr);
270 }
271 
272 // Tests that calls to OnScroll in turn calls GetScrollOffsetMultiplier
273 // for mapping scroll ticks to pixels.
274 TEST_F(FlutterWindowTest, OnScrollCallsGetScrollOffsetMultiplier) {
275  MockFlutterWindow win32window;
276  MockWindowBindingHandlerDelegate delegate;
277  EXPECT_CALL(win32window, OnWindowStateEvent).Times(AnyNumber());
278  win32window.SetView(&delegate);
279 
280  EXPECT_CALL(win32window, GetWindowHandle).WillOnce([&win32window]() {
281  return win32window.FlutterWindow::GetWindowHandle();
282  });
283  EXPECT_CALL(win32window, GetScrollOffsetMultiplier).WillOnce(Return(120.0f));
284 
285  EXPECT_CALL(delegate,
286  OnScroll(_, _, 0, 0, 120.0f, kFlutterPointerDeviceKindMouse,
287  kDefaultPointerDeviceId))
288  .Times(1);
289 
290  win32window.OnScroll(0.0f, 0.0f, kFlutterPointerDeviceKindMouse,
291  kDefaultPointerDeviceId);
292 }
293 
294 TEST_F(FlutterWindowTest, OnWindowRepaint) {
295  MockFlutterWindow win32window;
296  MockWindowBindingHandlerDelegate delegate;
297  EXPECT_CALL(win32window, OnWindowStateEvent).Times(AnyNumber());
298  win32window.SetView(&delegate);
299 
300  EXPECT_CALL(delegate, OnWindowRepaint()).Times(1);
301 
302  win32window.InjectWindowMessage(WM_PAINT, 0, 0);
303 }
304 
305 TEST_F(FlutterWindowTest, OnThemeChange) {
306  MockFlutterWindow win32window;
307  MockWindowBindingHandlerDelegate delegate;
308  EXPECT_CALL(win32window, OnWindowStateEvent).Times(AnyNumber());
309  win32window.SetView(&delegate);
310 
311  EXPECT_CALL(delegate, OnHighContrastChanged).Times(1);
312 
313  win32window.InjectWindowMessage(WM_THEMECHANGED, 0, 0);
314 }
315 
316 // The window should return no root accessibility node if
317 // it isn't attached to a view.
318 // Regression test for https://github.com/flutter/flutter/issues/129791
319 TEST_F(FlutterWindowTest, AccessibilityNodeWithoutView) {
320  MockFlutterWindow win32window;
321 
322  EXPECT_EQ(win32window.GetNativeViewAccessible(), nullptr);
323 }
324 
325 // Ensure that announcing the alert propagates the message to the alert node.
326 // Different screen readers use different properties for alerts.
327 TEST_F(FlutterWindowTest, AlertNode) {
328  std::unique_ptr<FlutterWindowsEngine> engine =
329  FlutterWindowsEngineBuilder{GetContext()}.Build();
330  auto win32window = std::make_unique<MockFlutterWindow>();
331  EXPECT_CALL(*win32window.get(), GetAxFragmentRootDelegate())
332  .WillRepeatedly(Return(nullptr));
333  EXPECT_CALL(*win32window.get(), OnWindowStateEvent).Times(AnyNumber());
334  EXPECT_CALL(*win32window.get(), GetWindowHandle).Times(AnyNumber());
335  MockFlutterWindowsView view{engine.get(), std::move(win32window)};
336  std::wstring message = L"Test alert";
337  EXPECT_CALL(view, NotifyWinEventWrapper(_, ax::mojom::Event::kAlert))
338  .Times(1);
339  view.AnnounceAlert(message);
340 
341  IAccessible* alert = view.AlertNode();
342  VARIANT self{.vt = VT_I4, .lVal = CHILDID_SELF};
343  BSTR strptr;
344  alert->get_accName(self, &strptr);
345  EXPECT_EQ(message, strptr);
346 
347  alert->get_accDescription(self, &strptr);
348  EXPECT_EQ(message, strptr);
349 
350  alert->get_accValue(self, &strptr);
351  EXPECT_EQ(message, strptr);
352 
353  VARIANT role;
354  alert->get_accRole(self, &role);
355  EXPECT_EQ(role.vt, VT_I4);
356  EXPECT_EQ(role.lVal, ROLE_SYSTEM_ALERT);
357 }
358 
359 TEST_F(FlutterWindowTest, LifecycleFocusMessages) {
360  MockFlutterWindow win32window;
361  EXPECT_CALL(win32window, GetWindowHandle)
362  .WillRepeatedly(Return(reinterpret_cast<HWND>(1)));
363  MockWindowBindingHandlerDelegate delegate;
364 
365  WindowStateEvent last_event;
366  EXPECT_CALL(delegate, OnWindowStateEvent)
367  .WillRepeatedly([&last_event](HWND hwnd, WindowStateEvent event) {
368  last_event = event;
369  });
370  EXPECT_CALL(win32window, OnWindowStateEvent)
371  .WillRepeatedly([&](WindowStateEvent event) {
372  win32window.FlutterWindow::OnWindowStateEvent(event);
373  });
374  EXPECT_CALL(win32window, OnResize).Times(AnyNumber());
375 
376  win32window.SetView(&delegate);
377 
378  win32window.InjectWindowMessage(WM_SIZE, 0, 0);
379  EXPECT_EQ(last_event, WindowStateEvent::kHide);
380 
381  win32window.InjectWindowMessage(WM_SIZE, 0, MAKEWORD(1, 1));
382  EXPECT_EQ(last_event, WindowStateEvent::kShow);
383 
384  EXPECT_CALL(delegate, OnFocus(Eq(FlutterViewFocusState::kFocused),
385  Eq(FlutterViewFocusDirection::kUndefined)))
386  .Times(1);
387  win32window.InjectWindowMessage(WM_SETFOCUS, 0, 0);
388  EXPECT_EQ(last_event, WindowStateEvent::kFocus);
389 
390  EXPECT_CALL(delegate, OnFocus(Eq(FlutterViewFocusState::kUnfocused),
391  Eq(FlutterViewFocusDirection::kUndefined)))
392  .Times(1);
393  win32window.InjectWindowMessage(WM_KILLFOCUS, 0, 0);
394  EXPECT_EQ(last_event, WindowStateEvent::kUnfocus);
395 }
396 
397 TEST_F(FlutterWindowTest, CachedLifecycleMessage) {
398  MockFlutterWindow win32window;
399  EXPECT_CALL(win32window, GetWindowHandle)
400  .WillRepeatedly(Return(reinterpret_cast<HWND>(1)));
401  EXPECT_CALL(win32window, OnWindowStateEvent)
402  .WillRepeatedly([&](WindowStateEvent event) {
403  win32window.FlutterWindow::OnWindowStateEvent(event);
404  });
405  EXPECT_CALL(win32window, OnResize).Times(1);
406 
407  // Restore
408  win32window.InjectWindowMessage(WM_SIZE, 0, MAKEWORD(1, 1));
409 
410  // Focus
411  win32window.InjectWindowMessage(WM_SETFOCUS, 0, 0);
412 
413  MockWindowBindingHandlerDelegate delegate;
414  bool focused = false;
415  bool restored = false;
416  EXPECT_CALL(delegate, OnWindowStateEvent)
417  .WillRepeatedly([&](HWND hwnd, WindowStateEvent event) {
418  if (event == WindowStateEvent::kFocus) {
419  focused = true;
420  } else if (event == WindowStateEvent::kShow) {
421  restored = true;
422  }
423  });
424 
425  EXPECT_CALL(delegate, OnFocus(Eq(FlutterViewFocusState::kFocused),
426  Eq(FlutterViewFocusDirection::kUndefined)))
427  .Times(1);
428  win32window.SetView(&delegate);
429  EXPECT_TRUE(focused);
430  EXPECT_TRUE(restored);
431 }
432 
433 } // namespace testing
434 } // namespace flutter
virtual void OnPointerLeave(double x, double y, FlutterPointerDeviceKind device_kind, int32_t device_id)
virtual bool OnBitmapSurfaceUpdated(const void *allocation, size_t row_bytes, size_t height) override
virtual void OnPointerUp(double x, double y, FlutterPointerDeviceKind device_kind, int32_t device_id, UINT button)
virtual void OnPointerDown(double x, double y, FlutterPointerDeviceKind device_kind, int32_t device_id, UINT button)
virtual void OnPointerMove(double x, double y, FlutterPointerDeviceKind device_kind, int32_t device_id, int modifiers_state)
virtual void SetView(WindowBindingHandlerDelegate *view) override
virtual float GetDpiScale() override
FlutterWindowsView(FlutterViewId view_id, FlutterWindowsEngine *engine, std::unique_ptr< WindowBindingHandler > window_binding, bool is_sized_to_content, const BoxConstraints &box_constraints, FlutterWindowsViewSizingDelegate *sizing_delegate=nullptr, std::shared_ptr< WindowsProcTable > windows_proc_table=nullptr)
MOCK_METHOD(void, NotifyWinEventWrapper,(ui::AXPlatformNodeWin *, ax::mojom::Event),(override))
MockFlutterWindowsView(FlutterWindowsEngine *engine, std::unique_ptr< WindowBindingHandler > wbh)
Win32Message message
TEST_F(AccessibilityPluginTest, DirectAnnounceCall)
WindowStateEvent
An event representing a change in window state that may update the.
constexpr FlutterViewId kImplicitViewId