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