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"
21 #include "flutter/shell/platform/windows/testing/engine_modifier.h"
22 #include "flutter/shell/platform/windows/testing/mock_angle_surface_manager.h"
23 #include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h"
24 #include "flutter/shell/platform/windows/testing/mock_windows_proc_table.h"
25 #include "flutter/shell/platform/windows/testing/test_keyboard.h"
26 
27 #include "gmock/gmock.h"
28 #include "gtest/gtest.h"
29 
30 namespace flutter {
31 namespace testing {
32 
33 using ::testing::_;
34 using ::testing::InSequence;
35 using ::testing::NiceMock;
36 using ::testing::Return;
37 
38 constexpr uint64_t kScanCodeKeyA = 0x1e;
39 constexpr uint64_t kVirtualKeyA = 0x41;
40 
41 namespace {
42 
43 // A struct to use as a FlutterPlatformMessageResponseHandle so it can keep the
44 // callbacks and user data passed to the engine's
45 // PlatformMessageCreateResponseHandle for use in the SendPlatformMessage
46 // overridden function.
47 struct TestResponseHandle {
49  void* user_data;
50 };
51 
52 static bool test_response = false;
53 
54 constexpr uint64_t kKeyEventFromChannel = 0x11;
55 constexpr uint64_t kKeyEventFromEmbedder = 0x22;
56 static std::vector<int> key_event_logs;
57 
58 std::unique_ptr<std::vector<uint8_t>> keyHandlingResponse(bool handled) {
59  rapidjson::Document document;
60  auto& allocator = document.GetAllocator();
61  document.SetObject();
62  document.AddMember("handled", test_response, allocator);
64 }
65 
66 // Returns a Flutter project with the required path values to create
67 // a test engine.
68 FlutterProjectBundle GetTestProject() {
69  FlutterDesktopEngineProperties properties = {};
70  properties.assets_path = L"C:\\foo\\flutter_assets";
71  properties.icu_data_path = L"C:\\foo\\icudtl.dat";
72  properties.aot_library_path = L"C:\\foo\\aot.so";
73 
74  return FlutterProjectBundle{properties};
75 }
76 
77 // Returns an engine instance configured with test project path values, and
78 // overridden methods for sending platform messages, so that the engine can
79 // respond as if the framework were connected.
80 std::unique_ptr<FlutterWindowsEngine> GetTestEngine() {
81  auto engine = std::make_unique<FlutterWindowsEngine>(GetTestProject());
82 
83  EngineModifier modifier(engine.get());
84 
85  auto key_response_controller = std::make_shared<MockKeyResponseController>();
86  key_response_controller->SetChannelResponse(
87  [](MockKeyResponseController::ResponseCallback callback) {
88  key_event_logs.push_back(kKeyEventFromChannel);
89  callback(test_response);
90  });
91  key_response_controller->SetEmbedderResponse(
92  [](const FlutterKeyEvent* event,
93  MockKeyResponseController::ResponseCallback callback) {
94  key_event_logs.push_back(kKeyEventFromEmbedder);
95  callback(test_response);
96  });
97  modifier.embedder_api().NotifyDisplayUpdate =
98  MOCK_ENGINE_PROC(NotifyDisplayUpdate,
99  ([engine_instance = engine.get()](
100  FLUTTER_API_SYMBOL(FlutterEngine) raw_engine,
101  const FlutterEngineDisplaysUpdateType update_type,
102  const FlutterEngineDisplay* embedder_displays,
103  size_t display_count) { return kSuccess; }));
104 
105  MockEmbedderApiForKeyboard(modifier, key_response_controller);
106 
107  engine->Run();
108  return engine;
109 }
110 
111 class MockFlutterWindowsEngine : public FlutterWindowsEngine {
112  public:
113  MockFlutterWindowsEngine() : FlutterWindowsEngine(GetTestProject()) {}
114 
115  MOCK_METHOD(bool, running, (), (const));
116  MOCK_METHOD(bool, Stop, (), ());
117  MOCK_METHOD(bool, PostRasterThreadTask, (fml::closure), (const));
118 
119  private:
120  FML_DISALLOW_COPY_AND_ASSIGN(MockFlutterWindowsEngine);
121 };
122 
123 } // namespace
124 
125 // Ensure that submenu buttons have their expanded/collapsed status set
126 // apropriately.
127 TEST(FlutterWindowsViewTest, SubMenuExpandedState) {
128  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
129  EngineModifier modifier(engine.get());
130  modifier.embedder_api().UpdateSemanticsEnabled =
131  [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
132  return kSuccess;
133  };
134 
135  auto window_binding_handler =
136  std::make_unique<NiceMock<MockWindowBindingHandler>>();
137  FlutterWindowsView view(std::move(window_binding_handler));
138  view.SetEngine(engine.get());
139 
140  // Enable semantics to instantiate accessibility bridge.
141  view.OnUpdateSemanticsEnabled(true);
142 
143  auto bridge = view.accessibility_bridge().lock();
144  ASSERT_TRUE(bridge);
145 
146  FlutterSemanticsNode2 root{sizeof(FlutterSemanticsNode2), 0};
147  root.id = 0;
148  root.label = "root";
149  root.hint = "";
150  root.value = "";
151  root.increased_value = "";
152  root.decreased_value = "";
153  root.child_count = 0;
154  root.custom_accessibility_actions_count = 0;
155  root.flags = static_cast<FlutterSemanticsFlag>(
156  FlutterSemanticsFlag::kFlutterSemanticsFlagHasExpandedState |
157  FlutterSemanticsFlag::kFlutterSemanticsFlagIsExpanded);
158  bridge->AddFlutterSemanticsNodeUpdate(root);
159 
160  bridge->CommitUpdates();
161 
162  {
163  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
164  EXPECT_TRUE(root_node->GetData().HasState(ax::mojom::State::kExpanded));
165 
166  // Get the IAccessible for the root node.
167  IAccessible* native_view = root_node->GetNativeViewAccessible();
168  ASSERT_TRUE(native_view != nullptr);
169 
170  // Look up against the node itself (not one of its children).
171  VARIANT varchild = {};
172  varchild.vt = VT_I4;
173 
174  // Verify the submenu is expanded.
175  varchild.lVal = CHILDID_SELF;
176  VARIANT native_state = {};
177  ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
178  EXPECT_TRUE(native_state.lVal & STATE_SYSTEM_EXPANDED);
179 
180  // Perform similar tests for UIA value;
181  IRawElementProviderSimple* uia_node;
182  native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
183  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
184  UIA_ExpandCollapseExpandCollapseStatePropertyId, &native_state)));
185  EXPECT_EQ(native_state.lVal, ExpandCollapseState_Expanded);
186 
187  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
188  UIA_AriaPropertiesPropertyId, &native_state)));
189  EXPECT_NE(std::wcsstr(native_state.bstrVal, L"expanded=true"), nullptr);
190  }
191 
192  // Test collapsed too.
193  root.flags = static_cast<FlutterSemanticsFlag>(
194  FlutterSemanticsFlag::kFlutterSemanticsFlagHasExpandedState);
195  bridge->AddFlutterSemanticsNodeUpdate(root);
196  bridge->CommitUpdates();
197 
198  {
199  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
200  EXPECT_TRUE(root_node->GetData().HasState(ax::mojom::State::kCollapsed));
201 
202  // Get the IAccessible for the root node.
203  IAccessible* native_view = root_node->GetNativeViewAccessible();
204  ASSERT_TRUE(native_view != nullptr);
205 
206  // Look up against the node itself (not one of its children).
207  VARIANT varchild = {};
208  varchild.vt = VT_I4;
209 
210  // Verify the submenu is collapsed.
211  varchild.lVal = CHILDID_SELF;
212  VARIANT native_state = {};
213  ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
214  EXPECT_TRUE(native_state.lVal & STATE_SYSTEM_COLLAPSED);
215 
216  // Perform similar tests for UIA value;
217  IRawElementProviderSimple* uia_node;
218  native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
219  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
220  UIA_ExpandCollapseExpandCollapseStatePropertyId, &native_state)));
221  EXPECT_EQ(native_state.lVal, ExpandCollapseState_Collapsed);
222 
223  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
224  UIA_AriaPropertiesPropertyId, &native_state)));
225  EXPECT_NE(std::wcsstr(native_state.bstrVal, L"expanded=false"), nullptr);
226  }
227 }
228 
229 // The view's surface must be destroyed after the engine is shutdown.
230 // See: https://github.com/flutter/flutter/issues/124463
231 TEST(FlutterWindowsViewTest, Shutdown) {
232  std::unique_ptr<MockFlutterWindowsEngine> engine =
233  std::make_unique<MockFlutterWindowsEngine>();
234  auto window_binding_handler =
235  std::make_unique<NiceMock<MockWindowBindingHandler>>();
236  std::unique_ptr<MockAngleSurfaceManager> surface_manager =
237  std::make_unique<MockAngleSurfaceManager>();
238 
239  EngineModifier modifier(engine.get());
240  FlutterWindowsView view(std::move(window_binding_handler));
241 
242  // The engine must be stopped before the surface can be destroyed.
243  InSequence s;
244  EXPECT_CALL(*engine.get(), Stop).Times(1);
245  EXPECT_CALL(*surface_manager.get(), DestroySurface).Times(1);
246 
247  modifier.SetSurfaceManager(std::move(surface_manager));
248  view.SetEngine(engine.get());
249 }
250 
251 TEST(FlutterWindowsViewTest, KeySequence) {
252  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
253 
254  test_response = false;
255 
256  auto window_binding_handler =
257  std::make_unique<NiceMock<MockWindowBindingHandler>>();
258  FlutterWindowsView view(std::move(window_binding_handler));
259  view.SetEngine(engine.get());
260 
261  view.OnKey(kVirtualKeyA, kScanCodeKeyA, WM_KEYDOWN, 'a', false, false,
262  [](bool handled) {});
263 
264  EXPECT_EQ(key_event_logs.size(), 2);
265  EXPECT_EQ(key_event_logs[0], kKeyEventFromEmbedder);
266  EXPECT_EQ(key_event_logs[1], kKeyEventFromChannel);
267 
268  key_event_logs.clear();
269 }
270 
271 TEST(FlutterWindowsViewTest, EnableSemantics) {
272  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
273  EngineModifier modifier(engine.get());
274 
275  bool semantics_enabled = false;
276  modifier.embedder_api().UpdateSemanticsEnabled = MOCK_ENGINE_PROC(
277  UpdateSemanticsEnabled,
278  [&semantics_enabled](FLUTTER_API_SYMBOL(FlutterEngine) engine,
279  bool enabled) {
280  semantics_enabled = enabled;
281  return kSuccess;
282  });
283 
284  auto window_binding_handler =
285  std::make_unique<NiceMock<MockWindowBindingHandler>>();
286  FlutterWindowsView view(std::move(window_binding_handler));
287  view.SetEngine(engine.get());
288 
289  view.OnUpdateSemanticsEnabled(true);
290  EXPECT_TRUE(semantics_enabled);
291 }
292 
293 TEST(FlutterWindowsViewTest, AddSemanticsNodeUpdate) {
294  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
295  EngineModifier modifier(engine.get());
296  modifier.embedder_api().UpdateSemanticsEnabled =
297  [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
298  return kSuccess;
299  };
300 
301  auto window_binding_handler =
302  std::make_unique<NiceMock<MockWindowBindingHandler>>();
303  FlutterWindowsView view(std::move(window_binding_handler));
304  view.SetEngine(engine.get());
305 
306  // Enable semantics to instantiate accessibility bridge.
307  view.OnUpdateSemanticsEnabled(true);
308 
309  auto bridge = view.accessibility_bridge().lock();
310  ASSERT_TRUE(bridge);
311 
312  // Add root node.
313  FlutterSemanticsNode2 node{sizeof(FlutterSemanticsNode2), 0};
314  node.label = "name";
315  node.value = "value";
316  node.platform_view_id = -1;
317  bridge->AddFlutterSemanticsNodeUpdate(node);
318  bridge->CommitUpdates();
319 
320  // Look up the root windows node delegate.
321  auto node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
322  ASSERT_TRUE(node_delegate);
323  EXPECT_EQ(node_delegate->GetChildCount(), 0);
324 
325  // Get the native IAccessible object.
326  IAccessible* native_view = node_delegate->GetNativeViewAccessible();
327  ASSERT_TRUE(native_view != nullptr);
328 
329  // Property lookups will be made against this node itself.
330  VARIANT varchild{};
331  varchild.vt = VT_I4;
332  varchild.lVal = CHILDID_SELF;
333 
334  // Verify node name matches our label.
335  BSTR bname = nullptr;
336  ASSERT_EQ(native_view->get_accName(varchild, &bname), S_OK);
337  std::string name(_com_util::ConvertBSTRToString(bname));
338  EXPECT_EQ(name, "name");
339 
340  // Verify node value matches.
341  BSTR bvalue = nullptr;
342  ASSERT_EQ(native_view->get_accValue(varchild, &bvalue), S_OK);
343  std::string value(_com_util::ConvertBSTRToString(bvalue));
344  EXPECT_EQ(value, "value");
345 
346  // Verify node type is static text.
347  VARIANT varrole{};
348  varrole.vt = VT_I4;
349  ASSERT_EQ(native_view->get_accRole(varchild, &varrole), S_OK);
350  EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_STATICTEXT);
351 
352  // Get the IRawElementProviderFragment object.
353  IRawElementProviderSimple* uia_view;
354  native_view->QueryInterface(IID_PPV_ARGS(&uia_view));
355  ASSERT_TRUE(uia_view != nullptr);
356 
357  // Verify name property matches our label.
358  VARIANT varname{};
359  ASSERT_EQ(uia_view->GetPropertyValue(UIA_NamePropertyId, &varname), S_OK);
360  EXPECT_EQ(varname.vt, VT_BSTR);
361  name = _com_util::ConvertBSTRToString(varname.bstrVal);
362  EXPECT_EQ(name, "name");
363 
364  // Verify value property matches our label.
365  VARIANT varvalue{};
366  ASSERT_EQ(uia_view->GetPropertyValue(UIA_ValueValuePropertyId, &varvalue),
367  S_OK);
368  EXPECT_EQ(varvalue.vt, VT_BSTR);
369  value = _com_util::ConvertBSTRToString(varvalue.bstrVal);
370  EXPECT_EQ(value, "value");
371 
372  // Verify node control type is text.
373  varrole = {};
374  ASSERT_EQ(uia_view->GetPropertyValue(UIA_ControlTypePropertyId, &varrole),
375  S_OK);
376  EXPECT_EQ(varrole.vt, VT_I4);
377  EXPECT_EQ(varrole.lVal, UIA_TextControlTypeId);
378 }
379 
380 // Verify the native IAccessible COM object tree is an accurate reflection of
381 // the platform-agnostic tree. Verify both a root node with children as well as
382 // a non-root node with children, since the AX tree includes special handling
383 // for the root.
384 //
385 // node0
386 // / \
387 // node1 node2
388 // |
389 // node3
390 //
391 // node0 and node2 are grouping nodes. node1 and node2 are static text nodes.
392 TEST(FlutterWindowsViewTest, AddSemanticsNodeUpdateWithChildren) {
393  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
394  EngineModifier modifier(engine.get());
395  modifier.embedder_api().UpdateSemanticsEnabled =
396  [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
397  return kSuccess;
398  };
399 
400  auto window_binding_handler =
401  std::make_unique<NiceMock<MockWindowBindingHandler>>();
402  FlutterWindowsView view(std::move(window_binding_handler));
403  view.SetEngine(engine.get());
404 
405  // Enable semantics to instantiate accessibility bridge.
406  view.OnUpdateSemanticsEnabled(true);
407 
408  auto bridge = view.accessibility_bridge().lock();
409  ASSERT_TRUE(bridge);
410 
411  // Add root node.
412  FlutterSemanticsNode2 node0{sizeof(FlutterSemanticsNode2), 0};
413  std::vector<int32_t> node0_children{1, 2};
414  node0.child_count = node0_children.size();
415  node0.children_in_traversal_order = node0_children.data();
416  node0.children_in_hit_test_order = node0_children.data();
417 
418  FlutterSemanticsNode2 node1{sizeof(FlutterSemanticsNode2), 1};
419  node1.label = "prefecture";
420  node1.value = "Kyoto";
421  FlutterSemanticsNode2 node2{sizeof(FlutterSemanticsNode2), 2};
422  std::vector<int32_t> node2_children{3};
423  node2.child_count = node2_children.size();
424  node2.children_in_traversal_order = node2_children.data();
425  node2.children_in_hit_test_order = node2_children.data();
426  FlutterSemanticsNode2 node3{sizeof(FlutterSemanticsNode2), 3};
427  node3.label = "city";
428  node3.value = "Uji";
429 
430  bridge->AddFlutterSemanticsNodeUpdate(node0);
431  bridge->AddFlutterSemanticsNodeUpdate(node1);
432  bridge->AddFlutterSemanticsNodeUpdate(node2);
433  bridge->AddFlutterSemanticsNodeUpdate(node3);
434  bridge->CommitUpdates();
435 
436  // Look up the root windows node delegate.
437  auto node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
438  ASSERT_TRUE(node_delegate);
439  EXPECT_EQ(node_delegate->GetChildCount(), 2);
440 
441  // Get the native IAccessible object.
442  IAccessible* node0_accessible = node_delegate->GetNativeViewAccessible();
443  ASSERT_TRUE(node0_accessible != nullptr);
444 
445  // Property lookups will be made against this node itself.
446  VARIANT varchild{};
447  varchild.vt = VT_I4;
448  varchild.lVal = CHILDID_SELF;
449 
450  // Verify node type is a group.
451  VARIANT varrole{};
452  varrole.vt = VT_I4;
453  ASSERT_EQ(node0_accessible->get_accRole(varchild, &varrole), S_OK);
454  EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_GROUPING);
455 
456  // Verify child count.
457  long node0_child_count = 0;
458  ASSERT_EQ(node0_accessible->get_accChildCount(&node0_child_count), S_OK);
459  EXPECT_EQ(node0_child_count, 2);
460 
461  {
462  // Look up first child of node0 (node1), a static text node.
463  varchild.lVal = 1;
464  IDispatch* node1_dispatch = nullptr;
465  ASSERT_EQ(node0_accessible->get_accChild(varchild, &node1_dispatch), S_OK);
466  ASSERT_TRUE(node1_dispatch != nullptr);
467  IAccessible* node1_accessible = nullptr;
468  ASSERT_EQ(node1_dispatch->QueryInterface(
469  IID_IAccessible, reinterpret_cast<void**>(&node1_accessible)),
470  S_OK);
471  ASSERT_TRUE(node1_accessible != nullptr);
472 
473  // Verify node name matches our label.
474  varchild.lVal = CHILDID_SELF;
475  BSTR bname = nullptr;
476  ASSERT_EQ(node1_accessible->get_accName(varchild, &bname), S_OK);
477  std::string name(_com_util::ConvertBSTRToString(bname));
478  EXPECT_EQ(name, "prefecture");
479 
480  // Verify node value matches.
481  BSTR bvalue = nullptr;
482  ASSERT_EQ(node1_accessible->get_accValue(varchild, &bvalue), S_OK);
483  std::string value(_com_util::ConvertBSTRToString(bvalue));
484  EXPECT_EQ(value, "Kyoto");
485 
486  // Verify node type is static text.
487  VARIANT varrole{};
488  varrole.vt = VT_I4;
489  ASSERT_EQ(node1_accessible->get_accRole(varchild, &varrole), S_OK);
490  EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_STATICTEXT);
491 
492  // Verify the parent node is the root.
493  IDispatch* parent_dispatch;
494  node1_accessible->get_accParent(&parent_dispatch);
495  IAccessible* parent_accessible;
496  ASSERT_EQ(
497  parent_dispatch->QueryInterface(
498  IID_IAccessible, reinterpret_cast<void**>(&parent_accessible)),
499  S_OK);
500  EXPECT_EQ(parent_accessible, node0_accessible);
501  }
502 
503  // Look up second child of node0 (node2), a parent group for node3.
504  varchild.lVal = 2;
505  IDispatch* node2_dispatch = nullptr;
506  ASSERT_EQ(node0_accessible->get_accChild(varchild, &node2_dispatch), S_OK);
507  ASSERT_TRUE(node2_dispatch != nullptr);
508  IAccessible* node2_accessible = nullptr;
509  ASSERT_EQ(node2_dispatch->QueryInterface(
510  IID_IAccessible, reinterpret_cast<void**>(&node2_accessible)),
511  S_OK);
512  ASSERT_TRUE(node2_accessible != nullptr);
513 
514  {
515  // Verify child count.
516  long node2_child_count = 0;
517  ASSERT_EQ(node2_accessible->get_accChildCount(&node2_child_count), S_OK);
518  EXPECT_EQ(node2_child_count, 1);
519 
520  // Verify node type is static text.
521  varchild.lVal = CHILDID_SELF;
522  VARIANT varrole{};
523  varrole.vt = VT_I4;
524  ASSERT_EQ(node2_accessible->get_accRole(varchild, &varrole), S_OK);
525  EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_GROUPING);
526 
527  // Verify the parent node is the root.
528  IDispatch* parent_dispatch;
529  node2_accessible->get_accParent(&parent_dispatch);
530  IAccessible* parent_accessible;
531  ASSERT_EQ(
532  parent_dispatch->QueryInterface(
533  IID_IAccessible, reinterpret_cast<void**>(&parent_accessible)),
534  S_OK);
535  EXPECT_EQ(parent_accessible, node0_accessible);
536  }
537 
538  {
539  // Look up only child of node2 (node3), a static text node.
540  varchild.lVal = 1;
541  IDispatch* node3_dispatch = nullptr;
542  ASSERT_EQ(node2_accessible->get_accChild(varchild, &node3_dispatch), S_OK);
543  ASSERT_TRUE(node3_dispatch != nullptr);
544  IAccessible* node3_accessible = nullptr;
545  ASSERT_EQ(node3_dispatch->QueryInterface(
546  IID_IAccessible, reinterpret_cast<void**>(&node3_accessible)),
547  S_OK);
548  ASSERT_TRUE(node3_accessible != nullptr);
549 
550  // Verify node name matches our label.
551  varchild.lVal = CHILDID_SELF;
552  BSTR bname = nullptr;
553  ASSERT_EQ(node3_accessible->get_accName(varchild, &bname), S_OK);
554  std::string name(_com_util::ConvertBSTRToString(bname));
555  EXPECT_EQ(name, "city");
556 
557  // Verify node value matches.
558  BSTR bvalue = nullptr;
559  ASSERT_EQ(node3_accessible->get_accValue(varchild, &bvalue), S_OK);
560  std::string value(_com_util::ConvertBSTRToString(bvalue));
561  EXPECT_EQ(value, "Uji");
562 
563  // Verify node type is static text.
564  VARIANT varrole{};
565  varrole.vt = VT_I4;
566  ASSERT_EQ(node3_accessible->get_accRole(varchild, &varrole), S_OK);
567  EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_STATICTEXT);
568 
569  // Verify the parent node is node2.
570  IDispatch* parent_dispatch;
571  node3_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, node2_accessible);
578  }
579 }
580 
581 // Flutter used to assume that the accessibility root had ID 0.
582 // In a multi-view world, each view has its own accessibility root
583 // with a globally unique node ID.
584 //
585 // node1
586 // |
587 // node2
588 //
589 // node1 is a grouping node, node0 is a static text node.
590 TEST(FlutterWindowsViewTest, NonZeroSemanticsRoot) {
591  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
592  EngineModifier modifier(engine.get());
593  modifier.embedder_api().UpdateSemanticsEnabled =
594  [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
595  return kSuccess;
596  };
597 
598  auto window_binding_handler =
599  std::make_unique<NiceMock<MockWindowBindingHandler>>();
600  FlutterWindowsView view(std::move(window_binding_handler));
601  view.SetEngine(engine.get());
602 
603  // Enable semantics to instantiate accessibility bridge.
604  view.OnUpdateSemanticsEnabled(true);
605 
606  auto bridge = view.accessibility_bridge().lock();
607  ASSERT_TRUE(bridge);
608 
609  // Add root node.
610  FlutterSemanticsNode2 node1{sizeof(FlutterSemanticsNode2), 1};
611  std::vector<int32_t> node1_children{2};
612  node1.child_count = node1_children.size();
613  node1.children_in_traversal_order = node1_children.data();
614  node1.children_in_hit_test_order = node1_children.data();
615 
616  FlutterSemanticsNode2 node2{sizeof(FlutterSemanticsNode2), 2};
617  node2.label = "prefecture";
618  node2.value = "Kyoto";
619 
620  bridge->AddFlutterSemanticsNodeUpdate(node1);
621  bridge->AddFlutterSemanticsNodeUpdate(node2);
622  bridge->CommitUpdates();
623 
624  // Look up the root windows node delegate.
625  auto root_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock();
626  ASSERT_TRUE(root_delegate);
627  EXPECT_EQ(root_delegate->GetChildCount(), 1);
628 
629  // Look up the child node delegate
630  auto child_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(2).lock();
631  ASSERT_TRUE(child_delegate);
632  EXPECT_EQ(child_delegate->GetChildCount(), 0);
633 
634  // Ensure a node with ID 0 does not exist.
635  auto fake_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
636  ASSERT_FALSE(fake_delegate);
637 
638  // Get the root's native IAccessible object.
639  IAccessible* node1_accessible = root_delegate->GetNativeViewAccessible();
640  ASSERT_TRUE(node1_accessible != nullptr);
641 
642  // Property lookups will be made against this node itself.
643  VARIANT varchild{};
644  varchild.vt = VT_I4;
645  varchild.lVal = CHILDID_SELF;
646 
647  // Verify node type is a group.
648  VARIANT varrole{};
649  varrole.vt = VT_I4;
650  ASSERT_EQ(node1_accessible->get_accRole(varchild, &varrole), S_OK);
651  EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_GROUPING);
652 
653  // Verify child count.
654  long node1_child_count = 0;
655  ASSERT_EQ(node1_accessible->get_accChildCount(&node1_child_count), S_OK);
656  EXPECT_EQ(node1_child_count, 1);
657 
658  {
659  // Look up first child of node1 (node0), a static text node.
660  varchild.lVal = 1;
661  IDispatch* node2_dispatch = nullptr;
662  ASSERT_EQ(node1_accessible->get_accChild(varchild, &node2_dispatch), S_OK);
663  ASSERT_TRUE(node2_dispatch != nullptr);
664  IAccessible* node2_accessible = nullptr;
665  ASSERT_EQ(node2_dispatch->QueryInterface(
666  IID_IAccessible, reinterpret_cast<void**>(&node2_accessible)),
667  S_OK);
668  ASSERT_TRUE(node2_accessible != nullptr);
669 
670  // Verify node name matches our label.
671  varchild.lVal = CHILDID_SELF;
672  BSTR bname = nullptr;
673  ASSERT_EQ(node2_accessible->get_accName(varchild, &bname), S_OK);
674  std::string name(_com_util::ConvertBSTRToString(bname));
675  EXPECT_EQ(name, "prefecture");
676 
677  // Verify node value matches.
678  BSTR bvalue = nullptr;
679  ASSERT_EQ(node2_accessible->get_accValue(varchild, &bvalue), S_OK);
680  std::string value(_com_util::ConvertBSTRToString(bvalue));
681  EXPECT_EQ(value, "Kyoto");
682 
683  // Verify node type is static text.
684  VARIANT varrole{};
685  varrole.vt = VT_I4;
686  ASSERT_EQ(node2_accessible->get_accRole(varchild, &varrole), S_OK);
687  EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_STATICTEXT);
688 
689  // Verify the parent node is the root.
690  IDispatch* parent_dispatch;
691  node2_accessible->get_accParent(&parent_dispatch);
692  IAccessible* parent_accessible;
693  ASSERT_EQ(
694  parent_dispatch->QueryInterface(
695  IID_IAccessible, reinterpret_cast<void**>(&parent_accessible)),
696  S_OK);
697  EXPECT_EQ(parent_accessible, node1_accessible);
698  }
699 }
700 
701 // Verify the native IAccessible accHitTest method returns the correct
702 // IAccessible COM object for the given coordinates.
703 //
704 // +-----------+
705 // | | |
706 // node0 | | B |
707 // / \ | A |-----|
708 // node1 node2 | | C |
709 // | | | |
710 // node3 +-----------+
711 //
712 // node0 and node2 are grouping nodes. node1 and node2 are static text nodes.
713 //
714 // node0 is located at 0,0 with size 500x500. It spans areas A, B, and C.
715 // node1 is located at 0,0 with size 250x500. It spans area A.
716 // node2 is located at 250,0 with size 250x500. It spans areas B and C.
717 // node3 is located at 250,250 with size 250x250. It spans area C.
718 TEST(FlutterWindowsViewTest, AccessibilityHitTesting) {
719  constexpr FlutterTransformation kIdentityTransform = {1, 0, 0, //
720  0, 1, 0, //
721  0, 0, 1};
722 
723  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
724  EngineModifier modifier(engine.get());
725  modifier.embedder_api().UpdateSemanticsEnabled =
726  [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
727  return kSuccess;
728  };
729 
730  auto window_binding_handler =
731  std::make_unique<NiceMock<MockWindowBindingHandler>>();
732  FlutterWindowsView view(std::move(window_binding_handler));
733  view.SetEngine(engine.get());
734 
735  // Enable semantics to instantiate accessibility bridge.
736  view.OnUpdateSemanticsEnabled(true);
737 
738  auto bridge = view.accessibility_bridge().lock();
739  ASSERT_TRUE(bridge);
740 
741  // Add root node at origin. Size 500x500.
742  FlutterSemanticsNode2 node0{sizeof(FlutterSemanticsNode2), 0};
743  std::vector<int32_t> node0_children{1, 2};
744  node0.rect = {0, 0, 500, 500};
745  node0.transform = kIdentityTransform;
746  node0.child_count = node0_children.size();
747  node0.children_in_traversal_order = node0_children.data();
748  node0.children_in_hit_test_order = node0_children.data();
749 
750  // Add node 1 located at 0,0 relative to node 0. Size 250x500.
751  FlutterSemanticsNode2 node1{sizeof(FlutterSemanticsNode2), 1};
752  node1.rect = {0, 0, 250, 500};
753  node1.transform = kIdentityTransform;
754  node1.label = "prefecture";
755  node1.value = "Kyoto";
756 
757  // Add node 2 located at 250,0 relative to node 0. Size 250x500.
758  FlutterSemanticsNode2 node2{sizeof(FlutterSemanticsNode2), 2};
759  std::vector<int32_t> node2_children{3};
760  node2.rect = {0, 0, 250, 500};
761  node2.transform = {1, 0, 250, 0, 1, 0, 0, 0, 1};
762  node2.child_count = node2_children.size();
763  node2.children_in_traversal_order = node2_children.data();
764  node2.children_in_hit_test_order = node2_children.data();
765 
766  // Add node 3 located at 0,250 relative to node 2. Size 250, 250.
767  FlutterSemanticsNode2 node3{sizeof(FlutterSemanticsNode2), 3};
768  node3.rect = {0, 0, 250, 250};
769  node3.transform = {1, 0, 0, 0, 1, 250, 0, 0, 1};
770  node3.label = "city";
771  node3.value = "Uji";
772 
773  bridge->AddFlutterSemanticsNodeUpdate(node0);
774  bridge->AddFlutterSemanticsNodeUpdate(node1);
775  bridge->AddFlutterSemanticsNodeUpdate(node2);
776  bridge->AddFlutterSemanticsNodeUpdate(node3);
777  bridge->CommitUpdates();
778 
779  // Look up the root windows node delegate.
780  auto node0_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
781  ASSERT_TRUE(node0_delegate);
782  auto node1_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock();
783  ASSERT_TRUE(node1_delegate);
784  auto node2_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(2).lock();
785  ASSERT_TRUE(node2_delegate);
786  auto node3_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(3).lock();
787  ASSERT_TRUE(node3_delegate);
788 
789  // Get the native IAccessible root object.
790  IAccessible* node0_accessible = node0_delegate->GetNativeViewAccessible();
791  ASSERT_TRUE(node0_accessible != nullptr);
792 
793  // Perform a hit test that should hit node 1.
794  VARIANT varchild{};
795  ASSERT_TRUE(SUCCEEDED(node0_accessible->accHitTest(150, 150, &varchild)));
796  EXPECT_EQ(varchild.vt, VT_DISPATCH);
797  EXPECT_EQ(varchild.pdispVal, node1_delegate->GetNativeViewAccessible());
798 
799  // Perform a hit test that should hit node 2.
800  varchild = {};
801  ASSERT_TRUE(SUCCEEDED(node0_accessible->accHitTest(450, 150, &varchild)));
802  EXPECT_EQ(varchild.vt, VT_DISPATCH);
803  EXPECT_EQ(varchild.pdispVal, node2_delegate->GetNativeViewAccessible());
804 
805  // Perform a hit test that should hit node 3.
806  varchild = {};
807  ASSERT_TRUE(SUCCEEDED(node0_accessible->accHitTest(450, 450, &varchild)));
808  EXPECT_EQ(varchild.vt, VT_DISPATCH);
809  EXPECT_EQ(varchild.pdispVal, node3_delegate->GetNativeViewAccessible());
810 }
811 
812 TEST(FlutterWindowsViewTest, WindowResizeTests) {
813  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
814  EngineModifier modifier(engine.get());
815 
816  auto window_binding_handler =
817  std::make_unique<NiceMock<MockWindowBindingHandler>>();
818  auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
819  std::unique_ptr<MockAngleSurfaceManager> surface_manager =
820  std::make_unique<MockAngleSurfaceManager>();
821 
822  EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled)
823  .WillOnce(Return(true));
824  EXPECT_CALL(
825  *surface_manager.get(),
826  ResizeSurface(_, /*width=*/500, /*height=*/500, /*enable_vsync=*/false))
827  .Times(1);
828  EXPECT_CALL(*surface_manager.get(), DestroySurface).Times(1);
829 
830  FlutterWindowsView view(std::move(window_binding_handler),
831  std::move(windows_proc_table));
832  modifier.SetSurfaceManager(std::move(surface_manager));
833  view.SetEngine(engine.get());
834 
835  fml::AutoResetWaitableEvent metrics_sent_latch;
836  modifier.embedder_api().SendWindowMetricsEvent = MOCK_ENGINE_PROC(
837  SendWindowMetricsEvent,
838  ([&metrics_sent_latch](auto engine,
839  const FlutterWindowMetricsEvent* event) {
840  metrics_sent_latch.Signal();
841  return kSuccess;
842  }));
843 
844  fml::AutoResetWaitableEvent resized_latch;
845  std::thread([&resized_latch, &view]() {
846  // Start the window resize. This sends the new window metrics
847  // and then blocks until another thread completes the window resize.
848  view.OnWindowSizeChanged(500, 500);
849  resized_latch.Signal();
850  }).detach();
851 
852  // Wait until the platform thread has started the window resize.
853  metrics_sent_latch.Wait();
854 
855  // Complete the window resize by requesting a buffer with the new window size.
856  view.GetFrameBufferId(500, 500);
857  resized_latch.Wait();
858 }
859 
860 TEST(FlutterWindowsViewTest, WindowRepaintTests) {
861  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
862  EngineModifier modifier(engine.get());
863 
864  FlutterWindowsView view(std::make_unique<flutter::FlutterWindow>(100, 100));
865  view.SetEngine(engine.get());
866 
867  bool schedule_frame_called = false;
868  modifier.embedder_api().ScheduleFrame =
869  MOCK_ENGINE_PROC(ScheduleFrame, ([&schedule_frame_called](auto engine) {
870  schedule_frame_called = true;
871  return kSuccess;
872  }));
873 
874  view.OnWindowRepaint();
875  EXPECT_TRUE(schedule_frame_called);
876 }
877 
878 // Ensure that checkboxes have their checked status set apropriately
879 // Previously, only Radios could have this flag updated
880 // Resulted in the issue seen at
881 // https://github.com/flutter/flutter/issues/96218
882 // This test ensures that the native state of Checkboxes on Windows,
883 // specifically, is updated as desired.
884 TEST(FlutterWindowsViewTest, CheckboxNativeState) {
885  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
886  EngineModifier modifier(engine.get());
887  modifier.embedder_api().UpdateSemanticsEnabled =
888  [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
889  return kSuccess;
890  };
891 
892  auto window_binding_handler =
893  std::make_unique<NiceMock<MockWindowBindingHandler>>();
894  FlutterWindowsView view(std::move(window_binding_handler));
895  view.SetEngine(engine.get());
896 
897  // Enable semantics to instantiate accessibility bridge.
898  view.OnUpdateSemanticsEnabled(true);
899 
900  auto bridge = view.accessibility_bridge().lock();
901  ASSERT_TRUE(bridge);
902 
903  FlutterSemanticsNode2 root{sizeof(FlutterSemanticsNode2), 0};
904  root.id = 0;
905  root.label = "root";
906  root.hint = "";
907  root.value = "";
908  root.increased_value = "";
909  root.decreased_value = "";
910  root.child_count = 0;
911  root.custom_accessibility_actions_count = 0;
912  root.flags = static_cast<FlutterSemanticsFlag>(
913  FlutterSemanticsFlag::kFlutterSemanticsFlagHasCheckedState |
914  FlutterSemanticsFlag::kFlutterSemanticsFlagIsChecked);
915  bridge->AddFlutterSemanticsNodeUpdate(root);
916 
917  bridge->CommitUpdates();
918 
919  {
920  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
921  EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kCheckBox);
922  EXPECT_EQ(root_node->GetData().GetCheckedState(),
923  ax::mojom::CheckedState::kTrue);
924 
925  // Get the IAccessible for the root node.
926  IAccessible* native_view = root_node->GetNativeViewAccessible();
927  ASSERT_TRUE(native_view != nullptr);
928 
929  // Look up against the node itself (not one of its children).
930  VARIANT varchild = {};
931  varchild.vt = VT_I4;
932 
933  // Verify the checkbox is checked.
934  varchild.lVal = CHILDID_SELF;
935  VARIANT native_state = {};
936  ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
937  EXPECT_TRUE(native_state.lVal & STATE_SYSTEM_CHECKED);
938 
939  // Perform similar tests for UIA value;
940  IRawElementProviderSimple* uia_node;
941  native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
942  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
943  UIA_ToggleToggleStatePropertyId, &native_state)));
944  EXPECT_EQ(native_state.lVal, ToggleState_On);
945 
946  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
947  UIA_AriaPropertiesPropertyId, &native_state)));
948  EXPECT_NE(std::wcsstr(native_state.bstrVal, L"checked=true"), nullptr);
949  }
950 
951  // Test unchecked too.
952  root.flags = static_cast<FlutterSemanticsFlag>(
953  FlutterSemanticsFlag::kFlutterSemanticsFlagHasCheckedState);
954  bridge->AddFlutterSemanticsNodeUpdate(root);
955  bridge->CommitUpdates();
956 
957  {
958  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
959  EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kCheckBox);
960  EXPECT_EQ(root_node->GetData().GetCheckedState(),
961  ax::mojom::CheckedState::kFalse);
962 
963  // Get the IAccessible for the root node.
964  IAccessible* native_view = root_node->GetNativeViewAccessible();
965  ASSERT_TRUE(native_view != nullptr);
966 
967  // Look up against the node itself (not one of its children).
968  VARIANT varchild = {};
969  varchild.vt = VT_I4;
970 
971  // Verify the checkbox is unchecked.
972  varchild.lVal = CHILDID_SELF;
973  VARIANT native_state = {};
974  ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
975  EXPECT_FALSE(native_state.lVal & STATE_SYSTEM_CHECKED);
976 
977  // Perform similar tests for UIA value;
978  IRawElementProviderSimple* uia_node;
979  native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
980  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
981  UIA_ToggleToggleStatePropertyId, &native_state)));
982  EXPECT_EQ(native_state.lVal, ToggleState_Off);
983 
984  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
985  UIA_AriaPropertiesPropertyId, &native_state)));
986  EXPECT_NE(std::wcsstr(native_state.bstrVal, L"checked=false"), nullptr);
987  }
988 
989  // Now check mixed state.
990  root.flags = static_cast<FlutterSemanticsFlag>(
991  FlutterSemanticsFlag::kFlutterSemanticsFlagHasCheckedState |
992  FlutterSemanticsFlag::kFlutterSemanticsFlagIsCheckStateMixed);
993  bridge->AddFlutterSemanticsNodeUpdate(root);
994  bridge->CommitUpdates();
995 
996  {
997  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
998  EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kCheckBox);
999  EXPECT_EQ(root_node->GetData().GetCheckedState(),
1000  ax::mojom::CheckedState::kMixed);
1001 
1002  // Get the IAccessible for the root node.
1003  IAccessible* native_view = root_node->GetNativeViewAccessible();
1004  ASSERT_TRUE(native_view != nullptr);
1005 
1006  // Look up against the node itself (not one of its children).
1007  VARIANT varchild = {};
1008  varchild.vt = VT_I4;
1009 
1010  // Verify the checkbox is mixed.
1011  varchild.lVal = CHILDID_SELF;
1012  VARIANT native_state = {};
1013  ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
1014  EXPECT_TRUE(native_state.lVal & STATE_SYSTEM_MIXED);
1015 
1016  // Perform similar tests for UIA value;
1017  IRawElementProviderSimple* uia_node;
1018  native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
1019  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
1020  UIA_ToggleToggleStatePropertyId, &native_state)));
1021  EXPECT_EQ(native_state.lVal, ToggleState_Indeterminate);
1022 
1023  ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
1024  UIA_AriaPropertiesPropertyId, &native_state)));
1025  EXPECT_NE(std::wcsstr(native_state.bstrVal, L"checked=mixed"), nullptr);
1026  }
1027 }
1028 
1029 // Ensure that switches have their toggle status set apropriately
1030 TEST(FlutterWindowsViewTest, SwitchNativeState) {
1031  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1032  EngineModifier modifier(engine.get());
1033  modifier.embedder_api().UpdateSemanticsEnabled =
1034  [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
1035  return kSuccess;
1036  };
1037 
1038  auto window_binding_handler =
1039  std::make_unique<NiceMock<MockWindowBindingHandler>>();
1040  FlutterWindowsView view(std::move(window_binding_handler));
1041  view.SetEngine(engine.get());
1042 
1043  // Enable semantics to instantiate accessibility bridge.
1044  view.OnUpdateSemanticsEnabled(true);
1045 
1046  auto bridge = view.accessibility_bridge().lock();
1047  ASSERT_TRUE(bridge);
1048 
1049  FlutterSemanticsNode2 root{sizeof(FlutterSemanticsNode2), 0};
1050  root.id = 0;
1051  root.label = "root";
1052  root.hint = "";
1053  root.value = "";
1054  root.increased_value = "";
1055  root.decreased_value = "";
1056  root.child_count = 0;
1057  root.custom_accessibility_actions_count = 0;
1058  root.flags = static_cast<FlutterSemanticsFlag>(
1059  FlutterSemanticsFlag::kFlutterSemanticsFlagHasToggledState |
1060  FlutterSemanticsFlag::kFlutterSemanticsFlagIsToggled);
1061  bridge->AddFlutterSemanticsNodeUpdate(root);
1062 
1063  bridge->CommitUpdates();
1064 
1065  {
1066  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
1067  EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kSwitch);
1068  EXPECT_EQ(root_node->GetData().GetCheckedState(),
1069  ax::mojom::CheckedState::kTrue);
1070 
1071  // Get the IAccessible for the root node.
1072  IAccessible* native_view = root_node->GetNativeViewAccessible();
1073  ASSERT_TRUE(native_view != nullptr);
1074 
1075  // Look up against the node itself (not one of its children).
1076  VARIANT varchild = {};
1077  varchild.vt = VT_I4;
1078 
1079  varchild.lVal = CHILDID_SELF;
1080  VARIANT varrole = {};
1081 
1082  // Verify the role of the switch is CHECKBUTTON
1083  ASSERT_EQ(native_view->get_accRole(varchild, &varrole), S_OK);
1084  ASSERT_EQ(varrole.lVal, ROLE_SYSTEM_CHECKBUTTON);
1085 
1086  // Verify the switch is pressed.
1087  VARIANT native_state = {};
1088  ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
1089  EXPECT_TRUE(native_state.lVal & STATE_SYSTEM_PRESSED);
1090  EXPECT_TRUE(native_state.lVal & STATE_SYSTEM_CHECKED);
1091 
1092  // Test similarly on UIA node.
1093  IRawElementProviderSimple* uia_node;
1094  native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
1095  ASSERT_EQ(uia_node->GetPropertyValue(UIA_ControlTypePropertyId, &varrole),
1096  S_OK);
1097  EXPECT_EQ(varrole.lVal, UIA_ButtonControlTypeId);
1098  ASSERT_EQ(uia_node->GetPropertyValue(UIA_ToggleToggleStatePropertyId,
1099  &native_state),
1100  S_OK);
1101  EXPECT_EQ(native_state.lVal, ToggleState_On);
1102  ASSERT_EQ(
1103  uia_node->GetPropertyValue(UIA_AriaPropertiesPropertyId, &native_state),
1104  S_OK);
1105  EXPECT_NE(std::wcsstr(native_state.bstrVal, L"pressed=true"), nullptr);
1106  }
1107 
1108  // Test unpressed too.
1109  root.flags = static_cast<FlutterSemanticsFlag>(
1110  FlutterSemanticsFlag::kFlutterSemanticsFlagHasToggledState);
1111  bridge->AddFlutterSemanticsNodeUpdate(root);
1112  bridge->CommitUpdates();
1113 
1114  {
1115  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
1116  EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kSwitch);
1117  EXPECT_EQ(root_node->GetData().GetCheckedState(),
1118  ax::mojom::CheckedState::kFalse);
1119 
1120  // Get the IAccessible for the root node.
1121  IAccessible* native_view = root_node->GetNativeViewAccessible();
1122  ASSERT_TRUE(native_view != nullptr);
1123 
1124  // Look up against the node itself (not one of its children).
1125  VARIANT varchild = {};
1126  varchild.vt = VT_I4;
1127 
1128  // Verify the switch is not pressed.
1129  varchild.lVal = CHILDID_SELF;
1130  VARIANT native_state = {};
1131  ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
1132  EXPECT_FALSE(native_state.lVal & STATE_SYSTEM_PRESSED);
1133  EXPECT_FALSE(native_state.lVal & STATE_SYSTEM_CHECKED);
1134 
1135  // Test similarly on UIA node.
1136  IRawElementProviderSimple* uia_node;
1137  native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
1138  ASSERT_EQ(uia_node->GetPropertyValue(UIA_ToggleToggleStatePropertyId,
1139  &native_state),
1140  S_OK);
1141  EXPECT_EQ(native_state.lVal, ToggleState_Off);
1142  ASSERT_EQ(
1143  uia_node->GetPropertyValue(UIA_AriaPropertiesPropertyId, &native_state),
1144  S_OK);
1145  EXPECT_NE(std::wcsstr(native_state.bstrVal, L"pressed=false"), nullptr);
1146  }
1147 }
1148 
1149 TEST(FlutterWindowsViewTest, TooltipNodeData) {
1150  std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1151  EngineModifier modifier(engine.get());
1152  modifier.embedder_api().UpdateSemanticsEnabled =
1153  [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
1154  return kSuccess;
1155  };
1156 
1157  auto window_binding_handler =
1158  std::make_unique<NiceMock<MockWindowBindingHandler>>();
1159  FlutterWindowsView view(std::move(window_binding_handler));
1160  view.SetEngine(engine.get());
1161 
1162  // Enable semantics to instantiate accessibility bridge.
1163  view.OnUpdateSemanticsEnabled(true);
1164 
1165  auto bridge = view.accessibility_bridge().lock();
1166  ASSERT_TRUE(bridge);
1167 
1168  FlutterSemanticsNode2 root{sizeof(FlutterSemanticsNode2), 0};
1169  root.id = 0;
1170  root.label = "root";
1171  root.hint = "";
1172  root.value = "";
1173  root.increased_value = "";
1174  root.decreased_value = "";
1175  root.tooltip = "tooltip";
1176  root.child_count = 0;
1177  root.custom_accessibility_actions_count = 0;
1178  root.flags = static_cast<FlutterSemanticsFlag>(
1179  FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField);
1180  bridge->AddFlutterSemanticsNodeUpdate(root);
1181 
1182  bridge->CommitUpdates();
1183  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
1184  std::string tooltip = root_node->GetData().GetStringAttribute(
1185  ax::mojom::StringAttribute::kTooltip);
1186  EXPECT_EQ(tooltip, "tooltip");
1187 
1188  // Check that MSAA name contains the tooltip.
1189  IAccessible* native_view = bridge->GetFlutterPlatformNodeDelegateFromID(0)
1190  .lock()
1191  ->GetNativeViewAccessible();
1192  VARIANT varchild = {.vt = VT_I4, .lVal = CHILDID_SELF};
1193  BSTR bname;
1194  ASSERT_EQ(native_view->get_accName(varchild, &bname), S_OK);
1195  EXPECT_NE(std::wcsstr(bname, L"tooltip"), nullptr);
1196 
1197  // Check that UIA help text is equal to the tooltip.
1198  IRawElementProviderSimple* uia_node;
1199  native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
1200  VARIANT varname{};
1201  ASSERT_EQ(uia_node->GetPropertyValue(UIA_HelpTextPropertyId, &varname), S_OK);
1202  std::string uia_tooltip = _com_util::ConvertBSTRToString(varname.bstrVal);
1203  EXPECT_EQ(uia_tooltip, "tooltip");
1204 }
1205 
1206 // Don't block until the v-blank if it is disabled by the window.
1207 // The surface is updated on the platform thread at startup.
1208 TEST(FlutterWindowsViewTest, DisablesVSyncAtStartup) {
1209  std::unique_ptr<MockFlutterWindowsEngine> engine =
1210  std::make_unique<MockFlutterWindowsEngine>();
1211  auto window_binding_handler =
1212  std::make_unique<NiceMock<MockWindowBindingHandler>>();
1213  auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
1214  std::unique_ptr<MockAngleSurfaceManager> surface_manager =
1215  std::make_unique<MockAngleSurfaceManager>();
1216 
1217  EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(false));
1218  EXPECT_CALL(*engine.get(), PostRasterThreadTask).Times(0);
1219 
1220  EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled)
1221  .WillOnce(Return(true));
1222 
1223  EngineModifier modifier(engine.get());
1224  FlutterWindowsView view(std::move(window_binding_handler),
1225  std::move(windows_proc_table));
1226 
1227  InSequence s;
1228  EXPECT_CALL(*surface_manager.get(), CreateSurface(_, _, _))
1229  .Times(1)
1230  .WillOnce(Return(true));
1231  EXPECT_CALL(*surface_manager.get(), SetVSyncEnabled(false)).Times(1);
1232  EXPECT_CALL(*surface_manager.get(), ClearCurrent).WillOnce(Return(true));
1233 
1234  EXPECT_CALL(*engine.get(), Stop).Times(1);
1235  EXPECT_CALL(*surface_manager.get(), DestroySurface).Times(1);
1236 
1237  modifier.SetSurfaceManager(std::move(surface_manager));
1238  view.SetEngine(engine.get());
1239 
1240  view.CreateRenderSurface();
1241 }
1242 
1243 // Blocks until the v-blank if it is enabled by the window.
1244 // The surface is updated on the platform thread at startup.
1245 TEST(FlutterWindowsViewTest, EnablesVSyncAtStartup) {
1246  std::unique_ptr<MockFlutterWindowsEngine> engine =
1247  std::make_unique<MockFlutterWindowsEngine>();
1248  auto window_binding_handler =
1249  std::make_unique<NiceMock<MockWindowBindingHandler>>();
1250  auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
1251  std::unique_ptr<MockAngleSurfaceManager> surface_manager =
1252  std::make_unique<MockAngleSurfaceManager>();
1253 
1254  EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(false));
1255  EXPECT_CALL(*engine.get(), PostRasterThreadTask).Times(0);
1256  EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled)
1257  .WillOnce(Return(false));
1258 
1259  EngineModifier modifier(engine.get());
1260  FlutterWindowsView view(std::move(window_binding_handler),
1261  std::move(windows_proc_table));
1262 
1263  InSequence s;
1264  EXPECT_CALL(*surface_manager.get(), CreateSurface(_, _, _))
1265  .Times(1)
1266  .WillOnce(Return(true));
1267  EXPECT_CALL(*surface_manager.get(), SetVSyncEnabled(true)).Times(1);
1268  EXPECT_CALL(*surface_manager.get(), ClearCurrent).WillOnce(Return(true));
1269 
1270  EXPECT_CALL(*engine.get(), Stop).Times(1);
1271  EXPECT_CALL(*surface_manager.get(), DestroySurface).Times(1);
1272 
1273  modifier.SetSurfaceManager(std::move(surface_manager));
1274  view.SetEngine(engine.get());
1275 
1276  view.CreateRenderSurface();
1277 }
1278 
1279 // Don't block until the v-blank if it is disabled by the window.
1280 // The surface is updated on the raster thread if the engine is running.
1281 TEST(FlutterWindowsViewTest, DisablesVSyncAfterStartup) {
1282  std::unique_ptr<MockFlutterWindowsEngine> engine =
1283  std::make_unique<MockFlutterWindowsEngine>();
1284  auto window_binding_handler =
1285  std::make_unique<NiceMock<MockWindowBindingHandler>>();
1286  auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
1287  std::unique_ptr<MockAngleSurfaceManager> surface_manager =
1288  std::make_unique<MockAngleSurfaceManager>();
1289 
1290  EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(true));
1291  EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled)
1292  .WillOnce(Return(true));
1293 
1294  EngineModifier modifier(engine.get());
1295  FlutterWindowsView view(std::move(window_binding_handler),
1296  std::move(windows_proc_table));
1297 
1298  InSequence s;
1299  EXPECT_CALL(*surface_manager.get(), CreateSurface(_, _, _))
1300  .Times(1)
1301  .WillOnce(Return(true));
1302  EXPECT_CALL(*engine.get(), PostRasterThreadTask)
1303  .WillOnce([](fml::closure callback) {
1304  callback();
1305  return true;
1306  });
1307  EXPECT_CALL(*surface_manager.get(), SetVSyncEnabled(false)).Times(1);
1308  EXPECT_CALL(*surface_manager.get(), ClearCurrent).Times(0);
1309 
1310  EXPECT_CALL(*engine.get(), Stop).Times(1);
1311  EXPECT_CALL(*surface_manager.get(), DestroySurface).Times(1);
1312 
1313  modifier.SetSurfaceManager(std::move(surface_manager));
1314  view.SetEngine(engine.get());
1315 
1316  view.CreateRenderSurface();
1317 }
1318 
1319 // Blocks until the v-blank if it is enabled by the window.
1320 // The surface is updated on the raster thread if the engine is running.
1321 TEST(FlutterWindowsViewTest, EnablesVSyncAfterStartup) {
1322  std::unique_ptr<MockFlutterWindowsEngine> engine =
1323  std::make_unique<MockFlutterWindowsEngine>();
1324  auto window_binding_handler =
1325  std::make_unique<NiceMock<MockWindowBindingHandler>>();
1326  auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
1327  std::unique_ptr<MockAngleSurfaceManager> surface_manager =
1328  std::make_unique<MockAngleSurfaceManager>();
1329 
1330  EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(true));
1331 
1332  EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled)
1333  .WillOnce(Return(false));
1334 
1335  EngineModifier modifier(engine.get());
1336  FlutterWindowsView view(std::move(window_binding_handler),
1337  std::move(windows_proc_table));
1338 
1339  InSequence s;
1340  EXPECT_CALL(*surface_manager.get(), CreateSurface(_, _, _))
1341  .Times(1)
1342  .WillOnce(Return(true));
1343  EXPECT_CALL(*engine.get(), PostRasterThreadTask)
1344  .WillOnce([](fml::closure callback) {
1345  callback();
1346  return true;
1347  });
1348  EXPECT_CALL(*surface_manager.get(), SetVSyncEnabled(true)).Times(1);
1349  EXPECT_CALL(*surface_manager.get(), ClearCurrent).Times(0);
1350 
1351  EXPECT_CALL(*engine.get(), Stop).Times(1);
1352  EXPECT_CALL(*surface_manager.get(), DestroySurface).Times(1);
1353 
1354  modifier.SetSurfaceManager(std::move(surface_manager));
1355  view.SetEngine(engine.get());
1356 
1357  view.CreateRenderSurface();
1358 }
1359 
1360 // Desktop Window Manager composition can be disabled on Windows 7.
1361 // If this happens, the app must synchronize with the vsync to prevent
1362 // screen tearing.
1363 TEST(FlutterWindowsViewTest, UpdatesVSyncOnDwmUpdates) {
1364  std::unique_ptr<MockFlutterWindowsEngine> engine =
1365  std::make_unique<MockFlutterWindowsEngine>();
1366  auto window_binding_handler =
1367  std::make_unique<NiceMock<MockWindowBindingHandler>>();
1368  auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
1369  std::unique_ptr<MockAngleSurfaceManager> surface_manager =
1370  std::make_unique<MockAngleSurfaceManager>();
1371 
1372  EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(true));
1373 
1374  EXPECT_CALL(*engine.get(), PostRasterThreadTask)
1375  .WillRepeatedly([](fml::closure callback) {
1376  callback();
1377  return true;
1378  });
1379 
1380  EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled)
1381  .WillOnce(Return(false))
1382  .WillOnce(Return(true));
1383 
1384  EXPECT_CALL(*surface_manager.get(), ClearCurrent).Times(0);
1385 
1386  EngineModifier modifier(engine.get());
1387  FlutterWindowsView view(std::move(window_binding_handler),
1388  std::move(windows_proc_table));
1389 
1390  InSequence s;
1391  EXPECT_CALL(*surface_manager.get(), SetVSyncEnabled(true)).Times(1);
1392  EXPECT_CALL(*surface_manager.get(), SetVSyncEnabled(false)).Times(1);
1393 
1394  EXPECT_CALL(*engine.get(), Stop).Times(1);
1395  EXPECT_CALL(*surface_manager.get(), DestroySurface).Times(1);
1396 
1397  modifier.SetSurfaceManager(std::move(surface_manager));
1398  view.SetEngine(engine.get());
1399 
1402 }
1403 
1404 } // namespace testing
1405 } // namespace flutter
FlutterDesktopEngineProperties::aot_library_path
const wchar_t * aot_library_path
Definition: flutter_windows.h:51
flutter::FlutterWindowsView::CreateRenderSurface
void CreateRenderSurface()
Definition: flutter_windows_view.cc:622
flutter::FlutterWindowsView::OnWindowSizeChanged
void OnWindowSizeChanged(size_t width, size_t height) override
Definition: flutter_windows_view.cc:143
flutter_windows_texture_registrar.h
flutter::FlutterWindowsView
Definition: flutter_windows_view.h:35
flutter::JsonMessageCodec::GetInstance
static const JsonMessageCodec & GetInstance()
Definition: json_message_codec.cc:17
FlutterDesktopEngineProperties
Definition: flutter_windows.h:36
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:49
FlutterDesktopEngineProperties::icu_data_path
const wchar_t * icu_data_path
Definition: flutter_windows.h:45
json_message_codec.h
flutter::FlutterWindowsView::OnWindowRepaint
void OnWindowRepaint() override
Definition: flutter_windows_view.cc:177
flutter::FlutterWindowsEngine::OnDwmCompositionChanged
void OnDwmCompositionChanged()
Definition: flutter_windows_engine.cc:805
flutter_windows_view.h
flutter_window.h
flutter::testing::kScanCodeKeyA
constexpr uint64_t kScanCodeKeyA
Definition: flutter_windows_view_unittests.cc:38
flutter
Definition: accessibility_bridge_windows.cc:11
flutter::FlutterWindowsView::SetEngine
void SetEngine(FlutterWindowsEngine *engine)
Definition: flutter_windows_view.cc:95
flutter_windows_engine.h
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:235
FlutterDesktopEngineProperties::assets_path
const wchar_t * assets_path
Definition: flutter_windows.h:40
flutter::testing::kVirtualKeyA
constexpr uint64_t kVirtualKeyA
Definition: flutter_windows_view_unittests.cc:39
flutter::FlutterWindowsView::GetEngine
FlutterWindowsEngine * GetEngine()
Definition: flutter_windows_view.cc:649
flutter::FlutterWindowsView::GetFrameBufferId
uint32_t GetFrameBufferId(size_t width, size_t height)
Definition: flutter_windows_view.cc:109
callback
FlutterDesktopBinaryReply callback
Definition: flutter_windows_view_unittests.cc:48
node_delegate
std::shared_ptr< FlutterPlatformNodeDelegateWindows > node_delegate
Definition: accessibility_bridge_windows_unittests.cc:32