From 04c7209f09c7a2f3b78bb334030e98ae761c9787 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 6 Sep 2023 12:45:00 -0400 Subject: [PATCH] Add macOS support for plugin value publishing --- .../Headers/FlutterPluginRegistrarMacOS.h | 24 +++++++++ .../macos/framework/Source/FlutterEngine.mm | 43 ++++++++++++++-- .../framework/Source/FlutterEngineTest.mm | 50 +++++++++++++++++++ .../framework/Source/FlutterViewController.mm | 4 ++ 4 files changed, 118 insertions(+), 3 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Headers/FlutterPluginRegistrarMacOS.h b/shell/platform/darwin/macos/framework/Headers/FlutterPluginRegistrarMacOS.h index 5affc8d0fe9da..16768776afb0c 100644 --- a/shell/platform/darwin/macos/framework/Headers/FlutterPluginRegistrarMacOS.h +++ b/shell/platform/darwin/macos/framework/Headers/FlutterPluginRegistrarMacOS.h @@ -71,6 +71,20 @@ FLUTTER_DARWIN_EXPORT - (void)registerViewFactory:(nonnull NSObject*)factory withId:(nonnull NSString*)factoryId; +/** + * Publishes a value for external use of the plugin. + * + * Plugins may publish a single value, such as an instance of the + * plugin's main class, for situations where external control or + * interaction is needed. + * + * The published value will be available from the `FlutterPluginRegistry`. + * Repeated calls overwrite any previous publication. + * + * @param value The value to be published. + */ +- (void)publish:(nonnull NSObject*)value; + /** * Returns the file name for the given asset. * The returned file name can be used to access the asset in the application's main bundle. @@ -119,4 +133,14 @@ FLUTTER_DARWIN_EXPORT */ - (nonnull id)registrarForPlugin:(nonnull NSString*)pluginKey; +/** + * Returns a value published by the specified plugin. + * + * @param pluginKey The unique key identifying the plugin. + * @return An object published by the plugin, if any. Will be `NSNull` if + * nothing has been published. Will be `nil` if the plugin has not been + * registered. + */ +- (nullable NSObject*)valuePublishedByPlugin:(nonnull NSString*)pluginKey; + @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 7d4a6e6e0670c..ac808395c0c05 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -25,6 +25,8 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewEngineProvider.h" +@class FlutterEngineRegistrar; + NSString* const kFlutterPlatformChannel = @"flutter/platform"; NSString* const kFlutterSettingsChannel = @"flutter/settings"; NSString* const kFlutterLifecycleChannel = @"flutter/lifecycle"; @@ -95,6 +97,12 @@ @interface FlutterEngine () */ @property(nonatomic, strong) NSPointerArray* pluginAppDelegates; +/** + * All registrars returned from registrarForPlugin: + */ +@property(nonatomic, readonly) + NSMutableDictionary* pluginRegistrars; + - (nullable FlutterViewController*)viewControllerForId:(FlutterViewId)viewId; /** @@ -274,12 +282,19 @@ @interface FlutterEngineRegistrar : NSObject - (instancetype)initWithPlugin:(nonnull NSString*)pluginKey flutterEngine:(nonnull FlutterEngine*)flutterEngine; -- (NSView*)viewForId:(FlutterViewId)viewId; +- (nullable NSView*)viewForId:(FlutterViewId)viewId; + +/** + * The value published by this plugin, or NSNull if nothing has been published. + * + * The unusual NSNull is for the documented behavior of valuePublishedByPlugin:. + */ +@property(nonatomic, readonly, nonnull) NSObject* publishedValue; @end @implementation FlutterEngineRegistrar { NSString* _pluginKey; - FlutterEngine* _flutterEngine; + __weak FlutterEngine* _flutterEngine; } @dynamic view; @@ -289,6 +304,7 @@ - (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine if (self) { _pluginKey = [pluginKey copy]; _flutterEngine = flutterEngine; + _publishedValue = [NSNull null]; } return self; } @@ -340,6 +356,10 @@ - (void)registerViewFactory:(nonnull NSObject*)facto [[_flutterEngine platformViewController] registerViewFactory:factory withId:factoryId]; } +- (void)publish:(NSObject*)value { + _publishedValue = value; +} + - (NSString*)lookupKeyForAsset:(NSString*)asset { return [FlutterDartProject lookupKeyForAsset:asset]; } @@ -438,6 +458,7 @@ - (instancetype)initWithName:(NSString*)labelPrefix _messengerHandlers = [[NSMutableDictionary alloc] init]; _binaryMessenger = [[FlutterBinaryMessengerRelay alloc] initWithParent:self]; _pluginAppDelegates = [NSPointerArray weakObjectsPointerArray]; + _pluginRegistrars = [[NSMutableDictionary alloc] init]; _currentMessengerConnection = 1; _allowHeadlessExecution = allowHeadlessExecution; _semanticsEnabled = NO; @@ -494,6 +515,11 @@ - (void)dealloc { } } } + // Clear any published values, just in case a plugin has created a retain cycle with the + // registrar. + for (NSString* pluginName in _pluginRegistrars) { + [_pluginRegistrars[pluginName] publish:[NSNull null]]; + } @synchronized(_isResponseValid) { [_isResponseValid removeAllObjects]; [_isResponseValid addObject:@NO]; @@ -1334,7 +1360,18 @@ - (void)cleanUpConnection:(FlutterBinaryMessengerConnection)connection { #pragma mark - FlutterPluginRegistry - (id)registrarForPlugin:(NSString*)pluginName { - return [[FlutterEngineRegistrar alloc] initWithPlugin:pluginName flutterEngine:self]; + id registrar = self.pluginRegistrars[pluginName]; + if (!registrar) { + FlutterEngineRegistrar* registrarImpl = + [[FlutterEngineRegistrar alloc] initWithPlugin:pluginName flutterEngine:self]; + self.pluginRegistrars[pluginName] = registrarImpl; + registrar = registrarImpl; + } + return registrar; +} + +- (nullable NSObject*)valuePublishedByPlugin:(NSString*)pluginName { + return self.pluginRegistrars[pluginName].publishedValue; } #pragma mark - FlutterTextureRegistrar diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm index fee6cdefadb51..4aa073de93506 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm @@ -608,6 +608,56 @@ + (void)registerWithRegistrar:(id)registrar { EXPECT_EQ(weakEngine, nil); } +TEST_F(FlutterEngineTest, PublishedValueNilForUnknownPlugin) { + NSString* fixtures = @(flutter::testing::GetFixturesPath()); + FlutterDartProject* project = [[FlutterDartProject alloc] + initWithAssetsPath:fixtures + ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]]; + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" + project:project + allowHeadlessExecution:YES]; + + EXPECT_EQ([engine valuePublishedByPlugin:@"NoSuchPlugin"], nil); +} + +TEST_F(FlutterEngineTest, PublishedValueNSNullIfNoPublishedValue) { + NSString* fixtures = @(flutter::testing::GetFixturesPath()); + FlutterDartProject* project = [[FlutterDartProject alloc] + initWithAssetsPath:fixtures + ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]]; + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" + project:project + allowHeadlessExecution:YES]; + NSString* pluginName = @"MyPlugin"; + // Request the registarar to register the plugin as existing. + [engine registrarForPlugin:pluginName]; + + // The documented behavior is that a plugin that exists but hasn't published + // anything returns NSNull, rather than nil, as on iOS. + EXPECT_EQ([engine valuePublishedByPlugin:pluginName], [NSNull null]); +} + +TEST_F(FlutterEngineTest, PublishedValueReturnsLastPublished) { + NSString* fixtures = @(flutter::testing::GetFixturesPath()); + FlutterDartProject* project = [[FlutterDartProject alloc] + initWithAssetsPath:fixtures + ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]]; + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" + project:project + allowHeadlessExecution:YES]; + NSString* pluginName = @"MyPlugin"; + id registrar = [engine registrarForPlugin:pluginName]; + + NSString* firstValue = @"A published value"; + NSArray* secondValue = @[ @"A different published value" ]; + + [registrar publish:firstValue]; + EXPECT_EQ([engine valuePublishedByPlugin:pluginName], firstValue); + + [registrar publish:secondValue]; + EXPECT_EQ([engine valuePublishedByPlugin:pluginName], secondValue); +} + // If a channel overrides a previous channel with the same name, cleaning // the previous channel should not affect the new channel. // diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm index c86403d1a1390..62d81dbaa43bd 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm @@ -903,6 +903,10 @@ - (void)viewDidReshape:(NSView*)view { return [_engine registrarForPlugin:pluginName]; } +- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey { + return [_engine valuePublishedByPlugin:pluginKey]; +} + #pragma mark - FlutterKeyboardViewDelegate - (void)sendKeyEvent:(const FlutterKeyEvent&)event