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