diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index cde5c91c75a28..536c859585413 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1227,6 +1227,7 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppD FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterDartProject.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterMacOS.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterPlatformViews.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterPluginMacOS.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterPluginRegistrarMacOS.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h @@ -1288,6 +1289,9 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterOpenG FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMac.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMac.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMacTest.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewControllerTest.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderingBackend.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderingBackend.mm @@ -1316,6 +1320,8 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/KeyCodeMap.m FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/KeyCodeMap_Internal.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/MacOSGLContextSwitch.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/MacOSGLContextSwitch.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/TestFlutterPlatformView.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/TestFlutterPlatformView.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart FILE: ../../../flutter/shell/platform/darwin/macos/framework/module.modulemap FILE: ../../../flutter/shell/platform/embedder/assets/EmbedderInfo.plist diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index b24f9db330c19..fc77383826df9 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -40,6 +40,7 @@ _flutter_framework_headers = [ "framework/Headers/FlutterDartProject.h", "framework/Headers/FlutterEngine.h", "framework/Headers/FlutterMacOS.h", + "framework/Headers/FlutterPlatformViews.h", "framework/Headers/FlutterPluginMacOS.h", "framework/Headers/FlutterPluginRegistrarMacOS.h", "framework/Headers/FlutterViewController.h", @@ -95,6 +96,8 @@ source_set("flutter_framework_source") { "framework/Source/FlutterOpenGLRenderer.mm", "framework/Source/FlutterPlatformNodeDelegateMac.h", "framework/Source/FlutterPlatformNodeDelegateMac.mm", + "framework/Source/FlutterPlatformViewController.h", + "framework/Source/FlutterPlatformViewController.mm", "framework/Source/FlutterRenderer.h", "framework/Source/FlutterRenderingBackend.h", "framework/Source/FlutterRenderingBackend.mm", @@ -186,11 +189,14 @@ executable("flutter_desktop_darwin_unittests") { "framework/Source/FlutterMetalSurfaceManagerTest.mm", "framework/Source/FlutterOpenGLRendererTest.mm", "framework/Source/FlutterPlatformNodeDelegateMacTest.mm", + "framework/Source/FlutterPlatformViewControllerTest.mm", "framework/Source/FlutterTextInputPluginTest.mm", "framework/Source/FlutterTextInputSemanticsObjectTest.mm", "framework/Source/FlutterViewControllerTest.mm", "framework/Source/FlutterViewControllerTestUtils.h", "framework/Source/FlutterViewControllerTestUtils.mm", + "framework/Source/TestFlutterPlatformView.h", + "framework/Source/TestFlutterPlatformView.mm", ] cflags_objcc = flutter_cflags_objcc_arc diff --git a/shell/platform/darwin/macos/framework/Headers/FlutterPlatformViews.h b/shell/platform/darwin/macos/framework/Headers/FlutterPlatformViews.h new file mode 100644 index 0000000000000..f414e35ef7696 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Headers/FlutterPlatformViews.h @@ -0,0 +1,40 @@ +// 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_FLUTTERPLATFORMVIEWS_H_ +#define FLUTTER_FLUTTERPLATFORMVIEWS_H_ + +#import + +#import "FlutterCodecs.h" +#import "FlutterMacros.h" + +@protocol FlutterPlatformViewFactory + +/** + * Create a Platform View which is an `NSView`. + * + * A MacOS plugin should implement this method and return an `NSView`, which can be embedded in a + * Flutter App. + * + * The implementation of this method should create a new `NSView`. + * + * @param viewId A unique identifier for this view. + * @param args Parameters for creating the view sent from the Dart side of the + * Flutter app. If `createArgsCodec` is not implemented, or if no creation arguments were sent from + * the Dart code, this will be null. Otherwise this will be the value sent from the Dart code as + * decoded by `createArgsCodec`. + */ +- (nonnull NSView*)createWithviewIdentifier:(int64_t)viewId arguments:(nullable id)args; + +/** + * Returns the `FlutterMessageCodec` for decoding the args parameter of `createWithFrame`. + * + * Only implement this if `createWithFrame` needs an arguments parameter. + */ +@optional +- (nullable NSObject*)createArgsCodec; +@end + +#endif // FLUTTER_FLUTTERPLATFORMVIEWS_H_ diff --git a/shell/platform/darwin/macos/framework/Headers/FlutterPluginRegistrarMacOS.h b/shell/platform/darwin/macos/framework/Headers/FlutterPluginRegistrarMacOS.h index 28e8a309ecc32..12c7f618a38e8 100644 --- a/shell/platform/darwin/macos/framework/Headers/FlutterPluginRegistrarMacOS.h +++ b/shell/platform/darwin/macos/framework/Headers/FlutterPluginRegistrarMacOS.h @@ -7,6 +7,7 @@ #import "FlutterBinaryMessenger.h" #import "FlutterChannels.h" #import "FlutterMacros.h" +#import "FlutterPlatformViews.h" #import "FlutterPluginMacOS.h" #import "FlutterTexture.h" @@ -48,6 +49,18 @@ FLUTTER_DARWIN_EXPORT - (void)addMethodCallDelegate:(nonnull id)delegate channel:(nonnull FlutterMethodChannel*)channel; +/** + * Registers a `FlutterPlatformViewFactory` for creation of platform views. + * + * Plugins expose `NSView` for embedding in Flutter apps by registering a view factory. + * + * @param factory The view factory that will be registered. + * @param factoryId A unique identifier for the factory, the Dart code of the Flutter app can use + * this identifier to request creation of a `NSView` by the registered factory. + */ +- (void)registerViewFactory:(nonnull NSObject*)factory + withId:(nonnull NSString*)factoryId; + @end /** diff --git a/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h b/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h index 2349dad3be4b9..43c88215d3f90 100644 --- a/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h +++ b/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h @@ -6,6 +6,7 @@ #import "FlutterEngine.h" #import "FlutterMacros.h" +#import "FlutterPlatformViews.h" #import "FlutterPluginRegistrarMacOS.h" /** diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index c62353b10d64d..a69258c47ad17 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -15,9 +15,10 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalRenderer.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterOpenGLRenderer.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderingBackend.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" -#include "flutter/shell/platform/embedder/embedder.h" +#import "flutter/shell/platform/embedder/embedder.h" /** * Constructs and returns a FlutterLocale struct corresponding to |locale|, which must outlive @@ -95,6 +96,11 @@ - (void)postMainThreadTask:(FlutterTask)task targetTimeInNanoseconds:(uint64_t)t */ - (void)loadAOTData:(NSString*)assetsDir; +/** + * Creates a platform view channel and sets up the method handler. + */ +- (void)setupPlatformViewChannel; + @end #pragma mark - @@ -145,6 +151,11 @@ - (void)addMethodCallDelegate:(nonnull id)delegate }]; } +- (void)registerViewFactory:(nonnull NSObject*)factory + withId:(nonnull NSString*)factoryId { + [[_flutterEngine platformViewController] registerViewFactory:factory withId:factoryId]; +} + @end // Callbacks provided to the engine. See the called methods for documentation. @@ -186,6 +197,14 @@ @implementation FlutterEngine { // FlutterCompositor is copied and used in embedder.cc. FlutterCompositor _compositor; + + // Method channel for platform view functions. These functions include creating, disposing and + // mutating a platform view. + FlutterMethodChannel* _platformViewsChannel; + + // Used to support creation and deletion of platform views and registering platform view + // factories. Lifecycle is tied to the engine. + FlutterPlatformViewController* _platformViewController; } - (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)project { @@ -219,6 +238,9 @@ - (instancetype)initWithName:(NSString*)labelPrefix name:NSCurrentLocaleDidChangeNotification object:nil]; + _platformViewController = [[FlutterPlatformViewController alloc] init]; + [self setupPlatformViewChannel]; + return self; } @@ -383,8 +405,8 @@ - (FlutterCompositor*)createFlutterCompositor { if ([FlutterRenderingBackend renderUsingMetal]) { FlutterMetalRenderer* metalRenderer = reinterpret_cast(_renderer); - _macOSCompositor = - std::make_unique(_viewController, metalRenderer.device); + _macOSCompositor = std::make_unique( + _viewController, _platformViewController, metalRenderer.device); _macOSCompositor->SetPresentCallback([weakSelf](bool has_flutter_content) { if (has_flutter_content) { FlutterMetalRenderer* metalRenderer = @@ -541,6 +563,10 @@ - (void)dispatchSemanticsAction:(FlutterSemanticsAction)action _embedderAPI.DispatchSemanticsAction(_engine, target, action, data.GetMapping(), data.GetSize()); } +- (FlutterPlatformViewController*)platformViewController { + return _platformViewController; +} + #pragma mark - Private methods - (void)sendUserLocales { @@ -630,6 +656,18 @@ - (void)shutDownEngine { _engine = nullptr; } +- (void)setupPlatformViewChannel { + _platformViewsChannel = + [FlutterMethodChannel methodChannelWithName:@"flutter/platform_views" + binaryMessenger:self.binaryMessenger + codec:[FlutterStandardMethodCodec sharedInstance]]; + + __weak FlutterEngine* weakSelf = self; + [_platformViewsChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { + [[weakSelf platformViewController] handleMethodCall:call result:result]; + }]; +} + #pragma mark - FlutterBinaryMessenger - (void)sendOnChannel:(nonnull NSString*)channel message:(nullable NSData*)message { @@ -781,20 +819,22 @@ - (void)updateSemanticsCustomActions:(const FlutterSemanticsCustomAction*)action #pragma mark - Task runner integration -- (void)postMainThreadTask:(FlutterTask)task targetTimeInNanoseconds:(uint64_t)targetTime { - const auto engine_time = _embedderAPI.GetCurrentTime(); +- (void)runTaskOnEmbedder:(FlutterTask)task { + if (_engine) { + auto result = _embedderAPI.RunTask(_engine, &task); + if (result != kSuccess) { + NSLog(@"Could not post a task to the Flutter engine."); + } + } +} - __weak FlutterEngine* weak_self = self; +- (void)postMainThreadTask:(FlutterTask)task targetTimeInNanoseconds:(uint64_t)targetTime { + __weak FlutterEngine* weakSelf = self; auto worker = ^{ - FlutterEngine* strong_self = weak_self; - if (strong_self && strong_self->_engine) { - auto result = _embedderAPI.RunTask(strong_self->_engine, &task); - if (result != kSuccess) { - NSLog(@"Could not post a task to the Flutter engine."); - } - } + [weakSelf runTaskOnEmbedder:task]; }; + const auto engine_time = _embedderAPI.GetCurrentTime(); if (targetTime <= engine_time) { dispatch_async(dispatch_get_main_queue(), worker); diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm index 7710c8b4597c5..0b341865f7903 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm @@ -343,7 +343,8 @@ @interface FlutterEngine (Test) ASSERT_TRUE(latch_called); } -TEST(FlutterEngine, Compositor) { +// TODO: Enable after https://github.com/flutter/flutter/issues/96668 is fixed. +TEST(FlutterEngine, DISABLED_Compositor) { NSString* fixtures = @(flutter::testing::GetFixturesPath()); FlutterDartProject* project = [[FlutterDartProject alloc] initWithAssetsPath:fixtures diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h index 8066bd579eae7..0629ba3abc916 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h @@ -10,6 +10,7 @@ #include "flutter/shell/platform/common/accessibility_bridge.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h" @interface FlutterEngine () @@ -70,6 +71,8 @@ */ - (BOOL)unregisterTextureWithID:(int64_t)textureID; +- (nonnull FlutterPlatformViewController*)platformViewController; + // Accessibility API. /** diff --git a/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.h b/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.h index 2c089e2f59317..84e67710fc445 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.h @@ -7,13 +7,16 @@ #include "flutter/fml/macros.h" #include "flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h" namespace flutter { class FlutterMetalCompositor : public FlutterCompositor { public: - explicit FlutterMetalCompositor(FlutterViewController* view_controller, - id mtl_device); + explicit FlutterMetalCompositor( + FlutterViewController* view_controller, + FlutterPlatformViewController* platform_views_controller, + id mtl_device); virtual ~FlutterMetalCompositor() = default; @@ -42,7 +45,12 @@ class FlutterMetalCompositor : public FlutterCompositor { bool Present(const FlutterLayer** layers, size_t layers_count) override; private: + // Presents the platform view layer represented by `layer`. `layer_index` is + // used to position the layer in the z-axis. + void PresentPlatformView(const FlutterLayer* layer, size_t layer_index); + const id mtl_device_; + const FlutterPlatformViewController* platform_views_controller_; FML_DISALLOW_COPY_AND_ASSIGN(FlutterMetalCompositor); }; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.mm b/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.mm index 101df075dbb5b..e1bcf9bab1f6b 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.mm @@ -10,9 +10,13 @@ namespace flutter { -FlutterMetalCompositor::FlutterMetalCompositor(FlutterViewController* view_controller, - id mtl_device) - : FlutterCompositor(view_controller), mtl_device_(mtl_device) {} +FlutterMetalCompositor::FlutterMetalCompositor( + FlutterViewController* view_controller, + FlutterPlatformViewController* platform_views_controller, + id mtl_device) + : FlutterCompositor(view_controller), + mtl_device_(mtl_device), + platform_views_controller_(platform_views_controller) {} bool FlutterMetalCompositor::CreateBackingStore(const FlutterBackingStoreConfig* config, FlutterBackingStore* backing_store_out) { @@ -77,7 +81,6 @@ SetFrameStatus(FrameStatus::kPresenting); bool has_flutter_content = false; - for (size_t i = 0; i < layers_count; ++i) { const auto* layer = layers[i]; FlutterBackingStore* backing_store = const_cast(layer->backing_store); @@ -94,8 +97,7 @@ break; } case kFlutterLayerContentTypePlatformView: - // Add functionality in follow up PR. - FML_LOG(WARNING) << "Presenting PlatformViews not yet supported"; + PresentPlatformView(layer, i); break; }; } @@ -103,4 +105,24 @@ return EndFrame(has_flutter_content); } +void FlutterMetalCompositor::PresentPlatformView(const FlutterLayer* layer, size_t layer_position) { + // TODO (https://github.com/flutter/flutter/issues/96668) + // once the issue is fixed, this check will pass. + FML_DCHECK([[NSThread currentThread] isMainThread]) + << "Must be on the main thread to present platform views"; + + int64_t platform_view_id = layer->platform_view->identifier; + NSView* platform_view = [platform_views_controller_ platformViewWithID:platform_view_id]; + + FML_DCHECK(platform_view) << "Platform view not found for id: " << platform_view_id; + + CGFloat scale = [[NSScreen mainScreen] backingScaleFactor]; + platform_view.frame = CGRectMake(layer->offset.x / scale, layer->offset.y / scale, + layer->size.width / scale, layer->size.height / scale); + if (platform_view.superview == nil) { + [view_controller_.flutterView addSubview:platform_view]; + } + platform_view.layer.zPosition = layer_position; +} + } // namespace flutter diff --git a/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositorUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositorUnittests.mm index 6d6ee12ea494c..8a94ecd3d647b 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositorUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositorUnittests.mm @@ -14,7 +14,8 @@ id mockViewController = CreateMockViewController(nil); std::unique_ptr macos_compositor = - std::make_unique(mockViewController, nullptr); + std::make_unique( + mockViewController, /*platform_view_controller*/ nullptr, /*mtl_device*/ nullptr); bool flag = false; macos_compositor->SetPresentCallback([f = &flag](bool has_flutter_content) { @@ -31,7 +32,8 @@ [mockViewController loadView]; std::unique_ptr macos_compositor = - std::make_unique(mockViewController, nullptr); + std::make_unique( + mockViewController, /*platform_view_controller*/ nullptr, /*mtl_device*/ nullptr); FlutterBackingStore backing_store; FlutterBackingStoreConfig config; @@ -52,7 +54,8 @@ [mockViewController loadView]; std::unique_ptr macos_compositor = - std::make_unique(mockViewController, nullptr); + std::make_unique( + mockViewController, /*platform_view_controller*/ nullptr, /*mtl_device*/ nullptr); FlutterBackingStore backing_store; FlutterBackingStoreConfig config; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h new file mode 100644 index 0000000000000..d0969e1f1a443 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h @@ -0,0 +1,56 @@ +// 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 "FlutterChannels.h" + +#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterPlatformViews.h" + +#include +#include + +@interface FlutterPlatformViewController : NSViewController +@end + +@interface FlutterPlatformViewController () + +/** + * Creates a platform view of viewType with viewId. + * FlutterResult is updated to contain nil for success or to contain + * a FlutterError if there is an error. + */ +- (void)onCreateWithViewID:(int64_t)viewId + viewType:(nonnull NSString*)viewType + result:(nonnull FlutterResult)result; + +/** + * Disposes the platform view with `viewId`. + * FlutterResult is updated to contain nil for success or a FlutterError if there is an error. + */ +- (void)onDisposeWithViewID:(int64_t)viewId result:(nonnull FlutterResult)result; + +/** + * Returns the platform view associated with the viewId. + */ +- (nullable NSView*)platformViewWithID:(int64_t)viewId; + +/** + * Register a view factory by adding an entry into the platformViewFactories map with key factoryId + * and value factory. + */ +- (void)registerViewFactory:(nonnull NSObject*)factory + withId:(nonnull NSString*)factoryId; + +/** + * Handles platform view related method calls, for example create, dispose, etc. + */ +- (void)handleMethodCall:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result; + +/** + * Removes platform views slated to be disposed via method handler calls. + */ +- (void)disposePlatformViews; + +@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.mm b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.mm new file mode 100644 index 0000000000000..1bdae16115dcf --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.mm @@ -0,0 +1,114 @@ +// 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/fml/logging.h" + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h" + +@implementation FlutterPlatformViewController { + // NSDictionary maps platform view type identifiers to FlutterPlatformViewFactories. + NSMutableDictionary*>* _platformViewFactories; + + // Map from platform view id to the underlying NSView. + std::map _platformViews; + + // View ids that are going to be disposed on the next present call. + std::unordered_set _platformViewsToDispose; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _platformViewFactories = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void)onCreateWithViewID:(int64_t)viewId + viewType:(nonnull NSString*)viewType + result:(nonnull FlutterResult)result { + if (_platformViews.count(viewId) != 0) { + result([FlutterError errorWithCode:@"recreating_view" + message:@"trying to create an already created view" + details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]); + return; + } + + NSObject* factory = _platformViewFactories[viewType]; + if (!factory) { + result([FlutterError + errorWithCode:@"unregistered_view_type" + message:@"trying to create a view with an unregistered type" + details:[NSString stringWithFormat:@"unregistered view type: '%@'", viewType]]); + return; + } + + NSView* platform_view = [factory createWithviewIdentifier:viewId arguments:nil]; + _platformViews[viewId] = platform_view; + result(nil); +} + +- (void)onDisposeWithViewID:(int64_t)viewId result:(nonnull FlutterResult)result { + if (_platformViews.count(viewId) == 0) { + result([FlutterError errorWithCode:@"unknown_view" + message:@"trying to dispose an unknown" + details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]); + return; + } + + // The following disposePlatformViews call will dispose the views. + _platformViewsToDispose.insert(viewId); + result(nil); +} + +- (void)registerViewFactory:(nonnull NSObject*)factory + withId:(nonnull NSString*)factoryId { + _platformViewFactories[factoryId] = factory; +} + +- (nullable NSView*)platformViewWithID:(int64_t)viewId { + if (_platformViews.count(viewId)) { + return _platformViews[viewId]; + } else { + return nil; + } +} + +- (void)handleMethodCall:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result { + if ([[call method] isEqualToString:@"create"]) { + NSMutableDictionary* args = [call arguments]; + if ([args objectForKey:@"id"]) { + int64_t viewId = [args[@"id"] longValue]; + NSString* viewType = [NSString stringWithUTF8String:([args[@"viewType"] UTF8String])]; + [self onCreateWithViewID:viewId viewType:viewType result:result]; + } else { + result([FlutterError errorWithCode:@"unknown_view" + message:@"'id' argument must be passed to create a platform view." + details:[NSString stringWithFormat:@"'id' not specified."]]); + } + } else if ([[call method] isEqualToString:@"dispose"]) { + NSNumber* arg = [call arguments]; + int64_t viewId = [arg longValue]; + [self onDisposeWithViewID:viewId result:result]; + } else { + result(FlutterMethodNotImplemented); + } +} + +- (void)disposePlatformViews { + if (_platformViewsToDispose.empty()) { + return; + } + + FML_DCHECK([[NSThread currentThread] isMainThread]) + << "Must be on the main thread to handle disposing platform views"; + for (int64_t viewId : _platformViewsToDispose) { + NSView* view = _platformViews[viewId]; + [view removeFromSuperview]; + _platformViews.erase(viewId); + } + _platformViewsToDispose.clear(); +} + +@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewControllerTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewControllerTest.mm new file mode 100644 index 0000000000000..03c435ec0b369 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewControllerTest.mm @@ -0,0 +1,128 @@ +// 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/macos/framework/Source/FlutterPlatformViewController.h" + +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/TestFlutterPlatformView.h" + +#include "flutter/testing/testing.h" + +namespace flutter::testing { + +TEST(FlutterPlatformViewController, TestCreatePlatformViewNoMatchingViewType) { + // Use id so we can access handleMethodCall method. + id platformViewController = [[FlutterPlatformViewController alloc] init]; + + FlutterMethodCall* methodCall = + [FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"FlutterPlatformViewMock" + }]; + + __block bool errored = false; + FlutterResult result = ^(id result) { + if ([result isKindOfClass:[FlutterError class]]) { + errored = true; + } + }; + + [platformViewController handleMethodCall:methodCall result:result]; + + // We expect the call to error since no factories are registered. + EXPECT_TRUE(errored); +} + +TEST(FlutterPlatformViewController, TestRegisterPlatformViewFactoryAndCreate) { + // Use id so we can access handleMethodCall method. + id platformViewController = [[FlutterPlatformViewController alloc] init]; + + TestFlutterPlatformViewFactory* factory = [TestFlutterPlatformViewFactory alloc]; + + [platformViewController registerViewFactory:factory withId:@"MockPlatformView"]; + + FlutterMethodCall* methodCall = + [FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockPlatformView" + }]; + + __block bool success = false; + FlutterResult result = ^(id result) { + // If a platform view is successfully created, the result is nil. + if (result == nil) { + success = true; + } + }; + [platformViewController handleMethodCall:methodCall result:result]; + + EXPECT_TRUE(success); +} + +TEST(FlutterPlatformViewController, TestCreateAndDispose) { + // Use id so we can access handleMethodCall method. + id platformViewController = [[FlutterPlatformViewController alloc] init]; + + TestFlutterPlatformViewFactory* factory = [TestFlutterPlatformViewFactory alloc]; + + [platformViewController registerViewFactory:factory withId:@"MockPlatformView"]; + + FlutterMethodCall* methodCallOnCreate = + [FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockPlatformView" + }]; + + __block bool created = false; + FlutterResult resultOnCreate = ^(id result) { + // If a platform view is successfully created, the result is nil. + if (result == nil) { + created = true; + } + }; + + [platformViewController handleMethodCall:methodCallOnCreate result:resultOnCreate]; + + FlutterMethodCall* methodCallOnDispose = + [FlutterMethodCall methodCallWithMethodName:@"dispose" + arguments:[NSNumber numberWithLongLong:2]]; + + __block bool disposed = false; + FlutterResult resultOnDispose = ^(id result) { + // If a platform view is successfully created, the result is nil. + if (result == nil) { + disposed = true; + } + }; + + [platformViewController handleMethodCall:methodCallOnDispose result:resultOnDispose]; + + EXPECT_TRUE(created); + EXPECT_TRUE(disposed); +} + +TEST(FlutterPlatformViewController, TestDisposeOnMissingViewId) { + // Use id so we can access handleMethodCall method. + id platformViewController = [[FlutterPlatformViewController alloc] init]; + + FlutterMethodCall* methodCall = + [FlutterMethodCall methodCallWithMethodName:@"dispose" + arguments:[NSNumber numberWithLongLong:20]]; + + __block bool errored = false; + FlutterResult result = ^(id result) { + if ([result isKindOfClass:[FlutterError class]]) { + errored = true; + } + }; + + [platformViewController handleMethodCall:methodCall result:result]; + + EXPECT_TRUE(errored); +} + +} // namespace flutter::testing diff --git a/shell/platform/darwin/macos/framework/Source/TestFlutterPlatformView.h b/shell/platform/darwin/macos/framework/Source/TestFlutterPlatformView.h new file mode 100644 index 0000000000000..719000ae66f08 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/TestFlutterPlatformView.h @@ -0,0 +1,13 @@ +// 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/macos/framework/Headers/FlutterEngine.h" + +@interface TestFlutterPlatformView : NSView +@end + +@interface TestFlutterPlatformViewFactory : NSObject +@end diff --git a/shell/platform/darwin/macos/framework/Source/TestFlutterPlatformView.mm b/shell/platform/darwin/macos/framework/Source/TestFlutterPlatformView.mm new file mode 100644 index 0000000000000..7f3ade5f1e06e --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/TestFlutterPlatformView.mm @@ -0,0 +1,28 @@ +// 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 + +#import "flutter/shell/platform/darwin/macos/framework/Source/TestFlutterPlatformView.h" + +@implementation TestFlutterPlatformView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + return self; +} + +@end + +@implementation TestFlutterPlatformViewFactory +- (NSView*)createWithviewIdentifier:(int64_t)viewId arguments:(nullable id)args { + return [[TestFlutterPlatformView alloc] initWithFrame:CGRectZero]; +} + +- (NSObject*)createArgsCodec { + return [FlutterStandardMessageCodec sharedInstance]; +} + +@end