From 02144b9009d3e30cf2911b7380c7d77848100143 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Thu, 4 May 2023 17:16:56 -0700 Subject: [PATCH 1/7] Only accept exit requests after the app is initialized --- shell/platform/linux/fl_platform_plugin.cc | 19 +++++++++++++++++-- shell/platform/linux/fl_platform_plugin.h | 4 +++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/shell/platform/linux/fl_platform_plugin.cc b/shell/platform/linux/fl_platform_plugin.cc index 35b954f6b1ac3..3ba5cc2d23ae2 100644 --- a/shell/platform/linux/fl_platform_plugin.cc +++ b/shell/platform/linux/fl_platform_plugin.cc @@ -46,6 +46,7 @@ struct _FlPlatformPlugin { FlMethodChannel* channel; FlMethodCall* exit_application_method_call; GCancellable* cancellable; + bool app_initialization_complete; }; G_DEFINE_TYPE(FlPlatformPlugin, fl_platform_plugin, G_TYPE_OBJECT) @@ -245,6 +246,16 @@ static void request_app_exit(FlPlatformPlugin* self, const char* type) { request_app_exit_response_cb, self); } +// Called when the Dart app has finished initialization and is ready to handle +// requests. For the Flutter framework, this means after the ServicesBinding has +// been intialized and it sends a System.initializationComplete message. +static FlMethodResponse* system_intitialization_complete( + FlPlatformPlugin* self, + FlMethodCall* method_call) { + self->app_initialization_complete = TRUE; + return nullptr; +} + // Called when Flutter wants to exit the application. static FlMethodResponse* system_exit_application(FlPlatformPlugin* self, FlMethodCall* method_call) { @@ -270,8 +281,10 @@ static FlMethodResponse* system_exit_application(FlPlatformPlugin* self, self->exit_application_method_call = FL_METHOD_CALL(g_object_ref(method_call)); - // Requested to immediately quit. - if (g_str_equal(type, kExitTypeRequired)) { + // Requested to immediately quit if the app hasn't yet signaled that it is + // ready to handle requests, or if the type of exit requested is "required". + if (!self->app_initialization_complete || + g_str_equal(type, kExitTypeRequired)) { quit_application(); g_autoptr(FlValue) exit_result = fl_value_new_map(); fl_value_set_string_take(exit_result, kExitResponseKey, @@ -333,6 +346,8 @@ static void method_call_cb(FlMethodChannel* channel, response = clipboard_has_strings_async(self, method_call); } else if (strcmp(method, kExitApplicationMethod) == 0) { response = system_exit_application(self, method_call); + } else if (strcmp(method, kInitializationComplete) == 0) { + response = system_intitialization_complete(self, method_call); } else if (strcmp(method, kPlaySoundMethod) == 0) { response = system_sound_play(self, args); } else if (strcmp(method, kSystemNavigatorPopMethod) == 0) { diff --git a/shell/platform/linux/fl_platform_plugin.h b/shell/platform/linux/fl_platform_plugin.h index c25874da74f95..32bdda6b20c7d 100644 --- a/shell/platform/linux/fl_platform_plugin.h +++ b/shell/platform/linux/fl_platform_plugin.h @@ -38,7 +38,9 @@ FlPlatformPlugin* fl_platform_plugin_new(FlBinaryMessenger* messenger); * @plugin: an #FlPlatformPlugin * * Request the application exits (i.e. due to the window being requested to be - * closed). + * closed). Will only actually send a request to the framework to ask if it + * should exit if the framework has indicated that it is ready to receive + * requests. */ void fl_platform_plugin_request_app_exit(FlPlatformPlugin* plugin); From a1dc40e614bc885babd614bd60a6012e7971e51d Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Thu, 4 May 2023 17:23:09 -0700 Subject: [PATCH 2/7] Just don't request an exit if not ready --- shell/platform/linux/fl_platform_plugin.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/shell/platform/linux/fl_platform_plugin.cc b/shell/platform/linux/fl_platform_plugin.cc index 3ba5cc2d23ae2..1efdd5ca291ff 100644 --- a/shell/platform/linux/fl_platform_plugin.cc +++ b/shell/platform/linux/fl_platform_plugin.cc @@ -237,9 +237,15 @@ static void request_app_exit_response_cb(GObject* object, } } -// Send a request to Flutter to exit the application. +// Send a request to Flutter to exit the application, but only if it's ready for +// a request. static void request_app_exit(FlPlatformPlugin* self, const char* type) { g_autoptr(FlValue) args = fl_value_new_map(); + if (!self->app_initialization_complete || + g_str_equal(type, kExitTypeRequired)) { + quit_application(); + } + fl_value_set_string_take(args, kExitTypeKey, fl_value_new_string(type)); fl_method_channel_invoke_method(self->channel, kRequestAppExitMethod, args, self->cancellable, From 92c829840ee1181741b6e75594bd0988ecbb4a6f Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 5 May 2023 11:39:37 -0700 Subject: [PATCH 3/7] Add test --- shell/platform/linux/fl_platform_plugin.cc | 1 + shell/platform/linux/fl_platform_plugin.h | 9 ++++++--- shell/platform/linux/fl_platform_plugin_test.cc | 16 +++++++++++----- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/shell/platform/linux/fl_platform_plugin.cc b/shell/platform/linux/fl_platform_plugin.cc index 1efdd5ca291ff..9eabbf76d4a07 100644 --- a/shell/platform/linux/fl_platform_plugin.cc +++ b/shell/platform/linux/fl_platform_plugin.cc @@ -244,6 +244,7 @@ static void request_app_exit(FlPlatformPlugin* self, const char* type) { if (!self->app_initialization_complete || g_str_equal(type, kExitTypeRequired)) { quit_application(); + return; } fl_value_set_string_take(args, kExitTypeKey, fl_value_new_string(type)); diff --git a/shell/platform/linux/fl_platform_plugin.h b/shell/platform/linux/fl_platform_plugin.h index 32bdda6b20c7d..573becb44a660 100644 --- a/shell/platform/linux/fl_platform_plugin.h +++ b/shell/platform/linux/fl_platform_plugin.h @@ -38,9 +38,12 @@ FlPlatformPlugin* fl_platform_plugin_new(FlBinaryMessenger* messenger); * @plugin: an #FlPlatformPlugin * * Request the application exits (i.e. due to the window being requested to be - * closed). Will only actually send a request to the framework to ask if it - * should exit if the framework has indicated that it is ready to receive - * requests. + * closed). + * + * Calling this will only send an exit request to the framework if the framework + * has already indicated that it is ready to receive requests by sending a + * "System.initializationComplete" method call on the platform channel. Calls + * before initialization is complete will result in an immediate exit. */ void fl_platform_plugin_request_app_exit(FlPlatformPlugin* plugin); diff --git a/shell/platform/linux/fl_platform_plugin_test.cc b/shell/platform/linux/fl_platform_plugin_test.cc index 0bf5b8007eb30..5a7eadacad21b 100644 --- a/shell/platform/linux/fl_platform_plugin_test.cc +++ b/shell/platform/linux/fl_platform_plugin_test.cc @@ -112,16 +112,18 @@ TEST(FlPlatformPluginTest, ExitApplication) { g_autoptr(FlPlatformPlugin) plugin = fl_platform_plugin_new(messenger); EXPECT_NE(plugin, nullptr); - - g_autoptr(FlValue) args = fl_value_new_map(); - fl_value_set_string_take(args, "type", fl_value_new_string("cancelable")); g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(GBytes) message = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "System.exitApplication", args, nullptr); g_autoptr(FlValue) requestArgs = fl_value_new_map(); fl_value_set_string_take(requestArgs, "type", fl_value_new_string("cancelable")); + + // Indicate that the binding is initialized. + g_autoptr(GBytes) init_message = fl_method_codec_encode_method_call( + FL_METHOD_CODEC(codec), "System.initializationComplete", nullptr, + nullptr); + messenger.ReceiveMessage("flutter/platform", init_message); + EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( ::testing::Eq(messenger), @@ -129,5 +131,9 @@ TEST(FlPlatformPluginTest, ExitApplication) { MethodCall("System.requestAppExit", FlValueEq(requestArgs)), ::testing::_, ::testing::_, ::testing::_)); + g_autoptr(FlValue) args = fl_value_new_map(); + fl_value_set_string_take(args, "type", fl_value_new_string("cancelable")); + g_autoptr(GBytes) message = fl_method_codec_encode_method_call( + FL_METHOD_CODEC(codec), "System.exitApplication", args, nullptr); messenger.ReceiveMessage("flutter/platform", message); } From a063dd80fc7589a65921dabe46e2d2f30e239e49 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 5 May 2023 13:39:53 -0700 Subject: [PATCH 4/7] Update shell/platform/linux/fl_platform_plugin.cc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Loïc Sharma <737941+loic-sharma@users.noreply.github.com> --- shell/platform/linux/fl_platform_plugin.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/linux/fl_platform_plugin.cc b/shell/platform/linux/fl_platform_plugin.cc index 9eabbf76d4a07..198eb1dca7239 100644 --- a/shell/platform/linux/fl_platform_plugin.cc +++ b/shell/platform/linux/fl_platform_plugin.cc @@ -255,7 +255,7 @@ static void request_app_exit(FlPlatformPlugin* self, const char* type) { // Called when the Dart app has finished initialization and is ready to handle // requests. For the Flutter framework, this means after the ServicesBinding has -// been intialized and it sends a System.initializationComplete message. +// been initialized and it sends a System.initializationComplete message. static FlMethodResponse* system_intitialization_complete( FlPlatformPlugin* self, FlMethodCall* method_call) { From 149bb4a059761adb462eeb1c88d5fd4ea28bcd9a Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Mon, 8 May 2023 08:48:15 -0700 Subject: [PATCH 5/7] Send nullptr return value --- shell/platform/linux/fl_platform_plugin.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/linux/fl_platform_plugin.cc b/shell/platform/linux/fl_platform_plugin.cc index 198eb1dca7239..e168840146522 100644 --- a/shell/platform/linux/fl_platform_plugin.cc +++ b/shell/platform/linux/fl_platform_plugin.cc @@ -260,7 +260,7 @@ static FlMethodResponse* system_intitialization_complete( FlPlatformPlugin* self, FlMethodCall* method_call) { self->app_initialization_complete = TRUE; - return nullptr; + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); } // Called when Flutter wants to exit the application. From d7ee743d55d174b10a2e47adc3cad4a1b9563507 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Mon, 8 May 2023 15:37:44 -0700 Subject: [PATCH 6/7] Fix test --- shell/platform/linux/fl_platform_plugin_test.cc | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/shell/platform/linux/fl_platform_plugin_test.cc b/shell/platform/linux/fl_platform_plugin_test.cc index 5a7eadacad21b..68e53eed2bda7 100644 --- a/shell/platform/linux/fl_platform_plugin_test.cc +++ b/shell/platform/linux/fl_platform_plugin_test.cc @@ -114,21 +114,25 @@ TEST(FlPlatformPluginTest, ExitApplication) { EXPECT_NE(plugin, nullptr); g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); - g_autoptr(FlValue) requestArgs = fl_value_new_map(); - fl_value_set_string_take(requestArgs, "type", - fl_value_new_string("cancelable")); + ON_CALL(messenger, fl_binary_messenger_send_response( + ::testing::Eq(messenger), + ::testing::_, ::testing::_, ::testing::_)) + .WillByDefault(testing::Return(TRUE)); // Indicate that the binding is initialized. + g_autoptr(GError) error = nullptr; g_autoptr(GBytes) init_message = fl_method_codec_encode_method_call( - FL_METHOD_CODEC(codec), "System.initializationComplete", nullptr, - nullptr); + FL_METHOD_CODEC(codec), "System.initializationComplete", nullptr, &error); messenger.ReceiveMessage("flutter/platform", init_message); + g_autoptr(FlValue) request_args = fl_value_new_map(); + fl_value_set_string_take(request_args, "type", + fl_value_new_string("cancelable")); EXPECT_CALL(messenger, fl_binary_messenger_send_on_channel( ::testing::Eq(messenger), ::testing::StrEq("flutter/platform"), - MethodCall("System.requestAppExit", FlValueEq(requestArgs)), + MethodCall("System.requestAppExit", FlValueEq(request_args)), ::testing::_, ::testing::_, ::testing::_)); g_autoptr(FlValue) args = fl_value_new_map(); From c13eed24b99c7fa9adec8326dafb8edd7ed6b0ed Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Tue, 9 May 2023 10:32:44 -0700 Subject: [PATCH 7/7] Add SuccessResponse to ON_CALL --- shell/platform/linux/fl_platform_plugin.cc | 7 ++----- shell/platform/linux/fl_platform_plugin_test.cc | 3 ++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/shell/platform/linux/fl_platform_plugin.cc b/shell/platform/linux/fl_platform_plugin.cc index e168840146522..008d3d0b1d58b 100644 --- a/shell/platform/linux/fl_platform_plugin.cc +++ b/shell/platform/linux/fl_platform_plugin.cc @@ -353,16 +353,12 @@ static void method_call_cb(FlMethodChannel* channel, response = clipboard_has_strings_async(self, method_call); } else if (strcmp(method, kExitApplicationMethod) == 0) { response = system_exit_application(self, method_call); - } else if (strcmp(method, kInitializationComplete) == 0) { + } else if (strcmp(method, kInitializationCompleteMethod) == 0) { response = system_intitialization_complete(self, method_call); } else if (strcmp(method, kPlaySoundMethod) == 0) { response = system_sound_play(self, args); } else if (strcmp(method, kSystemNavigatorPopMethod) == 0) { response = system_navigator_pop(self); - } else if (strcmp(method, kInitializationCompleteMethod) == 0) { - // TODO(gspencergoog): Handle this message to enable exit message listening. - // https://github.com/flutter/flutter/issues/126033 - response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); } else { response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); } @@ -403,6 +399,7 @@ FlPlatformPlugin* fl_platform_plugin_new(FlBinaryMessenger* messenger) { fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self, nullptr); + self->app_initialization_complete = FALSE; return self; } diff --git a/shell/platform/linux/fl_platform_plugin_test.cc b/shell/platform/linux/fl_platform_plugin_test.cc index 68e53eed2bda7..09b2b7f76dc8b 100644 --- a/shell/platform/linux/fl_platform_plugin_test.cc +++ b/shell/platform/linux/fl_platform_plugin_test.cc @@ -114,9 +114,10 @@ TEST(FlPlatformPluginTest, ExitApplication) { EXPECT_NE(plugin, nullptr); g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); + g_autoptr(FlValue) null = fl_value_new_null(); ON_CALL(messenger, fl_binary_messenger_send_response( ::testing::Eq(messenger), - ::testing::_, ::testing::_, ::testing::_)) + ::testing::_, SuccessResponse(null), ::testing::_)) .WillByDefault(testing::Return(TRUE)); // Indicate that the binding is initialized.