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 
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::kImplicitViewId
constexpr FlutterViewId kImplicitViewId
Definition: flutter_windows_engine.h:55
flutter::testing::PlatformHandlerTest::UseEngineWithView
void UseEngineWithView()
Definition: platform_handler_unittests.cc:152
flutter::testing::PlatformHandlerTest::~PlatformHandlerTest
virtual ~PlatformHandlerTest()=default
flutter::PlatformHandler
Definition: platform_handler.h:33
flutter::FlutterWindowsEngine
Definition: flutter_windows_engine.h:90
json_method_codec.h
flutter::testing::PlatformHandlerTest::engine
FlutterWindowsEngine * engine()
Definition: platform_handler_unittests.cc:144
flutter_windows_view.h
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
flutter::testing::PlatformHandlerTest::UseHeadlessEngine
void UseHeadlessEngine()
Definition: platform_handler_unittests.cc:146
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:138
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::TEST_F
TEST_F(CompositorOpenGLTest, CreateBackingStore)
Definition: compositor_opengl_unittests.cc:125
key
int key
Definition: keyboard_key_handler_unittests.cc:114