Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit a49138e

Browse files
authored
[linux] Wait for binding to be ready before requesting exits from framework (#41782)
## Description Similar to #41733 and #41753 this causes the linux embedding to wait until it hears that the scheduler binding has registered itself before proceeding to send termination requests to the framework. This allows applications that don't use the framework (just use `dart:ui` directly) to exit automatically when the last window is closed. Without this change, the app does not exit when the window is closed. Depends on framework PR flutter/flutter#126075 landing first. ## Related PRs - #41733 - #41753 ## Related Issues - flutter/flutter#126033. ## Tests - Added a test to make sure that it doesn't send a termination request if the binding hasn't notified that it is ready yet.
1 parent e8c00a3 commit a49138e

File tree

3 files changed

+50
-15
lines changed

3 files changed

+50
-15
lines changed

shell/platform/linux/fl_platform_plugin.cc

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ struct _FlPlatformPlugin {
4646
FlMethodChannel* channel;
4747
FlMethodCall* exit_application_method_call;
4848
GCancellable* cancellable;
49+
bool app_initialization_complete;
4950
};
5051

5152
G_DEFINE_TYPE(FlPlatformPlugin, fl_platform_plugin, G_TYPE_OBJECT)
@@ -236,15 +237,32 @@ static void request_app_exit_response_cb(GObject* object,
236237
}
237238
}
238239

239-
// Send a request to Flutter to exit the application.
240+
// Send a request to Flutter to exit the application, but only if it's ready for
241+
// a request.
240242
static void request_app_exit(FlPlatformPlugin* self, const char* type) {
241243
g_autoptr(FlValue) args = fl_value_new_map();
244+
if (!self->app_initialization_complete ||
245+
g_str_equal(type, kExitTypeRequired)) {
246+
quit_application();
247+
return;
248+
}
249+
242250
fl_value_set_string_take(args, kExitTypeKey, fl_value_new_string(type));
243251
fl_method_channel_invoke_method(self->channel, kRequestAppExitMethod, args,
244252
self->cancellable,
245253
request_app_exit_response_cb, self);
246254
}
247255

