Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Closed
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
1 change: 1 addition & 0 deletions common/config.gni
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ if (is_ios || is_mac) {
flutter_cflags_objc = [
"-Werror=overriding-method-mismatch",
"-Werror=undeclared-selector",
"-fapplication-extension",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be set by a buildroot flag so the engine can be built with or without app extension safety. I put it here for testing.

]
flutter_cflags_objcc = flutter_cflags_objc
flutter_cflags_objc_arc = flutter_cflags_objc + [ "-fobjc-arc" ]
Expand Down
15 changes: 12 additions & 3 deletions shell/platform/darwin/ios/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ source_set("flutter_framework_source_arc") {
cflags_objc = flutter_cflags_objc_arc
cflags_objcc = flutter_cflags_objcc_arc

defines = [ "FLUTTER_FRAMEWORK=1" ]
defines = [
"FLUTTER_FRAMEWORK=1",
"APPLICATION_EXTENSION_API_ONLY=1",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, this would be set by a buildroot flag.

]
allow_circular_includes_from = [ ":flutter_framework_source" ]
deps = [
":flutter_framework_source",
Expand Down Expand Up @@ -153,7 +156,10 @@ source_set("flutter_framework_source") {

sources += _flutter_framework_headers

defines = [ "FLUTTER_FRAMEWORK=1" ]
defines = [
"FLUTTER_FRAMEWORK=1",
"APPLICATION_EXTENSION_API_ONLY=1",
]

if (shell_enable_metal) {
sources += [
Expand Down Expand Up @@ -320,7 +326,10 @@ shared_library("create_flutter_framework_dylib") {

output_name = "Flutter"

ldflags = [ "-Wl,-install_name,@rpath/Flutter.framework/Flutter" ]
ldflags = [
"-Wl,-install_name,@rpath/Flutter.framework/Flutter",
"-fapplication-extension",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, behind a new flag, etc.

]

public = _flutter_framework_headers

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* code as necessary from FlutterAppDelegate.mm.
*/
FLUTTER_DARWIN_EXPORT
NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions")
@interface FlutterAppDelegate
: UIResponder <UIApplicationDelegate, FlutterPluginRegistry, FlutterAppLifeCycleProvider>

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
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ NS_ASSUME_NONNULL_BEGIN
* Propagates `UIAppDelegate` callbacks to registered plugins.
*/
FLUTTER_DARWIN_EXPORT
NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in plugins used in app extensions")
@interface FlutterPluginAppLifeCycleDelegate : NSObject <UNUserNotificationCenterDelegate>

/**
Expand Down
65 changes: 44 additions & 21 deletions shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,39 @@
return [NSBundle bundleWithIdentifier:bundleID];
}

NS_INLINE NSBundle* FLTFrameworkBundle() {
NSBundle* mainBundle = [NSBundle mainBundle];
NSBundle* bundle = FLTFrameworkBundleWithIdentifier([FlutterDartProject defaultBundleIdentifier]);

// App extension bundle is in Runner.app/PlugIns/Extension.appex.
if ([mainBundle.bundleURL.pathExtension isEqualToString:@"appex"]) {
// Up two levels.
NSBundle* appBundle =
[NSBundle bundleWithURL:mainBundle.bundleURL.URLByDeletingLastPathComponent
.URLByDeletingLastPathComponent];
bundle = FLTFrameworkBundleInternal([FlutterDartProject defaultBundleIdentifier],
appBundle.privateFrameworksURL);
}

if (bundle == nil) {
bundle = mainBundle;
}

return bundle;
}

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

if ([assets checkResourceIsReachableAndReturnError:NULL]) {
return assets;
}
return nil;
}

flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle) {
auto command_line = flutter::CommandLineFromNSProcessInfo();

Expand All @@ -90,10 +123,7 @@

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

auto settings = flutter::SettingsFromCommandLine(command_line);
Expand Down Expand Up @@ -166,29 +196,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:@""];
NSURL* assetsURL = FLTAssetsFromBundle(bundle);

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

if (assetsPath.length == 0) {
NSLog(@"Failed to find assets path for \"%@\"", assetsName);
if (assetsURL == nil) {
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 @@ -361,11 +386,9 @@ - (instancetype)initWithSettings:(const flutter::Settings&)settings {

+ (NSString*)flutterAssetsName:(NSBundle*)bundle {
if (bundle == nil) {
bundle = FLTFrameworkBundleWithIdentifier([FlutterDartProject defaultBundleIdentifier]);
}
if (bundle == nil) {
bundle = [NSBundle mainBundle];
bundle = FLTFrameworkBundle();
}

NSString* flutterAssetsName = [bundle objectForInfoDictionaryKey:@"FLTAssetsPath"];
if (flutterAssetsName == nil) {
flutterAssetsName = @"Frameworks/App.framework/flutter_assets";
Expand Down
6 changes: 5 additions & 1 deletion shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,11 @@ - (BOOL)createShell:(NSString*)entrypoint
);

_isGpuDisabled =
#if APPLICATION_EXTENSION_API_ONLY
NO;
#else
[UIApplication sharedApplication].applicationState == UIApplicationStateBackground;
#endif
// Create the shell. This is a blocking operation.
std::unique_ptr<flutter::Shell> shell = flutter::Shell::Create(
/*platform_data=*/platformData,
Expand Down Expand Up @@ -1433,7 +1437,7 @@ - (void)addMethodCallDelegate:(NSObject<FlutterPlugin>*)delegate
}];
}

- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate {
- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate NS_EXTENSION_UNAVAILABLE_IOS("") {
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 @@ -158,6 +158,7 @@ - (void)setSystemChromeApplicationSwitcherDescription:(NSDictionary*)object {
}

- (void)setSystemChromeEnabledSystemUIOverlays:(NSArray*)overlays {
#if !APPLICATION_EXTENSION_API_ONLY
// Checks if the top status bar should be visible. This platform ignores all
// other overlays

Expand All @@ -175,9 +176,11 @@ - (void)setSystemChromeEnabledSystemUIOverlays:(NSArray*)overlays {
postNotificationName:FlutterViewControllerHideHomeIndicator
object:nil];
}
#endif
}

- (void)setSystemChromeEnabledSystemUIMode:(NSString*)mode {
#if !APPLICATION_EXTENSION_API_ONLY
// Checks if the top status bar should be visible, reflected by edge to edge setting. This
// platform ignores all other system ui modes.

Expand All @@ -195,6 +198,7 @@ - (void)setSystemChromeEnabledSystemUIMode:(NSString*)mode {
postNotificationName:FlutterViewControllerHideHomeIndicator
object:nil];
}
#endif
}

- (void)restoreSystemChromeSystemUIOverlays {
Expand Down Expand Up @@ -231,9 +235,11 @@ - (void)setSystemChromeSystemUIOverlayStyle:(NSDictionary*)message {
object:nil
userInfo:@{@(kOverlayStyleUpdateNotificationKey) : @(statusBarStyle)}];
} else {
#if !APPLICATION_EXTENSION_API_ONLY
// Note: -[UIApplication setStatusBarStyle] is deprecated in iOS9
// in favor of delegating to the view controller
[[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle];
#endif
}
}

Expand All @@ -249,11 +255,15 @@ - (void)popSystemNavigator:(BOOL)isAnimated {
if (navigationController) {
[navigationController popViewControllerAnimated:isAnimated];
} else {
#if !APPLICATION_EXTENSION_API_ONLY
UIViewController* rootViewController =
[UIApplication sharedApplication].keyWindow.rootViewController;
if (engineViewController != rootViewController) {
#endif
[engineViewController dismissViewControllerAnimated:isAnimated completion:nil];
#if !APPLICATION_EXTENSION_API_ONLY
}
#endif
}
}

Expand Down
54 changes: 41 additions & 13 deletions shell/platform/darwin/ios/framework/Source/FlutterViewController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -814,9 +814,14 @@ - (void)viewDidAppear:(BOOL)animated {
if ([_engine.get() viewController] == self) {
[self onUserSettingsChanged:nil];
[self onAccessibilityStatusChanged:nil];

#if !APPLICATION_EXTENSION_API_ONLY
if (UIApplication.sharedApplication.applicationState == UIApplicationStateActive) {
#endif
[[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.resumed"];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to end up sending this if the whole process hosting the extension itself was in the background, thus UIApplication.sharedApplication.applicationState != UIApplicationStateActive if we had access to the sharedApplication? If we sent in "resumed" without checking, might be end up in an engine state where we continue to issue GPU commands while in the background and crash?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a race that I bet could happen in the app extension #23175

#if !APPLICATION_EXTENSION_API_ONLY
}
#endif
}
[super viewDidAppear:animated];
}
Expand Down Expand Up @@ -1304,8 +1309,13 @@ - (void)viewDidLayoutSubviews {
// There is no guarantee that UIKit will layout subviews when the application is active. Creating
// the surface when inactive will cause GPU accesses from the background. Only wait for the first
// frame to render when the application is actually active.
bool applicationIsActive =
BOOL applicationIsActive =

#if APPLICATION_EXTENSION_API_ONLY
YES;
#else
[UIApplication sharedApplication].applicationState == UIApplicationStateActive;
#endif

// This must run after updateViewportMetrics so that the surface creation tasks are queued after
// the viewport metrics update tasks.
Expand Down Expand Up @@ -1808,24 +1818,19 @@ - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
_orientationPreferences = new_preferences;

if (@available(iOS 16.0, *)) {
#if APPLICATION_EXTENSION_API_ONLY
UIWindowScene* windowScene = self.viewIfLoaded.window.windowScene;
[self performOrientationUpdateOnWindowScene:windowScene];
#else
for (UIScene* scene in UIApplication.sharedApplication.connectedScenes) {
if (![scene isKindOfClass:[UIWindowScene class]]) {
continue;
}
UIWindowScene* windowScene = (UIWindowScene*)scene;
UIWindowSceneGeometryPreferencesIOS* preference =
[[UIWindowSceneGeometryPreferencesIOS alloc]
initWithInterfaceOrientations:_orientationPreferences];
[windowScene
requestGeometryUpdateWithPreferences:preference
errorHandler:^(NSError* error) {
os_log_error(OS_LOG_DEFAULT,
"Failed to change device orientation: %@",
error);
}];
[self setNeedsUpdateOfSupportedInterfaceOrientations];
[self performOrientationUpdateOnWindowScene:(UIWindowScene*)scene];
}
#endif
} else {
#if !APPLICATION_EXTENSION_API_ONLY
UIInterfaceOrientationMask currentInterfaceOrientation =
1 << [[UIApplication sharedApplication] statusBarOrientation];
if (!(_orientationPreferences & currentInterfaceOrientation)) {
Expand All @@ -1848,10 +1853,28 @@ - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
forKey:@"orientation"];
}
}
#endif
}
}
}

- (void)performOrientationUpdateOnWindowScene:(UIWindowScene*)windowScene API_AVAILABLE(ios(16.0)) {
if (windowScene == nil) {
return;
}

UIWindowSceneGeometryPreferencesIOS* preference = [[UIWindowSceneGeometryPreferencesIOS alloc]
initWithInterfaceOrientations:_orientationPreferences];
[windowScene
requestGeometryUpdateWithPreferences:preference
errorHandler:^(NSError* error) {
os_log_error(OS_LOG_DEFAULT,
"Failed to change device orientation: %@", error);
}];
[self setNeedsUpdateOfSupportedInterfaceOrientations];
[preference release];
}

- (void)onHideHomeIndicatorNotification:(NSNotification*)notification {
self.isHomeIndicatorHidden = YES;
}
Expand Down Expand Up @@ -1951,7 +1974,12 @@ - (void)onUserSettingsChanged:(NSNotification*)notification {
}

- (CGFloat)textScaleFactor {
#if APPLICATION_EXTENSION_API_ONLY
UIContentSizeCategory category =
self.mainScreenIfViewLoaded.traitCollection.preferredContentSizeCategory;
#else
UIContentSizeCategory category = [UIApplication sharedApplication].preferredContentSizeCategory;
#endif
// The delta is computed by approximating Apple's typography guidelines:
// https://developer.apple.com/ios/human-interface-guidelines/visual-design/typography/
//
Expand Down
6 changes: 5 additions & 1 deletion shell/platform/darwin/macos/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ source_set("flutter_framework_source") {
defines = [
"FLUTTER_FRAMEWORK",
"FLUTTER_ENGINE_NO_PROTOTYPES",
"APPLICATION_EXTENSION_API_ONLY=1",
]

cflags_objcc = flutter_cflags_objcc_arc
Expand All @@ -151,7 +152,10 @@ shared_library("flutter_framework_dylib") {
visibility = [ ":*" ]
output_name = "$_flutter_framework_name"

ldflags = [ "-Wl,-install_name,@rpath/$_flutter_framework_filename/$_framework_binary_subpath" ]
ldflags = [
"-Wl,-install_name,@rpath/$_flutter_framework_filename/$_framework_binary_subpath",
"-fapplication-extension",
]

deps = [ ":flutter_framework_source" ]
}
Expand Down