From c160f5ca1c5d1a0a6cbedaf0d8cb3348bbd62a9b Mon Sep 17 00:00:00 2001 From: gaaclarke <30870216+gaaclarke@users.noreply.github.com> Date: Fri, 19 Nov 2021 14:14:45 -0800 Subject: [PATCH 1/3] iOS Background Platform Channels (#29665) --- ci/licenses_golden/licenses_flutter | 5 +- .../Headers/FlutterBinaryMessenger.h | 12 ++ .../framework/Headers/FlutterChannels.h | 70 +++++++++- .../framework/Source/FlutterChannels.mm | 76 ++++++++-- .../framework/Source/FlutterChannelsTest.m | 76 +++++++++- shell/platform/darwin/ios/BUILD.gn | 7 +- .../Source/FlutterBinaryMessengerRelay.mm | 22 +++ .../Source/FlutterBinaryMessengerRelayTest.mm | 25 ++++ .../ios/framework/Source/FlutterEngine.mm | 17 ++- .../framework/Source/FlutterViewController.mm | 14 +- .../Source/platform_message_router.h | 37 ----- .../Source/platform_message_router.mm | 52 ------- .../darwin/ios/platform_message_handler_ios.h | 51 +++++++ .../ios/platform_message_handler_ios.mm | 118 ++++++++++++++++ .../ios/platform_message_handler_ios_test.mm | 132 ++++++++++++++++++ shell/platform/darwin/ios/platform_view_ios.h | 18 +-- .../platform/darwin/ios/platform_view_ios.mm | 9 +- 17 files changed, 618 insertions(+), 123 deletions(-) delete mode 100644 shell/platform/darwin/ios/framework/Source/platform_message_router.h delete mode 100644 shell/platform/darwin/ios/framework/Source/platform_message_router.mm create mode 100644 shell/platform/darwin/ios/platform_message_handler_ios.h create mode 100644 shell/platform/darwin/ios/platform_message_handler_ios.mm create mode 100644 shell/platform/darwin/ios/platform_message_handler_ios_test.mm diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index e6a3c0997b1d6..485f73fdecf98 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1169,8 +1169,6 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_col FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection_test.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.mm -FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_router.h -FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_router.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h @@ -1202,6 +1200,9 @@ FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_software.h FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_software.mm FILE: ../../../flutter/shell/platform/darwin/ios/ios_switchable_gl_context.h FILE: ../../../flutter/shell/platform/darwin/ios/ios_switchable_gl_context.mm +FILE: ../../../flutter/shell/platform/darwin/ios/platform_message_handler_ios.h +FILE: ../../../flutter/shell/platform/darwin/ios/platform_message_handler_ios.mm +FILE: ../../../flutter/shell/platform/darwin/ios/platform_message_handler_ios_test.mm FILE: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.h FILE: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.mm FILE: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.h diff --git a/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h b/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h index 6b39687162e60..9f59ec82a0ebf 100644 --- a/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h +++ b/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h @@ -31,6 +31,8 @@ typedef void (^FlutterBinaryMessageHandler)(NSData* _Nullable message, FlutterBi typedef int64_t FlutterBinaryMessengerConnection; +@protocol FlutterTaskQueue; + /** * A facility for communicating with the Flutter side using asynchronous message * passing with binary messages. @@ -44,6 +46,16 @@ typedef int64_t FlutterBinaryMessengerConnection; */ FLUTTER_DARWIN_EXPORT @protocol FlutterBinaryMessenger +/// TODO(gaaclarke): Remove optional when macos supports Background Platform Channels. +@optional +- (NSObject*)makeBackgroundTaskQueue; + +- (FlutterBinaryMessengerConnection) + setMessageHandlerOnChannel:(NSString*)channel + binaryMessageHandler:(FlutterBinaryMessageHandler _Nullable)handler + taskQueue:(NSObject* _Nullable)taskQueue; + +@required /** * Sends a binary message to the Flutter side on the specified channel, expecting * no reply. diff --git a/shell/platform/darwin/common/framework/Headers/FlutterChannels.h b/shell/platform/darwin/common/framework/Headers/FlutterChannels.h index 8b24faed0f961..9b84cd64c7178 100644 --- a/shell/platform/darwin/common/framework/Headers/FlutterChannels.h +++ b/shell/platform/darwin/common/framework/Headers/FlutterChannels.h @@ -8,6 +8,8 @@ #import "FlutterBinaryMessenger.h" #import "FlutterCodecs.h" +@protocol FlutterTaskQueue; + NS_ASSUME_NONNULL_BEGIN /** * A message reply callback. @@ -24,7 +26,8 @@ typedef void (^FlutterReply)(id _Nullable reply); * asynchronous replies back to Flutter. * * @param message The message. - * @param callback A callback for submitting a reply to the sender. + * @param callback A callback for submitting a reply to the sender which can be invoked from any + * thread. */ typedef void (^FlutterMessageHandler)(id _Nullable message, FlutterReply callback); @@ -88,6 +91,27 @@ FLUTTER_DARWIN_EXPORT binaryMessenger:(NSObject*)messenger codec:(NSObject*)codec; +/** + * Initializes a `FlutterBasicMessageChannel` with the specified name, binary + * messenger, and message codec. + * + * The channel name logically identifies the channel; identically named channels + * interfere with each other's communication. + * + * The binary messenger is a facility for sending raw, binary messages to the + * Flutter side. This protocol is implemented by `FlutterEngine` and `FlutterViewController`. + * + * @param name The channel name. + * @param messenger The binary messenger. + * @param codec The message codec. + * @param taskQueue The FlutterTaskQueue that executes the handler (see + -[FlutterBinaryMessenger makeBackgroundTaskQueue]). + */ +- (instancetype)initWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger + codec:(NSObject*)codec + taskQueue:(NSObject* _Nullable)taskQueue; + /** * Sends the specified message to the Flutter side, ignoring any reply. * @@ -142,7 +166,7 @@ typedef void (^FlutterResult)(id _Nullable result); * Invoke the callback with a `FlutterError` to indicate that the call failed. * Invoke the callback with `FlutterMethodNotImplemented` to indicate that the * method was unknown. Any other values, including `nil`, are interpreted as - * successful results. + * successful results. This can be invoked from any thread. */ typedef void (^FlutterMethodCallHandler)(FlutterMethodCall* call, FlutterResult result); @@ -213,6 +237,27 @@ FLUTTER_DARWIN_EXPORT binaryMessenger:(NSObject*)messenger codec:(NSObject*)codec; +/** + * Initializes a `FlutterMethodChannel` with the specified name, binary messenger, + * method codec, and task queue. + * + * The channel name logically identifies the channel; identically named channels + * interfere with each other's communication. + * + * The binary messenger is a facility for sending raw, binary messages to the + * Flutter side. This protocol is implemented by `FlutterEngine` and `FlutterViewController`. + * + * @param name The channel name. + * @param messenger The binary messenger. + * @param codec The method codec. + * @param taskQueue The FlutterTaskQueue that executes the handler (see + -[FlutterBinaryMessenger makeBackgroundTaskQueue]). + */ +- (instancetype)initWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger + codec:(NSObject*)codec + taskQueue:(NSObject* _Nullable)taskQueue; + // clang-format off /** * Invokes the specified Flutter method with the specified arguments, expecting @@ -371,6 +416,27 @@ FLUTTER_DARWIN_EXPORT - (instancetype)initWithName:(NSString*)name binaryMessenger:(NSObject*)messenger codec:(NSObject*)codec; + +/** + * Initializes a `FlutterEventChannel` with the specified name, binary messenger, + * method codec and task queue. + * + * The channel name logically identifies the channel; identically named channels + * interfere with each other's communication. + * + * The binary messenger is a facility for sending raw, binary messages to the + * Flutter side. This protocol is implemented by `FlutterEngine` and `FlutterViewController`. + * + * @param name The channel name. + * @param messenger The binary messenger. + * @param codec The method codec. + * @param taskQueue The FlutterTaskQueue that executes the handler (see + -[FlutterBinaryMessenger makeBackgroundTaskQueue]). + */ +- (instancetype)initWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger + codec:(NSObject*)codec + taskQueue:(NSObject* _Nullable)taskQueue; /** * Registers a handler for stream setup requests from the Flutter side. * diff --git a/shell/platform/darwin/common/framework/Source/FlutterChannels.mm b/shell/platform/darwin/common/framework/Source/FlutterChannels.mm index b59098d646d19..a0bf05c10f4aa 100644 --- a/shell/platform/darwin/common/framework/Source/FlutterChannels.mm +++ b/shell/platform/darwin/common/framework/Source/FlutterChannels.mm @@ -16,11 +16,30 @@ static void ResizeChannelBuffer(NSObject* binaryMessenge [binaryMessenger sendOnChannel:FlutterChannelBuffersChannel message:message]; } +static FlutterBinaryMessengerConnection SetMessageHandler( + NSObject* messenger, + NSString* name, + FlutterBinaryMessageHandler handler, + NSObject* taskQueue) { + if (taskQueue) { + NSCAssert([messenger respondsToSelector:@selector(setMessageHandlerOnChannel: + binaryMessageHandler:taskQueue:)], + @""); + return [messenger setMessageHandlerOnChannel:name + binaryMessageHandler:handler + taskQueue:taskQueue]; + } else { + return [messenger setMessageHandlerOnChannel:name binaryMessageHandler:handler]; + } +} + +//////////////////////////////////////////////////////////////////////////////// @implementation FlutterBasicMessageChannel { NSObject* _messenger; NSString* _name; NSObject* _codec; FlutterBinaryMessengerConnection _connection; + NSObject* _taskQueue; } + (instancetype)messageChannelWithName:(NSString*)name binaryMessenger:(NSObject*)messenger { @@ -40,11 +59,20 @@ + (instancetype)messageChannelWithName:(NSString*)name - (instancetype)initWithName:(NSString*)name binaryMessenger:(NSObject*)messenger codec:(NSObject*)codec { + self = [self initWithName:name binaryMessenger:messenger codec:codec taskQueue:nil]; + return self; +} + +- (instancetype)initWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger + codec:(NSObject*)codec + taskQueue:(NSObject*)taskQueue { self = [super init]; NSAssert(self, @"Super init cannot be nil"); _name = [name retain]; _messenger = [messenger retain]; _codec = [codec retain]; + _taskQueue = [taskQueue retain]; return self; } @@ -52,6 +80,7 @@ - (void)dealloc { [_name release]; [_messenger release]; [_codec release]; + [_taskQueue release]; [super dealloc]; } @@ -85,7 +114,7 @@ - (void)setMessageHandler:(FlutterMessageHandler)handler { callback([codec encode:reply]); }); }; - _connection = [_messenger setMessageHandlerOnChannel:_name binaryMessageHandler:messageHandler]; + _connection = SetMessageHandler(_messenger, _name, messageHandler, _taskQueue); } - (void)resizeChannelBuffer:(NSInteger)newSize { @@ -96,6 +125,7 @@ - (void)resizeChannelBuffer:(NSInteger)newSize { #pragma mark - Method channel +//////////////////////////////////////////////////////////////////////////////// @implementation FlutterError + (instancetype)errorWithCode:(NSString*)code message:(NSString*)message details:(id)details { return [[[FlutterError alloc] initWithCode:code message:message details:details] autorelease]; @@ -136,6 +166,7 @@ - (NSUInteger)hash { } @end +//////////////////////////////////////////////////////////////////////////////// @implementation FlutterMethodCall + (instancetype)methodCallWithMethodName:(NSString*)method arguments:(id)arguments { return [[[FlutterMethodCall alloc] initWithMethodName:method arguments:arguments] autorelease]; @@ -175,11 +206,13 @@ - (NSUInteger)hash { NSObject const* FlutterMethodNotImplemented = [[NSObject alloc] init]; +//////////////////////////////////////////////////////////////////////////////// @implementation FlutterMethodChannel { NSObject* _messenger; NSString* _name; NSObject* _codec; FlutterBinaryMessengerConnection _connection; + NSObject* _taskQueue; } + (instancetype)methodChannelWithName:(NSString*)name @@ -198,11 +231,19 @@ + (instancetype)methodChannelWithName:(NSString*)name - (instancetype)initWithName:(NSString*)name binaryMessenger:(NSObject*)messenger codec:(NSObject*)codec { + self = [self initWithName:name binaryMessenger:messenger codec:codec taskQueue:nil]; + return self; +} +- (instancetype)initWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger + codec:(NSObject*)codec + taskQueue:(NSObject*)taskQueue { self = [super init]; NSAssert(self, @"Super init cannot be nil"); _name = [name retain]; _messenger = [messenger retain]; _codec = [codec retain]; + _taskQueue = [taskQueue retain]; return self; } @@ -210,6 +251,7 @@ - (void)dealloc { [_name release]; [_messenger release]; [_codec release]; + [_taskQueue release]; [super dealloc]; } @@ -256,7 +298,7 @@ - (void)setMethodCallHandler:(FlutterMethodCallHandler)handler { } }); }; - _connection = [_messenger setMessageHandlerOnChannel:_name binaryMessageHandler:messageHandler]; + _connection = SetMessageHandler(_messenger, _name, messageHandler, _taskQueue); } - (void)resizeChannelBuffer:(NSInteger)newSize { @@ -269,10 +311,13 @@ - (void)resizeChannelBuffer:(NSInteger)newSize { NSObject const* FlutterEndOfEventStream = [[NSObject alloc] init]; +//////////////////////////////////////////////////////////////////////////////// @implementation FlutterEventChannel { NSObject* _messenger; NSString* _name; NSObject* _codec; + NSObject* _taskQueue; + FlutterBinaryMessengerConnection _connection; } + (instancetype)eventChannelWithName:(NSString*)name binaryMessenger:(NSObject*)messenger { @@ -290,11 +335,19 @@ + (instancetype)eventChannelWithName:(NSString*)name - (instancetype)initWithName:(NSString*)name binaryMessenger:(NSObject*)messenger codec:(NSObject*)codec { + return [self initWithName:name binaryMessenger:messenger codec:codec taskQueue:nil]; +} + +- (instancetype)initWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger + codec:(NSObject*)codec + taskQueue:(NSObject* _Nullable)taskQueue { self = [super init]; NSAssert(self, @"Super init cannot be nil"); _name = [name retain]; _messenger = [messenger retain]; _codec = [codec retain]; + _taskQueue = [taskQueue retain]; return self; } @@ -302,13 +355,16 @@ - (void)dealloc { [_name release]; [_codec release]; [_messenger release]; + [_taskQueue release]; [super dealloc]; } -static void SetStreamHandlerMessageHandlerOnChannel(NSObject* handler, - NSString* name, - NSObject* messenger, - NSObject* codec) { +static FlutterBinaryMessengerConnection SetStreamHandlerMessageHandlerOnChannel( + NSObject* handler, + NSString* name, + NSObject* messenger, + NSObject* codec, + NSObject* taskQueue) { __block FlutterEventSink currentSink = nil; FlutterBinaryMessageHandler messageHandler = ^(NSData* message, FlutterBinaryReply callback) { FlutterMethodCall* call = [codec decodeMethodCall:message]; @@ -354,14 +410,16 @@ static void SetStreamHandlerMessageHandlerOnChannel(NSObject*)handler { if (!handler) { - [_messenger setMessageHandlerOnChannel:_name binaryMessageHandler:nil]; + [_messenger cleanUpConnection:_connection]; + _connection = 0; return; } - SetStreamHandlerMessageHandlerOnChannel(handler, _name, _messenger, _codec); + _connection = + SetStreamHandlerMessageHandlerOnChannel(handler, _name, _messenger, _codec, _taskQueue); } @end diff --git a/shell/platform/darwin/common/framework/Source/FlutterChannelsTest.m b/shell/platform/darwin/common/framework/Source/FlutterChannelsTest.m index fbefe90a8d7c6..a499fbea8f34d 100644 --- a/shell/platform/darwin/common/framework/Source/FlutterChannelsTest.m +++ b/shell/platform/darwin/common/framework/Source/FlutterChannelsTest.m @@ -108,7 +108,6 @@ - (void)testMethodMessageHandler { OCMStub([codec encodeMethodCall:[OCMArg any]]).andReturn(encodedMethodCall); FlutterMethodCallHandler handler = ^(FlutterMethodCall* _Nonnull call, FlutterResult _Nonnull result) { - NSLog(@"hey"); }; [channel setMethodCallHandler:handler]; OCMVerify([binaryMessenger setMessageHandlerOnChannel:channelName @@ -175,7 +174,6 @@ - (void)testBasicMessageChannelCleanup { binaryMessenger:binaryMessenger codec:codec]; FlutterMessageHandler handler = ^(id _Nullable message, FlutterReply callback) { - NSLog(@"hey"); }; OCMStub([binaryMessenger setMessageHandlerOnChannel:channelName binaryMessageHandler:[OCMArg any]]) @@ -211,4 +209,78 @@ - (void)testMethodChannelCleanup { OCMVerify([binaryMessenger cleanUpConnection:connection]); } +- (void)testBasicMessageChannelTaskQueue { + NSString* channelName = @"foo"; + FlutterBinaryMessengerConnection connection = 123; + id binaryMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); + id codec = OCMProtocolMock(@protocol(FlutterMethodCodec)); + id taskQueue = OCMClassMock([NSObject class]); + FlutterBasicMessageChannel* channel = + [[FlutterBasicMessageChannel alloc] initWithName:channelName + binaryMessenger:binaryMessenger + codec:codec + taskQueue:taskQueue]; + FlutterMessageHandler handler = ^(id _Nullable message, FlutterReply callback) { + }; + OCMStub([binaryMessenger setMessageHandlerOnChannel:channelName + binaryMessageHandler:[OCMArg any] + taskQueue:taskQueue]) + .andReturn(connection); + [channel setMessageHandler:handler]; + OCMVerify([binaryMessenger setMessageHandlerOnChannel:channelName + binaryMessageHandler:[OCMArg isNotNil] + taskQueue:taskQueue]); + [channel setMessageHandler:nil]; + OCMVerify([binaryMessenger cleanUpConnection:connection]); +} + +- (void)testMethodChannelTaskQueue { + NSString* channelName = @"foo"; + FlutterBinaryMessengerConnection connection = 123; + id binaryMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); + id codec = OCMProtocolMock(@protocol(FlutterMethodCodec)); + id taskQueue = OCMClassMock([NSObject class]); + FlutterMethodChannel* channel = [[FlutterMethodChannel alloc] initWithName:channelName + binaryMessenger:binaryMessenger + codec:codec + taskQueue:taskQueue]; + XCTAssertNotNil(channel); + FlutterMethodCallHandler handler = ^(FlutterMethodCall* call, FlutterResult result) { + }; + OCMStub([binaryMessenger setMessageHandlerOnChannel:channelName + binaryMessageHandler:[OCMArg any] + taskQueue:taskQueue]) + .andReturn(connection); + [channel setMethodCallHandler:handler]; + OCMVerify([binaryMessenger setMessageHandlerOnChannel:channelName + binaryMessageHandler:[OCMArg isNotNil] + taskQueue:taskQueue]); + [channel setMethodCallHandler:nil]; + OCMVerify([binaryMessenger cleanUpConnection:connection]); +} + +- (void)testEventChannelTaskQueue { + NSString* channelName = @"foo"; + FlutterBinaryMessengerConnection connection = 123; + id binaryMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); + id codec = OCMProtocolMock(@protocol(FlutterMethodCodec)); + id taskQueue = OCMClassMock([NSObject class]); + id handler = OCMProtocolMock(@protocol(FlutterStreamHandler)); + FlutterEventChannel* channel = [[FlutterEventChannel alloc] initWithName:channelName + binaryMessenger:binaryMessenger + codec:codec + taskQueue:taskQueue]; + XCTAssertNotNil(channel); + OCMStub([binaryMessenger setMessageHandlerOnChannel:channelName + binaryMessageHandler:[OCMArg any] + taskQueue:taskQueue]) + .andReturn(connection); + [channel setStreamHandler:handler]; + OCMVerify([binaryMessenger setMessageHandlerOnChannel:channelName + binaryMessageHandler:[OCMArg isNotNil] + taskQueue:taskQueue]); + [channel setStreamHandler:nil]; + OCMVerify([binaryMessenger cleanUpConnection:connection]); +} + @end diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 303e1edddb7a2..7a09f6b691c01 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -100,8 +100,6 @@ source_set("flutter_framework_source") { "framework/Source/connection_collection.mm", "framework/Source/platform_message_response_darwin.h", "framework/Source/platform_message_response_darwin.mm", - "framework/Source/platform_message_router.h", - "framework/Source/platform_message_router.mm", "framework/Source/profiler_metrics_ios.h", "framework/Source/profiler_metrics_ios.mm", "framework/Source/vsync_waiter_ios.h", @@ -126,6 +124,8 @@ source_set("flutter_framework_source") { "ios_surface_software.mm", "ios_switchable_gl_context.h", "ios_switchable_gl_context.mm", + "platform_message_handler_ios.h", + "platform_message_handler_ios.mm", "platform_view_ios.h", "platform_view_ios.mm", "rendering_api_selection.h", @@ -205,9 +205,12 @@ source_set("ios_test_flutter_mrc") { "framework/Source/FlutterPlatformViewsTest.mm", "framework/Source/FlutterViewTest.mm", "framework/Source/accessibility_bridge_test.mm", + "platform_message_handler_ios_test.mm", ] deps = [ ":flutter_framework_source", + "//flutter/common:common", + "//flutter/shell/common:common", "//flutter/shell/platform/darwin/common:framework_shared", "//flutter/shell/platform/embedder:embedder_as_internal_library", "//flutter/third_party/tonic", diff --git a/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelay.mm b/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelay.mm index 24f9beb2d2a19..22d364b5e957c 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelay.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelay.mm @@ -35,6 +35,14 @@ - (void)sendOnChannel:(NSString*)channel } } +- (NSObject*)makeBackgroundTaskQueue { + if (self.parent) { + return [self.parent makeBackgroundTaskQueue]; + } else { + return nil; + }; +} + - (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(NSString*)channel binaryMessageHandler: (FlutterBinaryMessageHandler)handler { @@ -46,6 +54,20 @@ - (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(NSString*)channe } } +- (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(NSString*)channel + binaryMessageHandler:(FlutterBinaryMessageHandler)handler + taskQueue: + (NSObject*)taskQueue { + if (self.parent) { + return [self.parent setMessageHandlerOnChannel:channel + binaryMessageHandler:handler + taskQueue:taskQueue]; + } else { + FML_LOG(WARNING) << "Communicating on a dead channel."; + return -1; + } +} + - (void)cleanUpConnection:(FlutterBinaryMessengerConnection)connection { if (self.parent) { return [self.parent cleanUpConnection:connection]; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelayTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelayTest.mm index f4e6374a9745d..e50097f6d6dc0 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelayTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelayTest.mm @@ -11,6 +11,9 @@ FLUTTER_ASSERT_ARC +@protocol FlutterTaskQueue +@end + @interface FlutterBinaryMessengerRelayTest : XCTestCase @end @@ -52,4 +55,26 @@ - (void)testDoesntPassCallOn { [relay sendOnChannel:channel message:message binaryReply:nil]; } +- (void)testSetMessageHandlerWithTaskQueue { + id messenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); + FlutterBinaryMessengerRelay* relay = + [[FlutterBinaryMessengerRelay alloc] initWithParent:messenger]; + NSString* channel = @"foobar"; + NSObject* taskQueue = OCMProtocolMock(@protocol(FlutterTaskQueue)); + FlutterBinaryMessageHandler handler = ^(NSData* _Nullable, FlutterBinaryReply _Nonnull) { + }; + [relay setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:taskQueue]; + OCMVerify([messenger setMessageHandlerOnChannel:channel + binaryMessageHandler:handler + taskQueue:taskQueue]); +} + +- (void)testMakeBackgroundTaskQueue { + id messenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); + FlutterBinaryMessengerRelay* relay = + [[FlutterBinaryMessengerRelay alloc] initWithParent:messenger]; + [relay makeBackgroundTaskQueue]; + OCMVerify([messenger makeBackgroundTaskQueue]); +} + @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 03fe0a06e8e39..9890f46836762 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -952,12 +952,24 @@ - (void)sendOnChannel:(NSString*)channel _shell->GetPlatformView()->DispatchPlatformMessage(std::move(platformMessage)); } +- (NSObject*)makeBackgroundTaskQueue { + return flutter::PlatformMessageHandlerIos::MakeBackgroundTaskQueue(); +} + - (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(NSString*)channel binaryMessageHandler: (FlutterBinaryMessageHandler)handler { + return [self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil]; +} + +- (FlutterBinaryMessengerConnection) + setMessageHandlerOnChannel:(NSString*)channel + binaryMessageHandler:(FlutterBinaryMessageHandler)handler + taskQueue:(NSObject* _Nullable)taskQueue { NSParameterAssert(channel); if (_shell && _shell->IsSetup()) { - self.iosPlatformView->GetPlatformMessageRouter().SetMessageHandler(channel.UTF8String, handler); + self.iosPlatformView->GetPlatformMessageHandlerIos()->SetMessageHandler(channel.UTF8String, + handler, taskQueue); return _connections->AquireConnection(channel.UTF8String); } else { NSAssert(!handler, @"Setting a message handler before the FlutterEngine has been run."); @@ -970,7 +982,8 @@ - (void)cleanUpConnection:(FlutterBinaryMessengerConnection)connection { if (_shell && _shell->IsSetup()) { std::string channel = _connections->CleanupConnection(connection); if (!channel.empty()) { - self.iosPlatformView->GetPlatformMessageRouter().SetMessageHandler(channel.c_str(), nil); + self.iosPlatformView->GetPlatformMessageHandlerIos()->SetMessageHandler(channel.c_str(), nil, + nil); } } } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 1f727a17d28e2..708d3faa98cce 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1608,12 +1608,24 @@ - (void)sendOnChannel:(NSString*)channel [_engine.get().binaryMessenger sendOnChannel:channel message:message binaryReply:callback]; } +- (NSObject*)makeBackgroundTaskQueue { + return [_engine.get().binaryMessenger makeBackgroundTaskQueue]; +} + - (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(NSString*)channel binaryMessageHandler: (FlutterBinaryMessageHandler)handler { + return [self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil]; +} + +- (FlutterBinaryMessengerConnection) + setMessageHandlerOnChannel:(NSString*)channel + binaryMessageHandler:(FlutterBinaryMessageHandler _Nullable)handler + taskQueue:(NSObject* _Nullable)taskQueue { NSAssert(channel, @"The channel must not be null"); return [_engine.get().binaryMessenger setMessageHandlerOnChannel:channel - binaryMessageHandler:handler]; + binaryMessageHandler:handler + taskQueue:taskQueue]; } - (void)cleanUpConnection:(FlutterBinaryMessengerConnection)connection { diff --git a/shell/platform/darwin/ios/framework/Source/platform_message_router.h b/shell/platform/darwin/ios/framework/Source/platform_message_router.h deleted file mode 100644 index e8927552ddc32..0000000000000 --- a/shell/platform/darwin/ios/framework/Source/platform_message_router.h +++ /dev/null @@ -1,37 +0,0 @@ -// 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 SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_PLATFORM_MESSAGE_ROUTER_H_ -#define SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_PLATFORM_MESSAGE_ROUTER_H_ - -#include - -#include "flutter/fml/memory/weak_ptr.h" -#include "flutter/fml/platform/darwin/scoped_block.h" -#include "flutter/lib/ui/window/platform_message.h" -#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h" - -namespace flutter { - -class PlatformMessageRouter { - public: - PlatformMessageRouter(); - ~PlatformMessageRouter(); - - void HandlePlatformMessage( - std::unique_ptr message) const; - - void SetMessageHandler(const std::string& channel, - FlutterBinaryMessageHandler handler); - - private: - std::unordered_map> - message_handlers_; - - FML_DISALLOW_COPY_AND_ASSIGN(PlatformMessageRouter); -}; - -} // namespace flutter - -#endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_ACCESSIBILITY_BRIDGE_H_ diff --git a/shell/platform/darwin/ios/framework/Source/platform_message_router.mm b/shell/platform/darwin/ios/framework/Source/platform_message_router.mm deleted file mode 100644 index 841d54627e8df..0000000000000 --- a/shell/platform/darwin/ios/framework/Source/platform_message_router.mm +++ /dev/null @@ -1,52 +0,0 @@ -// 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. - -#import "flutter/shell/platform/darwin/ios/framework/Source/platform_message_router.h" - -#include - -#import "flutter/shell/platform/darwin/common/buffer_conversions.h" - -namespace flutter { - -PlatformMessageRouter::PlatformMessageRouter() = default; - -PlatformMessageRouter::~PlatformMessageRouter() = default; - -void PlatformMessageRouter::HandlePlatformMessage( - std::unique_ptr message) const { - fml::RefPtr completer = message->response(); - auto it = message_handlers_.find(message->channel()); - if (it != message_handlers_.end()) { - FlutterBinaryMessageHandler handler = it->second; - NSData* data = nil; - if (message->hasData()) { - data = ConvertMappingToNSData(message->releaseData()); - } - handler(data, ^(NSData* reply) { - if (completer) { - if (reply) { - completer->Complete(ConvertNSDataToMappingPtr(reply)); - } else { - completer->CompleteEmpty(); - } - } - }); - } else { - if (completer) { - completer->CompleteEmpty(); - } - } -} - -void PlatformMessageRouter::SetMessageHandler(const std::string& channel, - FlutterBinaryMessageHandler handler) { - message_handlers_.erase(channel); - if (handler) { - message_handlers_[channel] = - fml::ScopedBlock{handler, fml::OwnershipPolicy::Retain}; - } -} - -} // namespace flutter diff --git a/shell/platform/darwin/ios/platform_message_handler_ios.h b/shell/platform/darwin/ios/platform_message_handler_ios.h new file mode 100644 index 0000000000000..24247b392072e --- /dev/null +++ b/shell/platform/darwin/ios/platform_message_handler_ios.h @@ -0,0 +1,51 @@ +// 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_DARWIN_IOS_PLATFORM_MESSAGE_HANDLER_IOS_H_ +#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_PLATFORM_MESSAGE_HANDLER_IOS_H_ + +#include + +#include "flutter/common/task_runners.h" +#include "flutter/fml/platform/darwin/scoped_block.h" +#include "flutter/fml/platform/darwin/scoped_nsobject.h" +#include "flutter/shell/common/platform_message_handler.h" +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h" + +@protocol FlutterTaskQueue; + +namespace flutter { + +class PlatformMessageHandlerIos : public PlatformMessageHandler { + public: + static NSObject* MakeBackgroundTaskQueue(); + + PlatformMessageHandlerIos(TaskRunners task_runners); + + void HandlePlatformMessage(std::unique_ptr message) override; + + void InvokePlatformMessageResponseCallback(int response_id, + std::unique_ptr mapping) override; + + void InvokePlatformMessageEmptyResponseCallback(int response_id) override; + + void SetMessageHandler(const std::string& channel, + FlutterBinaryMessageHandler handler, + NSObject* task_queue); + + struct HandlerInfo { + fml::scoped_nsprotocol*> task_queue; + fml::ScopedBlock handler; + }; + + private: + std::unordered_map message_handlers_; + TaskRunners task_runners_; + std::mutex message_handlers_mutex_; + FML_DISALLOW_COPY_AND_ASSIGN(PlatformMessageHandlerIos); +}; + +} // namespace flutter + +#endif diff --git a/shell/platform/darwin/ios/platform_message_handler_ios.mm b/shell/platform/darwin/ios/platform_message_handler_ios.mm new file mode 100644 index 0000000000000..9e316cc3514a7 --- /dev/null +++ b/shell/platform/darwin/ios/platform_message_handler_ios.mm @@ -0,0 +1,118 @@ +// 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. + +#import "flutter/shell/platform/darwin/ios/platform_message_handler_ios.h" + +#import "flutter/shell/platform/darwin/common/buffer_conversions.h" +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h" + +@protocol FlutterTaskQueue +- (void)dispatch:(dispatch_block_t)block; +@end + +@interface FLTSerialTaskQueue : NSObject +@property(nonatomic, strong) dispatch_queue_t queue; +@end + +@implementation FLTSerialTaskQueue +- (instancetype)init { + self = [super init]; + if (self) { + _queue = dispatch_queue_create("FLTSerialTaskQueue", DISPATCH_QUEUE_SERIAL); + } + return self; +} + +- (void)dealloc { + dispatch_release(_queue); + [super dealloc]; +} + +- (void)dispatch:(dispatch_block_t)block { + dispatch_async(self.queue, block); +} +@end + +namespace flutter { + +NSObject* PlatformMessageHandlerIos::MakeBackgroundTaskQueue() { + return [[[FLTSerialTaskQueue alloc] init] autorelease]; +} + +PlatformMessageHandlerIos::PlatformMessageHandlerIos(TaskRunners task_runners) + : task_runners_(task_runners) {} + +void PlatformMessageHandlerIos::HandlePlatformMessage(std::unique_ptr message) { + FML_CHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); + fml::RefPtr completer = message->response(); + HandlerInfo handler_info; + { + std::lock_guard lock(message_handlers_mutex_); + auto it = message_handlers_.find(message->channel()); + if (it != message_handlers_.end()) { + handler_info = it->second; + } + } + if (handler_info.handler) { + FlutterBinaryMessageHandler handler = handler_info.handler; + NSData* data = nil; + if (message->hasData()) { + data = ConvertMappingToNSData(message->releaseData()); + } + + dispatch_block_t run_handler = ^{ + handler(data, ^(NSData* reply) { + // Called from any thread. + if (completer) { + if (reply) { + completer->Complete(ConvertNSDataToMappingPtr(reply)); + } else { + completer->CompleteEmpty(); + } + } + }); + }; + + if (handler_info.task_queue.get()) { + [handler_info.task_queue.get() dispatch:run_handler]; + } else { + dispatch_async(dispatch_get_main_queue(), run_handler); + } + } else { + if (completer) { + completer->CompleteEmpty(); + } + } +} + +void PlatformMessageHandlerIos::InvokePlatformMessageResponseCallback( + int response_id, + std::unique_ptr mapping) { + // Called from any thread. + // TODO(gaaclarke): This vestigal from the Android implementation, find a way + // to migrate this to PlatformMessageHandlerAndroid. +} + +void PlatformMessageHandlerIos::InvokePlatformMessageEmptyResponseCallback(int response_id) { + // Called from any thread. + // TODO(gaaclarke): This vestigal from the Android implementation, find a way + // to migrate this to PlatformMessageHandlerAndroid. +} + +void PlatformMessageHandlerIos::SetMessageHandler(const std::string& channel, + FlutterBinaryMessageHandler handler, + NSObject* task_queue) { + FML_CHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); + /// TODO(gaaclarke): This should be migrated to a lockfree datastructure. + std::lock_guard lock(message_handlers_mutex_); + message_handlers_.erase(channel); + if (handler) { + message_handlers_[channel] = { + .task_queue = fml::scoped_nsprotocol([task_queue retain]), + .handler = + fml::ScopedBlock{handler, fml::OwnershipPolicy::Retain}, + }; + } +} +} // namespace flutter diff --git a/shell/platform/darwin/ios/platform_message_handler_ios_test.mm b/shell/platform/darwin/ios/platform_message_handler_ios_test.mm new file mode 100644 index 0000000000000..376162ead5980 --- /dev/null +++ b/shell/platform/darwin/ios/platform_message_handler_ios_test.mm @@ -0,0 +1,132 @@ +// 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. + +#import + +#import "flutter/shell/platform/darwin/ios/platform_message_handler_ios.h" + +#import "flutter/common/task_runners.h" +#import "flutter/fml/message_loop.h" +#import "flutter/fml/thread.h" +#import "flutter/shell/common/thread_host.h" +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" + +FLUTTER_ASSERT_NOT_ARC + +namespace { +using namespace flutter; +fml::RefPtr CreateNewThread(std::string name) { + auto thread = std::make_unique(name); + auto runner = thread->GetTaskRunner(); + return runner; +} + +fml::RefPtr GetCurrentTaskRunner() { + fml::MessageLoop::EnsureInitializedForCurrentThread(); + return fml::MessageLoop::GetCurrent().GetTaskRunner(); +} + +class MockPlatformMessageResponse : public PlatformMessageResponse { + public: + static fml::RefPtr Create() { + return fml::AdoptRef(new MockPlatformMessageResponse()); + } + void Complete(std::unique_ptr data) override { is_complete_ = true; } + void CompleteEmpty() override { is_complete_ = true; } +}; +} // namespace + +@interface PlatformMessageHandlerIosTest : XCTestCase +@end + +@implementation PlatformMessageHandlerIosTest +- (void)testCreate { + flutter::TaskRunners task_runners("test", GetCurrentTaskRunner(), CreateNewThread("raster"), + CreateNewThread("ui"), CreateNewThread("io")); + auto handler = std::make_unique(task_runners); + XCTAssertTrue(handler); +} + +- (void)testSetAndCallHandler { + ThreadHost thread_host("io.flutter.test." + std::string(self.name.UTF8String), + ThreadHost::Type::RASTER | ThreadHost::Type::IO | ThreadHost::Type::UI); + TaskRunners task_runners( + "test", GetCurrentTaskRunner(), thread_host.raster_thread->GetTaskRunner(), + thread_host.ui_thread->GetTaskRunner(), thread_host.io_thread->GetTaskRunner()); + + auto handler = std::make_unique(task_runners); + std::string channel = "foo"; + XCTestExpectation* didCallReply = [self expectationWithDescription:@"didCallReply"]; + handler->SetMessageHandler( + channel, + ^(NSData* _Nullable data, FlutterBinaryReply _Nonnull reply) { + reply(nil); + [didCallReply fulfill]; + }, + nil); + auto response = MockPlatformMessageResponse::Create(); + task_runners.GetUITaskRunner()->PostTask([channel, response, &handler] { + auto platform_message = std::make_unique(channel, response); + handler->HandlePlatformMessage(std::move(platform_message)); + }); + [self waitForExpectationsWithTimeout:1.0 handler:nil]; + XCTAssertTrue(response->is_complete()); +} + +- (void)testSetClearAndCallHandler { + ThreadHost thread_host("io.flutter.test." + std::string(self.name.UTF8String), + ThreadHost::Type::RASTER | ThreadHost::Type::IO | ThreadHost::Type::UI); + TaskRunners task_runners( + "test", GetCurrentTaskRunner(), thread_host.raster_thread->GetTaskRunner(), + thread_host.ui_thread->GetTaskRunner(), thread_host.io_thread->GetTaskRunner()); + + auto handler = std::make_unique(task_runners); + std::string channel = "foo"; + XCTestExpectation* didCallMessage = [self expectationWithDescription:@"didCallMessage"]; + handler->SetMessageHandler( + channel, + ^(NSData* _Nullable data, FlutterBinaryReply _Nonnull reply) { + XCTFail(@"This shouldn't be called"); + reply(nil); + }, + nil); + handler->SetMessageHandler(channel, nil, nil); + auto response = MockPlatformMessageResponse::Create(); + task_runners.GetUITaskRunner()->PostTask([channel, response, &handler, &didCallMessage] { + auto platform_message = std::make_unique(channel, response); + handler->HandlePlatformMessage(std::move(platform_message)); + [didCallMessage fulfill]; + }); + [self waitForExpectationsWithTimeout:1.0 handler:nil]; + XCTAssertTrue(response->is_complete()); +} + +- (void)testSetAndCallHandlerTaskQueue { + ThreadHost thread_host("io.flutter.test." + std::string(self.name.UTF8String), + ThreadHost::Type::RASTER | ThreadHost::Type::IO | ThreadHost::Type::UI); + TaskRunners task_runners( + "test", GetCurrentTaskRunner(), thread_host.raster_thread->GetTaskRunner(), + thread_host.ui_thread->GetTaskRunner(), thread_host.io_thread->GetTaskRunner()); + + auto handler = std::make_unique(task_runners); + std::string channel = "foo"; + XCTestExpectation* didCallReply = [self expectationWithDescription:@"didCallReply"]; + NSObject* taskQueue = PlatformMessageHandlerIos::MakeBackgroundTaskQueue(); + handler->SetMessageHandler( + channel, + ^(NSData* _Nullable data, FlutterBinaryReply _Nonnull reply) { + XCTAssertFalse([NSThread isMainThread]); + reply(nil); + [didCallReply fulfill]; + }, + taskQueue); + auto response = MockPlatformMessageResponse::Create(); + task_runners.GetUITaskRunner()->PostTask([channel, response, &handler] { + auto platform_message = std::make_unique(channel, response); + handler->HandlePlatformMessage(std::move(platform_message)); + }); + [self waitForExpectationsWithTimeout:1.0 handler:nil]; + XCTAssertTrue(response->is_complete()); +} +@end diff --git a/shell/platform/darwin/ios/platform_view_ios.h b/shell/platform/darwin/ios/platform_view_ios.h index 3af69b7b2bf8a..74c17eb332392 100644 --- a/shell/platform/darwin/ios/platform_view_ios.h +++ b/shell/platform/darwin/ios/platform_view_ios.h @@ -16,10 +16,10 @@ #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h" #import "flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h" -#import "flutter/shell/platform/darwin/ios/framework/Source/platform_message_router.h" #import "flutter/shell/platform/darwin/ios/ios_context.h" #import "flutter/shell/platform/darwin/ios/ios_external_view_embedder.h" #import "flutter/shell/platform/darwin/ios/ios_surface.h" +#import "flutter/shell/platform/darwin/ios/platform_message_handler_ios.h" #import "flutter/shell/platform/darwin/ios/rendering_api_selection.h" @class FlutterViewController; @@ -53,12 +53,6 @@ class PlatformViewIOS final : public PlatformView { ~PlatformViewIOS() override; - /** - * The `PlatformMessageRouter` is the iOS bridge connecting the shell's - * platform agnostic `PlatformMessage` to iOS's channel message handler. - */ - PlatformMessageRouter& GetPlatformMessageRouter(); - /** * Returns the `FlutterViewController` currently attached to the `FlutterEngine` owning * this PlatformViewIOS. @@ -96,6 +90,14 @@ class PlatformViewIOS final : public PlatformView { /** Accessor for the `IOSContext` associated with the platform view. */ const std::shared_ptr& GetIosContext() { return ios_context_; } + std::shared_ptr GetPlatformMessageHandlerIos() const { + return platform_message_handler_; + } + + std::shared_ptr GetPlatformMessageHandler() const override { + return platform_message_handler_; + } + private: /// Smart pointer for use with objective-c observers. /// This guarantees we remove the observer. @@ -136,12 +138,12 @@ class PlatformViewIOS final : public PlatformView { std::unique_ptr ios_surface_; std::shared_ptr ios_context_; const std::shared_ptr& platform_views_controller_; - PlatformMessageRouter platform_message_router_; AccessibilityBridgePtr accessibility_bridge_; fml::scoped_nsprotocol text_input_plugin_; fml::closure firstFrameCallback_; ScopedObserver dealloc_view_controller_observer_; std::vector platform_resolved_locale_; + std::shared_ptr platform_message_handler_; // |PlatformView| void HandlePlatformMessage(std::unique_ptr message) override; diff --git a/shell/platform/darwin/ios/platform_view_ios.mm b/shell/platform/darwin/ios/platform_view_ios.mm index 081bbe0db130a..8ae73189874a3 100644 --- a/shell/platform/darwin/ios/platform_view_ios.mm +++ b/shell/platform/darwin/ios/platform_view_ios.mm @@ -53,7 +53,8 @@ : PlatformView(delegate, std::move(task_runners)), ios_context_(context), platform_views_controller_(platform_views_controller), - accessibility_bridge_([this](bool enabled) { PlatformView::SetSemanticsEnabled(enabled); }) {} + accessibility_bridge_([this](bool enabled) { PlatformView::SetSemanticsEnabled(enabled); }), + platform_message_handler_(new PlatformMessageHandlerIos(task_runners)) {} PlatformViewIOS::PlatformViewIOS( PlatformView::Delegate& delegate, @@ -67,13 +68,9 @@ PlatformViewIOS::~PlatformViewIOS() = default; -PlatformMessageRouter& PlatformViewIOS::GetPlatformMessageRouter() { - return platform_message_router_; -} - // |PlatformView| void PlatformViewIOS::HandlePlatformMessage(std::unique_ptr message) { - platform_message_router_.HandlePlatformMessage(std::move(message)); + platform_message_handler_->HandlePlatformMessage(std::move(message)); } fml::WeakPtr PlatformViewIOS::GetOwnerViewController() const { From 4e078f700cc96cb0d5662e8ef6c41a5f4349ca4c Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Tue, 11 Jan 2022 14:01:49 -0800 Subject: [PATCH 2/3] added test that passes before this change, and fails after it --- .../ios/framework/Source/FlutterEngineTest.mm | 21 +++++++++++++++++++ .../ios/framework/Source/FlutterEngine_Test.h | 2 ++ 2 files changed, 23 insertions(+) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm index 0f7fa74fad041..8283c5b3f2840 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm @@ -218,4 +218,25 @@ - (void)testDeallocNotification { [center removeObserver:observer]; } +- (void)testSetHandlerAfterRun { + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"]; + NSObject* registrar = [engine registrarForPlugin:@"foo"]; + XCTestExpectation* gotMessage = [self expectationWithDescription:@"gotMessage"]; + fml::AutoResetWaitableEvent latch; + [engine run]; + flutter::Shell& shell = engine.shell; + engine.shell.GetTaskRunners().GetUITaskRunner()->PostTask([&latch, &shell] { + flutter::Engine::Delegate& delegate = shell; + auto message = std::make_unique("foo", nullptr); + delegate.OnEngineHandlePlatformMessage(std::move(message)); + latch.Signal(); + }); + latch.Wait(); + [registrar.messenger setMessageHandlerOnChannel:@"foo" + binaryMessageHandler:^(NSData* message, FlutterBinaryReply reply) { + [gotMessage fulfill]; + }]; + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h index c080a1d3cfb98..ef20a3b4c8e2e 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#import "flutter/shell/common/shell.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h" #import "flutter/shell/platform/darwin/ios/rendering_api_selection.h" @@ -15,6 +16,7 @@ class ThreadHost; // Category to add test-only visibility. @interface FlutterEngine (Test) +- (flutter::Shell&)shell; - (void)setBinaryMessenger:(FlutterBinaryMessengerRelay*)binaryMessenger; - (flutter::IOSRenderingAPI)platformViewsRenderingAPI; - (void)waitForFirstFrame:(NSTimeInterval)timeout callback:(void (^)(BOOL didTimeout))callback; From 6a91c6afc76307f0a6d1d21614c817d901b091ef Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Tue, 11 Jan 2022 14:19:46 -0800 Subject: [PATCH 3/3] started supporting backwards compatible usage of setting handlers --- shell/common/shell.cc | 32 ++++++++++++++++++- shell/common/shell.h | 1 + .../ios/framework/Source/FlutterEngineTest.mm | 30 +++++++++-------- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/shell/common/shell.cc b/shell/common/shell.cc index ff891ffbcac17..5228392561d6d 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -632,6 +632,13 @@ bool Shell::Setup(std::unique_ptr platform_view, platform_view_ = std::move(platform_view); platform_message_handler_ = platform_view_->GetPlatformMessageHandler(); + route_messages_through_platform_thread_.store(true); + task_runners_.GetPlatformTaskRunner()->PostTask( + [self = weak_factory_.GetWeakPtr()] { + if (self) { + self->route_messages_through_platform_thread_.store(false); + } + }); engine_ = std::move(engine); rasterizer_ = std::move(rasterizer); io_manager_ = io_manager; @@ -1183,7 +1190,30 @@ void Shell::OnEngineHandlePlatformMessage( } if (platform_message_handler_) { - platform_message_handler_->HandlePlatformMessage(std::move(message)); + if (route_messages_through_platform_thread_) { + // We route messages through the platform thread temporarily when the + // shell is being initialized to be backwards compatible with setting + // message handlers in the same event as starting the isolate, but after + // it is started. + auto ui_task_runner = task_runners_.GetUITaskRunner(); + task_runners_.GetPlatformTaskRunner()->PostTask(fml::MakeCopyable( + [weak_platform_message_handler = + std::weak_ptr(platform_message_handler_), + message = std::move(message), ui_task_runner]() mutable { + ui_task_runner->PostTask(fml::MakeCopyable( + [weak_platform_message_handler, message = std::move(message), + ui_task_runner]() mutable { + auto platform_message_handler = + weak_platform_message_handler.lock(); + if (platform_message_handler) { + platform_message_handler->HandlePlatformMessage( + std::move(message)); + } + })); + })); + } else { + platform_message_handler_->HandlePlatformMessage(std::move(message)); + } } else { task_runners_.GetPlatformTaskRunner()->PostTask( fml::MakeCopyable([view = platform_view_->GetWeakPtr(), diff --git a/shell/common/shell.h b/shell/common/shell.h index 0448da1c424d7..ae7a9b3578033 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -419,6 +419,7 @@ class Shell final : public PlatformView::Delegate, std::shared_ptr is_gpu_disabled_sync_switch_; std::shared_ptr volatile_path_tracker_; std::shared_ptr platform_message_handler_; + std::atomic route_messages_through_platform_thread_ = false; fml::WeakPtr weak_engine_; // to be shared across threads fml::TaskRunnerAffineWeakPtr diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm index 8283c5b3f2840..4a5f1f75d8c9b 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm @@ -220,22 +220,24 @@ - (void)testDeallocNotification { - (void)testSetHandlerAfterRun { FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"]; - NSObject* registrar = [engine registrarForPlugin:@"foo"]; XCTestExpectation* gotMessage = [self expectationWithDescription:@"gotMessage"]; - fml::AutoResetWaitableEvent latch; - [engine run]; - flutter::Shell& shell = engine.shell; - engine.shell.GetTaskRunners().GetUITaskRunner()->PostTask([&latch, &shell] { - flutter::Engine::Delegate& delegate = shell; - auto message = std::make_unique("foo", nullptr); - delegate.OnEngineHandlePlatformMessage(std::move(message)); - latch.Signal(); + dispatch_async(dispatch_get_main_queue(), ^{ + NSObject* registrar = [engine registrarForPlugin:@"foo"]; + fml::AutoResetWaitableEvent latch; + [engine run]; + flutter::Shell& shell = engine.shell; + engine.shell.GetTaskRunners().GetUITaskRunner()->PostTask([&latch, &shell] { + flutter::Engine::Delegate& delegate = shell; + auto message = std::make_unique("foo", nullptr); + delegate.OnEngineHandlePlatformMessage(std::move(message)); + latch.Signal(); + }); + latch.Wait(); + [registrar.messenger setMessageHandlerOnChannel:@"foo" + binaryMessageHandler:^(NSData* message, FlutterBinaryReply reply) { + [gotMessage fulfill]; + }]; }); - latch.Wait(); - [registrar.messenger setMessageHandlerOnChannel:@"foo" - binaryMessageHandler:^(NSData* message, FlutterBinaryReply reply) { - [gotMessage fulfill]; - }]; [self waitForExpectationsWithTimeout:1 handler:nil]; }