Flutter Windows Embedder
flutter_windows_engine_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 <thread>
7 
8 #include "flutter/fml/logging.h"
9 #include "flutter/fml/macros.h"
10 #include "flutter/shell/platform/embedder/embedder.h"
11 #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
14 #include "flutter/shell/platform/windows/testing/egl/mock_manager.h"
15 #include "flutter/shell/platform/windows/testing/engine_modifier.h"
16 #include "flutter/shell/platform/windows/testing/flutter_windows_engine_builder.h"
17 #include "flutter/shell/platform/windows/testing/mock_platform_view_manager.h"
18 #include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h"
19 #include "flutter/shell/platform/windows/testing/mock_windows_proc_table.h"
20 #include "flutter/shell/platform/windows/testing/test_keyboard.h"
21 #include "flutter/shell/platform/windows/testing/windows_test.h"
22 #include "flutter/shell/platform/windows/testing/windows_test_config_builder.h"
23 #include "flutter/third_party/accessibility/ax/platform/ax_platform_node_win.h"
24 #include "fml/synchronization/waitable_event.h"
25 #include "gmock/gmock.h"
26 #include "gtest/gtest.h"
27 
28 // winbase.h defines GetCurrentTime as a macro.
29 #undef GetCurrentTime
30 
31 namespace {
32 // Process the next win32 message if there is one. This can be used to
33 // pump the Windows platform thread task runner.
34 void PumpMessage() {
35  ::MSG msg;
36  if (::GetMessage(&msg, nullptr, 0, 0)) {
37  ::TranslateMessage(&msg);
38  ::DispatchMessage(&msg);
39  }
40 }
41 } // namespace
42 
43 namespace flutter {
44 namespace testing {
45 
46 using ::testing::_;
47 using ::testing::DoAll;
48 using ::testing::NiceMock;
49 using ::testing::Return;
50 using ::testing::SetArgPointee;
51 
52 class FlutterWindowsEngineTest : public WindowsTest {};
53 
54 // The engine can be run without any views.
56  FlutterWindowsEngineBuilder builder{GetContext()};
57  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
58 
59  EngineModifier modifier(engine.get());
60  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
61 
62  ASSERT_TRUE(engine->Run());
63  ASSERT_EQ(engine->view(kImplicitViewId), nullptr);
64  ASSERT_EQ(engine->view(123), nullptr);
65 }
66 
67 TEST_F(FlutterWindowsEngineTest, TaskRunnerDelayedTask) {
68  bool finished = false;
69  auto runner = std::make_unique<TaskRunner>(
70  [] {
71  return static_cast<uint64_t>(
72  fml::TimePoint::Now().ToEpochDelta().ToNanoseconds());
73  },
74  [&](const FlutterTask*) { finished = true; });
75  runner->PostFlutterTask(
76  FlutterTask{},
77  static_cast<uint64_t>((fml::TimePoint::Now().ToEpochDelta() +
78  fml::TimeDelta::FromMilliseconds(50))
79  .ToNanoseconds()));
80  auto start = fml::TimePoint::Now();
81  while (!finished) {
82  PumpMessage();
83  }
84  auto duration = fml::TimePoint::Now() - start;
85  EXPECT_GE(duration, fml::TimeDelta::FromMilliseconds(50));
86 }
87 
88 // https://github.com/flutter/flutter/issues/173843)
89 TEST_F(FlutterWindowsEngineTest, TaskRunnerDoesNotDeadlock) {
90  auto runner = std::make_unique<TaskRunner>(
91  [] {
92  return static_cast<uint64_t>(
93  fml::TimePoint::Now().ToEpochDelta().ToNanoseconds());
94  },
95  [&](const FlutterTask*) {});
96 
97  struct RunnerHolder {
98  void PostTaskLoop() {
99  runner->PostTask([this] { PostTaskLoop(); });
100  }
101  std::unique_ptr<TaskRunner> runner;
102  };
103 
104  RunnerHolder container{.runner = std::move(runner)};
105  // Spam flutter tasks.
106  container.PostTaskLoop();
107 
108  const LPCWSTR class_name = L"FlutterTestWindowClass";
109  WNDCLASS wc = {0};
110  wc.lpfnWndProc = DefWindowProc;
111  wc.lpszClassName = class_name;
112  RegisterClass(&wc);
113 
114  HWND window;
115  container.runner->PostTask([&] {
116  window = CreateWindowEx(0, class_name, L"Empty Window", WS_OVERLAPPEDWINDOW,
117  CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, nullptr,
118  nullptr, nullptr, nullptr);
119  ShowWindow(window, SW_SHOW);
120  });
121 
122  while (true) {
123  ::MSG msg;
124  if (::GetMessage(&msg, nullptr, 0, 0)) {
125  if (msg.message == WM_PAINT) {
126  break;
127  }
128  ::TranslateMessage(&msg);
129  ::DispatchMessage(&msg);
130  }
131  }
132 
133  DestroyWindow(window);
134  UnregisterClassW(class_name, nullptr);
135 }
136 
137 TEST_F(FlutterWindowsEngineTest, RunDoesExpectedInitialization) {
138  FlutterWindowsEngineBuilder builder{GetContext()};
139  builder.AddDartEntrypointArgument("arg1");
140  builder.AddDartEntrypointArgument("arg2");
141 
142  auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
143 
144  HMONITOR mock_monitor = reinterpret_cast<HMONITOR>(1);
145 
146  MONITORINFOEXW monitor_info = {};
147  monitor_info.cbSize = sizeof(MONITORINFOEXW);
148  monitor_info.rcMonitor = {0, 0, 1920, 1080};
149  monitor_info.rcWork = {0, 0, 1920, 1080};
150  monitor_info.dwFlags = MONITORINFOF_PRIMARY;
151  wcscpy_s(monitor_info.szDevice, L"\\\\.\\DISPLAY1");
152 
153  EXPECT_CALL(*windows_proc_table, GetMonitorInfoW(mock_monitor, _))
154  .WillRepeatedly(DoAll(SetArgPointee<1>(monitor_info), Return(TRUE)));
155 
156  EXPECT_CALL(*windows_proc_table, EnumDisplayMonitors(nullptr, nullptr, _, _))
157  .WillRepeatedly([&](HDC hdc, LPCRECT lprcClip, MONITORENUMPROC lpfnEnum,
158  LPARAM dwData) {
159  lpfnEnum(mock_monitor, nullptr, &monitor_info.rcMonitor, dwData);
160  return TRUE;
161  });
162 
163  EXPECT_CALL(*windows_proc_table, GetDpiForMonitor(mock_monitor, _))
164  .WillRepeatedly(Return(96));
165 
166  // Mock locale information
167  EXPECT_CALL(*windows_proc_table, GetThreadPreferredUILanguages(_, _, _, _))
168  .WillRepeatedly(
169  [](DWORD flags, PULONG count, PZZWSTR languages, PULONG length) {
170  // We need to mock the locale information twice because the first
171  // call is to get the size and the second call is to fill the
172  // buffer.
173  if (languages == nullptr) {
174  // First call is to get the size
175  *count = 1; // One language
176  *length = 10; // "fr-FR\0\0" (double null-terminated)
177  return TRUE;
178  } else {
179  // Second call is to fill the buffer
180  *count = 1;
181  // Fill with "fr-FR\0\0" (double null-terminated)
182  wchar_t* lang_buffer = languages;
183  wcscpy(lang_buffer, L"fr-FR");
184  // Move past the first null terminator to add the second
185  lang_buffer += wcslen(L"fr-FR") + 1;
186  *lang_buffer = L'\0';
187  return TRUE;
188  }
189  });
190 
191  builder.SetWindowsProcTable(windows_proc_table);
192 
193  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
194  EngineModifier modifier(engine.get());
195 
196  // The engine should be run with expected configuration values.
197  bool run_called = false;
198  modifier.embedder_api().Run = MOCK_ENGINE_PROC(
199  Run, ([&run_called, engine_instance = engine.get()](
200  size_t version, const FlutterRendererConfig* config,
201  const FlutterProjectArgs* args, void* user_data,
202  FLUTTER_API_SYMBOL(FlutterEngine) * engine_out) {
203  run_called = true;
204  *engine_out = reinterpret_cast<FLUTTER_API_SYMBOL(FlutterEngine)>(1);
205 
206  EXPECT_EQ(version, FLUTTER_ENGINE_VERSION);
207  EXPECT_NE(config, nullptr);
208  // We have an EGL manager, so this should be using OpenGL.
209  EXPECT_EQ(config->type, kOpenGL);
210  EXPECT_EQ(user_data, engine_instance);
211  // Spot-check arguments.
212  EXPECT_NE(args->assets_path, nullptr);
213  EXPECT_NE(args->icu_data_path, nullptr);
214  EXPECT_EQ(args->dart_entrypoint_argc, 2U);
215  EXPECT_EQ(strcmp(args->dart_entrypoint_argv[0], "arg1"), 0);
216  EXPECT_EQ(strcmp(args->dart_entrypoint_argv[1], "arg2"), 0);
217  EXPECT_NE(args->platform_message_callback, nullptr);
218  EXPECT_NE(args->custom_task_runners, nullptr);
219  EXPECT_NE(args->custom_task_runners->thread_priority_setter, nullptr);
220  EXPECT_EQ(args->custom_dart_entrypoint, nullptr);
221  EXPECT_NE(args->vsync_callback, nullptr);
222  EXPECT_EQ(args->update_semantics_callback, nullptr);
223  EXPECT_NE(args->update_semantics_callback2, nullptr);
224  EXPECT_EQ(args->update_semantics_node_callback, nullptr);
225  EXPECT_EQ(args->update_semantics_custom_action_callback, nullptr);
226  EXPECT_NE(args->view_focus_change_request_callback, nullptr);
227 
228  args->custom_task_runners->thread_priority_setter(
229  FlutterThreadPriority::kRaster);
230  EXPECT_EQ(GetThreadPriority(GetCurrentThread()),
231  THREAD_PRIORITY_ABOVE_NORMAL);
232  return kSuccess;
233  }));
234  // Accessibility updates must do nothing when the embedder engine is mocked
235  modifier.embedder_api().UpdateAccessibilityFeatures = MOCK_ENGINE_PROC(
236  UpdateAccessibilityFeatures,
237  [](FLUTTER_API_SYMBOL(FlutterEngine) engine,
238  FlutterAccessibilityFeature flags) { return kSuccess; });
239 
240  // It should send locale info.
241  bool update_locales_called = false;
242  modifier.embedder_api().UpdateLocales = MOCK_ENGINE_PROC(
243  UpdateLocales,
244  ([&update_locales_called](auto engine, const FlutterLocale** locales,
245  size_t locales_count) {
246  update_locales_called = true;
247 
248  EXPECT_GT(locales_count, 0);
249  EXPECT_NE(locales, nullptr);
250 
251  return kSuccess;
252  }));
253 
254  // And it should send initial settings info.
255  bool settings_message_sent = false;
256  modifier.embedder_api().SendPlatformMessage = MOCK_ENGINE_PROC(
257  SendPlatformMessage,
258  ([&settings_message_sent](auto engine, auto message) {
259  if (std::string(message->channel) == std::string("flutter/settings")) {
260  settings_message_sent = true;
261  }
262 
263  return kSuccess;
264  }));
265 
266  // And it should send display info.
267  bool notify_display_update_called = false;
268 
269  modifier.embedder_api().NotifyDisplayUpdate = MOCK_ENGINE_PROC(
270  NotifyDisplayUpdate,
271  ([&notify_display_update_called](
272  FLUTTER_API_SYMBOL(FlutterEngine) raw_engine,
273  const FlutterEngineDisplaysUpdateType update_type,
274  const FlutterEngineDisplay* embedder_displays,
275  size_t display_count) {
276  EXPECT_EQ(update_type, kFlutterEngineDisplaysUpdateTypeStartup);
277  notify_display_update_called = true;
278  return kSuccess;
279  }));
280 
281  // Set the EGL manager to !nullptr to test ANGLE rendering.
282  modifier.SetEGLManager(std::make_unique<egl::MockManager>());
283 
284  engine->Run();
285 
286  EXPECT_TRUE(run_called);
287  EXPECT_TRUE(update_locales_called);
288  EXPECT_TRUE(settings_message_sent);
289  EXPECT_TRUE(notify_display_update_called);
290 
291  // Ensure that deallocation doesn't call the actual Shutdown with the bogus
292  // engine pointer that the overridden Run returned.
293  modifier.embedder_api().Shutdown = [](auto engine) { return kSuccess; };
294  modifier.ReleaseEGLManager();
295 }
296 
297 TEST_F(FlutterWindowsEngineTest, ConfiguresFrameVsync) {
298  FlutterWindowsEngineBuilder builder{GetContext()};
299  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
300  EngineModifier modifier(engine.get());
301  bool on_vsync_called = false;
302 
303  modifier.embedder_api().GetCurrentTime =
304  MOCK_ENGINE_PROC(GetCurrentTime, ([]() -> uint64_t { return 1; }));
305  modifier.embedder_api().OnVsync = MOCK_ENGINE_PROC(
306  OnVsync,
307  ([&on_vsync_called, engine_instance = engine.get()](
308  FLUTTER_API_SYMBOL(FlutterEngine) engine, intptr_t baton,
309  uint64_t frame_start_time_nanos, uint64_t frame_target_time_nanos) {
310  EXPECT_EQ(baton, 1);
311  EXPECT_EQ(frame_start_time_nanos, 16600000);
312  EXPECT_EQ(frame_target_time_nanos, 33200000);
313  on_vsync_called = true;
314  return kSuccess;
315  }));
316  modifier.SetStartTime(0);
317  modifier.SetFrameInterval(16600000);
318 
319  engine->OnVsync(1);
320 
321  EXPECT_TRUE(on_vsync_called);
322 }
323 
324 TEST_F(FlutterWindowsEngineTest, RunWithoutANGLEUsesSoftware) {
325  FlutterWindowsEngineBuilder builder{GetContext()};
326  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
327  EngineModifier modifier(engine.get());
328 
329  modifier.embedder_api().NotifyDisplayUpdate =
330  MOCK_ENGINE_PROC(NotifyDisplayUpdate,
331  ([engine_instance = engine.get()](
332  FLUTTER_API_SYMBOL(FlutterEngine) raw_engine,
333  const FlutterEngineDisplaysUpdateType update_type,
334  const FlutterEngineDisplay* embedder_displays,
335  size_t display_count) { return kSuccess; }));
336 
337  // The engine should be run with expected configuration values.
338  bool run_called = false;
339  modifier.embedder_api().Run = MOCK_ENGINE_PROC(
340  Run, ([&run_called, engine_instance = engine.get()](
341  size_t version, const FlutterRendererConfig* config,
342  const FlutterProjectArgs* args, void* user_data,
343  FLUTTER_API_SYMBOL(FlutterEngine) * engine_out) {
344  run_called = true;
345  *engine_out = reinterpret_cast<FLUTTER_API_SYMBOL(FlutterEngine)>(1);
346  // We don't have an EGL Manager, so we should be using software.
347  EXPECT_EQ(config->type, kSoftware);
348  return kSuccess;
349  }));
350  // Accessibility updates must do nothing when the embedder engine is mocked
351  modifier.embedder_api().UpdateAccessibilityFeatures = MOCK_ENGINE_PROC(
352  UpdateAccessibilityFeatures,
353  [](FLUTTER_API_SYMBOL(FlutterEngine) engine,
354  FlutterAccessibilityFeature flags) { return kSuccess; });
355 
356  // Stub out UpdateLocales and SendPlatformMessage as we don't have a fully
357  // initialized engine instance.
358  modifier.embedder_api().UpdateLocales = MOCK_ENGINE_PROC(
359  UpdateLocales, ([](auto engine, const FlutterLocale** locales,
360  size_t locales_count) { return kSuccess; }));
361  modifier.embedder_api().SendPlatformMessage =
362  MOCK_ENGINE_PROC(SendPlatformMessage,
363  ([](auto engine, auto message) { return kSuccess; }));
364 
365  // Set the EGL manager to nullptr to test software fallback path.
366  modifier.SetEGLManager(nullptr);
367 
368  engine->Run();
369 
370  EXPECT_TRUE(run_called);
371 
372  // Ensure that deallocation doesn't call the actual Shutdown with the bogus
373  // engine pointer that the overridden Run returned.
374  modifier.embedder_api().Shutdown = [](auto engine) { return kSuccess; };
375 }
376 
377 TEST_F(FlutterWindowsEngineTest, RunWithoutANGLEOnImpellerFailsToStart) {
378  FlutterWindowsEngineBuilder builder{GetContext()};
379  builder.SetSwitches({"--enable-impeller=true"});
380  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
381  EngineModifier modifier(engine.get());
382 
383  modifier.embedder_api().NotifyDisplayUpdate =
384  MOCK_ENGINE_PROC(NotifyDisplayUpdate,
385  ([engine_instance = engine.get()](
386  FLUTTER_API_SYMBOL(FlutterEngine) raw_engine,
387  const FlutterEngineDisplaysUpdateType update_type,
388  const FlutterEngineDisplay* embedder_displays,
389  size_t display_count) { return kSuccess; }));
390 
391  // Accessibility updates must do nothing when the embedder engine is mocked
392  modifier.embedder_api().UpdateAccessibilityFeatures = MOCK_ENGINE_PROC(
393  UpdateAccessibilityFeatures,
394  [](FLUTTER_API_SYMBOL(FlutterEngine) engine,
395  FlutterAccessibilityFeature flags) { return kSuccess; });
396 
397  // Stub out UpdateLocales and SendPlatformMessage as we don't have a fully
398  // initialized engine instance.
399  modifier.embedder_api().UpdateLocales = MOCK_ENGINE_PROC(
400  UpdateLocales, ([](auto engine, const FlutterLocale** locales,
401  size_t locales_count) { return kSuccess; }));
402  modifier.embedder_api().SendPlatformMessage =
403  MOCK_ENGINE_PROC(SendPlatformMessage,
404  ([](auto engine, auto message) { return kSuccess; }));
405 
406  // Set the EGL manager to nullptr to test software fallback path.
407  modifier.SetEGLManager(nullptr);
408 
409  EXPECT_FALSE(engine->Run());
410 }
411 
412 TEST_F(FlutterWindowsEngineTest, SendPlatformMessageWithoutResponse) {
413  FlutterWindowsEngineBuilder builder{GetContext()};
414  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
415  EngineModifier modifier(engine.get());
416 
417  const char* channel = "test";
418  const std::vector<uint8_t> test_message = {1, 2, 3, 4};
419 
420  // Without a response, SendPlatformMessage should be a simple pass-through.
421  bool called = false;
422  modifier.embedder_api().SendPlatformMessage = MOCK_ENGINE_PROC(
423  SendPlatformMessage, ([&called, test_message](auto engine, auto message) {
424  called = true;
425  EXPECT_STREQ(message->channel, "test");
426  EXPECT_EQ(message->message_size, test_message.size());
427  EXPECT_EQ(memcmp(message->message, test_message.data(),
428  message->message_size),
429  0);
430  EXPECT_EQ(message->response_handle, nullptr);
431  return kSuccess;
432  }));
433 
434  engine->SendPlatformMessage(channel, test_message.data(), test_message.size(),
435  nullptr, nullptr);
436  EXPECT_TRUE(called);
437 }
438 
439 TEST_F(FlutterWindowsEngineTest, PlatformMessageRoundTrip) {
440  FlutterWindowsEngineBuilder builder{GetContext()};
441  builder.SetDartEntrypoint("hiPlatformChannels");
442 
443  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
444  EngineModifier modifier(engine.get());
445  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
446 
447  auto binary_messenger =
448  std::make_unique<BinaryMessengerImpl>(engine->messenger());
449 
450  engine->Run();
451  bool did_call_callback = false;
452  bool did_call_reply = false;
453  bool did_call_dart_reply = false;
454  std::string channel = "hi";
455  binary_messenger->SetMessageHandler(
456  channel,
457  [&did_call_callback, &did_call_dart_reply](
458  const uint8_t* message, size_t message_size, BinaryReply reply) {
459  if (message_size == 5) {
460  EXPECT_EQ(message[0], static_cast<uint8_t>('h'));
461  char response[] = {'b', 'y', 'e'};
462  reply(reinterpret_cast<uint8_t*>(response), 3);
463  did_call_callback = true;
464  } else {
465  EXPECT_EQ(message_size, 3);
466  EXPECT_EQ(message[0], static_cast<uint8_t>('b'));
467  did_call_dart_reply = true;
468  }
469  });
470  char payload[] = {'h', 'e', 'l', 'l', 'o'};
471  binary_messenger->Send(
472  channel, reinterpret_cast<uint8_t*>(payload), 5,
473  [&did_call_reply](const uint8_t* reply, size_t reply_size) {
474  EXPECT_EQ(reply_size, 5);
475  EXPECT_EQ(reply[0], static_cast<uint8_t>('h'));
476  did_call_reply = true;
477  });
478  // Rely on timeout mechanism in CI.
479  while (!did_call_callback || !did_call_reply || !did_call_dart_reply) {
480  engine->task_runner()->ProcessTasks();
481  }
482 }
483 
484 TEST_F(FlutterWindowsEngineTest, PlatformMessageRespondOnDifferentThread) {
485  FlutterWindowsEngineBuilder builder{GetContext()};
486  builder.SetDartEntrypoint("hiPlatformChannels");
487 
488  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
489 
490  EngineModifier modifier(engine.get());
491  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
492 
493  auto binary_messenger =
494  std::make_unique<BinaryMessengerImpl>(engine->messenger());
495 
496  engine->Run();
497  bool did_call_callback = false;
498  bool did_call_reply = false;
499  bool did_call_dart_reply = false;
500  std::string channel = "hi";
501  std::unique_ptr<std::thread> reply_thread;
502  binary_messenger->SetMessageHandler(
503  channel,
504  [&did_call_callback, &did_call_dart_reply, &reply_thread](
505  const uint8_t* message, size_t message_size, BinaryReply reply) {
506  if (message_size == 5) {
507  EXPECT_EQ(message[0], static_cast<uint8_t>('h'));
508  reply_thread.reset(new std::thread([reply = std::move(reply)]() {
509  char response[] = {'b', 'y', 'e'};
510  reply(reinterpret_cast<uint8_t*>(response), 3);
511  }));
512  did_call_callback = true;
513  } else {
514  EXPECT_EQ(message_size, 3);
515  EXPECT_EQ(message[0], static_cast<uint8_t>('b'));
516  did_call_dart_reply = true;
517  }
518  });
519  char payload[] = {'h', 'e', 'l', 'l', 'o'};
520  binary_messenger->Send(
521  channel, reinterpret_cast<uint8_t*>(payload), 5,
522  [&did_call_reply](const uint8_t* reply, size_t reply_size) {
523  EXPECT_EQ(reply_size, 5);
524  EXPECT_EQ(reply[0], static_cast<uint8_t>('h'));
525  did_call_reply = true;
526  });
527  // Rely on timeout mechanism in CI.
528  while (!did_call_callback || !did_call_reply || !did_call_dart_reply) {
529  engine->task_runner()->ProcessTasks();
530  }
531  ASSERT_TRUE(reply_thread);
532  reply_thread->join();
533 }
534 
535 TEST_F(FlutterWindowsEngineTest, SendPlatformMessageWithResponse) {
536  FlutterWindowsEngineBuilder builder{GetContext()};
537  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
538  EngineModifier modifier(engine.get());
539 
540  const char* channel = "test";
541  const std::vector<uint8_t> test_message = {1, 2, 3, 4};
542  auto* dummy_response_handle =
543  reinterpret_cast<FlutterPlatformMessageResponseHandle*>(5);
544  const FlutterDesktopBinaryReply reply_handler = [](auto... args) {};
545  void* reply_user_data = reinterpret_cast<void*>(6);
546 
547  // When a response is requested, a handle should be created, passed as part
548  // of the message, and then released.
549  bool create_response_handle_called = false;
550  modifier.embedder_api().PlatformMessageCreateResponseHandle =
551  MOCK_ENGINE_PROC(
552  PlatformMessageCreateResponseHandle,
553  ([&create_response_handle_called, &reply_handler, reply_user_data,
554  dummy_response_handle](auto engine, auto reply, auto user_data,
555  auto response_handle) {
556  create_response_handle_called = true;
557  EXPECT_EQ(reply, reply_handler);
558  EXPECT_EQ(user_data, reply_user_data);
559  EXPECT_NE(response_handle, nullptr);
560  *response_handle = dummy_response_handle;
561  return kSuccess;
562  }));
563  bool release_response_handle_called = false;
564  modifier.embedder_api().PlatformMessageReleaseResponseHandle =
565  MOCK_ENGINE_PROC(
566  PlatformMessageReleaseResponseHandle,
567  ([&release_response_handle_called, dummy_response_handle](
568  auto engine, auto response_handle) {
569  release_response_handle_called = true;
570  EXPECT_EQ(response_handle, dummy_response_handle);
571  return kSuccess;
572  }));
573  bool send_message_called = false;
574  modifier.embedder_api().SendPlatformMessage = MOCK_ENGINE_PROC(
575  SendPlatformMessage, ([&send_message_called, test_message,
576  dummy_response_handle](auto engine, auto message) {
577  send_message_called = true;
578  EXPECT_STREQ(message->channel, "test");
579  EXPECT_EQ(message->message_size, test_message.size());
580  EXPECT_EQ(memcmp(message->message, test_message.data(),
581  message->message_size),
582  0);
583  EXPECT_EQ(message->response_handle, dummy_response_handle);
584  return kSuccess;
585  }));
586 
587  engine->SendPlatformMessage(channel, test_message.data(), test_message.size(),
588  reply_handler, reply_user_data);
589  EXPECT_TRUE(create_response_handle_called);
590  EXPECT_TRUE(release_response_handle_called);
591  EXPECT_TRUE(send_message_called);
592 }
593 
594 TEST_F(FlutterWindowsEngineTest, DispatchSemanticsAction) {
595  FlutterWindowsEngineBuilder builder{GetContext()};
596  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
597  EngineModifier modifier(engine.get());
598 
599  bool called = false;
600  std::string message = "Hello";
601  modifier.embedder_api().SendSemanticsAction = MOCK_ENGINE_PROC(
602  SendSemanticsAction, ([&called, &message](auto engine, auto info) {
603  called = true;
604  EXPECT_EQ(info->view_id, 456);
605  EXPECT_EQ(info->node_id, 42);
606  EXPECT_EQ(info->action, kFlutterSemanticsActionDismiss);
607  EXPECT_EQ(memcmp(info->data, message.c_str(), message.size()), 0);
608  EXPECT_EQ(info->data_length, message.size());
609  return kSuccess;
610  }));
611 
612  auto data = fml::MallocMapping::Copy(message.c_str(), message.size());
613  engine->DispatchSemanticsAction(456, 42, kFlutterSemanticsActionDismiss,
614  std::move(data));
615  EXPECT_TRUE(called);
616 }
617 
618 TEST_F(FlutterWindowsEngineTest, SetsThreadPriority) {
619  WindowsPlatformThreadPrioritySetter(FlutterThreadPriority::kBackground);
620  EXPECT_EQ(GetThreadPriority(GetCurrentThread()),
621  THREAD_PRIORITY_BELOW_NORMAL);
622 
623  WindowsPlatformThreadPrioritySetter(FlutterThreadPriority::kDisplay);
624  EXPECT_EQ(GetThreadPriority(GetCurrentThread()),
625  THREAD_PRIORITY_ABOVE_NORMAL);
626 
627  WindowsPlatformThreadPrioritySetter(FlutterThreadPriority::kRaster);
628  EXPECT_EQ(GetThreadPriority(GetCurrentThread()),
629  THREAD_PRIORITY_ABOVE_NORMAL);
630 
631  // FlutterThreadPriority::kNormal does not change thread priority, reset to 0
632  // here.
633  SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
634 
635  WindowsPlatformThreadPrioritySetter(FlutterThreadPriority::kNormal);
636  EXPECT_EQ(GetThreadPriority(GetCurrentThread()), THREAD_PRIORITY_NORMAL);
637 }
638 
639 TEST_F(FlutterWindowsEngineTest, AddPluginRegistrarDestructionCallback) {
640  FlutterWindowsEngineBuilder builder{GetContext()};
641  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
642  EngineModifier modifier(engine.get());
643 
644  MockEmbedderApiForKeyboard(modifier,
645  std::make_shared<MockKeyResponseController>());
646 
647  engine->Run();
648 
649  // Verify that destruction handlers don't overwrite each other.
650  int result1 = 0;
651  int result2 = 0;
652  engine->AddPluginRegistrarDestructionCallback(
654  auto result = reinterpret_cast<int*>(ref);
655  *result = 1;
656  },
657  reinterpret_cast<FlutterDesktopPluginRegistrarRef>(&result1));
658  engine->AddPluginRegistrarDestructionCallback(
660  auto result = reinterpret_cast<int*>(ref);
661  *result = 2;
662  },
663  reinterpret_cast<FlutterDesktopPluginRegistrarRef>(&result2));
664 
665  engine->Stop();
666  EXPECT_EQ(result1, 1);
667  EXPECT_EQ(result2, 2);
668 }
669 
671  FlutterWindowsEngineBuilder builder{GetContext()};
672  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
673  EngineModifier modifier(engine.get());
674 
675  bool called = false;
676  modifier.embedder_api().ScheduleFrame =
677  MOCK_ENGINE_PROC(ScheduleFrame, ([&called](auto engine) {
678  called = true;
679  return kSuccess;
680  }));
681 
682  engine->ScheduleFrame();
683  EXPECT_TRUE(called);
684 }
685 
686 TEST_F(FlutterWindowsEngineTest, SetNextFrameCallback) {
687  FlutterWindowsEngineBuilder builder{GetContext()};
688  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
689  EngineModifier modifier(engine.get());
690 
691  bool called = false;
692  modifier.embedder_api().SetNextFrameCallback = MOCK_ENGINE_PROC(
693  SetNextFrameCallback, ([&called](auto engine, auto callback, auto data) {
694  called = true;
695  return kSuccess;
696  }));
697 
698  engine->SetNextFrameCallback([]() {});
699  EXPECT_TRUE(called);
700 }
701 
702 TEST_F(FlutterWindowsEngineTest, GetExecutableName) {
703  FlutterWindowsEngineBuilder builder{GetContext()};
704  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
705  EXPECT_EQ(engine->GetExecutableName(), "flutter_windows_unittests.exe");
706 }
707 
708 // Ensure that after setting or resetting the high contrast feature,
709 // the corresponding status flag can be retrieved from the engine.
710 TEST_F(FlutterWindowsEngineTest, UpdateHighContrastFeature) {
711  auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
712  EXPECT_CALL(*windows_proc_table, GetHighContrastEnabled)
713  .WillOnce(Return(true))
714  .WillOnce(Return(false));
715 
716  FlutterWindowsEngineBuilder builder{GetContext()};
717  builder.SetWindowsProcTable(windows_proc_table);
718  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
719  EngineModifier modifier(engine.get());
720 
721  std::optional<FlutterAccessibilityFeature> engine_flags;
722  modifier.embedder_api().UpdateAccessibilityFeatures = MOCK_ENGINE_PROC(
723  UpdateAccessibilityFeatures, ([&engine_flags](auto engine, auto flags) {
724  engine_flags = flags;
725  return kSuccess;
726  }));
727  modifier.embedder_api().SendPlatformMessage = MOCK_ENGINE_PROC(
728  SendPlatformMessage,
729  [](auto engine, const auto message) { return kSuccess; });
730 
731  // 1: High contrast is enabled.
732  engine->UpdateHighContrastMode();
733 
734  EXPECT_TRUE(engine->high_contrast_enabled());
735  EXPECT_TRUE(engine_flags.has_value());
736  EXPECT_TRUE(
737  engine_flags.value() &
738  FlutterAccessibilityFeature::kFlutterAccessibilityFeatureHighContrast);
739 
740  // 2: High contrast is disabled.
741  engine_flags.reset();
742  engine->UpdateHighContrastMode();
743 
744  EXPECT_FALSE(engine->high_contrast_enabled());
745  EXPECT_TRUE(engine_flags.has_value());
746  EXPECT_FALSE(
747  engine_flags.value() &
748  FlutterAccessibilityFeature::kFlutterAccessibilityFeatureHighContrast);
749 }
750 
751 TEST_F(FlutterWindowsEngineTest, PostRasterThreadTask) {
752  FlutterWindowsEngineBuilder builder{GetContext()};
753  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
754  EngineModifier modifier(engine.get());
755 
756  modifier.embedder_api().PostRenderThreadTask = MOCK_ENGINE_PROC(
757  PostRenderThreadTask, ([](auto engine, auto callback, auto context) {
758  callback(context);
759  return kSuccess;
760  }));
761 
762  bool called = false;
763  engine->PostRasterThreadTask([&called]() { called = true; });
764 
765  EXPECT_TRUE(called);
766 }
767 
768 class MockFlutterWindowsView : public FlutterWindowsView {
769  public:
771  std::unique_ptr<WindowBindingHandler> wbh)
773  engine,
774  std::move(wbh),
775  false,
776  BoxConstraints()) {}
778 
780  NotifyWinEventWrapper,
781  (ui::AXPlatformNodeWin*, ax::mojom::Event),
782  (override));
783  MOCK_METHOD(HWND, GetWindowHandle, (), (const, override));
784  MOCK_METHOD(bool, Focus, (), (override));
785 
786  private:
787  FML_DISALLOW_COPY_AND_ASSIGN(MockFlutterWindowsView);
788 };
789 
790 // Verify the view is notified of accessibility announcements.
791 TEST_F(FlutterWindowsEngineTest, AccessibilityAnnouncement) {
792  auto& context = GetContext();
793  WindowsConfigBuilder builder{context};
794  builder.SetDartEntrypoint("sendAccessibilityAnnouncement");
795 
796  bool done = false;
797  auto native_entry =
798  CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { done = true; });
799  context.AddNativeFunction("Signal", native_entry);
800 
801  EnginePtr engine{builder.RunHeadless()};
802  ASSERT_NE(engine, nullptr);
803 
804  ui::AXPlatformNodeDelegateBase parent_delegate;
805  AlertPlatformNodeDelegate delegate{parent_delegate};
806 
807  auto window_binding_handler =
808  std::make_unique<NiceMock<MockWindowBindingHandler>>();
809  EXPECT_CALL(*window_binding_handler, GetAlertDelegate)
810  .WillOnce(Return(&delegate));
811 
812  auto windows_engine = reinterpret_cast<FlutterWindowsEngine*>(engine.get());
813  MockFlutterWindowsView view{windows_engine,
814  std::move(window_binding_handler)};
815  EngineModifier modifier{windows_engine};
816  modifier.SetImplicitView(&view);
817 
818  windows_engine->UpdateSemanticsEnabled(true);
819 
820  EXPECT_CALL(view, NotifyWinEventWrapper).Times(1);
821 
822  // Rely on timeout mechanism in CI.
823  while (!done) {
824  windows_engine->task_runner()->ProcessTasks();
825  }
826 }
827 
828 // Verify the app can send accessibility announcements while in headless mode.
829 TEST_F(FlutterWindowsEngineTest, AccessibilityAnnouncementHeadless) {
830  auto& context = GetContext();
831  WindowsConfigBuilder builder{context};
832  builder.SetDartEntrypoint("sendAccessibilityAnnouncement");
833 
834  bool done = false;
835  auto native_entry =
836  CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { done = true; });
837  context.AddNativeFunction("Signal", native_entry);
838 
839  EnginePtr engine{builder.RunHeadless()};
840  ASSERT_NE(engine, nullptr);
841 
842  auto windows_engine = reinterpret_cast<FlutterWindowsEngine*>(engine.get());
843  windows_engine->UpdateSemanticsEnabled(true);
844 
845  // Rely on timeout mechanism in CI.
846  while (!done) {
847  windows_engine->task_runner()->ProcessTasks();
848  }
849 }
850 
851 // Verify the engine does not crash if it receives an accessibility event
852 // it does not support yet.
853 TEST_F(FlutterWindowsEngineTest, AccessibilityTooltip) {
854  fml::testing::LogCapture log_capture;
855 
856  auto& context = GetContext();
857  WindowsConfigBuilder builder{context};
858  builder.SetDartEntrypoint("sendAccessibilityTooltipEvent");
859 
860  bool done = false;
861  auto native_entry =
862  CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { done = true; });
863  context.AddNativeFunction("Signal", native_entry);
864 
865  ViewControllerPtr controller{builder.Run()};
866  ASSERT_NE(controller, nullptr);
867 
868  auto engine = FlutterDesktopViewControllerGetEngine(controller.get());
869  auto windows_engine = reinterpret_cast<FlutterWindowsEngine*>(engine);
870  windows_engine->UpdateSemanticsEnabled(true);
871 
872  // Rely on timeout mechanism in CI.
873  while (!done) {
874  windows_engine->task_runner()->ProcessTasks();
875  }
876 
877  // Verify no error was logged.
878  // Regression test for:
879  // https://github.com/flutter/flutter/issues/144274
880  EXPECT_EQ(log_capture.str().find("tooltip"), std::string::npos);
881 }
882 
884  public:
886  : WindowsLifecycleManager(engine) {}
888 
890  void,
891  Quit,
892  (std::optional<HWND>, std::optional<WPARAM>, std::optional<LPARAM>, UINT),
893  (override));
894  MOCK_METHOD(void, DispatchMessage, (HWND, UINT, WPARAM, LPARAM), (override));
895  MOCK_METHOD(bool, IsLastWindowOfProcess, (), (override));
897 
898  void BeginProcessingLifecycle() override {
902  }
903  }
904 
905  std::function<void()> begin_processing_callback = nullptr;
906 };
907 
909  FlutterWindowsEngineBuilder builder{GetContext()};
910  builder.SetDartEntrypoint("exitTestExit");
911  bool finished = false;
912 
913  auto engine = builder.Build();
914  auto window_binding_handler =
915  std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
916  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));
917 
918  EngineModifier modifier(engine.get());
919  modifier.SetImplicitView(&view);
920  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
921  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
922  EXPECT_CALL(*handler, Quit)
923  .WillOnce([&finished](std::optional<HWND> hwnd,
924  std::optional<WPARAM> wparam,
925  std::optional<LPARAM> lparam,
926  UINT exit_code) { finished = exit_code == 0; });
927  EXPECT_CALL(*handler, IsLastWindowOfProcess).WillRepeatedly(Return(true));
928  modifier.SetLifecycleManager(std::move(handler));
929 
930  engine->lifecycle_manager()->BeginProcessingExit();
931 
932  engine->Run();
933 
934  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(0, WM_CLOSE, 0,
935  0);
936 
937  // The test will only succeed when this while loop exits. Otherwise it will
938  // timeout.
939  while (!finished) {
940  engine->task_runner()->ProcessTasks();
941  }
942 }
943 
945  FlutterWindowsEngineBuilder builder{GetContext()};
946  builder.SetDartEntrypoint("exitTestCancel");
947  bool did_call = false;
948 
949  auto engine = builder.Build();
950  auto window_binding_handler =
951  std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
952  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));
953 
954  EngineModifier modifier(engine.get());
955  modifier.SetImplicitView(&view);
956  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
957  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
958  EXPECT_CALL(*handler, IsLastWindowOfProcess).WillRepeatedly(Return(true));
959  EXPECT_CALL(*handler, Quit).Times(0);
960  modifier.SetLifecycleManager(std::move(handler));
961  engine->lifecycle_manager()->BeginProcessingExit();
962 
963  auto binary_messenger =
964  std::make_unique<BinaryMessengerImpl>(engine->messenger());
965  binary_messenger->SetMessageHandler(
966  "flutter/platform", [&did_call](const uint8_t* message,
967  size_t message_size, BinaryReply reply) {
968  std::string contents(message, message + message_size);
969  EXPECT_NE(contents.find("\"method\":\"System.exitApplication\""),
970  std::string::npos);
971  EXPECT_NE(contents.find("\"type\":\"required\""), std::string::npos);
972  EXPECT_NE(contents.find("\"exitCode\":0"), std::string::npos);
973  did_call = true;
974  char response[] = "";
975  reply(reinterpret_cast<uint8_t*>(response), 0);
976  });
977 
978  engine->Run();
979 
980  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(0, WM_CLOSE, 0,
981  0);
982 
983  while (!did_call) {
984  engine->task_runner()->ProcessTasks();
985  }
986 }
987 
988 // Flutter consumes the first WM_CLOSE message to allow the app to cancel the
989 // exit. If the app does not cancel the exit, Flutter synthesizes a second
990 // WM_CLOSE message.
991 TEST_F(FlutterWindowsEngineTest, TestExitSecondCloseMessage) {
992  FlutterWindowsEngineBuilder builder{GetContext()};
993  builder.SetDartEntrypoint("exitTestExit");
994  bool second_close = false;
995 
996  auto engine = builder.Build();
997  auto window_binding_handler =
998  std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
999  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));
1000 
1001  EngineModifier modifier(engine.get());
1002  modifier.SetImplicitView(&view);
1003  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
1004  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
1005  EXPECT_CALL(*handler, IsLastWindowOfProcess).WillOnce(Return(true));
1006  EXPECT_CALL(*handler, Quit)
1007  .WillOnce([handler_ptr = handler.get()](
1008  std::optional<HWND> hwnd, std::optional<WPARAM> wparam,
1009  std::optional<LPARAM> lparam, UINT exit_code) {
1010  handler_ptr->WindowsLifecycleManager::Quit(hwnd, wparam, lparam,
1011  exit_code);
1012  });
1013  EXPECT_CALL(*handler, DispatchMessage)
1014  .WillRepeatedly(
1015  [&engine](HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
1016  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(
1017  hwnd, msg, wparam, lparam);
1018  });
1019  modifier.SetLifecycleManager(std::move(handler));
1020  engine->lifecycle_manager()->BeginProcessingExit();
1021 
1022  engine->Run();
1023 
1024  // This delegate will be registered after the lifecycle manager, so it will be
1025  // called only when a message is not consumed by the lifecycle manager. This
1026  // should be called on the second, synthesized WM_CLOSE message that the
1027  // lifecycle manager posts.
1028  engine->window_proc_delegate_manager()->RegisterTopLevelWindowProcDelegate(
1029  [](HWND hwnd, UINT message, WPARAM wpar, LPARAM lpar, void* user_data,
1030  LRESULT* result) {
1031  switch (message) {
1032  case WM_CLOSE: {
1033  bool* called = reinterpret_cast<bool*>(user_data);
1034  *called = true;
1035  return true;
1036  }
1037  }
1038  return false;
1039  },
1040  reinterpret_cast<void*>(&second_close));
1041 
1042  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(0, WM_CLOSE, 0,
1043  0);
1044 
1045  while (!second_close) {
1046  engine->task_runner()->ProcessTasks();
1047  }
1048 }
1049 
1050 TEST_F(FlutterWindowsEngineTest, TestExitCloseMultiWindow) {
1051  FlutterWindowsEngineBuilder builder{GetContext()};
1052  builder.SetDartEntrypoint("exitTestExit");
1053  bool finished = false;
1054 
1055  auto engine = builder.Build();
1056  auto window_binding_handler =
1057  std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
1058  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));
1059 
1060  EngineModifier modifier(engine.get());
1061  modifier.SetImplicitView(&view);
1062  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
1063  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
1064  EXPECT_CALL(*handler, IsLastWindowOfProcess).WillOnce([&finished]() {
1065  finished = true;
1066  return false;
1067  });
1068  // Quit should not be called when there is more than one window.
1069  EXPECT_CALL(*handler, Quit).Times(0);
1070  modifier.SetLifecycleManager(std::move(handler));
1071  engine->lifecycle_manager()->BeginProcessingExit();
1072 
1073  engine->Run();
1074 
1075  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(0, WM_CLOSE, 0,
1076  0);
1077 
1078  while (!finished) {
1079  engine->task_runner()->ProcessTasks();
1080  }
1081 }
1082 
1083 TEST_F(FlutterWindowsEngineTest, LifecycleManagerDisabledByDefault) {
1084  FlutterWindowsEngineBuilder builder{GetContext()};
1085 
1086  auto engine = builder.Build();
1087  auto window_binding_handler =
1088  std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
1089  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));
1090 
1091  EngineModifier modifier(engine.get());
1092  modifier.SetImplicitView(&view);
1093  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
1094  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
1095  EXPECT_CALL(*handler, IsLastWindowOfProcess).Times(0);
1096  modifier.SetLifecycleManager(std::move(handler));
1097 
1098  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(0, WM_CLOSE, 0,
1099  0);
1100 }
1101 
1102 TEST_F(FlutterWindowsEngineTest, EnableApplicationLifecycle) {
1103  FlutterWindowsEngineBuilder builder{GetContext()};
1104 
1105  auto engine = builder.Build();
1106  auto window_binding_handler =
1107  std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
1108  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));
1109 
1110  EngineModifier modifier(engine.get());
1111  modifier.SetImplicitView(&view);
1112  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
1113  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
1114  EXPECT_CALL(*handler, IsLastWindowOfProcess).WillOnce(Return(false));
1115  modifier.SetLifecycleManager(std::move(handler));
1116  engine->lifecycle_manager()->BeginProcessingExit();
1117 
1118  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(0, WM_CLOSE, 0,
1119  0);
1120 }
1121 
1122 TEST_F(FlutterWindowsEngineTest, ApplicationLifecycleExternalWindow) {
1123  FlutterWindowsEngineBuilder builder{GetContext()};
1124 
1125  auto engine = builder.Build();
1126  auto window_binding_handler =
1127  std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
1128  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));
1129 
1130  EngineModifier modifier(engine.get());
1131  modifier.SetImplicitView(&view);
1132  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
1133  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
1134  EXPECT_CALL(*handler, IsLastWindowOfProcess).WillOnce(Return(false));
1135  modifier.SetLifecycleManager(std::move(handler));
1136  engine->lifecycle_manager()->BeginProcessingExit();
1137 
1138  engine->lifecycle_manager()->ExternalWindowMessage(0, WM_CLOSE, 0, 0);
1139 }
1140 
1141 TEST_F(FlutterWindowsEngineTest, LifecycleStateTransition) {
1142  FlutterWindowsEngineBuilder builder{GetContext()};
1143 
1144  auto engine = builder.Build();
1145  auto window_binding_handler =
1146  std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
1147  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));
1148 
1149  EngineModifier modifier(engine.get());
1150  modifier.SetImplicitView(&view);
1151  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
1152  engine->Run();
1153 
1154  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(
1155  (HWND)1, WM_SIZE, SIZE_RESTORED, 0);
1156 
1157  while (engine->lifecycle_manager()->IsUpdateStateScheduled()) {
1158  PumpMessage();
1159  }
1160 
1161  EXPECT_EQ(engine->lifecycle_manager()->GetLifecycleState(),
1163 
1164  engine->lifecycle_manager()->OnWindowStateEvent((HWND)1,
1166 
1167  while (engine->lifecycle_manager()->IsUpdateStateScheduled()) {
1168  PumpMessage();
1169  }
1170 
1171  EXPECT_EQ(engine->lifecycle_manager()->GetLifecycleState(),
1173 
1174  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(
1175  (HWND)1, WM_SIZE, SIZE_MINIMIZED, 0);
1176 
1177  while (engine->lifecycle_manager()->IsUpdateStateScheduled()) {
1178  PumpMessage();
1179  }
1180 
1181  EXPECT_EQ(engine->lifecycle_manager()->GetLifecycleState(),
1183 
1184  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(
1185  (HWND)1, WM_SIZE, SIZE_RESTORED, 0);
1186 
1187  while (engine->lifecycle_manager()->IsUpdateStateScheduled()) {
1188  PumpMessage();
1189  }
1190 
1191  EXPECT_EQ(engine->lifecycle_manager()->GetLifecycleState(),
1193 }
1194 
1195 TEST_F(FlutterWindowsEngineTest, ExternalWindowMessage) {
1196  FlutterWindowsEngineBuilder builder{GetContext()};
1197 
1198  auto engine = builder.Build();
1199  auto window_binding_handler =
1200  std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
1201  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));
1202 
1203  EngineModifier modifier(engine.get());
1204  modifier.SetImplicitView(&view);
1205  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
1206  // Sets lifecycle state to resumed.
1207  engine->Run();
1208 
1209  // Ensure HWND(1) is in the set of visible windows before hiding it.
1210  engine->ProcessExternalWindowMessage(reinterpret_cast<HWND>(1), WM_SHOWWINDOW,
1211  TRUE, NULL);
1212  engine->ProcessExternalWindowMessage(reinterpret_cast<HWND>(1), WM_SHOWWINDOW,
1213  FALSE, NULL);
1214 
1215  while (engine->lifecycle_manager()->IsUpdateStateScheduled()) {
1216  PumpMessage();
1217  }
1218 
1219  EXPECT_EQ(engine->lifecycle_manager()->GetLifecycleState(),
1221 }
1222 
1223 TEST_F(FlutterWindowsEngineTest, InnerWindowHidden) {
1224  FlutterWindowsEngineBuilder builder{GetContext()};
1225  HWND outer = reinterpret_cast<HWND>(1);
1226  HWND inner = reinterpret_cast<HWND>(2);
1227 
1228  auto engine = builder.Build();
1229  auto window_binding_handler =
1230  std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
1231  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));
1232  ON_CALL(view, GetWindowHandle).WillByDefault([=]() { return inner; });
1233 
1234  EngineModifier modifier(engine.get());
1235  modifier.SetImplicitView(&view);
1236  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
1237  // Sets lifecycle state to resumed.
1238  engine->Run();
1239 
1240  // Show both top-level and Flutter window.
1241  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(
1242  outer, WM_SHOWWINDOW, TRUE, NULL);
1243  view.OnWindowStateEvent(inner, WindowStateEvent::kShow);
1244  view.OnWindowStateEvent(inner, WindowStateEvent::kFocus);
1245 
1246  while (engine->lifecycle_manager()->IsUpdateStateScheduled()) {
1247  PumpMessage();
1248  }
1249 
1250  EXPECT_EQ(engine->lifecycle_manager()->GetLifecycleState(),
1252 
1253  // Hide Flutter window, but not top level window.
1254  view.OnWindowStateEvent(inner, WindowStateEvent::kHide);
1255 
1256  while (engine->lifecycle_manager()->IsUpdateStateScheduled()) {
1257  PumpMessage();
1258  }
1259 
1260  // The top-level window is still visible, so we ought not enter hidden state.
1261  EXPECT_EQ(engine->lifecycle_manager()->GetLifecycleState(),
1263 }
1264 
1265 TEST_F(FlutterWindowsEngineTest, EnableLifecycleState) {
1266  FlutterWindowsEngineBuilder builder{GetContext()};
1267  builder.SetDartEntrypoint("enableLifecycleTest");
1268  bool finished = false;
1269 
1270  auto engine = builder.Build();
1271  auto window_binding_handler =
1272  std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
1273  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));
1274 
1275  EngineModifier modifier(engine.get());
1276  modifier.SetImplicitView(&view);
1277  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
1278  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
1279  EXPECT_CALL(*handler, SetLifecycleState)
1280  .WillRepeatedly([handler_ptr = handler.get()](AppLifecycleState state) {
1281  handler_ptr->WindowsLifecycleManager::SetLifecycleState(state);
1282  });
1283  modifier.SetLifecycleManager(std::move(handler));
1284 
1285  auto binary_messenger =
1286  std::make_unique<BinaryMessengerImpl>(engine->messenger());
1287  // Mark the test only as completed on receiving an inactive state message.
1288  binary_messenger->SetMessageHandler(
1289  "flutter/unittest", [&finished](const uint8_t* message,
1290  size_t message_size, BinaryReply reply) {
1291  std::string contents(message, message + message_size);
1292  EXPECT_NE(contents.find("AppLifecycleState.inactive"),
1293  std::string::npos);
1294  finished = true;
1295  char response[] = "";
1296  reply(reinterpret_cast<uint8_t*>(response), 0);
1297  });
1298 
1299  engine->Run();
1300 
1301  // Test that setting the state before enabling lifecycle does nothing.
1302  HWND hwnd = reinterpret_cast<HWND>(1);
1303  view.OnWindowStateEvent(hwnd, WindowStateEvent::kShow);
1304  view.OnWindowStateEvent(hwnd, WindowStateEvent::kHide);
1305  EXPECT_FALSE(finished);
1306 
1307  // Test that we can set the state afterwards.
1308 
1309  engine->lifecycle_manager()->BeginProcessingLifecycle();
1310  view.OnWindowStateEvent(hwnd, WindowStateEvent::kShow);
1311 
1312  while (!finished) {
1313  engine->task_runner()->ProcessTasks();
1314  }
1315 }
1316 
1317 TEST_F(FlutterWindowsEngineTest, LifecycleStateToFrom) {
1318  FlutterWindowsEngineBuilder builder{GetContext()};
1319  builder.SetDartEntrypoint("enableLifecycleToFrom");
1320  bool enabled_lifecycle = false;
1321  bool dart_responded = false;
1322 
1323  auto engine = builder.Build();
1324  auto window_binding_handler =
1325  std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
1326  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));
1327 
1328  EngineModifier modifier(engine.get());
1329  modifier.SetImplicitView(&view);
1330  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
1331  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
1332  EXPECT_CALL(*handler, SetLifecycleState)
1333  .WillRepeatedly([handler_ptr = handler.get()](AppLifecycleState state) {
1334  handler_ptr->WindowsLifecycleManager::SetLifecycleState(state);
1335  });
1336  handler->begin_processing_callback = [&]() { enabled_lifecycle = true; };
1337  modifier.SetLifecycleManager(std::move(handler));
1338 
1339  auto binary_messenger =
1340  std::make_unique<BinaryMessengerImpl>(engine->messenger());
1341  binary_messenger->SetMessageHandler(
1342  "flutter/unittest",
1343  [&](const uint8_t* message, size_t message_size, BinaryReply reply) {
1344  std::string contents(message, message + message_size);
1345  EXPECT_NE(contents.find("AppLifecycleState."), std::string::npos);
1346  dart_responded = true;
1347  char response[] = "";
1348  reply(reinterpret_cast<uint8_t*>(response), 0);
1349  });
1350 
1351  engine->Run();
1352 
1353  while (!enabled_lifecycle) {
1354  engine->task_runner()->ProcessTasks();
1355  }
1356 
1357  HWND hwnd = reinterpret_cast<HWND>(1);
1358  view.OnWindowStateEvent(hwnd, WindowStateEvent::kShow);
1359  view.OnWindowStateEvent(hwnd, WindowStateEvent::kHide);
1360 
1361  while (!dart_responded) {
1362  engine->task_runner()->ProcessTasks();
1363  }
1364 }
1365 
1366 TEST_F(FlutterWindowsEngineTest, ChannelListenedTo) {
1367  FlutterWindowsEngineBuilder builder{GetContext()};
1368  builder.SetDartEntrypoint("enableLifecycleToFrom");
1369 
1370  auto engine = builder.Build();
1371  auto window_binding_handler =
1372  std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
1373  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));
1374 
1375  EngineModifier modifier(engine.get());
1376  modifier.SetImplicitView(&view);
1377  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
1378 
1379  bool lifecycle_began = false;
1380  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
1381  handler->begin_processing_callback = [&]() { lifecycle_began = true; };
1382  modifier.SetLifecycleManager(std::move(handler));
1383 
1384  engine->Run();
1385 
1386  while (!lifecycle_began) {
1387  engine->task_runner()->ProcessTasks();
1388  }
1389 }
1390 
1391 TEST_F(FlutterWindowsEngineTest, ReceivePlatformViewMessage) {
1392  FlutterWindowsEngineBuilder builder{GetContext()};
1393  builder.SetDartEntrypoint("sendCreatePlatformViewMethod");
1394  auto engine = builder.Build();
1395 
1396  EngineModifier modifier{engine.get()};
1397  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
1398 
1399  bool received_call = false;
1400 
1401  auto manager = std::make_unique<MockPlatformViewManager>(engine.get());
1402  EXPECT_CALL(*manager, AddPlatformView)
1403  .WillOnce([&](PlatformViewId id, std::string_view type_name) {
1404  received_call = true;
1405  return true;
1406  });
1407  modifier.SetPlatformViewPlugin(std::move(manager));
1408 
1409  engine->Run();
1410 
1411  while (!received_call) {
1412  engine->task_runner()->ProcessTasks();
1413  }
1414 }
1415 
1416 TEST_F(FlutterWindowsEngineTest, AddViewFailureDoesNotHang) {
1417  FlutterWindowsEngineBuilder builder{GetContext()};
1418  auto engine = builder.Build();
1419 
1420  EngineModifier modifier{engine.get()};
1421 
1422  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
1423  modifier.embedder_api().AddView = MOCK_ENGINE_PROC(
1424  AddView,
1425  [](FLUTTER_API_SYMBOL(FlutterEngine) engine,
1426  const FlutterAddViewInfo* info) { return kInternalInconsistency; });
1427 
1428  ASSERT_TRUE(engine->Run());
1429 
1430  // Create the first view. This is the implicit view and isn't added to the
1431  // engine.
1432  auto implicit_window = std::make_unique<NiceMock<MockWindowBindingHandler>>();
1433 
1434  std::unique_ptr<FlutterWindowsView> implicit_view =
1435  engine->CreateView(std::move(implicit_window),
1436  /*is_sized_to_content=*/false, BoxConstraints());
1437 
1438  EXPECT_TRUE(implicit_view);
1439 
1440  // Create a second view. The embedder attempts to add it to the engine.
1441  auto second_window = std::make_unique<NiceMock<MockWindowBindingHandler>>();
1442 
1443  EXPECT_DEBUG_DEATH(
1444  engine->CreateView(std::move(second_window),
1445  /*is_sized_to_content=*/false, BoxConstraints()),
1446  "FlutterEngineAddView returned an unexpected result");
1447 }
1448 
1449 TEST_F(FlutterWindowsEngineTest, RemoveViewFailureDoesNotHang) {
1450  FlutterWindowsEngineBuilder builder{GetContext()};
1451  builder.SetDartEntrypoint("sendCreatePlatformViewMethod");
1452  auto engine = builder.Build();
1453 
1454  EngineModifier modifier{engine.get()};
1455 
1456  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
1457  modifier.embedder_api().RemoveView = MOCK_ENGINE_PROC(
1458  RemoveView,
1459  [](FLUTTER_API_SYMBOL(FlutterEngine) engine,
1460  const FlutterRemoveViewInfo* info) { return kInternalInconsistency; });
1461 
1462  ASSERT_TRUE(engine->Run());
1463  EXPECT_DEBUG_DEATH(engine->RemoveView(123),
1464  "FlutterEngineRemoveView returned an unexpected result");
1465 }
1466 
1468  auto& context = GetContext();
1469  WindowsConfigBuilder builder{context};
1470  builder.SetDartEntrypoint("mergedUIThread");
1471  builder.SetUIThreadPolicy(FlutterDesktopUIThreadPolicy::RunOnPlatformThread);
1472 
1473  std::optional<std::thread::id> ui_thread_id;
1474 
1475  auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
1476  ui_thread_id = std::this_thread::get_id();
1477  });
1478  context.AddNativeFunction("Signal", native_entry);
1479 
1480  EnginePtr engine{builder.RunHeadless()};
1481  while (!ui_thread_id) {
1482  PumpMessage();
1483  }
1484  ASSERT_EQ(*ui_thread_id, std::this_thread::get_id());
1485 }
1486 
1487 TEST_F(FlutterWindowsEngineTest, OnViewFocusChangeRequest) {
1488  FlutterWindowsEngineBuilder builder{GetContext()};
1489  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
1490  auto window_binding_handler =
1491  std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
1492  MockFlutterWindowsView view(engine.get(), std::move(window_binding_handler));
1493 
1494  EngineModifier modifier(engine.get());
1495  modifier.SetImplicitView(&view);
1496 
1497  FlutterViewFocusChangeRequest request;
1498  request.view_id = kImplicitViewId;
1499 
1500  EXPECT_CALL(view, Focus()).WillOnce(Return(true));
1501  modifier.OnViewFocusChangeRequest(&request);
1502 }
1503 
1504 TEST_F(FlutterWindowsEngineTest, UpdateSemanticsMultiView) {
1505  auto& context = GetContext();
1506  WindowsConfigBuilder builder{context};
1507  builder.SetDartEntrypoint("sendSemanticsTreeInfo");
1508 
1509  // Setup: a signal for when we have send out all of our semantics updates
1510  bool done = false;
1511  auto native_entry =
1512  CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { done = true; });
1513  context.AddNativeFunction("Signal", native_entry);
1514 
1515  // Setup: Create the engine and two views + enable semantics
1516  EnginePtr engine{builder.RunHeadless()};
1517  ASSERT_NE(engine, nullptr);
1518 
1519  auto window_binding_handler1 =
1520  std::make_unique<NiceMock<MockWindowBindingHandler>>();
1521  auto window_binding_handler2 =
1522  std::make_unique<NiceMock<MockWindowBindingHandler>>();
1523 
1524  // The following mocks are required by
1525  // FlutterWindowsView::CreateWindowMetricsEvent so that we create a valid
1526  // view.
1527  EXPECT_CALL(*window_binding_handler1, GetPhysicalWindowBounds)
1528  .WillRepeatedly(testing::Return(PhysicalWindowBounds{100, 100}));
1529  EXPECT_CALL(*window_binding_handler1, GetDpiScale)
1530  .WillRepeatedly(testing::Return(96.0));
1531  EXPECT_CALL(*window_binding_handler2, GetPhysicalWindowBounds)
1532  .WillRepeatedly(testing::Return(PhysicalWindowBounds{200, 200}));
1533  EXPECT_CALL(*window_binding_handler2, GetDpiScale)
1534  .WillRepeatedly(testing::Return(96.0));
1535 
1536  auto windows_engine = reinterpret_cast<FlutterWindowsEngine*>(engine.get());
1537  EngineModifier modifier{windows_engine};
1538  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
1539 
1540  // We want to avoid adding an implicit view as the first view
1541  modifier.SetNextViewId(kImplicitViewId + 1);
1542 
1543  auto view1 = windows_engine->CreateView(std::move(window_binding_handler1),
1544  /*is_sized_to_content=*/false,
1545  BoxConstraints());
1546  auto view2 = windows_engine->CreateView(std::move(window_binding_handler2),
1547  /*is_sized_to_content=*/false,
1548  BoxConstraints());
1549 
1550  // Act: UpdateSemanticsEnabled will trigger the semantics updates
1551  // to get sent.
1552  windows_engine->UpdateSemanticsEnabled(true);
1553 
1554  while (!done) {
1555  windows_engine->task_runner()->ProcessTasks();
1556  }
1557 
1558  auto accessibility_bridge1 = view1->accessibility_bridge().lock();
1559  auto accessibility_bridge2 = view2->accessibility_bridge().lock();
1560 
1561  // Expect: that the semantics trees are updated with their
1562  // respective nodes.
1563  while (
1564  !accessibility_bridge1->GetPlatformNodeFromTree(view1->view_id() + 1)) {
1565  windows_engine->task_runner()->ProcessTasks();
1566  }
1567 
1568  while (
1569  !accessibility_bridge2->GetPlatformNodeFromTree(view2->view_id() + 1)) {
1570  windows_engine->task_runner()->ProcessTasks();
1571  }
1572 
1573  // Rely on timeout mechanism in CI.
1574  auto tree1 = accessibility_bridge1->GetTree();
1575  auto tree2 = accessibility_bridge2->GetTree();
1576  EXPECT_NE(tree1->GetFromId(view1->view_id() + 1), nullptr);
1577  EXPECT_NE(tree2->GetFromId(view2->view_id() + 1), nullptr);
1578 }
1579 
1580 } // namespace testing
1581 } // namespace flutter
std::unique_ptr< FlutterWindowsView > CreateView(std::unique_ptr< WindowBindingHandler > window, bool is_sized_to_content, const BoxConstraints &box_constraints, FlutterWindowsViewSizingDelegate *sizing_delegate=nullptr)
virtual void Quit(std::optional< HWND > window, std::optional< WPARAM > wparam, std::optional< LPARAM > lparam, UINT exit_code)
virtual void DispatchMessage(HWND window, UINT msg, WPARAM wparam, LPARAM lparam)
virtual void SetLifecycleState(AppLifecycleState state)
MOCK_METHOD(bool, Focus,(),(override))
MOCK_METHOD(void, NotifyWinEventWrapper,(ui::AXPlatformNodeWin *, ax::mojom::Event),(override))
MockFlutterWindowsView(FlutterWindowsEngine *engine, std::unique_ptr< WindowBindingHandler > wbh)
MOCK_METHOD(HWND, GetWindowHandle,(),(const, override))
MOCK_METHOD(void, DispatchMessage,(HWND, UINT, WPARAM, LPARAM),(override))
MOCK_METHOD(void, Quit,(std::optional< HWND >, std::optional< WPARAM >, std::optional< LPARAM >, UINT),(override))
MOCK_METHOD(void, SetLifecycleState,(AppLifecycleState),(override))
MOCK_METHOD(bool, IsLastWindowOfProcess,(),(override))
void(* FlutterDesktopBinaryReply)(const uint8_t *data, size_t data_size, void *user_data)
FlutterDesktopEngineRef FlutterDesktopViewControllerGetEngine(FlutterDesktopViewControllerRef ref)
@ RunOnPlatformThread
int64_t PlatformViewId
FlutterDesktopBinaryReply callback
Win32Message message
TEST_F(AccessibilityPluginTest, DirectAnnounceCall)
UINT GetDpiForMonitor(HMONITOR monitor)
Definition: dpi_utils.cc:132
std::function< void(const uint8_t *reply, size_t reply_size)> BinaryReply
static void WindowsPlatformThreadPrioritySetter(FlutterThreadPriority priority)
constexpr FlutterViewId kImplicitViewId