Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion shell/common/engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ std::unique_ptr<Engine> Engine::Spawn(
Delegate& delegate,
const PointerDataDispatcherMaker& dispatcher_maker,
Settings settings,
std::unique_ptr<Animator> animator) const {
std::unique_ptr<Animator> animator,
const std::string& initial_route) const {
auto result = std::make_unique<Engine>(
/*delegate=*/delegate,
/*dispatcher_maker=*/dispatcher_maker,
Expand All @@ -133,6 +134,7 @@ std::unique_ptr<Engine> Engine::Spawn(
settings_.isolate_shutdown_callback, // isolate shutdown callback
settings_.persistent_isolate_data // persistent isolate data
);
result->initial_route_ = std::move(initial_route);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you want an std::move here. This has to initiate a copy anyways, I think it is just needlessly invalidating the reference.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

return result;
}

Expand Down
3 changes: 2 additions & 1 deletion shell/common/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,8 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
Delegate& delegate,
const PointerDataDispatcherMaker& dispatcher_maker,
Settings settings,
std::unique_ptr<Animator> animator) const;
std::unique_ptr<Animator> animator,
const std::string& initial_route) const;

//----------------------------------------------------------------------------
/// @brief Destroys the engine engine. Called by the shell on the UI task
Expand Down
30 changes: 28 additions & 2 deletions shell/common/engine_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -268,13 +268,39 @@ TEST_F(EngineTest, SpawnSharesFontLibrary) {
/*font_collection=*/std::make_shared<FontCollection>(),
/*runtime_controller=*/std::move(mock_runtime_controller));

auto spawn =
engine->Spawn(delegate_, dispatcher_maker_, settings_, nullptr);
auto spawn = engine->Spawn(delegate_, dispatcher_maker_, settings_, nullptr,
std::string());
EXPECT_TRUE(spawn != nullptr);
EXPECT_EQ(&engine->GetFontCollection(), &spawn->GetFontCollection());
});
}

TEST_F(EngineTest, SpawnWithCustomInitialRoute) {
PostUITaskSync([this] {
MockRuntimeDelegate client;
auto mock_runtime_controller =
std::make_unique<MockRuntimeController>(client, task_runners_);
auto vm_ref = DartVMRef::Create(settings_);
EXPECT_CALL(*mock_runtime_controller, GetDartVM())
.WillRepeatedly(::testing::Return(vm_ref.get()));
auto engine = std::make_unique<Engine>(
/*delegate=*/delegate_,
/*dispatcher_maker=*/dispatcher_maker_,
/*image_decoder_task_runner=*/image_decoder_task_runner_,
/*task_runners=*/task_runners_,
/*settings=*/settings_,
/*animator=*/std::move(animator_),
/*io_manager=*/io_manager_,
/*font_collection=*/std::make_shared<FontCollection>(),
/*runtime_controller=*/std::move(mock_runtime_controller));

auto spawn =
engine->Spawn(delegate_, dispatcher_maker_, settings_, nullptr, "/foo");
EXPECT_TRUE(spawn != nullptr);
ASSERT_EQ("/foo", spawn->InitialRoute());
});
}

