diff --git a/lib/ui/hooks.dart b/lib/ui/hooks.dart index 6ffcea0f93d72..273f44edaa0e6 100644 --- a/lib/ui/hooks.dart +++ b/lib/ui/hooks.dart @@ -126,10 +126,14 @@ typedef _ListStringArgFunction(List args); @pragma('vm:entry-point') // ignore: unused_element void _runMainZoned(Function startMainIsolateFunction, + Function? dartPluginRegistrant, Function userMainFunction, List args) { startMainIsolateFunction(() { runZonedGuarded(() { + if (dartPluginRegistrant != null) { + dartPluginRegistrant(); + } if (userMainFunction is _ListStringArgFunction) { (userMainFunction as dynamic)(args); } else { diff --git a/runtime/dart_isolate.cc b/runtime/dart_isolate.cc index 9cb2fce5e91a6..686e88f3a3534 100644 --- a/runtime/dart_isolate.cc +++ b/runtime/dart_isolate.cc @@ -674,6 +674,7 @@ bool DartIsolate::MarkIsolateRunnable() { [[nodiscard]] static bool InvokeMainEntrypoint( Dart_Handle user_entrypoint_function, + Dart_Handle plugin_registrant_function, Dart_Handle args) { if (tonic::LogIfError(user_entrypoint_function)) { FML_LOG(ERROR) << "Could not resolve main entrypoint function."; @@ -691,7 +692,8 @@ bool DartIsolate::MarkIsolateRunnable() { if (tonic::LogIfError(tonic::DartInvokeField( Dart_LookupLibrary(tonic::ToDart("dart:ui")), "_runMainZoned", - {start_main_isolate_function, user_entrypoint_function, args}))) { + {start_main_isolate_function, plugin_registrant_function, + user_entrypoint_function, args}))) { FML_LOG(ERROR) << "Could not invoke the main entrypoint."; return false; } @@ -716,12 +718,34 @@ bool DartIsolate::RunFromLibrary(std::optional library_name, auto entrypoint_handle = entrypoint.has_value() && !entrypoint.value().empty() ? tonic::ToDart(entrypoint.value().c_str()) : tonic::ToDart("main"); + auto entrypoint_args = tonic::ToDart(args); auto user_entrypoint_function = ::Dart_GetField(library_handle, entrypoint_handle); - auto entrypoint_args = tonic::ToDart(args); - - if (!InvokeMainEntrypoint(user_entrypoint_function, entrypoint_args)) { + // The Dart plugin registrant is a function named `_registerPlugins` + // generated by the Flutter tool. + // + // This function binds a plugin implementation to their platform + // interface based on the configuration of the app's pubpec.yaml, and the + // plugin's pubspec.yaml. + // + // Since this function may or may not be defined. Check that it is a top + // level function, and call it in hooks.dart before the main entrypoint + // function. + // + // If it's not defined, then just call the main entrypoint function + // as usual. + // + // This allows embeddings to change the name of the entrypoint function. + auto plugin_registrant_function = + ::Dart_GetField(library_handle, tonic::ToDart("_registerPlugins")); + + if (Dart_IsError(plugin_registrant_function)) { + plugin_registrant_function = Dart_Null(); + } + + if (!InvokeMainEntrypoint(user_entrypoint_function, + plugin_registrant_function, entrypoint_args)) { return false; } diff --git a/runtime/dart_isolate_unittests.cc b/runtime/dart_isolate_unittests.cc index 3b25c02dec0ae..98c52b4989d0a 100644 --- a/runtime/dart_isolate_unittests.cc +++ b/runtime/dart_isolate_unittests.cc @@ -597,5 +597,39 @@ TEST_F(DartIsolateTest, DISABLED_ValidLoadingUnitSucceeds) { Wait(); } +TEST_F(DartIsolateTest, DartPluginRegistrantIsCalled) { + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); + + std::vector messages; + fml::AutoResetWaitableEvent latch; + + AddNativeCallback( + "PassMessage", + CREATE_NATIVE_ENTRY(([&latch, &messages](Dart_NativeArguments args) { + auto message = tonic::DartConverter::FromDart( + Dart_GetNativeArgument(args, 0)); + messages.push_back(message); + latch.Signal(); + }))); + + const auto settings = CreateSettingsForFixture(); + auto vm_ref = DartVMRef::Create(settings); + auto thread = CreateNewThread(); + TaskRunners task_runners(GetCurrentTestName(), // + thread, // + thread, // + thread, // + thread // + ); + auto isolate = RunDartCodeInIsolate(vm_ref, settings, task_runners, + "mainForPluginRegistrantTest", {}, + GetFixturesPath()); + ASSERT_TRUE(isolate); + ASSERT_EQ(isolate->get()->GetPhase(), DartIsolate::Phase::Running); + latch.Wait(); + ASSERT_EQ(messages.size(), 1u); + ASSERT_EQ(messages[0], "_registerPlugins was called"); +} + } // namespace testing } // namespace flutter diff --git a/runtime/fixtures/runtime_test.dart b/runtime/fixtures/runtime_test.dart index 9e137e1fb6acb..9a0169fb0d1ed 100644 --- a/runtime/fixtures/runtime_test.dart +++ b/runtime/fixtures/runtime_test.dart @@ -10,8 +10,7 @@ import 'dart:ui'; import 'split_lib_test.dart' deferred as splitlib; -void main() { -} +void main() {} @pragma('vm:entry-point') void sayHi() { @@ -115,3 +114,24 @@ void testCanConvertListOfInts(List args){ args[2] == 3 && args[3] == 4); } + +bool didCallRegistrantBeforeEntrypoint = false; + +// Test the Dart plugin registrant. +// _registerPlugins requires the entrypoint annotation, so the compiler doesn't tree shake it. +@pragma('vm:entry-point') +void _registerPlugins() { // ignore: unused_element + if (didCallRegistrantBeforeEntrypoint) { + throw '_registerPlugins is being called twice'; + } + didCallRegistrantBeforeEntrypoint = true; +} + +@pragma('vm:entry-point') +void mainForPluginRegistrantTest() { // ignore: unused_element + if (didCallRegistrantBeforeEntrypoint) { + passMessage('_registerPlugins was called'); + } else { + passMessage('_registerPlugins was not called'); + } +}