| Flutter Windows Embedder |

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