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