| Flutter Windows Embedder |

platform_handler_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/platform_handler.h"
6
7 #include <memory>
8
9 #include "flutter/fml/macros.h"
10 #include "flutter/shell/platform/common/json_method_codec.h"
11 #include "flutter/shell/platform/windows/flutter_windows_view.h"
12 #include "flutter/shell/platform/windows/testing/engine_modifier.h"
13 #include "flutter/shell/platform/windows/testing/flutter_windows_engine_builder.h"
14 #include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h"
15 #include "flutter/shell/platform/windows/testing/test_binary_messenger.h"
16 #include "flutter/shell/platform/windows/testing/windows_test.h"
17 #include "gmock/gmock.h"
18 #include "gtest/gtest.h"
19 #include "rapidjson/document.h"
20
21 namespace flutter {
22 namespace testing {
23
24 namespace {
25 using ::testing::_;
26 using ::testing::NiceMock;
27 using ::testing::Return;
28
29 static constexpr char kChannelName[] = "flutter/platform";
30
31 static constexpr char kClipboardGetDataMessage[] =
32 "{\"method\":\"Clipboard.getData\",\"args\":\"text/plain\"}";
33 static constexpr char kClipboardGetDataFakeContentTypeMessage[] =
34 "{\"method\":\"Clipboard.getData\",\"args\":\"text/madeupcontenttype\"}";
35 static constexpr char kClipboardHasStringsMessage[] =
36 "{\"method\":\"Clipboard.hasStrings\",\"args\":\"text/plain\"}";
37 static constexpr char kClipboardHasStringsFakeContentTypeMessage[] =
38 "{\"method\":\"Clipboard.hasStrings\",\"args\":\"text/madeupcontenttype\"}";
39 static constexpr char kClipboardSetDataMessage[] =
40 "{\"method\":\"Clipboard.setData\",\"args\":{\"text\":\"hello\"}}";
41 static constexpr char kClipboardSetDataNullTextMessage[] =
42 "{\"method\":\"Clipboard.setData\",\"args\":{\"text\":null}}";
43 static constexpr char kClipboardSetDataUnknownTypeMessage[] =
44 "{\"method\":\"Clipboard.setData\",\"args\":{\"madeuptype\":\"hello\"}}";
45 static constexpr char kSystemSoundTypeAlertMessage[] =
46 "{\"method\":\"SystemSound.play\",\"args\":\"SystemSoundType.alert\"}";
47 static constexpr char kSystemExitApplicationRequiredMessage[] =
48 "{\"method\":\"System.exitApplication\",\"args\":{\"type\":\"required\","
49 "\"exitCode\":1}}";
50 static constexpr char kSystemExitApplicationCancelableMessage[] =
51 "{\"method\":\"System.exitApplication\",\"args\":{\"type\":\"cancelable\","
52 "\"exitCode\":2}}";
53 static constexpr char kExitResponseCancelMessage[] =
54 "[{\"response\":\"cancel\"}]";
55 static constexpr char kExitResponseExitMessage[] = "[{\"response\":\"exit\"}]";
56
57 static constexpr int kAccessDeniedErrorCode = 5;
58 static constexpr int kErrorSuccess = 0;
59 static constexpr int kArbitraryErrorCode = 1;
60
61 // Test implementation of PlatformHandler to allow testing the PlatformHandler
62 // logic.
63 class MockPlatformHandler : public PlatformHandler {
64 public:
65 explicit MockPlatformHandler(
66 BinaryMessenger* messenger,
67 FlutterWindowsEngine* engine,
68 std::optional<std::function<std::unique_ptr<ScopedClipboardInterface>()>>
69 scoped_clipboard_provider = std::nullopt)
70 : PlatformHandler(messenger, engine, scoped_clipboard_provider) {}
71
72 virtual ~MockPlatformHandler() = default;
73
74 MOCK_METHOD(void,
75 GetPlainText,
76 (std::unique_ptr<MethodResult<rapidjson::Document>>,
77 std::string_view key),
78 (override));
79 MOCK_METHOD(void,
80 GetHasStrings,
81 (std::unique_ptr<MethodResult<rapidjson::Document>>),
82 (override));
83 MOCK_METHOD(void,
84 SetPlainText,
85 (const std::string&,
86 std::unique_ptr<MethodResult<rapidjson::Document>>),
87 (override));
88 MOCK_METHOD(void,
89 SystemSoundPlay,
90 (const std::string&,
91 std::unique_ptr<MethodResult<rapidjson::Document>>),
92 (override));
93
94 MOCK_METHOD(void,
95 QuitApplication,
96 (std::optional<HWND> hwnd,
97 std::optional<WPARAM> wparam,
98 std::optional<LPARAM> lparam,
99 UINT exit_code),
100 (override));
101
102 private:
103 FML_DISALLOW_COPY_AND_ASSIGN(MockPlatformHandler);
104 };
105
106 // A test version of the private ScopedClipboard.
107 class MockScopedClipboard : public ScopedClipboardInterface {
108 public:
109 MockScopedClipboard() = default;
110 virtual ~MockScopedClipboard() = default;
111
112 MOCK_METHOD(int, Open, (HWND window), (override));
113 MOCK_METHOD(bool, HasString, (), (override));
114 MOCK_METHOD((std::variant<std::wstring, int>), GetString, (), (override));
115 MOCK_METHOD(int, SetString, (const std::wstring string), (override));
116
117 private:
118 FML_DISALLOW_COPY_AND_ASSIGN(MockScopedClipboard);
119 };
120
121 std::string SimulatePlatformMessage(TestBinaryMessenger* messenger,
122 std::string message) {
123 std::string result;
124 EXPECT_TRUE(messenger->SimulateEngineMessage(
125 kChannelName, reinterpret_cast<const uint8_t*>(message.c_str()),
126 message.size(),
127 [result = &result](const uint8_t* reply, size_t reply_size) {
128 std::string response(reinterpret_cast<const char*>(reply), reply_size);
129
130 *result = response;
131 }));
132
133 return result;
134 }
135
136 } // namespace
137
138 class PlatformHandlerTest : public WindowsTest {
139 public:
140 PlatformHandlerTest() = default;
141 virtual ~PlatformHandlerTest() = default;
142
143 protected:
144 FlutterWindowsEngine* engine() { return engine_.get(); }
145
146 void UseHeadlessEngine() {
147 FlutterWindowsEngineBuilder builder{GetContext()};
148
149 engine_ = builder.Build();
150 }
151
152 void UseEngineWithView() {
153 FlutterWindowsEngineBuilder builder{GetContext()};
154
155 auto window = std::make_unique<NiceMock<MockWindowBindingHandler>>();
156
157 engine_ = builder.Build();
158 view_ = std::make_unique<FlutterWindowsView>(kImplicitViewId, engine_.get(),
159 std::move(window));
160
161 EngineModifier modifier{engine_.get()};
162 modifier.SetImplicitView(view_.get());
163 }
164
165 private:
166 std::unique_ptr<FlutterWindowsEngine> engine_;
167 std::unique_ptr<FlutterWindowsView> view_;
168
169 FML_DISALLOW_COPY_AND_ASSIGN(PlatformHandlerTest);
170 };
171
172 TEST_F(PlatformHandlerTest, GetClipboardData) {
173 UseEngineWithView();
174
175 TestBinaryMessenger messenger;
176 PlatformHandler platform_handler(&messenger, engine(), []() {
177 auto clipboard = std::make_unique<MockScopedClipboard>();
178
179 EXPECT_CALL(*clipboard.get(), Open)
180 .Times(1)
181 .WillOnce(Return(kErrorSuccess));
182 EXPECT_CALL(*clipboard.get(), HasString).Times(1).WillOnce(Return(true));
183 EXPECT_CALL(*clipboard.get(), GetString)
184 .Times(1)
185 .WillOnce(Return(std::wstring(L"Hello world")));
186
187 return clipboard;
188 });
189
190 std::string result =
191 SimulatePlatformMessage(&messenger, kClipboardGetDataMessage);
192
193 EXPECT_EQ(result, "[{\"text\":\"Hello world\"}]");
194 }
195
196 TEST_F(PlatformHandlerTest, GetClipboardDataRejectsUnknownContentType) {
197 UseEngineWithView();
198
199 TestBinaryMessenger messenger;
200 PlatformHandler platform_handler(&messenger, engine());
201
202 // Requesting an unknown content type is an error.
203 std::string result = SimulatePlatformMessage(
204 &messenger, kClipboardGetDataFakeContentTypeMessage);
205
206 EXPECT_EQ(result, "[\"Clipboard error\",\"Unknown clipboard format\",null]");
207 }
208
209 TEST_F(PlatformHandlerTest, GetClipboardDataRequiresView) {
210 UseHeadlessEngine();
211
212 TestBinaryMessenger messenger;
213 PlatformHandler platform_handler(&messenger, engine());
214
215 std::string result =
216 SimulatePlatformMessage(&messenger, kClipboardGetDataMessage);
217
218 EXPECT_EQ(result,
219 "[\"Clipboard error\",\"Clipboard is not available in "
220 "Windows headless mode\",null]");
221 }
222
223 TEST_F(PlatformHandlerTest, GetClipboardDataReportsOpenFailure) {
224 UseEngineWithView();
225
226 TestBinaryMessenger messenger;
227 PlatformHandler platform_handler(&messenger, engine(), []() {
228 auto clipboard = std::make_unique<MockScopedClipboard>();
229
230 EXPECT_CALL(*clipboard.get(), Open)
231 .Times(1)
232 .WillOnce(Return(kArbitraryErrorCode));
233
234 return clipboard;
235 });
236
237 std::string result =
238 SimulatePlatformMessage(&messenger, kClipboardGetDataMessage);
239
240 EXPECT_EQ(result, "[\"Clipboard error\",\"Unable to open clipboard\",1]");
241 }
242
243 TEST_F(PlatformHandlerTest, GetClipboardDataReportsGetDataFailure) {
244 UseEngineWithView();
245
246 TestBinaryMessenger messenger;
247 PlatformHandler platform_handler(&messenger, engine(), []() {
248 auto clipboard = std::make_unique<MockScopedClipboard>();
249
250 EXPECT_CALL(*clipboard.get(), Open)
251 .Times(1)
252 .WillOnce(Return(kErrorSuccess));
253 EXPECT_CALL(*clipboard.get(), HasString).Times(1).WillOnce(Return(true));
254 EXPECT_CALL(*clipboard.get(), GetString)
255 .Times(1)
256 .WillOnce(Return(kArbitraryErrorCode));
257
258 return clipboard;
259 });
260
261 std::string result =
262 SimulatePlatformMessage(&messenger, kClipboardGetDataMessage);
263
264 EXPECT_EQ(result, "[\"Clipboard error\",\"Unable to get clipboard data\",1]");
265 }
266
267 TEST_F(PlatformHandlerTest, ClipboardHasStrings) {
268 UseEngineWithView();
269
270 TestBinaryMessenger messenger;
271 PlatformHandler platform_handler(&messenger, engine(), []() {
272 auto clipboard = std::make_unique<MockScopedClipboard>();
273
274 EXPECT_CALL(*clipboard.get(), Open)
275 .Times(1)
276 .WillOnce(Return(kErrorSuccess));
277 EXPECT_CALL(*clipboard.get(), HasString).Times(1).WillOnce(Return(true));
278
279 return clipboard;
280 });
281
282 std::string result =
283 SimulatePlatformMessage(&messenger, kClipboardHasStringsMessage);
284
285 EXPECT_EQ(result, "[{\"value\":true}]");
286 }
287
288 TEST_F(PlatformHandlerTest, ClipboardHasStringsReturnsFalse) {
289 UseEngineWithView();
290
291 TestBinaryMessenger messenger;
292 PlatformHandler platform_handler(&messenger, engine(), []() {
293 auto clipboard = std::make_unique<MockScopedClipboard>();
294
295 EXPECT_CALL(*clipboard.get(), Open)
296 .Times(1)
297 .WillOnce(Return(kErrorSuccess));
298 EXPECT_CALL(*clipboard.get(), HasString).Times(1).WillOnce(Return(false));
299
300 return clipboard;
301 });
302
303 std::string result =
304 SimulatePlatformMessage(&messenger, kClipboardHasStringsMessage);
305
306 EXPECT_EQ(result, "[{\"value\":false}]");
307 }
308
309 TEST_F(PlatformHandlerTest, ClipboardHasStringsRejectsUnknownContentType) {
310 UseEngineWithView();
311
312 TestBinaryMessenger messenger;
313 PlatformHandler platform_handler(&messenger, engine());
314
315 std::string result = SimulatePlatformMessage(
316 &messenger, kClipboardHasStringsFakeContentTypeMessage);
317
318 EXPECT_EQ(result, "[\"Clipboard error\",\"Unknown clipboard format\",null]");
319 }
320
321 TEST_F(PlatformHandlerTest, ClipboardHasStringsRequiresView) {
322 UseHeadlessEngine();
323
324 TestBinaryMessenger messenger;
325 PlatformHandler platform_handler(&messenger, engine());
326
327 std::string result =
328 SimulatePlatformMessage(&messenger, kClipboardHasStringsMessage);
329
330 EXPECT_EQ(result,
331 "[\"Clipboard error\",\"Clipboard is not available in Windows "
332 "headless mode\",null]");
333 }
334
335 // Regression test for https://github.com/flutter/flutter/issues/95817.
336 TEST_F(PlatformHandlerTest, ClipboardHasStringsIgnoresPermissionErrors) {
337 UseEngineWithView();
338
339 TestBinaryMessenger messenger;
340 PlatformHandler platform_handler(&messenger, engine(), []() {
341 auto clipboard = std::make_unique<MockScopedClipboard>();
342
343 EXPECT_CALL(*clipboard.get(), Open)
344 .Times(1)
345 .WillOnce(Return(kAccessDeniedErrorCode));
346
347 return clipboard;
348 });
349
350 std::string result =
351 SimulatePlatformMessage(&messenger, kClipboardHasStringsMessage);
352
353 EXPECT_EQ(result, "[{\"value\":false}]");
354 }
355
356 TEST_F(PlatformHandlerTest, ClipboardHasStringsReportsErrors) {
357 UseEngineWithView();
358
359 TestBinaryMessenger messenger;
360 PlatformHandler platform_handler(&messenger, engine(), []() {
361 auto clipboard = std::make_unique<MockScopedClipboard>();
362
363 EXPECT_CALL(*clipboard.get(), Open)
364 .Times(1)
365 .WillOnce(Return(kArbitraryErrorCode));
366
367 return clipboard;
368 });
369
370 std::string result =
371 SimulatePlatformMessage(&messenger, kClipboardHasStringsMessage);
372
373 EXPECT_EQ(result, "[\"Clipboard error\",\"Unable to open clipboard\",1]");
374 }
375
376 TEST_F(PlatformHandlerTest, ClipboardSetData) {
377 UseEngineWithView();
378
379 TestBinaryMessenger messenger;
380 PlatformHandler platform_handler(&messenger, engine(), []() {
381 auto clipboard = std::make_unique<MockScopedClipboard>();
382
383 EXPECT_CALL(*clipboard.get(), Open)
384 .Times(1)
385 .WillOnce(Return(kErrorSuccess));
386 EXPECT_CALL(*clipboard.get(), SetString)
387 .Times(1)
388 .WillOnce([](std::wstring string) {
389 EXPECT_EQ(string, L"hello");
390 return kErrorSuccess;
391 });
392
393 return clipboard;
394 });
395
396 std::string result =
397 SimulatePlatformMessage(&messenger, kClipboardSetDataMessage);
398
399 EXPECT_EQ(result, "[null]");
400 }
401
402 // Regression test for: https://github.com/flutter/flutter/issues/121976
403 TEST_F(PlatformHandlerTest, ClipboardSetDataTextMustBeString) {
404 UseEngineWithView();
405
406 TestBinaryMessenger messenger;
407 PlatformHandler platform_handler(&messenger, engine());
408
409 std::string result =
410 SimulatePlatformMessage(&messenger, kClipboardSetDataNullTextMessage);
411
412 EXPECT_EQ(result, "[\"Clipboard error\",\"Unknown clipboard format\",null]");
413 }
414
415 TEST_F(PlatformHandlerTest, ClipboardSetDataUnknownType) {
416 UseEngineWithView();
417
418 TestBinaryMessenger messenger;
419 PlatformHandler platform_handler(&messenger, engine());
420
421 std::string result =
422 SimulatePlatformMessage(&messenger, kClipboardSetDataUnknownTypeMessage);
423
424 EXPECT_EQ(result, "[\"Clipboard error\",\"Unknown clipboard format\",null]");
425 }
426
427 TEST_F(PlatformHandlerTest, ClipboardSetDataRequiresView) {
428 UseHeadlessEngine();
429
430 TestBinaryMessenger messenger;
431 PlatformHandler platform_handler(&messenger, engine());
432
433 std::string result =
434 SimulatePlatformMessage(&messenger, kClipboardSetDataMessage);
435
436 EXPECT_EQ(result,
437 "[\"Clipboard error\",\"Clipboard is not available in Windows "
438 "headless mode\",null]");
439 }
440
441 TEST_F(PlatformHandlerTest, ClipboardSetDataReportsOpenFailure) {
442 UseEngineWithView();
443
444 TestBinaryMessenger messenger;
445 PlatformHandler platform_handler(&messenger, engine(), []() {
446 auto clipboard = std::make_unique<MockScopedClipboard>();
447
448 EXPECT_CALL(*clipboard.get(), Open)
449 .Times(1)
450 .WillOnce(Return(kArbitraryErrorCode));
451
452 return clipboard;
453 });
454
455 std::string result =
456 SimulatePlatformMessage(&messenger, kClipboardSetDataMessage);
457
458 EXPECT_EQ(result, "[\"Clipboard error\",\"Unable to open clipboard\",1]");
459 }
460
461 TEST_F(PlatformHandlerTest, ClipboardSetDataReportsSetDataFailure) {
462 UseEngineWithView();
463
464 TestBinaryMessenger messenger;
465 PlatformHandler platform_handler(&messenger, engine(), []() {
466 auto clipboard = std::make_unique<MockScopedClipboard>();
467
468 EXPECT_CALL(*clipboard.get(), Open)
469 .Times(1)
470 .WillOnce(Return(kErrorSuccess));
471 EXPECT_CALL(*clipboard.get(), SetString)
472 .Times(1)
473 .WillOnce(Return(kArbitraryErrorCode));
474
475 return clipboard;
476 });
477
478 std::string result =
479 SimulatePlatformMessage(&messenger, kClipboardSetDataMessage);
480
481 EXPECT_EQ(result, "[\"Clipboard error\",\"Unable to set clipboard data\",1]");
482 }
483
484 TEST_F(PlatformHandlerTest, PlaySystemSound) {
485 UseHeadlessEngine();
486
487 TestBinaryMessenger messenger;
488 MockPlatformHandler platform_handler(&messenger, engine());
489
490 EXPECT_CALL(platform_handler, SystemSoundPlay("SystemSoundType.alert", _))
491 .WillOnce([](const std::string& sound,
492 std::unique_ptr<MethodResult<rapidjson::Document>> result) {
493 result->Success();
494 });
495
496 std::string result =
497 SimulatePlatformMessage(&messenger, kSystemSoundTypeAlertMessage);
498
499 EXPECT_EQ(result, "[null]");
500 }
501
502 TEST_F(PlatformHandlerTest, SystemExitApplicationRequired) {
503 UseHeadlessEngine();
504 UINT exit_code = 0;
505
506 TestBinaryMessenger messenger([](const std::string& channel,
507 const uint8_t* message, size_t size,
508 BinaryReply reply) {});
509 MockPlatformHandler platform_handler(&messenger, engine());
510
511 ON_CALL(platform_handler, QuitApplication)
512 .WillByDefault([&exit_code](std::optional<HWND> hwnd,
513 std::optional<WPARAM> wparam,
514 std::optional<LPARAM> lparam,
515 UINT ec) { exit_code = ec; });
516 EXPECT_CALL(platform_handler, QuitApplication).Times(1);
517
518 std::string result = SimulatePlatformMessage(
519 &messenger, kSystemExitApplicationRequiredMessage);
520 EXPECT_EQ(result, "[{\"response\":\"exit\"}]");
521 EXPECT_EQ(exit_code, 1);
522 }
523
524 TEST_F(PlatformHandlerTest, SystemExitApplicationCancelableCancel) {
525 UseHeadlessEngine();
526 bool called_cancel = false;
527
528 TestBinaryMessenger messenger(
529 [&called_cancel](const std::string& channel, const uint8_t* message,
530 size_t size, BinaryReply reply) {
531 reply(reinterpret_cast<const uint8_t*>(kExitResponseCancelMessage),
532 sizeof(kExitResponseCancelMessage));
533 called_cancel = true;
534 });
535 MockPlatformHandler platform_handler(&messenger, engine());
536
537 EXPECT_CALL(platform_handler, QuitApplication).Times(0);
538
539 std::string result = SimulatePlatformMessage(
540 &messenger, kSystemExitApplicationCancelableMessage);
541 EXPECT_EQ(result, "[{\"response\":\"cancel\"}]");
542 EXPECT_TRUE(called_cancel);
543 }
544
545 TEST_F(PlatformHandlerTest, SystemExitApplicationCancelableExit) {
546 UseHeadlessEngine();
547 bool called_cancel = false;
548 UINT exit_code = 0;
549
550 TestBinaryMessenger messenger(
551 [&called_cancel](const std::string& channel, const uint8_t* message,
552 size_t size, BinaryReply reply) {
553 reply(reinterpret_cast<const uint8_t*>(kExitResponseExitMessage),
554 sizeof(kExitResponseExitMessage));
555 called_cancel = true;
556 });
557 MockPlatformHandler platform_handler(&messenger, engine());
558
559 ON_CALL(platform_handler, QuitApplication)
560 .WillByDefault([&exit_code](std::optional<HWND> hwnd,
561 std::optional<WPARAM> wparam,
562 std::optional<LPARAM> lparam,
563 UINT ec) { exit_code = ec; });
564 EXPECT_CALL(platform_handler, QuitApplication).Times(1);
565
566 std::string result = SimulatePlatformMessage(
567 &messenger, kSystemExitApplicationCancelableMessage);
568 EXPECT_EQ(result, "[{\"response\":\"cancel\"}]");
569 EXPECT_TRUE(called_cancel);
570 EXPECT_EQ(exit_code, 2);
571 }
572
573 } // namespace testing
574 } // namespace flutter
flutter::FlutterWindowsEngine
Definition: flutter_windows_engine.h:90
flutter::MethodResult
Definition: method_result.h:17
flutter::PlatformHandler
Definition: platform_handler.h:33
flutter::testing::PlatformHandlerTest
Definition: platform_handler_unittests.cc:138
flutter::testing::PlatformHandlerTest::UseEngineWithView
void UseEngineWithView()
Definition: platform_handler_unittests.cc:152
flutter::testing::PlatformHandlerTest::UseHeadlessEngine
void UseHeadlessEngine()
Definition: platform_handler_unittests.cc:146
flutter::testing::PlatformHandlerTest::PlatformHandlerTest
PlatformHandlerTest()=default
flutter::testing::PlatformHandlerTest::engine
FlutterWindowsEngine * engine()
Definition: platform_handler_unittests.cc:144
flutter::testing::PlatformHandlerTest::~PlatformHandlerTest
virtual ~PlatformHandlerTest()=default
kChannelName
static constexpr char kChannelName[]
Definition: cursor_handler.cc:13
flutter_windows_view.h
json_method_codec.h
key
int key
Definition: keyboard_key_handler_unittests.cc:114
message
Win32Message message
Definition: keyboard_unittests.cc:137
flutter::testing::TEST_F
TEST_F(CompositorOpenGLTest, CreateBackingStore)
Definition: compositor_opengl_unittests.cc:125
flutter
Definition: accessibility_bridge_windows.cc:11
flutter::BinaryReply
std::function< void(const uint8_t *reply, size_t reply_size)> BinaryReply
Definition: binary_messenger.h:17
flutter::kImplicitViewId
constexpr FlutterViewId kImplicitViewId
Definition: flutter_windows_engine.h:55
kErrorSuccess
static constexpr int kErrorSuccess
Definition: platform_handler.cc:45
kAccessDeniedErrorCode
static constexpr int kAccessDeniedErrorCode
Definition: platform_handler.cc:44
platform_handler.h