TEST_F(EngineTest, PassesLoadDartDeferredLibraryErrorToRuntime) {
PostUITaskSync([this] {
intptr_t error_id = 123;
Expand Down
7 changes: 5 additions & 2 deletions shell/common/shell.cc
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ Shell::~Shell() {

std::unique_ptr<Shell> Shell::Spawn(
RunConfiguration run_configuration,
const std::string& initial_route,
const CreateCallback<PlatformView>& on_create_platform_view,
const CreateCallback<Rasterizer>& on_create_rasterizer) const {
FML_DCHECK(task_runners_.IsValid());
Expand All @@ -488,7 +489,8 @@ std::unique_ptr<Shell> Shell::Spawn(
PlatformData{}, task_runners_, rasterizer_->GetRasterThreadMerger(),
GetSettings(), vm_, vm_->GetVMData()->GetIsolateSnapshot(),
on_create_platform_view, on_create_rasterizer,
[engine = this->engine_.get()](
[engine = this->engine_.get(),
initial_route = std::move(initial_route)](

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here for std::move. Since this is executing on a separate thread, just explicitly make a copy.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Engine::Delegate& delegate,
const PointerDataDispatcherMaker& dispatcher_maker, DartVM& vm,
fml::RefPtr<const DartSnapshot> isolate_snapshot,
Expand All @@ -501,7 +503,8 @@ std::unique_ptr<Shell> Shell::Spawn(
return engine->Spawn(/*delegate=*/delegate,
/*dispatcher_maker=*/dispatcher_maker,
/*settings=*/settings,
/*animator=*/std::move(animator));
/*animator=*/std::move(animator),
/*initial_route*/ initial_route);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/initial_route/initial_route=

I wish the linter worked for that, doesn't seem to be.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

},
is_gpu_disabled));
return result;
Expand Down
1 change: 1 addition & 0 deletions shell/common/shell.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ class Shell final : public PlatformView::Delegate,
/// @see http://flutter.dev/go/multiple-engines
std::unique_ptr<Shell> Spawn(
RunConfiguration run_configuration,
const std::string& initial_route,
const CreateCallback<PlatformView>& on_create_platform_view,
const CreateCallback<Rasterizer>& on_create_rasterizer) const;

Expand Down
8 changes: 6 additions & 2 deletions shell/common/shell_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2695,6 +2695,8 @@ TEST_F(ShellTest, Spawn) {
ASSERT_TRUE(second_configuration.IsValid());
second_configuration.SetEntrypoint("testCanLaunchSecondaryIsolate");

const std::string initial_route("/foo");

fml::AutoResetWaitableEvent main_latch;
std::string last_entry_point;
// Fulfill native function for the first Shell's entrypoint.
Expand All @@ -2719,10 +2721,11 @@ TEST_F(ShellTest, Spawn) {

PostSync(
shell->GetTaskRunners().GetPlatformTaskRunner(),
[this, &spawner = shell, &second_configuration, &second_latch]() {
[this, &spawner = shell, &second_configuration, &second_latch,
&initial_route]() {
MockPlatformViewDelegate platform_view_delegate;
auto spawn = spawner->Spawn(
std::move(second_configuration),
std::move(second_configuration), initial_route,
[&platform_view_delegate](Shell& shell) {
auto result = std::make_unique<MockPlatformView>(
platform_view_delegate, shell.GetTaskRunners());
Expand All @@ -2740,6 +2743,7 @@ TEST_F(ShellTest, Spawn) {
// Check second shell ran the second entrypoint.
ASSERT_EQ("testCanLaunchSecondaryIsolate",
spawn->GetEngine()->GetLastEntrypoint());
ASSERT_EQ("/foo", spawn->GetEngine()->InitialRoute());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/"/foo"/initial_route

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


// TODO(74520): Remove conditional once isolate groups are
// supported by JIT.
Expand Down
8 changes: 5 additions & 3 deletions shell/platform/android/android_shell_holder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ const flutter::Settings& AndroidShellHolder::GetSettings() const {
std::unique_ptr<AndroidShellHolder> AndroidShellHolder::Spawn(
std::shared_ptr<PlatformViewAndroidJNI> jni_facade,
const std::string& entrypoint,
const std::string& libraryUrl) const {
const std::string& libraryUrl,
const std::string& initial_route) const {
FML_DCHECK(shell_ && shell_->IsSetup())
<< "A new Shell can only be spawned "
"if the current Shell is properly constructed";
Expand Down Expand Up @@ -226,8 +227,9 @@ std::unique_ptr<AndroidShellHolder> AndroidShellHolder::Spawn(
return nullptr;
}

std::unique_ptr<flutter::Shell> shell = shell_->Spawn(
std::move(config.value()), on_create_platform_view, on_create_rasterizer);
std::unique_ptr<flutter::Shell> shell =
shell_->Spawn(std::move(config.value()), initial_route,
on_create_platform_view, on_create_rasterizer);

return std::unique_ptr<AndroidShellHolder>(
new AndroidShellHolder(GetSettings(), jni_facade, thread_host_,
Expand Down
3 changes: 2 additions & 1 deletion shell/platform/android/android_shell_holder.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ class AndroidShellHolder {
std::unique_ptr<AndroidShellHolder> Spawn(
std::shared_ptr<PlatformViewAndroidJNI> jni_facade,
const std::string& entrypoint,
const std::string& libraryUrl) const;
const std::string& libraryUrl,
const std::string& initial_route) const;

void Launch(std::shared_ptr<AssetManager> asset_manager,
const std::string& entrypoint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,15 +384,19 @@ private boolean isAttachedToJni() {
*/
@NonNull
/*package*/ FlutterEngine spawn(
@NonNull Context context, @NonNull DartEntrypoint dartEntrypoint) {
@NonNull Context context,
@NonNull DartEntrypoint dartEntrypoint,
@Nullable String initialRoute) {
if (!isAttachedToJni()) {
throw new IllegalStateException(
"Spawn can only be called on a fully constructed FlutterEngine");
}

FlutterJNI newFlutterJNI =
flutterJNI.spawn(
dartEntrypoint.dartEntrypointFunctionName, dartEntrypoint.dartEntrypointLibrary);
dartEntrypoint.dartEntrypointFunctionName,
dartEntrypoint.dartEntrypointLibrary,
initialRoute);
return new FlutterEngine(
context, // Context.
null, // FlutterLoader. A null value passed here causes the constructor to get it from the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,27 @@ public FlutterEngine createAndRunDefaultEngine(@NonNull Context context) {
*/
public FlutterEngine createAndRunEngine(
@NonNull Context context, @Nullable DartEntrypoint dartEntrypoint) {
return createAndRunEngine(context, dartEntrypoint, null);
}

/**
* 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} and
* the specified {@code initialRoute}.
*
* <p>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.
*
* <p>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 Context context,
@Nullable DartEntrypoint dartEntrypoint,
@Nullable String initialRoute) {
FlutterEngine engine = null;

if (dartEntrypoint == null) {
Expand All @@ -104,9 +125,12 @@ public FlutterEngine createAndRunEngine(

if (activeEngines.size() == 0) {
engine = createEngine(context);
if (initialRoute != null) {
engine.getNavigationChannel().setInitialRoute(initialRoute);
}
engine.getDartExecutor().executeDartEntrypoint(dartEntrypoint);
} else {
engine = activeEngines.get(0).spawn(context, dartEntrypoint);
engine = activeEngines.get(0).spawn(context, dartEntrypoint, initialRoute);
}

activeEngines.add(engine);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,11 +335,14 @@ public long performNativeAttach(@NonNull FlutterJNI flutterJNI) {
@UiThread
@NonNull
public FlutterJNI spawn(
@Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction) {
@Nullable String entrypointFunctionName,
@Nullable String pathToEntrypointFunction,
@Nullable String initialRoute) {
ensureRunningOnMainThread();
ensureAttachedToNative();
FlutterJNI spawnedJNI =
nativeSpawn(nativeShellHolderId, entrypointFunctionName, pathToEntrypointFunction);
nativeSpawn(
nativeShellHolderId, entrypointFunctionName, pathToEntrypointFunction, initialRoute);
Preconditions.checkState(
spawnedJNI.nativeShellHolderId != null && spawnedJNI.nativeShellHolderId != 0,
"Failed to spawn new JNI connected shell from existing shell.");
Expand All @@ -350,7 +353,8 @@ public FlutterJNI spawn(
private native FlutterJNI nativeSpawn(
long nativeSpawningShellId,
@Nullable String entrypointFunctionName,
@Nullable String pathToEntrypointFunction);
@Nullable String pathToEntrypointFunction,
@Nullable String initialRoute);

/**
* Detaches this {@code FlutterJNI} instance from Flutter's native engine, which precludes any
Expand All @@ -359,7 +363,7 @@ private native FlutterJNI nativeSpawn(
* <p>This method must not be invoked if {@code FlutterJNI} is not already attached to native.
*
* <p>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)}, or accumulated
* up during {@link #attachToNative()} or {@link #spawn(String, String, String)}, or accumulated
* thereafter.
*
* <p>It is permissible to re-attach this instance to native after detaching it from native.
Expand Down
11 changes: 7 additions & 4 deletions shell/platform/android/platform_view_android_jni_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ static jobject SpawnJNI(JNIEnv* env,
jobject jcaller,
jlong shell_holder,
jstring jEntrypoint,
jstring jLibraryUrl) {
jstring jLibraryUrl,
jstring jInitialRoute) {
jobject jni = env->NewObject(g_flutter_jni_class->obj(), g_jni_constructor);
if (jni == nullptr) {
FML_LOG(ERROR) << "Could not create a FlutterJNI instance";
Expand All @@ -167,9 +168,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 spawned_shell_holder =
ANDROID_SHELL_HOLDER->Spawn(jni_facade, entrypoint, libraryUrl);
auto spawned_shell_holder = ANDROID_SHELL_HOLDER->Spawn(
jni_facade, entrypoint, libraryUrl, initial_route);

if (spawned_shell_holder == nullptr || !spawned_shell_holder->IsValid()) {
FML_LOG(ERROR) << "Could not spawn Shell";
Expand Down Expand Up @@ -620,7 +622,8 @@ bool RegisterApi(JNIEnv* env) {
},
{
.name = "nativeSpawn",
.signature = "(JLjava/lang/String;Ljava/lang/String;)Lio/flutter/"
.signature = "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/"
"String;)Lio/flutter/"
"embedding/engine/FlutterJNI;",
.fnPtr = reinterpret_cast<void*>(&SpawnJNI),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.nullable;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
Expand All @@ -21,6 +22,7 @@
import io.flutter.FlutterInjector;
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 org.junit.After;
import org.junit.Before;
Expand Down Expand Up @@ -119,7 +121,7 @@ public void canSpawnMoreEngines() {

doReturn(mock(FlutterEngine.class))
.when(firstEngine)
.spawn(any(Context.class), any(DartEntrypoint.class));
.spawn(any(Context.class), any(DartEntrypoint.class), nullable(String.class));

FlutterEngine secondEngine =
engineGroupUnderTest.createAndRunEngine(
Expand All @@ -131,7 +133,7 @@ public void canSpawnMoreEngines() {

// 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)))
when(secondEngine.spawn(any(Context.class), any(DartEntrypoint.class), nullable(String.class)))
.thenReturn(mock(FlutterEngine.class));

FlutterEngine thirdEngine =
Expand All @@ -156,4 +158,32 @@ public void canCreateAndRunCustomEntrypoints() {
isNull(String.class),
any(AssetManager.class));
}

@Test
public void canCreateAndRunWithCustomInitialRoute() {
when(firstEngineUnderTest.getNavigationChannel()).thenReturn(mock(NavigationChannel.class));

FlutterEngine firstEngine =
engineGroupUnderTest.createAndRunEngine(
RuntimeEnvironment.application, mock(DartEntrypoint.class), "/foo");
assertEquals(1, engineGroupUnderTest.activeEngines.size());
verify(firstEngine.getNavigationChannel(), times(1)).setInitialRoute("/foo");

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));

FlutterEngine secondEngine =
engineGroupUnderTest.createAndRunEngine(
RuntimeEnvironment.application, mock(DartEntrypoint.class), "/bar");

assertEquals(2, engineGroupUnderTest.activeEngines.size());
verify(mockflutterJNI, times(1))
.spawn(nullable(String.class), nullable(String.class), eq("/bar"));
}
}
21 changes: 21 additions & 0 deletions shell/platform/darwin/ios/framework/Headers/FlutterEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,27 @@ FLUTTER_DARWIN_EXPORT
*/
- (BOOL)runWithEntrypoint:(nullable NSString*)entrypoint libraryURI:(nullable NSString*)uri;

/**
* 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.
* @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;

/**
* Destroy running context for an engine.
*
Expand Down
Loading