Skip to content

Commit

Permalink
Reland "ios: remove shared_application and support app extension build
Browse files Browse the repository at this point in the history
…#44732" (#45351)

Relands #44732 with fix. 

The original PR returns nil when the assets is not reachable, in some cases, the assets are not loaded yet but will be loaded later, so we should return the asset URL regardless. 

Also added a fallback to main bundle to match the previous implementation. 

The original PR was failed in internal tests in b/297654739
Now with the fix, all tests passed: cl/561449914

fixes flutter/flutter#124289

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
  • Loading branch information
Chris Yang authored Sep 1, 2023
1 parent d00b69a commit 9446392
Show file tree
Hide file tree
Showing 12 changed files with 211 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@

NS_ASSUME_NONNULL_BEGIN

// Finds a bundle with the named `bundleID` within `searchURL`.
extern const NSString* kDefaultAssetPath;

// Finds a bundle with the named `flutterFrameworkBundleID` within `searchURL`.
//
// Returns `nil` if the bundle cannot be found or if errors are encountered.
NSBundle* FLTFrameworkBundleInternal(NSString* bundleID, NSURL* searchURL);
NSBundle* FLTFrameworkBundleInternal(NSString* flutterFrameworkBundleID, NSURL* searchURL);

// Finds a bundle with the named `bundleID`.
// Finds a bundle with the named `flutterFrameworkBundleID`.
//
// `+[NSBundle bundleWithIdentifier:]` is slow, and can take in the order of
// tens of milliseconds in a minimal flutter app, and closer to 100 milliseconds
Expand All @@ -28,7 +30,25 @@ NSBundle* FLTFrameworkBundleInternal(NSString* bundleID, NSURL* searchURL);
// frameworks used by this file are placed. If the desired bundle cannot be
// found here, the implementation falls back to
// `+[NSBundle bundleWithIdentifier:]`.
NSBundle* FLTFrameworkBundleWithIdentifier(NSString* bundleID);
NSBundle* FLTFrameworkBundleWithIdentifier(NSString* flutterFrameworkBundleID);

// Finds the bundle of the application.
//
// Returns [NSBundle mainBundle] if the current running process is the application.
NSBundle* FLTGetApplicationBundle();

// Gets the flutter assets path directory from `bundle`.
//
// Returns `kDefaultAssetPath` if unable to find asset path from info.plist in `bundle`.
NSString* FLTAssetPath(NSBundle* bundle);

// Finds the Flutter asset directory from `bundle`.
//
// The raw path can be set by the application via info.plist's `FLTAssetsPath` key.
// If the key is not set, `flutter_assets` is used as the raw path value.
//
// If no valid asset is found under the raw path, returns nil.
NSURL* FLTAssetsURLFromBundle(NSBundle* bundle);

NS_ASSUME_NONNULL_END

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

FLUTTER_ASSERT_ARC

