Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 9446392

Browse files
author
Chris Yang
authored
Reland "ios: remove shared_application and support app extension build #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
1 parent d00b69a commit 9446392

12 files changed

+211
-58
lines changed

shell/platform/darwin/common/framework/Source/FlutterNSBundleUtils.h

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99

1010
NS_ASSUME_NONNULL_BEGIN
1111

12-
// Finds a bundle with the named `bundleID` within `searchURL`.
12+
extern const NSString* kDefaultAssetPath;
13+
14+
// Finds a bundle with the named `flutterFrameworkBundleID` within `searchURL`.
1315
//
1416
// Returns `nil` if the bundle cannot be found or if errors are encountered.
15-
NSBundle* FLTFrameworkBundleInternal(NSString* bundleID, NSURL* searchURL);
17+
NSBundle* FLTFrameworkBundleInternal(NSString* flutterFrameworkBundleID, NSURL* searchURL);
1618

17-
// Finds a bundle with the named `bundleID`.
19+
// Finds a bundle with the named `flutterFrameworkBundleID`.
1820
//
1921
// `+[NSBundle bundleWithIdentifier:]` is slow, and can take in the order of
2022
// tens of milliseconds in a minimal flutter app, and closer to 100 milliseconds
@@ -28,7 +30,25 @@ NSBundle* FLTFrameworkBundleInternal(NSString* bundleID, NSURL* searchURL);
2830
// frameworks used by this file are placed. If the desired bundle cannot be
2931
// found here, the implementation falls back to
3032
// `+[NSBundle bundleWithIdentifier:]`.
31-
NSBundle* FLTFrameworkBundleWithIdentifier(NSString* bundleID);
33+
NSBundle* FLTFrameworkBundleWithIdentifier(NSString* flutterFrameworkBundleID);
34+
35+
// Finds the bundle of the application.
36+
//
37+
// Returns [NSBundle mainBundle] if the current running process is the application.
38+
NSBundle* FLTGetApplicationBundle();
39+
40+
// Gets the flutter assets path directory from `bundle`.
41+
//
42+
// Returns `kDefaultAssetPath` if unable to find asset path from info.plist in `bundle`.
43+
NSString* FLTAssetPath(NSBundle* bundle);
44+
45+
// Finds the Flutter asset directory from `bundle`.
46+
//
47+
// The raw path can be set by the application via info.plist's `FLTAssetsPath` key.
48+
// If the key is not set, `flutter_assets` is used as the raw path value.
49+
//
50+
// If no valid asset is found under the raw path, returns nil.
51+
NSURL* FLTAssetsURLFromBundle(NSBundle* bundle);
3252

3353
NS_ASSUME_NONNULL_END
3454

shell/platform/darwin/common/framework/Source/FlutterNSBundleUtils.mm

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88

99
FLUTTER_ASSERT_ARC
1010

