diff --git a/engine/src/flutter/BUILD.gn b/engine/src/flutter/BUILD.gn index 5ea2631ea53..1cffa58132c 100644 --- a/engine/src/flutter/BUILD.gn +++ b/engine/src/flutter/BUILD.gn @@ -58,8 +58,10 @@ group("flutter") { # If on the host, compile all unittests targets. if (current_toolchain == host_toolchain) { if (is_mac) { - public_deps += - [ "$flutter_root/shell/platform/darwin:flutter_channels_unittests" ] + public_deps += [ + "$flutter_root/shell/platform/darwin:flutter_channels_unittests", + "$flutter_root/shell/platform/darwin/macos:flutter_desktop_darwin_unittests", + ] } public_deps += [ diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index 5e651ebcdad..b12f2b1322a 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -824,6 +824,7 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDe 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 +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineUnittests.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.mm @@ -835,6 +836,7 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterView. FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterView.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart FILE: ../../../flutter/shell/platform/darwin/macos/framework/module.modulemap FILE: ../../../flutter/shell/platform/embedder/assets/EmbedderInfo.plist FILE: ../../../flutter/shell/platform/embedder/assets/embedder.modulemap diff --git a/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn b/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn index 4cb1fa32b55..c1afe227873 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn +++ b/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn @@ -8,6 +8,7 @@ import("//build/config/mac/mac_sdk.gni") import("$flutter_root/common/config.gni") import("$flutter_root/shell/platform/darwin/common/framework_shared.gni") import("$flutter_root/shell/platform/glfw/config.gni") +import("$flutter_root/testing/testing.gni") group("macos") { deps = [ @@ -41,11 +42,9 @@ _flutter_framework_headers = [ _flutter_framework_headers_copy_dir = "$_flutter_framework_dir/Versions/A/Headers" -shared_library("create_flutter_framework_dylib") { +source_set("flutter_framework_source") { visibility = [ ":*" ] - output_name = "$_flutter_framework_name" - sources = [ "framework/Source/FlutterAppDelegate.mm", "framework/Source/FlutterDartProject.mm", @@ -83,6 +82,43 @@ shared_library("create_flutter_framework_dylib") { ] } +shared_library("flutter_framework_dylib") { + visibility = [ ":*" ] + + output_name = "$_flutter_framework_name" + + deps = [ + ":flutter_framework_source", + ] +} + +test_fixtures("flutter_desktop_darwin_fixtures") { + dart_main = "framework/Source/fixtures/flutter_desktop_test.dart" + + fixtures = [ "//third_party/icu/common/icudtl.dat" ] +} + +executable("flutter_desktop_darwin_unittests") { + testonly = true + + sources = [ + "framework/Source/FlutterEngineUnittests.mm", + ] + + cflags_objcc = [ "-fobjc-arc" ] + + deps = [ + ":flutter_desktop_darwin_fixtures", + ":flutter_framework_source", + "$flutter_root/shell/platform/darwin/common:framework_shared", + "$flutter_root/shell/platform/embedder:embedder_with_symbol_prefix", + "$flutter_root/testing", + "$flutter_root/testing:dart", + "$flutter_root/testing:skia", + "$flutter_root/testing:testing_lib", + ] +} + action("copy_dylib_and_update_framework_install_name") { visibility = [ ":*" ] script = "$flutter_root/sky/tools/change_install_name.py" @@ -108,7 +144,7 @@ action("copy_dylib_and_update_framework_install_name") { ] deps = [ - ":create_flutter_framework_dylib", + ":flutter_framework_dylib", ] } diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h b/engine/src/flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h index 4f4583878ff..23b2eb556ef 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h @@ -72,6 +72,13 @@ FLUTTER_EXPORT */ @property(nonatomic, nonnull, readonly) id binaryMessenger; +/** + * Shuts the Flutter engine if it is running. The FlutterEngine instance must always be shutdown + * before it may be collected. Not shutting down the FlutterEngine instance before releasing it will + * result in the leak of that engine instance. + */ +- (void)shutDownEngine; + @end #endif // FLUTTER_FLUTTERENGINE_H_ diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject.mm index a13c9394307..5fc590a4bcb 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject.mm @@ -12,6 +12,8 @@ static NSString* const kAppBundleIdentifier = @"io.flutter.flutter.app"; @implementation FlutterDartProject { NSBundle* _dartBundle; + NSString* _assetsPath; + NSString* _ICUDataPath; } - (instancetype)init { @@ -26,7 +28,19 @@ static NSString* const kAppBundleIdentifier = @"io.flutter.flutter.app"; return self; } +- (instancetype)initWithAssetsPath:(NSString*)assets ICUDataPath:(NSString*)icuPath { + self = [super init]; + NSAssert(self, @"Super init cannot be nil"); + _assetsPath = assets; + _ICUDataPath = icuPath; + return self; +} + - (NSString*)assetsPath { + if (_assetsPath) { + return _assetsPath; + } + // If there's no App.framework, fall back to checking the main bundle for assets. NSBundle* assetBundle = _dartBundle ?: [NSBundle mainBundle]; NSString* flutterAssetsName = [assetBundle objectForInfoDictionaryKey:@"FLTAssetsPath"]; @@ -41,6 +55,10 @@ static NSString* const kAppBundleIdentifier = @"io.flutter.flutter.app"; } - (NSString*)ICUDataPath { + if (_ICUDataPath) { + return _ICUDataPath; + } + NSString* path = [[NSBundle bundleForClass:[self class]] pathForResource:kICUBundlePath ofType:nil]; if (!path) { diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h index ecc6388af31..e082a2731b3 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h @@ -33,6 +33,13 @@ */ @property(nonatomic, readonly) std::vector argv; +/** + * Instead of looking up the assets and ICU data path in the application bundle, this initializer + * allows callers to create a Dart project with custom locations specified for the both. + */ +- (nonnull instancetype)initWithAssetsPath:(nonnull NSString*)assets + ICUDataPath:(nonnull NSString*)icuPath NS_DESIGNATED_INITIALIZER; + @end #endif // SHELL_PLATFORM_DARWIN_MACOS_FRAMEWORK_SOURCE_FLUTTERDARTPROJECT_INTERNAL_H_ diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index a4bdd2e46a9..e00db7014b6 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -42,17 +42,18 @@ */ - (void)engineCallbackOnPlatformMessage:(const FlutterPlatformMessage*)message; -/** - * Shuts the Flutter engine if it is running. - */ -- (void)shutDownEngine; - /** * Forwards texture copy request to the corresponding texture via |textureID|. */ - (BOOL)populateTextureWithIdentifier:(int64_t)textureID openGLTexture:(FlutterOpenGLTexture*)openGLTexture; +/** + * Requests that the task be posted back the to the Flutter engine at the target time. The target + * time is in the clock used by the Flutter engine. + */ +- (void)postMainThreadTask:(FlutterTask)task targetTimeInNanoseconds:(uint64_t)targetTime; + @end #pragma mark - @@ -218,6 +219,26 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine, flutterArguments.command_line_argv = &arguments[0]; flutterArguments.platform_message_callback = (FlutterPlatformMessageCallback)OnPlatformMessage; flutterArguments.custom_dart_entrypoint = entrypoint.UTF8String; + static size_t sTaskRunnerIdentifiers = 0; + const FlutterTaskRunnerDescription cocoa_task_runner_description = { + .struct_size = sizeof(FlutterTaskRunnerDescription), + .user_data = (void*)CFBridgingRetain(self), + .runs_task_on_current_thread_callback = [](void* user_data) -> bool { + return [[NSThread currentThread] isMainThread]; + }, + .post_task_callback = [](FlutterTask task, uint64_t target_time_nanos, + void* user_data) -> void { + [((__bridge FlutterEngine*)(user_data)) postMainThreadTask:task + targetTimeInNanoseconds:target_time_nanos]; + }, + .identifier = ++sTaskRunnerIdentifiers, + }; + const FlutterCustomTaskRunners custom_task_runners = { + .struct_size = sizeof(FlutterCustomTaskRunners), + .platform_task_runner = &cocoa_task_runner_description, + .render_task_runner = &cocoa_task_runner_description, + }; + flutterArguments.custom_task_runners = &custom_task_runners; FlutterEngineResult result = FlutterEngineInitialize( FLUTTER_ENGINE_VERSION, &rendererConfig, &flutterArguments, (__bridge void*)(self), &_engine); @@ -358,6 +379,9 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine, NSLog(@"Could not de-initialize the Flutter engine: error %d", result); } + // Balancing release for the retain in the task runner dispatch table. + CFRelease((CFTypeRef)self); + result = FlutterEngineShutdown(_engine); if (result != kSuccess) { NSLog(@"Failed to shut down Flutter engine: error %d", result); @@ -459,4 +483,29 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine, [_textures removeObjectForKey:@(textureID)]; } +#pragma mark - Task runner integration + +- (void)postMainThreadTask:(FlutterTask)task targetTimeInNanoseconds:(uint64_t)targetTime { + const auto engine_time = FlutterEngineGetCurrentTime(); + + __weak FlutterEngine* weak_self = self; + auto worker = ^{ + FlutterEngine* strong_self = weak_self; + if (strong_self && strong_self->_engine) { + auto result = FlutterEngineRunTask(strong_self->_engine, &task); + if (result != kSuccess) { + NSLog(@"Could not post a task to the Flutter engine."); + } + } + }; + + if (targetTime <= engine_time) { + dispatch_async(dispatch_get_main_queue(), worker); + + } else { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, targetTime - engine_time), + dispatch_get_main_queue(), worker); + } +} + @end diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineUnittests.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineUnittests.mm new file mode 100644 index 00000000000..11302a47e10 --- /dev/null +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineUnittests.mm @@ -0,0 +1,25 @@ +// 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/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h" +#include "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h" +#include "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h" +#include "flutter/testing/testing.h" + +namespace flutter::testing { + +TEST(FlutterEngineTest, FlutterEngineCanLaunch) { + NSString* fixtures = @(testing::GetFixturesPath()); + FlutterDartProject* project = [[FlutterDartProject alloc] + initWithAssetsPath:fixtures + ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]]; + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" + project:project + allowHeadlessExecution:true]; + ASSERT_TRUE([engine runWithEntrypoint:@"main"]); + ASSERT_TRUE(engine.running); + [engine shutDownEngine]; +} + +} // flutter::testing diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart new file mode 100644 index 00000000000..f858e3d0e2d --- /dev/null +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart @@ -0,0 +1,7 @@ +// 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. + +void main() { + +}