diff --git a/engine/src/flutter/common/settings.h b/engine/src/flutter/common/settings.h index 4854c9b45f3..b9b35b1a3b7 100644 --- a/engine/src/flutter/common/settings.h +++ b/engine/src/flutter/common/settings.h @@ -127,6 +127,8 @@ struct Settings { std::string temp_directory_path; std::vector dart_flags; // Arguments passed as a List to Dart's entrypoint function. + // TODO(93459): Remove it when it is no longer used. + // https://github.com/flutter/flutter/issues/93459 std::vector dart_entrypoint_args; // Isolate settings diff --git a/engine/src/flutter/fml/platform/android/jni_util.cc b/engine/src/flutter/fml/platform/android/jni_util.cc index d5937c90af7..86942d20e9f 100644 --- a/engine/src/flutter/fml/platform/android/jni_util.cc +++ b/engine/src/flutter/fml/platform/android/jni_util.cc @@ -125,6 +125,35 @@ std::vector StringArrayToVector(JNIEnv* env, jobjectArray array) { return out; } +std::vector StringListToVector(JNIEnv* env, jobject list) { + std::vector out; + if (env == nullptr || list == nullptr) { + return out; + } + + ScopedJavaLocalRef list_clazz(env, env->FindClass("java/util/List")); + FML_DCHECK(!list_clazz.is_null()); + + jmethodID list_get = + env->GetMethodID(list_clazz.obj(), "get", "(I)Ljava/lang/Object;"); + jmethodID list_size = env->GetMethodID(list_clazz.obj(), "size", "()I"); + + jint size = env->CallIntMethod(list, list_size); + + if (size == 0) { + return out; + } + + out.resize(size); + for (jint i = 0; i < size; ++i) { + ScopedJavaLocalRef java_string( + env, static_cast(env->CallObjectMethod(list, list_get, i))); + out[i] = JavaStringToString(env, java_string.obj()); + } + + return out; +} + ScopedJavaLocalRef VectorToStringArray( JNIEnv* env, const std::vector& vector) { diff --git a/engine/src/flutter/fml/platform/android/jni_util.h b/engine/src/flutter/fml/platform/android/jni_util.h index da056f627bc..0fb2be7c2fa 100644 --- a/engine/src/flutter/fml/platform/android/jni_util.h +++ b/engine/src/flutter/fml/platform/android/jni_util.h @@ -30,6 +30,8 @@ ScopedJavaLocalRef StringToJavaString(JNIEnv* env, std::vector StringArrayToVector(JNIEnv* env, jobjectArray jargs); +std::vector StringListToVector(JNIEnv* env, jobject list); + ScopedJavaLocalRef VectorToStringArray( JNIEnv* env, const std::vector& vector); diff --git a/engine/src/flutter/runtime/dart_isolate.cc b/engine/src/flutter/runtime/dart_isolate.cc index b1021f22a2c..ca2f384d120 100644 --- a/engine/src/flutter/runtime/dart_isolate.cc +++ b/engine/src/flutter/runtime/dart_isolate.cc @@ -93,6 +93,7 @@ std::weak_ptr DartIsolate::SpawnIsolate( const fml::closure& isolate_shutdown_callback, std::optional dart_entrypoint, std::optional dart_entrypoint_library, + const std::vector& dart_entrypoint_args, std::unique_ptr isolate_configuration) const { return CreateRunningRootIsolate( settings, // @@ -104,6 +105,7 @@ std::weak_ptr DartIsolate::SpawnIsolate( isolate_shutdown_callback, // dart_entrypoint, // dart_entrypoint_library, // + dart_entrypoint_args, // std::move(isolate_configuration), // UIDartState::Context{GetTaskRunners(), // snapshot_delegate, // @@ -128,6 +130,7 @@ std::weak_ptr DartIsolate::CreateRunningRootIsolate( const fml::closure& isolate_shutdown_callback, std::optional dart_entrypoint, std::optional dart_entrypoint_library, + const std::vector& dart_entrypoint_args, std::unique_ptr isolate_configuration, const UIDartState::Context& context, const DartIsolate* spawning_isolate) { @@ -195,10 +198,14 @@ std::weak_ptr DartIsolate::CreateRunningRootIsolate( root_isolate_create_callback(); } - if (!isolate->RunFromLibrary(dart_entrypoint_library, // - dart_entrypoint, // - settings.dart_entrypoint_args // - )) { + FML_DCHECK(dart_entrypoint_args.empty() || + settings.dart_entrypoint_args.empty()); + const std::vector& args = !dart_entrypoint_args.empty() + ? dart_entrypoint_args + : settings.dart_entrypoint_args; + if (!isolate->RunFromLibrary(dart_entrypoint_library, // + dart_entrypoint, // + args)) { FML_LOG(ERROR) << "Could not run the run main Dart entrypoint."; return {}; } diff --git a/engine/src/flutter/runtime/dart_isolate.h b/engine/src/flutter/runtime/dart_isolate.h index 8e81577f9a2..6c172d6b610 100644 --- a/engine/src/flutter/runtime/dart_isolate.h +++ b/engine/src/flutter/runtime/dart_isolate.h @@ -170,6 +170,8 @@ class DartIsolate : public UIDartState { /// function to invoke. /// @param[in] dart_entrypoint_library The name of the dart library /// containing the entrypoint. + /// @param[in] dart_entrypoint_args Arguments passed as a List + /// to Dart's entrypoint function. /// @param[in] isolate_configuration The isolate configuration used to /// configure the isolate before /// invoking the entrypoint. @@ -215,6 +217,7 @@ class DartIsolate : public UIDartState { const fml::closure& isolate_shutdown_callback, std::optional dart_entrypoint, std::optional dart_entrypoint_library, + const std::vector& dart_entrypoint_args, std::unique_ptr isolate_configuration, const UIDartState::Context& context, const DartIsolate* spawning_isolate = nullptr); @@ -245,6 +248,7 @@ class DartIsolate : public UIDartState { const fml::closure& isolate_shutdown_callback, std::optional dart_entrypoint, std::optional dart_entrypoint_library, + const std::vector& dart_entrypoint_args, std::unique_ptr isolate_configuration) const; // |UIDartState| diff --git a/engine/src/flutter/runtime/dart_isolate_unittests.cc b/engine/src/flutter/runtime/dart_isolate_unittests.cc index 8a60979197f..38686d88464 100644 --- a/engine/src/flutter/runtime/dart_isolate_unittests.cc +++ b/engine/src/flutter/runtime/dart_isolate_unittests.cc @@ -64,6 +64,7 @@ TEST_F(DartIsolateTest, RootIsolateCreationAndShutdown) { settings.isolate_shutdown_callback, // isolate shutdown callback "main", // dart entrypoint std::nullopt, // dart entrypoint library + {}, // dart entrypoint arguments std::move(isolate_configuration), // isolate configuration std::move(context) // engine context ); @@ -103,6 +104,7 @@ TEST_F(DartIsolateTest, SpawnIsolate) { settings.isolate_shutdown_callback, // isolate shutdown callback "main", // dart entrypoint std::nullopt, // dart entrypoint library + {}, // dart entrypoint arguments std::move(isolate_configuration), // isolate configuration std::move(context) // engine context ); @@ -123,6 +125,7 @@ TEST_F(DartIsolateTest, SpawnIsolate) { /*isolate_shutdown_callback=*/settings.isolate_shutdown_callback, /*dart_entrypoint=*/"main", /*dart_entrypoint_library=*/std::nullopt, + /*dart_entrypoint_args=*/{}, /*isolate_configuration=*/std::move(spawn_configuration)); auto spawn = weak_spawn.lock(); ASSERT_TRUE(spawn); @@ -177,6 +180,7 @@ TEST_F(DartIsolateTest, IsolateShutdownCallbackIsInIsolateScope) { settings.isolate_shutdown_callback, // isolate shutdown callback "main", // dart entrypoint std::nullopt, // dart entrypoint library + {}, // dart entrypoint arguments std::move(isolate_configuration), // isolate configuration std::move(context) // engine context ); @@ -400,6 +404,7 @@ TEST_F(DartIsolateTest, CanCreateServiceIsolate) { settings.isolate_shutdown_callback, // isolate shutdown callback "main", // dart entrypoint std::nullopt, // dart entrypoint library + {}, // dart entrypoint arguments std::move(isolate_configuration), // isolate configuration std::move(context) // engine context ); @@ -499,6 +504,7 @@ TEST_F(DartIsolateTest, InvalidLoadingUnitFails) { settings.isolate_shutdown_callback, // isolate shutdown callback "main", // dart entrypoint std::nullopt, // dart entrypoint library + {}, // dart entrypoint arguments std::move(isolate_configuration), // isolate configuration std::move(context) // engine context ); diff --git a/engine/src/flutter/runtime/dart_lifecycle_unittests.cc b/engine/src/flutter/runtime/dart_lifecycle_unittests.cc index 0992b729d80..6b3b4d2a362 100644 --- a/engine/src/flutter/runtime/dart_lifecycle_unittests.cc +++ b/engine/src/flutter/runtime/dart_lifecycle_unittests.cc @@ -69,6 +69,7 @@ static std::shared_ptr CreateAndRunRootIsolate( settings.isolate_shutdown_callback, // isolate shutdown callback, entrypoint, // dart entrypoint std::nullopt, // dart entrypoint library + {}, // dart entrypoint arguments std::move(isolate_configuration), // isolate configuration std::move(context) // engine context ) diff --git a/engine/src/flutter/runtime/runtime_controller.cc b/engine/src/flutter/runtime/runtime_controller.cc index 8976f145857..f7dfa4fbf8a 100644 --- a/engine/src/flutter/runtime/runtime_controller.cc +++ b/engine/src/flutter/runtime/runtime_controller.cc @@ -344,6 +344,7 @@ bool RuntimeController::LaunchRootIsolate( fml::closure root_isolate_create_callback, std::optional dart_entrypoint, std::optional dart_entrypoint_library, + const std::vector& dart_entrypoint_args, std::unique_ptr isolate_configuration) { if (root_isolate_.lock()) { FML_LOG(ERROR) << "Root isolate was already running."; @@ -361,6 +362,7 @@ bool RuntimeController::LaunchRootIsolate( isolate_shutdown_callback_, // dart_entrypoint, // dart_entrypoint_library, // + dart_entrypoint_args, // std::move(isolate_configuration), // context_, // spawning_isolate_.lock().get()) // diff --git a/engine/src/flutter/runtime/runtime_controller.h b/engine/src/flutter/runtime/runtime_controller.h index 3a8feeb1bd9..7b46139a528 100644 --- a/engine/src/flutter/runtime/runtime_controller.h +++ b/engine/src/flutter/runtime/runtime_controller.h @@ -132,6 +132,8 @@ class RuntimeController : public PlatformConfigurationClient { /// @param[in] dart_entrypoint_library The dart entrypoint library. If /// `std::nullopt` or empty, the core /// library will be attempted. + /// @param[in] dart_entrypoint_args Arguments passed as a List + /// to Dart's entrypoint function. /// @param[in] isolate_configuration The isolate configuration /// /// @return If the isolate could be launched and guided to the @@ -142,6 +144,7 @@ class RuntimeController : public PlatformConfigurationClient { fml::closure root_isolate_create_callback, std::optional dart_entrypoint, std::optional dart_entrypoint_library, + const std::vector& dart_entrypoint_args, std::unique_ptr isolate_configuration); //---------------------------------------------------------------------------- diff --git a/engine/src/flutter/shell/common/engine.cc b/engine/src/flutter/shell/common/engine.cc index 7618a58e7b0..13056e7755c 100644 --- a/engine/src/flutter/shell/common/engine.cc +++ b/engine/src/flutter/shell/common/engine.cc @@ -199,6 +199,10 @@ Engine::RunStatus Engine::Run(RunConfiguration configuration) { last_entry_point_ = configuration.GetEntrypoint(); last_entry_point_library_ = configuration.GetEntrypointLibrary(); +#if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG) + // This is only used to support restart. + last_entry_point_args_ = configuration.GetEntrypointArgs(); +#endif UpdateAssetManager(configuration.GetAssetManager()); @@ -220,6 +224,7 @@ Engine::RunStatus Engine::Run(RunConfiguration configuration) { root_isolate_create_callback, // configuration.GetEntrypoint(), // configuration.GetEntrypointLibrary(), // + configuration.GetEntrypointArgs(), // configuration.TakeIsolateConfiguration()) // ) { return RunStatus::Failure; @@ -562,6 +567,10 @@ const std::string& Engine::GetLastEntrypointLibrary() const { return last_entry_point_library_; } +const std::vector& Engine::GetLastEntrypointArgs() const { + return last_entry_point_args_; +} + // |RuntimeDelegate| void Engine::RequestDartDeferredLibrary(intptr_t loading_unit_id) { return delegate_.RequestDartDeferredLibrary(loading_unit_id); diff --git a/engine/src/flutter/shell/common/engine.h b/engine/src/flutter/shell/common/engine.h index 0dca6dc0fc0..b578bf07585 100644 --- a/engine/src/flutter/shell/common/engine.h +++ b/engine/src/flutter/shell/common/engine.h @@ -819,6 +819,13 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { /// const std::string& GetLastEntrypointLibrary() const; + //---------------------------------------------------------------------------- + /// @brief Get the last Entrypoint Arguments that was used in the + /// RunConfiguration when |Engine::Run| was called.This is only + /// valid in debug mode. + /// + const std::vector& GetLastEntrypointArgs() const; + //---------------------------------------------------------------------------- /// @brief Getter for the initial route. This can be set with a platform /// message. @@ -955,6 +962,7 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { std::string last_entry_point_; std::string last_entry_point_library_; + std::vector last_entry_point_args_; std::string initial_route_; ViewportMetrics viewport_metrics_; std::shared_ptr asset_manager_; diff --git a/engine/src/flutter/shell/common/fixtures/shell_test.dart b/engine/src/flutter/shell/common/fixtures/shell_test.dart index 981e184c65f..cd612828ef3 100644 --- a/engine/src/flutter/shell/common/fixtures/shell_test.dart +++ b/engine/src/flutter/shell/common/fixtures/shell_test.dart @@ -247,3 +247,17 @@ void canAccessResourceFromAssetDir() async { }, ); } + +void notifyNativeWhenEngineRun(bool success) native 'NotifyNativeWhenEngineRun'; + +void notifyNativeWhenEngineSpawn(bool success) native 'NotifyNativeWhenEngineSpawn'; + +@pragma('vm:entry-point') +void canRecieveArgumentsWhenEngineRun(List args) { + notifyNativeWhenEngineRun(args.length == 2 && args[0] == 'foo' && args[1] == 'bar'); +} + +@pragma('vm:entry-point') +void canRecieveArgumentsWhenEngineSpawn(List args) { + notifyNativeWhenEngineSpawn(args.length == 2 && args[0] == 'arg1' && args[1] == 'arg2'); +} diff --git a/engine/src/flutter/shell/common/run_configuration.cc b/engine/src/flutter/shell/common/run_configuration.cc index beace53a40e..0d905e2791e 100644 --- a/engine/src/flutter/shell/common/run_configuration.cc +++ b/engine/src/flutter/shell/common/run_configuration.cc @@ -77,6 +77,11 @@ void RunConfiguration::SetEntrypointAndLibrary(std::string entrypoint, entrypoint_library_ = std::move(library); } +void RunConfiguration::SetEntrypointArgs( + const std::vector& entrypoint_args) { + entrypoint_args_ = entrypoint_args; +} + std::shared_ptr RunConfiguration::GetAssetManager() const { return asset_manager_; } @@ -89,6 +94,10 @@ const std::string& RunConfiguration::GetEntrypointLibrary() const { return entrypoint_library_; } +const std::vector& RunConfiguration::GetEntrypointArgs() const { + return entrypoint_args_; +} + std::unique_ptr RunConfiguration::TakeIsolateConfiguration() { return std::move(isolate_configuration_); diff --git a/engine/src/flutter/shell/common/run_configuration.h b/engine/src/flutter/shell/common/run_configuration.h index 2a223efc1d9..ee680fd6aba 100644 --- a/engine/src/flutter/shell/common/run_configuration.h +++ b/engine/src/flutter/shell/common/run_configuration.h @@ -151,6 +151,12 @@ class RunConfiguration { /// void SetEntrypointAndLibrary(std::string entrypoint, std::string library); + //---------------------------------------------------------------------------- + /// @brief Updates the main application entrypoint arguments. + /// + /// @param[in] entrypoint_args The entrypoint arguments to use. + void SetEntrypointArgs(const std::vector& entrypoint_args); + //---------------------------------------------------------------------------- /// @return The asset manager referencing all previously registered asset /// resolvers. @@ -168,6 +174,12 @@ class RunConfiguration { /// const std::string& GetEntrypointLibrary() const; + //---------------------------------------------------------------------------- + /// @return Arguments passed as a List to Dart's entrypoint + /// function. + /// + const std::vector& GetEntrypointArgs() const; + //---------------------------------------------------------------------------- /// @brief The engine uses this to take the isolate configuration from /// the run configuration. The run configuration is no longer @@ -184,6 +196,7 @@ class RunConfiguration { std::shared_ptr asset_manager_; std::string entrypoint_ = "main"; std::string entrypoint_library_ = ""; + std::vector entrypoint_args_; FML_DISALLOW_COPY_AND_ASSIGN(RunConfiguration); }; diff --git a/engine/src/flutter/shell/common/shell.cc b/engine/src/flutter/shell/common/shell.cc index 08e8a450061..f7c91472fac 100644 --- a/engine/src/flutter/shell/common/shell.cc +++ b/engine/src/flutter/shell/common/shell.cc @@ -1556,6 +1556,7 @@ bool Shell::OnServiceProtocolRunInView( configuration.SetEntrypointAndLibrary(engine_->GetLastEntrypoint(), engine_->GetLastEntrypointLibrary()); + configuration.SetEntrypointArgs(engine_->GetLastEntrypointArgs()); configuration.AddAssetResolver(std::make_unique( fml::OpenDirectory(asset_directory_path.c_str(), false, diff --git a/engine/src/flutter/shell/common/shell_unittests.cc b/engine/src/flutter/shell/common/shell_unittests.cc index 5d11dd5fcb7..2aacb4b2f81 100644 --- a/engine/src/flutter/shell/common/shell_unittests.cc +++ b/engine/src/flutter/shell/common/shell_unittests.cc @@ -460,6 +460,39 @@ TEST_F(ShellTest, LastEntrypoint) { ASSERT_FALSE(DartVMRef::IsInstanceRunning()); } +TEST_F(ShellTest, LastEntrypointArgs) { + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); + auto settings = CreateSettingsForFixture(); + auto shell = CreateShell(settings); + ASSERT_TRUE(ValidateShell(shell.get())); + + auto configuration = RunConfiguration::InferFromSettings(settings); + ASSERT_TRUE(configuration.IsValid()); + std::string entry_point = "fixturesAreFunctionalMain"; + std::vector entry_point_args = {"arg1"}; + configuration.SetEntrypoint(entry_point); + configuration.SetEntrypointArgs(entry_point_args); + + fml::AutoResetWaitableEvent main_latch; + std::vector last_entry_point_args; + AddNativeCallback( + "SayHiFromFixturesAreFunctionalMain", CREATE_NATIVE_ENTRY([&](auto args) { + last_entry_point_args = shell->GetEngine()->GetLastEntrypointArgs(); + main_latch.Signal(); + })); + + RunEngine(shell.get(), std::move(configuration)); + main_latch.Wait(); +#if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG) + EXPECT_EQ(last_entry_point_args, entry_point_args); +#else + ASSERT_TRUE(last_entry_point_args.empty()); +#endif + ASSERT_TRUE(DartVMRef::IsInstanceRunning()); + DestroyShell(std::move(shell)); + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); +} + TEST_F(ShellTest, #if defined(WINUWP) // TODO(cbracken): https://github.com/flutter/flutter/issues/90481 @@ -2823,6 +2856,113 @@ TEST_F(ShellTest, Spawn) { ASSERT_FALSE(DartVMRef::IsInstanceRunning()); } +TEST_F(ShellTest, SpawnWithDartEntrypointArgs) { + auto settings = CreateSettingsForFixture(); + auto shell = CreateShell(settings); + ASSERT_TRUE(ValidateShell(shell.get())); + + auto configuration = RunConfiguration::InferFromSettings(settings); + ASSERT_TRUE(configuration.IsValid()); + configuration.SetEntrypoint("canRecieveArgumentsWhenEngineRun"); + const std::vector entrypoint_args{"foo", "bar"}; + configuration.SetEntrypointArgs(entrypoint_args); + + auto second_configuration = RunConfiguration::InferFromSettings(settings); + ASSERT_TRUE(second_configuration.IsValid()); + second_configuration.SetEntrypoint("canRecieveArgumentsWhenEngineSpawn"); + const std::vector second_entrypoint_args{"arg1", "arg2"}; + second_configuration.SetEntrypointArgs(second_entrypoint_args); + + const std::string initial_route("/foo"); + + fml::AutoResetWaitableEvent main_latch; + std::string last_entry_point; + // Fulfill native function for the first Shell's entrypoint. + AddNativeCallback("NotifyNativeWhenEngineRun", + CREATE_NATIVE_ENTRY(([&](Dart_NativeArguments args) { + ASSERT_TRUE(tonic::DartConverter::FromDart( + Dart_GetNativeArgument(args, 0))); + last_entry_point = + shell->GetEngine()->GetLastEntrypoint(); + main_latch.Signal(); + }))); + + fml::AutoResetWaitableEvent second_latch; + // Fulfill native function for the second Shell's entrypoint. + AddNativeCallback("NotifyNativeWhenEngineSpawn", + CREATE_NATIVE_ENTRY(([&](Dart_NativeArguments args) { + ASSERT_TRUE(tonic::DartConverter::FromDart( + Dart_GetNativeArgument(args, 0))); + last_entry_point = + shell->GetEngine()->GetLastEntrypoint(); + second_latch.Signal(); + }))); + + RunEngine(shell.get(), std::move(configuration)); + main_latch.Wait(); + ASSERT_TRUE(DartVMRef::IsInstanceRunning()); + // Check first Shell ran the first entrypoint. + ASSERT_EQ("canRecieveArgumentsWhenEngineRun", last_entry_point); + + PostSync( + shell->GetTaskRunners().GetPlatformTaskRunner(), + [this, &spawner = shell, &second_configuration, &second_latch, + initial_route]() { + MockPlatformViewDelegate platform_view_delegate; + auto spawn = spawner->Spawn( + std::move(second_configuration), initial_route, + [&platform_view_delegate](Shell& shell) { + auto result = std::make_unique( + platform_view_delegate, shell.GetTaskRunners()); + ON_CALL(*result, CreateRenderingSurface()) + .WillByDefault(::testing::Invoke( + [] { return std::make_unique(); })); + return result; + }, + [](Shell& shell) { return std::make_unique(shell); }); + ASSERT_NE(nullptr, spawn.get()); + ASSERT_TRUE(ValidateShell(spawn.get())); + + PostSync(spawner->GetTaskRunners().GetUITaskRunner(), + [&spawn, &spawner, initial_route] { + // Check second shell ran the second entrypoint. + ASSERT_EQ("canRecieveArgumentsWhenEngineSpawn", + spawn->GetEngine()->GetLastEntrypoint()); + ASSERT_EQ(initial_route, spawn->GetEngine()->InitialRoute()); + + // TODO(74520): Remove conditional once isolate groups are + // supported by JIT. + if (DartVM::IsRunningPrecompiledCode()) { + ASSERT_NE(spawner->GetEngine() + ->GetRuntimeController() + ->GetRootIsolateGroup(), + 0u); + ASSERT_EQ(spawner->GetEngine() + ->GetRuntimeController() + ->GetRootIsolateGroup(), + spawn->GetEngine() + ->GetRuntimeController() + ->GetRootIsolateGroup()); + } + }); + + PostSync( + spawner->GetTaskRunners().GetIOTaskRunner(), [&spawner, &spawn] { + ASSERT_EQ(spawner->GetIOManager()->GetResourceContext().get(), + spawn->GetIOManager()->GetResourceContext().get()); + }); + + // Before destroying the shell, wait for expectations of the spawned + // isolate to be met. + second_latch.Wait(); + + DestroyShell(std::move(spawn)); + }); + + DestroyShell(std::move(shell)); + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); +} + TEST_F(ShellTest, UpdateAssetResolverByTypeReplaces) { ASSERT_FALSE(DartVMRef::IsInstanceRunning()); Settings settings = CreateSettingsForFixture(); diff --git a/engine/src/flutter/shell/platform/android/android_shell_holder.cc b/engine/src/flutter/shell/platform/android/android_shell_holder.cc index e9b287e734d..1634f55b0de 100644 --- a/engine/src/flutter/shell/platform/android/android_shell_holder.cc +++ b/engine/src/flutter/shell/platform/android/android_shell_holder.cc @@ -178,7 +178,8 @@ std::unique_ptr AndroidShellHolder::Spawn( std::shared_ptr jni_facade, const std::string& entrypoint, const std::string& libraryUrl, - const std::string& initial_route) const { + const std::string& initial_route, + const std::vector& entrypoint_args) const { FML_DCHECK(shell_ && shell_->IsSetup()) << "A new Shell can only be spawned " "if the current Shell is properly constructed"; @@ -225,7 +226,8 @@ std::unique_ptr AndroidShellHolder::Spawn( // TODO(xster): could be worth tracing this to investigate whether // the IsolateConfiguration could be cached somewhere. - auto config = BuildRunConfiguration(asset_manager_, entrypoint, libraryUrl); + auto config = BuildRunConfiguration(asset_manager_, entrypoint, libraryUrl, + entrypoint_args); if (!config) { // If the RunConfiguration was null, the kernel blob wasn't readable. // Fail the whole thing. @@ -241,15 +243,18 @@ std::unique_ptr AndroidShellHolder::Spawn( std::move(shell), weak_platform_view)); } -void AndroidShellHolder::Launch(std::shared_ptr asset_manager, - const std::string& entrypoint, - const std::string& libraryUrl) { +void AndroidShellHolder::Launch( + std::shared_ptr asset_manager, + const std::string& entrypoint, + const std::string& libraryUrl, + const std::vector& entrypoint_args) { if (!IsValid()) { return; } asset_manager_ = asset_manager; - auto config = BuildRunConfiguration(asset_manager, entrypoint, libraryUrl); + auto config = BuildRunConfiguration(asset_manager, entrypoint, libraryUrl, + entrypoint_args); if (!config) { return; } @@ -278,7 +283,8 @@ void AndroidShellHolder::NotifyLowMemoryWarning() { std::optional AndroidShellHolder::BuildRunConfiguration( std::shared_ptr asset_manager, const std::string& entrypoint, - const std::string& libraryUrl) const { + const std::string& libraryUrl, + const std::vector& entrypoint_args) const { std::unique_ptr isolate_configuration; if (flutter::DartVM::IsRunningPrecompiledCode()) { isolate_configuration = IsolateConfiguration::CreateForAppSnapshot(); @@ -304,6 +310,9 @@ std::optional AndroidShellHolder::BuildRunConfiguration( } else if (entrypoint.size() > 0) { config.SetEntrypoint(std::move(entrypoint)); } + if (entrypoint_args.size() > 0) { + config.SetEntrypointArgs(entrypoint_args); + } } return config; } diff --git a/engine/src/flutter/shell/platform/android/android_shell_holder.h b/engine/src/flutter/shell/platform/android/android_shell_holder.h index 062e7e45c4c..c8c52602677 100644 --- a/engine/src/flutter/shell/platform/android/android_shell_holder.h +++ b/engine/src/flutter/shell/platform/android/android_shell_holder.h @@ -80,11 +80,13 @@ class AndroidShellHolder { std::shared_ptr jni_facade, const std::string& entrypoint, const std::string& libraryUrl, - const std::string& initial_route) const; + const std::string& initial_route, + const std::vector& entrypoint_args) const; void Launch(std::shared_ptr asset_manager, const std::string& entrypoint, - const std::string& libraryUrl); + const std::string& libraryUrl, + const std::vector& entrypoint_args); const flutter::Settings& GetSettings() const; @@ -132,7 +134,8 @@ class AndroidShellHolder { std::optional BuildRunConfiguration( std::shared_ptr asset_manager, const std::string& entrypoint, - const std::string& libraryUrl) const; + const std::string& libraryUrl, + const std::vector& entrypoint_args) const; bool IsNDKImageDecoderAvailable(); diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 828bb945868..ff712d4edcc 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -38,6 +38,7 @@ import io.flutter.embedding.engine.systemchannels.TextInputChannel; import io.flutter.plugin.localization.LocalizationPlugin; import io.flutter.plugin.platform.PlatformViewsController; import java.util.HashSet; +import java.util.List; import java.util.Set; /** @@ -380,13 +381,17 @@ public class FlutterEngine { * @param dartEntrypoint specifies the {@link DartEntrypoint} the new engine should run. It * doesn't need to be the same entrypoint as the current engine but must be built in the same * AOT or snapshot. + * @param initialRoute The name of the initial Flutter `Navigator` `Route` to load. If this is + * null, it will default to the "/" route. + * @param dartEntrypointArgs Arguments passed as a list of string to Dart's entrypoint function. * @return a new {@link io.flutter.embedding.engine.FlutterEngine}. */ @NonNull /*package*/ FlutterEngine spawn( @NonNull Context context, @NonNull DartEntrypoint dartEntrypoint, - @Nullable String initialRoute) { + @Nullable String initialRoute, + @Nullable List dartEntrypointArgs) { if (!isAttachedToJni()) { throw new IllegalStateException( "Spawn can only be called on a fully constructed FlutterEngine"); @@ -396,7 +401,8 @@ public class FlutterEngine { flutterJNI.spawn( dartEntrypoint.dartEntrypointFunctionName, dartEntrypoint.dartEntrypointLibrary, - initialRoute); + initialRoute, + dartEntrypointArgs); return new FlutterEngine( context, // Context. null, // FlutterLoader. A null value passed here causes the constructor to get it from the diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java index 4dd8f43922f..bb1a9ba2995 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java @@ -117,8 +117,32 @@ public class FlutterEngineGroup { @NonNull Context context, @Nullable DartEntrypoint dartEntrypoint, @Nullable String initialRoute) { + return createAndRunEngine( + new Options(context).setDartEntrypoint(dartEntrypoint).setInitialRoute(initialRoute)); + } + + /** + * Creates a {@link io.flutter.embedding.engine.FlutterEngine} in this group and run its {@link + * io.flutter.embedding.engine.dart.DartExecutor} with the specified {@link DartEntrypoint}, the + * specified {@code initialRoute} and the {@code dartEntrypointArgs}. + * + *

If no prior {@link io.flutter.embedding.engine.FlutterEngine} were created in this group, + * the initialization cost will be slightly higher than subsequent engines. The very first {@link + * io.flutter.embedding.engine.FlutterEngine} created per program, regardless of + * FlutterEngineGroup, also incurs the Dart VM creation time. + * + *

Subsequent engine creations will share resources with existing engines. However, if all + * existing engines were {@link io.flutter.embedding.engine.FlutterEngine#destroy()}ed, the next + * engine created will recreate its dependencies. + */ + public FlutterEngine createAndRunEngine(@NonNull Options options) { FlutterEngine engine = null; + Context context = options.getContext(); + DartEntrypoint dartEntrypoint = options.getDartEntrypoint(); + String initialRoute = options.getInitialRoute(); + List dartEntrypointArgs = options.getDartEntrypointArgs(); + if (dartEntrypoint == null) { dartEntrypoint = DartEntrypoint.createDefault(); } @@ -128,9 +152,10 @@ public class FlutterEngineGroup { if (initialRoute != null) { engine.getNavigationChannel().setInitialRoute(initialRoute); } - engine.getDartExecutor().executeDartEntrypoint(dartEntrypoint); + engine.getDartExecutor().executeDartEntrypoint(dartEntrypoint, dartEntrypointArgs); } else { - engine = activeEngines.get(0).spawn(context, dartEntrypoint, initialRoute); + engine = + activeEngines.get(0).spawn(context, dartEntrypoint, initialRoute, dartEntrypointArgs); } activeEngines.add(engine); @@ -156,4 +181,75 @@ public class FlutterEngineGroup { /* package */ FlutterEngine createEngine(Context context) { return new FlutterEngine(context); } + + /** Options that control how a FlutterEngine should be created. */ + public static class Options { + @NonNull private Context context; + @Nullable private DartEntrypoint dartEntrypoint; + @Nullable private String initialRoute; + @Nullable private List dartEntrypointArgs; + + public Options(@NonNull Context context) { + this.context = context; + } + + public Context getContext() { + return context; + } + + /** + * dartEntrypoint specifies the {@link DartEntrypoint} the new engine should run. It doesn't + * need to be the same entrypoint as the current engine but must be built in the same AOT or + * snapshot. + */ + public DartEntrypoint getDartEntrypoint() { + return dartEntrypoint; + } + + /** + * The name of the initial Flutter `Navigator` `Route` to load. If this is null, it will default + * to the "/" route. + */ + public String getInitialRoute() { + return initialRoute; + } + + /** Arguments passed as a list of string to Dart's entrypoint function. */ + public List getDartEntrypointArgs() { + return dartEntrypointArgs; + } + + /** + * Setter for `dartEntrypoint` property. + * + * @param dartEntrypoint specifies the {@link DartEntrypoint} the new engine should run. It + * doesn't need to be the same entrypoint as the current engine but must be built in the + * same AOT or snapshot. + */ + public Options setDartEntrypoint(DartEntrypoint dartEntrypoint) { + this.dartEntrypoint = dartEntrypoint; + return this; + } + + /** + * Setter for `initialRoute` property. + * + * @param initialRoute The name of the initial Flutter `Navigator` `Route` to load. If this is + * null, it will default to the "/" route. + */ + public Options setInitialRoute(String initialRoute) { + this.initialRoute = initialRoute; + return this; + } + + /** + * Setter for `dartEntrypointArgs` property. + * + * @param dartEntrypointArgs Arguments passed as a list of string to Dart's entrypoint function. + */ + public Options setDartEntrypointArgs(List dartEntrypointArgs) { + this.dartEntrypointArgs = dartEntrypointArgs; + return this; + } + } } diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 24114ed3a42..5b6c6579de6 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -371,12 +371,17 @@ public class FlutterJNI { public FlutterJNI spawn( @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction, - @Nullable String initialRoute) { + @Nullable String initialRoute, + @Nullable List entrypointArgs) { ensureRunningOnMainThread(); ensureAttachedToNative(); FlutterJNI spawnedJNI = nativeSpawn( - nativeShellHolderId, entrypointFunctionName, pathToEntrypointFunction, initialRoute); + nativeShellHolderId, + entrypointFunctionName, + pathToEntrypointFunction, + initialRoute, + entrypointArgs); Preconditions.checkState( spawnedJNI.nativeShellHolderId != null && spawnedJNI.nativeShellHolderId != 0, "Failed to spawn new JNI connected shell from existing shell."); @@ -388,7 +393,8 @@ public class FlutterJNI { long nativeSpawningShellId, @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction, - @Nullable String initialRoute); + @Nullable String initialRoute, + @Nullable List entrypointArgs); /** * Detaches this {@code FlutterJNI} instance from Flutter's native engine, which precludes any @@ -397,8 +403,8 @@ public class FlutterJNI { *

This method must not be invoked if {@code FlutterJNI} is not already attached to native. * *

Invoking this method will result in the release of all native-side resources that were set - * up during {@link #attachToNative()} or {@link #spawn(String, String, String)}, or accumulated - * thereafter. + * up during {@link #attachToNative()} or {@link #spawn(String, String, String, List)}, or + * accumulated thereafter. * *

It is permissible to re-attach this instance to native after detaching it from native. */ @@ -850,7 +856,8 @@ public class FlutterJNI { @NonNull String bundlePath, @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction, - @NonNull AssetManager assetManager) { + @NonNull AssetManager assetManager, + @Nullable List entrypointArgs) { ensureRunningOnMainThread(); ensureAttachedToNative(); nativeRunBundleAndSnapshotFromLibrary( @@ -858,7 +865,8 @@ public class FlutterJNI { bundlePath, entrypointFunctionName, pathToEntrypointFunction, - assetManager); + assetManager, + entrypointArgs); } private native void nativeRunBundleAndSnapshotFromLibrary( @@ -866,7 +874,8 @@ public class FlutterJNI { @NonNull String bundlePath, @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction, - @NonNull AssetManager manager); + @NonNull AssetManager manager, + @Nullable List entrypointArgs); // ------ End Dart Execution Support ------- // --------- Start Platform Message Support ------ diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java index 9baa319fa5f..533a037fbc5 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java @@ -17,6 +17,7 @@ import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.StringCodec; import io.flutter.view.FlutterCallbackInformation; import java.nio.ByteBuffer; +import java.util.List; /** * Configures, bootstraps, and starts executing Dart code. @@ -121,6 +122,20 @@ public class DartExecutor implements BinaryMessenger { * @param dartEntrypoint specifies which Dart function to run, and where to find it */ public void executeDartEntrypoint(@NonNull DartEntrypoint dartEntrypoint) { + executeDartEntrypoint(dartEntrypoint, null); + } + + /** + * Starts executing Dart code based on the given {@code dartEntrypoint} and the {@code + * dartEntrypointArgs}. + * + *

See {@link DartEntrypoint} for configuration options. + * + * @param dartEntrypoint specifies which Dart function to run, and where to find it + * @param dartEntrypointArgs Arguments passed as a list of string to Dart's entrypoint function. + */ + public void executeDartEntrypoint( + @NonNull DartEntrypoint dartEntrypoint, @Nullable List dartEntrypointArgs) { if (isApplicationRunning) { Log.w(TAG, "Attempted to run a DartExecutor that is already running."); return; @@ -134,7 +149,8 @@ public class DartExecutor implements BinaryMessenger { dartEntrypoint.pathToBundle, dartEntrypoint.dartEntrypointFunctionName, dartEntrypoint.dartEntrypointLibrary, - assetManager); + assetManager, + dartEntrypointArgs); isApplicationRunning = true; } finally { @@ -163,7 +179,8 @@ public class DartExecutor implements BinaryMessenger { dartCallback.pathToBundle, dartCallback.callbackHandle.callbackName, dartCallback.callbackHandle.callbackLibraryPath, - dartCallback.androidAssetManager); + dartCallback.androidAssetManager, + null); isApplicationRunning = true; } finally { diff --git a/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterNativeView.java b/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterNativeView.java index d82d1f645cc..190a9ecbb16 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterNativeView.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterNativeView.java @@ -111,7 +111,11 @@ public class FlutterNativeView implements BinaryMessenger { if (applicationIsRunning) throw new AssertionError("This Flutter engine instance is already running an application"); mFlutterJNI.runBundleAndSnapshotFromLibrary( - args.bundlePath, args.entrypoint, args.libraryPath, mContext.getResources().getAssets()); + args.bundlePath, + args.entrypoint, + args.libraryPath, + mContext.getResources().getAssets(), + null); applicationIsRunning = true; } diff --git a/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc b/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc index 8b8494b3d3a..454969b2172 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc +++ b/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc @@ -155,7 +155,8 @@ static jobject SpawnJNI(JNIEnv* env, jlong shell_holder, jstring jEntrypoint, jstring jLibraryUrl, - jstring jInitialRoute) { + jstring jInitialRoute, + jobject jEntrypointArgs) { jobject jni = env->NewObject(g_flutter_jni_class->obj(), g_jni_constructor); if (jni == nullptr) { FML_LOG(ERROR) << "Could not create a FlutterJNI instance"; @@ -169,9 +170,10 @@ static jobject SpawnJNI(JNIEnv* env, auto entrypoint = fml::jni::JavaStringToString(env, jEntrypoint); auto libraryUrl = fml::jni::JavaStringToString(env, jLibraryUrl); auto initial_route = fml::jni::JavaStringToString(env, jInitialRoute); + auto entrypoint_args = fml::jni::StringListToVector(env, jEntrypointArgs); auto spawned_shell_holder = ANDROID_SHELL_HOLDER->Spawn( - jni_facade, entrypoint, libraryUrl, initial_route); + jni_facade, entrypoint, libraryUrl, initial_route, entrypoint_args); if (spawned_shell_holder == nullptr || !spawned_shell_holder->IsValid()) { FML_LOG(ERROR) << "Could not spawn Shell"; @@ -237,7 +239,8 @@ static void RunBundleAndSnapshotFromLibrary(JNIEnv* env, jstring jBundlePath, jstring jEntrypoint, jstring jLibraryUrl, - jobject jAssetManager) { + jobject jAssetManager, + jobject jEntrypointArgs) { auto asset_manager = std::make_shared(); asset_manager->PushBack(std::make_unique( @@ -248,8 +251,10 @@ static void RunBundleAndSnapshotFromLibrary(JNIEnv* env, auto entrypoint = fml::jni::JavaStringToString(env, jEntrypoint); auto libraryUrl = fml::jni::JavaStringToString(env, jLibraryUrl); + auto entrypoint_args = fml::jni::StringListToVector(env, jEntrypointArgs); - ANDROID_SHELL_HOLDER->Launch(asset_manager, entrypoint, libraryUrl); + ANDROID_SHELL_HOLDER->Launch(asset_manager, entrypoint, libraryUrl, + entrypoint_args); } static jobject LookupCallbackInformation(JNIEnv* env, @@ -629,14 +634,15 @@ bool RegisterApi(JNIEnv* env) { { .name = "nativeSpawn", .signature = "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/" - "String;)Lio/flutter/" + "String;Ljava/util/List;)Lio/flutter/" "embedding/engine/FlutterJNI;", .fnPtr = reinterpret_cast(&SpawnJNI), }, { .name = "nativeRunBundleAndSnapshotFromLibrary", .signature = "(JLjava/lang/String;Ljava/lang/String;" - "Ljava/lang/String;Landroid/content/res/AssetManager;)V", + "Ljava/lang/String;Landroid/content/res/" + "AssetManager;Ljava/util/List;)V", .fnPtr = reinterpret_cast(&RunBundleAndSnapshotFromLibrary), }, { diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java index e836560e009..aa6ffb00ab7 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java @@ -24,6 +24,8 @@ import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.systemchannels.NavigationChannel; import io.flutter.plugins.GeneratedPluginRegistrant; +import java.util.ArrayList; +import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -121,7 +123,11 @@ public class FlutterEngineGroupComponentTest { doReturn(mock(FlutterEngine.class)) .when(firstEngine) - .spawn(any(Context.class), any(DartEntrypoint.class), nullable(String.class)); + .spawn( + any(Context.class), + any(DartEntrypoint.class), + nullable(String.class), + nullable(List.class)); FlutterEngine secondEngine = engineGroupUnderTest.createAndRunEngine( @@ -133,7 +139,11 @@ public class FlutterEngineGroupComponentTest { // Now the second spawned engine is the only one left and it will be called to spawn the next // engine in the chain. - when(secondEngine.spawn(any(Context.class), any(DartEntrypoint.class), nullable(String.class))) + when(secondEngine.spawn( + any(Context.class), + any(DartEntrypoint.class), + nullable(String.class), + nullable(List.class))) .thenReturn(mock(FlutterEngine.class)); FlutterEngine thirdEngine = @@ -156,7 +166,8 @@ public class FlutterEngineGroupComponentTest { eq("some/path/to/flutter_assets"), eq("other entrypoint"), isNull(String.class), - any(AssetManager.class)); + any(AssetManager.class), + nullable(List.class)); } @Test @@ -176,7 +187,11 @@ public class FlutterEngineGroupComponentTest { doAnswer(invocation -> jniAttached = true).when(secondMockflutterJNI).attachToNative(); doReturn(secondMockflutterJNI) .when(mockflutterJNI) - .spawn(nullable(String.class), nullable(String.class), nullable(String.class)); + .spawn( + nullable(String.class), + nullable(String.class), + nullable(String.class), + nullable(List.class)); FlutterEngine secondEngine = engineGroupUnderTest.createAndRunEngine( @@ -184,6 +199,51 @@ public class FlutterEngineGroupComponentTest { assertEquals(2, engineGroupUnderTest.activeEngines.size()); verify(mockflutterJNI, times(1)) - .spawn(nullable(String.class), nullable(String.class), eq("/bar")); + .spawn(nullable(String.class), nullable(String.class), eq("/bar"), nullable(List.class)); + } + + @Test + public void canCreateAndRunWithCustomEntrypointArgs() { + List firstDartEntrypointArgs = new ArrayList(); + FlutterEngine firstEngine = + engineGroupUnderTest.createAndRunEngine( + new FlutterEngineGroup.Options(RuntimeEnvironment.application) + .setDartEntrypoint(mock(DartEntrypoint.class)) + .setDartEntrypointArgs(firstDartEntrypointArgs)); + assertEquals(1, engineGroupUnderTest.activeEngines.size()); + verify(mockflutterJNI, times(1)) + .runBundleAndSnapshotFromLibrary( + nullable(String.class), + nullable(String.class), + isNull(String.class), + any(AssetManager.class), + eq(firstDartEntrypointArgs)); + + when(mockflutterJNI.isAttached()).thenReturn(true); + jniAttached = false; + FlutterJNI secondMockflutterJNI = mock(FlutterJNI.class); + when(secondMockflutterJNI.isAttached()).thenAnswer(invocation -> jniAttached); + doAnswer(invocation -> jniAttached = true).when(secondMockflutterJNI).attachToNative(); + doReturn(secondMockflutterJNI) + .when(mockflutterJNI) + .spawn( + nullable(String.class), + nullable(String.class), + nullable(String.class), + nullable(List.class)); + List secondDartEntrypointArgs = new ArrayList(); + FlutterEngine secondEngine = + engineGroupUnderTest.createAndRunEngine( + new FlutterEngineGroup.Options(RuntimeEnvironment.application) + .setDartEntrypoint(mock(DartEntrypoint.class)) + .setDartEntrypointArgs(secondDartEntrypointArgs)); + + assertEquals(2, engineGroupUnderTest.activeEngines.size()); + verify(mockflutterJNI, times(1)) + .spawn( + nullable(String.class), + nullable(String.class), + nullable(String.class), + eq(secondDartEntrypointArgs)); } } diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h index 48f9d4bf567..ea10495c614 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h @@ -246,6 +246,29 @@ FLUTTER_DARWIN_EXPORT libraryURI:(nullable NSString*)libraryURI initialRoute:(nullable NSString*)initialRoute; +/** + * Runs a Dart program on an Isolate using the specified entrypoint and Dart library, + * which may not be the same as the library containing the Dart program's `main()` function. + * + * The first call to this method will create a new Isolate. Subsequent calls will return + * immediately and have no effect. + * + * @param entrypoint The name of a top-level function from a Dart library. If this is + * FlutterDefaultDartEntrypoint (or nil); this will default to `main()`. If it is not the app's + * main() function, that function must be decorated with `@pragma(vm:entry-point)` to ensure the + * method is not tree-shaken by the Dart compiler. + * @param libraryURI The URI of the Dart library which contains the entrypoint method. IF nil, + * this will default to the same library as the `main()` function in the Dart program. + * @param initialRoute The name of the initial Flutter `Navigator` `Route` to load. If this is + * FlutterDefaultInitialRoute (or nil), it will default to the "/" route. + * @param entrypointArgs Arguments passed as a list of string to Dart's entrypoint function. + * @return YES if the call succeeds in creating and running a Flutter Engine instance; NO otherwise. + */ +- (BOOL)runWithEntrypoint:(nullable NSString*)entrypoint + libraryURI:(nullable NSString*)libraryURI + initialRoute:(nullable NSString*)initialRoute + entrypointArgs:(nullable NSArray*)entrypointArgs; + /** * Destroy running context for an engine. * diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h index 7bf295ddbb1..097ffd6669f 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h @@ -8,6 +8,36 @@ NS_ASSUME_NONNULL_BEGIN +/** Options that control how a FlutterEngine should be created. */ +FLUTTER_DARWIN_EXPORT +@interface FlutterEngineGroupOptions : NSObject + +/** + * The name of a top-level function from a Dart library. If this is FlutterDefaultDartEntrypoint + * (or nil); this will default to `main()`. If it is not the app's main() function, that function + * must be decorated with `@pragma(vm:entry-point)` to ensure themethod is not tree-shaken by the + * Dart compiler. + */ +@property(nonatomic, copy, nullable) NSString* entrypoint; + +/** + * The URI of the Dart library which contains the entrypoint method. If nil, this will default to + * the same library as the `main()` function in the Dart program. + */ +@property(nonatomic, copy, nullable) NSString* libraryURI; + +/** + * The name of the initial Flutter `Navigator` `Route` to load. If this is + * FlutterDefaultInitialRoute (or nil), it will default to the "/" route. + */ +@property(nonatomic, copy, nullable) NSString* initialRoute; + +/** + * Arguments passed as a list of string to Dart's entrypoint function. + */ +@property(nonatomic, retain, nullable) NSArray* entrypointArgs; +@end + /** * Represents a collection of FlutterEngines who share resources which allows * them to be created with less time const and occupy less memory than just @@ -66,6 +96,15 @@ FLUTTER_DARWIN_EXPORT - (FlutterEngine*)makeEngineWithEntrypoint:(nullable NSString*)entrypoint libraryURI:(nullable NSString*)libraryURI initialRoute:(nullable NSString*)initialRoute; + +/** + * Creates a running `FlutterEngine` that shares components with this group. + * + * @param options Options that control how a FlutterEngine should be created. + * + * @see FlutterEngineGroupOptions + */ +- (FlutterEngine*)makeEngineWithOptions:(nullable FlutterEngineGroupOptions*)options; @end NS_ASSUME_NONNULL_END diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm index dbf656a38f5..f9069751edc 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm @@ -240,6 +240,15 @@ flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle) { - (flutter::RunConfiguration)runConfigurationForEntrypoint:(nullable NSString*)entrypointOrNil libraryOrNil:(nullable NSString*)dartLibraryOrNil { + return [self runConfigurationForEntrypoint:entrypointOrNil + libraryOrNil:dartLibraryOrNil + entrypointArgs:nil]; +} + +- (flutter::RunConfiguration)runConfigurationForEntrypoint:(nullable NSString*)entrypointOrNil + libraryOrNil:(nullable NSString*)dartLibraryOrNil + entrypointArgs: + (nullable NSArray*)entrypointArgs { auto config = flutter::RunConfiguration::InferFromSettings(_settings); if (dartLibraryOrNil && entrypointOrNil) { config.SetEntrypointAndLibrary(std::string([entrypointOrNil UTF8String]), @@ -248,6 +257,15 @@ flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle) { } else if (entrypointOrNil) { config.SetEntrypoint(std::string([entrypointOrNil UTF8String])); } + + if (entrypointArgs.count) { + std::vector cppEntrypointArgs; + for (NSString* arg in entrypointArgs) { + cppEntrypointArgs.push_back(std::string([arg UTF8String])); + } + config.SetEntrypointArgs(cppEntrypointArgs); + } + return config; } diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h index 1efdf758802..74203864f42 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h @@ -27,6 +27,10 @@ flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle = nil); - (flutter::RunConfiguration)runConfigurationForEntrypoint:(nullable NSString*)entrypointOrNil; - (flutter::RunConfiguration)runConfigurationForEntrypoint:(nullable NSString*)entrypointOrNil libraryOrNil:(nullable NSString*)dartLibraryOrNil; +- (flutter::RunConfiguration)runConfigurationForEntrypoint:(nullable NSString*)entrypointOrNil + libraryOrNil:(nullable NSString*)dartLibraryOrNil + entrypointArgs: + (nullable NSArray*)entrypointArgs; + (NSString*)flutterAssetsName:(NSBundle*)bundle; + (NSString*)domainNetworkPolicy:(NSDictionary*)appTransportSecurity; @@ -35,7 +39,7 @@ flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle = nil); /** * The embedder can specify data that the isolate can request synchronously on launch. Engines * launched using this configuration can access the persistent isolate data via the - * `Window.getPersistentIsolateData` accessor. + * `PlatformDispatcher.getPersistentIsolateData` accessor. * * @param data The persistent isolate data. This data is persistent for the duration of the Flutter * application and is available even after isolate restarts. Because of this lifecycle, diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index c3eeb47ab8f..03fe0a06e8e 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -569,10 +569,13 @@ static constexpr int kNumProfilerSamplesPerSec = 5; return self.shell.Screenshot(type, base64Encode); } -- (void)launchEngine:(NSString*)entrypoint libraryURI:(NSString*)libraryOrNil { +- (void)launchEngine:(NSString*)entrypoint + libraryURI:(NSString*)libraryOrNil + entrypointArgs:(NSArray*)entrypointArgs { // Launch the Dart application with the inferred run configuration. self.shell.RunEngine([_dartProject.get() runConfigurationForEntrypoint:entrypoint - libraryOrNil:libraryOrNil]); + libraryOrNil:libraryOrNil + entrypointArgs:entrypointArgs]); } - (void)setupShell:(std::unique_ptr)shell @@ -725,8 +728,18 @@ static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSS - (BOOL)runWithEntrypoint:(NSString*)entrypoint libraryURI:(NSString*)libraryURI initialRoute:(NSString*)initialRoute { + return [self runWithEntrypoint:entrypoint + libraryURI:libraryURI + initialRoute:initialRoute + entrypointArgs:nil]; +} + +- (BOOL)runWithEntrypoint:(NSString*)entrypoint + libraryURI:(NSString*)libraryURI + initialRoute:(NSString*)initialRoute + entrypointArgs:(NSArray*)entrypointArgs { if ([self createShell:entrypoint libraryURI:libraryURI initialRoute:initialRoute]) { - [self launchEngine:entrypoint libraryURI:libraryURI]; + [self launchEngine:entrypoint libraryURI:libraryURI entrypointArgs:entrypointArgs]; } return _shell != nullptr; @@ -1073,14 +1086,16 @@ static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSS - (FlutterEngine*)spawnWithEntrypoint:(/*nullable*/ NSString*)entrypoint libraryURI:(/*nullable*/ NSString*)libraryURI - initialRoute:(/*nullable*/ NSString*)initialRoute { + initialRoute:(/*nullable*/ NSString*)initialRoute + entrypointArgs:(/*nullable*/ NSArray*)entrypointArgs { NSAssert(_shell, @"Spawning from an engine without a shell (possibly not run)."); FlutterEngine* result = [[FlutterEngine alloc] initWithName:_labelPrefix project:_dartProject.get() allowHeadlessExecution:_allowHeadlessExecution]; - flutter::RunConfiguration configuration = - [_dartProject.get() runConfigurationForEntrypoint:entrypoint libraryOrNil:libraryURI]; + [_dartProject.get() runConfigurationForEntrypoint:entrypoint + libraryOrNil:libraryURI + entrypointArgs:entrypointArgs]; fml::WeakPtr platform_view = _shell->GetPlatformView(); FML_DCHECK(platform_view); @@ -1115,7 +1130,7 @@ static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSS result->_profiler_metrics = _profiler_metrics; result->_isGpuDisabled = _isGpuDisabled; [result setupShell:std::move(shell) withObservatoryPublication:NO]; - return result; + return [result autorelease]; } - (const flutter::ThreadHost&)threadHost { diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm index 741c98f6055..5c02f8ad588 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineGroup.mm @@ -5,6 +5,18 @@ #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h" +@implementation FlutterEngineGroupOptions + +- (void)dealloc { + [_entrypoint release]; + [_libraryURI release]; + [_initialRoute release]; + [_entrypointArgs release]; + [super dealloc]; +} + +@end + @interface FlutterEngineGroup () @property(nonatomic, copy) NSString* name; @property(nonatomic, retain) NSMutableArray* engines; @@ -42,16 +54,32 @@ - (FlutterEngine*)makeEngineWithEntrypoint:(nullable NSString*)entrypoint libraryURI:(nullable NSString*)libraryURI initialRoute:(nullable NSString*)initialRoute { - NSString* engineName = [NSString stringWithFormat:@"%@.%d", self.name, ++_enginesCreatedCount]; + FlutterEngineGroupOptions* options = [[[FlutterEngineGroupOptions alloc] init] autorelease]; + options.entrypoint = entrypoint; + options.libraryURI = libraryURI; + options.initialRoute = initialRoute; + return [self makeEngineWithOptions:options]; +} + +- (FlutterEngine*)makeEngineWithOptions:(nullable FlutterEngineGroupOptions*)options { + NSString* entrypoint = options.entrypoint; + NSString* libraryURI = options.libraryURI; + NSString* initialRoute = options.initialRoute; + NSArray* entrypointArgs = options.entrypointArgs; + FlutterEngine* engine; if (self.engines.count <= 0) { - engine = [[FlutterEngine alloc] initWithName:engineName project:self.project]; - [engine runWithEntrypoint:entrypoint libraryURI:libraryURI initialRoute:initialRoute]; + engine = [self makeEngine]; + [engine runWithEntrypoint:entrypoint + libraryURI:libraryURI + initialRoute:initialRoute + entrypointArgs:entrypointArgs]; } else { FlutterEngine* spawner = (FlutterEngine*)[self.engines[0] pointerValue]; engine = [spawner spawnWithEntrypoint:entrypoint libraryURI:libraryURI - initialRoute:initialRoute]; + initialRoute:initialRoute + entrypointArgs:entrypointArgs]; } [_engines addObject:[NSValue valueWithPointer:engine]]; @@ -61,7 +89,13 @@ name:FlutterEngineWillDealloc object:engine]; - return [engine autorelease]; + return engine; +} + +- (FlutterEngine*)makeEngine { + NSString* engineName = [NSString stringWithFormat:@"%@.%d", self.name, ++_enginesCreatedCount]; + FlutterEngine* result = [[FlutterEngine alloc] initWithName:engineName project:self.project]; + return [result autorelease]; } - (void)onEngineWillBeDealloced:(NSNotification*)notification { diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineGroupTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineGroupTest.mm index a861eb4f013..db43205c051 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineGroupTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineGroupTest.mm @@ -10,6 +10,10 @@ FLUTTER_ASSERT_ARC +@interface FlutterEngineGroup () +- (FlutterEngine*)makeEngine; +@end + @interface FlutterEngineGroupTest : XCTestCase @end @@ -42,6 +46,86 @@ FLUTTER_ASSERT_ARC XCTAssertNotNil(spawnee); } +- (void)testCustomEntrypoint { + FlutterEngineGroup* group = OCMPartialMock([[FlutterEngineGroup alloc] initWithName:@"foo" + project:nil]); + FlutterEngine* mockEngine = OCMClassMock([FlutterEngine class]); + OCMStub([group makeEngine]).andReturn(mockEngine); + OCMStub([mockEngine spawnWithEntrypoint:[OCMArg any] + libraryURI:[OCMArg any] + initialRoute:[OCMArg any] + entrypointArgs:[OCMArg any]]) + .andReturn(OCMClassMock([FlutterEngine class])); + FlutterEngine* spawner = [group makeEngineWithEntrypoint:@"firstEntrypoint" + libraryURI:@"firstLibraryURI"]; + XCTAssertNotNil(spawner); + OCMVerify([spawner runWithEntrypoint:@"firstEntrypoint" + libraryURI:@"firstLibraryURI" + initialRoute:nil + entrypointArgs:nil]); + + FlutterEngine* spawnee = [group makeEngineWithEntrypoint:@"secondEntrypoint" + libraryURI:@"secondLibraryURI"]; + XCTAssertNotNil(spawnee); + OCMVerify([spawner spawnWithEntrypoint:@"secondEntrypoint" + libraryURI:@"secondLibraryURI" + initialRoute:nil + entrypointArgs:nil]); +} + +- (void)testCustomInitialRoute { + FlutterEngineGroup* group = OCMPartialMock([[FlutterEngineGroup alloc] initWithName:@"foo" + project:nil]); + FlutterEngine* mockEngine = OCMClassMock([FlutterEngine class]); + OCMStub([group makeEngine]).andReturn(mockEngine); + OCMStub([mockEngine spawnWithEntrypoint:[OCMArg any] + libraryURI:[OCMArg any] + initialRoute:[OCMArg any] + entrypointArgs:[OCMArg any]]) + .andReturn(OCMClassMock([FlutterEngine class])); + FlutterEngine* spawner = [group makeEngineWithEntrypoint:nil libraryURI:nil initialRoute:@"foo"]; + XCTAssertNotNil(spawner); + OCMVerify([spawner runWithEntrypoint:nil libraryURI:nil initialRoute:@"foo" entrypointArgs:nil]); + + FlutterEngine* spawnee = [group makeEngineWithEntrypoint:nil libraryURI:nil initialRoute:@"bar"]; + XCTAssertNotNil(spawnee); + OCMVerify([spawner spawnWithEntrypoint:nil + libraryURI:nil + initialRoute:@"bar" + entrypointArgs:nil]); +} + +- (void)testCustomEntrypointArgs { + FlutterEngineGroup* group = OCMPartialMock([[FlutterEngineGroup alloc] initWithName:@"foo" + project:nil]); + FlutterEngine* mockEngine = OCMClassMock([FlutterEngine class]); + OCMStub([group makeEngine]).andReturn(mockEngine); + OCMStub([mockEngine spawnWithEntrypoint:[OCMArg any] + libraryURI:[OCMArg any] + initialRoute:[OCMArg any] + entrypointArgs:[OCMArg any]]) + .andReturn(OCMClassMock([FlutterEngine class])); + FlutterEngineGroupOptions* firstOptions = [[FlutterEngineGroupOptions alloc] init]; + NSArray* firstEntrypointArgs = @[ @"foo", @"first" ]; + firstOptions.entrypointArgs = firstEntrypointArgs; + FlutterEngine* spawner = [group makeEngineWithOptions:firstOptions]; + XCTAssertNotNil(spawner); + OCMVerify([spawner runWithEntrypoint:nil + libraryURI:nil + initialRoute:nil + entrypointArgs:firstEntrypointArgs]); + + NSArray* secondEntrypointArgs = @[ @"bar", @"second" ]; + FlutterEngineGroupOptions* secondOptions = [[FlutterEngineGroupOptions alloc] init]; + secondOptions.entrypointArgs = secondEntrypointArgs; + FlutterEngine* spawnee = [group makeEngineWithOptions:secondOptions]; + XCTAssertNotNil(spawnee); + OCMVerify([spawner spawnWithEntrypoint:nil + libraryURI:nil + initialRoute:nil + entrypointArgs:secondEntrypointArgs]); +} + - (void)testReleasesProjectOnDealloc { __weak FlutterDartProject* weakProject; @autoreleasepool { diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm index 1263b71adf7..0f7fa74fad0 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm @@ -194,7 +194,10 @@ FLUTTER_ASSERT_ARC - (void)testSpawn { FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"]; [engine run]; - FlutterEngine* spawn = [engine spawnWithEntrypoint:nil libraryURI:nil initialRoute:nil]; + FlutterEngine* spawn = [engine spawnWithEntrypoint:nil + libraryURI:nil + initialRoute:nil + entrypointArgs:nil]; XCTAssertNotNil(spawn); } diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest_mrc.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest_mrc.mm index 612404f926f..1acf3755f07 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest_mrc.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest_mrc.mm @@ -38,7 +38,10 @@ FLUTTER_ASSERT_NOT_ARC - (void)testSpawnsShareGpuContext { FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"]; [engine run]; - FlutterEngine* spawn = [engine spawnWithEntrypoint:nil libraryURI:nil initialRoute:nil]; + FlutterEngine* spawn = [engine spawnWithEntrypoint:nil + libraryURI:nil + initialRoute:nil + entrypointArgs:nil]; XCTAssertNotNil(spawn); XCTAssertTrue([engine iosPlatformView] != nullptr); XCTAssertTrue([spawn iosPlatformView] != nullptr); @@ -50,7 +53,6 @@ FLUTTER_ASSERT_NOT_ARC XCTAssertTrue(engine_context->GetMainContext() != nullptr); XCTAssertEqual(engine_context->GetMainContext(), spawn_context->GetMainContext()); [engine release]; - [spawn release]; } - (void)testEnableSemanticsWhenFlutterViewAccessibilityDidCall { diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h index f5a45952226..18ecb7c7896 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h @@ -50,7 +50,9 @@ extern NSString* _Nonnull const FlutterEngineWillDealloc; - (std::shared_ptr&)platformViewsController; - (nonnull FlutterTextInputPlugin*)textInputPlugin; - (nonnull FlutterRestorationPlugin*)restorationPlugin; -- (void)launchEngine:(nullable NSString*)entrypoint libraryURI:(nullable NSString*)libraryOrNil; +- (void)launchEngine:(nullable NSString*)entrypoint + libraryURI:(nullable NSString*)libraryOrNil + entrypointArgs:(nullable NSArray*)entrypointArgs; - (BOOL)createShell:(nullable NSString*)entrypoint libraryURI:(nullable NSString*)libraryOrNil initialRoute:(nullable NSString*)initialRoute; @@ -69,7 +71,8 @@ extern NSString* _Nonnull const FlutterEngineWillDealloc; */ - (nonnull FlutterEngine*)spawnWithEntrypoint:(nullable NSString*)entrypoint libraryURI:(nullable NSString*)libraryURI - initialRoute:(nullable NSString*)initialRoute; + initialRoute:(nullable NSString*)initialRoute + entrypointArgs:(nullable NSArray*)entrypointArgs; /** * Dispatches the given key event data to the framework through the engine. diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h index 9d251a24b1c..c080a1d3cfb 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h @@ -20,6 +20,7 @@ class ThreadHost; - (void)waitForFirstFrame:(NSTimeInterval)timeout callback:(void (^)(BOOL didTimeout))callback; - (FlutterEngine*)spawnWithEntrypoint:(/*nullable*/ NSString*)entrypoint libraryURI:(/*nullable*/ NSString*)libraryURI - initialRoute:(/*nullable*/ NSString*)initialRoute; + initialRoute:(/*nullable*/ NSString*)initialRoute + entrypointArgs:(/*nullable*/ NSArray*)entrypointArgs; - (const flutter::ThreadHost&)threadHost; @end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index a89ae9cd12c..01695e8e2f1 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -648,7 +648,7 @@ static void SendFakeTouchEvent(FlutterEngine* engine, // Register internal plugins before starting the engine. [self addInternalPlugins]; - [_engine.get() launchEngine:nil libraryURI:nil]; + [_engine.get() launchEngine:nil libraryURI:nil entrypointArgs:nil]; [_engine.get() setViewController:self]; _engineNeedsLaunch = NO; } diff --git a/engine/src/flutter/testing/dart_isolate_runner.cc b/engine/src/flutter/testing/dart_isolate_runner.cc index 6d9fb28e77f..760764b93f3 100644 --- a/engine/src/flutter/testing/dart_isolate_runner.cc +++ b/engine/src/flutter/testing/dart_isolate_runner.cc @@ -135,6 +135,7 @@ std::unique_ptr RunDartCodeInIsolateOnUITaskRunner( settings.isolate_shutdown_callback, // isolate shutdown callback entrypoint, // entrypoint std::nullopt, // library + {}, // args std::move(isolate_configuration), // isolate configuration context // engine context ) diff --git a/engine/src/flutter/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m b/engine/src/flutter/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m index b806e34541a..00892f9b830 100644 --- a/engine/src/flutter/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m +++ b/engine/src/flutter/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m @@ -91,7 +91,7 @@ FlutterEngine* spawner = [[FlutterEngine alloc] initWithName:@"FlutterControllerTest" project:nil]; [spawner run]; - return [spawner spawnWithEntrypoint:nil libraryURI:nil initialRoute:nil]; + return [spawner spawnWithEntrypoint:nil libraryURI:nil initialRoute:nil entrypointArgs:nil]; } else { FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"FlutterControllerTest" project:nil]; diff --git a/engine/src/flutter/testing/scenario_app/ios/Scenarios/Scenarios/FlutterEngine+ScenariosTest.h b/engine/src/flutter/testing/scenario_app/ios/Scenarios/Scenarios/FlutterEngine+ScenariosTest.h index 80ad6c8fcc5..9438dd3774e 100644 --- a/engine/src/flutter/testing/scenario_app/ios/Scenarios/Scenarios/FlutterEngine+ScenariosTest.h +++ b/engine/src/flutter/testing/scenario_app/ios/Scenarios/Scenarios/FlutterEngine+ScenariosTest.h @@ -10,6 +10,7 @@ NS_ASSUME_NONNULL_BEGIN withCompletion:(nullable void (^)(void))engineRunCompletion; - (FlutterEngine*)spawnWithEntrypoint:(nullable NSString*)entrypoint libraryURI:(nullable NSString*)libraryURI - initialRoute:(nullable NSString*)initialRoute; + initialRoute:(nullable NSString*)initialRoute + entrypointArgs:(nullable NSArray*)entrypointArgs; @end NS_ASSUME_NONNULL_END diff --git a/engine/src/flutter/third_party/tonic/tests/dart_state_unittest.cc b/engine/src/flutter/third_party/tonic/tests/dart_state_unittest.cc index b5ee4d8e2fe..8ce1dfa15af 100644 --- a/engine/src/flutter/third_party/tonic/tests/dart_state_unittest.cc +++ b/engine/src/flutter/third_party/tonic/tests/dart_state_unittest.cc @@ -41,6 +41,7 @@ TEST_F(DartState, IsShuttingDown) { settings.isolate_shutdown_callback, // isolate shutdown callback "main", // dart entrypoint std::nullopt, // dart entrypoint library + {}, // dart entrypoint arguments std::move(isolate_configuration), // isolate configuration std::move(context) // engine context ); diff --git a/engine/src/flutter/tools/gen_objcdoc.sh b/engine/src/flutter/tools/gen_objcdoc.sh index 928acfa8d21..dd0583b9392 100755 --- a/engine/src/flutter/tools/gen_objcdoc.sh +++ b/engine/src/flutter/tools/gen_objcdoc.sh @@ -60,6 +60,7 @@ FlutterCallbackInformation.html FlutterDartProject.html FlutterEngine.html FlutterEngineGroup.html +FlutterEngineGroupOptions.html FlutterError.html FlutterEventChannel.html FlutterHeadlessDartRunner.html