Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@

NS_ASSUME_NONNULL_BEGIN

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

// Finds a bundle with the named `flutterFrameworkBundleID`.
// Finds a bundle with the named `bundleID`.
//
// `+[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,20 +28,7 @@ NSBundle* FLTFrameworkBundleInternal(NSString* flutterFrameworkBundleID, NSURL*
// 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* flutterFrameworkBundleID);

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

// 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);
NSBundle* FLTFrameworkBundleWithIdentifier(NSString* bundleID);

NS_ASSUME_NONNULL_END

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

FLUTTER_ASSERT_ARC

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

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

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];
NSBundle* FLTFrameworkBundleWithIdentifier(NSString* bundleID) {
NSBundle* bundle = FLTFrameworkBundleInternal(bundleID, NSBundle.mainBundle.privateFrameworksURL);
if (bundle != nil) {
return bundle;
}
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;
}

NSURL* FLTAssetsURLFromBundle(NSBundle* bundle) {
NSString* assetsPathFromInfoPlist = [bundle objectForInfoDictionaryKey:@"FLTAssetsPath"];
NSString* flutterAssetsPath = assetsPathFromInfoPlist ?: @"flutter_assets";
NSURL* assets = [bundle URLForResource:flutterAssetsPath withExtension:nil];

if ([assets checkResourceIsReachableAndReturnError:NULL]) {
return assets;
}
return nil;
// Fallback to slow implementation.
return [NSBundle bundleWithIdentifier:bundleID];
}
20 changes: 1 addition & 19 deletions shell/platform/darwin/ios/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -348,10 +348,6 @@ 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 @@ -442,28 +438,14 @@ 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",
"-fapplication-extension",
]
cflags_objc = [ "-F$framework_search_path" ]

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

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

NSBundle* mainBundle = FLTGetApplicationBundle();
NSBundle* mainBundle = [NSBundle mainBundle];
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 @@ -119,24 +122,29 @@

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

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

if (!assetsURL) {
NSLog(@"Failed to find assets path for 1\"%@\"", bundle);
if (assetsPath.length == 0) {
NSLog(@"Failed to find assets path for \"%@\"", assetsName);
} else {
settings.assets_path = assetsURL.path.UTF8String;
settings.assets_path = assetsPath.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 =
[assetsURL URLByAppendingPathComponent:@(kApplicationKernelSnapshotFileName)];
NSError* error;
if ([applicationKernelSnapshotURL checkResourceIsReachableAndReturnError:&error]) {
[NSURL URLWithString:@(kApplicationKernelSnapshotFileName)
relativeToURL:[NSURL fileURLWithPath:assetsPath]];
if ([[NSFileManager defaultManager] fileExistsAtPath:applicationKernelSnapshotURL.path]) {
settings.application_kernel_asset = applicationKernelSnapshotURL.path.UTF8String;
} else {
NSLog(@"Failed to find snapshot at %@: %@", applicationKernelSnapshotURL.path, error);
NSLog(@"Failed to find snapshot: %@", applicationKernelSnapshotURL.path);
}
}
}
Expand Down Expand Up @@ -331,7 +339,9 @@ + (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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,6 @@ - (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)testDisableImpellerSettingIsCorrectlyParsed {
id mockMainBundle = OCMPartialMock([NSBundle mainBundle]);
OCMStub([mockMainBundle objectForInfoDictionaryKey:@"FLTEnableImpeller"]).andReturn(@"NO");
Expand Down
3 changes: 1 addition & 2 deletions shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1515,8 +1515,7 @@ - (void)addMethodCallDelegate:(NSObject<FlutterPlugin>*)delegate
}];
}

- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate
NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in plugins used in app extensions") {
- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate {
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,10 +17,7 @@

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 @@ -40,24 +37,6 @@

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 @@ -162,9 +141,6 @@ - (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 @@ -173,7 +149,6 @@ - (void)searchWeb:(NSString*)searchTerm {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:searchURL]
options:@{}
completionHandler:nil];
#endif
}

- (void)playSystemSound:(NSString*)soundType {
Expand Down Expand Up @@ -256,7 +231,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.
SetStatusBarHiddenForSharedApplication(statusBarShouldBeHidden);
[UIApplication sharedApplication].statusBarHidden = statusBarShouldBeHidden;
}
}

Expand All @@ -271,7 +246,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.
SetStatusBarHiddenForSharedApplication(!edgeToEdge);
[UIApplication sharedApplication].statusBarHidden = !edgeToEdge;
}
[[NSNotificationCenter defaultCenter]
postNotificationName:edgeToEdge ? FlutterViewControllerShowHomeIndicator
Expand Down Expand Up @@ -309,7 +284,9 @@ - (void)setSystemChromeSystemUIOverlayStyle:(NSDictionary*)message {
object:nil
userInfo:@{@(kOverlayStyleUpdateNotificationKey) : @(statusBarStyle)}];
} else {
SetStatusBarStyleForSharedApplication(statusBarStyle);
// Note: -[UIApplication setStatusBarStyle] is deprecated in iOS9
// in favor of delegating to the view controller.
[[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle];
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,9 @@ - (void)testSearchWebInvokedWithEscapedTerm {

FlutterResult result = ^(id result) {
OCMVerify([mockPlugin searchWeb:@"Testing Word!"]);
#if not APPLICATION_EXTENSION_API_ONLY
OCMVerify([mockApplication openURL:[NSURL URLWithString:@"x-web-search://?Testing%20Word!"]
options:@{}
completionHandler:nil]);
#endif
[invokeExpectation fulfill];
};

Expand Down Expand Up @@ -84,11 +82,9 @@ - (void)testSearchWebInvokedWithNonEscapedTerm {

FlutterResult result = ^(id result) {
OCMVerify([mockPlugin searchWeb:@"Test"]);
#if not APPLICATION_EXTENSION_API_ONLY
OCMVerify([mockApplication openURL:[NSURL URLWithString:@"x-web-search://?Test"]
options:@{}
completionHandler:nil]);
#endif
[invokeExpectation fulfill];
};

Expand Down
Loading