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