diff --git a/shell/platform/windows/accessibility_bridge_delegate_windows_unittests.cc b/shell/platform/windows/accessibility_bridge_delegate_windows_unittests.cc index 27d2fedce3b85..5db7fc4c7de38 100644 --- a/shell/platform/windows/accessibility_bridge_delegate_windows_unittests.cc +++ b/shell/platform/windows/accessibility_bridge_delegate_windows_unittests.cc @@ -86,7 +86,7 @@ std::unique_ptr GetTestEngine() { MockEmbedderApiForKeyboard(modifier, std::make_shared()); - engine->RunWithEntrypoint(nullptr); + engine->Run(); return engine; } diff --git a/shell/platform/windows/client_wrapper/flutter_engine.cc b/shell/platform/windows/client_wrapper/flutter_engine.cc index e9130805c824f..f35c20cd9e5a2 100644 --- a/shell/platform/windows/client_wrapper/flutter_engine.cc +++ b/shell/platform/windows/client_wrapper/flutter_engine.cc @@ -16,6 +16,7 @@ FlutterEngine::FlutterEngine(const DartProject& project) { c_engine_properties.assets_path = project.assets_path().c_str(); c_engine_properties.icu_data_path = project.icu_data_path().c_str(); c_engine_properties.aot_library_path = project.aot_library_path().c_str(); + c_engine_properties.dart_entrypoint = project.dart_entrypoint().c_str(); const std::vector& entrypoint_args = project.dart_entrypoint_arguments(); @@ -40,6 +41,10 @@ FlutterEngine::~FlutterEngine() { ShutDown(); } +bool FlutterEngine::Run() { + return Run(nullptr); +} + bool FlutterEngine::Run(const char* entry_point) { if (!engine_) { std::cerr << "Cannot run an engine that failed creation." << std::endl; diff --git a/shell/platform/windows/client_wrapper/flutter_engine_unittests.cc b/shell/platform/windows/client_wrapper/flutter_engine_unittests.cc index e52fb336a3373..6f09bb960aac5 100644 --- a/shell/platform/windows/client_wrapper/flutter_engine_unittests.cc +++ b/shell/platform/windows/client_wrapper/flutter_engine_unittests.cc @@ -86,6 +86,23 @@ TEST(FlutterEngineTest, CreateDestroy) { EXPECT_EQ(test_api->destroy_called(), true); } +TEST(FlutterEngineTest, CreateDestroyWithCustomEntrypoint) { + testing::ScopedStubFlutterWindowsApi scoped_api_stub( + std::make_unique()); + auto test_api = static_cast(scoped_api_stub.stub()); + { + DartProject project(L"fake/project/path"); + project.set_dart_entrypoint("customEntrypoint"); + FlutterEngine engine(project); + engine.Run(); + EXPECT_EQ(test_api->create_called(), true); + EXPECT_EQ(test_api->run_called(), true); + EXPECT_EQ(test_api->destroy_called(), false); + } + // Destroying should implicitly shut down if it hasn't been done manually. + EXPECT_EQ(test_api->destroy_called(), true); +} + TEST(FlutterEngineTest, ExplicitShutDown) { testing::ScopedStubFlutterWindowsApi scoped_api_stub( std::make_unique()); diff --git a/shell/platform/windows/client_wrapper/include/flutter/dart_project.h b/shell/platform/windows/client_wrapper/include/flutter/dart_project.h index cbc07ff502359..903cfc45e9299 100644 --- a/shell/platform/windows/client_wrapper/include/flutter/dart_project.h +++ b/shell/platform/windows/client_wrapper/include/flutter/dart_project.h @@ -45,6 +45,20 @@ class DartProject { ~DartProject() = default; + // Sets the Dart entrypoint to the specified value. + // + // If not set, the default entrypoint (main) is used. Custom Dart entrypoints + // must be decorated with `@pragma('vm:entry-point')`. + void set_dart_entrypoint(const std::string& entrypoint) { + if (entrypoint.empty()) { + return; + } + dart_entrypoint_ = entrypoint; + } + + // Returns the Dart entrypoint. + const std::string& dart_entrypoint() const { return dart_entrypoint_; } + // Sets the command line arguments that should be passed to the Dart // entrypoint. void set_dart_entrypoint_arguments(std::vector arguments) { @@ -77,6 +91,8 @@ class DartProject { // The path to the AOT library. This will always return a path, but non-AOT // builds will not be expected to actually have a library at that path. std::wstring aot_library_path_; + // The Dart entrypoint to launch. + std::string dart_entrypoint_; // The list of arguments to pass through to the Dart entrypoint. std::vector dart_entrypoint_arguments_; }; diff --git a/shell/platform/windows/client_wrapper/include/flutter/flutter_engine.h b/shell/platform/windows/client_wrapper/include/flutter/flutter_engine.h index 688361740ebc8..897fccfe0eb94 100644 --- a/shell/platform/windows/client_wrapper/include/flutter/flutter_engine.h +++ b/shell/platform/windows/client_wrapper/include/flutter/flutter_engine.h @@ -35,13 +35,17 @@ class FlutterEngine : public PluginRegistry { FlutterEngine(FlutterEngine const&) = delete; FlutterEngine& operator=(FlutterEngine const&) = delete; + // Starts running the engine at the entrypoint function specified in the + // DartProject used to configure the engine, or main() by default. + bool Run(); + // Starts running the engine, with an optional entry point. // // If provided, entry_point must be the name of a top-level function from the // same Dart library that contains the app's main() function, and must be // decorated with `@pragma(vm:entry-point)` to ensure the method is not // tree-shaken by the Dart compiler. If not provided, defaults to main(). - bool Run(const char* entry_point = nullptr); + bool Run(const char* entry_point); // Terminates the running engine. void ShutDown(); diff --git a/shell/platform/windows/fixtures/main.dart b/shell/platform/windows/fixtures/main.dart index 6d207279d54ee..219c83e8eb6d5 100644 --- a/shell/platform/windows/fixtures/main.dart +++ b/shell/platform/windows/fixtures/main.dart @@ -3,5 +3,10 @@ // found in the LICENSE file. void main() { - print('Hello windows engine test!'); + print('Hello windows engine test main!'); +} + +@pragma('vm:entry-point') +void customEntrypoint() { + print('Hello windows engine test customEntrypoint!'); } diff --git a/shell/platform/windows/flutter_project_bundle.cc b/shell/platform/windows/flutter_project_bundle.cc index d01ef65a17815..90b2d782d7d7e 100644 --- a/shell/platform/windows/flutter_project_bundle.cc +++ b/shell/platform/windows/flutter_project_bundle.cc @@ -20,6 +20,10 @@ FlutterProjectBundle::FlutterProjectBundle( aot_library_path_ = std::filesystem::path(properties.aot_library_path); } + if (properties.dart_entrypoint && properties.dart_entrypoint[0] != '\0') { + dart_entrypoint_ = properties.dart_entrypoint; + } + for (int i = 0; i < properties.dart_entrypoint_argc; i++) { dart_entrypoint_arguments_.push_back( std::string(properties.dart_entrypoint_argv[i])); diff --git a/shell/platform/windows/flutter_project_bundle.h b/shell/platform/windows/flutter_project_bundle.h index 3cb5d3ca61622..09770b5c094fc 100644 --- a/shell/platform/windows/flutter_project_bundle.h +++ b/shell/platform/windows/flutter_project_bundle.h @@ -50,6 +50,9 @@ class FlutterProjectBundle { // Logs and returns nullptr on failure. UniqueAotDataPtr LoadAotData(const FlutterEngineProcTable& engine_procs); + // Returns the Dart entrypoint. + const std::string& dart_entrypoint() const { return dart_entrypoint_; } + // Returns the command line arguments to be passed through to the Dart // entrypoint. const std::vector& dart_entrypoint_arguments() const { @@ -63,6 +66,9 @@ class FlutterProjectBundle { // Path to the AOT library file, if any. std::filesystem::path aot_library_path_; + // The Dart entrypoint to launch. + std::string dart_entrypoint_; + // Dart entrypoint arguments. std::vector dart_entrypoint_arguments_; diff --git a/shell/platform/windows/flutter_windows.cc b/shell/platform/windows/flutter_windows.cc index 9e07ed003b648..87c6c8bd55df4 100644 --- a/shell/platform/windows/flutter_windows.cc +++ b/shell/platform/windows/flutter_windows.cc @@ -78,7 +78,7 @@ FlutterDesktopViewControllerRef FlutterDesktopViewControllerCreate( std::unique_ptr(EngineFromHandle(engine))); state->view->CreateRenderSurface(); if (!state->view->GetEngine()->running()) { - if (!state->view->GetEngine()->RunWithEntrypoint(nullptr)) { + if (!state->view->GetEngine()->Run()) { return nullptr; } } @@ -144,7 +144,7 @@ bool FlutterDesktopEngineDestroy(FlutterDesktopEngineRef engine_ref) { bool FlutterDesktopEngineRun(FlutterDesktopEngineRef engine, const char* entry_point) { - return EngineFromHandle(engine)->RunWithEntrypoint(entry_point); + return EngineFromHandle(engine)->Run(entry_point); } uint64_t FlutterDesktopEngineProcessMessages(FlutterDesktopEngineRef engine) { diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc index a1edfee1c46f3..a0358bb6090d8 100644 --- a/shell/platform/windows/flutter_windows_engine.cc +++ b/shell/platform/windows/flutter_windows_engine.cc @@ -200,7 +200,11 @@ void FlutterWindowsEngine::SetSwitches( project_->SetSwitches(switches); } -bool FlutterWindowsEngine::RunWithEntrypoint(const char* entrypoint) { +bool FlutterWindowsEngine::Run() { + return Run(""); +} + +bool FlutterWindowsEngine::Run(std::string_view entrypoint) { if (!project_->HasValidPaths()) { std::cerr << "Missing or unresolvable paths to assets." << std::endl; return false; @@ -259,6 +263,26 @@ bool FlutterWindowsEngine::RunWithEntrypoint(const char* entrypoint) { args.icu_data_path = icu_path_string.c_str(); args.command_line_argc = static_cast(argv.size()); args.command_line_argv = argv.empty() ? nullptr : argv.data(); + + // Fail if conflicting non-default entrypoints are specified in the method + // argument and the project. + // + // TODO(cbracken): https://github.com/flutter/flutter/issues/109285 + // The entrypoint method parameter should eventually be removed from this + // method and only the entrypoint specified in project_ should be used. + if (!project_->dart_entrypoint().empty() && !entrypoint.empty() && + project_->dart_entrypoint() != entrypoint) { + std::cerr << "Conflicting entrypoints were specified in " + "FlutterDesktopEngineProperties.dart_entrypoint and " + "FlutterDesktopEngineRun(engine, entry_point). " + << std::endl; + return false; + } + if (!entrypoint.empty()) { + args.custom_dart_entrypoint = entrypoint.data(); + } else if (!project_->dart_entrypoint().empty()) { + args.custom_dart_entrypoint = project_->dart_entrypoint().c_str(); + } args.dart_entrypoint_argc = static_cast(entrypoint_argv.size()); args.dart_entrypoint_argv = entrypoint_argv.empty() ? nullptr : entrypoint_argv.data(); @@ -301,9 +325,6 @@ bool FlutterWindowsEngine::RunWithEntrypoint(const char* entrypoint) { if (aot_data_) { args.aot_data = aot_data_.get(); } - if (entrypoint) { - args.custom_dart_entrypoint = entrypoint; - } FlutterRendererConfig renderer_config = surface_manager_ ? GetOpenGLRendererConfig() diff --git a/shell/platform/windows/flutter_windows_engine.h b/shell/platform/windows/flutter_windows_engine.h index 71233c318524c..1512870a4e710 100644 --- a/shell/platform/windows/flutter_windows_engine.h +++ b/shell/platform/windows/flutter_windows_engine.h @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include "flutter/shell/platform/common/accessibility_bridge.h" @@ -72,11 +74,22 @@ class FlutterWindowsEngine { FlutterWindowsEngine(FlutterWindowsEngine const&) = delete; FlutterWindowsEngine& operator=(FlutterWindowsEngine const&) = delete; - // Starts running the engine with the given entrypoint. If null, defaults to - // main(). + // Starts running the entrypoint function specifed in the project bundle. If + // unspecified, defaults to main(). // // Returns false if the engine couldn't be started. - bool RunWithEntrypoint(const char* entrypoint); + bool Run(); + + // Starts running the engine with the given entrypoint. If the empty string + // is specified, defaults to the entrypoint function specified in the project + // bundle, or main() if both are unspecified. + // + // Returns false if the engine couldn't be started or if conflicting, + // non-default values are passed here and in the project bundle.. + // + // DEPRECATED: Prefer setting the entrypoint in the FlutterProjectBundle + // passed to the constructor and calling the no-parameter overload. + bool Run(std::string_view entrypoint); // Returns true if the engine is currently running. bool running() { return engine_ != nullptr; } diff --git a/shell/platform/windows/flutter_windows_engine_unittests.cc b/shell/platform/windows/flutter_windows_engine_unittests.cc index fad87e75e1846..191740f213ebd 100644 --- a/shell/platform/windows/flutter_windows_engine_unittests.cc +++ b/shell/platform/windows/flutter_windows_engine_unittests.cc @@ -129,7 +129,7 @@ TEST(FlutterWindowsEngine, RunDoesExpectedInitialization) { // Set the AngleSurfaceManager to !nullptr to test ANGLE rendering. modifier.SetSurfaceManager(reinterpret_cast(1)); - engine->RunWithEntrypoint(nullptr); + engine->Run(); EXPECT_TRUE(run_called); EXPECT_TRUE(update_locales_called); @@ -206,7 +206,7 @@ TEST(FlutterWindowsEngine, RunWithoutANGLEUsesSoftware) { // Set the AngleSurfaceManager to nullptr to test software fallback path. modifier.SetSurfaceManager(nullptr); - engine->RunWithEntrypoint(nullptr); + engine->Run(); EXPECT_TRUE(run_called); @@ -351,7 +351,7 @@ TEST(FlutterWindowsEngine, AddPluginRegistrarDestructionCallback) { MockEmbedderApiForKeyboard(modifier, std::make_shared()); - engine->RunWithEntrypoint(nullptr); + engine->Run(); // Verify that destruction handlers don't overwrite each other. int result1 = 0; diff --git a/shell/platform/windows/flutter_windows_unittests.cc b/shell/platform/windows/flutter_windows_unittests.cc index 7b21046eac897..00520426c1897 100644 --- a/shell/platform/windows/flutter_windows_unittests.cc +++ b/shell/platform/windows/flutter_windows_unittests.cc @@ -30,7 +30,7 @@ TEST(WindowsNoFixtureTest, GetTextureRegistrar) { TEST_F(WindowsTest, LaunchMain) { auto& context = GetContext(); WindowsConfigBuilder builder(context); - ViewControllerPtr controller{builder.LaunchEngine()}; + ViewControllerPtr controller{builder.Run()}; ASSERT_NE(controller, nullptr); // Run for 1 second, then shut down. @@ -41,5 +41,56 @@ TEST_F(WindowsTest, LaunchMain) { std::this_thread::sleep_for(std::chrono::seconds(1)); } +TEST_F(WindowsTest, LaunchCustomEntrypoint) { + auto& context = GetContext(); + WindowsConfigBuilder builder(context); + builder.SetDartEntrypoint("customEntrypoint"); + ViewControllerPtr controller{builder.Run()}; + ASSERT_NE(controller, nullptr); + + // Run for 1 second, then shut down. + // + // TODO(cbracken): Support registring a native function we can use to + // determine that execution has made it to a specific point in the Dart + // code. https://github.com/flutter/flutter/issues/109242 + std::this_thread::sleep_for(std::chrono::seconds(1)); +} + +// Verify that engine launches with the custom entrypoint specified in the +// FlutterDesktopEngineRun parameter when no entrypoint is specified in +// FlutterDesktopEngineProperties.dart_entrypoint. +// +// TODO(cbracken): https://github.com/flutter/flutter/issues/109285 +TEST_F(WindowsTest, LaunchCustomEntrypointInEngineRunInvocation) { + auto& context = GetContext(); + WindowsConfigBuilder builder(context); + EnginePtr engine{builder.InitializeEngine()}; + ASSERT_NE(engine, nullptr); + + ASSERT_TRUE(FlutterDesktopEngineRun(engine.get(), "customEntrypoint")); + + // Run for 1 second, then shut down. + // + // TODO(cbracken): Support registring a native function we can use to + // determine that execution has made it to a specific point in the Dart + // code. https://github.com/flutter/flutter/issues/109242 + std::this_thread::sleep_for(std::chrono::seconds(1)); +} + +// Verify that engine fails to launch when a conflicting entrypoint in +// FlutterDesktopEngineProperties.dart_entrypoint and the +// FlutterDesktopEngineRun parameter. +// +// TODO(cbracken): https://github.com/flutter/flutter/issues/109285 +TEST_F(WindowsTest, LaunchConflictingCustomEntrypoints) { + auto& context = GetContext(); + WindowsConfigBuilder builder(context); + builder.SetDartEntrypoint("customEntrypoint"); + EnginePtr engine{builder.InitializeEngine()}; + ASSERT_NE(engine, nullptr); + + ASSERT_FALSE(FlutterDesktopEngineRun(engine.get(), "conflictingEntrypoint")); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/flutter_windows_view_unittests.cc b/shell/platform/windows/flutter_windows_view_unittests.cc index cf823c6357718..9513322daa2b7 100644 --- a/shell/platform/windows/flutter_windows_view_unittests.cc +++ b/shell/platform/windows/flutter_windows_view_unittests.cc @@ -90,7 +90,7 @@ std::unique_ptr GetTestEngine() { MockEmbedderApiForKeyboard(modifier, key_response_controller); - engine->RunWithEntrypoint(nullptr); + engine->Run(); return engine; } diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index f532c1a92af85..4ce5ba4724e06 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -524,7 +524,7 @@ class KeyboardTester { MockEmbedderApiForKeyboard(modifier, key_response_controller); - engine->RunWithEntrypoint(nullptr); + engine->Run(); return engine; } diff --git a/shell/platform/windows/public/flutter_windows.h b/shell/platform/windows/public/flutter_windows.h index b5d2738778e5a..ca1655f1e9f40 100644 --- a/shell/platform/windows/public/flutter_windows.h +++ b/shell/platform/windows/public/flutter_windows.h @@ -47,6 +47,14 @@ typedef struct { // it will be ignored in that case. const wchar_t* aot_library_path; + // The name of the top-level Dart entrypoint function. If null or the empty + // string, 'main' is assumed. If a custom entrypoint is used, this parameter + // must specifiy the name of a top-level function in the same Dart library as + // the app's main() function. Custom entrypoint functions must be decorated + // with `@pragma('vm:entry-point')` to ensure the method is not tree-shaken + // by the Dart compiler. + const char* dart_entrypoint; + // Number of elements in the array passed in as dart_entrypoint_argv. int dart_entrypoint_argc; @@ -129,13 +137,19 @@ FLUTTER_EXPORT FlutterDesktopEngineRef FlutterDesktopEngineCreate( // |engine| is no longer valid after this call. FLUTTER_EXPORT bool FlutterDesktopEngineDestroy(FlutterDesktopEngineRef engine); -// Starts running the given engine instance and optional entry point in the Dart -// project. If the entry point is null, defaults to main(). +// Starts running the given engine instance. +// +// The entry_point parameter is deprecated but preserved for +// backward-compatibility. If desired, a custom Dart entrypoint function can be +// set in the dart_entrypoint field of the FlutterDesktopEngineProperties +// struct passed to FlutterDesktopEngineCreate. // -// If provided, entry_point must be the name of a top-level function from the +// If sprecified, entry_point must be the name of a top-level function from the // same Dart library that contains the app's main() function, and must be // decorated with `@pragma(vm:entry-point)` to ensure the method is not -// tree-shaken by the Dart compiler. +// tree-shaken by the Dart compiler. If conflicting non-null values are passed +// to this function and via the FlutterDesktopEngineProperties struct, the run +// will fail. // // Returns false if running the engine failed. FLUTTER_EXPORT bool FlutterDesktopEngineRun(FlutterDesktopEngineRef engine, diff --git a/shell/platform/windows/testing/windows_test_config_builder.cc b/shell/platform/windows/testing/windows_test_config_builder.cc index 69843ea5bb061..c3c2d0ecf9680 100644 --- a/shell/platform/windows/testing/windows_test_config_builder.cc +++ b/shell/platform/windows/testing/windows_test_config_builder.cc @@ -22,6 +22,13 @@ WindowsConfigBuilder::WindowsConfigBuilder(WindowsTestContext& context) WindowsConfigBuilder::~WindowsConfigBuilder() = default; +void WindowsConfigBuilder::SetDartEntrypoint(std::string_view entrypoint) { + if (entrypoint.empty()) { + return; + } + dart_entrypoint_ = entrypoint; +} + void WindowsConfigBuilder::AddDartEntrypointArgument(std::string_view arg) { if (arg.empty()) { return; @@ -36,6 +43,9 @@ FlutterDesktopEngineProperties WindowsConfigBuilder::GetEngineProperties() engine_properties.assets_path = context_.GetAssetsPath().c_str(); engine_properties.icu_data_path = context_.GetIcuDataPath().c_str(); + // Set Dart entrypoint. + engine_properties.dart_entrypoint = dart_entrypoint_.c_str(); + // Set Dart entrypoint argc, argv. std::vector dart_args; dart_args.reserve(dart_entrypoint_arguments_.size()); @@ -55,7 +65,12 @@ FlutterDesktopEngineProperties WindowsConfigBuilder::GetEngineProperties() return engine_properties; } -ViewControllerPtr WindowsConfigBuilder::LaunchEngine() const { +EnginePtr WindowsConfigBuilder::InitializeEngine() const { + FlutterDesktopEngineProperties engine_properties = GetEngineProperties(); + return EnginePtr(FlutterDesktopEngineCreate(&engine_properties)); +} + +ViewControllerPtr WindowsConfigBuilder::Run() const { InitializeCOM(); EnginePtr engine = InitializeEngine(); @@ -78,10 +93,5 @@ void WindowsConfigBuilder::InitializeCOM() const { FML_CHECK(SUCCEEDED(::CoInitializeEx(nullptr, COINIT_MULTITHREADED))); } -EnginePtr WindowsConfigBuilder::InitializeEngine() const { - FlutterDesktopEngineProperties engine_properties = GetEngineProperties(); - return EnginePtr(FlutterDesktopEngineCreate(&engine_properties)); -} - } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/testing/windows_test_config_builder.h b/shell/platform/windows/testing/windows_test_config_builder.h index 0a1bfb400002e..33b2660b2f147 100644 --- a/shell/platform/windows/testing/windows_test_config_builder.h +++ b/shell/platform/windows/testing/windows_test_config_builder.h @@ -52,22 +52,29 @@ class WindowsConfigBuilder { // Returns the desktop engine properties configured for this test. FlutterDesktopEngineProperties GetEngineProperties() const; + // Sets the Dart entrypoint to the specified value. + // + // If not set, the default entrypoint (main) is used. Custom Dart entrypoints + // must be decorated with `@pragma('vm:entry-point')`. + void SetDartEntrypoint(std::string_view entrypoint); + // Adds an argument to the Dart entrypoint arguments List. void AddDartEntrypointArgument(std::string_view arg); + // Returns a configured and initialized engine. + EnginePtr InitializeEngine() const; + // Returns a configured and initialized view controller running the default // Dart entrypoint. - ViewControllerPtr LaunchEngine() const; + ViewControllerPtr Run() const; private: // Initialize COM, so that it is available for use in the library and/or // plugins. void InitializeCOM() const; - // Returns a configured and initialized engine. - EnginePtr InitializeEngine() const; - WindowsTestContext& context_; + std::string dart_entrypoint_; std::vector dart_entrypoint_arguments_; FML_DISALLOW_COPY_AND_ASSIGN(WindowsConfigBuilder);