From 182b97516fe38b8b1f572d21839a4a79eaa1f05e Mon Sep 17 00:00:00 2001 From: richardjcai Date: Fri, 4 Dec 2020 23:24:11 -0500 Subject: [PATCH 1/5] Add MacOS platform view support to FlutterViewController Add platform view support in FlutterGLCompositor and FlutterViewController --- ci/licenses_golden/licenses_flutter | 1 + shell/platform/darwin/macos/BUILD.gn | 1 + .../framework/Source/FlutterGLCompositor.h | 9 +++ .../framework/Source/FlutterGLCompositor.mm | 71 ++++++++++++++----- .../framework/Source/FlutterPlatformViews.h | 62 ++++++++++++++++ .../framework/Source/FlutterViewController.mm | 68 ++++++++++++++++++ .../Source/FlutterViewController_Internal.h | 45 +++++++++++- 7 files changed, 238 insertions(+), 19 deletions(-) create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 28b7135dfc04d..d5a6ab66a0bc8 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1072,6 +1072,7 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSur FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index 79996675a9965..bad5b34ff1a29 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -62,6 +62,7 @@ source_set("flutter_framework_source") { "framework/Source/FlutterIOSurfaceHolder.mm", "framework/Source/FlutterMouseCursorPlugin.h", "framework/Source/FlutterMouseCursorPlugin.mm", + "framework/Source/FlutterPlatformViews.h", "framework/Source/FlutterResizeSynchronizer.h", "framework/Source/FlutterResizeSynchronizer.mm", "framework/Source/FlutterSurfaceManager.h", diff --git a/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h b/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h index e33dd6bb1c7db..e9f39f0df9689 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h @@ -5,6 +5,7 @@ #include #include "flutter/fml/macros.h" +#include "flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStoreData.h" #include "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h" #include "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" #include "flutter/shell/platform/embedder/embedder.h" @@ -66,9 +67,17 @@ class FlutterGLCompositor { // created for the frame. bool frame_started_ = false; + // Update the backing CALayer using the backing store's specifications. + void PresentBackingStoreContent( + FlutterBackingStoreData* flutter_backing_store_data, + size_t layer_position); + // Set frame_started_ to true and reset all layer state. void StartFrame(); + // Remove platform views that are specified for deletion. + void DisposePlatformViews(); + // Creates a CALayer and adds it to ca_layer_map_ and increments // ca_layer_count_; Returns the key value (size_t) for the layer in // ca_layer_map_. diff --git a/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.mm b/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.mm index 4f187232ec143..46af625379f14 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.mm @@ -71,35 +71,31 @@ } bool FlutterGLCompositor::Present(const FlutterLayer** layers, size_t layers_count) { + DisposePlatformViews(); for (size_t i = 0; i < layers_count; ++i) { const auto* layer = layers[i]; FlutterBackingStore* backing_store = const_cast(layer->backing_store); switch (layer->type) { case kFlutterLayerContentTypeBackingStore: { if (backing_store->open_gl.framebuffer.user_data) { - FlutterBackingStoreData* backing_store_data = + FlutterBackingStoreData* flutter_backing_store_data = (__bridge FlutterBackingStoreData*)backing_store->open_gl.framebuffer.user_data; - - FlutterIOSurfaceHolder* io_surface_holder = [backing_store_data ioSurfaceHolder]; - size_t layer_id = [backing_store_data layerId]; - - CALayer* content_layer = ca_layer_map_[layer_id]; - - FML_CHECK(content_layer) << "Unable to find a content layer with layer id " << layer_id; - - content_layer.frame = content_layer.superlayer.bounds; - - // The surface is an OpenGL texture, which means it has origin in bottom left corner - // and needs to be flipped vertically - content_layer.transform = CATransform3DMakeScale(1, -1, 1); - IOSurfaceRef io_surface_contents = [io_surface_holder ioSurface]; - [content_layer setContents:(__bridge id)io_surface_contents]; + PresentBackingStoreContent(flutter_backing_store_data, i); } break; } case kFlutterLayerContentTypePlatformView: - // Add functionality in follow up PR. - FML_LOG(WARNING) << "Presenting PlatformViews not yet supported"; + FML_CHECK([[NSThread currentThread] isMainThread]) + << "Must be on the main thread to handle presenting platform views"; + NSView* platform_view = view_controller_.platformViews[layer->platform_view->identifier]; + 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]; + } else { + platform_view.layer.zPosition = i; + } break; }; } @@ -109,6 +105,29 @@ return present_callback_(); } +void FlutterGLCompositor::PresentBackingStoreContent( + FlutterBackingStoreData* flutter_backing_store_data, + size_t layer_position) { + FML_CHECK([[NSThread currentThread] isMainThread]) + << "Must be on the main thread to update CALayer contents"; + + FlutterIOSurfaceHolder* io_surface_holder = [flutter_backing_store_data ioSurfaceHolder]; + size_t layer_id = [flutter_backing_store_data layerId]; + + CALayer* content_layer = ca_layer_map_[layer_id]; + + FML_CHECK(content_layer) << "Unable to find a content layer with layer id " << layer_id; + + content_layer.frame = content_layer.superlayer.bounds; + content_layer.zPosition = layer_position; + + // The surface is an OpenGL texture, which means it has origin in bottom left corner + // and needs to be flipped vertically + content_layer.transform = CATransform3DMakeScale(1, -1, 1); + IOSurfaceRef io_surface_contents = [io_surface_holder ioSurface]; + [content_layer setContents:(__bridge id)io_surface_contents]; +} + void FlutterGLCompositor::SetPresentCallback( const FlutterGLCompositor::PresentCallback& present_callback) { present_callback_ = present_callback; @@ -139,4 +158,20 @@ return ca_layer_count_++; } +void FlutterGLCompositor::DisposePlatformViews() { + auto views_to_dispose = view_controller_.platformViewsToDispose; + if (views_to_dispose.empty()) { + return; + } + + for (int64_t viewId : views_to_dispose) { + FML_CHECK([[NSThread currentThread] isMainThread]) + << "Must be on the main thread to handle disposing platform views"; + NSView* view = view_controller_.platformViews[viewId]; + [view removeFromSuperview]; + view_controller_.platformViews.erase(viewId); + } + views_to_dispose.clear(); +} + } // namespace flutter diff --git a/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h new file mode 100644 index 0000000000000..d0b415c62876f --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h @@ -0,0 +1,62 @@ +// 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" + +NS_ASSUME_NONNULL_BEGIN + +/** + * Wraps a `NSView` for embedding in the Flutter hierarchy + */ +@protocol FlutterPlatformView +/** + * Returns a reference to the `NSView` that is wrapped by this `FlutterPlatformView`. + * + * It is recommended to return a cached view instance in this method. + * Constructing and returning a new NSView instance in this method might cause undefined behavior. + * + * TODO(richardjcai): Prevent [FlutterPlatformView view] to be called multiple times + * in a single frame. + */ +- (NSView*)view; +@end + +FLUTTER_EXPORT +@protocol FlutterPlatformViewFactory +/** + * Create a `FlutterPlatformView`. + * + * Implemented by MacOS code that expose a `FlutterPlatformView` for embedding in a Flutter app. + * + * The implementation of this method should create a new `FlutterPlatformView` and return it. + * + * @param frame The rectangle for the newly created `FlutterPlatformView` measured in points. + * @param viewId A unique identifier for this `FlutterPlatformView`. + * @param args Parameters for creating the `FlutterPlatformView` 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`. + */ +- (NSObject*)createWithFrame:(CGRect)frame + viewIdentifier:(int64_t)viewId + arguments:(id _Nullable)args; + +/** + * Returns the `FlutterMessageCodec` for decoding the args parameter of `createWithFrame`. + * + * Only needs to be implemented if `createWithFrame` needs an arguments parameter. + */ +@optional +- (NSObject*)createArgsCodec; +@end + +NS_ASSUME_NONNULL_END + +#endif // FLUTTER_FLUTTERPLATFORMVIEWS_H_ diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm index cc5eaba49bf39..1885598d78059 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm @@ -198,6 +198,9 @@ @implementation FlutterViewController { // A method channel for miscellaneous platform functionality. FlutterMethodChannel* _platformChannel; + + // A method channel for platform view functionality. + FlutterMethodChannel* _platformViewsChannel; } @dynamic view; @@ -211,6 +214,7 @@ static void CommonInit(FlutterViewController* controller) { allowHeadlessExecution:NO]; controller->_additionalKeyResponders = [[NSMutableOrderedSet alloc] init]; controller->_mouseTrackingMode = FlutterMouseTrackingModeInKeyWindow; + controller->_factories = [[NSMutableDictionary alloc] init]; } - (instancetype)initWithCoder:(NSCoder*)coder { @@ -377,10 +381,70 @@ - (void)addInternalPlugins { [FlutterMethodChannel methodChannelWithName:@"flutter/platform" binaryMessenger:_engine.binaryMessenger codec:[FlutterJSONMethodCodec sharedInstance]]; + + _platformViewsChannel = + [FlutterMethodChannel methodChannelWithName:@"flutter/platform_views" + binaryMessenger:_engine.binaryMessenger + codec:[FlutterStandardMethodCodec sharedInstance]]; + __weak FlutterViewController* weakSelf = self; [_platformChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { [weakSelf handleMethodCall:call result:result]; }]; + + [_platformViewsChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { + [weakSelf handleMethodCall:call result:result]; + }]; +} + +- (void)onCreate:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result { + NSMutableDictionary* args = [call arguments]; + int64_t viewId = [args[@"id"] longValue]; + NSString* viewType = [NSString stringWithUTF8String:([args[@"viewType"] UTF8String])]; + + 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]]); + } + + NSObject* factory = _factories[viewType]; + if (factory == nil) { + result([FlutterError errorWithCode:@"unregistered_view_type" + message:@"trying to create a view with an unregistered type" + details:[NSString stringWithFormat:@"unregistered view type: '%@'", + args[@"viewType"]]]); + return; + } + + NSObject* platform_view = [factory createWithFrame:CGRectZero + viewIdentifier:viewId + arguments:nil]; + + _platformViews[viewId] = [platform_view view]; + result(nil); +} + +- (void)onDispose:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result { + NSNumber* arg = [call arguments]; + int64_t viewId = [arg longLongValue]; + NSLog(@"onDispose ViewId: %lld", viewId); + + 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 FlutterGLCompositor::Present call will dispose the views. + _platformViewsToDispose.insert(viewId); + result(nil); +} + +- (void)registerViewFactory:(nonnull NSObject*)factory + withId:(nonnull NSString*)factoryId { + _factories[factoryId] = factory; } - (void)dispatchMouseEvent:(nonnull NSEvent*)event { @@ -511,6 +575,10 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { result(nil); } else if ([call.method isEqualToString:@"Clipboard.hasStrings"]) { result(@{@"value" : @([self clipboardHasStrings])}); + } else if ([[call method] isEqualToString:@"create"]) { + [self onCreate:call result:result]; + } else if ([[call method] isEqualToString:@"dispose"]) { + [self onDispose:call result:result]; } else { result(FlutterMethodNotImplemented); } diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h index a2202fe9498a9..9e254ed72024b 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h @@ -2,8 +2,11 @@ // 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/Headers/FlutterViewController.h" +#include +#include +#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h" @interface FlutterViewController () @@ -11,11 +14,51 @@ // The FlutterView for this view controller. @property(nonatomic, readonly, nullable) FlutterView* flutterView; +// NSDictionary maps strings to FlutterPlatformViewFactorys. +@property(nonnull, nonatomic) + NSMutableDictionary*>* factories; + +// A map of platform view ids to views. +@property(nonatomic) std::map platformViews; + +// View ids that are going to be disposed on the next present call. +@property(nonatomic) std::unordered_set platformViewsToDispose; + /** * This just returns the NSPasteboard so that it can be mocked in the tests. */ @property(nonatomic, readonly, nonnull) NSPasteboard* pasteboard; +/** + * Platform View Methods. + */ + +/** + * Creates a platform view using the arguments from the provided call. + * The call's arguments should be castable to an NSMutableDictionary* + * and the dictionary should at least hold one key for "id" that maps to the view id and + * one key for "viewType" which maps to the view type (string) that was used to register + * the factory. + * FlutterResult is updated to contain nil for success or to contain + * a FlutterError if there is an error. + */ +- (void)onCreate:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result; + +/** + * Disposes a platform view using the arguments from the provided call. + * The call's arguments should be the Id (castable to NSNumber*) of the platform view + * that should be disposed. + * FlutterResult is updated to contain nil for success or a FlutterError if there is an error. + */ +- (void)onDispose:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result; + +/** + * Register a view factory by adding an entry into the factories_ map with key factoryId + * and value factory. + */ +- (void)registerViewFactory:(nonnull NSObject*)factory + withId:(nonnull NSString*)factoryId; + /** * Adds a responder for keyboard events. Key up and key down events are forwarded to all added * responders. From ac08defb6d01624bdf826081745ab6558c400a7b Mon Sep 17 00:00:00 2001 From: richardjcai Date: Mon, 7 Dec 2020 19:29:19 -0500 Subject: [PATCH 2/5] Create FlutterPlatformViewController --- ci/licenses_golden/licenses_flutter | 2 + shell/platform/darwin/macos/BUILD.gn | 2 + .../macos/framework/Source/FlutterEngine.mm | 128 +++++++++++------- .../framework/Source/FlutterGLCompositor.h | 8 +- .../framework/Source/FlutterGLCompositor.mm | 39 +++--- .../Source/FlutterGLCompositorUnittests.mm | 5 +- .../Source/FlutterPlatformViewController.mm | 92 +++++++++++++ .../FlutterPlatformViewController_Internal.h | 58 ++++++++ .../framework/Source/FlutterViewController.mm | 68 ---------- .../Source/FlutterViewController_Internal.h | 45 +----- 10 files changed, 260 insertions(+), 187 deletions(-) create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.mm create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index d5a6ab66a0bc8..a2768d1e751cc 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1072,6 +1072,8 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSur FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.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/FlutterPlatformViews.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.mm diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index bad5b34ff1a29..dc3c866afdc33 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -62,6 +62,8 @@ source_set("flutter_framework_source") { "framework/Source/FlutterIOSurfaceHolder.mm", "framework/Source/FlutterMouseCursorPlugin.h", "framework/Source/FlutterMouseCursorPlugin.mm", + "framework/Source/FlutterPlatformViewController.mm", + "framework/Source/FlutterPlatformViewController_Internal.h", "framework/Source/FlutterPlatformViews.h", "framework/Source/FlutterResizeSynchronizer.h", "framework/Source/FlutterResizeSynchronizer.mm", diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 4bf9e3f2b1845..cacb614050c9b 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -11,6 +11,7 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h" #import "flutter/shell/platform/embedder/embedder.h" /** @@ -204,6 +205,13 @@ @implementation FlutterEngine { // FlutterCompositor is copied and used in embedder.cc. FlutterCompositor _compositor; + + // A method channel for platform view functionality. + FlutterMethodChannel* _platformViewsChannel; + + // Used to support creation and deletion of platform views and + // registering platform view factories. + FlutterPlatformViewController* _platformViewController; } - (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)project { @@ -313,6 +321,9 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint { flutterArguments.aot_data = _aotData; } + [self setupPlatformViewChannel]; + [self createPlatformViewController]; + flutterArguments.compositor = [self createFlutterCompositor]; FlutterEngineResult result = _embedderAPI.Initialize( @@ -369,54 +380,6 @@ - (void)setViewController:(FlutterViewController*)controller { } } -- (FlutterCompositor*)createFlutterCompositor { - // TODO(richardjcai): Add support for creating a FlutterGLCompositor - // with a nil _viewController for headless engines. - // https://github.com/flutter/flutter/issues/71606 - if (_viewController == nullptr) { - return nullptr; - } - - [_mainOpenGLContext makeCurrentContext]; - - _macOSGLCompositor = std::make_unique(_viewController); - - _compositor = {}; - _compositor.struct_size = sizeof(FlutterCompositor); - _compositor.user_data = _macOSGLCompositor.get(); - - _compositor.create_backing_store_callback = [](const FlutterBackingStoreConfig* config, // - FlutterBackingStore* backing_store_out, // - void* user_data // - ) { - return reinterpret_cast(user_data)->CreateBackingStore( - config, backing_store_out); - }; - - _compositor.collect_backing_store_callback = [](const FlutterBackingStore* backing_store, // - void* user_data // - ) { - return reinterpret_cast(user_data)->CollectBackingStore( - backing_store); - }; - - _compositor.present_layers_callback = [](const FlutterLayer** layers, // - size_t layers_count, // - void* user_data // - ) { - return reinterpret_cast(user_data)->Present(layers, - layers_count); - }; - - __weak FlutterEngine* weak_self = self; - _macOSGLCompositor->SetPresentCallback( - [weak_self]() { return [weak_self engineCallbackOnPresent]; }); - - _compositor.avoid_backing_store_cache = true; - - return &_compositor; -} - - (id)binaryMessenger { // TODO(stuartmorgan): Switch to FlutterBinaryMessengerRelay to avoid plugins // keeping the engine alive. @@ -493,6 +456,10 @@ - (void)sendPointerEvent:(const FlutterPointerEvent&)event { _embedderAPI.SendPointerEvent(_engine, &event, 1); } +- (FlutterPlatformViewController*)platformViewController { + return _platformViewController; +} + #pragma mark - Private methods - (void)sendUserLocales { @@ -600,6 +567,71 @@ - (void)shutDownEngine { _engine = nullptr; } +- (FlutterCompositor*)createFlutterCompositor { + // TODO(richardjcai): Add support for creating a FlutterGLCompositor + // with a nil _viewController for headless engines. + // https://github.com/flutter/flutter/issues/71606 + if (_viewController == nullptr) { + return nullptr; + } + + [_mainOpenGLContext makeCurrentContext]; + + _macOSGLCompositor = + std::make_unique(_viewController, _platformViewController); + + _compositor = {}; + _compositor.struct_size = sizeof(FlutterCompositor); + _compositor.user_data = _macOSGLCompositor.get(); + + _compositor.create_backing_store_callback = [](const FlutterBackingStoreConfig* config, // + FlutterBackingStore* backing_store_out, // + void* user_data // + ) { + return reinterpret_cast(user_data)->CreateBackingStore( + config, backing_store_out); + }; + + _compositor.collect_backing_store_callback = [](const FlutterBackingStore* backing_store, // + void* user_data // + ) { + return reinterpret_cast(user_data)->CollectBackingStore( + backing_store); + }; + + _compositor.present_layers_callback = [](const FlutterLayer** layers, // + size_t layers_count, // + void* user_data // + ) { + return reinterpret_cast(user_data)->Present(layers, + layers_count); + }; + + __weak FlutterEngine* weak_self = self; + _macOSGLCompositor->SetPresentCallback( + [weak_self]() { return [weak_self engineCallbackOnPresent]; }); + + _compositor.avoid_backing_store_cache = true; + + return &_compositor; +} + +- (void)createPlatformViewController { + _platformViewController = [[FlutterPlatformViewController alloc] init]; +} + +- (void)setupPlatformViewChannel { + _platformViewsChannel = + [FlutterMethodChannel methodChannelWithName:@"flutter/platform_views" + binaryMessenger:self.binaryMessenger + codec:[FlutterStandardMethodCodec sharedInstance]]; + + __weak FlutterEngine* weak_self = self; + [_platformViewsChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { + [[weak_self platformViewController] handleMethodCall:call result:result]; + }]; +} + #pragma mark - FlutterBinaryMessenger - (void)sendOnChannel:(nonnull NSString*)channel message:(nullable NSData*)message { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h b/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h index e9f39f0df9689..ad0be0d7c7986 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h @@ -6,6 +6,7 @@ #include "flutter/fml/macros.h" #include "flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStoreData.h" +#include "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h" #include "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h" #include "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" #include "flutter/shell/platform/embedder/embedder.h" @@ -19,7 +20,8 @@ namespace flutter { // FlutterGLCompositor is created and destroyed by FlutterEngine. class FlutterGLCompositor { public: - FlutterGLCompositor(FlutterViewController* view_controller); + FlutterGLCompositor(FlutterViewController* view_controller, + FlutterPlatformViewController* platform_view_controller); // Creates a BackingStore and saves updates the backing_store_out // data with the new BackingStore data. @@ -50,6 +52,7 @@ class FlutterGLCompositor { private: const FlutterViewController* view_controller_; + const FlutterPlatformViewController* platform_view_controller_; const NSOpenGLContext* open_gl_context_; PresentCallback present_callback_; @@ -75,9 +78,6 @@ class FlutterGLCompositor { // Set frame_started_ to true and reset all layer state. void StartFrame(); - // Remove platform views that are specified for deletion. - void DisposePlatformViews(); - // Creates a CALayer and adds it to ca_layer_map_ and increments // ca_layer_count_; Returns the key value (size_t) for the layer in // ca_layer_map_. diff --git a/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.mm b/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.mm index 46af625379f14..eba04b8bd689e 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.mm @@ -18,10 +18,15 @@ namespace flutter { -FlutterGLCompositor::FlutterGLCompositor(FlutterViewController* view_controller) +FlutterGLCompositor::FlutterGLCompositor(FlutterViewController* view_controller, + FlutterPlatformViewController* platform_view_controller) : open_gl_context_(view_controller.flutterView.openGLContext) { FML_CHECK(view_controller != nullptr) << "FlutterViewController* cannot be nullptr"; + FML_CHECK(platform_view_controller != nullptr) + << "FlutterPlatformViewController* cannot be nullptr"; + view_controller_ = view_controller; + platform_view_controller_ = platform_view_controller; } bool FlutterGLCompositor::CreateBackingStore(const FlutterBackingStoreConfig* config, @@ -71,7 +76,7 @@ } bool FlutterGLCompositor::Present(const FlutterLayer** layers, size_t layers_count) { - DisposePlatformViews(); + [platform_view_controller_ disposePlatformViews]; for (size_t i = 0; i < layers_count; ++i) { const auto* layer = layers[i]; FlutterBackingStore* backing_store = const_cast(layer->backing_store); @@ -85,9 +90,15 @@ break; } case kFlutterLayerContentTypePlatformView: - FML_CHECK([[NSThread currentThread] isMainThread]) + FML_DCHECK([[NSThread currentThread] isMainThread]) << "Must be on the main thread to handle presenting platform views"; - NSView* platform_view = view_controller_.platformViews[layer->platform_view->identifier]; + + FML_DCHECK(platform_view_controller_.platformViews.count(layer->platform_view->identifier)) + << "Platform view not found for id: " << layer->platform_view->identifier; + + NSView* platform_view = + platform_view_controller_.platformViews[layer->platform_view->identifier]; + 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); @@ -108,7 +119,7 @@ void FlutterGLCompositor::PresentBackingStoreContent( FlutterBackingStoreData* flutter_backing_store_data, size_t layer_position) { - FML_CHECK([[NSThread currentThread] isMainThread]) + FML_DCHECK([[NSThread currentThread] isMainThread]) << "Must be on the main thread to update CALayer contents"; FlutterIOSurfaceHolder* io_surface_holder = [flutter_backing_store_data ioSurfaceHolder]; @@ -116,7 +127,7 @@ CALayer* content_layer = ca_layer_map_[layer_id]; - FML_CHECK(content_layer) << "Unable to find a content layer with layer id " << layer_id; + FML_DCHECK(content_layer) << "Unable to find a content layer with layer id " << layer_id; content_layer.frame = content_layer.superlayer.bounds; content_layer.zPosition = layer_position; @@ -158,20 +169,4 @@ return ca_layer_count_++; } -void FlutterGLCompositor::DisposePlatformViews() { - auto views_to_dispose = view_controller_.platformViewsToDispose; - if (views_to_dispose.empty()) { - return; - } - - for (int64_t viewId : views_to_dispose) { - FML_CHECK([[NSThread currentThread] isMainThread]) - << "Must be on the main thread to handle disposing platform views"; - NSView* view = view_controller_.platformViews[viewId]; - [view removeFromSuperview]; - view_controller_.platformViews.erase(viewId); - } - views_to_dispose.clear(); -} - } // namespace flutter diff --git a/shell/platform/darwin/macos/framework/Source/FlutterGLCompositorUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterGLCompositorUnittests.mm index 7db8d870e05a9..b783307c7e45b 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterGLCompositorUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterGLCompositorUnittests.mm @@ -5,6 +5,7 @@ #import #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.h" #import "flutter/testing/testing.h" @@ -12,9 +13,11 @@ TEST(FlutterGLCompositorTest, TestPresent) { id mockViewController = CreateMockViewController(nil); + FlutterPlatformViewController* platformViewController = + [[FlutterPlatformViewController alloc] init]; std::unique_ptr macos_compositor = - std::make_unique(mockViewController); + std::make_unique(mockViewController, platformViewController); bool flag = false; macos_compositor->SetPresentCallback([f = &flag]() { 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..b25ce75cf81a6 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.mm @@ -0,0 +1,92 @@ +// 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_Internal.h" + +@implementation FlutterPlatformViewController + +- (instancetype)init { + self = [super init]; + + 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]]); + } + + NSObject* factory = _platformViewFactories[viewType]; + if (factory == nil) { + result([FlutterError + errorWithCode:@"unregistered_view_type" + message:@"trying to create a view with an unregistered type" + details:[NSString stringWithFormat:@"unregistered view type: '%@'", viewType]]); + return; + } + + NSObject* platform_view = [factory createWithFrame:CGRectZero + viewIdentifier:viewId + arguments:nil]; + + _platformViews[viewId] = [platform_view 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; +} + +- (void)handleMethodCall:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result { + if ([[call method] isEqualToString:@"create"]) { + NSMutableDictionary* args = [call arguments]; + int64_t viewId = [args[@"id"] longValue]; + NSString* viewType = [NSString stringWithUTF8String:([args[@"viewType"] UTF8String])]; + [self onCreateWithViewId:viewId viewType:viewType result:result]; + } else if ([[call method] isEqualToString:@"dispose"]) { + NSNumber* arg = [call arguments]; + int64_t viewId = [arg longLongValue]; + [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/FlutterPlatformViewController_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h new file mode 100644 index 0000000000000..7e82e0fa3fa4f --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h @@ -0,0 +1,58 @@ +// 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" + +#include +#include + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h" + +@interface FlutterPlatformViewController : NSViewController +@end + +@interface FlutterPlatformViewController () + +// NSDictionary maps strings to FlutterPlatformViewFactorys. +@property(nonnull, nonatomic) + NSMutableDictionary*>* platformViewFactories; + +// A map of platform view ids to views. +@property(nonatomic) std::map platformViews; + +// View ids that are going to be disposed on the next present call. +@property(nonatomic) std::unordered_set platformViewsToDispose; + +/** + * 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 id 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; + +/** + * 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; + +- (void)handleMethodCall:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result; + +/** + * Remove platform views who's ids are in the platformViewsToDispose set. + * Called before a new frame is presented. + */ +- (void)disposePlatformViews; + +@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm index 1885598d78059..cc5eaba49bf39 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm @@ -198,9 +198,6 @@ @implementation FlutterViewController { // A method channel for miscellaneous platform functionality. FlutterMethodChannel* _platformChannel; - - // A method channel for platform view functionality. - FlutterMethodChannel* _platformViewsChannel; } @dynamic view; @@ -214,7 +211,6 @@ static void CommonInit(FlutterViewController* controller) { allowHeadlessExecution:NO]; controller->_additionalKeyResponders = [[NSMutableOrderedSet alloc] init]; controller->_mouseTrackingMode = FlutterMouseTrackingModeInKeyWindow; - controller->_factories = [[NSMutableDictionary alloc] init]; } - (instancetype)initWithCoder:(NSCoder*)coder { @@ -381,70 +377,10 @@ - (void)addInternalPlugins { [FlutterMethodChannel methodChannelWithName:@"flutter/platform" binaryMessenger:_engine.binaryMessenger codec:[FlutterJSONMethodCodec sharedInstance]]; - - _platformViewsChannel = - [FlutterMethodChannel methodChannelWithName:@"flutter/platform_views" - binaryMessenger:_engine.binaryMessenger - codec:[FlutterStandardMethodCodec sharedInstance]]; - __weak FlutterViewController* weakSelf = self; [_platformChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { [weakSelf handleMethodCall:call result:result]; }]; - - [_platformViewsChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { - [weakSelf handleMethodCall:call result:result]; - }]; -} - -- (void)onCreate:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result { - NSMutableDictionary* args = [call arguments]; - int64_t viewId = [args[@"id"] longValue]; - NSString* viewType = [NSString stringWithUTF8String:([args[@"viewType"] UTF8String])]; - - 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]]); - } - - NSObject* factory = _factories[viewType]; - if (factory == nil) { - result([FlutterError errorWithCode:@"unregistered_view_type" - message:@"trying to create a view with an unregistered type" - details:[NSString stringWithFormat:@"unregistered view type: '%@'", - args[@"viewType"]]]); - return; - } - - NSObject* platform_view = [factory createWithFrame:CGRectZero - viewIdentifier:viewId - arguments:nil]; - - _platformViews[viewId] = [platform_view view]; - result(nil); -} - -- (void)onDispose:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result { - NSNumber* arg = [call arguments]; - int64_t viewId = [arg longLongValue]; - NSLog(@"onDispose ViewId: %lld", viewId); - - 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 FlutterGLCompositor::Present call will dispose the views. - _platformViewsToDispose.insert(viewId); - result(nil); -} - -- (void)registerViewFactory:(nonnull NSObject*)factory - withId:(nonnull NSString*)factoryId { - _factories[factoryId] = factory; } - (void)dispatchMouseEvent:(nonnull NSEvent*)event { @@ -575,10 +511,6 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { result(nil); } else if ([call.method isEqualToString:@"Clipboard.hasStrings"]) { result(@{@"value" : @([self clipboardHasStrings])}); - } else if ([[call method] isEqualToString:@"create"]) { - [self onCreate:call result:result]; - } else if ([[call method] isEqualToString:@"dispose"]) { - [self onDispose:call result:result]; } else { result(FlutterMethodNotImplemented); } diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h index 9e254ed72024b..a2202fe9498a9 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h @@ -2,11 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include -#include - #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h" -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h" + #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h" @interface FlutterViewController () @@ -14,51 +11,11 @@ // The FlutterView for this view controller. @property(nonatomic, readonly, nullable) FlutterView* flutterView; -// NSDictionary maps strings to FlutterPlatformViewFactorys. -@property(nonnull, nonatomic) - NSMutableDictionary*>* factories; - -// A map of platform view ids to views. -@property(nonatomic) std::map platformViews; - -// View ids that are going to be disposed on the next present call. -@property(nonatomic) std::unordered_set platformViewsToDispose; - /** * This just returns the NSPasteboard so that it can be mocked in the tests. */ @property(nonatomic, readonly, nonnull) NSPasteboard* pasteboard; -/** - * Platform View Methods. - */ - -/** - * Creates a platform view using the arguments from the provided call. - * The call's arguments should be castable to an NSMutableDictionary* - * and the dictionary should at least hold one key for "id" that maps to the view id and - * one key for "viewType" which maps to the view type (string) that was used to register - * the factory. - * FlutterResult is updated to contain nil for success or to contain - * a FlutterError if there is an error. - */ -- (void)onCreate:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result; - -/** - * Disposes a platform view using the arguments from the provided call. - * The call's arguments should be the Id (castable to NSNumber*) of the platform view - * that should be disposed. - * FlutterResult is updated to contain nil for success or a FlutterError if there is an error. - */ -- (void)onDispose:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result; - -/** - * Register a view factory by adding an entry into the factories_ map with key factoryId - * and value factory. - */ -- (void)registerViewFactory:(nonnull NSObject*)factory - withId:(nonnull NSString*)factoryId; - /** * Adds a responder for keyboard events. Key up and key down events are forwarded to all added * responders. From 8fbb2882a3653e7794f1bfe57579ac052fdd678a Mon Sep 17 00:00:00 2001 From: richardjcai Date: Mon, 7 Dec 2020 20:28:00 -0500 Subject: [PATCH 3/5] Adding tests for FlutterPlatformViewController --- ci/licenses_golden/licenses_flutter | 3 + shell/platform/darwin/macos/BUILD.gn | 3 + .../FlutterPlatformViewControllerTest.mm | 128 ++++++++++++++++++ .../Source/FlutterPlatformViewMock.h | 18 +++ .../Source/FlutterPlatformViewMock.mm | 42 ++++++ 5 files changed, 194 insertions(+) create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterPlatformViewControllerTest.mm create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.h create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.mm diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index a2768d1e751cc..26b623695ce34 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1074,6 +1074,9 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouse FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.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/FlutterPlatformViewMock.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.mm diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index dc3c866afdc33..2619c2cd16969 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -130,6 +130,9 @@ executable("flutter_desktop_darwin_unittests") { sources = [ "framework/Source/FlutterEngineTest.mm", "framework/Source/FlutterGLCompositorUnittests.mm", + "framework/Source/FlutterPlatformViewControllerTest.mm", + "framework/Source/FlutterPlatformViewMock.h", + "framework/Source/FlutterPlatformViewMock.mm", "framework/Source/FlutterViewControllerTest.mm", "framework/Source/FlutterViewControllerTestUtils.h", "framework/Source/FlutterViewControllerTestUtils.mm", 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..e20d9d4091522 --- /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_Internal.h" + +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.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]; + + FlutterPlatformViewMockFactory* factory = [FlutterPlatformViewMockFactory 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]; + + FlutterPlatformViewMockFactory* factory = [FlutterPlatformViewMockFactory 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); +} + +} // flutter::testing diff --git a/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.h b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.h new file mode 100644 index 0000000000000..b1b7e398c5f7e --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.h @@ -0,0 +1,18 @@ +// 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/FlutterPlatformViews.h" + +@interface FlutterPlatformViewMock : NSObject +@property(nonatomic, strong) NSView* view; +@end + +@interface MockPlatformView : NSView +@end + +@interface FlutterPlatformViewMockFactory : NSObject +@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.mm b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.mm new file mode 100644 index 0000000000000..9c2b891e794cb --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.mm @@ -0,0 +1,42 @@ +// 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/FlutterPlatformViewMock.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h" + +@implementation MockPlatformView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + return self; +} + +@end + +@implementation FlutterPlatformViewMock + +- (instancetype)initWithFrame:(CGRect)frame arguments:(id _Nullable)args { + if (self = [super init]) { + _view = [[MockPlatformView alloc] initWithFrame:frame]; + } + return self; +} + +@end + +@implementation FlutterPlatformViewMockFactory +- (NSObject*)createWithFrame:(CGRect)frame + viewIdentifier:(int64_t)viewId + arguments:(id _Nullable)args { + return [[FlutterPlatformViewMock alloc] initWithFrame:frame arguments:args]; +} + +- (NSObject*)createArgsCodec { + return [FlutterStandardMessageCodec sharedInstance]; +} + +@end From a4b8540eb07c2e02db078a54996587424bedec21 Mon Sep 17 00:00:00 2001 From: richardjcai Date: Tue, 8 Dec 2020 11:07:52 -0500 Subject: [PATCH 4/5] Update license for FlutterPlatformViewContoller_Internal.h --- ci/licenses_golden/licenses_flutter | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 26b623695ce34..c099ee6df274e 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1072,9 +1072,9 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSur FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.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/FlutterPlatformViewController_Internal.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h From b33da8376b92de9e107ca9f8d946945afe2ea9e1 Mon Sep 17 00:00:00 2001 From: richardjcai Date: Mon, 7 Dec 2020 17:14:06 -0500 Subject: [PATCH 5/5] Add experimental plist setting to enable experimental platform view support for MacOS. Enabling the key disables smooth resizing. --- ci/licenses_golden/licenses_flutter | 1 + .../macos/framework/Source/FlutterConstants.h | 6 ++++ .../macos/framework/Source/FlutterEngine.mm | 9 +++++- .../Source/FlutterPlatformViewController.mm | 32 +++++++++++++++++-- .../FlutterPlatformViewControllerTest.mm | 27 ++++++++++++++++ .../FlutterPlatformViewController_Internal.h | 11 +++++++ .../macos/framework/Source/FlutterView.mm | 30 ++++++++++++++--- 7 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterConstants.h diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index c099ee6df274e..415f4a14717af 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1056,6 +1056,7 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Info.plist FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStoreData.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStoreData.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterConstants.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm diff --git a/shell/platform/darwin/macos/framework/Source/FlutterConstants.h b/shell/platform/darwin/macos/framework/Source/FlutterConstants.h new file mode 100644 index 0000000000000..0c21486dbe1e8 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterConstants.h @@ -0,0 +1,6 @@ +// 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. + +// The name of the Info.plist flag to enable the embedded MacOS views preview. +const char* const kEmbeddedViewsPreview = "io.flutter_embedded_views_preview"; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index cacb614050c9b..d1d5cb44ce634 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -310,10 +310,17 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint { }, .identifier = ++sTaskRunnerIdentifiers, }; + + bool embedded_views_preview_enabled = [FlutterPlatformViewController embeddedViewsEnabled]; + const FlutterCustomTaskRunners custom_task_runners = { .struct_size = sizeof(FlutterCustomTaskRunners), .platform_task_runner = &cocoa_task_runner_description, - }; + // If platform views are enabled, set the render thread to the platform thread. + // Otherwise the render thread is created separately in embedder_thread_host.cc. + .render_task_runner = + embedded_views_preview_enabled ? &cocoa_task_runner_description : nullptr}; + flutterArguments.custom_task_runners = &custom_task_runners; [self loadAOTData:_project.assetsPath]; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.mm b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.mm index b25ce75cf81a6..1e7341d3a4a21 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.mm @@ -4,20 +4,29 @@ #include "flutter/fml/logging.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterConstants.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h" -@implementation FlutterPlatformViewController +@implementation FlutterPlatformViewController { + bool _embeddedViewsEnabled; +} - (instancetype)init { self = [super init]; - self->_platformViewFactories = [[NSMutableDictionary alloc] init]; + _platformViewFactories = [[NSMutableDictionary alloc] init]; + [self setEmbeddedViewsEnabled:[FlutterPlatformViewController embeddedViewsEnabled]]; return self; } - (void)onCreateWithViewId:(int64_t)viewId viewType:(nonnull NSString*)viewType result:(nonnull FlutterResult)result { + if (!_embeddedViewsEnabled) { + NSLog(@"Must set `io.flutter_embedded_views_preview` to true in Info.plist to enable platform " + @"views"); + return; + } if (_platformViews.count(viewId) != 0) { result([FlutterError errorWithCode:@"recreating_view" message:@"trying to create an already created view" @@ -42,6 +51,12 @@ - (void)onCreateWithViewId:(int64_t)viewId } - (void)onDisposeWithViewId:(int64_t)viewId result:(nonnull FlutterResult)result { + if (!_embeddedViewsEnabled) { + NSLog(@"Must set `io.flutter_embedded_views_preview` to true in Info.plist to enable platform " + @"views"); + return; + } + if (_platformViews.count(viewId) == 0) { result([FlutterError errorWithCode:@"unknown_view" message:@"trying to dispose an unknown" @@ -56,6 +71,11 @@ - (void)onDisposeWithViewId:(int64_t)viewId result:(nonnull FlutterResult)result - (void)registerViewFactory:(nonnull NSObject*)factory withId:(nonnull NSString*)factoryId { + if (!_embeddedViewsEnabled) { + NSLog(@"Must set `io.flutter_embedded_views_preview` to true in Info.plist to enable platform " + @"views"); + return; + } _platformViewFactories[factoryId] = factory; } @@ -89,4 +109,12 @@ - (void)disposePlatformViews { _platformViewsToDispose.clear(); } +- (void)setEmbeddedViewsEnabled:(bool)embeddedViewsEnabled { + _embeddedViewsEnabled = embeddedViewsEnabled; +} + ++ (bool)embeddedViewsEnabled { + return [[[NSBundle mainBundle] objectForInfoDictionaryKey:@(kEmbeddedViewsPreview)] boolValue]; +} + @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewControllerTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewControllerTest.mm index e20d9d4091522..cac040d9e2c61 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewControllerTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewControllerTest.mm @@ -11,9 +11,33 @@ namespace flutter::testing { +TEST(FlutterPlatformViewController, EmbeddedViewsDisabled) { + // Use id so we can access handleMethodCall method. + id platformViewController = [[FlutterPlatformViewController alloc] init]; + [platformViewController setEmbeddedViewsEnabled:false]; + + FlutterMethodCall* methodCall = + [FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"FlutterPlatformViewMock" + }]; + + __block bool called = false; + FlutterResult result = ^(id result) { + called = true; + }; + + [platformViewController handleMethodCall:methodCall result:result]; + + // The call should not have happened if embeddedViewsEnabled is false. + EXPECT_FALSE(called); +} + TEST(FlutterPlatformViewController, TestCreatePlatformViewNoMatchingViewType) { // Use id so we can access handleMethodCall method. id platformViewController = [[FlutterPlatformViewController alloc] init]; + [platformViewController setEmbeddedViewsEnabled:true]; FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:@"create" @@ -38,6 +62,7 @@ TEST(FlutterPlatformViewController, TestRegisterPlatformViewFactoryAndCreate) { // Use id so we can access handleMethodCall method. id platformViewController = [[FlutterPlatformViewController alloc] init]; + [platformViewController setEmbeddedViewsEnabled:true]; FlutterPlatformViewMockFactory* factory = [FlutterPlatformViewMockFactory alloc]; @@ -65,6 +90,7 @@ TEST(FlutterPlatformViewController, TestCreateAndDispose) { // Use id so we can access handleMethodCall method. id platformViewController = [[FlutterPlatformViewController alloc] init]; + [platformViewController setEmbeddedViewsEnabled:true]; FlutterPlatformViewMockFactory* factory = [FlutterPlatformViewMockFactory alloc]; @@ -108,6 +134,7 @@ TEST(FlutterPlatformViewController, TestDisposeOnMissingViewId) { // Use id so we can access handleMethodCall method. id platformViewController = [[FlutterPlatformViewController alloc] init]; + [platformViewController setEmbeddedViewsEnabled:true]; FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:@"dispose" diff --git a/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h index 7e82e0fa3fa4f..781a7df892e98 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h @@ -55,4 +55,15 @@ */ - (void)disposePlatformViews; +/** + * Set _embedded_views_preview_enabled value. + */ +- (void)setEmbeddedViewsEnabled:(bool)embeddedViewsEnabled; + +/** + * Return whether or not platform views are enabled by looking for the + * io.flutter_embedded_views_preview flag in Info.plist. + */ ++ (bool)embeddedViewsEnabled; + @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterView.mm b/shell/platform/darwin/macos/framework/Source/FlutterView.mm index d6ce889f54e81..9cbc87aa1bb81 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterView.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterView.mm @@ -4,6 +4,7 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h" #import "flutter/shell/platform/darwin/macos/framework/Source/MacOSGLContextSwitch.h" @@ -15,6 +16,7 @@ @interface FlutterView () { __weak id _reshapeListener; FlutterResizeSynchronizer* _resizeSynchronizer; FlutterSurfaceManager* _surfaceManager; + bool _embedded_views_preview_enabled; } @end @@ -42,6 +44,9 @@ - (instancetype)initWithFrame:(NSRect)frame _reshapeListener = reshapeListener; } + + _embedded_views_preview_enabled = [FlutterPlatformViewController embeddedViewsEnabled]; + return self; } @@ -67,15 +72,30 @@ - (int)frameBufferIDForSize:(CGSize)size { } - (void)present { - [_resizeSynchronizer requestCommit]; + // If _embedded_views_preview_enabled is true, the main and raster + // threads are merged. Thus we cannot call resizeSynchronizer::requestCommit + // as it blocks on the raster thread. + if (_embedded_views_preview_enabled) { + [self resizeSynchronizerFlush:_resizeSynchronizer]; + [self resizeSynchronizerCommit:_resizeSynchronizer]; + } else { + [_resizeSynchronizer requestCommit]; + } } - (void)reshaped { CGSize scaledSize = [self convertSizeToBacking:self.bounds.size]; - [_resizeSynchronizer beginResize:scaledSize - notify:^{ - [_reshapeListener viewDidReshape:self]; - }]; + // If _embedded_views_preview_enabled is true, the main and raster + // threads are merged. Thus we cannot call resizeSynchronizer::beginResize + // as it blocks on the main thread. + if (_embedded_views_preview_enabled) { + [_reshapeListener viewDidReshape:self]; + } else { + [_resizeSynchronizer beginResize:scaledSize + notify:^{ + [_reshapeListener viewDidReshape:self]; + }]; + } } #pragma mark - NSView overrides