| 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 private:
153 std::unique_ptr<FlutterWindowsEngine> engine_;
154 std::unique_ptr<FlutterWindowsView> view_;
155
156 FML_DISALLOW_COPY_AND_ASSIGN(PlatformHandlerTest);
157 };
158
159 TEST_F(PlatformHandlerTest, GetClipboardData) {
160 UseHeadlessEngine();
161
162 TestBinaryMessenger messenger;
163 PlatformHandler platform_handler(&messenger, engine(), []() {
164 auto clipboard = std::make_unique<MockScopedClipboard>();
165
166 EXPECT_CALL(*clipboard.get(), Open)
167 .Times(1)
168 .WillOnce(Return(kErrorSuccess));
169 EXPECT_CALL(*clipboard.get(), HasString).Times(1).WillOnce(Return(true));
170 EXPECT_CALL(*clipboard.get(), GetString)
171 .Times(1)
172 .WillOnce(Return(std::wstring(L"Hello world")));
173
174 return clipboard;
175 });
176
177 std::string result =
178 SimulatePlatformMessage(&messenger, kClipboardGetDataMessage);
179
180 EXPECT_EQ(result, "[{\"text\":\"Hello world\"}]");
181 }
182
183 TEST_F(PlatformHandlerTest, GetClipboardDataRejectsUnknownContentType) {
184 UseHeadlessEngine();
185
186 TestBinaryMessenger messenger;
187 PlatformHandler platform_handler(&messenger, engine());
188
189 // Requesting an unknown content type is an error.
190 std::string result = SimulatePlatformMessage(
191 &messenger, kClipboardGetDataFakeContentTypeMessage);
192
193 EXPECT_EQ(result, "[\"Clipboard error\",\"Unknown clipboard format\",null]");
194 }
195
196 TEST_F(PlatformHandlerTest, GetClipboardDataReportsOpenFailure) {
197 UseHeadlessEngine();
198
199 TestBinaryMessenger messenger;
200 PlatformHandler platform_handler(&messenger, engine(), []() {
201 auto clipboard = std::make_unique<MockScopedClipboard>();
202
203 EXPECT_CALL(*clipboard.get(), Open)
204 .Times(1)
205 .WillOnce(Return(kArbitraryErrorCode));
206
207 return clipboard;
208 });
209
210 std::string result =
211 SimulatePlatformMessage(&messenger, kClipboardGetDataMessage);
212
213 EXPECT_EQ(result, "[\"Clipboard error\",\"Unable to open clipboard\",1]");
214 }
215
216 TEST_F(PlatformHandlerTest, GetClipboardDataReportsGetDataFailure) {
217 UseHeadlessEngine();
218
219 TestBinaryMessenger messenger;
220 PlatformHandler platform_handler(&messenger, engine(), []() {
221 auto clipboard = std::make_unique<MockScopedClipboard>();
222
223 EXPECT_CALL(*clipboard.get(), Open)
224 .Times(1)
225 .WillOnce(Return(kErrorSuccess));
226 EXPECT_CALL(*clipboard.get(), HasString).Times(1).WillOnce(Return(true));
227 EXPECT_CALL(*clipboard.get(), GetString)
228 .Times(1)
229 .WillOnce(Return(kArbitraryErrorCode));
230
231 return clipboard;
232 });
233
234 std::string result =
235 SimulatePlatformMessage(&messenger, kClipboardGetDataMessage);
236
237 EXPECT_EQ(result, "[\"Clipboard error\",\"Unable to get clipboard data\",1]");
238 }
239
240 TEST_F(PlatformHandlerTest, ClipboardHasStrings) {
241 UseHeadlessEngine();
242
243 TestBinaryMessenger messenger;
244 PlatformHandler platform_handler(&messenger, engine(), []() {
245 auto clipboard = std::make_unique<MockScopedClipboard>();
246
247 EXPECT_CALL(*clipboard.get(), Open)
248 .Times(1)
249 .WillOnce(Return(kErrorSuccess));
250 EXPECT_CALL(*clipboard.get(), HasString).Times(1).WillOnce(Return(true));
251
252 return clipboard;
253 });
254
255 std::string result =
256 SimulatePlatformMessage(&messenger, kClipboardHasStringsMessage);
257
258 EXPECT_EQ(result, "[{\"value\":true}]");
259 }
260
261 TEST_F(PlatformHandlerTest, ClipboardHasStringsReturnsFalse) {
262 UseHeadlessEngine();
263
264 TestBinaryMessenger messenger;
265 PlatformHandler platform_handler(&messenger, engine(), []() {
266 auto clipboard = std::make_unique<MockScopedClipboard>();
267
268 EXPECT_CALL(*clipboard.get(), Open)
269 .Times(1)
270 .WillOnce(Return(kErrorSuccess));
271 EXPECT_CALL(*clipboard.get(), HasString).Times(1).WillOnce(Return(false));
272
273 return clipboard;
274 });
275
276 std::string result =
277 SimulatePlatformMessage(&messenger, kClipboardHasStringsMessage);
278
279 EXPECT_EQ(result, "[{\"value\":false}]");
280 }
281
282 TEST_F(PlatformHandlerTest, ClipboardHasStringsRejectsUnknownContentType) {
283 UseHeadlessEngine();
284
285 TestBinaryMessenger messenger;
286 PlatformHandler platform_handler(&messenger, engine());
287
288 std::string result = SimulatePlatformMessage(
289 &messenger, kClipboardHasStringsFakeContentTypeMessage);
290
291 EXPECT_EQ(result, "[\"Clipboard error\",\"Unknown clipboard format\",null]");
292 }
293
294 // Regression test for https://github.com/flutter/flutter/issues/95817.
295 TEST_F(PlatformHandlerTest, ClipboardHasStringsIgnoresPermissionErrors) {
296 UseHeadlessEngine();
297
298 TestBinaryMessenger messenger;
299 PlatformHandler platform_handler(&messenger, engine(), []() {
300 auto clipboard = std::make_unique<MockScopedClipboard>();
301
302 EXPECT_CALL(*clipboard.get(), Open)
303 .Times(1)
304 .WillOnce(Return(kAccessDeniedErrorCode));
305
306 return clipboard;
307 });
308
309 std::string result =
310 SimulatePlatformMessage(&messenger, kClipboardHasStringsMessage);
311
312 EXPECT_EQ(result, "[{\"value\":false}]");
313 }
314
315 TEST_F(PlatformHandlerTest, ClipboardHasStringsReportsErrors) {
316 UseHeadlessEngine();
317
318 TestBinaryMessenger messenger;
319 PlatformHandler platform_handler(&messenger, engine(), []() {
320 auto clipboard = std::make_unique<MockScopedClipboard>();
321
322 EXPECT_CALL(*clipboard.get(), Open)
323 .Times(1)
324 .WillOnce(Return(kArbitraryErrorCode));
325
326 return clipboard;
327 });
328
329 std::string result =
330 SimulatePlatformMessage(&messenger, kClipboardHasStringsMessage);
331
332 EXPECT_EQ(result, "[\"Clipboard error\",\"Unable to open clipboard\",1]");
333 }
334
335 TEST_F(PlatformHandlerTest, ClipboardSetData) {
336 UseHeadlessEngine();
337
338 TestBinaryMessenger messenger;
339 PlatformHandler platform_handler(&messenger, engine(), []() {
340 auto clipboard = std::make_unique<MockScopedClipboard>();
341
342 EXPECT_CALL(*clipboard.get(), Open)
343 .Times(1)
344 .WillOnce(Return(kErrorSuccess));
345 EXPECT_CALL(*clipboard.get(), SetString)
346 .Times(1)
347 .WillOnce([](std::wstring string) {
348 EXPECT_EQ(string, L"hello");
349 return kErrorSuccess;
350 });
351
352 return clipboard;
353 });
354
355 std::string result =
356 SimulatePlatformMessage(&messenger, kClipboardSetDataMessage);
357
358 EXPECT_EQ(result, "[null]");
359 }
360
361 // Regression test for: https://github.com/flutter/flutter/issues/121976
362 TEST_F(PlatformHandlerTest, ClipboardSetDataTextMustBeString) {
363 UseHeadlessEngine();
364
365 TestBinaryMessenger messenger;
366 PlatformHandler platform_handler(&messenger, engine());
367
368 std::string result =
369 SimulatePlatformMessage(&messenger, kClipboardSetDataNullTextMessage);
370
371 EXPECT_EQ(result, "[\"Clipboard error\",\"Unknown clipboard format\",null]");
372 }
373
374 TEST_F(PlatformHandlerTest, ClipboardSetDataUnknownType) {
375 UseHeadlessEngine();
376
377 TestBinaryMessenger messenger;
378 PlatformHandler platform_handler(&messenger, engine());
379
380 std::string result =
381 SimulatePlatformMessage(&messenger, kClipboardSetDataUnknownTypeMessage);
382
383 EXPECT_EQ(result, "[\"Clipboard error\",\"Unknown clipboard format\",null]");
384 }
385
386 TEST_F(PlatformHandlerTest, ClipboardSetDataReportsOpenFailure) {
387 UseHeadlessEngine();
388
389 TestBinaryMessenger messenger;
390 PlatformHandler platform_handler(&messenger, engine(), []() {
391 auto clipboard = std::make_unique<MockScopedClipboard>();
392
393 EXPECT_CALL(*clipboard.get(), Open)
394 .Times(1)
395 .WillOnce(Return(kArbitraryErrorCode));
396
397 return clipboard;
398 });
399
400 std::string result =
401 SimulatePlatformMessage(&messenger, kClipboardSetDataMessage);
402
403 EXPECT_EQ(result, "[\"Clipboard error\",\"Unable to open clipboard\",1]");
404 }
405
406 TEST_F(PlatformHandlerTest, ClipboardSetDataReportsSetDataFailure) {
407 UseHeadlessEngine();
408
409 TestBinaryMessenger messenger;
410 PlatformHandler platform_handler(&messenger, engine(), []() {
411 auto clipboard = std::make_unique<MockScopedClipboard>();
412
413 EXPECT_CALL(*clipboard.get(), Open)
414 .Times(1)
415 .WillOnce(Return(kErrorSuccess));
416 EXPECT_CALL(*clipboard.get(), SetString)
417 .Times(1)
418 .WillOnce(Return(kArbitraryErrorCode));
419
420 return clipboard;
421 });
422
423 std::string result =
424 SimulatePlatformMessage(&messenger, kClipboardSetDataMessage);
425
426 EXPECT_EQ(result, "[\"Clipboard error\",\"Unable to set clipboard data\",1]");
427 }
428
429 TEST_F(PlatformHandlerTest, PlaySystemSound) {
430 UseHeadlessEngine();
431
432 TestBinaryMessenger messenger;
433 MockPlatformHandler platform_handler(&messenger, engine());
434
435 EXPECT_CALL(platform_handler, SystemSoundPlay("SystemSoundType.alert", _))
436 .WillOnce([](const std::string& sound,
437 std::unique_ptr<MethodResult<rapidjson::Document>> result) {
438 result->Success();
439 });
440
441 std::string result =
442 SimulatePlatformMessage(&messenger, kSystemSoundTypeAlertMessage);
443
444 EXPECT_EQ(result, "[null]");
445 }
446
447 TEST_F(PlatformHandlerTest, SystemExitApplicationRequired) {
448 UseHeadlessEngine();
449 UINT exit_code = 0;
450
451 TestBinaryMessenger messenger([](const std::string& channel,
452 const uint8_t* message, size_t size,
453 BinaryReply reply) {});
454 MockPlatformHandler platform_handler(&messenger, engine());
455
456 ON_CALL(platform_handler, QuitApplication)
457 .WillByDefault([&exit_code](std::optional<HWND> hwnd,
458 std::optional<WPARAM> wparam,
459 std::optional<LPARAM> lparam,
460 UINT ec) { exit_code = ec; });
461 EXPECT_CALL(platform_handler, QuitApplication).Times(1);
462
463 std::string result = SimulatePlatformMessage(
464 &messenger, kSystemExitApplicationRequiredMessage);
465 EXPECT_EQ(result, "[{\"response\":\"exit\"}]");
466 EXPECT_EQ(exit_code, 1);
467 }
468
469 TEST_F(PlatformHandlerTest, SystemExitApplicationCancelableCancel) {
470 UseHeadlessEngine();
471 bool called_cancel = false;
472
473 TestBinaryMessenger messenger(
474 [&called_cancel](const std::string& channel, const uint8_t* message,
475 size_t size, BinaryReply reply) {
476 reply(reinterpret_cast<const uint8_t*>(kExitResponseCancelMessage),
477 sizeof(kExitResponseCancelMessage));
478 called_cancel = true;
479 });
480 MockPlatformHandler platform_handler(&messenger, engine());
481
482 EXPECT_CALL(platform_handler, QuitApplication).Times(0);
483
484 std::string result = SimulatePlatformMessage(
485 &messenger, kSystemExitApplicationCancelableMessage);
486 EXPECT_EQ(result, "[{\"response\":\"cancel\"}]");
487 EXPECT_TRUE(called_cancel);
488 }
489
490 TEST_F(PlatformHandlerTest, SystemExitApplicationCancelableExit) {
491 UseHeadlessEngine();
492 bool called_cancel = false;
493 UINT exit_code = 0;
494
495 TestBinaryMessenger messenger(
496 [&called_cancel](const std::string& channel, const uint8_t* message,
497 size_t size, BinaryReply reply) {
498 reply(reinterpret_cast<const uint8_t*>(kExitResponseExitMessage),
499 sizeof(kExitResponseExitMessage));
500 called_cancel = true;
501 });
502 MockPlatformHandler platform_handler(&messenger, engine());
503
504 ON_CALL(platform_handler, QuitApplication)
505 .WillByDefault([&exit_code](std::optional<HWND> hwnd,
506 std::optional<WPARAM> wparam,
507 std::optional<LPARAM> lparam,
508 UINT ec) { exit_code = ec; });
509 EXPECT_CALL(platform_handler, QuitApplication).Times(1);
510
511 std::string result = SimulatePlatformMessage(
512 &messenger, kSystemExitApplicationCancelableMessage);
513 EXPECT_EQ(result, "[{\"response\":\"cancel\"}]");
514 EXPECT_TRUE(called_cancel);
515 EXPECT_EQ(exit_code, 2);
516 }
517
518 } // namespace testing
519 } // namespace flutter
flutter::FlutterWindowsEngine
Definition: flutter_windows_engine.h:92
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::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:12
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(AccessibilityPluginTest, DirectAnnounceCall)
Definition: accessibility_plugin_unittests.cc:98
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:18
kErrorSuccess
static constexpr int kErrorSuccess
Definition: platform_handler.cc:45
kAccessDeniedErrorCode
static constexpr int kAccessDeniedErrorCode
Definition: platform_handler.cc:44
platform_handler.h