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

Commit 117a7d0

Browse files
[engine] dispatch platform channel messages through event loop, except navigation on start. (#55027)
This is a re-land of #55006, except that we special case the navigation channel to immediately dispatch its message if the isolate is not yet running. This preserves the existing behavior relied upon by several iOS add2app tests, as well as the still used embedder v1 - and potentially undicovered future embedders.
1 parent 331fd5a commit 117a7d0

File tree

5 files changed

+87
-13
lines changed

5 files changed

+87
-13
lines changed

runtime/runtime_controller.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ RuntimeController::~RuntimeController() {
100100
}
101101
}
102102

103-
bool RuntimeController::IsRootIsolateRunning() {
103+
bool RuntimeController::IsRootIsolateRunning() const {
104104
std::shared_ptr<DartIsolate> root_isolate = root_isolate_.lock();
105105
if (root_isolate) {
106106
return root_isolate->GetPhase() == DartIsolate::Phase::Running;

runtime/runtime_controller.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ class RuntimeController : public PlatformConfigurationClient,
452452
///
453453
/// @return True if root isolate running, False otherwise.
454454
///
455-
virtual bool IsRootIsolateRunning();
455+
virtual bool IsRootIsolateRunning() const;
456456

457457
//----------------------------------------------------------------------------
458458
/// @brief Dispatch the specified platform message to running root

shell/common/engine_unittests.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ class MockRuntimeController : public RuntimeController {
144144
MockRuntimeController(RuntimeDelegate& client,
145145
const TaskRunners& p_task_runners)
146146
: RuntimeController(client, p_task_runners) {}
147-
MOCK_METHOD(bool, IsRootIsolateRunning, (), (override));
147+
MOCK_METHOD(bool, IsRootIsolateRunning, (), (override, const));
148148
MOCK_METHOD(bool,
149149
DispatchPlatformMessage,
150150
(std::unique_ptr<PlatformMessage>),

shell/common/shell.cc

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,16 +1074,38 @@ void Shell::OnPlatformViewDispatchPlatformMessage(
10741074
}
10751075
#endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
10761076

1077-
// The static leak checker gets confused by the use of fml::MakeCopyable.
1078-
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
1079-
fml::TaskRunner::RunNowOrPostTask(
1080-
task_runners_.GetUITaskRunner(),
1081-
fml::MakeCopyable([engine = engine_->GetWeakPtr(),
1082-
message = std::move(message)]() mutable {
1083-
if (engine) {
1084-
engine->DispatchPlatformMessage(std::move(message));
1085-
}
1086-
}));
1077+
// If the root isolate is not yet running this may be the navigation
1078+
// channel initial route and must be dispatched immediately so that
1079+
// it can be set before isolate creation.
1080+
static constexpr char kNavigationChannel[] = "flutter/navigation";
1081+
if (!engine_->GetRuntimeController()->IsRootIsolateRunning() &&
1082+
message->channel() == kNavigationChannel) {
1083+
// The static leak checker gets confused by the use of fml::MakeCopyable.
1084+
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
1085+
fml::TaskRunner::RunNowOrPostTask(
1086+
task_runners_.GetUITaskRunner(),
1087+
fml::MakeCopyable([engine = engine_->GetWeakPtr(),
1088+
message = std::move(message)]() mutable {
1089+
if (engine) {
1090+
engine->DispatchPlatformMessage(std::move(message));
1091+
}
1092+
}));
1093+
} else {
1094+
// In all other cases, the message must be dispatched via a new task so
1095+
// that the completion of the platform channel response future is guaranteed
1096+
// to wake up the Dart event loop, even in cases where the platform and UI
1097+
// threads are the same.
1098+
1099+
// The static leak checker gets confused by the use of fml::MakeCopyable.
1100+
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
1101+
task_runners_.GetUITaskRunner()->PostTask(
1102+
fml::MakeCopyable([engine = engine_->GetWeakPtr(),
1103+
message = std::move(message)]() mutable {
1104+
if (engine) {
1105+
engine->DispatchPlatformMessage(std::move(message));
1106+
}
1107+
}));
1108+
}
10871109
}
10881110

10891111
// |PlatformView::Delegate|

shell/common/shell_unittests.cc

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,33 @@ using ::testing::_;
6868
using ::testing::Return;
6969

7070
namespace {
71+
72+
std::unique_ptr<PlatformMessage> MakePlatformMessage(
73+
const std::string& channel,
74+
const std::map<std::string, std::string>& values,
75+
const fml::RefPtr<PlatformMessageResponse>& response) {
76+
rapidjson::Document document;
77+
auto& allocator = document.GetAllocator();
78+
document.SetObject();
79+
80+
for (const auto& pair : values) {
81+
rapidjson::Value key(pair.first.c_str(), strlen(pair.first.c_str()),
82+
allocator);
83+
rapidjson::Value value(pair.second.c_str(), strlen(pair.second.c_str()),
84+
allocator);
85+
document.AddMember(key, value, allocator);
86+
}
87+
88+
rapidjson::StringBuffer buffer;
89+
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
90+
document.Accept(writer);
91+
const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString());
92+
93+
std::unique_ptr<PlatformMessage> message = std::make_unique<PlatformMessage>(
94+
channel, fml::MallocMapping::Copy(data, buffer.GetSize()), response);
95+
return message;
96+
}
97+
7198
class MockPlatformViewDelegate : public PlatformView::Delegate {
7299
MOCK_METHOD(void,
73100
OnPlatformViewCreated,
@@ -4260,6 +4287,31 @@ TEST_F(ShellTest, PrintsErrorWhenPlatformMessageSentFromWrongThread) {
42604287
#endif
42614288
}
42624289

4290+
TEST_F(ShellTest, NavigationMessageDispachedImmediately) {
4291+
Settings settings = CreateSettingsForFixture();
4292+
ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
4293+
ThreadHost::Type::kPlatform);
4294+
auto task_runner = thread_host.platform_thread->GetTaskRunner();
4295+
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
4296+
task_runner);
4297+
auto shell = CreateShell(settings, task_runners);
4298+
4299+
auto latch = std::make_shared<fml::CountDownLatch>(1u);
4300+
task_runner->PostTask([&]() {
4301+
auto message = MakePlatformMessage(
4302+
"flutter/navigation",
4303+
{{"method", "setInitialRoute"}, {"args", "/testo"}}, nullptr);
4304+
SendPlatformMessage(shell.get(), std::move(message));
4305+
EXPECT_EQ(shell->GetEngine()->InitialRoute(), "/testo");
4306+
4307+
latch->CountDown();
4308+
});
4309+
latch->Wait();
4310+
4311+
DestroyShell(std::move(shell), task_runners);
4312+
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
4313+
}
4314+
42634315
TEST_F(ShellTest, DiesIfSoftwareRenderingAndImpellerAreEnabledDeathTest) {
42644316
#if defined(OS_FUCHSIA)
42654317
GTEST_SKIP() << "Fuchsia";

0 commit comments

Comments
 (0)