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