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