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