diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 28b7135dfc04d..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 @@ -1072,6 +1073,12 @@ 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.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 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..2619c2cd16969 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -62,6 +62,9 @@ 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", "framework/Source/FlutterSurfaceManager.h", @@ -127,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/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 4bf9e3f2b1845..d1d5cb44ce634 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 { @@ -302,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]; @@ -313,6 +328,9 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint { flutterArguments.aot_data = _aotData; } + [self setupPlatformViewChannel]; + [self createPlatformViewController]; + flutterArguments.compositor = [self createFlutterCompositor]; FlutterEngineResult result = _embedderAPI.Initialize( @@ -369,54 +387,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 +463,10 @@ - (void)sendPointerEvent:(const FlutterPointerEvent&)event { _embedderAPI.SendPointerEvent(_engine, &event, 1); } +- (FlutterPlatformViewController*)platformViewController { + return _platformViewController; +} + #pragma mark - Private methods - (void)sendUserLocales { @@ -600,6 +574,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 e33dd6bb1c7db..ad0be0d7c7986 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h @@ -5,6 +5,8 @@ #include #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" @@ -18,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. @@ -49,6 +52,7 @@ class FlutterGLCompositor { private: const FlutterViewController* view_controller_; + const FlutterPlatformViewController* platform_view_controller_; const NSOpenGLContext* open_gl_context_; PresentCallback present_callback_; @@ -66,6 +70,11 @@ 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(); diff --git a/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.mm b/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.mm index 4f187232ec143..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,35 +76,37 @@ } bool FlutterGLCompositor::Present(const FlutterLayer** layers, size_t layers_count) { + [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); 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_DCHECK([[NSThread currentThread] isMainThread]) + << "Must be on the main thread to handle presenting platform views"; + + 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); + if (platform_view.superview == nil) { + [view_controller_.flutterView addSubview:platform_view]; + } else { + platform_view.layer.zPosition = i; + } break; }; } @@ -109,6 +116,29 @@ return present_callback_(); } +void FlutterGLCompositor::PresentBackingStoreContent( + FlutterBackingStoreData* flutter_backing_store_data, + size_t layer_position) { + FML_DCHECK([[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_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; + + // 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; 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..1e7341d3a4a21 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.mm @@ -0,0 +1,120 @@ +// 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/FlutterConstants.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h" + +@implementation FlutterPlatformViewController { + bool _embeddedViewsEnabled; +} + +- (instancetype)init { + self = [super 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" + 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 (!_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" + 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 { + if (!_embeddedViewsEnabled) { + NSLog(@"Must set `io.flutter_embedded_views_preview` to true in Info.plist to enable platform " + @"views"); + return; + } + _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(); +} + +- (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 new file mode 100644 index 0000000000000..cac040d9e2c61 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewControllerTest.mm @@ -0,0 +1,155 @@ +// 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, 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" + 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]; + [platformViewController setEmbeddedViewsEnabled:true]; + + 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]; + [platformViewController setEmbeddedViewsEnabled:true]; + + 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]; + [platformViewController setEmbeddedViewsEnabled:true]; + + 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/FlutterPlatformViewController_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h new file mode 100644 index 0000000000000..781a7df892e98 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h @@ -0,0 +1,69 @@ +// 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; + +/** + * 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/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 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/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