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

Commit 4a1709a

Browse files
author
Chris Yang
committed
Revert "[flutter roll] Revert "ios: remove shared_application and support app extension build" (#45250)"
This reverts commit 367c709. fix format tests format
1 parent b867c4d commit 4a1709a

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)