Flutter Windows Embedder
flutter_windows_view_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 
7 #include <UIAutomation.h>
8 #include <comdef.h>
9 #include <comutil.h>
10 #include <oleacc.h>
11 
12 #include <future>
13 #include <vector>
14 
15 #include "flutter/fml/logging.h"
16 #include "flutter/fml/synchronization/waitable_event.h"
18 #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
23 #include "flutter/shell/platform/windows/testing/egl/mock_context.h"
24 #include "flutter/shell/platform/windows/testing/egl/mock_manager.h"
25 #include "flutter/shell/platform/windows/testing/egl/mock_window_surface.h"
26 #include "flutter/shell/platform/windows/testing/engine_modifier.h"
27 #include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h"
28 #include "flutter/shell/platform/windows/testing/mock_windows_proc_table.h"
29 #include "flutter/shell/platform/windows/testing/test_keyboard.h"
30 #include "flutter/shell/platform/windows/testing/view_modifier.h"
31 
32 #include "gmock/gmock.h"
33 #include "gtest/gtest.h"
34 
35 namespace flutter {
36 namespace testing {
37 
38 using ::testing::_;
39 using ::testing::InSequence;
40 using ::testing::NiceMock;
41 using ::testing::Return;
42 
43 constexpr uint64_t kScanCodeKeyA = 0x1e;
44 constexpr uint64_t kVirtualKeyA = 0x41;
45 
46 namespace {
47 
48 // A struct to use as a FlutterPlatformMessageResponseHandle so it can keep the
49 // callbacks and user data passed to the engine's
50 // PlatformMessageCreateResponseHandle for use in the SendPlatformMessage
51 // overridden function.
52 struct TestResponseHandle {
54  void* user_data;
55 };
56 
57 static bool test_response = false;
58 
59 constexpr uint64_t kKeyEventFromChannel = 0x11;
60 constexpr uint64_t kKeyEventFromEmbedder = 0x22;
61 static std::vector<int> key_event_logs;
62 
63 std::unique_ptr<std::vector<uint8_t>> keyHandlingResponse(bool handled) {
64  rapidjson::Document document;
65  auto& allocator = document.GetAllocator();
66  document.SetObject();
67  document.AddMember("handled", test_response, allocator);
69 }
70 
71 // Returns a Flutter project with the required path values to create
72 // a test engine.
73 FlutterProjectBundle GetTestProject() {
74  FlutterDesktopEngineProperties properties = {};
75  properties.assets_path = L"C:\\foo\\flutter_assets";
76  properties.icu_data_path = L"C:\\foo\\icudtl.dat";
77  properties.aot_library_path = L"C:\\foo\\aot.so";
78  properties.impeller_switch = DefaultImpeller;
79 
80  return FlutterProjectBundle{properties};
81 }
82 
83 class ViewTestEGLManager : public egl::MockManager {
84  public:
85  ViewTestEGLManager() {
86  ON_CALL(*this, CreateWindowSurface).WillByDefault([](HWND, size_t, size_t) {
87  auto surface = std::make_unique<NiceMock<egl::MockWindowSurface>>();
88  ON_CALL(*surface, IsValid).WillByDefault(Return(true));
89  ON_CALL(*surface, MakeCurrent).WillByDefault(Return(true));
90  ON_CALL(*surface, SetVSyncEnabled).WillByDefault(Return(true));
91  ON_CALL(*surface, Destroy).WillByDefault(Return(true));
92  return surface;
93  });
94  ON_CALL(*this, render_context).WillByDefault(Return(&mock_context_));
95  ON_CALL(*this, resource_context).WillByDefault(Return(&mock_context_));
96  ON_CALL(mock_context_, ClearCurrent).WillByDefault(Return(true));
97  ON_CALL(mock_context_, MakeCurrent).WillByDefault(Return(true));
98  }
99 
100  private:
101  NiceMock<egl::MockContext> mock_context_;
102 };
103 
104 // Returns an engine instance configured with test project path values, and
105 // overridden methods for sending platform messages, so that the engine can
106 // respond as if the framework were connected.
107 std::unique_ptr<FlutterWindowsEngine> GetTestEngine(
108  std::shared_ptr<WindowsProcTable> windows_proc_table = nullptr) {
109  auto engine = std::make_unique<FlutterWindowsEngine>(
110  GetTestProject(), std::move(windows_proc_table));
111 
112  EngineModifier modifier(engine.get());
113  modifier.SetEGLManager(std::make_unique<NiceMock<ViewTestEGLManager>>());
114  modifier.embedder_api().Run = MOCK_ENGINE_PROC(
115  Run, ([](size_t version, const FlutterRendererConfig* config,
116  const FlutterProjectArgs* args, void* user_data,
117  FLUTTER_API_SYMBOL(FlutterEngine) * engine) {
118  *engine =
119  reinterpret_cast<FLUTTER_API_SYMBOL(FlutterEngine)>(0x12345678);
120  return kSuccess;
121  }));
122 
123  modifier.embedder_api().Shutdown = MOCK_ENGINE_PROC(
124  Shutdown,
125  ([](FLUTTER_API_SYMBOL(FlutterEngine) engine) { return kSuccess; }));
126 
127  modifier.embedder_api().UpdateLocales = MOCK_ENGINE_PROC(
128  UpdateLocales, ([](FLUTTER_API_SYMBOL(FlutterEngine) engine,
129  const FlutterLocale** locales,
130  size_t locales_count) { return kSuccess; }));
131 
132  modifier.embedder_api().UpdateAccessibilityFeatures = MOCK_ENGINE_PROC(
133  UpdateAccessibilityFeatures,
134  ([](FLUTTER_API_SYMBOL(FlutterEngine) engine,
135  FlutterAccessibilityFeature flags) { return kSuccess; }));
136 
137  modifier.embedder_api().PostRenderThreadTask = MOCK_ENGINE_PROC(
138  PostRenderThreadTask, ([](auto engine, auto callback, auto context) {
139  callback(context);
140  return kSuccess;
141  }));
142 
143  auto key_response_controller = std::make_shared<MockKeyResponseController>();
144  key_response_controller->SetChannelResponse(
145  [](MockKeyResponseController::ResponseCallback callback) {
146  key_event_logs.push_back(kKeyEventFromChannel);
147  callback(test_response);
148  });
149  key_response_controller->SetEmbedderResponse(
150  [](const FlutterKeyEvent* event,
151  MockKeyResponseController::ResponseCallback callback) {
152  key_event_logs.push_back(kKeyEventFromEmbedder);
153  callback(test_response);
154  });
155  modifier.embedder_api().NotifyDisplayUpdate =
156  MOCK_ENGINE_PROC(NotifyDisplayUpdate,
157  ([engine_instance = engine.get()](
158  FLUTTER_API_SYMBOL(FlutterEngine) raw_engine,
159  const FlutterEngineDisplaysUpdateType update_type,
160  const FlutterEngineDisplay* embedder_displays,
161  size_t display_count) { return kSuccess; }));
162 
163  MockEmbedderApiForKeyboard(modifier, key_response_controller);
164 
165  engine->Run();
166  return engine;
167 }
168 
169 class MockFlutterWindowsEngine : public FlutterWindowsEngine {
170  public:
171  explicit MockFlutterWindowsEngine(
172  std::shared_ptr<WindowsProcTable> windows_proc_table = nullptr)
173  : FlutterWindowsEngine(GetTestProject(), std::move(windows_proc_table)) {}
174 
175  MOCK_METHOD(bool, running, (), (const));
176  MOCK_METHOD(bool, Stop, (), ());
177  MOCK_METHOD(void, RemoveView, (FlutterViewId view_id), ());
178  MOCK_METHOD(bool, PostRasterThreadTask, (fml::closure), (const));
179 
180  private:
181  FML_DISALLOW_COPY_AND_ASSIGN(MockFlutterWindowsEngine);
182 };
183 
184 } // namespace
185 
186 // Ensure that submenu buttons have their expanded/collapsed status set
187 // apropriately.
188 TEST(FlutterWindowsViewTest, SubMenuExpandedState) {
189  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
190  EngineModifier modifier(engine.get());
191  modifier.embedder_api().UpdateSemanticsEnabled =
192  [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
193  return kSuccess;
194  };
195 
196  auto window_binding_handler =
197  std::make_unique<NiceMock<MockWindowBindingHandler>>();
198  std::unique_ptr<FlutterWindowsView> view =
199  engine->CreateView(std::move(window_binding_handler),
200  /*is_sized_to_content=*/false, BoxConstraints());
201 
202  // Enable semantics to instantiate accessibility bridge.
203  view->OnUpdateSemanticsEnabled(true);
204 
205  auto bridge = view->accessibility_bridge().lock();
206  ASSERT_TRUE(bridge);
207 
208  FlutterSemanticsNode2 root{sizeof(FlutterSemanticsNode2), 0};
209  root.id = 0;
210  root.label = "root";
211  root.hint = "";
212  root.value = "";
213  root.increased_value = "";
214  root.decreased_value = "";
215  root.child_count = 0;
216  root.custom_accessibility_actions_count = 0;
217  auto flags = FlutterSemanticsFlags{
218  .is_expanded = FlutterTristate::kFlutterTristateTrue,
219  };
220  root.flags2 = &flags;
221 
222  bridge->AddFlutterSemanticsNodeUpdate(root);
223 
224  bridge->CommitUpdates();
225 
226  {
227  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
228  EXPECT_TRUE(root_node->GetData().HasState(ax::mojom::State::kExpanded));
229 
230  // Get the IAccessible for the root node.
231  IAccessible* native_view = root_node->GetNativeViewAccessible();
232  ASSERT_TRUE(native_view != nullptr);
233 
234  // Look up against the node itself (not one of its children).
235  VARIANT varchild = {};
236  varchild.vt = VT_I4;
237 
238  // Verify the submenu is expanded.
239  varchild.lVal = CHILDID_SELF;
240  VARIANT native_state = {};
241  ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
242  EXPECT_TRUE(native_state.lVal & STATE_SYSTEM_EXPANDED);
243 
244  // Perform similar tests for UIA value;
245  IRawElementProviderSimple* uia_node;
246  native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
247  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
248  UIA_ExpandCollapseExpandCollapseStatePropertyId, &native_state)));
249  EXPECT_EQ(native_state.lVal, ExpandCollapseState_Expanded);
250 
251  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
252  UIA_AriaPropertiesPropertyId, &native_state)));
253  EXPECT_NE(std::wcsstr(native_state.bstrVal, L"expanded=true"), nullptr);
254  }
255 
256  // Test collapsed too.
257  auto updated_flags = FlutterSemanticsFlags{
258  .is_expanded = FlutterTristate::kFlutterTristateFalse,
259  };
260  root.flags2 = &updated_flags;
261 
262  bridge->AddFlutterSemanticsNodeUpdate(root);
263  bridge->CommitUpdates();
264 
265  {
266  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
267  EXPECT_TRUE(root_node->GetData().HasState(ax::mojom::State::kCollapsed));
268 
269  // Get the IAccessible for the root node.
270  IAccessible* native_view = root_node->GetNativeViewAccessible();
271  ASSERT_TRUE(native_view != nullptr);
272 
273  // Look up against the node itself (not one of its children).
274  VARIANT varchild = {};
275  varchild.vt = VT_I4;
276 
277  // Verify the submenu is collapsed.
278  varchild.lVal = CHILDID_SELF;
279  VARIANT native_state = {};
280  ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
281  EXPECT_TRUE(native_state.lVal & STATE_SYSTEM_COLLAPSED);
282 
283  // Perform similar tests for UIA value;
284  IRawElementProviderSimple* uia_node;
285  native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
286  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
287  UIA_ExpandCollapseExpandCollapseStatePropertyId, &native_state)));
288  EXPECT_EQ(native_state.lVal, ExpandCollapseState_Collapsed);
289 
290  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
291  UIA_AriaPropertiesPropertyId, &native_state)));
292  EXPECT_NE(std::wcsstr(native_state.bstrVal, L"expanded=false"), nullptr);
293  }
294 }
295 
296 // The view's surface must be destroyed after the engine is shutdown.
297 // See: https://github.com/flutter/flutter/issues/124463
298 TEST(FlutterWindowsViewTest, Shutdown) {
299  auto engine = std::make_unique<MockFlutterWindowsEngine>();
300  auto window_binding_handler =
301  std::make_unique<NiceMock<MockWindowBindingHandler>>();
302  auto egl_manager = std::make_unique<egl::MockManager>();
303  auto surface = std::make_unique<egl::MockWindowSurface>();
304  egl::MockContext render_context;
305 
306  auto engine_ptr = engine.get();
307  auto surface_ptr = surface.get();
308  auto egl_manager_ptr = egl_manager.get();
309 
310  EngineModifier modifier{engine.get()};
311  modifier.SetEGLManager(std::move(egl_manager));
312 
313  InSequence s;
314  std::unique_ptr<FlutterWindowsView> view;
315 
316  // Mock render surface initialization.
317  {
318  EXPECT_CALL(*egl_manager_ptr, CreateWindowSurface)
319  .WillOnce(Return(std::move(surface)));
320  EXPECT_CALL(*engine_ptr, running).WillOnce(Return(false));
321  EXPECT_CALL(*surface_ptr, IsValid).WillOnce(Return(true));
322  EXPECT_CALL(*surface_ptr, MakeCurrent).WillOnce(Return(true));
323  EXPECT_CALL(*surface_ptr, SetVSyncEnabled).WillOnce(Return(true));
324  EXPECT_CALL(*egl_manager_ptr, render_context)
325  .WillOnce(Return(&render_context));
326  EXPECT_CALL(render_context, ClearCurrent).WillOnce(Return(true));
327 
328  view = engine->CreateView(std::move(window_binding_handler), false,
329  BoxConstraints());
330  }
331 
332  // The view must be removed before the surface can be destroyed.
333  {
334  auto view_id = view->view_id();
335  FlutterWindowsViewController controller{std::move(engine), std::move(view)};
336 
337  EXPECT_CALL(*engine_ptr, running).WillOnce(Return(true));
338  EXPECT_CALL(*engine_ptr, RemoveView(view_id)).Times(1);
339  EXPECT_CALL(*engine_ptr, running).WillOnce(Return(true));
340  EXPECT_CALL(*engine_ptr, PostRasterThreadTask)
341  .WillOnce([](fml::closure callback) {
342  callback();
343  return true;
344  });
345  EXPECT_CALL(*surface_ptr, Destroy).Times(1);
346  }
347 }
348 
349 TEST(FlutterWindowsViewTest, KeySequence) {
350  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
351 
352  test_response = false;
353 
354  std::unique_ptr<FlutterWindowsView> view =
355  engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
356  /*is_sized_to_content=*/false, BoxConstraints());
357 
358  view->OnKey(kVirtualKeyA, kScanCodeKeyA, WM_KEYDOWN, 'a', false, false,
359  [](bool handled) {});
360 
361  EXPECT_EQ(key_event_logs.size(), 2);
362  EXPECT_EQ(key_event_logs[0], kKeyEventFromEmbedder);
363  EXPECT_EQ(key_event_logs[1], kKeyEventFromChannel);
364 
365  key_event_logs.clear();
366 }
367 
368 TEST(FlutterWindowsViewTest, KeyEventCallback) {
369  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
370 
371  std::unique_ptr<FlutterWindowsView> view =
372  engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
373  /*is_sized_to_content=*/false, BoxConstraints());
374 
375  class MockCallback {
376  public:
377  MOCK_METHOD(void, Call, ());
378  };
379 
380  NiceMock<MockCallback> callback_with_valid_view;
381  NiceMock<MockCallback> callback_with_invalid_view;
382 
383  auto trigger_key_event = [&](NiceMock<MockCallback>& callback) {
384  view->OnKey(kVirtualKeyA, kScanCodeKeyA, WM_KEYDOWN, 'a', false, false,
385  [&](bool) { callback.Call(); });
386  };
387 
388  EXPECT_CALL(callback_with_valid_view, Call()).Times(1);
389  EXPECT_CALL(callback_with_invalid_view, Call()).Times(0);
390 
391  trigger_key_event(callback_with_valid_view);
392  engine->RemoveView(view->view_id());
393  trigger_key_event(callback_with_invalid_view);
394 
395  key_event_logs.clear();
396 }
397 
398 TEST(FlutterWindowsViewTest, EnableSemantics) {
399  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
400  EngineModifier modifier(engine.get());
401 
402  bool semantics_enabled = false;
403  modifier.embedder_api().UpdateSemanticsEnabled = MOCK_ENGINE_PROC(
404  UpdateSemanticsEnabled,
405  [&semantics_enabled](FLUTTER_API_SYMBOL(FlutterEngine) engine,
406  bool enabled) {
407  semantics_enabled = enabled;
408  return kSuccess;
409  });
410 
411  auto window_binding_handler =
412  std::make_unique<NiceMock<MockWindowBindingHandler>>();
413  std::unique_ptr<FlutterWindowsView> view =
414  engine->CreateView(std::move(window_binding_handler),
415  /*is_sized_to_content=*/false, BoxConstraints());
416 
417  view->OnUpdateSemanticsEnabled(true);
418  EXPECT_TRUE(semantics_enabled);
419 }
420 
421 TEST(FlutterWindowsViewTest, AddSemanticsNodeUpdate) {
422  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
423  EngineModifier modifier(engine.get());
424  modifier.embedder_api().UpdateSemanticsEnabled =
425  [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
426  return kSuccess;
427  };
428 
429  auto window_binding_handler =
430  std::make_unique<NiceMock<MockWindowBindingHandler>>();
431  std::unique_ptr<FlutterWindowsView> view =
432  engine->CreateView(std::move(window_binding_handler),
433  /*is_sized_to_content=*/false, BoxConstraints());
434 
435  // Enable semantics to instantiate accessibility bridge.
436  view->OnUpdateSemanticsEnabled(true);
437 
438  auto bridge = view->accessibility_bridge().lock();
439  ASSERT_TRUE(bridge);
440 
441  // Add root node.
442  FlutterSemanticsNode2 node{sizeof(FlutterSemanticsNode2), 0};
443  node.label = "name";
444  node.value = "value";
445  node.platform_view_id = -1;
446  auto flags = FlutterSemanticsFlags{};
447  node.flags2 = &flags;
448  bridge->AddFlutterSemanticsNodeUpdate(node);
449  bridge->CommitUpdates();
450 
451  // Look up the root windows node delegate.
452  auto node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
453  ASSERT_TRUE(node_delegate);
454  EXPECT_EQ(node_delegate->GetChildCount(), 0);
455 
456  // Get the native IAccessible object.
457  IAccessible* native_view = node_delegate->GetNativeViewAccessible();
458  ASSERT_TRUE(native_view != nullptr);
459 
460  // Property lookups will be made against this node itself.
461  VARIANT varchild{};
462  varchild.vt = VT_I4;
463  varchild.lVal = CHILDID_SELF;
464 
465  // Verify node name matches our label.
466  BSTR bname = nullptr;
467  ASSERT_EQ(native_view->get_accName(varchild, &bname), S_OK);
468  std::string name(_com_util::ConvertBSTRToString(bname));
469  EXPECT_EQ(name, "name");
470 
471  // Verify node value matches.
472  BSTR bvalue = nullptr;
473  ASSERT_EQ(native_view->get_accValue(varchild, &bvalue), S_OK);
474  std::string value(_com_util::ConvertBSTRToString(bvalue));
475  EXPECT_EQ(value, "value");
476 
477  // Verify node type is static text.
478  VARIANT varrole{};
479  varrole.vt = VT_I4;
480  ASSERT_EQ(native_view->get_accRole(varchild, &varrole), S_OK);
481  EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_STATICTEXT);
482 
483  // Get the IRawElementProviderFragment object.
484  IRawElementProviderSimple* uia_view;
485  native_view->QueryInterface(IID_PPV_ARGS(&uia_view));
486  ASSERT_TRUE(uia_view != nullptr);
487 
488  // Verify name property matches our label.
489  VARIANT varname{};
490  ASSERT_EQ(uia_view->GetPropertyValue(UIA_NamePropertyId, &varname), S_OK);
491  EXPECT_EQ(varname.vt, VT_BSTR);
492  name = _com_util::ConvertBSTRToString(varname.bstrVal);
493  EXPECT_EQ(name, "name");
494 
495  // Verify value property matches our label.
496  VARIANT varvalue{};
497  ASSERT_EQ(uia_view->GetPropertyValue(UIA_ValueValuePropertyId, &varvalue),
498  S_OK);
499  EXPECT_EQ(varvalue.vt, VT_BSTR);
500  value = _com_util::ConvertBSTRToString(varvalue.bstrVal);
501  EXPECT_EQ(value, "value");
502 
503  // Verify node control type is text.
504  varrole = {};
505  ASSERT_EQ(uia_view->GetPropertyValue(UIA_ControlTypePropertyId, &varrole),
506  S_OK);
507  EXPECT_EQ(varrole.vt, VT_I4);
508  EXPECT_EQ(varrole.lVal, UIA_TextControlTypeId);
509 }
510 
511 // Verify the native IAccessible COM object tree is an accurate reflection of
512 // the platform-agnostic tree. Verify both a root node with children as well as
513 // a non-root node with children, since the AX tree includes special handling
514 // for the root.
515 //
516 // node0
517 // / \
518 // node1 node2
519 // |
520 // node3
521 //
522 // node0 and node2 are grouping nodes. node1 and node2 are static text nodes.
523 TEST(FlutterWindowsViewTest, AddSemanticsNodeUpdateWithChildren) {
524  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
525  EngineModifier modifier(engine.get());
526  modifier.embedder_api().UpdateSemanticsEnabled =
527  [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
528  return kSuccess;
529  };
530 
531  std::unique_ptr<FlutterWindowsView> view =
532  engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
533  /*is_sized_to_content=*/false, BoxConstraints());
534 
535  // Enable semantics to instantiate accessibility bridge.
536  view->OnUpdateSemanticsEnabled(true);
537 
538  auto bridge = view->accessibility_bridge().lock();
539  ASSERT_TRUE(bridge);
540 
541  // Add root node.
542  FlutterSemanticsNode2 node0{sizeof(FlutterSemanticsNode2), 0};
543  std::vector<int32_t> node0_children{1, 2};
544  node0.child_count = node0_children.size();
545  node0.children_in_traversal_order = node0_children.data();
546  node0.children_in_hit_test_order = node0_children.data();
547  auto empty_flags = FlutterSemanticsFlags{};
548  node0.flags2 = &empty_flags;
549 
550  FlutterSemanticsNode2 node1{sizeof(FlutterSemanticsNode2), 1};
551  node1.label = "prefecture";
552  node1.value = "Kyoto";
553  node1.flags2 = &empty_flags;
554  FlutterSemanticsNode2 node2{sizeof(FlutterSemanticsNode2), 2};
555  std::vector<int32_t> node2_children{3};
556  node2.child_count = node2_children.size();
557  node2.children_in_traversal_order = node2_children.data();
558  node2.children_in_hit_test_order = node2_children.data();
559  node2.flags2 = &empty_flags;
560  FlutterSemanticsNode2 node3{sizeof(FlutterSemanticsNode2), 3};
561  node3.label = "city";
562  node3.value = "Uji";
563  node3.flags2 = &empty_flags;
564 
565  bridge->AddFlutterSemanticsNodeUpdate(node0);
566  bridge->AddFlutterSemanticsNodeUpdate(node1);
567  bridge->AddFlutterSemanticsNodeUpdate(node2);
568  bridge->AddFlutterSemanticsNodeUpdate(node3);
569  bridge->CommitUpdates();
570 
571  // Look up the root windows node delegate.
572  auto node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
573  ASSERT_TRUE(node_delegate);
574  EXPECT_EQ(node_delegate->GetChildCount(), 2);
575 
576  // Get the native IAccessible object.
577  IAccessible* node0_accessible = node_delegate->GetNativeViewAccessible();
578  ASSERT_TRUE(node0_accessible != nullptr);
579 
580  // Property lookups will be made against this node itself.
581  VARIANT varchild{};
582  varchild.vt = VT_I4;
583  varchild.lVal = CHILDID_SELF;
584 
585  // Verify node type is a group.
586  VARIANT varrole{};
587  varrole.vt = VT_I4;
588  ASSERT_EQ(node0_accessible->get_accRole(varchild, &varrole), S_OK);
589  EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_GROUPING);
590 
591  // Verify child count.
592  long node0_child_count = 0;
593  ASSERT_EQ(node0_accessible->get_accChildCount(&node0_child_count), S_OK);
594  EXPECT_EQ(node0_child_count, 2);
595 
596  {
597  // Look up first child of node0 (node1), a static text node.
598  varchild.lVal = 1;
599  IDispatch* node1_dispatch = nullptr;
600  ASSERT_EQ(node0_accessible->get_accChild(varchild, &node1_dispatch), S_OK);
601  ASSERT_TRUE(node1_dispatch != nullptr);
602  IAccessible* node1_accessible = nullptr;
603  ASSERT_EQ(node1_dispatch->QueryInterface(
604  IID_IAccessible, reinterpret_cast<void**>(&node1_accessible)),
605  S_OK);
606  ASSERT_TRUE(node1_accessible != nullptr);
607 
608  // Verify node name matches our label.
609  varchild.lVal = CHILDID_SELF;
610  BSTR bname = nullptr;
611  ASSERT_EQ(node1_accessible->get_accName(varchild, &bname), S_OK);
612  std::string name(_com_util::ConvertBSTRToString(bname));
613  EXPECT_EQ(name, "prefecture");
614 
615  // Verify node value matches.
616  BSTR bvalue = nullptr;
617  ASSERT_EQ(node1_accessible->get_accValue(varchild, &bvalue), S_OK);
618  std::string value(_com_util::ConvertBSTRToString(bvalue));
619  EXPECT_EQ(value, "Kyoto");
620 
621  // Verify node type is static text.
622  VARIANT varrole{};
623  varrole.vt = VT_I4;
624  ASSERT_EQ(node1_accessible->get_accRole(varchild, &varrole), S_OK);
625  EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_STATICTEXT);
626 
627  // Verify the parent node is the root.
628  IDispatch* parent_dispatch;
629  node1_accessible->get_accParent(&parent_dispatch);
630  IAccessible* parent_accessible;
631  ASSERT_EQ(
632  parent_dispatch->QueryInterface(
633  IID_IAccessible, reinterpret_cast<void**>(&parent_accessible)),
634  S_OK);
635  EXPECT_EQ(parent_accessible, node0_accessible);
636  }
637 
638  // Look up second child of node0 (node2), a parent group for node3.
639  varchild.lVal = 2;
640  IDispatch* node2_dispatch = nullptr;
641  ASSERT_EQ(node0_accessible->get_accChild(varchild, &node2_dispatch), S_OK);
642  ASSERT_TRUE(node2_dispatch != nullptr);
643  IAccessible* node2_accessible = nullptr;
644  ASSERT_EQ(node2_dispatch->QueryInterface(
645  IID_IAccessible, reinterpret_cast<void**>(&node2_accessible)),
646  S_OK);
647  ASSERT_TRUE(node2_accessible != nullptr);
648 
649  {
650  // Verify child count.
651  long node2_child_count = 0;
652  ASSERT_EQ(node2_accessible->get_accChildCount(&node2_child_count), S_OK);
653  EXPECT_EQ(node2_child_count, 1);
654 
655  // Verify node type is static text.
656  varchild.lVal = CHILDID_SELF;
657  VARIANT varrole{};
658  varrole.vt = VT_I4;
659  ASSERT_EQ(node2_accessible->get_accRole(varchild, &varrole), S_OK);
660  EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_GROUPING);
661 
662  // Verify the parent node is the root.
663  IDispatch* parent_dispatch;
664  node2_accessible->get_accParent(&parent_dispatch);
665  IAccessible* parent_accessible;
666  ASSERT_EQ(
667  parent_dispatch->QueryInterface(
668  IID_IAccessible, reinterpret_cast<void**>(&parent_accessible)),
669  S_OK);
670  EXPECT_EQ(parent_accessible, node0_accessible);
671  }
672 
673  {
674  // Look up only child of node2 (node3), a static text node.
675  varchild.lVal = 1;
676  IDispatch* node3_dispatch = nullptr;
677  ASSERT_EQ(node2_accessible->get_accChild(varchild, &node3_dispatch), S_OK);
678  ASSERT_TRUE(node3_dispatch != nullptr);
679  IAccessible* node3_accessible = nullptr;
680  ASSERT_EQ(node3_dispatch->QueryInterface(
681  IID_IAccessible, reinterpret_cast<void**>(&node3_accessible)),
682  S_OK);
683  ASSERT_TRUE(node3_accessible != nullptr);
684 
685  // Verify node name matches our label.
686  varchild.lVal = CHILDID_SELF;
687  BSTR bname = nullptr;
688  ASSERT_EQ(node3_accessible->get_accName(varchild, &bname), S_OK);
689  std::string name(_com_util::ConvertBSTRToString(bname));
690  EXPECT_EQ(name, "city");
691 
692  // Verify node value matches.
693  BSTR bvalue = nullptr;
694  ASSERT_EQ(node3_accessible->get_accValue(varchild, &bvalue), S_OK);
695  std::string value(_com_util::ConvertBSTRToString(bvalue));
696  EXPECT_EQ(value, "Uji");
697 
698  // Verify node type is static text.
699  VARIANT varrole{};
700  varrole.vt = VT_I4;
701  ASSERT_EQ(node3_accessible->get_accRole(varchild, &varrole), S_OK);
702  EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_STATICTEXT);
703 
704  // Verify the parent node is node2.
705  IDispatch* parent_dispatch;
706  node3_accessible->get_accParent(&parent_dispatch);
707  IAccessible* parent_accessible;
708  ASSERT_EQ(
709  parent_dispatch->QueryInterface(
710  IID_IAccessible, reinterpret_cast<void**>(&parent_accessible)),
711  S_OK);
712  EXPECT_EQ(parent_accessible, node2_accessible);
713  }
714 }
715 
716 // Flutter used to assume that the accessibility root had ID 0.
717 // In a multi-view world, each view has its own accessibility root
718 // with a globally unique node ID.
719 //
720 // node1
721 // |
722 // node2
723 //
724 // node1 is a grouping node, node0 is a static text node.
725 TEST(FlutterWindowsViewTest, NonZeroSemanticsRoot) {
726  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
727  EngineModifier modifier(engine.get());
728  modifier.embedder_api().UpdateSemanticsEnabled =
729  [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
730  return kSuccess;
731  };
732 
733  std::unique_ptr<FlutterWindowsView> view =
734  engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
735  /*is_sized_to_content=*/false, BoxConstraints());
736 
737  // Enable semantics to instantiate accessibility bridge.
738  view->OnUpdateSemanticsEnabled(true);
739 
740  auto bridge = view->accessibility_bridge().lock();
741  ASSERT_TRUE(bridge);
742 
743  // Add root node.
744  FlutterSemanticsNode2 node1{sizeof(FlutterSemanticsNode2), 1};
745  std::vector<int32_t> node1_children{2};
746  node1.child_count = node1_children.size();
747  node1.children_in_traversal_order = node1_children.data();
748  node1.children_in_hit_test_order = node1_children.data();
749  auto empty_flags = FlutterSemanticsFlags{};
750  node1.flags2 = &empty_flags;
751 
752  FlutterSemanticsNode2 node2{sizeof(FlutterSemanticsNode2), 2};
753  node2.label = "prefecture";
754  node2.value = "Kyoto";
755  node2.flags2 = &empty_flags;
756  bridge->AddFlutterSemanticsNodeUpdate(node1);
757  bridge->AddFlutterSemanticsNodeUpdate(node2);
758  bridge->CommitUpdates();
759 
760  // Look up the root windows node delegate.
761  auto root_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock();
762  ASSERT_TRUE(root_delegate);
763  EXPECT_EQ(root_delegate->GetChildCount(), 1);
764 
765  // Look up the child node delegate
766  auto child_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(2).lock();
767  ASSERT_TRUE(child_delegate);
768  EXPECT_EQ(child_delegate->GetChildCount(), 0);
769 
770  // Ensure a node with ID 0 does not exist.
771  auto fake_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
772  ASSERT_FALSE(fake_delegate);
773 
774  // Get the root's native IAccessible object.
775  IAccessible* node1_accessible = root_delegate->GetNativeViewAccessible();
776  ASSERT_TRUE(node1_accessible != nullptr);
777 
778  // Property lookups will be made against this node itself.
779  VARIANT varchild{};
780  varchild.vt = VT_I4;
781  varchild.lVal = CHILDID_SELF;
782 
783  // Verify node type is a group.
784  VARIANT varrole{};
785  varrole.vt = VT_I4;
786  ASSERT_EQ(node1_accessible->get_accRole(varchild, &varrole), S_OK);
787  EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_GROUPING);
788 
789  // Verify child count.
790  long node1_child_count = 0;
791  ASSERT_EQ(node1_accessible->get_accChildCount(&node1_child_count), S_OK);
792  EXPECT_EQ(node1_child_count, 1);
793 
794  {
795  // Look up first child of node1 (node0), a static text node.
796  varchild.lVal = 1;
797  IDispatch* node2_dispatch = nullptr;
798  ASSERT_EQ(node1_accessible->get_accChild(varchild, &node2_dispatch), S_OK);
799  ASSERT_TRUE(node2_dispatch != nullptr);
800  IAccessible* node2_accessible = nullptr;
801  ASSERT_EQ(node2_dispatch->QueryInterface(
802  IID_IAccessible, reinterpret_cast<void**>(&node2_accessible)),
803  S_OK);
804  ASSERT_TRUE(node2_accessible != nullptr);
805 
806  // Verify node name matches our label.
807  varchild.lVal = CHILDID_SELF;
808  BSTR bname = nullptr;
809  ASSERT_EQ(node2_accessible->get_accName(varchild, &bname), S_OK);
810  std::string name(_com_util::ConvertBSTRToString(bname));
811  EXPECT_EQ(name, "prefecture");
812 
813  // Verify node value matches.
814  BSTR bvalue = nullptr;
815  ASSERT_EQ(node2_accessible->get_accValue(varchild, &bvalue), S_OK);
816  std::string value(_com_util::ConvertBSTRToString(bvalue));
817  EXPECT_EQ(value, "Kyoto");
818 
819  // Verify node type is static text.
820  VARIANT varrole{};
821  varrole.vt = VT_I4;
822  ASSERT_EQ(node2_accessible->get_accRole(varchild, &varrole), S_OK);
823  EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_STATICTEXT);
824 
825  // Verify the parent node is the root.
826  IDispatch* parent_dispatch;
827  node2_accessible->get_accParent(&parent_dispatch);
828  IAccessible* parent_accessible;
829  ASSERT_EQ(
830  parent_dispatch->QueryInterface(
831  IID_IAccessible, reinterpret_cast<void**>(&parent_accessible)),
832  S_OK);
833  EXPECT_EQ(parent_accessible, node1_accessible);
834  }
835 }
836 
837 // Verify the native IAccessible accHitTest method returns the correct
838 // IAccessible COM object for the given coordinates.
839 //
840 // +-----------+
841 // | | |
842 // node0 | | B |
843 // / \ | A |-----|
844 // node1 node2 | | C |
845 // | | | |
846 // node3 +-----------+
847 //
848 // node0 and node2 are grouping nodes. node1 and node2 are static text nodes.
849 //
850 // node0 is located at 0,0 with size 500x500. It spans areas A, B, and C.
851 // node1 is located at 0,0 with size 250x500. It spans area A.
852 // node2 is located at 250,0 with size 250x500. It spans areas B and C.
853 // node3 is located at 250,250 with size 250x250. It spans area C.
854 TEST(FlutterWindowsViewTest, AccessibilityHitTesting) {
855  constexpr FlutterTransformation kIdentityTransform = {1, 0, 0, //
856  0, 1, 0, //
857  0, 0, 1};
858 
859  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
860  EngineModifier modifier(engine.get());
861  modifier.embedder_api().UpdateSemanticsEnabled =
862  [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
863  return kSuccess;
864  };
865 
866  std::unique_ptr<FlutterWindowsView> view =
867  engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
868  /*is_sized_to_content=*/false, BoxConstraints());
869 
870  // Enable semantics to instantiate accessibility bridge.
871  view->OnUpdateSemanticsEnabled(true);
872 
873  auto bridge = view->accessibility_bridge().lock();
874  ASSERT_TRUE(bridge);
875 
876  // Add root node at origin. Size 500x500.
877  FlutterSemanticsNode2 node0{sizeof(FlutterSemanticsNode2), 0};
878  auto empty_flags = FlutterSemanticsFlags{};
879  std::vector<int32_t> node0_children{1, 2};
880  node0.rect = {0, 0, 500, 500};
881  node0.transform = kIdentityTransform;
882  node0.child_count = node0_children.size();
883  node0.children_in_traversal_order = node0_children.data();
884  node0.children_in_hit_test_order = node0_children.data();
885  node0.flags2 = &empty_flags;
886 
887  // Add node 1 located at 0,0 relative to node 0. Size 250x500.
888  FlutterSemanticsNode2 node1{sizeof(FlutterSemanticsNode2), 1};
889  node1.rect = {0, 0, 250, 500};
890  node1.transform = kIdentityTransform;
891  node1.label = "prefecture";
892  node1.value = "Kyoto";
893  node1.flags2 = &empty_flags;
894 
895  // Add node 2 located at 250,0 relative to node 0. Size 250x500.
896  FlutterSemanticsNode2 node2{sizeof(FlutterSemanticsNode2), 2};
897  std::vector<int32_t> node2_children{3};
898  node2.rect = {0, 0, 250, 500};
899  node2.transform = {1, 0, 250, 0, 1, 0, 0, 0, 1};
900  node2.child_count = node2_children.size();
901  node2.children_in_traversal_order = node2_children.data();
902  node2.children_in_hit_test_order = node2_children.data();
903  node2.flags2 = &empty_flags;
904 
905  // Add node 3 located at 0,250 relative to node 2. Size 250, 250.
906  FlutterSemanticsNode2 node3{sizeof(FlutterSemanticsNode2), 3};
907  node3.rect = {0, 0, 250, 250};
908  node3.transform = {1, 0, 0, 0, 1, 250, 0, 0, 1};
909  node3.label = "city";
910  node3.value = "Uji";
911  node3.flags2 = &empty_flags;
912 
913  bridge->AddFlutterSemanticsNodeUpdate(node0);
914  bridge->AddFlutterSemanticsNodeUpdate(node1);
915  bridge->AddFlutterSemanticsNodeUpdate(node2);
916  bridge->AddFlutterSemanticsNodeUpdate(node3);
917  bridge->CommitUpdates();
918 
919  // Look up the root windows node delegate.
920  auto node0_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
921  ASSERT_TRUE(node0_delegate);
922  auto node1_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock();
923  ASSERT_TRUE(node1_delegate);
924  auto node2_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(2).lock();
925  ASSERT_TRUE(node2_delegate);
926  auto node3_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(3).lock();
927  ASSERT_TRUE(node3_delegate);
928 
929  // Get the native IAccessible root object.
930  IAccessible* node0_accessible = node0_delegate->GetNativeViewAccessible();
931  ASSERT_TRUE(node0_accessible != nullptr);
932 
933  // Perform a hit test that should hit node 1.
934  VARIANT varchild{};
935  ASSERT_TRUE(SUCCEEDED(node0_accessible->accHitTest(150, 150, &varchild)));
936  EXPECT_EQ(varchild.vt, VT_DISPATCH);
937  EXPECT_EQ(varchild.pdispVal, node1_delegate->GetNativeViewAccessible());
938 
939  // Perform a hit test that should hit node 2.
940  varchild = {};
941  ASSERT_TRUE(SUCCEEDED(node0_accessible->accHitTest(450, 150, &varchild)));
942  EXPECT_EQ(varchild.vt, VT_DISPATCH);
943  EXPECT_EQ(varchild.pdispVal, node2_delegate->GetNativeViewAccessible());
944 
945  // Perform a hit test that should hit node 3.
946  varchild = {};
947  ASSERT_TRUE(SUCCEEDED(node0_accessible->accHitTest(450, 450, &varchild)));
948  EXPECT_EQ(varchild.vt, VT_DISPATCH);
949  EXPECT_EQ(varchild.pdispVal, node3_delegate->GetNativeViewAccessible());
950 }
951 
952 TEST(FlutterWindowsViewTest, WindowResizeTests) {
953  auto windows_proc_table = std::make_shared<NiceMock<MockWindowsProcTable>>();
954  std::unique_ptr<FlutterWindowsEngine> engine =
955  GetTestEngine(windows_proc_table);
956 
957  EngineModifier engine_modifier{engine.get()};
958  engine_modifier.embedder_api().PostRenderThreadTask = MOCK_ENGINE_PROC(
959  PostRenderThreadTask,
960  ([](auto engine, VoidCallback callback, void* user_data) {
962  return kSuccess;
963  }));
964 
965  auto egl_manager = std::make_unique<egl::MockManager>();
966  auto surface = std::make_unique<egl::MockWindowSurface>();
967  auto resized_surface = std::make_unique<egl::MockWindowSurface>();
968  egl::MockContext render_context;
969 
970  auto surface_ptr = surface.get();
971  auto resized_surface_ptr = resized_surface.get();
972 
973  // Mock render surface creation
974  EXPECT_CALL(*egl_manager, CreateWindowSurface)
975  .WillOnce(Return(std::move(surface)));
976  EXPECT_CALL(*surface_ptr, IsValid).WillRepeatedly(Return(true));
977  EXPECT_CALL(*surface_ptr, MakeCurrent).WillOnce(Return(true));
978  EXPECT_CALL(*surface_ptr, SetVSyncEnabled).WillOnce(Return(true));
979  EXPECT_CALL(*egl_manager, render_context).WillOnce(Return(&render_context));
980  EXPECT_CALL(render_context, ClearCurrent).WillOnce(Return(true));
981 
982  // Mock render surface resize
983  EXPECT_CALL(*surface_ptr, Destroy).WillOnce(Return(true));
984  EXPECT_CALL(*egl_manager.get(),
985  CreateWindowSurface(_, /*width=*/500, /*height=*/500))
986  .WillOnce(Return(std::move((resized_surface))));
987  EXPECT_CALL(*resized_surface_ptr, MakeCurrent).WillOnce(Return(true));
988  EXPECT_CALL(*resized_surface_ptr, SetVSyncEnabled).WillOnce(Return(true));
989  EXPECT_CALL(*windows_proc_table.get(), DwmFlush).WillOnce(Return(S_OK));
990 
991  EXPECT_CALL(*resized_surface_ptr, Destroy).WillOnce(Return(true));
992 
993  engine_modifier.SetEGLManager(std::move(egl_manager));
994 
995  std::unique_ptr<FlutterWindowsView> view =
996  engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
997  /*is_sized_to_content=*/false, BoxConstraints());
998 
999  fml::AutoResetWaitableEvent metrics_sent_latch;
1000  engine_modifier.embedder_api().SendWindowMetricsEvent = MOCK_ENGINE_PROC(
1001  SendWindowMetricsEvent,
1002  ([&metrics_sent_latch](auto engine,
1003  const FlutterWindowMetricsEvent* event) {
1004  metrics_sent_latch.Signal();
1005  return kSuccess;
1006  }));
1007 
1008  // Simulate raster thread.
1009  std::thread frame_thread([&metrics_sent_latch, &view]() {
1010  metrics_sent_latch.Wait();
1011  // Frame generated and presented from the raster thread.
1012  EXPECT_TRUE(view->OnFrameGenerated(500, 500));
1013  view->OnFramePresented();
1014  });
1015 
1016  // Start the window resize. This sends the new window metrics
1017  // and then blocks polling run loop until another thread completes the window
1018  // resize.
1019  EXPECT_TRUE(view->OnWindowSizeChanged(500, 500));
1020  frame_thread.join();
1021 }
1022 
1023 // Verify that an empty frame completes a view resize.
1024 TEST(FlutterWindowsViewTest, TestEmptyFrameResizes) {
1025  auto windows_proc_table = std::make_shared<NiceMock<MockWindowsProcTable>>();
1026  std::unique_ptr<FlutterWindowsEngine> engine =
1027  GetTestEngine(windows_proc_table);
1028 
1029  EngineModifier engine_modifier{engine.get()};
1030  engine_modifier.embedder_api().PostRenderThreadTask = MOCK_ENGINE_PROC(
1031  PostRenderThreadTask,
1032  ([](auto engine, VoidCallback callback, void* user_data) {
1034  return kSuccess;
1035  }));
1036 
1037  auto egl_manager = std::make_unique<egl::MockManager>();
1038  auto surface = std::make_unique<egl::MockWindowSurface>();
1039  auto resized_surface = std::make_unique<egl::MockWindowSurface>();
1040  auto resized_surface_ptr = resized_surface.get();
1041 
1042  EXPECT_CALL(*surface.get(), IsValid).WillRepeatedly(Return(true));
1043  EXPECT_CALL(*surface.get(), Destroy).WillOnce(Return(true));
1044 
1045  EXPECT_CALL(*egl_manager.get(),
1046  CreateWindowSurface(_, /*width=*/500, /*height=*/300))
1047  .WillOnce(Return(std::move((resized_surface))));
1048  EXPECT_CALL(*resized_surface_ptr, MakeCurrent).WillOnce(Return(true));
1049  EXPECT_CALL(*resized_surface_ptr, SetVSyncEnabled).WillOnce(Return(true));
1050  EXPECT_CALL(*windows_proc_table.get(), DwmFlush).WillOnce(Return(S_OK));
1051 
1052  EXPECT_CALL(*resized_surface_ptr, Destroy).WillOnce(Return(true));
1053 
1054  fml::AutoResetWaitableEvent metrics_sent_latch;
1055  engine_modifier.embedder_api().SendWindowMetricsEvent = MOCK_ENGINE_PROC(
1056  SendWindowMetricsEvent,
1057  ([&metrics_sent_latch](auto engine,
1058  const FlutterWindowMetricsEvent* event) {
1059  metrics_sent_latch.Signal();
1060  return kSuccess;
1061  }));
1062 
1063  std::unique_ptr<FlutterWindowsView> view =
1064  engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1065  /*is_sized_to_content=*/false, BoxConstraints());
1066 
1067  ViewModifier view_modifier{view.get()};
1068  engine_modifier.SetEGLManager(std::move(egl_manager));
1069  view_modifier.SetSurface(std::move(surface));
1070 
1071  // Simulate raster thread.
1072  std::thread frame_thread([&metrics_sent_latch, &view]() {
1073  metrics_sent_latch.Wait();
1074 
1075  // Empty frame generated and presented from the raster thread.
1076  EXPECT_TRUE(view->OnEmptyFrameGenerated());
1077  view->OnFramePresented();
1078  });
1079 
1080  // Start the window resize. This sends the new window metrics
1081  // and then blocks until another thread completes the window resize.
1082  EXPECT_TRUE(view->OnWindowSizeChanged(500, 300));
1083  frame_thread.join();
1084 }
1085 
1086 // A window resize can be interleaved between a frame generation and
1087 // presentation. This should not crash the app. Regression test for:
1088 // https://github.com/flutter/flutter/issues/141855
1089 TEST(FlutterWindowsViewTest, WindowResizeRace) {
1090  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1091 
1092  EngineModifier engine_modifier(engine.get());
1093  engine_modifier.embedder_api().PostRenderThreadTask = MOCK_ENGINE_PROC(
1094  PostRenderThreadTask,
1095  ([](auto engine, VoidCallback callback, void* user_data) {
1097  return kSuccess;
1098  }));
1099 
1100  auto egl_manager = std::make_unique<egl::MockManager>();
1101  auto surface = std::make_unique<egl::MockWindowSurface>();
1102 
1103  EXPECT_CALL(*surface.get(), IsValid).WillRepeatedly(Return(true));
1104  EXPECT_CALL(*surface.get(), Destroy).WillOnce(Return(true));
1105 
1106  std::unique_ptr<FlutterWindowsView> view =
1107  engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1108  /*is_sized_to_content=*/false, BoxConstraints());
1109 
1110  ViewModifier view_modifier{view.get()};
1111  engine_modifier.SetEGLManager(std::move(egl_manager));
1112  view_modifier.SetSurface(std::move(surface));
1113 
1114  // Begin a frame.
1115  ASSERT_TRUE(view->OnFrameGenerated(100, 100));
1116 
1117  // Inject a window resize between the frame generation and
1118  // frame presentation. The new size invalidates the current frame.
1119  EXPECT_FALSE(view->OnWindowSizeChanged(500, 500));
1120 
1121  // Complete the invalidated frame while a resize is pending. Although this
1122  // might mean that we presented a frame with the wrong size, this should not
1123  // crash the app.
1124  view->OnFramePresented();
1125 }
1126 
1127 // Window resize should succeed even if the render surface could not be created
1128 // even though EGL initialized successfully.
1129 TEST(FlutterWindowsViewTest, WindowResizeInvalidSurface) {
1130  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1131 
1132  EngineModifier engine_modifier(engine.get());
1133  engine_modifier.embedder_api().PostRenderThreadTask = MOCK_ENGINE_PROC(
1134  PostRenderThreadTask,
1135  ([](auto engine, VoidCallback callback, void* user_data) {
1137  return kSuccess;
1138  }));
1139 
1140  auto egl_manager = std::make_unique<egl::MockManager>();
1141  auto surface = std::make_unique<egl::MockWindowSurface>();
1142 
1143  EXPECT_CALL(*egl_manager.get(), CreateWindowSurface).Times(0);
1144  EXPECT_CALL(*surface.get(), IsValid).WillRepeatedly(Return(false));
1145  EXPECT_CALL(*surface.get(), Destroy).WillOnce(Return(false));
1146 
1147  std::unique_ptr<FlutterWindowsView> view =
1148  engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1149  /*is_sized_to_content=*/false, BoxConstraints());
1150 
1151  ViewModifier view_modifier{view.get()};
1152  engine_modifier.SetEGLManager(std::move(egl_manager));
1153  view_modifier.SetSurface(std::move(surface));
1154 
1155  auto metrics_sent = false;
1156  engine_modifier.embedder_api().SendWindowMetricsEvent = MOCK_ENGINE_PROC(
1157  SendWindowMetricsEvent,
1158  ([&metrics_sent](auto engine, const FlutterWindowMetricsEvent* event) {
1159  metrics_sent = true;
1160  return kSuccess;
1161  }));
1162 
1163  view->OnWindowSizeChanged(500, 500);
1164 }
1165 
1166 // Window resize should succeed even if EGL initialized successfully
1167 // but the EGL surface could not be created.
1168 TEST(FlutterWindowsViewTest, WindowResizeWithoutSurface) {
1169  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1170  EngineModifier modifier(engine.get());
1171 
1172  auto egl_manager = std::make_unique<egl::MockManager>();
1173 
1174  EXPECT_CALL(*egl_manager.get(), CreateWindowSurface).Times(0);
1175 
1176  std::unique_ptr<FlutterWindowsView> view =
1177  engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1178  /*is_sized_to_content=*/false, BoxConstraints());
1179 
1180  modifier.SetEGLManager(std::move(egl_manager));
1181 
1182  auto metrics_sent = false;
1183  modifier.embedder_api().SendWindowMetricsEvent = MOCK_ENGINE_PROC(
1184  SendWindowMetricsEvent,
1185  ([&metrics_sent](auto engine, const FlutterWindowMetricsEvent* event) {
1186  metrics_sent = true;
1187  return kSuccess;
1188  }));
1189 
1190  view->OnWindowSizeChanged(500, 500);
1191 }
1192 
1193 TEST(FlutterWindowsViewTest, WindowRepaintTests) {
1194  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1195  EngineModifier modifier(engine.get());
1196 
1197  FlutterWindowsView view{kImplicitViewId, engine.get(),
1198  std::make_unique<flutter::FlutterWindow>(
1199  100, 100, engine->display_manager()),
1200  /*is_sized_to_content=*/false, BoxConstraints()};
1201 
1202  bool schedule_frame_called = false;
1203  modifier.embedder_api().ScheduleFrame =
1204  MOCK_ENGINE_PROC(ScheduleFrame, ([&schedule_frame_called](auto engine) {
1205  schedule_frame_called = true;
1206  return kSuccess;
1207  }));
1208 
1209  view.OnWindowRepaint();
1210  EXPECT_TRUE(schedule_frame_called);
1211 }
1212 
1213 // Ensure that checkboxes have their checked status set apropriately
1214 // Previously, only Radios could have this flag updated
1215 // Resulted in the issue seen at
1216 // https://github.com/flutter/flutter/issues/96218
1217 // This test ensures that the native state of Checkboxes on Windows,
1218 // specifically, is updated as desired.
1219 TEST(FlutterWindowsViewTest, CheckboxNativeState) {
1220  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1221  EngineModifier modifier(engine.get());
1222  modifier.embedder_api().UpdateSemanticsEnabled =
1223  [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
1224  return kSuccess;
1225  };
1226 
1227  std::unique_ptr<FlutterWindowsView> view =
1228  engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1229  /*is_sized_to_content=*/false, BoxConstraints());
1230 
1231  // Enable semantics to instantiate accessibility bridge.
1232  view->OnUpdateSemanticsEnabled(true);
1233 
1234  auto bridge = view->accessibility_bridge().lock();
1235  ASSERT_TRUE(bridge);
1236 
1237  FlutterSemanticsNode2 root{sizeof(FlutterSemanticsNode2), 0};
1238  root.id = 0;
1239  root.label = "root";
1240  root.hint = "";
1241  root.value = "";
1242  root.increased_value = "";
1243  root.decreased_value = "";
1244  root.child_count = 0;
1245  root.custom_accessibility_actions_count = 0;
1246  auto flags = FlutterSemanticsFlags{
1247  .is_checked = FlutterCheckState::kFlutterCheckStateTrue,
1248  };
1249  root.flags2 = &flags;
1250  bridge->AddFlutterSemanticsNodeUpdate(root);
1251 
1252  bridge->CommitUpdates();
1253 
1254  {
1255  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
1256  EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kCheckBox);
1257  EXPECT_EQ(root_node->GetData().GetCheckedState(),
1258  ax::mojom::CheckedState::kTrue);
1259 
1260  // Get the IAccessible for the root node.
1261  IAccessible* native_view = root_node->GetNativeViewAccessible();
1262  ASSERT_TRUE(native_view != nullptr);
1263 
1264  // Look up against the node itself (not one of its children).
1265  VARIANT varchild = {};
1266  varchild.vt = VT_I4;
1267 
1268  // Verify the checkbox is checked.
1269  varchild.lVal = CHILDID_SELF;
1270  VARIANT native_state = {};
1271  ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
1272  EXPECT_TRUE(native_state.lVal & STATE_SYSTEM_CHECKED);
1273 
1274  // Perform similar tests for UIA value;
1275  IRawElementProviderSimple* uia_node;
1276  native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
1277  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
1278  UIA_ToggleToggleStatePropertyId, &native_state)));
1279  EXPECT_EQ(native_state.lVal, ToggleState_On);
1280 
1281  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
1282  UIA_AriaPropertiesPropertyId, &native_state)));
1283  EXPECT_NE(std::wcsstr(native_state.bstrVal, L"checked=true"), nullptr);
1284  }
1285 
1286  // Test unchecked too.
1287  auto updated_flags = FlutterSemanticsFlags{
1288  .is_checked = FlutterCheckState::kFlutterCheckStateFalse,
1289  };
1290  root.flags2 = &updated_flags;
1291  bridge->AddFlutterSemanticsNodeUpdate(root);
1292  bridge->CommitUpdates();
1293 
1294  {
1295  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
1296  EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kCheckBox);
1297  EXPECT_EQ(root_node->GetData().GetCheckedState(),
1298  ax::mojom::CheckedState::kFalse);
1299 
1300  // Get the IAccessible for the root node.
1301  IAccessible* native_view = root_node->GetNativeViewAccessible();
1302  ASSERT_TRUE(native_view != nullptr);
1303 
1304  // Look up against the node itself (not one of its children).
1305  VARIANT varchild = {};
1306  varchild.vt = VT_I4;
1307 
1308  // Verify the checkbox is unchecked.
1309  varchild.lVal = CHILDID_SELF;
1310  VARIANT native_state = {};
1311  ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
1312  EXPECT_FALSE(native_state.lVal & STATE_SYSTEM_CHECKED);
1313 
1314  // Perform similar tests for UIA value;
1315  IRawElementProviderSimple* uia_node;
1316  native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
1317  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
1318  UIA_ToggleToggleStatePropertyId, &native_state)));
1319  EXPECT_EQ(native_state.lVal, ToggleState_Off);
1320 
1321  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
1322  UIA_AriaPropertiesPropertyId, &native_state)));
1323  EXPECT_NE(std::wcsstr(native_state.bstrVal, L"checked=false"), nullptr);
1324  }
1325 
1326  // Now check mixed state.
1327  auto updated_mixe_flags = FlutterSemanticsFlags{
1328  .is_checked = FlutterCheckState::kFlutterCheckStateMixed,
1329  };
1330  root.flags2 = &updated_mixe_flags;
1331  bridge->AddFlutterSemanticsNodeUpdate(root);
1332  bridge->CommitUpdates();
1333 
1334  {
1335  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
1336  EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kCheckBox);
1337  EXPECT_EQ(root_node->GetData().GetCheckedState(),
1338  ax::mojom::CheckedState::kMixed);
1339 
1340  // Get the IAccessible for the root node.
1341  IAccessible* native_view = root_node->GetNativeViewAccessible();
1342  ASSERT_TRUE(native_view != nullptr);
1343 
1344  // Look up against the node itself (not one of its children).
1345  VARIANT varchild = {};
1346  varchild.vt = VT_I4;
1347 
1348  // Verify the checkbox is mixed.
1349  varchild.lVal = CHILDID_SELF;
1350  VARIANT native_state = {};
1351  ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
1352  EXPECT_TRUE(native_state.lVal & STATE_SYSTEM_MIXED);
1353 
1354  // Perform similar tests for UIA value;
1355  IRawElementProviderSimple* uia_node;
1356  native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
1357  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
1358  UIA_ToggleToggleStatePropertyId, &native_state)));
1359  EXPECT_EQ(native_state.lVal, ToggleState_Indeterminate);
1360 
1361  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
1362  UIA_AriaPropertiesPropertyId, &native_state)));
1363  EXPECT_NE(std::wcsstr(native_state.bstrVal, L"checked=mixed"), nullptr);
1364  }
1365 }
1366 
1367 // Ensure that switches have their toggle status set apropriately
1368 TEST(FlutterWindowsViewTest, SwitchNativeState) {
1369  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1370  EngineModifier modifier(engine.get());
1371  modifier.embedder_api().UpdateSemanticsEnabled =
1372  [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
1373  return kSuccess;
1374  };
1375 
1376  std::unique_ptr<FlutterWindowsView> view =
1377  engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1378  /*is_sized_to_content=*/false, BoxConstraints());
1379 
1380  // Enable semantics to instantiate accessibility bridge.
1381  view->OnUpdateSemanticsEnabled(true);
1382 
1383  auto bridge = view->accessibility_bridge().lock();
1384  ASSERT_TRUE(bridge);
1385 
1386  FlutterSemanticsNode2 root{sizeof(FlutterSemanticsNode2), 0};
1387  root.id = 0;
1388  root.label = "root";
1389  root.hint = "";
1390  root.value = "";
1391  root.increased_value = "";
1392  root.decreased_value = "";
1393  root.child_count = 0;
1394  root.custom_accessibility_actions_count = 0;
1395 
1396  auto flags = FlutterSemanticsFlags{
1397  .is_toggled = FlutterTristate::kFlutterTristateTrue,
1398  };
1399  root.flags2 = &flags;
1400  bridge->AddFlutterSemanticsNodeUpdate(root);
1401 
1402  bridge->CommitUpdates();
1403 
1404  {
1405  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
1406  EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kSwitch);
1407  EXPECT_EQ(root_node->GetData().GetCheckedState(),
1408  ax::mojom::CheckedState::kTrue);
1409 
1410  // Get the IAccessible for the root node.
1411  IAccessible* native_view = root_node->GetNativeViewAccessible();
1412  ASSERT_TRUE(native_view != nullptr);
1413 
1414  // Look up against the node itself (not one of its children).
1415  VARIANT varchild = {};
1416  varchild.vt = VT_I4;
1417 
1418  varchild.lVal = CHILDID_SELF;
1419  VARIANT varrole = {};
1420 
1421  // Verify the role of the switch is CHECKBUTTON
1422  ASSERT_EQ(native_view->get_accRole(varchild, &varrole), S_OK);
1423  ASSERT_EQ(varrole.lVal, ROLE_SYSTEM_CHECKBUTTON);
1424 
1425  // Verify the switch is pressed.
1426  VARIANT native_state = {};
1427  ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
1428  EXPECT_TRUE(native_state.lVal & STATE_SYSTEM_PRESSED);
1429  EXPECT_TRUE(native_state.lVal & STATE_SYSTEM_CHECKED);
1430 
1431  // Test similarly on UIA node.
1432  IRawElementProviderSimple* uia_node;
1433  native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
1434  ASSERT_EQ(uia_node->GetPropertyValue(UIA_ControlTypePropertyId, &varrole),
1435  S_OK);
1436  EXPECT_EQ(varrole.lVal, UIA_ButtonControlTypeId);
1437  ASSERT_EQ(uia_node->GetPropertyValue(UIA_ToggleToggleStatePropertyId,
1438  &native_state),
1439  S_OK);
1440  EXPECT_EQ(native_state.lVal, ToggleState_On);
1441  ASSERT_EQ(
1442  uia_node->GetPropertyValue(UIA_AriaPropertiesPropertyId, &native_state),
1443  S_OK);
1444  EXPECT_NE(std::wcsstr(native_state.bstrVal, L"pressed=true"), nullptr);
1445  }
1446 
1447  // Test unpressed too.
1448  auto updated_flags = FlutterSemanticsFlags{
1449  .is_toggled = FlutterTristate::kFlutterTristateFalse,
1450  };
1451  root.flags2 = &updated_flags;
1452 
1453  bridge->AddFlutterSemanticsNodeUpdate(root);
1454  bridge->CommitUpdates();
1455 
1456  {
1457  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
1458  EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kSwitch);
1459  EXPECT_EQ(root_node->GetData().GetCheckedState(),
1460  ax::mojom::CheckedState::kFalse);
1461 
1462  // Get the IAccessible for the root node.
1463  IAccessible* native_view = root_node->GetNativeViewAccessible();
1464  ASSERT_TRUE(native_view != nullptr);
1465 
1466  // Look up against the node itself (not one of its children).
1467  VARIANT varchild = {};
1468  varchild.vt = VT_I4;
1469 
1470  // Verify the switch is not pressed.
1471  varchild.lVal = CHILDID_SELF;
1472  VARIANT native_state = {};
1473  ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
1474  EXPECT_FALSE(native_state.lVal & STATE_SYSTEM_PRESSED);
1475  EXPECT_FALSE(native_state.lVal & STATE_SYSTEM_CHECKED);
1476 
1477  // Test similarly on UIA node.
1478  IRawElementProviderSimple* uia_node;
1479  native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
1480  ASSERT_EQ(uia_node->GetPropertyValue(UIA_ToggleToggleStatePropertyId,
1481  &native_state),
1482  S_OK);
1483  EXPECT_EQ(native_state.lVal, ToggleState_Off);
1484  ASSERT_EQ(
1485  uia_node->GetPropertyValue(UIA_AriaPropertiesPropertyId, &native_state),
1486  S_OK);
1487  EXPECT_NE(std::wcsstr(native_state.bstrVal, L"pressed=false"), nullptr);
1488  }
1489 }
1490 
1491 TEST(FlutterWindowsViewTest, TooltipNodeData) {
1492  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1493  EngineModifier modifier(engine.get());
1494  modifier.embedder_api().UpdateSemanticsEnabled =
1495  [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
1496  return kSuccess;
1497  };
1498 
1499  std::unique_ptr<FlutterWindowsView> view =
1500  engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1501  /*is_sized_to_content=*/false, BoxConstraints());
1502 
1503  // Enable semantics to instantiate accessibility bridge.
1504  view->OnUpdateSemanticsEnabled(true);
1505 
1506  auto bridge = view->accessibility_bridge().lock();
1507  ASSERT_TRUE(bridge);
1508 
1509  FlutterSemanticsNode2 root{sizeof(FlutterSemanticsNode2), 0};
1510  root.id = 0;
1511  root.label = "root";
1512  root.hint = "";
1513  root.value = "";
1514  root.increased_value = "";
1515  root.decreased_value = "";
1516  root.tooltip = "tooltip";
1517  root.child_count = 0;
1518  root.custom_accessibility_actions_count = 0;
1519  auto flags = FlutterSemanticsFlags{
1520  .is_text_field = true,
1521  };
1522  root.flags2 = &flags;
1523  bridge->AddFlutterSemanticsNodeUpdate(root);
1524 
1525  bridge->CommitUpdates();
1526  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
1527  std::string tooltip = root_node->GetData().GetStringAttribute(
1528  ax::mojom::StringAttribute::kTooltip);
1529  EXPECT_EQ(tooltip, "tooltip");
1530 
1531  // Check that MSAA name contains the tooltip.
1532  IAccessible* native_view = bridge->GetFlutterPlatformNodeDelegateFromID(0)
1533  .lock()
1534  ->GetNativeViewAccessible();
1535  VARIANT varchild = {.vt = VT_I4, .lVal = CHILDID_SELF};
1536  BSTR bname;
1537  ASSERT_EQ(native_view->get_accName(varchild, &bname), S_OK);
1538  EXPECT_NE(std::wcsstr(bname, L"tooltip"), nullptr);
1539 
1540  // Check that UIA help text is equal to the tooltip.
1541  IRawElementProviderSimple* uia_node;
1542  native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
1543  VARIANT varname{};
1544  ASSERT_EQ(uia_node->GetPropertyValue(UIA_HelpTextPropertyId, &varname), S_OK);
1545  std::string uia_tooltip = _com_util::ConvertBSTRToString(varname.bstrVal);
1546  EXPECT_EQ(uia_tooltip, "tooltip");
1547 }
1548 
1549 // Don't block until the v-blank if it is disabled by the window.
1550 // The surface is updated on the platform thread at startup.
1551 TEST(FlutterWindowsViewTest, DisablesVSyncAtStartup) {
1552  auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
1553  auto engine = std::make_unique<MockFlutterWindowsEngine>(windows_proc_table);
1554  auto egl_manager = std::make_unique<egl::MockManager>();
1555  egl::MockContext render_context;
1556  auto surface = std::make_unique<egl::MockWindowSurface>();
1557  auto surface_ptr = surface.get();
1558 
1559  EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(false));
1560  EXPECT_CALL(*engine.get(), PostRasterThreadTask).Times(0);
1561 
1562  EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled)
1563  .WillOnce(Return(true));
1564 
1565  EXPECT_CALL(*egl_manager.get(), render_context)
1566  .WillOnce(Return(&render_context));
1567  EXPECT_CALL(*surface_ptr, IsValid).WillOnce(Return(true));
1568 
1569  InSequence s;
1570  EXPECT_CALL(*egl_manager.get(), CreateWindowSurface)
1571  .WillOnce(Return(std::move(surface)));
1572  EXPECT_CALL(*surface_ptr, MakeCurrent).WillOnce(Return(true));
1573  EXPECT_CALL(*surface_ptr, SetVSyncEnabled(false)).WillOnce(Return(true));
1574  EXPECT_CALL(render_context, ClearCurrent).WillOnce(Return(true));
1575 
1576  EXPECT_CALL(*surface_ptr, Destroy).Times(1);
1577 
1578  EngineModifier modifier{engine.get()};
1579  modifier.SetEGLManager(std::move(egl_manager));
1580 
1581  std::unique_ptr<FlutterWindowsView> view =
1582  engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1583  /*is_sized_to_content=*/false, BoxConstraints());
1584 }
1585 
1586 // Blocks until the v-blank if it is enabled by the window.
1587 // The surface is updated on the platform thread at startup.
1588 TEST(FlutterWindowsViewTest, EnablesVSyncAtStartup) {
1589  auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
1590  auto engine = std::make_unique<MockFlutterWindowsEngine>(windows_proc_table);
1591  auto egl_manager = std::make_unique<egl::MockManager>();
1592  egl::MockContext render_context;
1593  auto surface = std::make_unique<egl::MockWindowSurface>();
1594  auto surface_ptr = surface.get();
1595 
1596  EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(false));
1597  EXPECT_CALL(*engine.get(), PostRasterThreadTask).Times(0);
1598  EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled)
1599  .WillOnce(Return(false));
1600 
1601  EXPECT_CALL(*egl_manager.get(), render_context)
1602  .WillOnce(Return(&render_context));
1603  EXPECT_CALL(*surface_ptr, IsValid).WillOnce(Return(true));
1604 
1605  InSequence s;
1606  EXPECT_CALL(*egl_manager.get(), CreateWindowSurface)
1607  .WillOnce(Return(std::move(surface)));
1608  EXPECT_CALL(*surface_ptr, MakeCurrent).WillOnce(Return(true));
1609  EXPECT_CALL(*surface_ptr, SetVSyncEnabled(true)).WillOnce(Return(true));
1610  EXPECT_CALL(render_context, ClearCurrent).WillOnce(Return(true));
1611 
1612  EXPECT_CALL(*surface_ptr, Destroy).Times(1);
1613 
1614  EngineModifier modifier{engine.get()};
1615  modifier.SetEGLManager(std::move(egl_manager));
1616 
1617  std::unique_ptr<FlutterWindowsView> view =
1618  engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1619  /*is_sized_to_content=*/false, BoxConstraints());
1620 }
1621 
1622 // Don't block until the v-blank if it is disabled by the window.
1623 // The surface is updated on the raster thread if the engine is running.
1624 TEST(FlutterWindowsViewTest, DisablesVSyncAfterStartup) {
1625  auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
1626  auto engine = std::make_unique<MockFlutterWindowsEngine>(windows_proc_table);
1627  auto egl_manager = std::make_unique<egl::MockManager>();
1628  egl::MockContext render_context;
1629  auto surface = std::make_unique<egl::MockWindowSurface>();
1630  auto surface_ptr = surface.get();
1631 
1632  EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(true));
1633  EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled)
1634  .WillOnce(Return(true));
1635 
1636  EXPECT_CALL(*egl_manager.get(), render_context)
1637  .WillOnce(Return(&render_context));
1638  EXPECT_CALL(*surface_ptr, IsValid).WillOnce(Return(true));
1639 
1640  InSequence s;
1641  EXPECT_CALL(*egl_manager.get(), CreateWindowSurface)
1642  .WillOnce(Return(std::move(surface)));
1643  EXPECT_CALL(*engine.get(), PostRasterThreadTask)
1644  .WillOnce([](fml::closure callback) {
1645  callback();
1646  return true;
1647  });
1648  EXPECT_CALL(*surface_ptr, MakeCurrent).WillOnce(Return(true));
1649  EXPECT_CALL(*surface_ptr, SetVSyncEnabled(false)).WillOnce(Return(true));
1650  EXPECT_CALL(render_context, ClearCurrent).WillOnce(Return(true));
1651  EXPECT_CALL(*engine.get(), PostRasterThreadTask)
1652  .WillOnce([](fml::closure callback) {
1653  callback();
1654  return true;
1655  });
1656  EXPECT_CALL(*surface_ptr, Destroy).Times(1);
1657 
1658  EngineModifier modifier{engine.get()};
1659  modifier.SetEGLManager(std::move(egl_manager));
1660 
1661  std::unique_ptr<FlutterWindowsView> view =
1662  engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1663  /*is_sized_to_content=*/false, BoxConstraints());
1664 }
1665 
1666 // Blocks until the v-blank if it is enabled by the window.
1667 // The surface is updated on the raster thread if the engine is running.
1668 TEST(FlutterWindowsViewTest, EnablesVSyncAfterStartup) {
1669  auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
1670  auto engine = std::make_unique<MockFlutterWindowsEngine>(windows_proc_table);
1671  auto egl_manager = std::make_unique<egl::MockManager>();
1672  egl::MockContext render_context;
1673  auto surface = std::make_unique<egl::MockWindowSurface>();
1674  auto surface_ptr = surface.get();
1675 
1676  EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(true));
1677 
1678  EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled)
1679  .WillOnce(Return(false));
1680 
1681  EXPECT_CALL(*egl_manager.get(), render_context)
1682  .WillOnce(Return(&render_context));
1683  EXPECT_CALL(*surface_ptr, IsValid).WillOnce(Return(true));
1684 
1685  InSequence s;
1686  EXPECT_CALL(*egl_manager.get(), CreateWindowSurface)
1687  .WillOnce(Return(std::move(surface)));
1688  EXPECT_CALL(*engine.get(), PostRasterThreadTask)
1689  .WillOnce([](fml::closure callback) {
1690  callback();
1691  return true;
1692  });
1693 
1694  EXPECT_CALL(*surface_ptr, MakeCurrent).WillOnce(Return(true));
1695  EXPECT_CALL(*surface_ptr, SetVSyncEnabled(true)).WillOnce(Return(true));
1696  EXPECT_CALL(render_context, ClearCurrent).WillOnce(Return(true));
1697 
1698  EXPECT_CALL(*engine.get(), PostRasterThreadTask)
1699  .WillOnce([](fml::closure callback) {
1700  callback();
1701  return true;
1702  });
1703  EXPECT_CALL(*surface_ptr, Destroy).Times(1);
1704 
1705  EngineModifier modifier{engine.get()};
1706  modifier.SetEGLManager(std::move(egl_manager));
1707 
1708  std::unique_ptr<FlutterWindowsView> view =
1709  engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1710  /*is_sized_to_content=*/false, BoxConstraints());
1711 }
1712 
1713 // Desktop Window Manager composition can be disabled on Windows 7.
1714 // If this happens, the app must synchronize with the vsync to prevent
1715 // screen tearing.
1716 TEST(FlutterWindowsViewTest, UpdatesVSyncOnDwmUpdates) {
1717  auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
1718  auto engine = std::make_unique<MockFlutterWindowsEngine>(windows_proc_table);
1719  auto egl_manager = std::make_unique<egl::MockManager>();
1720  egl::MockContext render_context;
1721  auto surface = std::make_unique<egl::MockWindowSurface>();
1722  auto surface_ptr = surface.get();
1723 
1724  EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(true));
1725 
1726  EXPECT_CALL(*engine.get(), PostRasterThreadTask)
1727  .WillRepeatedly([](fml::closure callback) {
1728  callback();
1729  return true;
1730  });
1731 
1732  EXPECT_CALL(*egl_manager.get(), render_context)
1733  .WillRepeatedly(Return(&render_context));
1734 
1735  EXPECT_CALL(*surface_ptr, IsValid).WillRepeatedly(Return(true));
1736  EXPECT_CALL(*surface_ptr, MakeCurrent).WillRepeatedly(Return(true));
1737  EXPECT_CALL(*surface_ptr, Destroy).Times(1);
1738  EXPECT_CALL(render_context, ClearCurrent).WillRepeatedly(Return(true));
1739 
1740  InSequence s;
1741 
1742  // Mock render surface initialization.
1743  std::unique_ptr<FlutterWindowsView> view;
1744  {
1745  EXPECT_CALL(*egl_manager, CreateWindowSurface)
1746  .WillOnce(Return(std::move(surface)));
1747  EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled)
1748  .WillOnce(Return(true));
1749  EXPECT_CALL(*surface_ptr, SetVSyncEnabled).WillOnce(Return(true));
1750 
1751  EngineModifier engine_modifier{engine.get()};
1752  engine_modifier.SetEGLManager(std::move(egl_manager));
1753 
1754  view = engine->CreateView(
1755  std::make_unique<NiceMock<MockWindowBindingHandler>>(), false,
1756  BoxConstraints());
1757  }
1758 
1759  // Disabling DWM composition should enable vsync blocking on the surface.
1760  {
1761  EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled)
1762  .WillOnce(Return(false));
1763  EXPECT_CALL(*surface_ptr, SetVSyncEnabled(true)).WillOnce(Return(true));
1764 
1765  engine->OnDwmCompositionChanged();
1766  }
1767 
1768  // Enabling DWM composition should disable vsync blocking on the surface.
1769  {
1770  EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled)
1771  .WillOnce(Return(true));
1772  EXPECT_CALL(*surface_ptr, SetVSyncEnabled(false)).WillOnce(Return(true));
1773 
1774  engine->OnDwmCompositionChanged();
1775  }
1776 }
1777 
1778 TEST(FlutterWindowsViewTest, FocusTriggersWindowFocus) {
1779  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1780  auto window_binding_handler =
1781  std::make_unique<NiceMock<MockWindowBindingHandler>>();
1782  EXPECT_CALL(*window_binding_handler, Focus()).WillOnce(Return(true));
1783  std::unique_ptr<FlutterWindowsView> view =
1784  engine->CreateView(std::move(window_binding_handler),
1785  /*is_sized_to_content=*/false, BoxConstraints());
1786  EXPECT_TRUE(view->Focus());
1787 }
1788 
1789 TEST(FlutterWindowsViewTest, OnFocusTriggersSendFocusViewEvent) {
1790  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1791  auto window_binding_handler =
1792  std::make_unique<NiceMock<MockWindowBindingHandler>>();
1793  std::unique_ptr<FlutterWindowsView> view =
1794  engine->CreateView(std::move(window_binding_handler),
1795  /*is_sized_to_content=*/false, BoxConstraints());
1796 
1797  EngineModifier modifier(engine.get());
1798  bool received_focus_event = false;
1799  modifier.embedder_api().SendViewFocusEvent = MOCK_ENGINE_PROC(
1800  SendViewFocusEvent, [&](FLUTTER_API_SYMBOL(FlutterEngine) raw_engine,
1801  FlutterViewFocusEvent const* event) {
1802  EXPECT_EQ(event->state, FlutterViewFocusState::kFocused);
1803  EXPECT_EQ(event->direction, FlutterViewFocusDirection::kUndefined);
1804  EXPECT_EQ(event->view_id, view->view_id());
1805  EXPECT_EQ(event->struct_size, sizeof(FlutterViewFocusEvent));
1806  received_focus_event = true;
1807  return kSuccess;
1808  });
1809  view->OnFocus(FlutterViewFocusState::kFocused,
1810  FlutterViewFocusDirection::kUndefined);
1811  EXPECT_TRUE(received_focus_event);
1812 }
1813 
1814 TEST(FlutterWindowsViewTest, WindowMetricsEventContainsDisplayId) {
1815  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1816  EngineModifier modifier(engine.get());
1817 
1818  auto window_binding_handler =
1819  std::make_unique<NiceMock<MockWindowBindingHandler>>();
1820  EXPECT_CALL(*window_binding_handler, GetDisplayId)
1821  .WillOnce(testing::Return(12));
1822  FlutterWindowsView view{kImplicitViewId, engine.get(),
1823  std::move(window_binding_handler), false,
1824  BoxConstraints()};
1825 
1826  FlutterWindowMetricsEvent event = view.CreateWindowMetricsEvent();
1827  EXPECT_EQ(event.display_id, 12);
1828 }
1829 
1830 TEST(FlutterWindowsViewTest, SizeChangeTriggersMetricsEventWhichHasDisplayId) {
1831  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1832  EngineModifier modifier(engine.get());
1833 
1834  auto window_binding_handler =
1835  std::make_unique<NiceMock<MockWindowBindingHandler>>();
1836  EXPECT_CALL(*window_binding_handler, GetDisplayId)
1837  .WillOnce(testing::Return(12));
1838  FlutterWindowsView view{kImplicitViewId, engine.get(),
1839  std::move(window_binding_handler), false,
1840  BoxConstraints()};
1841 
1842  bool received_metrics = false;
1843  modifier.embedder_api().SendWindowMetricsEvent = MOCK_ENGINE_PROC(
1844  SendWindowMetricsEvent,
1845  ([&received_metrics](auto engine,
1846  const FlutterWindowMetricsEvent* event) {
1847  received_metrics = true;
1848  EXPECT_EQ(event->display_id, 12);
1849  return kSuccess;
1850  }));
1851  view.OnWindowSizeChanged(100, 100);
1852  EXPECT_TRUE(received_metrics);
1853 }
1854 
1855 // Verify that the first frame callback fires after OnFramePresented (OpenGL
1856 // rendering path).
1857 TEST(FlutterWindowsViewTest, FirstFrameCallbackFiresOnFramePresented) {
1858  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1859 
1860  std::unique_ptr<FlutterWindowsView> view =
1861  engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1862  /*is_sized_to_content=*/false, BoxConstraints());
1863 
1864  bool callback_fired = false;
1865  view->SetFirstFrameCallback([&callback_fired]() { callback_fired = true; });
1866 
1867  // Simulate the raster thread presenting a frame.
1868  view->OnFramePresented();
1869 
1870  // The callback is posted to the platform thread's task runner.
1871  // Process pending tasks to execute it.
1872  engine->task_runner()->ProcessTasks();
1873 
1874  EXPECT_TRUE(callback_fired);
1875 }
1876 
1877 // Verify that the first frame callback fires after PresentSoftwareBitmap
1878 // (software rendering path).
1879 TEST(FlutterWindowsViewTest, FirstFrameCallbackFiresOnSoftwareBitmapPresent) {
1880  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1881 
1882  auto window_binding_handler =
1883  std::make_unique<NiceMock<MockWindowBindingHandler>>();
1884  EXPECT_CALL(*window_binding_handler, OnBitmapSurfaceUpdated)
1885  .WillOnce(Return(true));
1886 
1887  std::unique_ptr<FlutterWindowsView> view =
1888  engine->CreateView(std::move(window_binding_handler),
1889  /*is_sized_to_content=*/false, BoxConstraints());
1890 
1891  bool callback_fired = false;
1892  view->SetFirstFrameCallback([&callback_fired]() { callback_fired = true; });
1893 
1894  // Simulate software rendering presenting a bitmap.
1895  const uint32_t pixel = 0;
1896  view->PresentSoftwareBitmap(&pixel, sizeof(pixel), 1);
1897 
1898  // Process pending tasks.
1899  engine->task_runner()->ProcessTasks();
1900 
1901  EXPECT_TRUE(callback_fired);
1902 }
1903 
1904 // Verify that the first frame callback fires only once, even if multiple
1905 // frames are presented.
1906 TEST(FlutterWindowsViewTest, FirstFrameCallbackFiresOnlyOnce) {
1907  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1908 
1909  std::unique_ptr<FlutterWindowsView> view =
1910  engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1911  /*is_sized_to_content=*/false, BoxConstraints());
1912 
1913  int callback_count = 0;
1914  view->SetFirstFrameCallback([&callback_count]() { callback_count++; });
1915 
1916  // Present two frames.
1917  view->OnFramePresented();
1918  view->OnFramePresented();
1919 
1920  // Process all pending tasks.
1921  engine->task_runner()->ProcessTasks();
1922 
1923  EXPECT_EQ(callback_count, 1);
1924 }
1925 
1926 // Verify that the first frame callback does not fire if PresentSoftwareBitmap
1927 // fails.
1928 TEST(FlutterWindowsViewTest, FirstFrameCallbackSkippedOnFailedSoftwarePresent) {
1929  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1930 
1931  auto window_binding_handler =
1932  std::make_unique<NiceMock<MockWindowBindingHandler>>();
1933  EXPECT_CALL(*window_binding_handler, OnBitmapSurfaceUpdated)
1934  .WillOnce(Return(false));
1935 
1936  std::unique_ptr<FlutterWindowsView> view =
1937  engine->CreateView(std::move(window_binding_handler),
1938  /*is_sized_to_content=*/false, BoxConstraints());
1939 
1940  bool callback_fired = false;
1941  view->SetFirstFrameCallback([&callback_fired]() { callback_fired = true; });
1942 
1943  const uint32_t pixel = 0;
1944  view->PresentSoftwareBitmap(&pixel, sizeof(pixel), 1);
1945 
1946  engine->task_runner()->ProcessTasks();
1947 
1948  EXPECT_FALSE(callback_fired);
1949 }
1950 
1951 } // namespace testing
1952 } // namespace flutter
std::shared_ptr< FlutterPlatformNodeDelegateWindows > node_delegate
Controls a view that displays Flutter content.
static const JsonMessageCodec & GetInstance()
std::unique_ptr< std::vector< uint8_t > > EncodeMessage(const T &message) const
Definition: message_codec.h:45
void(* FlutterDesktopBinaryReply)(const uint8_t *data, size_t data_size, void *user_data)
void(* VoidCallback)(void *)
@ DefaultImpeller
FlutterDesktopBinaryReply callback
TEST(AccessibilityBridgeWindows, GetParent)
int64_t FlutterViewId
constexpr FlutterViewId kImplicitViewId
FlutterDesktopImpellerSwitch impeller_switch