Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
flutter_windows_view_unittests.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
6
7#include <UIAutomation.h>
8#include <comdef.h>
9#include <comutil.h>
10#include <oleacc.h>
11
12#include <future>
13#include <vector>
14
15#include "flutter/fml/logging.h"
31
32#include "gmock/gmock.h"
33#include "gtest/gtest.h"
34
35namespace flutter {
36namespace testing {
37
38using ::testing::_;
39using ::testing::InSequence;
40using ::testing::NiceMock;
41using ::testing::Return;
42
43constexpr uint64_t kScanCodeKeyA = 0x1e;
44constexpr uint64_t kVirtualKeyA = 0x41;
45
46namespace {
47
48// A struct to use as a FlutterPlatformMessageResponseHandle so it can keep the
49// callbacks and user data passed to the engine's
50// PlatformMessageCreateResponseHandle for use in the SendPlatformMessage
51// overridden function.
52struct TestResponseHandle {
54 void* user_data;
55};
56
57static bool test_response = false;
58
59constexpr uint64_t kKeyEventFromChannel = 0x11;
60constexpr uint64_t kKeyEventFromEmbedder = 0x22;
61static std::vector<int> key_event_logs;
62
63std::unique_ptr<std::vector<uint8_t>> keyHandlingResponse(bool handled) {
64 rapidjson::Document document;
65 auto& allocator = document.GetAllocator();
66 document.SetObject();
67 document.AddMember("handled", test_response, allocator);
69}
70
71// Returns a Flutter project with the required path values to create
72// a test engine.
73FlutterProjectBundle GetTestProject() {
74 FlutterDesktopEngineProperties properties = {};
75 properties.assets_path = L"C:\\foo\\flutter_assets";
76 properties.icu_data_path = L"C:\\foo\\icudtl.dat";
77 properties.aot_library_path = L"C:\\foo\\aot.so";
79
80 return FlutterProjectBundle{properties};
81}
82
83class ViewTestEGLManager : public egl::MockManager {
84 public:
85 ViewTestEGLManager() {
86 ON_CALL(*this, CreateWindowSurface).WillByDefault([](HWND, size_t, size_t) {
87 auto surface = std::make_unique<NiceMock<egl::MockWindowSurface>>();
88 ON_CALL(*surface, IsValid).WillByDefault(Return(true));
89 ON_CALL(*surface, MakeCurrent).WillByDefault(Return(true));
90 ON_CALL(*surface, SetVSyncEnabled).WillByDefault(Return(true));
91 ON_CALL(*surface, Destroy).WillByDefault(Return(true));
92 return surface;
93 });
94 ON_CALL(*this, render_context).WillByDefault(Return(&mock_context_));
95 ON_CALL(*this, resource_context).WillByDefault(Return(&mock_context_));
96 ON_CALL(mock_context_, ClearCurrent).WillByDefault(Return(true));
97 ON_CALL(mock_context_, MakeCurrent).WillByDefault(Return(true));
98 }
99
100 private:
101 NiceMock<egl::MockContext> mock_context_;
102};
103
104// Returns an engine instance configured with test project path values, and
105// overridden methods for sending platform messages, so that the engine can
106// respond as if the framework were connected.
107std::unique_ptr<FlutterWindowsEngine> GetTestEngine(
108 std::shared_ptr<WindowsProcTable> windows_proc_table = nullptr) {
109 auto engine = std::make_unique<FlutterWindowsEngine>(
110 GetTestProject(), std::move(windows_proc_table));
111
112 EngineModifier modifier(engine.get());
113 modifier.SetEGLManager(std::make_unique<NiceMock<ViewTestEGLManager>>());
114 modifier.embedder_api().Run = MOCK_ENGINE_PROC(
115 Run, ([](size_t version, const FlutterRendererConfig* config,
116 const FlutterProjectArgs* args, void* user_data,
118 *engine =
119 reinterpret_cast<FLUTTER_API_SYMBOL(FlutterEngine)>(0x12345678);
120 return kSuccess;
121 }));
122
123 modifier.embedder_api().Shutdown = MOCK_ENGINE_PROC(
124 Shutdown,
126
127 modifier.embedder_api().UpdateLocales = MOCK_ENGINE_PROC(
128 UpdateLocales, ([](FLUTTER_API_SYMBOL(FlutterEngine) engine,
129 const FlutterLocale** locales,
130 size_t locales_count) { return kSuccess; }));
131
132 modifier.embedder_api().UpdateAccessibilityFeatures = MOCK_ENGINE_PROC(
133 UpdateAccessibilityFeatures,
135 FlutterAccessibilityFeature flags) { return kSuccess; }));
136
137 modifier.embedder_api().PostRenderThreadTask = MOCK_ENGINE_PROC(
138 PostRenderThreadTask, ([](auto engine, auto callback, auto context) {
140 return kSuccess;
141 }));
142
143 auto key_response_controller = std::make_shared<MockKeyResponseController>();
144 key_response_controller->SetChannelResponse(
146 key_event_logs.push_back(kKeyEventFromChannel);
147 callback(test_response);
148 });
149 key_response_controller->SetEmbedderResponse(
150 [](const FlutterKeyEvent* event,
152 key_event_logs.push_back(kKeyEventFromEmbedder);
153 callback(test_response);
154 });
155 modifier.embedder_api().NotifyDisplayUpdate =
156 MOCK_ENGINE_PROC(NotifyDisplayUpdate,
157 ([engine_instance = engine.get()](
159 const FlutterEngineDisplaysUpdateType update_type,
160 const FlutterEngineDisplay* embedder_displays,
161 size_t display_count) { return kSuccess; }));
162
163 MockEmbedderApiForKeyboard(modifier, key_response_controller);
164
165 engine->Run();
166 return engine;
167}
168
169class MockFlutterWindowsEngine : public FlutterWindowsEngine {
170 public:
171 explicit MockFlutterWindowsEngine(
172 std::shared_ptr<WindowsProcTable> windows_proc_table = nullptr)
173 : FlutterWindowsEngine(GetTestProject(), std::move(windows_proc_table)) {}
174
175 MOCK_METHOD(bool, running, (), (const));
176 MOCK_METHOD(bool, Stop, (), ());
177 MOCK_METHOD(void, RemoveView, (FlutterViewId view_id), ());
178 MOCK_METHOD(bool, PostRasterThreadTask, (fml::closure), (const));
179
180 private:
181 FML_DISALLOW_COPY_AND_ASSIGN(MockFlutterWindowsEngine);
182};
183
184} // namespace
185
186// Ensure that submenu buttons have their expanded/collapsed status set
187// apropriately.
188TEST(FlutterWindowsViewTest, SubMenuExpandedState) {
189 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
190 EngineModifier modifier(engine.get());
192 [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
193 return kSuccess;
194 };
195
196 auto window_binding_handler =
197 std::make_unique<NiceMock<MockWindowBindingHandler>>();
198 std::unique_ptr<FlutterWindowsView> view =
199 engine->CreateView(std::move(window_binding_handler),
200 /*is_sized_to_content=*/false, BoxConstraints());
201
202 // Enable semantics to instantiate accessibility bridge.
203 view->OnUpdateSemanticsEnabled(true);
204
205 auto bridge = view->accessibility_bridge().lock();
206 ASSERT_TRUE(bridge);
207
209 root.id = 0;
210 root.label = "root";
211 root.hint = "";
212 root.value = "";
213 root.increased_value = "";
214 root.decreased_value = "";
215 root.child_count = 0;
216 root.custom_accessibility_actions_count = 0;
217 auto flags = FlutterSemanticsFlags{
219 };
220 root.flags2 = &flags;
221
222 bridge->AddFlutterSemanticsNodeUpdate(root);
223
224 bridge->CommitUpdates();
225
226 {
227 auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
228 EXPECT_TRUE(root_node->GetData().HasState(ax::mojom::State::kExpanded));
229
230 // Get the IAccessible for the root node.
231 IAccessible* native_view = root_node->GetNativeViewAccessible();
232 ASSERT_TRUE(native_view != nullptr);
233
234 // Look up against the node itself (not one of its children).
235 VARIANT varchild = {};
236 varchild.vt = VT_I4;
237
238 // Verify the submenu is expanded.
239 varchild.lVal = CHILDID_SELF;
240 VARIANT native_state = {};
241 ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
242 EXPECT_TRUE(native_state.lVal & STATE_SYSTEM_EXPANDED);
243
244 // Perform similar tests for UIA value;
245 IRawElementProviderSimple* uia_node;
246 native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
247 ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
248 UIA_ExpandCollapseExpandCollapseStatePropertyId, &native_state)));
249 EXPECT_EQ(native_state.lVal, ExpandCollapseState_Expanded);
250
251 ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
252 UIA_AriaPropertiesPropertyId, &native_state)));
253 EXPECT_NE(std::wcsstr(native_state.bstrVal, L"expanded=true"), nullptr);
254 }
255
256 // Test collapsed too.
257 auto updated_flags = FlutterSemanticsFlags{
259 };
260 root.flags2 = &updated_flags;
261
262 bridge->AddFlutterSemanticsNodeUpdate(root);
263 bridge->CommitUpdates();
264
265 {
266 auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
267 EXPECT_TRUE(root_node->GetData().HasState(ax::mojom::State::kCollapsed));
268
269 // Get the IAccessible for the root node.
270 IAccessible* native_view = root_node->GetNativeViewAccessible();
271 ASSERT_TRUE(native_view != nullptr);
272
273 // Look up against the node itself (not one of its children).
274 VARIANT varchild = {};
275 varchild.vt = VT_I4;
276
277 // Verify the submenu is collapsed.
278 varchild.lVal = CHILDID_SELF;
279 VARIANT native_state = {};
280 ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
281 EXPECT_TRUE(native_state.lVal & STATE_SYSTEM_COLLAPSED);
282
283 // Perform similar tests for UIA value;
284 IRawElementProviderSimple* uia_node;
285 native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
286 ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
287 UIA_ExpandCollapseExpandCollapseStatePropertyId, &native_state)));
288 EXPECT_EQ(native_state.lVal, ExpandCollapseState_Collapsed);
289
290 ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
291 UIA_AriaPropertiesPropertyId, &native_state)));
292 EXPECT_NE(std::wcsstr(native_state.bstrVal, L"expanded=false"), nullptr);
293 }
294}
295
296// The view's surface must be destroyed after the engine is shutdown.
297// See: https://github.com/flutter/flutter/issues/124463
298TEST(FlutterWindowsViewTest, Shutdown) {
299 auto engine = std::make_unique<MockFlutterWindowsEngine>();
300 auto window_binding_handler =
301 std::make_unique<NiceMock<MockWindowBindingHandler>>();
302 auto egl_manager = std::make_unique<egl::MockManager>();
303 auto surface = std::make_unique<egl::MockWindowSurface>();
304 egl::MockContext render_context;
305
306 auto engine_ptr = engine.get();
307 auto surface_ptr = surface.get();
308 auto egl_manager_ptr = egl_manager.get();
309
310 EngineModifier modifier{engine.get()};
311 modifier.SetEGLManager(std::move(egl_manager));
312
313 InSequence s;
314 std::unique_ptr<FlutterWindowsView> view;
315
316 // Mock render surface initialization.
317 {
318 EXPECT_CALL(*egl_manager_ptr, CreateWindowSurface)
319 .WillOnce(Return(std::move(surface)));
320 EXPECT_CALL(*engine_ptr, running).WillOnce(Return(false));
321 EXPECT_CALL(*surface_ptr, IsValid).WillOnce(Return(true));
322 EXPECT_CALL(*surface_ptr, MakeCurrent).WillOnce(Return(true));
323 EXPECT_CALL(*surface_ptr, SetVSyncEnabled).WillOnce(Return(true));
324 EXPECT_CALL(*egl_manager_ptr, render_context)
325 .WillOnce(Return(&render_context));
326 EXPECT_CALL(render_context, ClearCurrent).WillOnce(Return(true));
327
328 view = engine->CreateView(std::move(window_binding_handler), false,
330 }
331
332 // The view must be removed before the surface can be destroyed.
333 {
334 auto view_id = view->view_id();
335 FlutterWindowsViewController controller{std::move(engine), std::move(view)};
336
337 EXPECT_CALL(*engine_ptr, running).WillOnce(Return(true));
338 EXPECT_CALL(*engine_ptr, RemoveView(view_id)).Times(1);
339 EXPECT_CALL(*engine_ptr, running).WillOnce(Return(true));
340 EXPECT_CALL(*engine_ptr, PostRasterThreadTask)
341 .WillOnce([](fml::closure callback) {
342 callback();
343 return true;
344 });
345 EXPECT_CALL(*surface_ptr, Destroy).Times(1);
346 }
347}
348
349TEST(FlutterWindowsViewTest, KeySequence) {
350 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
351
352 test_response = false;
353
354 std::unique_ptr<FlutterWindowsView> view =
355 engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
356 /*is_sized_to_content=*/false, BoxConstraints());
357
358 view->OnKey(kVirtualKeyA, kScanCodeKeyA, WM_KEYDOWN, 'a', false, false,
359 [](bool handled) {});
360
361 EXPECT_EQ(key_event_logs.size(), 2);
362 EXPECT_EQ(key_event_logs[0], kKeyEventFromEmbedder);
363 EXPECT_EQ(key_event_logs[1], kKeyEventFromChannel);
364
365 key_event_logs.clear();
366}
367
368TEST(FlutterWindowsViewTest, KeyEventCallback) {
369 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
370
371 std::unique_ptr<FlutterWindowsView> view =
372 engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
373 /*is_sized_to_content=*/false, BoxConstraints());
374
375 class MockCallback {
376 public:
377 MOCK_METHOD(void, Call, ());
378 };
379
380 NiceMock<MockCallback> callback_with_valid_view;
381 NiceMock<MockCallback> callback_with_invalid_view;
382
383 auto trigger_key_event = [&](NiceMock<MockCallback>& callback) {
384 view->OnKey(kVirtualKeyA, kScanCodeKeyA, WM_KEYDOWN, 'a', false, false,
385 [&](bool) { callback.Call(); });
386 };
387
388 EXPECT_CALL(callback_with_valid_view, Call()).Times(1);
389 EXPECT_CALL(callback_with_invalid_view, Call()).Times(0);
390
391 trigger_key_event(callback_with_valid_view);
392 engine->RemoveView(view->view_id());
393 trigger_key_event(callback_with_invalid_view);
394
395 key_event_logs.clear();
396}
397
398TEST(FlutterWindowsViewTest, EnableSemantics) {
399 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
400 EngineModifier modifier(engine.get());
401
402 bool semantics_enabled = false;
404 UpdateSemanticsEnabled,
405 [&semantics_enabled](FLUTTER_API_SYMBOL(FlutterEngine) engine,
406 bool enabled) {
407 semantics_enabled = enabled;
408 return kSuccess;
409 });
410
411 auto window_binding_handler =
412 std::make_unique<NiceMock<MockWindowBindingHandler>>();
413 std::unique_ptr<FlutterWindowsView> view =
414 engine->CreateView(std::move(window_binding_handler),
415 /*is_sized_to_content=*/false, BoxConstraints());
416
417 view->OnUpdateSemanticsEnabled(true);
418 EXPECT_TRUE(semantics_enabled);
419}
420
421TEST(FlutterWindowsViewTest, AddSemanticsNodeUpdate) {
422 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
423 EngineModifier modifier(engine.get());
425 [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
426 return kSuccess;
427 };
428
429 auto window_binding_handler =
430 std::make_unique<NiceMock<MockWindowBindingHandler>>();
431 std::unique_ptr<FlutterWindowsView> view =
432 engine->CreateView(std::move(window_binding_handler),
433 /*is_sized_to_content=*/false, BoxConstraints());
434
435 // Enable semantics to instantiate accessibility bridge.
436 view->OnUpdateSemanticsEnabled(true);
437
438 auto bridge = view->accessibility_bridge().lock();
439 ASSERT_TRUE(bridge);
440
441 // Add root node.
443 node.label = "name";
444 node.value = "value";
445 node.platform_view_id = -1;
446 auto flags = FlutterSemanticsFlags{};
447 node.flags2 = &flags;
448 bridge->AddFlutterSemanticsNodeUpdate(node);
449 bridge->CommitUpdates();
450
451 // Look up the root windows node delegate.
452 auto node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
453 ASSERT_TRUE(node_delegate);
454 EXPECT_EQ(node_delegate->GetChildCount(), 0);
455
456 // Get the native IAccessible object.
457 IAccessible* native_view = node_delegate->GetNativeViewAccessible();
458 ASSERT_TRUE(native_view != nullptr);
459
460 // Property lookups will be made against this node itself.
461 VARIANT varchild{};
462 varchild.vt = VT_I4;
463 varchild.lVal = CHILDID_SELF;
464
465 // Verify node name matches our label.
466 BSTR bname = nullptr;
467 ASSERT_EQ(native_view->get_accName(varchild, &bname), S_OK);
468 std::string name(_com_util::ConvertBSTRToString(bname));
469 EXPECT_EQ(name, "name");
470
471 // Verify node value matches.
472 BSTR bvalue = nullptr;
473 ASSERT_EQ(native_view->get_accValue(varchild, &bvalue), S_OK);
474 std::string value(_com_util::ConvertBSTRToString(bvalue));
475 EXPECT_EQ(value, "value");
476
477 // Verify node type is static text.
478 VARIANT varrole{};
479 varrole.vt = VT_I4;
480 ASSERT_EQ(native_view->get_accRole(varchild, &varrole), S_OK);
481 EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_STATICTEXT);
482
483 // Get the IRawElementProviderFragment object.
484 IRawElementProviderSimple* uia_view;
485 native_view->QueryInterface(IID_PPV_ARGS(&uia_view));
486 ASSERT_TRUE(uia_view != nullptr);
487
488 // Verify name property matches our label.
489 VARIANT varname{};
490 ASSERT_EQ(uia_view->GetPropertyValue(UIA_NamePropertyId, &varname), S_OK);
491 EXPECT_EQ(varname.vt, VT_BSTR);
492 name = _com_util::ConvertBSTRToString(varname.bstrVal);
493 EXPECT_EQ(name, "name");
494
495 // Verify value property matches our label.
496 VARIANT varvalue{};
497 ASSERT_EQ(uia_view->GetPropertyValue(UIA_ValueValuePropertyId, &varvalue),
498 S_OK);
499 EXPECT_EQ(varvalue.vt, VT_BSTR);
500 value = _com_util::ConvertBSTRToString(varvalue.bstrVal);
501 EXPECT_EQ(value, "value");
502
503 // Verify node control type is text.
504 varrole = {};
505 ASSERT_EQ(uia_view->GetPropertyValue(UIA_ControlTypePropertyId, &varrole),
506 S_OK);
507 EXPECT_EQ(varrole.vt, VT_I4);
508 EXPECT_EQ(varrole.lVal, UIA_TextControlTypeId);
509}
510
511// Verify the native IAccessible COM object tree is an accurate reflection of
512// the platform-agnostic tree. Verify both a root node with children as well as
513// a non-root node with children, since the AX tree includes special handling
514// for the root.
515//
516// node0
517// / \
518// node1 node2
519// |
520// node3
521//
522// node0 and node2 are grouping nodes. node1 and node2 are static text nodes.
523TEST(FlutterWindowsViewTest, AddSemanticsNodeUpdateWithChildren) {
524 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
525 EngineModifier modifier(engine.get());
527 [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
528 return kSuccess;
529 };
530
531 std::unique_ptr<FlutterWindowsView> view =
532 engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
533 /*is_sized_to_content=*/false, BoxConstraints());
534
535 // Enable semantics to instantiate accessibility bridge.
536 view->OnUpdateSemanticsEnabled(true);
537
538 auto bridge = view->accessibility_bridge().lock();
539 ASSERT_TRUE(bridge);
540
541 // Add root node.
543 std::vector<int32_t> node0_children{1, 2};
544 node0.child_count = node0_children.size();
545 node0.children_in_traversal_order = node0_children.data();
546 node0.children_in_hit_test_order = node0_children.data();
547 auto empty_flags = FlutterSemanticsFlags{};
548 node0.flags2 = &empty_flags;
549
551 node1.label = "prefecture";
552 node1.value = "Kyoto";
553 node1.flags2 = &empty_flags;
555 std::vector<int32_t> node2_children{3};
556 node2.child_count = node2_children.size();
557 node2.children_in_traversal_order = node2_children.data();
558 node2.children_in_hit_test_order = node2_children.data();
559 node2.flags2 = &empty_flags;
561 node3.label = "city";
562 node3.value = "Uji";
563 node3.flags2 = &empty_flags;
564
565 bridge->AddFlutterSemanticsNodeUpdate(node0);
566 bridge->AddFlutterSemanticsNodeUpdate(node1);
567 bridge->AddFlutterSemanticsNodeUpdate(node2);
568 bridge->AddFlutterSemanticsNodeUpdate(node3);
569 bridge->CommitUpdates();
570
571 // Look up the root windows node delegate.
572 auto node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
573 ASSERT_TRUE(node_delegate);
574 EXPECT_EQ(node_delegate->GetChildCount(), 2);
575
576 // Get the native IAccessible object.
577 IAccessible* node0_accessible = node_delegate->GetNativeViewAccessible();
578 ASSERT_TRUE(node0_accessible != nullptr);
579
580 // Property lookups will be made against this node itself.
581 VARIANT varchild{};
582 varchild.vt = VT_I4;
583 varchild.lVal = CHILDID_SELF;
584
585 // Verify node type is a group.
586 VARIANT varrole{};
587 varrole.vt = VT_I4;
588 ASSERT_EQ(node0_accessible->get_accRole(varchild, &varrole), S_OK);
589 EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_GROUPING);
590
591 // Verify child count.
592 long node0_child_count = 0;
593 ASSERT_EQ(node0_accessible->get_accChildCount(&node0_child_count), S_OK);
594 EXPECT_EQ(node0_child_count, 2);
595
596 {
597 // Look up first child of node0 (node1), a static text node.
598 varchild.lVal = 1;
599 IDispatch* node1_dispatch = nullptr;
600 ASSERT_EQ(node0_accessible->get_accChild(varchild, &node1_dispatch), S_OK);
601 ASSERT_TRUE(node1_dispatch != nullptr);
602 IAccessible* node1_accessible = nullptr;
603 ASSERT_EQ(node1_dispatch->QueryInterface(
604 IID_IAccessible, reinterpret_cast<void**>(&node1_accessible)),
605 S_OK);
606 ASSERT_TRUE(node1_accessible != nullptr);
607
608 // Verify node name matches our label.
609 varchild.lVal = CHILDID_SELF;
610 BSTR bname = nullptr;
611 ASSERT_EQ(node1_accessible->get_accName(varchild, &bname), S_OK);
612 std::string name(_com_util::ConvertBSTRToString(bname));
613 EXPECT_EQ(name, "prefecture");
614
615 // Verify node value matches.
616 BSTR bvalue = nullptr;
617 ASSERT_EQ(node1_accessible->get_accValue(varchild, &bvalue), S_OK);
618 std::string value(_com_util::ConvertBSTRToString(bvalue));
619 EXPECT_EQ(value, "Kyoto");
620
621 // Verify node type is static text.
622 VARIANT varrole{};
623 varrole.vt = VT_I4;
624 ASSERT_EQ(node1_accessible->get_accRole(varchild, &varrole), S_OK);
625 EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_STATICTEXT);
626
627 // Verify the parent node is the root.
628 IDispatch* parent_dispatch;
629 node1_accessible->get_accParent(&parent_dispatch);
630 IAccessible* parent_accessible;
631 ASSERT_EQ(
632 parent_dispatch->QueryInterface(
633 IID_IAccessible, reinterpret_cast<void**>(&parent_accessible)),
634 S_OK);
635 EXPECT_EQ(parent_accessible, node0_accessible);
636 }
637
638 // Look up second child of node0 (node2), a parent group for node3.
639 varchild.lVal = 2;
640 IDispatch* node2_dispatch = nullptr;
641 ASSERT_EQ(node0_accessible->get_accChild(varchild, &node2_dispatch), S_OK);
642 ASSERT_TRUE(node2_dispatch != nullptr);
643 IAccessible* node2_accessible = nullptr;
644 ASSERT_EQ(node2_dispatch->QueryInterface(
645 IID_IAccessible, reinterpret_cast<void**>(&node2_accessible)),
646 S_OK);
647 ASSERT_TRUE(node2_accessible != nullptr);
648
649 {
650 // Verify child count.
651 long node2_child_count = 0;
652 ASSERT_EQ(node2_accessible->get_accChildCount(&node2_child_count), S_OK);
653 EXPECT_EQ(node2_child_count, 1);
654
655 // Verify node type is static text.
656 varchild.lVal = CHILDID_SELF;
657 VARIANT varrole{};
658 varrole.vt = VT_I4;
659 ASSERT_EQ(node2_accessible->get_accRole(varchild, &varrole), S_OK);
660 EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_GROUPING);
661
662 // Verify the parent node is the root.
663 IDispatch* parent_dispatch;
664 node2_accessible->get_accParent(&parent_dispatch);
665 IAccessible* parent_accessible;
666 ASSERT_EQ(
667 parent_dispatch->QueryInterface(
668 IID_IAccessible, reinterpret_cast<void**>(&parent_accessible)),
669 S_OK);
670 EXPECT_EQ(parent_accessible, node0_accessible);
671 }
672
673 {
674 // Look up only child of node2 (node3), a static text node.
675 varchild.lVal = 1;
676 IDispatch* node3_dispatch = nullptr;
677 ASSERT_EQ(node2_accessible->get_accChild(varchild, &node3_dispatch), S_OK);
678 ASSERT_TRUE(node3_dispatch != nullptr);
679 IAccessible* node3_accessible = nullptr;
680 ASSERT_EQ(node3_dispatch->QueryInterface(
681 IID_IAccessible, reinterpret_cast<void**>(&node3_accessible)),
682 S_OK);
683 ASSERT_TRUE(node3_accessible != nullptr);
684
685 // Verify node name matches our label.
686 varchild.lVal = CHILDID_SELF;
687 BSTR bname = nullptr;
688 ASSERT_EQ(node3_accessible->get_accName(varchild, &bname), S_OK);
689 std::string name(_com_util::ConvertBSTRToString(bname));
690 EXPECT_EQ(name, "city");
691
692 // Verify node value matches.
693 BSTR bvalue = nullptr;
694 ASSERT_EQ(node3_accessible->get_accValue(varchild, &bvalue), S_OK);
695 std::string value(_com_util::ConvertBSTRToString(bvalue));
696 EXPECT_EQ(value, "Uji");
697
698 // Verify node type is static text.
699 VARIANT varrole{};
700 varrole.vt = VT_I4;
701 ASSERT_EQ(node3_accessible->get_accRole(varchild, &varrole), S_OK);
702 EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_STATICTEXT);
703
704 // Verify the parent node is node2.
705 IDispatch* parent_dispatch;
706 node3_accessible->get_accParent(&parent_dispatch);
707 IAccessible* parent_accessible;
708 ASSERT_EQ(
709 parent_dispatch->QueryInterface(
710 IID_IAccessible, reinterpret_cast<void**>(&parent_accessible)),
711 S_OK);
712 EXPECT_EQ(parent_accessible, node2_accessible);
713 }
714}
715
716// Flutter used to assume that the accessibility root had ID 0.
717// In a multi-view world, each view has its own accessibility root
718// with a globally unique node ID.
719//
720// node1
721// |
722// node2
723//
724// node1 is a grouping node, node0 is a static text node.
725TEST(FlutterWindowsViewTest, NonZeroSemanticsRoot) {
726 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
727 EngineModifier modifier(engine.get());
729 [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
730 return kSuccess;
731 };
732
733 std::unique_ptr<FlutterWindowsView> view =
734 engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
735 /*is_sized_to_content=*/false, BoxConstraints());
736
737 // Enable semantics to instantiate accessibility bridge.
738 view->OnUpdateSemanticsEnabled(true);
739
740 auto bridge = view->accessibility_bridge().lock();
741 ASSERT_TRUE(bridge);
742
743 // Add root node.
745 std::vector<int32_t> node1_children{2};
746 node1.child_count = node1_children.size();
747 node1.children_in_traversal_order = node1_children.data();
748 node1.children_in_hit_test_order = node1_children.data();
749 auto empty_flags = FlutterSemanticsFlags{};
750 node1.flags2 = &empty_flags;
751
753 node2.label = "prefecture";
754 node2.value = "Kyoto";
755 node2.flags2 = &empty_flags;
756 bridge->AddFlutterSemanticsNodeUpdate(node1);
757 bridge->AddFlutterSemanticsNodeUpdate(node2);
758 bridge->CommitUpdates();
759
760 // Look up the root windows node delegate.
761 auto root_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock();
762 ASSERT_TRUE(root_delegate);
763 EXPECT_EQ(root_delegate->GetChildCount(), 1);
764
765 // Look up the child node delegate
766 auto child_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(2).lock();
767 ASSERT_TRUE(child_delegate);
768 EXPECT_EQ(child_delegate->GetChildCount(), 0);
769
770 // Ensure a node with ID 0 does not exist.
771 auto fake_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
772 ASSERT_FALSE(fake_delegate);
773
774 // Get the root's native IAccessible object.
775 IAccessible* node1_accessible = root_delegate->GetNativeViewAccessible();
776 ASSERT_TRUE(node1_accessible != nullptr);
777
778 // Property lookups will be made against this node itself.
779 VARIANT varchild{};
780 varchild.vt = VT_I4;
781 varchild.lVal = CHILDID_SELF;
782
783 // Verify node type is a group.
784 VARIANT varrole{};
785 varrole.vt = VT_I4;
786 ASSERT_EQ(node1_accessible->get_accRole(varchild, &varrole), S_OK);
787 EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_GROUPING);
788
789 // Verify child count.
790 long node1_child_count = 0;
791 ASSERT_EQ(node1_accessible->get_accChildCount(&node1_child_count), S_OK);
792 EXPECT_EQ(node1_child_count, 1);
793
794 {
795 // Look up first child of node1 (node0), a static text node.
796 varchild.lVal = 1;
797 IDispatch* node2_dispatch = nullptr;
798 ASSERT_EQ(node1_accessible->get_accChild(varchild, &node2_dispatch), S_OK);
799 ASSERT_TRUE(node2_dispatch != nullptr);
800 IAccessible* node2_accessible = nullptr;
801 ASSERT_EQ(node2_dispatch->QueryInterface(
802 IID_IAccessible, reinterpret_cast<void**>(&node2_accessible)),
803 S_OK);
804 ASSERT_TRUE(node2_accessible != nullptr);
805
806 // Verify node name matches our label.
807 varchild.lVal = CHILDID_SELF;
808 BSTR bname = nullptr;
809 ASSERT_EQ(node2_accessible->get_accName(varchild, &bname), S_OK);
810 std::string name(_com_util::ConvertBSTRToString(bname));
811 EXPECT_EQ(name, "prefecture");
812
813 // Verify node value matches.
814 BSTR bvalue = nullptr;
815 ASSERT_EQ(node2_accessible->get_accValue(varchild, &bvalue), S_OK);
816 std::string value(_com_util::ConvertBSTRToString(bvalue));
817 EXPECT_EQ(value, "Kyoto");
818
819 // Verify node type is static text.
820 VARIANT varrole{};
821 varrole.vt = VT_I4;
822 ASSERT_EQ(node2_accessible->get_accRole(varchild, &varrole), S_OK);
823 EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_STATICTEXT);
824
825 // Verify the parent node is the root.
826 IDispatch* parent_dispatch;
827 node2_accessible->get_accParent(&parent_dispatch);
828 IAccessible* parent_accessible;
829 ASSERT_EQ(
830 parent_dispatch->QueryInterface(
831 IID_IAccessible, reinterpret_cast<void**>(&parent_accessible)),
832 S_OK);
833 EXPECT_EQ(parent_accessible, node1_accessible);
834 }
835}
836
837// Verify the native IAccessible accHitTest method returns the correct
838// IAccessible COM object for the given coordinates.
839//
840// +-----------+
841// | | |
842// node0 | | B |
843// / \ | A |-----|
844// node1 node2 | | C |
845// | | | |
846// node3 +-----------+
847//
848// node0 and node2 are grouping nodes. node1 and node2 are static text nodes.
849//
850// node0 is located at 0,0 with size 500x500. It spans areas A, B, and C.
851// node1 is located at 0,0 with size 250x500. It spans area A.
852// node2 is located at 250,0 with size 250x500. It spans areas B and C.
853// node3 is located at 250,250 with size 250x250. It spans area C.
854TEST(FlutterWindowsViewTest, AccessibilityHitTesting) {
855 constexpr FlutterTransformation kIdentityTransform = {1, 0, 0, //
856 0, 1, 0, //
857 0, 0, 1};
858
859 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
860 EngineModifier modifier(engine.get());
862 [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
863 return kSuccess;
864 };
865
866 std::unique_ptr<FlutterWindowsView> view =
867 engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
868 /*is_sized_to_content=*/false, BoxConstraints());
869
870 // Enable semantics to instantiate accessibility bridge.
871 view->OnUpdateSemanticsEnabled(true);
872
873 auto bridge = view->accessibility_bridge().lock();
874 ASSERT_TRUE(bridge);
875
876 // Add root node at origin. Size 500x500.
878 auto empty_flags = FlutterSemanticsFlags{};
879 std::vector<int32_t> node0_children{1, 2};
880 node0.rect = {0, 0, 500, 500};
881 node0.transform = kIdentityTransform;
882 node0.child_count = node0_children.size();
883 node0.children_in_traversal_order = node0_children.data();
884 node0.children_in_hit_test_order = node0_children.data();
885 node0.flags2 = &empty_flags;
886
887 // Add node 1 located at 0,0 relative to node 0. Size 250x500.
889 node1.rect = {0, 0, 250, 500};
890 node1.transform = kIdentityTransform;
891 node1.label = "prefecture";
892 node1.value = "Kyoto";
893 node1.flags2 = &empty_flags;
894
895 // Add node 2 located at 250,0 relative to node 0. Size 250x500.
897 std::vector<int32_t> node2_children{3};
898 node2.rect = {0, 0, 250, 500};
899 node2.transform = {1, 0, 250, 0, 1, 0, 0, 0, 1};
900 node2.child_count = node2_children.size();
901 node2.children_in_traversal_order = node2_children.data();
902 node2.children_in_hit_test_order = node2_children.data();
903 node2.flags2 = &empty_flags;
904
905 // Add node 3 located at 0,250 relative to node 2. Size 250, 250.
907 node3.rect = {0, 0, 250, 250};
908 node3.transform = {1, 0, 0, 0, 1, 250, 0, 0, 1};
909 node3.label = "city";
910 node3.value = "Uji";
911 node3.flags2 = &empty_flags;
912
913 bridge->AddFlutterSemanticsNodeUpdate(node0);
914 bridge->AddFlutterSemanticsNodeUpdate(node1);
915 bridge->AddFlutterSemanticsNodeUpdate(node2);
916 bridge->AddFlutterSemanticsNodeUpdate(node3);
917 bridge->CommitUpdates();
918
919 // Look up the root windows node delegate.
920 auto node0_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
921 ASSERT_TRUE(node0_delegate);
922 auto node1_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock();
923 ASSERT_TRUE(node1_delegate);
924 auto node2_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(2).lock();
925 ASSERT_TRUE(node2_delegate);
926 auto node3_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(3).lock();
927 ASSERT_TRUE(node3_delegate);
928
929 // Get the native IAccessible root object.
930 IAccessible* node0_accessible = node0_delegate->GetNativeViewAccessible();
931 ASSERT_TRUE(node0_accessible != nullptr);
932
933 // Perform a hit test that should hit node 1.
934 VARIANT varchild{};
935 ASSERT_TRUE(SUCCEEDED(node0_accessible->accHitTest(150, 150, &varchild)));
936 EXPECT_EQ(varchild.vt, VT_DISPATCH);
937 EXPECT_EQ(varchild.pdispVal, node1_delegate->GetNativeViewAccessible());
938
939 // Perform a hit test that should hit node 2.
940 varchild = {};
941 ASSERT_TRUE(SUCCEEDED(node0_accessible->accHitTest(450, 150, &varchild)));
942 EXPECT_EQ(varchild.vt, VT_DISPATCH);
943 EXPECT_EQ(varchild.pdispVal, node2_delegate->GetNativeViewAccessible());
944
945 // Perform a hit test that should hit node 3.
946 varchild = {};
947 ASSERT_TRUE(SUCCEEDED(node0_accessible->accHitTest(450, 450, &varchild)));
948 EXPECT_EQ(varchild.vt, VT_DISPATCH);
949 EXPECT_EQ(varchild.pdispVal, node3_delegate->GetNativeViewAccessible());
950}
951
952TEST(FlutterWindowsViewTest, WindowResizeTests) {
953 auto windows_proc_table = std::make_shared<NiceMock<MockWindowsProcTable>>();
954 std::unique_ptr<FlutterWindowsEngine> engine =
955 GetTestEngine(windows_proc_table);
956
957 EngineModifier engine_modifier{engine.get()};
958 engine_modifier.embedder_api().PostRenderThreadTask = MOCK_ENGINE_PROC(
959 PostRenderThreadTask,
960 ([](auto engine, VoidCallback callback, void* user_data) {
962 return kSuccess;
963 }));
964
965 auto egl_manager = std::make_unique<egl::MockManager>();
966 auto surface = std::make_unique<egl::MockWindowSurface>();
967 auto resized_surface = std::make_unique<egl::MockWindowSurface>();
968 egl::MockContext render_context;
969
970 auto surface_ptr = surface.get();
971 auto resized_surface_ptr = resized_surface.get();
972
973 // Mock render surface creation
974 EXPECT_CALL(*egl_manager, CreateWindowSurface)
975 .WillOnce(Return(std::move(surface)));
976 EXPECT_CALL(*surface_ptr, IsValid).WillRepeatedly(Return(true));
977 EXPECT_CALL(*surface_ptr, MakeCurrent).WillOnce(Return(true));
978 EXPECT_CALL(*surface_ptr, SetVSyncEnabled).WillOnce(Return(true));
979 EXPECT_CALL(*egl_manager, render_context).WillOnce(Return(&render_context));
980 EXPECT_CALL(render_context, ClearCurrent).WillOnce(Return(true));
981
982 // Mock render surface resize
983 EXPECT_CALL(*surface_ptr, Destroy).WillOnce(Return(true));
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 engine_modifier.SetEGLManager(std::move(egl_manager));
994
995 std::unique_ptr<FlutterWindowsView> view =
996 engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
997 /*is_sized_to_content=*/false, BoxConstraints());
998
999 fml::AutoResetWaitableEvent metrics_sent_latch;
1000 engine_modifier.embedder_api().SendWindowMetricsEvent = MOCK_ENGINE_PROC(
1001 SendWindowMetricsEvent,
1002 ([&metrics_sent_latch](auto engine,
1003 const FlutterWindowMetricsEvent* event) {
1004 metrics_sent_latch.Signal();
1005 return kSuccess;
1006 }));
1007
1008 // Simulate raster thread.
1009 std::thread frame_thread([&metrics_sent_latch, &view]() {
1010 metrics_sent_latch.Wait();
1011 // Frame generated and presented from the raster thread.
1012 EXPECT_TRUE(view->OnFrameGenerated(500, 500));
1013 view->OnFramePresented();
1014 });
1015
1016 // Start the window resize. This sends the new window metrics
1017 // and then blocks polling run loop until another thread completes the window
1018 // resize.
1019 EXPECT_TRUE(view->OnWindowSizeChanged(500, 500));
1020 frame_thread.join();
1021}
1022
1023// Verify that an empty frame completes a view resize.
1024TEST(FlutterWindowsViewTest, TestEmptyFrameResizes) {
1025 auto windows_proc_table = std::make_shared<NiceMock<MockWindowsProcTable>>();
1026 std::unique_ptr<FlutterWindowsEngine> engine =
1027 GetTestEngine(windows_proc_table);
1028
1029 EngineModifier engine_modifier{engine.get()};
1030 engine_modifier.embedder_api().PostRenderThreadTask = MOCK_ENGINE_PROC(
1031 PostRenderThreadTask,
1032 ([](auto engine, VoidCallback callback, void* user_data) {
1034 return kSuccess;
1035 }));
1036
1037 auto egl_manager = std::make_unique<egl::MockManager>();
1038 auto surface = std::make_unique<egl::MockWindowSurface>();
1039 auto resized_surface = std::make_unique<egl::MockWindowSurface>();
1040 auto resized_surface_ptr = resized_surface.get();
1041
1042 EXPECT_CALL(*surface.get(), IsValid).WillRepeatedly(Return(true));
1043 EXPECT_CALL(*surface.get(), Destroy).WillOnce(Return(true));
1044
1045 EXPECT_CALL(*egl_manager.get(),
1046 CreateWindowSurface(_, /*width=*/500, /*height=*/300))
1047 .WillOnce(Return(std::move((resized_surface))));
1048 EXPECT_CALL(*resized_surface_ptr, MakeCurrent).WillOnce(Return(true));
1049 EXPECT_CALL(*resized_surface_ptr, SetVSyncEnabled).WillOnce(Return(true));
1050 EXPECT_CALL(*windows_proc_table.get(), DwmFlush).WillOnce(Return(S_OK));
1051
1052 EXPECT_CALL(*resized_surface_ptr, Destroy).WillOnce(Return(true));
1053
1054 fml::AutoResetWaitableEvent metrics_sent_latch;
1055 engine_modifier.embedder_api().SendWindowMetricsEvent = MOCK_ENGINE_PROC(
1056 SendWindowMetricsEvent,
1057 ([&metrics_sent_latch](auto engine,
1058 const FlutterWindowMetricsEvent* event) {
1059 metrics_sent_latch.Signal();
1060 return kSuccess;
1061 }));
1062
1063 std::unique_ptr<FlutterWindowsView> view =
1064 engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1065 /*is_sized_to_content=*/false, BoxConstraints());
1066
1067 ViewModifier view_modifier{view.get()};
1068 engine_modifier.SetEGLManager(std::move(egl_manager));
1069 view_modifier.SetSurface(std::move(surface));
1070
1071 // Simulate raster thread.
1072 std::thread frame_thread([&metrics_sent_latch, &view]() {
1073 metrics_sent_latch.Wait();
1074
1075 // Empty frame generated and presented from the raster thread.
1076 EXPECT_TRUE(view->OnEmptyFrameGenerated());
1077 view->OnFramePresented();
1078 });
1079
1080 // Start the window resize. This sends the new window metrics
1081 // and then blocks until another thread completes the window resize.
1082 EXPECT_TRUE(view->OnWindowSizeChanged(500, 300));
1083 frame_thread.join();
1084}
1085
1086// A window resize can be interleaved between a frame generation and
1087// presentation. This should not crash the app. Regression test for:
1088// https://github.com/flutter/flutter/issues/141855
1089TEST(FlutterWindowsViewTest, WindowResizeRace) {
1090 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1091
1092 EngineModifier engine_modifier(engine.get());
1094 PostRenderThreadTask,
1095 ([](auto engine, VoidCallback callback, void* user_data) {
1097 return kSuccess;
1098 }));
1099
1100 auto egl_manager = std::make_unique<egl::MockManager>();
1101 auto surface = std::make_unique<egl::MockWindowSurface>();
1102
1103 EXPECT_CALL(*surface.get(), IsValid).WillRepeatedly(Return(true));
1104 EXPECT_CALL(*surface.get(), Destroy).WillOnce(Return(true));
1105
1106 std::unique_ptr<FlutterWindowsView> view =
1107 engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1108 /*is_sized_to_content=*/false, BoxConstraints());
1109
1110 ViewModifier view_modifier{view.get()};
1111 engine_modifier.SetEGLManager(std::move(egl_manager));
1112 view_modifier.SetSurface(std::move(surface));
1113
1114 // Begin a frame.
1115 ASSERT_TRUE(view->OnFrameGenerated(100, 100));
1116
1117 // Inject a window resize between the frame generation and
1118 // frame presentation. The new size invalidates the current frame.
1119 EXPECT_FALSE(view->OnWindowSizeChanged(500, 500));
1120
1121 // Complete the invalidated frame while a resize is pending. Although this
1122 // might mean that we presented a frame with the wrong size, this should not
1123 // crash the app.
1124 view->OnFramePresented();
1125}
1126
1127// Window resize should succeed even if the render surface could not be created
1128// even though EGL initialized successfully.
1129TEST(FlutterWindowsViewTest, WindowResizeInvalidSurface) {
1130 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1131
1132 EngineModifier engine_modifier(engine.get());
1134 PostRenderThreadTask,
1135 ([](auto engine, VoidCallback callback, void* user_data) {
1137 return kSuccess;
1138 }));
1139
1140 auto egl_manager = std::make_unique<egl::MockManager>();
1141 auto surface = std::make_unique<egl::MockWindowSurface>();
1142
1143 EXPECT_CALL(*egl_manager.get(), CreateWindowSurface).Times(0);
1144 EXPECT_CALL(*surface.get(), IsValid).WillRepeatedly(Return(false));
1145 EXPECT_CALL(*surface.get(), Destroy).WillOnce(Return(false));
1146
1147 std::unique_ptr<FlutterWindowsView> view =
1148 engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1149 /*is_sized_to_content=*/false, BoxConstraints());
1150
1151 ViewModifier view_modifier{view.get()};
1152 engine_modifier.SetEGLManager(std::move(egl_manager));
1153 view_modifier.SetSurface(std::move(surface));
1154
1155 auto metrics_sent = false;
1157 SendWindowMetricsEvent,
1158 ([&metrics_sent](auto engine, const FlutterWindowMetricsEvent* event) {
1159 metrics_sent = true;
1160 return kSuccess;
1161 }));
1162
1163 view->OnWindowSizeChanged(500, 500);
1164}
1165
1166// Window resize should succeed even if EGL initialized successfully
1167// but the EGL surface could not be created.
1168TEST(FlutterWindowsViewTest, WindowResizeWithoutSurface) {
1169 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1170 EngineModifier modifier(engine.get());
1171
1172 auto egl_manager = std::make_unique<egl::MockManager>();
1173
1174 EXPECT_CALL(*egl_manager.get(), CreateWindowSurface).Times(0);
1175
1176 std::unique_ptr<FlutterWindowsView> view =
1177 engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1178 /*is_sized_to_content=*/false, BoxConstraints());
1179
1180 modifier.SetEGLManager(std::move(egl_manager));
1181
1182 auto metrics_sent = false;
1184 SendWindowMetricsEvent,
1185 ([&metrics_sent](auto engine, const FlutterWindowMetricsEvent* event) {
1186 metrics_sent = true;
1187 return kSuccess;
1188 }));
1189
1190 view->OnWindowSizeChanged(500, 500);
1191}
1192
1193TEST(FlutterWindowsViewTest, WindowRepaintTests) {
1194 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1195 EngineModifier modifier(engine.get());
1196
1198 std::make_unique<flutter::FlutterWindow>(
1199 100, 100, engine->display_manager()),
1200 /*is_sized_to_content=*/false, BoxConstraints()};
1201
1202 bool schedule_frame_called = false;
1203 modifier.embedder_api().ScheduleFrame =
1204 MOCK_ENGINE_PROC(ScheduleFrame, ([&schedule_frame_called](auto engine) {
1205 schedule_frame_called = true;
1206 return kSuccess;
1207 }));
1208
1209 view.OnWindowRepaint();
1210 EXPECT_TRUE(schedule_frame_called);
1211}
1212
1213// Ensure that checkboxes have their checked status set apropriately
1214// Previously, only Radios could have this flag updated
1215// Resulted in the issue seen at
1216// https://github.com/flutter/flutter/issues/96218
1217// This test ensures that the native state of Checkboxes on Windows,
1218// specifically, is updated as desired.
1219TEST(FlutterWindowsViewTest, CheckboxNativeState) {
1220 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1221 EngineModifier modifier(engine.get());
1223 [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
1224 return kSuccess;
1225 };
1226
1227 std::unique_ptr<FlutterWindowsView> view =
1228 engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1229 /*is_sized_to_content=*/false, BoxConstraints());
1230
1231 // Enable semantics to instantiate accessibility bridge.
1232 view->OnUpdateSemanticsEnabled(true);
1233
1234 auto bridge = view->accessibility_bridge().lock();
1235 ASSERT_TRUE(bridge);
1236
1238 root.id = 0;
1239 root.label = "root";
1240 root.hint = "";
1241 root.value = "";
1242 root.increased_value = "";
1243 root.decreased_value = "";
1244 root.child_count = 0;
1245 root.custom_accessibility_actions_count = 0;
1246 auto flags = FlutterSemanticsFlags{
1248 };
1249 root.flags2 = &flags;
1250 bridge->AddFlutterSemanticsNodeUpdate(root);
1251
1252 bridge->CommitUpdates();
1253
1254 {
1255 auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
1256 EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kCheckBox);
1257 EXPECT_EQ(root_node->GetData().GetCheckedState(),
1259
1260 // Get the IAccessible for the root node.
1261 IAccessible* native_view = root_node->GetNativeViewAccessible();
1262 ASSERT_TRUE(native_view != nullptr);
1263
1264 // Look up against the node itself (not one of its children).
1265 VARIANT varchild = {};
1266 varchild.vt = VT_I4;
1267
1268 // Verify the checkbox is checked.
1269 varchild.lVal = CHILDID_SELF;
1270 VARIANT native_state = {};
1271 ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
1272 EXPECT_TRUE(native_state.lVal & STATE_SYSTEM_CHECKED);
1273
1274 // Perform similar tests for UIA value;
1275 IRawElementProviderSimple* uia_node;
1276 native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
1277 ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
1278 UIA_ToggleToggleStatePropertyId, &native_state)));
1279 EXPECT_EQ(native_state.lVal, ToggleState_On);
1280
1281 ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
1282 UIA_AriaPropertiesPropertyId, &native_state)));
1283 EXPECT_NE(std::wcsstr(native_state.bstrVal, L"checked=true"), nullptr);
1284 }
1285
1286 // Test unchecked too.
1287 auto updated_flags = FlutterSemanticsFlags{
1289 };
1290 root.flags2 = &updated_flags;
1291 bridge->AddFlutterSemanticsNodeUpdate(root);
1292 bridge->CommitUpdates();
1293
1294 {
1295 auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
1296 EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kCheckBox);
1297 EXPECT_EQ(root_node->GetData().GetCheckedState(),
1299
1300 // Get the IAccessible for the root node.
1301 IAccessible* native_view = root_node->GetNativeViewAccessible();
1302 ASSERT_TRUE(native_view != nullptr);
1303
1304 // Look up against the node itself (not one of its children).
1305 VARIANT varchild = {};
1306 varchild.vt = VT_I4;
1307
1308 // Verify the checkbox is unchecked.
1309 varchild.lVal = CHILDID_SELF;
1310 VARIANT native_state = {};
1311 ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
1312 EXPECT_FALSE(native_state.lVal & STATE_SYSTEM_CHECKED);
1313
1314 // Perform similar tests for UIA value;
1315 IRawElementProviderSimple* uia_node;
1316 native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
1317 ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
1318 UIA_ToggleToggleStatePropertyId, &native_state)));
1319 EXPECT_EQ(native_state.lVal, ToggleState_Off);
1320
1321 ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
1322 UIA_AriaPropertiesPropertyId, &native_state)));
1323 EXPECT_NE(std::wcsstr(native_state.bstrVal, L"checked=false"), nullptr);
1324 }
1325
1326 // Now check mixed state.
1327 auto updated_mixe_flags = FlutterSemanticsFlags{
1329 };
1330 root.flags2 = &updated_mixe_flags;
1331 bridge->AddFlutterSemanticsNodeUpdate(root);
1332 bridge->CommitUpdates();
1333
1334 {
1335 auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
1336 EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kCheckBox);
1337 EXPECT_EQ(root_node->GetData().GetCheckedState(),
1339
1340 // Get the IAccessible for the root node.
1341 IAccessible* native_view = root_node->GetNativeViewAccessible();
1342 ASSERT_TRUE(native_view != nullptr);
1343
1344 // Look up against the node itself (not one of its children).
1345 VARIANT varchild = {};
1346 varchild.vt = VT_I4;
1347
1348 // Verify the checkbox is mixed.
1349 varchild.lVal = CHILDID_SELF;
1350 VARIANT native_state = {};
1351 ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
1352 EXPECT_TRUE(native_state.lVal & STATE_SYSTEM_MIXED);
1353
1354 // Perform similar tests for UIA value;
1355 IRawElementProviderSimple* uia_node;
1356 native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
1357 ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
1358 UIA_ToggleToggleStatePropertyId, &native_state)));
1359 EXPECT_EQ(native_state.lVal, ToggleState_Indeterminate);
1360
1361 ASSERT_TRUE(SUCCEEDED(uia_node->GetPropertyValue(
1362 UIA_AriaPropertiesPropertyId, &native_state)));
1363 EXPECT_NE(std::wcsstr(native_state.bstrVal, L"checked=mixed"), nullptr);
1364 }
1365}
1366
1367// Ensure that switches have their toggle status set apropriately
1368TEST(FlutterWindowsViewTest, SwitchNativeState) {
1369 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1370 EngineModifier modifier(engine.get());
1372 [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
1373 return kSuccess;
1374 };
1375
1376 std::unique_ptr<FlutterWindowsView> view =
1377 engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1378 /*is_sized_to_content=*/false, BoxConstraints());
1379
1380 // Enable semantics to instantiate accessibility bridge.
1381 view->OnUpdateSemanticsEnabled(true);
1382
1383 auto bridge = view->accessibility_bridge().lock();
1384 ASSERT_TRUE(bridge);
1385
1387 root.id = 0;
1388 root.label = "root";
1389 root.hint = "";
1390 root.value = "";
1391 root.increased_value = "";
1392 root.decreased_value = "";
1393 root.child_count = 0;
1394 root.custom_accessibility_actions_count = 0;
1395
1396 auto flags = FlutterSemanticsFlags{
1398 };
1399 root.flags2 = &flags;
1400 bridge->AddFlutterSemanticsNodeUpdate(root);
1401
1402 bridge->CommitUpdates();
1403
1404 {
1405 auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
1406 EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kSwitch);
1407 EXPECT_EQ(root_node->GetData().GetCheckedState(),
1409
1410 // Get the IAccessible for the root node.
1411 IAccessible* native_view = root_node->GetNativeViewAccessible();
1412 ASSERT_TRUE(native_view != nullptr);
1413
1414 // Look up against the node itself (not one of its children).
1415 VARIANT varchild = {};
1416 varchild.vt = VT_I4;
1417
1418 varchild.lVal = CHILDID_SELF;
1419 VARIANT varrole = {};
1420
1421 // Verify the role of the switch is CHECKBUTTON
1422 ASSERT_EQ(native_view->get_accRole(varchild, &varrole), S_OK);
1423 ASSERT_EQ(varrole.lVal, ROLE_SYSTEM_CHECKBUTTON);
1424
1425 // Verify the switch is pressed.
1426 VARIANT native_state = {};
1427 ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
1428 EXPECT_TRUE(native_state.lVal & STATE_SYSTEM_PRESSED);
1429 EXPECT_TRUE(native_state.lVal & STATE_SYSTEM_CHECKED);
1430
1431 // Test similarly on UIA node.
1432 IRawElementProviderSimple* uia_node;
1433 native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
1434 ASSERT_EQ(uia_node->GetPropertyValue(UIA_ControlTypePropertyId, &varrole),
1435 S_OK);
1436 EXPECT_EQ(varrole.lVal, UIA_ButtonControlTypeId);
1437 ASSERT_EQ(uia_node->GetPropertyValue(UIA_ToggleToggleStatePropertyId,
1438 &native_state),
1439 S_OK);
1440 EXPECT_EQ(native_state.lVal, ToggleState_On);
1441 ASSERT_EQ(
1442 uia_node->GetPropertyValue(UIA_AriaPropertiesPropertyId, &native_state),
1443 S_OK);
1444 EXPECT_NE(std::wcsstr(native_state.bstrVal, L"pressed=true"), nullptr);
1445 }
1446
1447 // Test unpressed too.
1448 auto updated_flags = FlutterSemanticsFlags{
1450 };
1451 root.flags2 = &updated_flags;
1452
1453 bridge->AddFlutterSemanticsNodeUpdate(root);
1454 bridge->CommitUpdates();
1455
1456 {
1457 auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
1458 EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kSwitch);
1459 EXPECT_EQ(root_node->GetData().GetCheckedState(),
1461
1462 // Get the IAccessible for the root node.
1463 IAccessible* native_view = root_node->GetNativeViewAccessible();
1464 ASSERT_TRUE(native_view != nullptr);
1465
1466 // Look up against the node itself (not one of its children).
1467 VARIANT varchild = {};
1468 varchild.vt = VT_I4;
1469
1470 // Verify the switch is not pressed.
1471 varchild.lVal = CHILDID_SELF;
1472 VARIANT native_state = {};
1473 ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state)));
1474 EXPECT_FALSE(native_state.lVal & STATE_SYSTEM_PRESSED);
1475 EXPECT_FALSE(native_state.lVal & STATE_SYSTEM_CHECKED);
1476
1477 // Test similarly on UIA node.
1478 IRawElementProviderSimple* uia_node;
1479 native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
1480 ASSERT_EQ(uia_node->GetPropertyValue(UIA_ToggleToggleStatePropertyId,
1481 &native_state),
1482 S_OK);
1483 EXPECT_EQ(native_state.lVal, ToggleState_Off);
1484 ASSERT_EQ(
1485 uia_node->GetPropertyValue(UIA_AriaPropertiesPropertyId, &native_state),
1486 S_OK);
1487 EXPECT_NE(std::wcsstr(native_state.bstrVal, L"pressed=false"), nullptr);
1488 }
1489}
1490
1491TEST(FlutterWindowsViewTest, TooltipNodeData) {
1492 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1493 EngineModifier modifier(engine.get());
1495 [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
1496 return kSuccess;
1497 };
1498
1499 std::unique_ptr<FlutterWindowsView> view =
1500 engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1501 /*is_sized_to_content=*/false, BoxConstraints());
1502
1503 // Enable semantics to instantiate accessibility bridge.
1504 view->OnUpdateSemanticsEnabled(true);
1505
1506 auto bridge = view->accessibility_bridge().lock();
1507 ASSERT_TRUE(bridge);
1508
1510 root.id = 0;
1511 root.label = "root";
1512 root.hint = "";
1513 root.value = "";
1514 root.increased_value = "";
1515 root.decreased_value = "";
1516 root.tooltip = "tooltip";
1517 root.child_count = 0;
1518 root.custom_accessibility_actions_count = 0;
1519 auto flags = FlutterSemanticsFlags{
1520 .is_text_field = true,
1521 };
1522 root.flags2 = &flags;
1523 bridge->AddFlutterSemanticsNodeUpdate(root);
1524
1525 bridge->CommitUpdates();
1526 auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
1527 std::string tooltip = root_node->GetData().GetStringAttribute(
1529 EXPECT_EQ(tooltip, "tooltip");
1530
1531 // Check that MSAA name contains the tooltip.
1532 IAccessible* native_view = bridge->GetFlutterPlatformNodeDelegateFromID(0)
1533 .lock()
1534 ->GetNativeViewAccessible();
1535 VARIANT varchild = {.vt = VT_I4, .lVal = CHILDID_SELF};
1536 BSTR bname;
1537 ASSERT_EQ(native_view->get_accName(varchild, &bname), S_OK);
1538 EXPECT_NE(std::wcsstr(bname, L"tooltip"), nullptr);
1539
1540 // Check that UIA help text is equal to the tooltip.
1541 IRawElementProviderSimple* uia_node;
1542 native_view->QueryInterface(IID_PPV_ARGS(&uia_node));
1543 VARIANT varname{};
1544 ASSERT_EQ(uia_node->GetPropertyValue(UIA_HelpTextPropertyId, &varname), S_OK);
1545 std::string uia_tooltip = _com_util::ConvertBSTRToString(varname.bstrVal);
1546 EXPECT_EQ(uia_tooltip, "tooltip");
1547}
1548
1549// Don't block until the v-blank if it is disabled by the window.
1550// The surface is updated on the platform thread at startup.
1551TEST(FlutterWindowsViewTest, DisablesVSyncAtStartup) {
1552 auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
1553 auto engine = std::make_unique<MockFlutterWindowsEngine>(windows_proc_table);
1554 auto egl_manager = std::make_unique<egl::MockManager>();
1555 egl::MockContext render_context;
1556 auto surface = std::make_unique<egl::MockWindowSurface>();
1557 auto surface_ptr = surface.get();
1558
1559 EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(false));
1560 EXPECT_CALL(*engine.get(), PostRasterThreadTask).Times(0);
1561
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(*surface_ptr, MakeCurrent).WillOnce(Return(true));
1573 EXPECT_CALL(*surface_ptr, SetVSyncEnabled(false)).WillOnce(Return(true));
1574 EXPECT_CALL(render_context, ClearCurrent).WillOnce(Return(true));
1575
1576 EXPECT_CALL(*surface_ptr, Destroy).Times(1);
1577
1578 EngineModifier modifier{engine.get()};
1579 modifier.SetEGLManager(std::move(egl_manager));
1580
1581 std::unique_ptr<FlutterWindowsView> view =
1582 engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1583 /*is_sized_to_content=*/false, BoxConstraints());
1584}
1585
1586// Blocks until the v-blank if it is enabled by the window.
1587// The surface is updated on the platform thread at startup.
1588TEST(FlutterWindowsViewTest, EnablesVSyncAtStartup) {
1589 auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
1590 auto engine = std::make_unique<MockFlutterWindowsEngine>(windows_proc_table);
1591 auto egl_manager = std::make_unique<egl::MockManager>();
1592 egl::MockContext render_context;
1593 auto surface = std::make_unique<egl::MockWindowSurface>();
1594 auto surface_ptr = surface.get();
1595
1596 EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(false));
1597 EXPECT_CALL(*engine.get(), PostRasterThreadTask).Times(0);
1598 EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled)
1599 .WillOnce(Return(false));
1600
1601 EXPECT_CALL(*egl_manager.get(), render_context)
1602 .WillOnce(Return(&render_context));
1603 EXPECT_CALL(*surface_ptr, IsValid).WillOnce(Return(true));
1604
1605 InSequence s;
1606 EXPECT_CALL(*egl_manager.get(), CreateWindowSurface)
1607 .WillOnce(Return(std::move(surface)));
1608 EXPECT_CALL(*surface_ptr, MakeCurrent).WillOnce(Return(true));
1609 EXPECT_CALL(*surface_ptr, SetVSyncEnabled(true)).WillOnce(Return(true));
1610 EXPECT_CALL(render_context, ClearCurrent).WillOnce(Return(true));
1611
1612 EXPECT_CALL(*surface_ptr, Destroy).Times(1);
1613
1614 EngineModifier modifier{engine.get()};
1615 modifier.SetEGLManager(std::move(egl_manager));
1616
1617 std::unique_ptr<FlutterWindowsView> view =
1618 engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1619 /*is_sized_to_content=*/false, BoxConstraints());
1620}
1621
1622// Don't block until the v-blank if it is disabled by the window.
1623// The surface is updated on the raster thread if the engine is running.
1624TEST(FlutterWindowsViewTest, DisablesVSyncAfterStartup) {
1625 auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
1626 auto engine = std::make_unique<MockFlutterWindowsEngine>(windows_proc_table);
1627 auto egl_manager = std::make_unique<egl::MockManager>();
1628 egl::MockContext render_context;
1629 auto surface = std::make_unique<egl::MockWindowSurface>();
1630 auto surface_ptr = surface.get();
1631
1632 EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(true));
1633 EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled)
1634 .WillOnce(Return(true));
1635
1636 EXPECT_CALL(*egl_manager.get(), render_context)
1637 .WillOnce(Return(&render_context));
1638 EXPECT_CALL(*surface_ptr, IsValid).WillOnce(Return(true));
1639
1640 InSequence s;
1641 EXPECT_CALL(*egl_manager.get(), CreateWindowSurface)
1642 .WillOnce(Return(std::move(surface)));
1643 EXPECT_CALL(*engine.get(), PostRasterThreadTask)
1644 .WillOnce([](fml::closure callback) {
1645 callback();
1646 return true;
1647 });
1648 EXPECT_CALL(*surface_ptr, MakeCurrent).WillOnce(Return(true));
1649 EXPECT_CALL(*surface_ptr, SetVSyncEnabled(false)).WillOnce(Return(true));
1650 EXPECT_CALL(render_context, ClearCurrent).WillOnce(Return(true));
1651 EXPECT_CALL(*engine.get(), PostRasterThreadTask)
1652 .WillOnce([](fml::closure callback) {
1653 callback();
1654 return true;
1655 });
1656 EXPECT_CALL(*surface_ptr, Destroy).Times(1);
1657
1658 EngineModifier modifier{engine.get()};
1659 modifier.SetEGLManager(std::move(egl_manager));
1660
1661 std::unique_ptr<FlutterWindowsView> view =
1662 engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1663 /*is_sized_to_content=*/false, BoxConstraints());
1664}
1665
1666// Blocks until the v-blank if it is enabled by the window.
1667// The surface is updated on the raster thread if the engine is running.
1668TEST(FlutterWindowsViewTest, EnablesVSyncAfterStartup) {
1669 auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
1670 auto engine = std::make_unique<MockFlutterWindowsEngine>(windows_proc_table);
1671 auto egl_manager = std::make_unique<egl::MockManager>();
1672 egl::MockContext render_context;
1673 auto surface = std::make_unique<egl::MockWindowSurface>();
1674 auto surface_ptr = surface.get();
1675
1676 EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(true));
1677
1678 EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled)
1679 .WillOnce(Return(false));
1680
1681 EXPECT_CALL(*egl_manager.get(), render_context)
1682 .WillOnce(Return(&render_context));
1683 EXPECT_CALL(*surface_ptr, IsValid).WillOnce(Return(true));
1684
1685 InSequence s;
1686 EXPECT_CALL(*egl_manager.get(), CreateWindowSurface)
1687 .WillOnce(Return(std::move(surface)));
1688 EXPECT_CALL(*engine.get(), PostRasterThreadTask)
1689 .WillOnce([](fml::closure callback) {
1690 callback();
1691 return true;
1692 });
1693
1694 EXPECT_CALL(*surface_ptr, MakeCurrent).WillOnce(Return(true));
1695 EXPECT_CALL(*surface_ptr, SetVSyncEnabled(true)).WillOnce(Return(true));
1696 EXPECT_CALL(render_context, ClearCurrent).WillOnce(Return(true));
1697
1698 EXPECT_CALL(*engine.get(), PostRasterThreadTask)
1699 .WillOnce([](fml::closure callback) {
1700 callback();
1701 return true;
1702 });
1703 EXPECT_CALL(*surface_ptr, Destroy).Times(1);
1704
1705 EngineModifier modifier{engine.get()};
1706 modifier.SetEGLManager(std::move(egl_manager));
1707
1708 std::unique_ptr<FlutterWindowsView> view =
1709 engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1710 /*is_sized_to_content=*/false, BoxConstraints());
1711}
1712
1713// Desktop Window Manager composition can be disabled on Windows 7.
1714// If this happens, the app must synchronize with the vsync to prevent
1715// screen tearing.
1716TEST(FlutterWindowsViewTest, UpdatesVSyncOnDwmUpdates) {
1717 auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
1718 auto engine = std::make_unique<MockFlutterWindowsEngine>(windows_proc_table);
1719 auto egl_manager = std::make_unique<egl::MockManager>();
1720 egl::MockContext render_context;
1721 auto surface = std::make_unique<egl::MockWindowSurface>();
1722 auto surface_ptr = surface.get();
1723
1724 EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(true));
1725
1726 EXPECT_CALL(*engine.get(), PostRasterThreadTask)
1727 .WillRepeatedly([](fml::closure callback) {
1728 callback();
1729 return true;
1730 });
1731
1732 EXPECT_CALL(*egl_manager.get(), render_context)
1733 .WillRepeatedly(Return(&render_context));
1734
1735 EXPECT_CALL(*surface_ptr, IsValid).WillRepeatedly(Return(true));
1736 EXPECT_CALL(*surface_ptr, MakeCurrent).WillRepeatedly(Return(true));
1737 EXPECT_CALL(*surface_ptr, Destroy).Times(1);
1738 EXPECT_CALL(render_context, ClearCurrent).WillRepeatedly(Return(true));
1739
1740 InSequence s;
1741
1742 // Mock render surface initialization.
1743 std::unique_ptr<FlutterWindowsView> view;
1744 {
1745 EXPECT_CALL(*egl_manager, CreateWindowSurface)
1746 .WillOnce(Return(std::move(surface)));
1747 EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled)
1748 .WillOnce(Return(true));
1749 EXPECT_CALL(*surface_ptr, SetVSyncEnabled).WillOnce(Return(true));
1750
1751 EngineModifier engine_modifier{engine.get()};
1752 engine_modifier.SetEGLManager(std::move(egl_manager));
1753
1754 view = engine->CreateView(
1755 std::make_unique<NiceMock<MockWindowBindingHandler>>(), false,
1756 BoxConstraints());
1757 }
1758
1759 // Disabling DWM composition should enable vsync blocking on the surface.
1760 {
1761 EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled)
1762 .WillOnce(Return(false));
1763 EXPECT_CALL(*surface_ptr, SetVSyncEnabled(true)).WillOnce(Return(true));
1764
1765 engine->OnDwmCompositionChanged();
1766 }
1767
1768 // Enabling DWM composition should disable vsync blocking on the surface.
1769 {
1770 EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled)
1771 .WillOnce(Return(true));
1772 EXPECT_CALL(*surface_ptr, SetVSyncEnabled(false)).WillOnce(Return(true));
1773
1774 engine->OnDwmCompositionChanged();
1775 }
1776}
1777
1778TEST(FlutterWindowsViewTest, FocusTriggersWindowFocus) {
1779 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1780 auto window_binding_handler =
1781 std::make_unique<NiceMock<MockWindowBindingHandler>>();
1782 EXPECT_CALL(*window_binding_handler, Focus()).WillOnce(Return(true));
1783 std::unique_ptr<FlutterWindowsView> view =
1784 engine->CreateView(std::move(window_binding_handler),
1785 /*is_sized_to_content=*/false, BoxConstraints());
1786 EXPECT_TRUE(view->Focus());
1787}
1788
1789TEST(FlutterWindowsViewTest, OnFocusTriggersSendFocusViewEvent) {
1790 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1791 auto window_binding_handler =
1792 std::make_unique<NiceMock<MockWindowBindingHandler>>();
1793 std::unique_ptr<FlutterWindowsView> view =
1794 engine->CreateView(std::move(window_binding_handler),
1795 /*is_sized_to_content=*/false, BoxConstraints());
1796
1797 EngineModifier modifier(engine.get());
1798 bool received_focus_event = false;
1800 SendViewFocusEvent, [&](FLUTTER_API_SYMBOL(FlutterEngine) raw_engine,
1801 FlutterViewFocusEvent const* event) {
1802 EXPECT_EQ(event->state, FlutterViewFocusState::kFocused);
1804 EXPECT_EQ(event->view_id, view->view_id());
1805 EXPECT_EQ(event->struct_size, sizeof(FlutterViewFocusEvent));
1806 received_focus_event = true;
1807 return kSuccess;
1808 });
1811 EXPECT_TRUE(received_focus_event);
1812}
1813
1814TEST(FlutterWindowsViewTest, WindowMetricsEventContainsDisplayId) {
1815 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1816 EngineModifier modifier(engine.get());
1817
1818 auto window_binding_handler =
1819 std::make_unique<NiceMock<MockWindowBindingHandler>>();
1820 EXPECT_CALL(*window_binding_handler, GetDisplayId)
1821 .WillOnce(testing::Return(12));
1823 std::move(window_binding_handler), false,
1824 BoxConstraints()};
1825
1826 FlutterWindowMetricsEvent event = view.CreateWindowMetricsEvent();
1827 EXPECT_EQ(event.display_id, 12);
1828}
1829
1830TEST(FlutterWindowsViewTest, SizeChangeTriggersMetricsEventWhichHasDisplayId) {
1831 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1832 EngineModifier modifier(engine.get());
1833
1834 auto window_binding_handler =
1835 std::make_unique<NiceMock<MockWindowBindingHandler>>();
1836 EXPECT_CALL(*window_binding_handler, GetDisplayId)
1837 .WillOnce(testing::Return(12));
1839 std::move(window_binding_handler), false,
1840 BoxConstraints()};
1841
1842 bool received_metrics = false;
1844 SendWindowMetricsEvent,
1845 ([&received_metrics](auto engine,
1846 const FlutterWindowMetricsEvent* event) {
1847 received_metrics = true;
1848 EXPECT_EQ(event->display_id, 12);
1849 return kSuccess;
1850 }));
1851 view.OnWindowSizeChanged(100, 100);
1852 EXPECT_TRUE(received_metrics);
1853}
1854
1855// Verify that the first frame callback fires after OnFramePresented (OpenGL
1856// rendering path).
1857TEST(FlutterWindowsViewTest, FirstFrameCallbackFiresOnFramePresented) {
1858 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1859
1860 std::unique_ptr<FlutterWindowsView> view =
1861 engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1862 /*is_sized_to_content=*/false, BoxConstraints());
1863
1864 bool callback_fired = false;
1865 view->SetFirstFrameCallback([&callback_fired]() { callback_fired = true; });
1866
1867 // Simulate the raster thread presenting a frame.
1868 view->OnFramePresented();
1869
1870 // The callback is posted to the platform thread's task runner.
1871 // Process pending tasks to execute it.
1872 engine->task_runner()->ProcessTasks();
1873
1874 EXPECT_TRUE(callback_fired);
1875}
1876
1877// Verify that the first frame callback fires after PresentSoftwareBitmap
1878// (software rendering path).
1879TEST(FlutterWindowsViewTest, FirstFrameCallbackFiresOnSoftwareBitmapPresent) {
1880 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1881
1882 auto window_binding_handler =
1883 std::make_unique<NiceMock<MockWindowBindingHandler>>();
1884 EXPECT_CALL(*window_binding_handler, OnBitmapSurfaceUpdated)
1885 .WillOnce(Return(true));
1886
1887 std::unique_ptr<FlutterWindowsView> view =
1888 engine->CreateView(std::move(window_binding_handler),
1889 /*is_sized_to_content=*/false, BoxConstraints());
1890
1891 bool callback_fired = false;
1892 view->SetFirstFrameCallback([&callback_fired]() { callback_fired = true; });
1893
1894 // Simulate software rendering presenting a bitmap.
1895 const uint32_t pixel = 0;
1896 view->PresentSoftwareBitmap(&pixel, sizeof(pixel), 1);
1897
1898 // Process pending tasks.
1899 engine->task_runner()->ProcessTasks();
1900
1901 EXPECT_TRUE(callback_fired);
1902}
1903
1904// Verify that the first frame callback fires only once, even if multiple
1905// frames are presented.
1906TEST(FlutterWindowsViewTest, FirstFrameCallbackFiresOnlyOnce) {
1907 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1908
1909 std::unique_ptr<FlutterWindowsView> view =
1910 engine->CreateView(std::make_unique<NiceMock<MockWindowBindingHandler>>(),
1911 /*is_sized_to_content=*/false, BoxConstraints());
1912
1913 int callback_count = 0;
1914 view->SetFirstFrameCallback([&callback_count]() { callback_count++; });
1915
1916 // Present two frames.
1917 view->OnFramePresented();
1918 view->OnFramePresented();
1919
1920 // Process all pending tasks.
1921 engine->task_runner()->ProcessTasks();
1922
1923 EXPECT_EQ(callback_count, 1);
1924}
1925
1926// Verify that the first frame callback does not fire if PresentSoftwareBitmap
1927// fails.
1928TEST(FlutterWindowsViewTest, FirstFrameCallbackSkippedOnFailedSoftwarePresent) {
1929 std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
1930
1931 auto window_binding_handler =
1932 std::make_unique<NiceMock<MockWindowBindingHandler>>();
1933 EXPECT_CALL(*window_binding_handler, OnBitmapSurfaceUpdated)
1934 .WillOnce(Return(false));
1935
1936 std::unique_ptr<FlutterWindowsView> view =
1937 engine->CreateView(std::move(window_binding_handler),
1938 /*is_sized_to_content=*/false, BoxConstraints());
1939
1940 bool callback_fired = false;
1941 view->SetFirstFrameCallback([&callback_fired]() { callback_fired = true; });
1942
1943 const uint32_t pixel = 0;
1944 view->PresentSoftwareBitmap(&pixel, sizeof(pixel), 1);
1945
1946 engine->task_runner()->ProcessTasks();
1947
1948 EXPECT_FALSE(callback_fired);
1949}
1950
1951} // namespace testing
1952} // namespace flutter
flutter::FakeDelegate fake_delegate
std::shared_ptr< FlutterPlatformNodeDelegateWindows > node_delegate
void SetEGLManager(std::unique_ptr< egl::Manager > egl_manager)
FlutterEngineProcTable & embedder_api()
Controls a view that displays Flutter content.
static const JsonMessageCodec & GetInstance()
std::unique_ptr< std::vector< uint8_t > > EncodeMessage(const T &message) const
std::function< void(bool)> ResponseCallback
Mock for the |Context| base class.
int32_t value
@ kFlutterCheckStateTrue
The semantics node is checked.
Definition embedder.h:291
@ kFlutterCheckStateFalse
The semantics node is not checked.
Definition embedder.h:293
@ kFlutterCheckStateMixed
The semantics node represents a checkbox in mixed state.
Definition embedder.h:295
#define FLUTTER_API_SYMBOL(symbol)
Definition embedder.h:67
@ kFocused
Specifies that a view has platform focus.
Definition embedder.h:1224
@ kUndefined
Definition embedder.h:1205
FlutterAccessibilityFeature
Definition embedder.h:91
void(* VoidCallback)(void *)
Definition embedder.h:416
FlutterEngineDisplaysUpdateType
Definition embedder.h:2373
int64_t FlutterViewId
Definition embedder.h:393
@ kFlutterTristateTrue
The property is applicable and its state is "true" or "on".
Definition embedder.h:282
@ kFlutterTristateFalse
The property is applicable and its state is "false" or "off".
Definition embedder.h:284
FlutterEngine engine
Definition main.cc:84
VkSurfaceKHR surface
Definition main.cc:65
FlView * view
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
G_BEGIN_DECLS FlutterViewId view_id
void(* FlutterDesktopBinaryReply)(const uint8_t *data, size_t data_size, void *user_data)
@ DefaultImpeller
FlutterDesktopBinaryReply callback
#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition macros.h:27
std::shared_ptr< ImpellerAllocator > allocator
void MockEmbedderApiForKeyboard(EngineModifier &modifier, std::shared_ptr< MockKeyResponseController > response_controller)
TEST(NativeAssetsManagerTest, NoAvailableAssets)
constexpr int64_t kImplicitViewId
DEF_SWITCHES_START aot vmservice shared library name
Definition switch_defs.h:27
static void ScheduleFrame(JNIEnv *env, jobject jcaller, jlong shell_holder)
std::function< void()> closure
Definition closure.h:14
Definition ref_ptr.h:261
#define MOCK_ENGINE_PROC(proc, mock_impl)
std::shared_ptr< ContextGLES > context
FlutterDesktopImpellerSwitch impeller_switch
FlutterEngineScheduleFrameFnPtr ScheduleFrame
Definition embedder.h:3805
FlutterEngineSendWindowMetricsEventFnPtr SendWindowMetricsEvent
Definition embedder.h:3774
FlutterEngineSendViewFocusEventFnPtr SendViewFocusEvent
Definition embedder.h:3809
FlutterEngineUpdateSemanticsEnabledFnPtr UpdateSemanticsEnabled
Definition embedder.h:3787
FlutterEnginePostRenderThreadTaskFnPtr PostRenderThreadTask
Definition embedder.h:3795
FlutterTristate is_toggled
Definition embedder.h:309
FlutterTristate is_expanded
Whether a semantic node that is currently expanded.
Definition embedder.h:311
bool is_text_field
Whether the semantic node represents a text field.
Definition embedder.h:320
FlutterCheckState is_checked
Whether a semantics node is checked.
Definition embedder.h:302
int32_t id
The unique identifier for this node.
Definition embedder.h:1668
FlutterRect rect
The bounding box for this node in its coordinate system.
Definition embedder.h:1713
size_t child_count
The number of children this node has.
Definition embedder.h:1718
const char * label
A textual description of the node.
Definition embedder.h:1698
FlutterViewFocusState state
The focus state of the view.
Definition embedder.h:1240
FlutterViewFocusDirection direction
The direction in which the focus transitioned across [FlutterView]s.
Definition embedder.h:1243
FlutterViewId view_id
The identifier of the view that received the focus event.
Definition embedder.h:1237
FlutterEngineDisplayId display_id
The identifier of the display the view is rendering on.
Definition embedder.h:1074
#define SUCCEEDED(hr)