diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 2dfc3857d0e25..0ad24986db20c 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1123,6 +1123,9 @@ FILE: ../../../flutter/lib/ui/window/platform_message_response.cc FILE: ../../../flutter/lib/ui/window/platform_message_response.h FILE: ../../../flutter/lib/ui/window/platform_message_response_dart.cc FILE: ../../../flutter/lib/ui/window/platform_message_response_dart.h +FILE: ../../../flutter/lib/ui/window/platform_message_response_dart_port.cc +FILE: ../../../flutter/lib/ui/window/platform_message_response_dart_port.h +FILE: ../../../flutter/lib/ui/window/platform_message_response_dart_port_unittests.cc FILE: ../../../flutter/lib/ui/window/platform_message_response_dart_unittests.cc FILE: ../../../flutter/lib/ui/window/pointer_data.cc FILE: ../../../flutter/lib/ui/window/pointer_data.h diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index 6e5c8296d3793..fb2d801241927 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -124,6 +124,8 @@ source_set("ui") { "window/platform_message_response.h", "window/platform_message_response_dart.cc", "window/platform_message_response_dart.h", + "window/platform_message_response_dart_port.cc", + "window/platform_message_response_dart_port.h", "window/pointer_data.cc", "window/pointer_data.h", "window/pointer_data_packet.cc", @@ -140,6 +142,7 @@ source_set("ui") { public_deps = [ "//flutter/flow", + "//flutter/shell/common:platform_message_handler", "//flutter/third_party/txt", ] @@ -230,6 +233,7 @@ if (enable_unittests) { "painting/single_frame_codec_unittests.cc", "semantics/semantics_update_builder_unittests.cc", "window/platform_configuration_unittests.cc", + "window/platform_message_response_dart_port_unittests.cc", "window/platform_message_response_dart_unittests.cc", "window/pointer_data_packet_converter_unittests.cc", ] diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc index fead4f06814a6..7a1eea6bff344 100644 --- a/lib/ui/dart_ui.cc +++ b/lib/ui/dart_ui.cc @@ -100,6 +100,9 @@ typedef CanvasPath Path; V(PlatformConfigurationNativeApi::ComputePlatformResolvedLocale, 1) \ V(PlatformConfigurationNativeApi::SendPlatformMessage, 3) \ V(PlatformConfigurationNativeApi::RespondToPlatformMessage, 2) \ + V(PlatformConfigurationNativeApi::GetRootIsolateToken, 0) \ + V(PlatformConfigurationNativeApi::RegisterBackgroundIsolate, 1) \ + V(PlatformConfigurationNativeApi::SendPortPlatformMessage, 4) \ V(DartRuntimeHooks::Logger_PrintDebugString, 1) \ V(DartRuntimeHooks::Logger_PrintString, 1) \ V(DartRuntimeHooks::ScheduleMicrotask, 1) \ diff --git a/lib/ui/fixtures/ui_test.dart b/lib/ui/fixtures/ui_test.dart index b04ef7be99fe4..6ccd2388ce877 100644 --- a/lib/ui/fixtures/ui_test.dart +++ b/lib/ui/fixtures/ui_test.dart @@ -5,6 +5,8 @@ import 'dart:async'; import 'dart:typed_data'; import 'dart:ui'; +import 'dart:isolate'; +import 'dart:ffi'; void main() {} @@ -234,6 +236,21 @@ void frameCallback(_Image, int) { print('called back'); } +@pragma('vm:entry-point') +void platformMessagePortResponseTest() async { + ReceivePort receivePort = ReceivePort(); + _callPlatformMessageResponseDartPort(receivePort.sendPort.nativePort); + List resultList = await receivePort.first; + int identifier = resultList[0] as int; + Uint8List? bytes = resultList[1] as Uint8List?; + ByteData result = ByteData.sublistView(bytes!); + if (result.lengthInBytes == 100) { + _finishCallResponse(true); + } else { + _finishCallResponse(false); + } +} + @pragma('vm:entry-point') void platformMessageResponseTest() { _callPlatformMessageResponseDart((ByteData? result) { @@ -246,6 +263,7 @@ void platformMessageResponseTest() { }); } +void _callPlatformMessageResponseDartPort(int port) native 'CallPlatformMessageResponseDartPort'; void _callPlatformMessageResponseDart(void Function(ByteData? result) callback) native 'CallPlatformMessageResponseDart'; void _finishCallResponse(bool didPass) native 'FinishCallResponse'; @@ -884,9 +902,55 @@ void hooksTests() { expectEquals(result, true); }); + test('root isolate token', () async { + if (RootIsolateToken.instance == null) { + throw Exception('We should have a token on a root isolate.'); + } + ReceivePort receivePort = ReceivePort(); + Isolate.spawn(_backgroundRootIsolateTestMain, receivePort.sendPort); + bool didPass = await receivePort.first as bool; + if (!didPass) { + throw Exception('Background isolate found a root isolate id.'); + } + }); + + test('send port message without registering', () async { + ReceivePort receivePort = ReceivePort(); + Isolate.spawn(_backgroundIsolateSendWithoutRegistering, receivePort.sendPort); + bool didError = await receivePort.first as bool; + if (!didError) { + throw Exception('Expected an error when not registering a root isolate and sending port messages.'); + } + }); + _finish(); } +/// Sends `true` on [port] if the isolate executing the function is not a root +/// isolate. +void _backgroundRootIsolateTestMain(SendPort port) { + port.send(RootIsolateToken.instance == null); +} + +/// Sends `true` on [port] if [PlatformDispatcher.sendPortPlatformMessage] +/// throws an exception without calling +/// [PlatformDispatcher.registerBackgroundIsolate]. +void _backgroundIsolateSendWithoutRegistering(SendPort port) { + bool didError = false; + ReceivePort messagePort = ReceivePort(); + try { + PlatformDispatcher.instance.sendPortPlatformMessage( + 'foo', + null, + 1, + messagePort.sendPort, + ); + } catch (_) { + didError = true; + } + port.send(didError); +} + typedef _Callback = void Function(T result); typedef _Callbacker = String? Function(_Callback callback); diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index 7c0af5dec2f54..a24b2f60f8318 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -73,6 +73,24 @@ const String _kFlutterKeyDataChannel = 'flutter/keydata'; ByteData? _wrapUnmodifiableByteData(ByteData? byteData) => byteData == null ? null : UnmodifiableByteDataView(byteData); +/// A token that represents a root isolate. +class RootIsolateToken { + RootIsolateToken._(this._token); + + /// An enumeration representing the root isolate (0 if not a root isolate). + final int _token; + + /// The token for the root isolate that is executing this Dart code. If this + /// Dart code is not executing on a root isolate [instance] will be null. + static final RootIsolateToken? instance = () { + final int token = __getRootIsolateToken(); + return token == 0 ? null : RootIsolateToken._(token); + }(); + + @FfiNative('PlatformConfigurationNativeApi::GetRootIsolateToken') + external static int __getRootIsolateToken(); +} + /// Platform event dispatcher singleton. /// /// The most basic interface to the host operating system's interface. @@ -532,12 +550,51 @@ class PlatformDispatcher { } } - String? _sendPlatformMessage(String name,PlatformMessageResponseCallback? callback, ByteData? data) => + String? _sendPlatformMessage(String name, PlatformMessageResponseCallback? callback, ByteData? data) => __sendPlatformMessage(name, callback, data); @FfiNative('PlatformConfigurationNativeApi::SendPlatformMessage') external static String? __sendPlatformMessage(String name, PlatformMessageResponseCallback? callback, ByteData? data); + /// Sends a message to a platform-specific plugin via a [SendPort]. + /// + /// This operates similarly to [sendPlatformMessage] but is used when sending + /// messages from background isolates. The [port] parameter allows Flutter to + /// know which isolate to send the result to. The [name] parameter is the name + /// of the channel communication will happen on. The [data] parameter is the + /// payload of the message. The [identifier] parameter is a unique integer + /// assigned to the message. + void sendPortPlatformMessage( + String name, + ByteData? data, + int identifier, + SendPort port) { + final String? error = + _sendPortPlatformMessage(name, identifier, port.nativePort, data); + if (error != null) { + throw Exception(error); + } + } + + String? _sendPortPlatformMessage(String name, int identifier, int port, ByteData? data) => + __sendPortPlatformMessage(name, identifier, port, data); + + @FfiNative('PlatformConfigurationNativeApi::SendPortPlatformMessage') + external static String? __sendPortPlatformMessage(String name, int identifier, int port, ByteData? data); + + /// Registers the current isolate with the isolate identified with by the + /// [token]. This is required if platform channels are to be used on a + /// background isolate. + void registerBackgroundIsolate(RootIsolateToken token) { + if (!Platform.isIOS) { + // Issue: https://github.com/flutter/flutter/issues/13937 + throw UnimplementedError("Platform doesn't yet support platform channels on background isolates."); + } + __registerBackgroundIsolate(token._token); + } + @FfiNative('PlatformConfigurationNativeApi::RegisterBackgroundIsolate') + external static void __registerBackgroundIsolate(int rootIsolateId); + /// Called whenever this platform dispatcher receives a message from a /// platform-specific plugin. /// diff --git a/lib/ui/ui_dart_state.cc b/lib/ui/ui_dart_state.cc index ed60f8cbf2c5b..208951c3238b6 100644 --- a/lib/ui/ui_dart_state.cc +++ b/lib/ui/ui_dart_state.cc @@ -107,6 +107,7 @@ UIDartState* UIDartState::Current() { void UIDartState::SetPlatformConfiguration( std::unique_ptr platform_configuration) { + FML_DCHECK(IsRootIsolate()); platform_configuration_ = std::move(platform_configuration); if (platform_configuration_) { platform_configuration_->client()->UpdateIsolateDescription(debug_name_, @@ -114,6 +115,12 @@ void UIDartState::SetPlatformConfiguration( } } +void UIDartState::SetPlatformMessageHandler( + std::weak_ptr handler) { + FML_DCHECK(!IsRootIsolate()); + platform_message_handler_ = handler; +} + const TaskRunners& UIDartState::GetTaskRunners() const { return context_.task_runners; } @@ -214,4 +221,27 @@ bool UIDartState::enable_skparagraph() const { return enable_skparagraph_; } +Dart_Handle UIDartState::HandlePlatformMessage( + std::unique_ptr message) { + if (platform_configuration_) { + platform_configuration_->client()->HandlePlatformMessage( + std::move(message)); + } else { + std::shared_ptr handler = + platform_message_handler_.lock(); + if (handler) { + handler->HandlePlatformMessage(std::move(message)); + } else { + return tonic::ToDart( + "No platform channel handler registered for background isolate."); + } + } + + return Dart_Null(); +} + +int64_t UIDartState::GetRootIsolateToken() const { + return IsRootIsolate() ? reinterpret_cast(this) : 0; +} + } // namespace flutter diff --git a/lib/ui/ui_dart_state.h b/lib/ui/ui_dart_state.h index bdcb49beadf4a..95ccd9b3e43f4 100644 --- a/lib/ui/ui_dart_state.h +++ b/lib/ui/ui_dart_state.h @@ -20,6 +20,7 @@ #include "flutter/lib/ui/painting/image_decoder.h" #include "flutter/lib/ui/snapshot_delegate.h" #include "flutter/lib/ui/volatile_path_tracker.h" +#include "flutter/shell/common/platform_message_handler.h" #include "third_party/dart/runtime/include/dart_api.h" #include "third_party/skia/include/gpu/GrDirectContext.h" #include "third_party/tonic/dart_microtask_queue.h" @@ -30,6 +31,7 @@ namespace flutter { class FontSelector; class ImageGeneratorRegistry; class PlatformConfiguration; +class PlatformMessage; class UIDartState : public tonic::DartState { public: @@ -106,6 +108,10 @@ class UIDartState : public tonic::DartState { return platform_configuration_.get(); } + void SetPlatformMessageHandler(std::weak_ptr handler); + + Dart_Handle HandlePlatformMessage(std::unique_ptr message); + const TaskRunners& GetTaskRunners() const; void ScheduleMicrotask(Dart_Handle handle); @@ -153,6 +159,10 @@ class UIDartState : public tonic::DartState { return unhandled_exception_callback_; } + /// Returns a enumeration that that uniquely represents this root isolate. + /// Returns `0` if called from a non-root isolate. + int64_t GetRootIsolateToken() const; + protected: UIDartState(TaskObserverAdd add_callback, TaskObserverRemove remove_callback, @@ -181,6 +191,7 @@ class UIDartState : public tonic::DartState { const bool is_root_isolate_; std::string debug_name_; std::unique_ptr platform_configuration_; + std::weak_ptr platform_message_handler_; tonic::DartMicrotaskQueue microtask_queue_; UnhandledExceptionCallback unhandled_exception_callback_; LogMessageCallback log_message_callback_; diff --git a/lib/ui/window/platform_configuration.cc b/lib/ui/window/platform_configuration.cc index 1a98b204a1456..0b61621ea9c17 100644 --- a/lib/ui/window/platform_configuration.cc +++ b/lib/ui/window/platform_configuration.cc @@ -9,6 +9,7 @@ #include "flutter/lib/ui/compositing/scene.h" #include "flutter/lib/ui/ui_dart_state.h" #include "flutter/lib/ui/window/platform_message_response_dart.h" +#include "flutter/lib/ui/window/platform_message_response_dart_port.h" #include "flutter/lib/ui/window/viewport_metrics.h" #include "flutter/lib/ui/window/window.h" #include "third_party/tonic/converter/dart_converter.h" @@ -284,6 +285,25 @@ void PlatformConfigurationNativeApi::SetNeedsReportTimings(bool value) { ->SetNeedsReportTimings(value); } +namespace { +Dart_Handle HandlePlatformMessage( + UIDartState* dart_state, + const std::string& name, + Dart_Handle data_handle, + const fml::RefPtr& response) { + if (Dart_IsNull(data_handle)) { + return dart_state->HandlePlatformMessage( + std::make_unique(name, response)); + } else { + tonic::DartByteData data(data_handle); + const uint8_t* buffer = static_cast(data.data()); + return dart_state->HandlePlatformMessage(std::make_unique( + name, fml::MallocMapping::Copy(buffer, data.length_in_bytes()), + response)); + } +} +} // namespace + Dart_Handle PlatformConfigurationNativeApi::SendPlatformMessage( const std::string& name, Dart_Handle callback, @@ -292,7 +312,8 @@ Dart_Handle PlatformConfigurationNativeApi::SendPlatformMessage( if (!dart_state->platform_configuration()) { return tonic::ToDart( - "Platform messages can only be sent from the main isolate"); + "SendPlatformMessage only works on the root isolate, see " + "SendPortPlatformMessage."); } fml::RefPtr response; @@ -301,18 +322,30 @@ Dart_Handle PlatformConfigurationNativeApi::SendPlatformMessage( tonic::DartPersistentValue(dart_state, callback), dart_state->GetTaskRunners().GetUITaskRunner(), name); } - if (Dart_IsNull(data_handle)) { - dart_state->platform_configuration()->client()->HandlePlatformMessage( - std::make_unique(name, response)); - } else { - tonic::DartByteData data(data_handle); - const uint8_t* buffer = static_cast(data.data()); - dart_state->platform_configuration()->client()->HandlePlatformMessage( - std::make_unique( - name, fml::MallocMapping::Copy(buffer, data.length_in_bytes()), - response)); + + return HandlePlatformMessage(dart_state, name, data_handle, response); +} + +Dart_Handle PlatformConfigurationNativeApi::SendPortPlatformMessage( + const std::string& name, + Dart_Handle identifier, + Dart_Handle send_port, + Dart_Handle data_handle) { + // This can be executed on any isolate. + UIDartState* dart_state = UIDartState::Current(); + + int64_t c_send_port = tonic::DartConverter::FromDart(send_port); + if (c_send_port == ILLEGAL_PORT) { + return tonic::ToDart("Invalid port specified"); } + fml::RefPtr response = + fml::MakeRefCounted( + c_send_port, tonic::DartConverter::FromDart(identifier), + name); + + HandlePlatformMessage(dart_state, name, data_handle, response); + return Dart_Null(); } @@ -396,4 +429,23 @@ std::string PlatformConfigurationNativeApi::DefaultRouteName() { ->DefaultRouteName(); } +int64_t PlatformConfigurationNativeApi::GetRootIsolateToken() { + UIDartState* dart_state = UIDartState::Current(); + FML_DCHECK(dart_state); + return dart_state->GetRootIsolateToken(); +} + +void PlatformConfigurationNativeApi::RegisterBackgroundIsolate( + int64_t root_isolate_token) { + UIDartState* dart_state = UIDartState::Current(); + FML_DCHECK(dart_state && !dart_state->IsRootIsolate()); + auto platform_message_handler = + (*static_cast*>( + Dart_CurrentIsolateGroupData())); + FML_DCHECK(platform_message_handler); + auto weak_platform_message_handler = + platform_message_handler->GetPlatformMessageHandler(root_isolate_token); + dart_state->SetPlatformMessageHandler(weak_platform_message_handler); +} + } // namespace flutter diff --git a/lib/ui/window/platform_configuration.h b/lib/ui/window/platform_configuration.h index 632bac5c9d1fa..d482c756984d3 100644 --- a/lib/ui/window/platform_configuration.h +++ b/lib/ui/window/platform_configuration.h @@ -23,6 +23,7 @@ namespace flutter { class FontCollection; class PlatformMessage; +class PlatformMessageHandler; class Scene; //-------------------------------------------------------------------------- @@ -442,6 +443,20 @@ class PlatformConfiguration final { pending_responses_; }; +//---------------------------------------------------------------------------- +/// An inteface that the result of `Dart_CurrentIsolateGroupData` should +/// implement for registering background isolates to work. +class PlatformMessageHandlerStorage { + public: + virtual ~PlatformMessageHandlerStorage() = default; + virtual void SetPlatformMessageHandler( + int64_t root_isolate_token, + std::weak_ptr handler) = 0; + + virtual std::weak_ptr GetPlatformMessageHandler( + int64_t root_isolate_token) const = 0; +}; + //---------------------------------------------------------------------------- // API exposed as FFI calls in Dart. // @@ -475,6 +490,11 @@ class PlatformConfigurationNativeApi { Dart_Handle callback, Dart_Handle data_handle); + static Dart_Handle SendPortPlatformMessage(const std::string& name, + Dart_Handle identifier, + Dart_Handle send_port, + Dart_Handle data_handle); + static void RespondToPlatformMessage(int response_id, const tonic::DartByteData& data); @@ -494,6 +514,10 @@ class PlatformConfigurationNativeApi { /// mode does. /// static int RequestDartPerformanceMode(int mode); + + static int64_t GetRootIsolateToken(); + + static void RegisterBackgroundIsolate(int64_t root_isolate_token); }; } // namespace flutter diff --git a/lib/ui/window/platform_message_response_dart_port.cc b/lib/ui/window/platform_message_response_dart_port.cc new file mode 100644 index 0000000000000..ffa4a0d79cc74 --- /dev/null +++ b/lib/ui/window/platform_message_response_dart_port.cc @@ -0,0 +1,77 @@ +// 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/lib/ui/window/platform_message_response_dart_port.h" + +#include +#include + +#include "flutter/common/task_runners.h" +#include "flutter/fml/make_copyable.h" +#include "flutter/fml/trace_event.h" +#include "third_party/dart/runtime/include/dart_native_api.h" +#include "third_party/tonic/converter/dart_converter.h" +#include "third_party/tonic/dart_state.h" +#include "third_party/tonic/logging/dart_invoke.h" +#include "third_party/tonic/typed_data/dart_byte_data.h" + +namespace flutter { +namespace { +void FreeFinalizer(void* isolate_callback_data, void* peer) { + free(peer); +} +} // namespace + +PlatformMessageResponseDartPort::PlatformMessageResponseDartPort( + Dart_Port send_port, + int64_t identifier, + const std::string& channel) + : send_port_(send_port), identifier_(identifier), channel_(channel) { + FML_DCHECK(send_port != ILLEGAL_PORT); +} + +void PlatformMessageResponseDartPort::Complete( + std::unique_ptr data) { + is_complete_ = true; + Dart_CObject response_identifier = { + .type = Dart_CObject_kInt64, + }; + response_identifier.value.as_int64 = identifier_; + Dart_CObject response_data = { + .type = Dart_CObject_kExternalTypedData, + }; + // TODO(https://github.com/dart-lang/sdk/issues/49827): Move to kTypedData + // when const values are accepted. Also consider using kExternalTypedData only + // when the payload is >= 1KB. + uint8_t* copy = static_cast(malloc(data->GetSize())); + memcpy(copy, data->GetMapping(), data->GetSize()); + response_data.value.as_external_typed_data.type = Dart_TypedData_kUint8; + response_data.value.as_external_typed_data.length = data->GetSize(); + response_data.value.as_external_typed_data.data = copy; + response_data.value.as_external_typed_data.peer = copy; + response_data.value.as_external_typed_data.callback = FreeFinalizer; + + std::array response_values = {&response_identifier, + &response_data}; + + Dart_CObject response = { + .type = Dart_CObject_kArray, + }; + response.value.as_array.length = response_values.size(); + response.value.as_array.values = response_values.data(); + + bool did_send = Dart_PostCObject(send_port_, &response); + FML_CHECK(did_send); +} + +void PlatformMessageResponseDartPort::CompleteEmpty() { + is_complete_ = true; + Dart_CObject response = { + .type = Dart_CObject_kNull, + }; + bool did_send = Dart_PostCObject(send_port_, &response); + FML_CHECK(did_send); +} + +} // namespace flutter diff --git a/lib/ui/window/platform_message_response_dart_port.h b/lib/ui/window/platform_message_response_dart_port.h new file mode 100644 index 0000000000000..f32806980ee48 --- /dev/null +++ b/lib/ui/window/platform_message_response_dart_port.h @@ -0,0 +1,35 @@ +// 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_LIB_UI_PLATFORM_PLATFORM_MESSAGE_RESPONSE_DART_PORT_H_ +#define FLUTTER_LIB_UI_PLATFORM_PLATFORM_MESSAGE_RESPONSE_DART_PORT_H_ + +#include "flutter/fml/message_loop.h" +#include "flutter/lib/ui/window/platform_message_response.h" +#include "third_party/tonic/dart_persistent_value.h" + +namespace flutter { + +/// A \ref PlatformMessageResponse that will respond over a Dart port. +class PlatformMessageResponseDartPort : public PlatformMessageResponse { + FML_FRIEND_MAKE_REF_COUNTED(PlatformMessageResponseDartPort); + + public: + // Callable on any thread. + void Complete(std::unique_ptr data) override; + void CompleteEmpty() override; + + protected: + explicit PlatformMessageResponseDartPort(Dart_Port send_port, + int64_t identifier, + const std::string& channel); + + Dart_Port send_port_; + int64_t identifier_; + const std::string channel_; +}; + +} // namespace flutter + +#endif // FLUTTER_LIB_UI_PLATFORM_PLATFORM_MESSAGE_RESPONSE_DART_PORT_H_ diff --git a/lib/ui/window/platform_message_response_dart_port_unittests.cc b/lib/ui/window/platform_message_response_dart_port_unittests.cc new file mode 100644 index 0000000000000..c30309d6e0c09 --- /dev/null +++ b/lib/ui/window/platform_message_response_dart_port_unittests.cc @@ -0,0 +1,74 @@ +// 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/common/task_runners.h" +#include "flutter/fml/mapping.h" +#include "flutter/fml/synchronization/waitable_event.h" +#include "flutter/lib/ui/window/platform_message_response_dart_port.h" +#include "flutter/runtime/dart_vm.h" +#include "flutter/shell/common/shell_test.h" +#include "flutter/shell/common/thread_host.h" +#include "flutter/testing/testing.h" + +namespace flutter { +namespace testing { + +TEST_F(ShellTest, PlatformMessageResponseDartPort) { + bool did_pass = false; + auto message_latch = std::make_shared(); + TaskRunners task_runners("test", // label + GetCurrentTaskRunner(), // platform + CreateNewThread(), // raster + CreateNewThread(), // ui + CreateNewThread() // io + ); + + auto nativeCallPlatformMessageResponseDartPort = + [ui_task_runner = + task_runners.GetUITaskRunner()](Dart_NativeArguments args) { + auto dart_state = std::make_shared(); + auto response = fml::MakeRefCounted( + tonic::DartConverter::FromDart( + Dart_GetNativeArgument(args, 0)), + 123, "foobar"); + uint8_t* data = static_cast(malloc(100)); + auto mapping = std::make_unique(data, 100); + response->Complete(std::move(mapping)); + }; + + AddNativeCallback( + "CallPlatformMessageResponseDartPort", + CREATE_NATIVE_ENTRY(nativeCallPlatformMessageResponseDartPort)); + + auto nativeFinishCallResponse = [message_latch, + &did_pass](Dart_NativeArguments args) { + did_pass = + tonic::DartConverter::FromDart(Dart_GetNativeArgument(args, 0)); + message_latch->Signal(); + }; + + AddNativeCallback("FinishCallResponse", + CREATE_NATIVE_ENTRY(nativeFinishCallResponse)); + + Settings settings = CreateSettingsForFixture(); + + std::unique_ptr shell = + CreateShell(std::move(settings), std::move(task_runners)); + + ASSERT_TRUE(shell->IsSetup()); + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("platformMessagePortResponseTest"); + + shell->RunEngine(std::move(configuration), [](auto result) { + ASSERT_EQ(result, Engine::RunStatus::Success); + }); + + message_latch->Wait(); + + ASSERT_TRUE(did_pass); + DestroyShell(std::move(shell), std::move(task_runners)); +} + +} // namespace testing +} // namespace flutter diff --git a/lib/web_ui/lib/platform_dispatcher.dart b/lib/web_ui/lib/platform_dispatcher.dart index 043d4999a350e..7c67e7c3d5f4f 100644 --- a/lib/web_ui/lib/platform_dispatcher.dart +++ b/lib/web_ui/lib/platform_dispatcher.dart @@ -16,6 +16,14 @@ typedef PlatformMessageCallback = void Function( typedef PlatformConfigurationChangedCallback = void Function(PlatformConfiguration configuration); typedef ErrorCallback = bool Function(Object exception, StackTrace stackTrace); +// ignore: avoid_classes_with_only_static_members +/// A token that represents a root isolate. +class RootIsolateToken { + static RootIsolateToken? get instance { + throw UnsupportedError('Root isolate not identifiable on web.'); + } +} + abstract class PlatformDispatcher { static PlatformDispatcher get instance => engine.EnginePlatformDispatcher.instance; @@ -49,6 +57,14 @@ abstract class PlatformDispatcher { PlatformMessageResponseCallback? callback, ); + void sendPortPlatformMessage( + String name, + ByteData? data, + int identifier, + Object port); + + void registerBackgroundIsolate(RootIsolateToken token); + PlatformMessageCallback? get onPlatformMessage; set onPlatformMessage(PlatformMessageCallback? callback); diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 311be4fa5e391..5f59713698149 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -343,6 +343,21 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { name, data, _zonedPlatformMessageResponseCallback(callback)); } + @override + void sendPortPlatformMessage( + String name, + ByteData? data, + int identifier, + Object port, + ) { + throw Exception("Isolates aren't supported in web."); + } + + @override + void registerBackgroundIsolate(ui.RootIsolateToken token) { + throw Exception("Isolates aren't supported in web."); + } + // TODO(ianh): Deprecate onPlatformMessage once the framework is moved over // to using channel buffers exclusively. @override diff --git a/runtime/dart_isolate.h b/runtime/dart_isolate.h index 3fe9cf2f81dc4..c51eb7081ab37 100644 --- a/runtime/dart_isolate.h +++ b/runtime/dart_isolate.h @@ -385,6 +385,10 @@ class DartIsolate : public UIDartState { const std::string error_message, bool transient); + DartIsolateGroupData& GetIsolateGroupData(); + + const DartIsolateGroupData& GetIsolateGroupData() const; + private: friend class IsolateConfiguration; class AutoFireClosure { @@ -444,10 +448,6 @@ class DartIsolate : public UIDartState { void OnShutdownCallback(); - DartIsolateGroupData& GetIsolateGroupData(); - - const DartIsolateGroupData& GetIsolateGroupData() const; - // |Dart_IsolateGroupCreateCallback| static Dart_Isolate DartIsolateGroupCreateCallback( const char* advisory_script_uri, diff --git a/runtime/dart_isolate_group_data.cc b/runtime/dart_isolate_group_data.cc index 5850d04ea68b3..3aeff009b855e 100644 --- a/runtime/dart_isolate_group_data.cc +++ b/runtime/dart_isolate_group_data.cc @@ -64,4 +64,21 @@ void DartIsolateGroupData::SetChildIsolatePreparer( child_isolate_preparer_ = value; } +void DartIsolateGroupData::SetPlatformMessageHandler( + int64_t root_isolate_token, + std::weak_ptr handler) { + std::scoped_lock lock(platform_message_handlers_mutex_); + platform_message_handlers_[root_isolate_token] = handler; +} + +std::weak_ptr +DartIsolateGroupData::GetPlatformMessageHandler( + int64_t root_isolate_token) const { + std::scoped_lock lock(platform_message_handlers_mutex_); + auto it = platform_message_handlers_.find(root_isolate_token); + return it == platform_message_handlers_.end() + ? std::weak_ptr() + : it->second; +} + } // namespace flutter diff --git a/runtime/dart_isolate_group_data.h b/runtime/dart_isolate_group_data.h index 4306bca1d6b00..8faef1abf8941 100644 --- a/runtime/dart_isolate_group_data.h +++ b/runtime/dart_isolate_group_data.h @@ -5,17 +5,20 @@ #ifndef FLUTTER_RUNTIME_DART_ISOLATE_GROUP_DATA_H_ #define FLUTTER_RUNTIME_DART_ISOLATE_GROUP_DATA_H_ +#include #include #include #include "flutter/common/settings.h" #include "flutter/fml/closure.h" #include "flutter/fml/memory/ref_ptr.h" +#include "flutter/lib/ui/window/platform_configuration.h" namespace flutter { class DartIsolate; class DartSnapshot; +class PlatformMessageHandler; using ChildIsolatePreparer = std::function; @@ -25,7 +28,7 @@ using ChildIsolatePreparer = std::function; // // This object must be thread safe because the Dart VM can invoke the isolate // group cleanup callback on any thread. -class DartIsolateGroupData { +class DartIsolateGroupData : public PlatformMessageHandlerStorage { public: DartIsolateGroupData(const Settings& settings, fml::RefPtr isolate_snapshot, @@ -53,6 +56,15 @@ class DartIsolateGroupData { void SetChildIsolatePreparer(const ChildIsolatePreparer& value); + // |PlatformMessageHandlerStorage| + void SetPlatformMessageHandler( + int64_t root_isolate_token, + std::weak_ptr handler) override; + + // |PlatformMessageHandlerStorage| + std::weak_ptr GetPlatformMessageHandler( + int64_t root_isolate_token) const override; + private: const Settings settings_; const fml::RefPtr isolate_snapshot_; @@ -62,6 +74,9 @@ class DartIsolateGroupData { ChildIsolatePreparer child_isolate_preparer_; const fml::closure isolate_create_callback_; const fml::closure isolate_shutdown_callback_; + std::map> + platform_message_handlers_; + mutable std::mutex platform_message_handlers_mutex_; FML_DISALLOW_COPY_AND_ASSIGN(DartIsolateGroupData); }; diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index d195f42e8e2ed..292b32ade2342 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -11,6 +11,7 @@ #include "flutter/lib/ui/window/platform_configuration.h" #include "flutter/lib/ui/window/viewport_metrics.h" #include "flutter/lib/ui/window/window.h" +#include "flutter/runtime/dart_isolate_group_data.h" #include "flutter/runtime/isolate_configuration.h" #include "flutter/runtime/runtime_delegate.h" #include "third_party/tonic/dart_message_handler.h" @@ -387,6 +388,11 @@ bool RuntimeController::LaunchRootIsolate( return false; } + // Enable platform channels for background isolates. + strong_root_isolate->GetIsolateGroupData().SetPlatformMessageHandler( + strong_root_isolate->GetRootIsolateToken(), + client_.GetPlatformMessageHandler()); + // The root isolate ivar is weak. root_isolate_ = strong_root_isolate; diff --git a/runtime/runtime_delegate.h b/runtime/runtime_delegate.h index ad680677338e0..679922e29d732 100644 --- a/runtime/runtime_delegate.h +++ b/runtime/runtime_delegate.h @@ -14,6 +14,7 @@ #include "flutter/lib/ui/semantics/semantics_node.h" #include "flutter/lib/ui/text/font_collection.h" #include "flutter/lib/ui/window/platform_message.h" +#include "flutter/shell/common/platform_message_handler.h" #include "third_party/dart/runtime/include/dart_api.h" namespace flutter { @@ -49,6 +50,9 @@ class RuntimeDelegate { virtual void RequestDartDeferredLibrary(intptr_t loading_unit_id) = 0; + virtual std::weak_ptr GetPlatformMessageHandler() + const = 0; + protected: virtual ~RuntimeDelegate(); }; diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index 137b906dfba34..3d6b73ec587e2 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -59,6 +59,12 @@ template("dart_embedder_resources") { } } +source_set("platform_message_handler") { + sources = [ "platform_message_handler.h" ] + public_configs = [ "//flutter:config" ] + deps = [ "//flutter/fml:fml" ] +} + source_set("common") { sources = [ "animator.cc", @@ -75,7 +81,6 @@ source_set("common") { "engine.h", "pipeline.cc", "pipeline.h", - "platform_message_handler.h", "platform_view.cc", "platform_view.h", "pointer_data_dispatcher.cc", @@ -115,6 +120,7 @@ source_set("common") { public_configs = [ "//flutter:config" ] public_deps = [ + ":platform_message_handler", "//flutter/shell/version", "//flutter/third_party/tonic", "//flutter/third_party/txt", diff --git a/shell/common/engine.cc b/shell/common/engine.cc index c2cbc9f512b98..3eeea5ccd0a10 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -540,6 +540,11 @@ void Engine::RequestDartDeferredLibrary(intptr_t loading_unit_id) { return delegate_.RequestDartDeferredLibrary(loading_unit_id); } +std::weak_ptr Engine::GetPlatformMessageHandler() + const { + return delegate_.GetPlatformMessageHandler(); +} + void Engine::LoadDartDeferredLibrary( intptr_t loading_unit_id, std::unique_ptr snapshot_data, diff --git a/shell/common/engine.h b/shell/common/engine.h index 04b968f106c41..cc178c31735e8 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -288,6 +288,12 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { /// This method is primarily provided to allow tests to control /// Any methods that rely on advancing the clock. virtual fml::TimePoint GetCurrentTimePoint() = 0; + + //---------------------------------------------------------------------------- + /// @brief Returns the delegate object that handles PlatformMessage's from + /// Flutter to the host platform (and its responses). + virtual const std::shared_ptr& + GetPlatformMessageHandler() const = 0; }; //---------------------------------------------------------------------------- @@ -902,6 +908,10 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { // |RuntimeDelegate| void RequestDartDeferredLibrary(intptr_t loading_unit_id) override; + // |RuntimeDelegate| + std::weak_ptr GetPlatformMessageHandler() + const override; + void SetNeedsReportTimings(bool value) override; bool HandleLifecyclePlatformMessage(PlatformMessage* message); diff --git a/shell/common/engine_unittests.cc b/shell/common/engine_unittests.cc index 6dfc93c555c36..365012b84f507 100644 --- a/shell/common/engine_unittests.cc +++ b/shell/common/engine_unittests.cc @@ -36,6 +36,8 @@ class MockDelegate : public Engine::Delegate { const std::vector&)); MOCK_METHOD1(RequestDartDeferredLibrary, void(intptr_t)); MOCK_METHOD0(GetCurrentTimePoint, fml::TimePoint()); + MOCK_CONST_METHOD0(GetPlatformMessageHandler, + const std::shared_ptr&()); }; class MockResponse : public PlatformMessageResponse { @@ -61,6 +63,8 @@ class MockRuntimeDelegate : public RuntimeDelegate { std::unique_ptr>( const std::vector&)); MOCK_METHOD1(RequestDartDeferredLibrary, void(intptr_t)); + MOCK_CONST_METHOD0(GetPlatformMessageHandler, + std::weak_ptr()); }; class MockRuntimeController : public RuntimeController { diff --git a/shell/common/platform_message_handler.h b/shell/common/platform_message_handler.h index 8e3f77e448236..725a341e72463 100644 --- a/shell/common/platform_message_handler.h +++ b/shell/common/platform_message_handler.h @@ -7,10 +7,12 @@ #include -#include "flutter/lib/ui/window/platform_message.h" +#include "flutter/fml/mapping.h" namespace flutter { +class PlatformMessage; + /// An interface over the ability to handle PlatformMessages that are being sent /// from Flutter to the host platform. class PlatformMessageHandler { diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 50f81d8d57b92..72f4e52301418 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -604,6 +604,7 @@ void Shell::RunEngine( if (run_result == flutter::Engine::RunStatus::Failure) { FML_LOG(ERROR) << "Could not launch engine with configuration."; } + result(run_result); })); } diff --git a/shell/common/shell.h b/shell/common/shell.h index 5a8138e0ccc5b..431ae99437d23 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -397,11 +397,9 @@ class Shell final : public PlatformView::Delegate, /// @see `CreateCompatibleGenerator` void RegisterImageDecoder(ImageGeneratorFactory factory, int32_t priority); - //---------------------------------------------------------------------------- - /// @brief Returns the delegate object that handles PlatformMessage's from - /// Flutter to the host platform (and its responses). + // |Engine::Delegate| const std::shared_ptr& GetPlatformMessageHandler() - const; + const override; const std::weak_ptr GetVsyncWaiter() const; diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index e3a8339e22033..27fc1f7f4e196 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -210,6 +210,7 @@ source_set("ios_test_flutter_mrc") { deps = [ ":flutter_framework_source", "//flutter/common:common", + "//flutter/lib/ui:ui", "//flutter/shell/common:common", "//flutter/shell/platform/darwin/common:framework_shared", "//flutter/shell/platform/embedder:embedder_as_internal_library", diff --git a/shell/platform/darwin/ios/platform_message_handler_ios.mm b/shell/platform/darwin/ios/platform_message_handler_ios.mm index 7f6efcd63cfc0..f6c4f392d97d4 100644 --- a/shell/platform/darwin/ios/platform_message_handler_ios.mm +++ b/shell/platform/darwin/ios/platform_message_handler_ios.mm @@ -5,6 +5,7 @@ #import "flutter/shell/platform/darwin/ios/platform_message_handler_ios.h" #import "flutter/fml/trace_event.h" +#import "flutter/lib/ui/window/platform_message.h" #import "flutter/shell/platform/darwin/common/buffer_conversions.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h" @@ -47,10 +48,13 @@ - (void)dispatch:(dispatch_block_t)block { : task_runners_(task_runners) {} void PlatformMessageHandlerIos::HandlePlatformMessage(std::unique_ptr message) { - FML_CHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); + // This can be called from any isolate's thread. fml::RefPtr completer = message->response(); HandlerInfo handler_info; { + // TODO(gaaclarke): This mutex is a bottleneck for multiple isolates sending + // messages at the same time. This could be potentially changed to a + // read-write lock. std::lock_guard lock(message_handlers_mutex_); auto it = message_handlers_.find(message->channel()); if (it != message_handlers_.end()) { diff --git a/shell/platform/darwin/ios/platform_message_handler_ios_test.mm b/shell/platform/darwin/ios/platform_message_handler_ios_test.mm index 376162ead5980..7249722c80624 100644 --- a/shell/platform/darwin/ios/platform_message_handler_ios_test.mm +++ b/shell/platform/darwin/ios/platform_message_handler_ios_test.mm @@ -9,6 +9,8 @@ #import "flutter/common/task_runners.h" #import "flutter/fml/message_loop.h" #import "flutter/fml/thread.h" +#import "flutter/lib/ui/window/platform_message.h" +#import "flutter/lib/ui/window/platform_message_response.h" #import "flutter/shell/common/thread_host.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"