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 
6 
7 #include <memory>
8 
9 #include "flutter/fml/macros.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 
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
static constexpr char kChannelName[]
Win32Message message
TEST_F(CompositorOpenGLTest, CreateBackingStore)
std::function< void(const uint8_t *reply, size_t reply_size)> BinaryReply
static constexpr int kErrorSuccess
static constexpr int kAccessDeniedErrorCode