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