diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 6a607ed5d88e7..90e79b9eba1c2 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -100,7 +100,7 @@ RuntimeController::~RuntimeController() { } } -bool RuntimeController::IsRootIsolateRunning() { +bool RuntimeController::IsRootIsolateRunning() const { std::shared_ptr root_isolate = root_isolate_.lock(); if (root_isolate) { return root_isolate->GetPhase() == DartIsolate::Phase::Running; diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index 93da07092e1a6..3f68408ebf0e8 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -452,7 +452,7 @@ class RuntimeController : public PlatformConfigurationClient, /// /// @return True if root isolate running, False otherwise. /// - virtual bool IsRootIsolateRunning(); + virtual bool IsRootIsolateRunning() const; //---------------------------------------------------------------------------- /// @brief Dispatch the specified platform message to running root diff --git a/shell/common/engine_unittests.cc b/shell/common/engine_unittests.cc index 7a822c5dca1b7..d549a6ccd5a73 100644 --- a/shell/common/engine_unittests.cc +++ b/shell/common/engine_unittests.cc @@ -144,7 +144,7 @@ class MockRuntimeController : public RuntimeController { MockRuntimeController(RuntimeDelegate& client, const TaskRunners& p_task_runners) : RuntimeController(client, p_task_runners) {} - MOCK_METHOD(bool, IsRootIsolateRunning, (), (override)); + MOCK_METHOD(bool, IsRootIsolateRunning, (), (override, const)); MOCK_METHOD(bool, DispatchPlatformMessage, (std::unique_ptr), diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 79c532640a969..75276e49b22f9 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -1074,16 +1074,38 @@ void Shell::OnPlatformViewDispatchPlatformMessage( } #endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG - // The static leak checker gets confused by the use of fml::MakeCopyable. - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) - fml::TaskRunner::RunNowOrPostTask( - task_runners_.GetUITaskRunner(), - fml::MakeCopyable([engine = engine_->GetWeakPtr(), - message = std::move(message)]() mutable { - if (engine) { - engine->DispatchPlatformMessage(std::move(message)); - } - })); + // If the root isolate is not yet running this may be the navigation + // channel initial route and must be dispatched immediately so that + // it can be set before isolate creation. + static constexpr char kNavigationChannel[] = "flutter/navigation"; + if (!engine_->GetRuntimeController()->IsRootIsolateRunning() && + message->channel() == kNavigationChannel) { + // The static leak checker gets confused by the use of fml::MakeCopyable. + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + fml::TaskRunner::RunNowOrPostTask( + task_runners_.GetUITaskRunner(), + fml::MakeCopyable([engine = engine_->GetWeakPtr(), + message = std::move(message)]() mutable { + if (engine) { + engine->DispatchPlatformMessage(std::move(message)); + } + })); + } else { + // In all other cases, the message must be dispatched via a new task so + // that the completion of the platform channel response future is guaranteed + // to wake up the Dart event loop, even in cases where the platform and UI + // threads are the same. + + // The static leak checker gets confused by the use of fml::MakeCopyable. + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + task_runners_.GetUITaskRunner()->PostTask( + fml::MakeCopyable([engine = engine_->GetWeakPtr(), + message = std::move(message)]() mutable { + if (engine) { + engine->DispatchPlatformMessage(std::move(message)); + } + })); + } } // |PlatformView::Delegate| diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index 60428cff23ec9..f8d102c0a920a 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -68,6 +68,33 @@ using ::testing::_; using ::testing::Return; namespace { + +std::unique_ptr MakePlatformMessage( + const std::string& channel, + const std::map& values, + const fml::RefPtr& response) { + rapidjson::Document document; + auto& allocator = document.GetAllocator(); + document.SetObject(); + + for (const auto& pair : values) { + rapidjson::Value key(pair.first.c_str(), strlen(pair.first.c_str()), + allocator); + rapidjson::Value value(pair.second.c_str(), strlen(pair.second.c_str()), + allocator); + document.AddMember(key, value, allocator); + } + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + document.Accept(writer); + const uint8_t* data = reinterpret_cast(buffer.GetString()); + + std::unique_ptr message = std::make_unique( + channel, fml::MallocMapping::Copy(data, buffer.GetSize()), response); + return message; +} + class MockPlatformViewDelegate : public PlatformView::Delegate { MOCK_METHOD(void, OnPlatformViewCreated, @@ -4260,6 +4287,31 @@ TEST_F(ShellTest, PrintsErrorWhenPlatformMessageSentFromWrongThread) { #endif } +TEST_F(ShellTest, NavigationMessageDispachedImmediately) { + Settings settings = CreateSettingsForFixture(); + ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".", + ThreadHost::Type::kPlatform); + auto task_runner = thread_host.platform_thread->GetTaskRunner(); + TaskRunners task_runners("test", task_runner, task_runner, task_runner, + task_runner); + auto shell = CreateShell(settings, task_runners); + + auto latch = std::make_shared(1u); + task_runner->PostTask([&]() { + auto message = MakePlatformMessage( + "flutter/navigation", + {{"method", "setInitialRoute"}, {"args", "/testo"}}, nullptr); + SendPlatformMessage(shell.get(), std::move(message)); + EXPECT_EQ(shell->GetEngine()->InitialRoute(), "/testo"); + + latch->CountDown(); + }); + latch->Wait(); + + DestroyShell(std::move(shell), task_runners); + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); +} + TEST_F(ShellTest, DiesIfSoftwareRenderingAndImpellerAreEnabledDeathTest) { #if defined(OS_FUCHSIA) GTEST_SKIP() << "Fuchsia";