Add Windows exit requests and responses (#40400)

* Communicate exit request

* Unit test exit message

* Comments

* Formatting

* Move exit code

* Formatting
diff --git a/shell/platform/windows/platform_handler.cc b/shell/platform/windows/platform_handler.cc
index 4c54b3d..a33710b 100644
--- a/shell/platform/windows/platform_handler.cc
+++ b/shell/platform/windows/platform_handler.cc
@@ -12,6 +12,7 @@
 #include "flutter/fml/logging.h"
 #include "flutter/fml/macros.h"
 #include "flutter/fml/platform/win/wstring_conversion.h"
+#include "flutter/shell/platform/common/client_wrapper/include/flutter/method_result_functions.h"
 #include "flutter/shell/platform/common/json_method_codec.h"
 #include "flutter/shell/platform/windows/flutter_windows_view.h"
 
@@ -20,8 +21,20 @@
 static constexpr char kGetClipboardDataMethod[] = "Clipboard.getData";
 static constexpr char kHasStringsClipboardMethod[] = "Clipboard.hasStrings";
 static constexpr char kSetClipboardDataMethod[] = "Clipboard.setData";
+static constexpr char kExitApplicationMethod[] = "System.exitApplication";
+static constexpr char kRequestAppExitMethod[] = "System.requestAppExit";
 static constexpr char kPlaySoundMethod[] = "SystemSound.play";
 
+static constexpr char kExitCodeKey[] = "exitCode";
+
+static constexpr char kExitTypeKey[] = "type";
+static constexpr char kExitTypeCancelable[] = "cancelable";
+static constexpr char kExitTypeRequired[] = "required";
+
+static constexpr char kExitResponseKey[] = "response";
+static constexpr char kExitResponseCancel[] = "cancel";
+static constexpr char kExitResponseExit[] = "exit";
+
 static constexpr char kTextPlainFormat[] = "text/plain";
 static constexpr char kTextKey[] = "text";
 static constexpr char kUnknownClipboardFormatMessage[] =
@@ -340,11 +353,61 @@
   }
 }
 
+void PlatformHandler::SystemExitApplication(
+    const std::string& exit_type,
+    int64_t exit_code,
+    std::unique_ptr<MethodResult<rapidjson::Document>> result) {
+  rapidjson::Document result_doc;
+  result_doc.SetObject();
+  if (exit_type.compare(kExitTypeRequired) == 0) {
+    QuitApplication(exit_code);
+    result_doc.GetObjectW().AddMember(kExitResponseKey, kExitResponseExit,
+                                      result_doc.GetAllocator());
+    result->Success(result_doc);
+  } else {
+    RequestAppExit(exit_type, exit_code);
+    result_doc.GetObjectW().AddMember(kExitResponseKey, kExitResponseCancel,
+                                      result_doc.GetAllocator());
+    result->Success(result_doc);
+  }
+}
+
+void PlatformHandler::RequestAppExit(const std::string& exit_type,
+                                     int64_t exit_code) {
+  auto callback = std::make_unique<MethodResultFunctions<rapidjson::Document>>(
+      [this, exit_code](const rapidjson::Document* response) {
+        RequestAppExitSuccess(response, exit_code);
+      },
+      nullptr, nullptr);
+  auto args = std::make_unique<rapidjson::Document>();
+  args->SetObject();
+  args->GetObjectW().AddMember(kExitTypeKey, exit_type, args->GetAllocator());
+  channel_->InvokeMethod(kRequestAppExitMethod, std::move(args),
+                         std::move(callback));
+}
+
+void PlatformHandler::RequestAppExitSuccess(const rapidjson::Document* result,
+                                            int64_t exit_code) {
+  const std::string& exit_type = result[0][kExitResponseKey].GetString();
+  if (exit_type.compare(kExitResponseExit) == 0) {
+    QuitApplication(exit_code);
+  }
+}
+
+void PlatformHandler::QuitApplication(int64_t exit_code) {
+  PostQuitMessage(exit_code);
+}
+
 void PlatformHandler::HandleMethodCall(
     const MethodCall<rapidjson::Document>& method_call,
     std::unique_ptr<MethodResult<rapidjson::Document>> result) {
   const std::string& method = method_call.method_name();
-  if (method.compare(kGetClipboardDataMethod) == 0) {
+  if (method.compare(kExitApplicationMethod) == 0) {
+    const rapidjson::Value& arguments = method_call.arguments()[0];
+    const std::string& exit_type = arguments[kExitTypeKey].GetString();
+    int64_t exit_code = arguments[kExitCodeKey].GetInt64();
+    SystemExitApplication(exit_type, exit_code, std::move(result));
+  } else if (method.compare(kGetClipboardDataMethod) == 0) {
     // Only one string argument is expected.
     const rapidjson::Value& format = method_call.arguments()[0];
 
diff --git a/shell/platform/windows/platform_handler.h b/shell/platform/windows/platform_handler.h
index 35f1cef..096378d 100644
--- a/shell/platform/windows/platform_handler.h
+++ b/shell/platform/windows/platform_handler.h
@@ -55,6 +55,24 @@
       const std::string& sound_type,
       std::unique_ptr<MethodResult<rapidjson::Document>> result);
 
+  // Handle a request from the framework to exit the application.
+  virtual void SystemExitApplication(
+      const std::string& exit_type,
+      int64_t exit_code,
+      std::unique_ptr<MethodResult<rapidjson::Document>> result);
+
+  // Actually quit the application with the provided exit code.
+  virtual void QuitApplication(int64_t exit_code);
+
+  // Send a request to the framework to test if a cancelable exit request
+  // should be canceled or honored.
+  virtual void RequestAppExit(const std::string& exit_type, int64_t exit_code);
+
+  // Callback from when the cancelable exit request response request is
+  // answered by the framework.
+  virtual void RequestAppExitSuccess(const rapidjson::Document* result,
+                                     int64_t exit_code);
+
   // A error type to use for error responses.
   static constexpr char kClipboardError[] = "Clipboard error";
 
diff --git a/shell/platform/windows/platform_handler_unittests.cc b/shell/platform/windows/platform_handler_unittests.cc
index 71f383a..85d4f5a 100644
--- a/shell/platform/windows/platform_handler_unittests.cc
+++ b/shell/platform/windows/platform_handler_unittests.cc
@@ -43,6 +43,15 @@
     "{\"method\":\"Clipboard.setData\",\"args\":{\"madeuptype\":\"hello\"}}";
 static constexpr char kSystemSoundTypeAlertMessage[] =
     "{\"method\":\"SystemSound.play\",\"args\":\"SystemSoundType.alert\"}";
+static constexpr char kSystemExitApplicationRequiredMessage[] =
+    "{\"method\":\"System.exitApplication\",\"args\":{\"type\":\"required\","
+    "\"exitCode\":1}}";
+static constexpr char kSystemExitApplicationCancelableMessage[] =
+    "{\"method\":\"System.exitApplication\",\"args\":{\"type\":\"cancelable\","
+    "\"exitCode\":2}}";
+static constexpr char kExitResponseCancelMessage[] =
+    "[{\"response\":\"cancel\"}]";
+static constexpr char kExitResponseExitMessage[] = "[{\"response\":\"exit\"}]";
 
 static constexpr int kAccessDeniedErrorCode = 5;
 static constexpr int kErrorSuccess = 0;
@@ -73,6 +82,8 @@
                void(const std::string&,
                     std::unique_ptr<MethodResult<rapidjson::Document>>));
 
+  MOCK_METHOD1(QuitApplication, void(int64_t exit_code));
+
  private:
   FML_DISALLOW_COPY_AND_ASSIGN(MockPlatformHandler);
 };
