Flutter Windows Embedder
platform_handler.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 <windows.h>
8 
9 #include <cstring>
10 #include <optional>
11 
12 #include "flutter/fml/logging.h"
13 #include "flutter/fml/macros.h"
14 #include "flutter/fml/platform/win/wstring_conversion.h"
18 
19 static constexpr char kChannelName[] = "flutter/platform";
20 
21 static constexpr char kGetClipboardDataMethod[] = "Clipboard.getData";
22 static constexpr char kHasStringsClipboardMethod[] = "Clipboard.hasStrings";
23 static constexpr char kSetClipboardDataMethod[] = "Clipboard.setData";
24 static constexpr char kExitApplicationMethod[] = "System.exitApplication";
25 static constexpr char kRequestAppExitMethod[] = "System.requestAppExit";
26 static constexpr char kInitializationCompleteMethod[] =
27  "System.initializationComplete";
28 static constexpr char kPlaySoundMethod[] = "SystemSound.play";
29 
30 static constexpr char kExitCodeKey[] = "exitCode";
31 
32 static constexpr char kExitTypeKey[] = "type";
33 
34 static constexpr char kExitResponseKey[] = "response";
35 static constexpr char kExitResponseCancel[] = "cancel";
36 static constexpr char kExitResponseExit[] = "exit";
37 
38 static constexpr char kTextPlainFormat[] = "text/plain";
39 static constexpr char kTextKey[] = "text";
40 static constexpr char kUnknownClipboardFormatMessage[] =
41  "Unknown clipboard format";
42 
43 static constexpr char kValueKey[] = "value";
44 static constexpr int kAccessDeniedErrorCode = 5;
45 static constexpr int kErrorSuccess = 0;
46 
47 static constexpr char kExitRequestError[] = "ExitApplication error";
48 static constexpr char kInvalidExitRequestMessage[] =
49  "Invalid application exit request";
50 
51 namespace flutter {
52 
53 namespace {
54 
55 // A scoped wrapper for GlobalAlloc/GlobalFree.
56 class ScopedGlobalMemory {
57  public:
58  // Allocates |bytes| bytes of global memory with the given flags.
59  ScopedGlobalMemory(unsigned int flags, size_t bytes) {
60  memory_ = ::GlobalAlloc(flags, bytes);
61  if (!memory_) {
62  FML_LOG(ERROR) << "Unable to allocate global memory: "
63  << ::GetLastError();
64  }
65  }
66 
67  ~ScopedGlobalMemory() {
68  if (memory_) {
69  if (::GlobalFree(memory_) != nullptr) {
70  FML_LOG(ERROR) << "Failed to free global allocation: "
71  << ::GetLastError();
72  }
73  }
74  }
75 
76  // Returns the memory pointer, which will be nullptr if allocation failed.
77  void* get() { return memory_; }
78 
79  void* release() {
80  void* memory = memory_;
81  memory_ = nullptr;
82  return memory;
83  }
84 
85  private:
86  HGLOBAL memory_;
87 
88  FML_DISALLOW_COPY_AND_ASSIGN(ScopedGlobalMemory);
89 };
90 
91 // A scoped wrapper for GlobalLock/GlobalUnlock.
92 class ScopedGlobalLock {
93  public:
94  // Attempts to acquire a global lock on |memory| for the life of this object.
95  ScopedGlobalLock(HGLOBAL memory) {
96  source_ = memory;
97  if (memory) {
98  locked_memory_ = ::GlobalLock(memory);
99  if (!locked_memory_) {
100  FML_LOG(ERROR) << "Unable to acquire global lock: " << ::GetLastError();
101  }
102  }
103  }
104 
105  ~ScopedGlobalLock() {
106  if (locked_memory_) {
107  if (!::GlobalUnlock(source_)) {
108  DWORD error = ::GetLastError();
109  if (error != NO_ERROR) {
110  FML_LOG(ERROR) << "Unable to release global lock: "
111  << ::GetLastError();
112  }
113  }
114  }
115  }
116 
117  // Returns the locked memory pointer, which will be nullptr if acquiring the
118  // lock failed.
119  void* get() { return locked_memory_; }
120 
121  private:
122  HGLOBAL source_;
123  void* locked_memory_;
124 
125  FML_DISALLOW_COPY_AND_ASSIGN(ScopedGlobalLock);
126 };
127 
128 // A Clipboard wrapper that automatically closes the clipboard when it goes out
129 // of scope.
130 class ScopedClipboard : public ScopedClipboardInterface {
131  public:
132  ScopedClipboard();
133  virtual ~ScopedClipboard();
134 
135  int Open(HWND window) override;
136 
137  bool HasString() override;
138 
139  std::variant<std::wstring, int> GetString() override;
140 
141  int SetString(const std::wstring string) override;
142 
143  private:
144  bool opened_ = false;
145 
146  FML_DISALLOW_COPY_AND_ASSIGN(ScopedClipboard);
147 };
148 
149 ScopedClipboard::ScopedClipboard() {}
150 
151 ScopedClipboard::~ScopedClipboard() {
152  if (opened_) {
153  ::CloseClipboard();
154  }
155 }
156 
157 int ScopedClipboard::Open(HWND window) {
158  opened_ = ::OpenClipboard(window);
159 
160  if (!opened_) {
161  return ::GetLastError();
162  }
163 
164  return kErrorSuccess;
165 }
166 
167 bool ScopedClipboard::HasString() {
168  // Allow either plain text format, since getting data will auto-interpolate.
169  return ::IsClipboardFormatAvailable(CF_UNICODETEXT) ||
170  ::IsClipboardFormatAvailable(CF_TEXT);
171 }
172 
173 std::variant<std::wstring, int> ScopedClipboard::GetString() {
174  FML_DCHECK(opened_) << "Called GetString when clipboard is closed";
175 
176  HANDLE data = ::GetClipboardData(CF_UNICODETEXT);
177  if (data == nullptr) {
178  return ::GetLastError();
179  }
180  ScopedGlobalLock locked_data(data);
181 
182  if (!locked_data.get()) {
183  return ::GetLastError();
184  }
185  return static_cast<wchar_t*>(locked_data.get());
186 }
187 
188 int ScopedClipboard::SetString(const std::wstring string) {
189  FML_DCHECK(opened_) << "Called GetString when clipboard is closed";
190  if (!::EmptyClipboard()) {
191  return ::GetLastError();
192  }
193  size_t null_terminated_byte_count =
194  sizeof(decltype(string)::traits_type::char_type) * (string.size() + 1);
195  ScopedGlobalMemory destination_memory(GMEM_MOVEABLE,
196  null_terminated_byte_count);
197  ScopedGlobalLock locked_memory(destination_memory.get());
198  if (!locked_memory.get()) {
199  return ::GetLastError();
200  }
201  memcpy(locked_memory.get(), string.c_str(), null_terminated_byte_count);
202  if (!::SetClipboardData(CF_UNICODETEXT, locked_memory.get())) {
203  return ::GetLastError();
204  }
205  // The clipboard now owns the global memory.
206  destination_memory.release();
207  return kErrorSuccess;
208 }
209 
210 } // namespace
211 
212 static AppExitType StringToAppExitType(const std::string& string) {
213  if (string.compare(PlatformHandler::kExitTypeRequired) == 0) {
214  return AppExitType::required;
215  } else if (string.compare(PlatformHandler::kExitTypeCancelable) == 0) {
217  }
218  FML_LOG(ERROR) << string << " is not recognized as a valid exit type.";
219  return AppExitType::required;
220 }
221 
223  BinaryMessenger* messenger,
224  FlutterWindowsEngine* engine,
225  std::optional<std::function<std::unique_ptr<ScopedClipboardInterface>()>>
226  scoped_clipboard_provider)
227  : channel_(std::make_unique<MethodChannel<rapidjson::Document>>(
228  messenger,
229  kChannelName,
230  &JsonMethodCodec::GetInstance())),
231  engine_(engine) {
232  channel_->SetMethodCallHandler(
233  [this](const MethodCall<rapidjson::Document>& call,
234  std::unique_ptr<MethodResult<rapidjson::Document>> result) {
235  HandleMethodCall(call, std::move(result));
236  });
237  if (scoped_clipboard_provider.has_value()) {
238  scoped_clipboard_provider_ = scoped_clipboard_provider.value();
239  } else {
240  scoped_clipboard_provider_ = []() {
241  return std::make_unique<ScopedClipboard>();
242  };
243  }
244 }
245 
247 
249  std::unique_ptr<MethodResult<rapidjson::Document>> result,
250  std::string_view key) {
251  const FlutterWindowsView* view = engine_->view();
252  if (view == nullptr) {
253  result->Error(kClipboardError,
254  "Clipboard is not available in Windows headless mode");
255  return;
256  }
257 
258  std::unique_ptr<ScopedClipboardInterface> clipboard =
259  scoped_clipboard_provider_();
260 
261  int open_result = clipboard->Open(view->GetWindowHandle());
262  if (open_result != kErrorSuccess) {
263  rapidjson::Document error_code;
264  error_code.SetInt(open_result);
265  result->Error(kClipboardError, "Unable to open clipboard", error_code);
266  return;
267  }
268  if (!clipboard->HasString()) {
269  result->Success(rapidjson::Document());
270  return;
271  }
272  std::variant<std::wstring, int> get_string_result = clipboard->GetString();
273  if (std::holds_alternative<int>(get_string_result)) {
274  rapidjson::Document error_code;
275  error_code.SetInt(std::get<int>(get_string_result));
276  result->Error(kClipboardError, "Unable to get clipboard data", error_code);
277  return;
278  }
279 
280  rapidjson::Document document;
281  document.SetObject();
282  rapidjson::Document::AllocatorType& allocator = document.GetAllocator();
283  document.AddMember(
284  rapidjson::Value(key.data(), allocator),
285  rapidjson::Value(
286  fml::WideStringToUtf8(std::get<std::wstring>(get_string_result)),
287  allocator),
288  allocator);
289  result->Success(document);
290 }
291 
293  std::unique_ptr<MethodResult<rapidjson::Document>> result) {
294  const FlutterWindowsView* view = engine_->view();
295  if (view == nullptr) {
296  result->Error(kClipboardError,
297  "Clipboard is not available in Windows headless mode");
298  return;
299  }
300 
301  std::unique_ptr<ScopedClipboardInterface> clipboard =
302  scoped_clipboard_provider_();
303 
304  bool hasStrings;
305  int open_result = clipboard->Open(view->GetWindowHandle());
306  if (open_result != kErrorSuccess) {
307  // Swallow errors of type ERROR_ACCESS_DENIED. These happen when the app is
308  // not in the foreground and GetHasStrings is irrelevant.
309  // See https://github.com/flutter/flutter/issues/95817.
310  if (open_result != kAccessDeniedErrorCode) {
311  rapidjson::Document error_code;
312  error_code.SetInt(open_result);
313  result->Error(kClipboardError, "Unable to open clipboard", error_code);
314  return;
315  }
316  hasStrings = false;
317  } else {
318  hasStrings = clipboard->HasString();
319  }
320 
321  rapidjson::Document document;
322  document.SetObject();
323  rapidjson::Document::AllocatorType& allocator = document.GetAllocator();
324  document.AddMember(rapidjson::Value(kValueKey, allocator),
325  rapidjson::Value(hasStrings), allocator);
326  result->Success(document);
327 }
328 
330  const std::string& text,
331  std::unique_ptr<MethodResult<rapidjson::Document>> result) {
332  const FlutterWindowsView* view = engine_->view();
333  if (view == nullptr) {
334  result->Error(kClipboardError,
335  "Clipboard is not available in Windows headless mode");
336  return;
337  }
338 
339  std::unique_ptr<ScopedClipboardInterface> clipboard =
340  scoped_clipboard_provider_();
341 
342  int open_result = clipboard->Open(view->GetWindowHandle());
343  if (open_result != kErrorSuccess) {
344  rapidjson::Document error_code;
345  error_code.SetInt(open_result);
346  result->Error(kClipboardError, "Unable to open clipboard", error_code);
347  return;
348  }
349  int set_result = clipboard->SetString(fml::Utf8ToWideString(text));
350  if (set_result != kErrorSuccess) {
351  rapidjson::Document error_code;
352  error_code.SetInt(set_result);
353  result->Error(kClipboardError, "Unable to set clipboard data", error_code);
354  return;
355  }
356  result->Success();
357 }
358 
360  const std::string& sound_type,
361  std::unique_ptr<MethodResult<rapidjson::Document>> result) {
362  if (sound_type.compare(kSoundTypeAlert) == 0) {
363  MessageBeep(MB_OK);
364  result->Success();
365  } else {
366  result->NotImplemented();
367  }
368 }
369 
371  AppExitType exit_type,
372  UINT exit_code,
373  std::unique_ptr<MethodResult<rapidjson::Document>> result) {
374  rapidjson::Document result_doc;
375  result_doc.SetObject();
376  if (exit_type == AppExitType::required) {
377  QuitApplication(std::nullopt, std::nullopt, std::nullopt, exit_code);
378  result_doc.GetObjectW().AddMember(kExitResponseKey, kExitResponseExit,
379  result_doc.GetAllocator());
380  result->Success(result_doc);
381  } else {
382  RequestAppExit(std::nullopt, std::nullopt, std::nullopt, exit_type,
383  exit_code);
384  result_doc.GetObjectW().AddMember(kExitResponseKey, kExitResponseCancel,
385  result_doc.GetAllocator());
386  result->Success(result_doc);
387  }
388 }
389 
390 // Indicates whether an exit request may be canceled by the framework.
391 // These values must be kept in sync with ExitType in platform_handler.h
392 static constexpr const char* kExitTypeNames[] = {
394 
395 void PlatformHandler::RequestAppExit(std::optional<HWND> hwnd,
396  std::optional<WPARAM> wparam,
397  std::optional<LPARAM> lparam,
398  AppExitType exit_type,
399  UINT exit_code) {
400  auto callback = std::make_unique<MethodResultFunctions<rapidjson::Document>>(
401  [this, exit_code, hwnd, wparam,
402  lparam](const rapidjson::Document* response) {
403  RequestAppExitSuccess(hwnd, wparam, lparam, response, exit_code);
404  },
405  nullptr, nullptr);
406  auto args = std::make_unique<rapidjson::Document>();
407  args->SetObject();
408  args->GetObjectW().AddMember(
409  kExitTypeKey, std::string(kExitTypeNames[static_cast<int>(exit_type)]),
410  args->GetAllocator());
411  channel_->InvokeMethod(kRequestAppExitMethod, std::move(args),
412  std::move(callback));
413 }
414 
415 void PlatformHandler::RequestAppExitSuccess(std::optional<HWND> hwnd,
416  std::optional<WPARAM> wparam,
417  std::optional<LPARAM> lparam,
418  const rapidjson::Document* result,
419  UINT exit_code) {
420  rapidjson::Value::ConstMemberIterator itr =
421  result->FindMember(kExitResponseKey);
422  if (itr == result->MemberEnd() || !itr->value.IsString()) {
423  FML_LOG(ERROR) << "Application request response did not contain a valid "
424  "response value";
425  return;
426  }
427  const std::string& exit_type = itr->value.GetString();
428 
429  if (exit_type.compare(kExitResponseExit) == 0) {
430  QuitApplication(hwnd, wparam, lparam, exit_code);
431  }
432 }
433 
434 void PlatformHandler::QuitApplication(std::optional<HWND> hwnd,
435  std::optional<WPARAM> wparam,
436  std::optional<LPARAM> lparam,
437  UINT exit_code) {
438  engine_->OnQuit(hwnd, wparam, lparam, exit_code);
439 }
440 
441 void PlatformHandler::HandleMethodCall(
442  const MethodCall<rapidjson::Document>& method_call,
443  std::unique_ptr<MethodResult<rapidjson::Document>> result) {
444  const std::string& method = method_call.method_name();
445  if (method.compare(kExitApplicationMethod) == 0) {
446  const rapidjson::Value& arguments = method_call.arguments()[0];
447 
448  rapidjson::Value::ConstMemberIterator itr =
449  arguments.FindMember(kExitTypeKey);
450  if (itr == arguments.MemberEnd() || !itr->value.IsString()) {
452  return;
453  }
454  const std::string& exit_type = itr->value.GetString();
455 
456  itr = arguments.FindMember(kExitCodeKey);
457  if (itr == arguments.MemberEnd() || !itr->value.IsInt()) {
459  return;
460  }
461  UINT exit_code = arguments[kExitCodeKey].GetInt();
462 
463  SystemExitApplication(StringToAppExitType(exit_type), exit_code,
464  std::move(result));
465  } else if (method.compare(kGetClipboardDataMethod) == 0) {
466  // Only one string argument is expected.
467  const rapidjson::Value& format = method_call.arguments()[0];
468 
469  if (strcmp(format.GetString(), kTextPlainFormat) != 0) {
471  return;
472  }
473  GetPlainText(std::move(result), kTextKey);
474  } else if (method.compare(kHasStringsClipboardMethod) == 0) {
475  // Only one string argument is expected.
476  const rapidjson::Value& format = method_call.arguments()[0];
477 
478  if (strcmp(format.GetString(), kTextPlainFormat) != 0) {
480  return;
481  }
482  GetHasStrings(std::move(result));
483  } else if (method.compare(kSetClipboardDataMethod) == 0) {
484  const rapidjson::Value& document = *method_call.arguments();
485  rapidjson::Value::ConstMemberIterator itr = document.FindMember(kTextKey);
486  if (itr == document.MemberEnd()) {
488  return;
489  }
490  if (!itr->value.IsString()) {
492  return;
493  }
494  SetPlainText(itr->value.GetString(), std::move(result));
495  } else if (method.compare(kPlaySoundMethod) == 0) {
496  // Only one string argument is expected.
497  const rapidjson::Value& sound_type = method_call.arguments()[0];
498 
499  SystemSoundPlay(sound_type.GetString(), std::move(result));
500  } else if (method.compare(kInitializationCompleteMethod) == 0) {
501  // Deprecated but should not cause an error.
502  result->Success();
503  } else {
504  result->NotImplemented();
505  }
506 }
507 
508 } // namespace flutter
flutter::PlatformHandler::SystemExitApplication
virtual void SystemExitApplication(AppExitType exit_type, UINT exit_code, std::unique_ptr< MethodResult< rapidjson::Document >> result)
Definition: platform_handler.cc:370
flutter::AppExitType::cancelable
@ cancelable
flutter::AppExitType
AppExitType
Definition: platform_handler.h:27
flutter::FlutterWindowsView
Definition: flutter_windows_view.h:35
flutter::PlatformHandler::SystemSoundPlay
virtual void SystemSoundPlay(const std::string &sound_type, std::unique_ptr< MethodResult< rapidjson::Document >> result)
Definition: platform_handler.cc:359
flutter::JsonMethodCodec
Definition: json_method_codec.h:16
flutter::MethodChannel
Definition: method_channel.h:34
kValueKey
static constexpr char kValueKey[]
Definition: platform_handler.cc:43
kGetClipboardDataMethod
static constexpr char kGetClipboardDataMethod[]
Definition: platform_handler.cc:21
kExitRequestError
static constexpr char kExitRequestError[]
Definition: platform_handler.cc:47
method_result_functions.h
flutter::PlatformHandler::~PlatformHandler
virtual ~PlatformHandler()
kInvalidExitRequestMessage
static constexpr char kInvalidExitRequestMessage[]
Definition: platform_handler.cc:48
flutter::FlutterWindowsEngine
Definition: flutter_windows_engine.h:78
json_method_codec.h
flutter::PlatformHandler::QuitApplication
virtual void QuitApplication(std::optional< HWND > hwnd, std::optional< WPARAM > wparam, std::optional< LPARAM > lparam, UINT exit_code)
Definition: platform_handler.cc:434
flutter::FlutterWindowsEngine::OnQuit
void OnQuit(std::optional< HWND > hwnd, std::optional< WPARAM > wparam, std::optional< LPARAM > lparam, UINT exit_code)
Definition: flutter_windows_engine.cc:798
flutter::PlatformHandler::SetPlainText
virtual void SetPlainText(const std::string &text, std::unique_ptr< MethodResult< rapidjson::Document >> result)
Definition: platform_handler.cc:329
kExitApplicationMethod
static constexpr char kExitApplicationMethod[]
Definition: platform_handler.cc:24
flutter::FlutterWindowsView::GetWindowHandle
virtual HWND GetWindowHandle() const
Definition: flutter_windows_view.cc:645
flutter::PlatformHandler::kExitTypeRequired
static constexpr char kExitTypeRequired[]
Definition: platform_handler.h:45
kExitCodeKey
static constexpr char kExitCodeKey[]
Definition: platform_handler.cc:30
kRequestAppExitMethod
static constexpr char kRequestAppExitMethod[]
Definition: platform_handler.cc:25
flutter::PlatformHandler::RequestAppExit
virtual void RequestAppExit(std::optional< HWND > hwnd, std::optional< WPARAM > wparam, std::optional< LPARAM > lparam, AppExitType exit_type, UINT exit_code)
Definition: platform_handler.cc:395
flutter::BinaryMessenger
Definition: binary_messenger.h:28
flutter_windows_view.h
text
std::u16string text
Definition: keyboard_unittests.cc:332
kInitializationCompleteMethod
static constexpr char kInitializationCompleteMethod[]
Definition: platform_handler.cc:26
flutter::MethodCall
Definition: method_call.h:18
flutter::PlatformHandler::kExitTypeCancelable
static constexpr char kExitTypeCancelable[]
Definition: platform_handler.h:44
flutter::PlatformHandler::kClipboardError
static constexpr char kClipboardError[]
Definition: platform_handler.h:104
kExitResponseKey
static constexpr char kExitResponseKey[]
Definition: platform_handler.cc:34
kChannelName
static constexpr char kChannelName[]
Definition: platform_handler.cc:19
kErrorSuccess
static constexpr int kErrorSuccess
Definition: platform_handler.cc:45
flutter
Definition: accessibility_bridge_windows.cc:11
kTextPlainFormat
static constexpr char kTextPlainFormat[]
Definition: platform_handler.cc:38
kHasStringsClipboardMethod
static constexpr char kHasStringsClipboardMethod[]
Definition: platform_handler.cc:22
kPlaySoundMethod
static constexpr char kPlaySoundMethod[]
Definition: platform_handler.cc:28
platform_handler.h
kTextKey
static constexpr char kTextKey[]
Definition: platform_handler.cc:39
flutter::MethodCall::method_name
const std::string & method_name() const
Definition: method_call.h:31
flutter::PlatformHandler::GetHasStrings
virtual void GetHasStrings(std::unique_ptr< MethodResult< rapidjson::Document >> result)
Definition: platform_handler.cc:292
flutter::MethodResult
Definition: method_result.h:17
flutter::StringToAppExitType
static AppExitType StringToAppExitType(const std::string &string)
Definition: platform_handler.cc:212
flutter::FlutterWindowsEngine::view
FlutterWindowsView * view()
Definition: flutter_windows_engine.h:117
kExitResponseExit
static constexpr char kExitResponseExit[]
Definition: platform_handler.cc:36
kSetClipboardDataMethod
static constexpr char kSetClipboardDataMethod[]
Definition: platform_handler.cc:23
flutter::kExitTypeNames
static constexpr const char * kExitTypeNames[]
Definition: platform_handler.cc:392
kAccessDeniedErrorCode
static constexpr int kAccessDeniedErrorCode
Definition: platform_handler.cc:44
flutter::PlatformHandler::RequestAppExitSuccess
virtual void RequestAppExitSuccess(std::optional< HWND > hwnd, std::optional< WPARAM > wparam, std::optional< LPARAM > lparam, const rapidjson::Document *result, UINT exit_code)
Definition: platform_handler.cc:415
flutter::PlatformHandler::kSoundTypeAlert
static constexpr char kSoundTypeAlert[]
Definition: platform_handler.h:106
key
int key
Definition: keyboard_key_handler_unittests.cc:114
flutter::AppExitType::required
@ required
flutter::PlatformHandler::PlatformHandler
PlatformHandler(BinaryMessenger *messenger, FlutterWindowsEngine *engine, std::optional< std::function< std::unique_ptr< ScopedClipboardInterface >()>> scoped_clipboard_provider=std::nullopt)
Definition: platform_handler.cc:222
flutter::PlatformHandler::GetPlainText
virtual void GetPlainText(std::unique_ptr< MethodResult< rapidjson::Document >> result, std::string_view key)
Definition: platform_handler.cc:248
kUnknownClipboardFormatMessage
static constexpr char kUnknownClipboardFormatMessage[]
Definition: platform_handler.cc:40
flutter::MethodCall::arguments
const T * arguments() const
Definition: method_call.h:34
kExitResponseCancel
static constexpr char kExitResponseCancel[]
Definition: platform_handler.cc:35
callback
FlutterDesktopBinaryReply callback
Definition: flutter_windows_view_unittests.cc:48
kExitTypeKey
static constexpr char kExitTypeKey[]
Definition: platform_handler.cc:32