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