@@ -470,5 +481,70 @@
   EXPECT_EQ(result, "[null]");
 }
 
+TEST_F(PlatformHandlerTest, SystemExitApplicationRequired) {
+  use_headless_engine();
+  int exit_code = -1;
+
+  TestBinaryMessenger messenger([](const std::string& channel,
+                                   const uint8_t* message, size_t size,
+                                   BinaryReply reply) {});
+  MockPlatformHandler platform_handler(&messenger, engine());
+
+  ON_CALL(platform_handler, QuitApplication)
+      .WillByDefault([&exit_code](int ec) { exit_code = ec; });
+  EXPECT_CALL(platform_handler, QuitApplication).Times(1);
+
+  std::string result = SimulatePlatformMessage(
+      &messenger, kSystemExitApplicationRequiredMessage);
+  EXPECT_EQ(result, "[{\"response\":\"exit\"}]");
+  EXPECT_EQ(exit_code, 1);
+}
+
+TEST_F(PlatformHandlerTest, SystemExitApplicationCancelableCancel) {
+  use_headless_engine();
+  bool called_cancel = false;
+
+  TestBinaryMessenger messenger(
+      [&called_cancel](const std::string& channel, const uint8_t* message,
+                       size_t size, BinaryReply reply) {
+        reply(reinterpret_cast<const uint8_t*>(kExitResponseCancelMessage),
+              sizeof(kExitResponseCancelMessage));
+        called_cancel = true;
+      });
+  MockPlatformHandler platform_handler(&messenger, engine());
+
+  EXPECT_CALL(platform_handler, QuitApplication).Times(0);
+
+  std::string result = SimulatePlatformMessage(
+      &messenger, kSystemExitApplicationCancelableMessage);
+  EXPECT_EQ(result, "[{\"response\":\"cancel\"}]");
+  EXPECT_TRUE(called_cancel);
+}
+
+TEST_F(PlatformHandlerTest, SystemExitApplicationCancelableExit) {
+  use_headless_engine();
+  bool called_cancel = false;
+  int exit_code = -1;
+
+  TestBinaryMessenger messenger(
+      [&called_cancel](const std::string& channel, const uint8_t* message,
+                       size_t size, BinaryReply reply) {
+        reply(reinterpret_cast<const uint8_t*>(kExitResponseExitMessage),
+              sizeof(kExitResponseExitMessage));
+        called_cancel = true;
+      });
+  MockPlatformHandler platform_handler(&messenger, engine());
+
+  ON_CALL(platform_handler, QuitApplication)
+      .WillByDefault([&exit_code](int ec) { exit_code = ec; });
+  EXPECT_CALL(platform_handler, QuitApplication).Times(1);
+
+  std::string result = SimulatePlatformMessage(
+      &messenger, kSystemExitApplicationCancelableMessage);
+  EXPECT_EQ(result, "[{\"response\":\"cancel\"}]");
+  EXPECT_TRUE(called_cancel);
+  EXPECT_EQ(exit_code, 2);
+}
+
 }  // namespace testing
 }  // namespace flutter