256+
// Called when the Dart app has finished initialization and is ready to handle
257+
// requests. For the Flutter framework, this means after the ServicesBinding has
258+
// been initialized and it sends a System.initializationComplete message.
259+
static FlMethodResponse* system_intitialization_complete(
260+
FlPlatformPlugin* self,
261+
FlMethodCall* method_call) {
262+
self->app_initialization_complete = TRUE;
263+
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
264+
}
265+
248266
// Called when Flutter wants to exit the application.
249267
static FlMethodResponse* system_exit_application(FlPlatformPlugin* self,
250268
FlMethodCall* method_call) {
@@ -270,8 +288,10 @@ static FlMethodResponse* system_exit_application(FlPlatformPlugin* self,
270288
self->exit_application_method_call =
271289
FL_METHOD_CALL(g_object_ref(method_call));
272290

273-
// Requested to immediately quit.
274-
if (g_str_equal(type, kExitTypeRequired)) {
291+
// Requested to immediately quit if the app hasn't yet signaled that it is
292+
// ready to handle requests, or if the type of exit requested is "required".
293+
if (!self->app_initialization_complete ||
294+
g_str_equal(type, kExitTypeRequired)) {
275295
quit_application();
276296
g_autoptr(FlValue) exit_result = fl_value_new_map();
277297
fl_value_set_string_take(exit_result, kExitResponseKey,
@@ -333,14 +353,12 @@ static void method_call_cb(FlMethodChannel* channel,
333353
response = clipboard_has_strings_async(self, method_call);
334354
} else if (strcmp(method, kExitApplicationMethod) == 0) {
335355
response = system_exit_application(self, method_call);
356+
} else if (strcmp(method, kInitializationCompleteMethod) == 0) {
357+
response = system_intitialization_complete(self, method_call);
336358
} else if (strcmp(method, kPlaySoundMethod) == 0) {
337359
response = system_sound_play(self, args);
338360
} else if (strcmp(method, kSystemNavigatorPopMethod) == 0) {
339361
response = system_navigator_pop(self);
340-
} else if (strcmp(method, kInitializationCompleteMethod) == 0) {
341-
// TODO(gspencergoog): Handle this message to enable exit message listening.
342-
// https://github.com/flutter/flutter/issues/126033
343-
response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
344362
} else {
345363
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
346364
}
@@ -381,6 +399,7 @@ FlPlatformPlugin* fl_platform_plugin_new(FlBinaryMessenger* messenger) {
381399
fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec));
382400
fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self,
383401
nullptr);
402+
self->app_initialization_complete = FALSE;
384403

385404
return self;
386405
}

shell/platform/linux/fl_platform_plugin.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ FlPlatformPlugin* fl_platform_plugin_new(FlBinaryMessenger* messenger);
3939
*
4040
* Request the application exits (i.e. due to the window being requested to be
4141
* closed).
42+
*
43+
* Calling this will only send an exit request to the framework if the framework
44+
* has already indicated that it is ready to receive requests by sending a
45+
* "System.initializationComplete" method call on the platform channel. Calls
46+
* before initialization is complete will result in an immediate exit.
4247
*/
4348
void fl_platform_plugin_request_app_exit(FlPlatformPlugin* plugin);
4449

shell/platform/linux/fl_platform_plugin_test.cc

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -112,22 +112,33 @@ TEST(FlPlatformPluginTest, ExitApplication) {
112112

113113
g_autoptr(FlPlatformPlugin) plugin = fl_platform_plugin_new(messenger);
114114
EXPECT_NE(plugin, nullptr);
115-
116-
g_autoptr(FlValue) args = fl_value_new_map();
117-
fl_value_set_string_take(args, "type", fl_value_new_string("cancelable"));
118115
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
119-
g_autoptr(GBytes) message = fl_method_codec_encode_method_call(
120-
FL_METHOD_CODEC(codec), "System.exitApplication", args, nullptr);
121116

122-
g_autoptr(FlValue) requestArgs = fl_value_new_map();
123-
fl_value_set_string_take(requestArgs, "type",
117+
g_autoptr(FlValue) null = fl_value_new_null();
118+
ON_CALL(messenger, fl_binary_messenger_send_response(
119+
::testing::Eq<FlBinaryMessenger*>(messenger),
120+
::testing::_, SuccessResponse(null), ::testing::_))
121+
.WillByDefault(testing::Return(TRUE));
122+
123+
// Indicate that the binding is initialized.
124+
g_autoptr(GError) error = nullptr;
125+
g_autoptr(GBytes) init_message = fl_method_codec_encode_method_call(
126+
FL_METHOD_CODEC(codec), "System.initializationComplete", nullptr, &error);
127+
messenger.ReceiveMessage("flutter/platform", init_message);
128+
129+
g_autoptr(FlValue) request_args = fl_value_new_map();
130+
fl_value_set_string_take(request_args, "type",
124131
fl_value_new_string("cancelable"));
125132
EXPECT_CALL(messenger,
126133
fl_binary_messenger_send_on_channel(
127134
::testing::Eq<FlBinaryMessenger*>(messenger),
128135
::testing::StrEq("flutter/platform"),
129-
MethodCall("System.requestAppExit", FlValueEq(requestArgs)),
136+
MethodCall("System.requestAppExit", FlValueEq(request_args)),
130137
::testing::_, ::testing::_, ::testing::_));
131138

139+
g_autoptr(FlValue) args = fl_value_new_map();
140+
fl_value_set_string_take(args, "type", fl_value_new_string("cancelable"));
141+
g_autoptr(GBytes) message = fl_method_codec_encode_method_call(
142+
FL_METHOD_CODEC(codec), "System.exitApplication", args, nullptr);
132143
messenger.ReceiveMessage("flutter/platform", message);
133144
}

0 commit comments

Comments
 (0)