diff --git a/shell/platform/linux/fl_method_channel.cc b/shell/platform/linux/fl_method_channel.cc index cb705d243733b..d4c45ed920a7c 100644 --- a/shell/platform/linux/fl_method_channel.cc +++ b/shell/platform/linux/fl_method_channel.cc @@ -75,6 +75,10 @@ static void channel_closed_cb(gpointer user_data) { g_autoptr(FlMethodChannel) self = FL_METHOD_CHANNEL(user_data); self->channel_closed = TRUE; + // Clear the messenger so that disposing the channel will not clear the + // messenger's mapped channel, since `channel_closed_cb` means the messenger + // has abandoned this channel. + self->messenger = nullptr; // Disconnect handler. if (self->method_call_handler_destroy_notify != nullptr) { diff --git a/shell/platform/linux/fl_method_channel_test.cc b/shell/platform/linux/fl_method_channel_test.cc index 0d9fed34a624c..74b9294c60e03 100644 --- a/shell/platform/linux/fl_method_channel_test.cc +++ b/shell/platform/linux/fl_method_channel_test.cc @@ -603,3 +603,144 @@ TEST(FlMethodChannelTest, ReceiveMethodCallRespondErrorError) { // Blocks here until method_call_error_error_cb is called. g_main_loop_run(loop); } + +struct UserDataReassignMethod { + GMainLoop* loop; + int count; +}; + +// This callback parses the user data as UserDataReassignMethod, +// increases its `count`, and quits `loop`. +static void reassign_method_cb(FlMethodChannel* channel, + FlMethodCall* method_call, + gpointer raw_user_data) { + UserDataReassignMethod* user_data = + static_cast(raw_user_data); + user_data->count += 1; + + g_autoptr(FlValue) result = fl_value_new_string("Polo!"); + g_autoptr(GError) error = nullptr; + EXPECT_TRUE(fl_method_call_respond_success(method_call, result, &error)); + EXPECT_EQ(error, nullptr); + + g_main_loop_quit(user_data->loop); +} + +// Make sure that the following steps will work properly: +// +// 1. Register a method channel. +// 2. Dispose the method channel, and it's unregistered. +// 3. Register a new channel with the same name. +// +// This is a regression test to https://github.com/flutter/flutter/issues/90817. +TEST(FlMethodChannelTest, ReplaceADisposedMethodChannel) { + const char* method_name = "test/standard-method"; + // The loop is used to pause the main process until the callback is fully + // executed. + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + g_autoptr(FlEngine) engine = make_mock_engine(); + FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + + g_autoptr(FlValue) args = fl_value_new_list(); + fl_value_append_take(args, fl_value_new_string(method_name)); + fl_value_append_take(args, fl_value_new_string("FOO")); + fl_value_append_take(args, fl_value_new_string("BAR")); + + // Register the first channel and test if it works. + UserDataReassignMethod user_data1{ + .loop = loop, + .count = 100, + }; + FlMethodChannel* channel1 = + fl_method_channel_new(messenger, method_name, FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler(channel1, reassign_method_cb, + &user_data1, nullptr); + + fl_method_channel_invoke_method(channel1, "InvokeMethod", args, nullptr, + nullptr, nullptr); + g_main_loop_run(loop); + EXPECT_EQ(user_data1.count, 101); + + // Dispose the first channel. + g_object_unref(channel1); + + // Register the second channel and test if it works. + UserDataReassignMethod user_data2{ + .loop = loop, + .count = 100, + }; + g_autoptr(FlMethodChannel) channel2 = + fl_method_channel_new(messenger, method_name, FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler(channel2, reassign_method_cb, + &user_data2, nullptr); + + fl_method_channel_invoke_method(channel2, "InvokeMethod", args, nullptr, + nullptr, nullptr); + g_main_loop_run(loop); + + EXPECT_EQ(user_data1.count, 101); + EXPECT_EQ(user_data2.count, 101); +} + +// Make sure that the following steps will work properly: +// +// 1. Register a method channel. +// 2. Register the same name with a new channel. +// 3. Dispose the previous method channel. +// +// This is a regression test to https://github.com/flutter/flutter/issues/90817. +TEST(FlMethodChannelTest, DisposeAReplacedMethodChannel) { + const char* method_name = "test/standard-method"; + // The loop is used to pause the main process until the callback is fully + // executed. + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + g_autoptr(FlEngine) engine = make_mock_engine(); + FlBinaryMessenger* messenger = fl_binary_messenger_new(engine); + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + + g_autoptr(FlValue) args = fl_value_new_list(); + fl_value_append_take(args, fl_value_new_string(method_name)); + fl_value_append_take(args, fl_value_new_string("FOO")); + fl_value_append_take(args, fl_value_new_string("BAR")); + + // Register the first channel and test if it works. + UserDataReassignMethod user_data1{ + .loop = loop, + .count = 100, + }; + FlMethodChannel* channel1 = + fl_method_channel_new(messenger, method_name, FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler(channel1, reassign_method_cb, + &user_data1, nullptr); + + fl_method_channel_invoke_method(channel1, "InvokeMethod", args, nullptr, + nullptr, nullptr); + g_main_loop_run(loop); + EXPECT_EQ(user_data1.count, 101); + + // Register a new channel to the same name. + UserDataReassignMethod user_data2{ + .loop = loop, + .count = 100, + }; + g_autoptr(FlMethodChannel) channel2 = + fl_method_channel_new(messenger, method_name, FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler(channel2, reassign_method_cb, + &user_data2, nullptr); + + fl_method_channel_invoke_method(channel2, "InvokeMethod", args, nullptr, + nullptr, nullptr); + g_main_loop_run(loop); + EXPECT_EQ(user_data1.count, 101); + EXPECT_EQ(user_data2.count, 101); + + // Dispose the first channel. The new channel should keep working. + g_object_unref(channel1); + + fl_method_channel_invoke_method(channel2, "InvokeMethod", args, nullptr, + nullptr, nullptr); + g_main_loop_run(loop); + EXPECT_EQ(user_data1.count, 101); + EXPECT_EQ(user_data2.count, 102); +}