NSBundle* FLTFrameworkBundleInternal(NSString* bundleID, NSURL* searchURL) {
const NSString* kDefaultAssetPath = @"Frameworks/App.framework/flutter_assets";

NSBundle* FLTFrameworkBundleInternal(NSString* flutterFrameworkBundleID, NSURL* searchURL) {
NSDirectoryEnumerator<NSURL*>* frameworkEnumerator = [NSFileManager.defaultManager
enumeratorAtURL:searchURL
includingPropertiesForKeys:nil
Expand All @@ -18,19 +20,49 @@
errorHandler:nil];

for (NSURL* candidate in frameworkEnumerator) {
NSBundle* bundle = [NSBundle bundleWithURL:candidate];
if ([bundle.bundleIdentifier isEqualToString:bundleID]) {
return bundle;
NSBundle* flutterFrameworkBundle = [NSBundle bundleWithURL:candidate];
if ([flutterFrameworkBundle.bundleIdentifier isEqualToString:flutterFrameworkBundleID]) {
return flutterFrameworkBundle;
}
}
return nil;
}

NSBundle* FLTFrameworkBundleWithIdentifier(NSString* bundleID) {
NSBundle* bundle = FLTFrameworkBundleInternal(bundleID, NSBundle.mainBundle.privateFrameworksURL);
if (bundle != nil) {
return bundle;
NSBundle* FLTGetApplicationBundle() {
NSBundle* mainBundle = [NSBundle mainBundle];
// App extension bundle is in <AppName>.app/PlugIns/Extension.appex.
if ([mainBundle.bundleURL.pathExtension isEqualToString:@"appex"]) {
// Up two levels.
return [NSBundle bundleWithURL:mainBundle.bundleURL.URLByDeletingLastPathComponent
.URLByDeletingLastPathComponent];
}
return mainBundle;
}

NSBundle* FLTFrameworkBundleWithIdentifier(NSString* flutterFrameworkBundleID) {
NSBundle* appBundle = FLTGetApplicationBundle();
NSBundle* flutterFrameworkBundle =
FLTFrameworkBundleInternal(flutterFrameworkBundleID, appBundle.privateFrameworksURL);
if (flutterFrameworkBundle == nil) {
// Fallback to slow implementation.
flutterFrameworkBundle = [NSBundle bundleWithIdentifier:flutterFrameworkBundleID];
}
if (flutterFrameworkBundle == nil) {
flutterFrameworkBundle = [NSBundle mainBundle];
}
return flutterFrameworkBundle;
}

NSString* FLTAssetPath(NSBundle* bundle) {
return [bundle objectForInfoDictionaryKey:@"FLTAssetsPath"] ?: kDefaultAssetPath;
}

NSURL* FLTAssetsURLFromBundle(NSBundle* bundle) {
NSString* flutterAssetsPath = FLTAssetPath(bundle);
NSURL* assets = [bundle URLForResource:flutterAssetsPath withExtension:nil];

if (!assets) {
assets = [[NSBundle mainBundle] URLForResource:flutterAssetsPath withExtension:nil];
}
// Fallback to slow implementation.
return [NSBundle bundleWithIdentifier:bundleID];
return assets;
}
20 changes: 19 additions & 1 deletion shell/platform/darwin/ios/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,10 @@ shared_library("create_flutter_framework_dylib") {

ldflags = [ "-Wl,-install_name,@rpath/Flutter.framework/Flutter" ]

if (darwin_extension_safe) {
ldflags += [ "-fapplication-extension" ]
}

public = _flutter_framework_headers

deps = [
Expand Down Expand Up @@ -438,14 +442,28 @@ copy("copy_license") {
shared_library("copy_and_verify_framework_module") {
framework_search_path = rebase_path("$root_out_dir")
visibility = [ ":*" ]
cflags_objc = [ "-F$framework_search_path" ]
cflags_objc = [
"-F$framework_search_path",
"-fapplication-extension",
]

sources = [ "framework/Source/FlutterUmbrellaImport.m" ]
deps = [
":copy_framework_headers",
":copy_framework_info_plist",
":copy_framework_module_map",
]

if (darwin_extension_safe) {
ldflags = [
"-F$framework_search_path",
"-fapplication-extension",
"-Xlinker",
"-fatal_warnings",
]
deps += [ ":copy_dylib" ]
frameworks = [ "Flutter.framework" ]
}
}

group("universal_flutter_framework") {
Expand Down
3 changes: 2 additions & 1 deletion shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,8 @@ typedef enum {
*
* @param delegate The receiving object, such as the plugin's main class.
*/
- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate;
- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate
NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in plugins used in app extensions");

/**
* Returns the file name for the given asset.
Expand Down
35 changes: 10 additions & 25 deletions shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,13 @@
// 3. Settings from the NSBundle with the default bundle ID.
// 4. Settings from the main NSBundle and default values.

NSBundle* mainBundle = [NSBundle mainBundle];
NSBundle* mainBundle = FLTGetApplicationBundle();
NSBundle* engineBundle = [NSBundle bundleForClass:[FlutterViewController class]];

bool hasExplicitBundle = bundle != nil;
if (bundle == nil) {
bundle = FLTFrameworkBundleWithIdentifier([FlutterDartProject defaultBundleIdentifier]);
}
if (bundle == nil) {
bundle = mainBundle;
}

auto settings = flutter::SettingsFromCommandLine(command_line);

Expand Down Expand Up @@ -122,29 +119,24 @@

// Checks to see if the flutter assets directory is already present.
if (settings.assets_path.empty()) {
NSString* assetsName = [FlutterDartProject flutterAssetsName:bundle];
NSString* assetsPath = [bundle pathForResource:assetsName ofType:@""];

if (assetsPath.length == 0) {
assetsPath = [mainBundle pathForResource:assetsName ofType:@""];
}
NSURL* assetsURL = FLTAssetsURLFromBundle(bundle);

if (assetsPath.length == 0) {
NSLog(@"Failed to find assets path for \"%@\"", assetsName);
if (!assetsURL) {
NSLog(@"Failed to find assets path for \"%@\"", bundle);
} else {
settings.assets_path = assetsPath.UTF8String;
settings.assets_path = assetsURL.path.UTF8String;

// Check if there is an application kernel snapshot in the assets directory we could
// potentially use. Looking for the snapshot makes sense only if we have a VM that can use
// it.
if (!flutter::DartVM::IsRunningPrecompiledCode()) {
NSURL* applicationKernelSnapshotURL =
[NSURL URLWithString:@(kApplicationKernelSnapshotFileName)
relativeToURL:[NSURL fileURLWithPath:assetsPath]];
if ([[NSFileManager defaultManager] fileExistsAtPath:applicationKernelSnapshotURL.path]) {
[assetsURL URLByAppendingPathComponent:@(kApplicationKernelSnapshotFileName)];
NSError* error;
if ([applicationKernelSnapshotURL checkResourceIsReachableAndReturnError:&error]) {
settings.application_kernel_asset = applicationKernelSnapshotURL.path.UTF8String;
} else {
NSLog(@"Failed to find snapshot: %@", applicationKernelSnapshotURL.path);
NSLog(@"Failed to find snapshot at %@: %@", applicationKernelSnapshotURL.path, error);
}
}
}
Expand Down Expand Up @@ -339,14 +331,7 @@ + (NSString*)flutterAssetsName:(NSBundle*)bundle {
if (bundle == nil) {
bundle = FLTFrameworkBundleWithIdentifier([FlutterDartProject defaultBundleIdentifier]);
}
if (bundle == nil) {
bundle = [NSBundle mainBundle];
}
NSString* flutterAssetsName = [bundle objectForInfoDictionaryKey:@"FLTAssetsPath"];
if (flutterAssetsName == nil) {
flutterAssetsName = @"Frameworks/App.framework/flutter_assets";
}
return flutterAssetsName;
return FLTAssetPath(bundle);
}

+ (NSString*)domainNetworkPolicy:(NSDictionary*)appTransportSecurity {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,50 @@ - (void)testFLTFrameworkBundleInternalWhenBundleIsPresent {
XCTAssertNotNil(found);
}

- (void)testFLTGetApplicationBundleWhenCurrentTargetIsNotExtension {
NSBundle* bundle = FLTGetApplicationBundle();
XCTAssertEqual(bundle, [NSBundle mainBundle]);
}

- (void)testFLTGetApplicationBundleWhenCurrentTargetIsExtension {
id mockMainBundle = OCMPartialMock([NSBundle mainBundle]);
NSURL* url = [[NSBundle mainBundle].bundleURL URLByAppendingPathComponent:@"foo/ext.appex"];
OCMStub([mockMainBundle bundleURL]).andReturn(url);
NSBundle* bundle = FLTGetApplicationBundle();
[mockMainBundle stopMocking];
XCTAssertEqualObjects(bundle.bundleURL, [NSBundle mainBundle].bundleURL);
}

- (void)testFLTAssetsURLFromBundle {
{
// Found asset path in info.plist (even not reachable)
id mockBundle = OCMClassMock([NSBundle class]);
OCMStub([mockBundle objectForInfoDictionaryKey:@"FLTAssetsPath"]).andReturn(@"foo/assets");
NSURL* mockAssetsURL = OCMClassMock([NSURL class]);
OCMStub([mockBundle URLForResource:@"foo/assets" withExtension:nil]).andReturn(mockAssetsURL);
OCMStub([mockAssetsURL checkResourceIsReachableAndReturnError:NULL]).andReturn(NO);
OCMStub([mockAssetsURL path]).andReturn(@"foo/assets");
NSURL* url = FLTAssetsURLFromBundle(mockBundle);
XCTAssertEqualObjects(url.path, @"foo/assets");
}
{
// No asset path in info.plist, defaults to main bundle
id mockBundle = OCMClassMock([NSBundle class]);
id mockMainBundle = OCMPartialMock([NSBundle mainBundle]);
NSURL* mockAssetsURL = OCMClassMock([NSURL class]);
OCMStub([mockBundle URLForResource:@"Frameworks/App.framework/flutter_assets"
withExtension:nil])
.andReturn(nil);
OCMStub([mockAssetsURL checkResourceIsReachableAndReturnError:NULL]).andReturn(NO);
OCMStub([mockAssetsURL path]).andReturn(@"path/to/foo/assets");
OCMStub([mockMainBundle URLForResource:@"Frameworks/App.framework/flutter_assets"
withExtension:nil])
.andReturn(mockAssetsURL);
NSURL* url = FLTAssetsURLFromBundle(mockBundle);
XCTAssertEqualObjects(url.path, @"path/to/foo/assets");
}
}

- (void)testDisableImpellerSettingIsCorrectlyParsed {
id mockMainBundle = OCMPartialMock([NSBundle mainBundle]);
OCMStub([mockMainBundle objectForInfoDictionaryKey:@"FLTEnableImpeller"]).andReturn(@"NO");
Expand Down
3 changes: 2 additions & 1 deletion shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1515,7 +1515,8 @@ - (void)addMethodCallDelegate:(NSObject<FlutterPlugin>*)delegate
}];
}

- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate {
- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate
NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in plugins used in app extensions") {
id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];
if ([appDelegate conformsToProtocol:@protocol(FlutterAppLifeCycleProvider)]) {
id<FlutterAppLifeCycleProvider> lifeCycleProvider =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@

constexpr char kTextPlainFormat[] = "text/plain";
const UInt32 kKeyPressClickSoundId = 1306;

#if not APPLICATION_EXTENSION_API_ONLY
const NSString* searchURLPrefix = @"x-web-search://?";
#endif

} // namespace

Expand All @@ -37,6 +40,24 @@

using namespace flutter;

static void SetStatusBarHiddenForSharedApplication(BOOL hidden) {
#if APPLICATION_EXTENSION_API_ONLY
[UIApplication sharedApplication].statusBarHidden = hidden;
#else
FML_LOG(WARNING) << "Application based status bar styling is not available in app extension.";
#endif
}

static void SetStatusBarStyleForSharedApplication(UIStatusBarStyle style) {
#if APPLICATION_EXTENSION_API_ONLY
// Note: -[UIApplication setStatusBarStyle] is deprecated in iOS9
// in favor of delegating to the view controller.
[[UIApplication sharedApplication] setStatusBarStyle:style];
#else
FML_LOG(WARNING) << "Application based status bar styling is not available in app extension.";
#endif
}

@interface FlutterPlatformPlugin ()

/**
Expand Down Expand Up @@ -141,6 +162,9 @@ - (void)showShareViewController:(NSString*)content {
}

- (void)searchWeb:(NSString*)searchTerm {
#if APPLICATION_EXTENSION_API_ONLY
FML_LOG(WARNING) << "SearchWeb.invoke is not availabe in app extension.";
#else
NSString* escapedText = [searchTerm
stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet
URLHostAllowedCharacterSet]];
Expand All @@ -149,6 +173,7 @@ - (void)searchWeb:(NSString*)searchTerm {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:searchURL]
options:@{}
completionHandler:nil];
#endif
}

- (void)playSystemSound:(NSString*)soundType {
Expand Down Expand Up @@ -231,7 +256,7 @@ - (void)setSystemChromeEnabledSystemUIOverlays:(NSArray*)overlays {
// We opt out of view controller based status bar visibility since we want
// to be able to modify this on the fly. The key used is
// UIViewControllerBasedStatusBarAppearance.
[UIApplication sharedApplication].statusBarHidden = statusBarShouldBeHidden;
SetStatusBarHiddenForSharedApplication(statusBarShouldBeHidden);
}
}

Expand All @@ -246,7 +271,7 @@ - (void)setSystemChromeEnabledSystemUIMode:(NSString*)mode {
// We opt out of view controller based status bar visibility since we want
// to be able to modify this on the fly. The key used is
// UIViewControllerBasedStatusBarAppearance.
[UIApplication sharedApplication].statusBarHidden = !edgeToEdge;
SetStatusBarHiddenForSharedApplication(!edgeToEdge);
}
[[NSNotificationCenter defaultCenter]
postNotificationName:edgeToEdge ? FlutterViewControllerShowHomeIndicator
Expand Down Expand Up @@ -284,9 +309,7 @@ - (void)setSystemChromeSystemUIOverlayStyle:(NSDictionary*)message {
object:nil
userInfo:@{@(kOverlayStyleUpdateNotificationKey) : @(statusBarStyle)}];
} else {
// Note: -[UIApplication setStatusBarStyle] is deprecated in iOS9
// in favor of delegating to the view controller.
[[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle];
SetStatusBarStyleForSharedApplication(statusBarStyle);
}
}

Expand Down
Loading

0 comments on commit 9446392

Please sign in to comment.