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