diff --git a/shell/platform/embedder/BUILD.gn b/shell/platform/embedder/BUILD.gn index 5df39518c8b62..f9659a3b5c5fb 100644 --- a/shell/platform/embedder/BUILD.gn +++ b/shell/platform/embedder/BUILD.gn @@ -74,6 +74,8 @@ executable("embedder_unittests") { "tests/embedder_context.h", "tests/embedder_test.cc", "tests/embedder_test.h", + "tests/embedder_test_resolver.cc", + "tests/embedder_test_resolver.h", "tests/embedder_unittests.cc", ] diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 003f40113c419..c15970de70775 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -624,6 +624,13 @@ FlutterEngineResult FlutterEngineRun(size_t version, // Step 3: Run the engine. auto run_configuration = shell::RunConfiguration::InferFromSettings(settings); + if (SAFE_ACCESS(args, custom_dart_entrypoint, nullptr) != nullptr) { + auto dart_entrypoint = std::string{args->custom_dart_entrypoint}; + if (dart_entrypoint.size() != 0) { + run_configuration.SetEntrypoint(std::move(dart_entrypoint)); + } + } + run_configuration.AddAssetResolver( std::make_unique( fml::Duplicate(settings.assets_dir))); diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 6127681c55774..442716a50114b 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -563,6 +563,15 @@ typedef struct { // internal engine-managed thread. If the components accessed on the embedder // are not thread safe, the appropriate re-threading must be done. VsyncCallback vsync_callback; + + // The name of a custom Dart entrypoint. This is optional and specifying a + // null or empty entrypoint makes the engine look for a method named "main" in + // the root library of the application. + // + // Care must be taken to ensure that the custom entrypoint is not tree-shaken + // away. Usually, this is done using the `@pragma('vm:entry-point')` + // decoration. + const char* custom_dart_entrypoint; } FlutterProjectArgs; FLUTTER_EXPORT diff --git a/shell/platform/embedder/fixtures/simple_main.dart b/shell/platform/embedder/fixtures/simple_main.dart index ab73b3a234abf..b1124b508b7e3 100644 --- a/shell/platform/embedder/fixtures/simple_main.dart +++ b/shell/platform/embedder/fixtures/simple_main.dart @@ -1 +1,8 @@ void main() {} + +@pragma('vm:entry-point') +void customEntrypoint() { + sayHiFromCustomEntrypoint(); +} + +void sayHiFromCustomEntrypoint() native "SayHiFromCustomEntrypoint"; diff --git a/shell/platform/embedder/tests/embedder_config_builder.cc b/shell/platform/embedder/tests/embedder_config_builder.cc index c93886b1133c6..23ef27cd3c954 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.cc +++ b/shell/platform/embedder/tests/embedder_config_builder.cc @@ -62,6 +62,15 @@ void EmbedderConfigBuilder::SetIsolateCreateCallbackHook() { EmbedderContext::GetIsolateCreateCallbackHook(); } +void EmbedderConfigBuilder::SetDartEntrypoint(std::string entrypoint) { + if (entrypoint.size() == 0) { + return; + } + + dart_entrypoint_ = std::move(entrypoint); + project_args_.custom_dart_entrypoint = dart_entrypoint_.c_str(); +} + UniqueEngine EmbedderConfigBuilder::LaunchEngine() const { FlutterEngine engine = nullptr; auto result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &renderer_config_, diff --git a/shell/platform/embedder/tests/embedder_config_builder.h b/shell/platform/embedder/tests/embedder_config_builder.h index 34dca1aed9f44..b76edb98734b9 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.h +++ b/shell/platform/embedder/tests/embedder_config_builder.h @@ -48,6 +48,8 @@ class EmbedderConfigBuilder { void SetIsolateCreateCallbackHook(); + void SetDartEntrypoint(std::string entrypoint); + UniqueEngine LaunchEngine() const; private: @@ -55,6 +57,7 @@ class EmbedderConfigBuilder { FlutterProjectArgs project_args_ = {}; FlutterRendererConfig renderer_config_ = {}; FlutterSoftwareRendererConfig software_renderer_config_ = {}; + std::string dart_entrypoint_; FML_DISALLOW_COPY_AND_ASSIGN(EmbedderConfigBuilder); }; diff --git a/shell/platform/embedder/tests/embedder_context.cc b/shell/platform/embedder/tests/embedder_context.cc index 57b4335e661ac..edb72ff4c2f24 100644 --- a/shell/platform/embedder/tests/embedder_context.cc +++ b/shell/platform/embedder/tests/embedder_context.cc @@ -36,7 +36,8 @@ static std::unique_ptr GetMapping(const fml::UniqueFD& directory, } EmbedderContext::EmbedderContext(std::string assets_path) - : assets_path_(std::move(assets_path)) { + : assets_path_(std::move(assets_path)), + native_resolver_(std::make_shared()) { auto assets_dir = fml::OpenDirectory(assets_path_.c_str(), false, fml::FilePermission::kRead); vm_snapshot_data_ = GetMapping(assets_dir, "vm_snapshot_data", false); @@ -49,6 +50,14 @@ EmbedderContext::EmbedderContext(std::string assets_path) isolate_snapshot_instructions_ = GetMapping(assets_dir, "isolate_snapshot_instr", true); } + + isolate_create_callbacks_.push_back( + [weak_resolver = + std::weak_ptr{native_resolver_}]() { + if (auto resolver = weak_resolver.lock()) { + resolver->SetNativeResolverForIsolate(); + } + }); } EmbedderContext::~EmbedderContext() = default; @@ -91,5 +100,10 @@ void EmbedderContext::FireIsolateCreateCallbacks() { } } +void EmbedderContext::AddNativeCallback(const char* name, + Dart_NativeFunction function) { + native_resolver_->AddNativeCallback({name}, function); +} + } // namespace testing } // namespace shell diff --git a/shell/platform/embedder/tests/embedder_context.h b/shell/platform/embedder/tests/embedder_context.h index 63aec332b2a0e..6405163d9cd6a 100644 --- a/shell/platform/embedder/tests/embedder_context.h +++ b/shell/platform/embedder/tests/embedder_context.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_CONTEXT_H_ #define FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_CONTEXT_H_ +#include #include #include #include @@ -13,6 +14,7 @@ #include "flutter/fml/macros.h" #include "flutter/fml/mapping.h" #include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/embedder/tests/embedder_test_resolver.h" namespace shell { namespace testing { @@ -35,6 +37,8 @@ class EmbedderContext { void AddIsolateCreateCallback(fml::closure closure); + void AddNativeCallback(const char* name, Dart_NativeFunction function); + private: // This allows the builder to access the hooks. friend class EmbedderConfigBuilder; @@ -45,11 +49,14 @@ class EmbedderContext { std::unique_ptr isolate_snapshot_data_; std::unique_ptr isolate_snapshot_instructions_; std::vector isolate_create_callbacks_; + std::shared_ptr native_resolver_; static VoidCallback GetIsolateCreateCallbackHook(); void FireIsolateCreateCallbacks(); + void SetNativeResolver(); + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderContext); }; diff --git a/shell/platform/embedder/tests/embedder_test_resolver.cc b/shell/platform/embedder/tests/embedder_test_resolver.cc new file mode 100644 index 0000000000000..49171a91df23c --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_resolver.cc @@ -0,0 +1,90 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/embedder/tests/embedder_test_resolver.h" + +#include +#include + +#include "flutter/fml/synchronization/thread_annotations.h" +#include "tonic/converter/dart_converter.h" + +namespace shell { +namespace testing { + +EmbedderTestResolver::EmbedderTestResolver() = default; + +EmbedderTestResolver::~EmbedderTestResolver() = default; + +void EmbedderTestResolver::AddNativeCallback(std::string name, + Dart_NativeFunction callback) { + native_callbacks_[name] = callback; +} + +Dart_NativeFunction EmbedderTestResolver::ResolveCallback( + std::string name) const { + auto found = native_callbacks_.find(name); + if (found == native_callbacks_.end()) { + return nullptr; + } + + return found->second; +} + +static std::mutex gIsolateResolversMutex; +static std::map> + gIsolateResolvers FML_GUARDED_BY(gIsolateResolversMutex); + +Dart_NativeFunction EmbedderTestResolver::DartNativeEntryResolverCallback( + Dart_Handle dart_name, + int num_of_arguments, + bool* auto_setup_scope) { + auto name = tonic::StdStringFromDart(dart_name); + + std::lock_guard lock(gIsolateResolversMutex); + auto found = gIsolateResolvers.find(Dart_CurrentIsolate()); + if (found == gIsolateResolvers.end()) { + return nullptr; + } + + if (auto resolver = found->second.lock()) { + return resolver->ResolveCallback(std::move(name)); + } else { + gIsolateResolvers.erase(found); + } + + return nullptr; +} + +static const uint8_t* DartNativeEntrySymbolCallback( + Dart_NativeFunction function) { + return reinterpret_cast("¯\\_(ツ)_/¯"); +} + +void EmbedderTestResolver::SetNativeResolverForIsolate() { + auto result = Dart_SetNativeResolver(Dart_RootLibrary(), + DartNativeEntryResolverCallback, + DartNativeEntrySymbolCallback); + + if (Dart_IsError(result)) { + return; + } + + std::lock_guard lock(gIsolateResolversMutex); + gIsolateResolvers[Dart_CurrentIsolate()] = shared_from_this(); + + std::vector isolates_with_dead_resolvers; + for (const auto& entry : gIsolateResolvers) { + if (!entry.second.lock()) { + isolates_with_dead_resolvers.push_back(entry.first); + } + } + + for (const auto& dead_isolate : isolates_with_dead_resolvers) { + gIsolateResolvers.erase(dead_isolate); + } +} + +} // namespace testing +} // namespace shell diff --git a/shell/platform/embedder/tests/embedder_test_resolver.h b/shell/platform/embedder/tests/embedder_test_resolver.h new file mode 100644 index 0000000000000..69d08efe1042d --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_resolver.h @@ -0,0 +1,47 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_RESOLVER_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_RESOLVER_H_ + +#include +#include + +#include "flutter/fml/macros.h" +#include "third_party/dart/runtime/include/dart_api.h" + +namespace shell { +namespace testing { + +class EmbedderTestResolver + : public std::enable_shared_from_this { + public: + EmbedderTestResolver(); + + ~EmbedderTestResolver(); + + void AddNativeCallback(std::string name, Dart_NativeFunction callback); + + private: + // Friend so that the context can set the native resolver. + friend class EmbedderContext; + + std::map native_callbacks_; + + void SetNativeResolverForIsolate(); + + Dart_NativeFunction ResolveCallback(std::string name) const; + + static Dart_NativeFunction DartNativeEntryResolverCallback( + Dart_Handle dart_name, + int num_of_arguments, + bool* auto_setup_scope); + + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestResolver); +}; + +} // namespace testing +} // namespace shell + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_RESOLVER_H_ diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index eda64316a016b..01d23ff0d885f 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -38,12 +38,26 @@ TEST_F(EmbedderTest, CanLaunchAndShutdownWithValidProjectArgs) { TEST_F(EmbedderTest, CanLaunchAndShutdownMultipleTimes) { EmbedderConfigBuilder builder(GetEmbedderContext()); - for (size_t i = 0; i < 100; ++i) { + for (size_t i = 0; i < 3; ++i) { auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); FML_LOG(INFO) << "Engine launch count: " << i + 1; } } +TEST_F(EmbedderTest, CanInvokeCustomEntrypoint) { + auto& context = GetEmbedderContext(); + static fml::AutoResetWaitableEvent latch; + Dart_NativeFunction entrypoint = [](Dart_NativeArguments args) { + latch.Signal(); + }; + context.AddNativeCallback("SayHiFromCustomEntrypoint", entrypoint); + EmbedderConfigBuilder builder(context); + builder.SetDartEntrypoint("customEntrypoint"); + auto engine = builder.LaunchEngine(); + latch.Wait(); + ASSERT_TRUE(engine.is_valid()); +} + } // namespace testing -} // namespace shell \ No newline at end of file +} // namespace shell