11-
NSBundle* FLTFrameworkBundleInternal(NSString* bundleID, NSURL* searchURL) {
11+
const NSString* kDefaultAssetPath = @"Frameworks/App.framework/flutter_assets";
12+
13+
NSBundle* FLTFrameworkBundleInternal(NSString* flutterFrameworkBundleID, NSURL* searchURL) {
1214
NSDirectoryEnumerator<NSURL*>* frameworkEnumerator = [NSFileManager.defaultManager
1315
enumeratorAtURL:searchURL
1416
includingPropertiesForKeys:nil
@@ -18,19 +20,49 @@
1820
errorHandler:nil];
1921

2022
for (NSURL* candidate in frameworkEnumerator) {
21-
NSBundle* bundle = [NSBundle bundleWithURL:candidate];
22-
if ([bundle.bundleIdentifier isEqualToString:bundleID]) {
23-
return bundle;
23+
NSBundle* flutterFrameworkBundle = [NSBundle bundleWithURL:candidate];
24+
if ([flutterFrameworkBundle.bundleIdentifier isEqualToString:flutterFrameworkBundleID]) {
25+
return flutterFrameworkBundle;
2426
}
2527
}
2628
return nil;
2729
}
2830

29-
NSBundle* FLTFrameworkBundleWithIdentifier(NSString* bundleID) {
30-
NSBundle* bundle = FLTFrameworkBundleInternal(bundleID, NSBundle.mainBundle.privateFrameworksURL);
31-
if (bundle != nil) {
32-
return bundle;
31+
NSBundle* FLTGetApplicationBundle() {
32+
NSBundle* mainBundle = [NSBundle mainBundle];
33+
// App extension bundle is in <AppName>.app/PlugIns/Extension.appex.
34+
if ([mainBundle.bundleURL.pathExtension isEqualToString:@"appex"]) {
35+
// Up two levels.
36+
return [NSBundle bundleWithURL:mainBundle.bundleURL.URLByDeletingLastPathComponent
37+
.URLByDeletingLastPathComponent];
38+
}
39+
return mainBundle;
40+
}
41+
42+
NSBundle* FLTFrameworkBundleWithIdentifier(NSString* flutterFrameworkBundleID) {
43+
NSBundle* appBundle = FLTGetApplicationBundle();
44+
NSBundle* flutterFrameworkBundle =
45+
FLTFrameworkBundleInternal(flutterFrameworkBundleID, appBundle.privateFrameworksURL);
46+
if (flutterFrameworkBundle == nil) {
47+
// Fallback to slow implementation.
48+
flutterFrameworkBundle = [NSBundle bundleWithIdentifier:flutterFrameworkBundleID];
49+
}
50+
if (flutterFrameworkBundle == nil) {
51+
flutterFrameworkBundle = [NSBundle mainBundle];
52+
}
53+
return flutterFrameworkBundle;
54+
}
55+
56+
NSString* FLTAssetPath(NSBundle* bundle) {
57+
return [bundle objectForInfoDictionaryKey:@"FLTAssetsPath"] ?: kDefaultAssetPath;
58+
}
59+
60+
NSURL* FLTAssetsURLFromBundle(NSBundle* bundle) {
61+
NSString* flutterAssetsPath = FLTAssetPath(bundle);
62+
NSURL* assets = [bundle URLForResource:flutterAssetsPath withExtension:nil];
63+
64+
if (!assets) {
65+
assets = [[NSBundle mainBundle] URLForResource:flutterAssetsPath withExtension:nil];
3366
}
34-
// Fallback to slow implementation.
35-
return [NSBundle bundleWithIdentifier:bundleID];
67+
return assets;
3668
}

shell/platform/darwin/ios/BUILD.gn

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,10 @@ shared_library("create_flutter_framework_dylib") {
348348

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

351+
if (darwin_extension_safe) {
352+
ldflags += [ "-fapplication-extension" ]
353+
}
354+
351355
public = _flutter_framework_headers
352356

353357
deps = [
@@ -438,14 +442,28 @@ copy("copy_license") {
438442
shared_library("copy_and_verify_framework_module") {
439443
framework_search_path = rebase_path("$root_out_dir")
440444
visibility = [ ":*" ]
441-
cflags_objc = [ "-F$framework_search_path" ]
445+
cflags_objc = [
446+
"-F$framework_search_path",
447+
"-fapplication-extension",
448+
]
442449

443450
sources = [ "framework/Source/FlutterUmbrellaImport.m" ]
444451
deps = [
445452
":copy_framework_headers",
446453
":copy_framework_info_plist",
447454
":copy_framework_module_map",
448455
]
456+
457+
if (darwin_extension_safe) {
458+
ldflags = [
459+
"-F$framework_search_path",
460+
"-fapplication-extension",
461+
"-Xlinker",
462+
"-fatal_warnings",
463+
]
464+
deps += [ ":copy_dylib" ]
465+
frameworks = [ "Flutter.framework" ]
466+
}
449467
}
450468

451469
group("universal_flutter_framework") {

shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,8 @@ typedef enum {
353353
*
354354
* @param delegate The receiving object, such as the plugin's main class.
355355
*/
356-
- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate;
356+
- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate
357+
NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in plugins used in app extensions");
357358

358359
/**
359360
* Returns the file name for the given asset.

shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,13 @@
4141
// 3. Settings from the NSBundle with the default bundle ID.
4242
// 4. Settings from the main NSBundle and default values.
4343

44-
NSBundle* mainBundle = [NSBundle mainBundle];
44+
NSBundle* mainBundle = FLTGetApplicationBundle();
4545
NSBundle* engineBundle = [NSBundle bundleForClass:[FlutterViewController class]];
4646

4747
bool hasExplicitBundle = bundle != nil;
4848
if (bundle == nil) {
4949
bundle = FLTFrameworkBundleWithIdentifier([FlutterDartProject defaultBundleIdentifier]);
5050
}
51-
if (bundle == nil) {
52-
bundle = mainBundle;
53-
}
5451

5552
auto settings = flutter::SettingsFromCommandLine(command_line);
5653

@@ -122,29 +119,24 @@
122119

123120
// Checks to see if the flutter assets directory is already present.
124121
if (settings.assets_path.empty()) {
125-
NSString* assetsName = [FlutterDartProject flutterAssetsName:bundle];
126-
NSString* assetsPath = [bundle pathForResource:assetsName ofType:@""];
127-
128-
if (assetsPath.length == 0) {
129-
assetsPath = [mainBundle pathForResource:assetsName ofType:@""];
130-
}
122+
NSURL* assetsURL = FLTAssetsURLFromBundle(bundle);
131123

132-
if (assetsPath.length == 0) {
133-
NSLog(@"Failed to find assets path for \"%@\"", assetsName);
124+
if (!assetsURL) {
125+
NSLog(@"Failed to find assets path for \"%@\"", bundle);
134126
} else {
135-
settings.assets_path = assetsPath.UTF8String;
127+
settings.assets_path = assetsURL.path.UTF8String;
136128

137129
// Check if there is an application kernel snapshot in the assets directory we could
138130
// potentially use. Looking for the snapshot makes sense only if we have a VM that can use
139131
// it.
140132
if (!flutter::DartVM::IsRunningPrecompiledCode()) {
141133
NSURL* applicationKernelSnapshotURL =
142-
[NSURL URLWithString:@(kApplicationKernelSnapshotFileName)
143-
relativeToURL:[NSURL fileURLWithPath:assetsPath]];
144-
if ([[NSFileManager defaultManager] fileExistsAtPath:applicationKernelSnapshotURL.path]) {
134+
[assetsURL URLByAppendingPathComponent:@(kApplicationKernelSnapshotFileName)];
135+
NSError* error;
136+
if ([applicationKernelSnapshotURL checkResourceIsReachableAndReturnError:&error]) {
145137
settings.application_kernel_asset = applicationKernelSnapshotURL.path.UTF8String;
146138
} else {
147-
NSLog(@"Failed to find snapshot: %@", applicationKernelSnapshotURL.path);
139+
NSLog(@"Failed to find snapshot at %@: %@", applicationKernelSnapshotURL.path, error);
148140
}
149141
}
150142
}
@@ -339,14 +331,7 @@ + (NSString*)flutterAssetsName:(NSBundle*)bundle {
339331
if (bundle == nil) {
340332
bundle = FLTFrameworkBundleWithIdentifier([FlutterDartProject defaultBundleIdentifier]);
341333
}
342-
if (bundle == nil) {
343-
bundle = [NSBundle mainBundle];
344-
}
345-
NSString* flutterAssetsName = [bundle objectForInfoDictionaryKey:@"FLTAssetsPath"];
346-
if (flutterAssetsName == nil) {
347-
flutterAssetsName = @"Frameworks/App.framework/flutter_assets";
348-
}
349-
return flutterAssetsName;
334+
return FLTAssetPath(bundle);
350335
}
351336

352337
+ (NSString*)domainNetworkPolicy:(NSDictionary*)appTransportSecurity {

shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,50 @@ - (void)testFLTFrameworkBundleInternalWhenBundleIsPresent {
7373
XCTAssertNotNil(found);
7474
}
7575

76+
- (void)testFLTGetApplicationBundleWhenCurrentTargetIsNotExtension {
77+
NSBundle* bundle = FLTGetApplicationBundle();
78+
XCTAssertEqual(bundle, [NSBundle mainBundle]);
79+
}
80+
81+
- (void)testFLTGetApplicationBundleWhenCurrentTargetIsExtension {
82+
id mockMainBundle = OCMPartialMock([NSBundle mainBundle]);
83+
NSURL* url = [[NSBundle mainBundle].bundleURL URLByAppendingPathComponent:@"foo/ext.appex"];
84+
OCMStub([mockMainBundle bundleURL]).andReturn(url);
85+
NSBundle* bundle = FLTGetApplicationBundle();
86+
[mockMainBundle stopMocking];
87+
XCTAssertEqualObjects(bundle.bundleURL, [NSBundle mainBundle].bundleURL);
88+
}
89+
90+
- (void)testFLTAssetsURLFromBundle {
91+
{
92+
// Found asset path in info.plist (even not reachable)
93+
id mockBundle = OCMClassMock([NSBundle class]);
94+
OCMStub([mockBundle objectForInfoDictionaryKey:@"FLTAssetsPath"]).andReturn(@"foo/assets");
95+
NSURL* mockAssetsURL = OCMClassMock([NSURL class]);
96+
OCMStub([mockBundle URLForResource:@"foo/assets" withExtension:nil]).andReturn(mockAssetsURL);
97+
OCMStub([mockAssetsURL checkResourceIsReachableAndReturnError:NULL]).andReturn(NO);
98+
OCMStub([mockAssetsURL path]).andReturn(@"foo/assets");
99+
NSURL* url = FLTAssetsURLFromBundle(mockBundle);
100+
XCTAssertEqualObjects(url.path, @"foo/assets");
101+
}
102+
{
103+
// No asset path in info.plist, defaults to main bundle
104+
id mockBundle = OCMClassMock([NSBundle class]);
105+
id mockMainBundle = OCMPartialMock([NSBundle mainBundle]);
106+
NSURL* mockAssetsURL = OCMClassMock([NSURL class]);
107+
OCMStub([mockBundle URLForResource:@"Frameworks/App.framework/flutter_assets"
108+
withExtension:nil])
109+
.andReturn(nil);
110+
OCMStub([mockAssetsURL checkResourceIsReachableAndReturnError:NULL]).andReturn(NO);
111+
OCMStub([mockAssetsURL path]).andReturn(@"path/to/foo/assets");
112+
OCMStub([mockMainBundle URLForResource:@"Frameworks/App.framework/flutter_assets"
113+
withExtension:nil])
114+
.andReturn(mockAssetsURL);
115+
NSURL* url = FLTAssetsURLFromBundle(mockBundle);
116+
XCTAssertEqualObjects(url.path, @"path/to/foo/assets");
117+
}
118+
}
119+
76120
- (void)testDisableImpellerSettingIsCorrectlyParsed {
77121
id mockMainBundle = OCMPartialMock([NSBundle mainBundle]);
78122
OCMStub([mockMainBundle objectForInfoDictionaryKey:@"FLTEnableImpeller"]).andReturn(@"NO");

shell/platform/darwin/ios/framework/Source/FlutterEngine.mm

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1515,7 +1515,8 @@ - (void)addMethodCallDelegate:(NSObject<FlutterPlugin>*)delegate
15151515
}];
15161516
}
15171517

1518-
- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate {
1518+
- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate
1519+
NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in plugins used in app extensions") {
15191520
id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];
15201521
if ([appDelegate conformsToProtocol:@protocol(FlutterAppLifeCycleProvider)]) {
15211522
id<FlutterAppLifeCycleProvider> lifeCycleProvider =

shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717

1818
constexpr char kTextPlainFormat[] = "text/plain";
1919
const UInt32 kKeyPressClickSoundId = 1306;
20+
21+
#if not APPLICATION_EXTENSION_API_ONLY
2022
const NSString* searchURLPrefix = @"x-web-search://?";
23+
#endif
2124

2225
} // namespace
2326

@@ -37,6 +40,24 @@
3740

3841
using namespace flutter;
3942

43+
static void SetStatusBarHiddenForSharedApplication(BOOL hidden) {
44+
#if APPLICATION_EXTENSION_API_ONLY
45+
[UIApplication sharedApplication].statusBarHidden = hidden;
46+
#else
47+
FML_LOG(WARNING) << "Application based status bar styling is not available in app extension.";
48+
#endif
49+
}
50+
51+
static void SetStatusBarStyleForSharedApplication(UIStatusBarStyle style) {
52+
#if APPLICATION_EXTENSION_API_ONLY
53+
// Note: -[UIApplication setStatusBarStyle] is deprecated in iOS9
54+
// in favor of delegating to the view controller.
55+
[[UIApplication sharedApplication] setStatusBarStyle:style];
56+
#else
57+
FML_LOG(WARNING) << "Application based status bar styling is not available in app extension.";
58+
#endif
59+
}
60+
4061
@interface FlutterPlatformPlugin ()
4162

4263
/**
@@ -141,6 +162,9 @@ - (void)showShareViewController:(NSString*)content {
141162
}
142163

143164
- (void)searchWeb:(NSString*)searchTerm {
165+
#if APPLICATION_EXTENSION_API_ONLY
166+
FML_LOG(WARNING) << "SearchWeb.invoke is not availabe in app extension.";
167+
#else
144168
NSString* escapedText = [searchTerm
145169
stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet
146170
URLHostAllowedCharacterSet]];
@@ -149,6 +173,7 @@ - (void)searchWeb:(NSString*)searchTerm {
149173
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:searchURL]
150174
options:@{}
151175
completionHandler:nil];
176+
#endif
152177
}
153178

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

@@ -246,7 +271,7 @@ - (void)setSystemChromeEnabledSystemUIMode:(NSString*)mode {
246271
// We opt out of view controller based status bar visibility since we want
247272
// to be able to modify this on the fly. The key used is
248273
// UIViewControllerBasedStatusBarAppearance.
249-
[UIApplication sharedApplication].statusBarHidden = !edgeToEdge;
274+
SetStatusBarHiddenForSharedApplication(!edgeToEdge);
250275
}
251276
[[NSNotificationCenter defaultCenter]
252277
postNotificationName:edgeToEdge ? FlutterViewControllerShowHomeIndicator
@@ -284,9 +309,7 @@ - (void)setSystemChromeSystemUIOverlayStyle:(NSDictionary*)message {
284309
object:nil
285310
userInfo:@{@(kOverlayStyleUpdateNotificationKey) : @(statusBarStyle)}];
286311
} else {
287-
// Note: -[UIApplication setStatusBarStyle] is deprecated in iOS9
288-
// in favor of delegating to the view controller.
289-
[[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle];
312+
SetStatusBarStyleForSharedApplication(statusBarStyle);
290313
}
291314
}
292315

0 commit comments

Comments
 (0)