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

Conversation

@jmagman
Copy link
Member

@jmagman jmagman commented Mar 15, 2023

First-pass on allowing engine to be built for app extensions without unsafe APIs. This would allow the engine to be built twice, once for apps and again for app extensions. Note the build rule tweaks are only to allow testing, this would require much more work to output two versions of the framework.

See design doc

An alternative would be to build two versions of the Flutter engines–one safe to be used in app extensions and built with with the -fapplication-extension flag and with APPLICATION_EXTENSION_API_ONLY=1 added to the defines, and the other identical to the one that exists today. Both versions would be built and published as artifacts with the same engine infrastructure and rolled into g3. Apps would continue to use the existing engine, and app extensions would instead link on and embed the new safe variant. This would double the Flutter engine size in the final app bundle (at time of writing 9.1 MB in an uncompressed release app). This alternative would be faster to implement than splitting the engines, and could be a medium-term stopgap for customers willing to sacrifice app size for extension features.

App extension unsafe code would be compiled out of the new safe framework behind conditional APPLICATION_EXTENSION_API_ONLY checks and NS_EXTENSION_UNAVAILABLE_IOS annotations. Some APIs like the FlutterPluginAppLifeCycleDelegate would be disallowed in the new safe framework, potentially impacting what plugins or plugin features can be used in extensions.

Steps to use

  1. In your Flutter SDK check out flutter/flutter@c80ef4768e which is a random commit but matches the same dart version as this branch. Run flutter to build the tool and download artifacts.

  2. In your engine repo check out this branch gh pr checkout 40332.

$ gclient sync 
$  ./flutter/tools/gn --no-goma --unoptimized --ios --simulator && ninja -C out/ios_debug_sim_unopt # Adjust options if you are on an arm Mac to generate ios_debug_sim_unopt_arm64
$ flutter create test_extension
$ cd test_extension
$ flutter build ios --config-only
$ open ios/Runner.xcworkspace/
  1. Add a new Share target called FlutterShare and Activate it.

Screenshot 2023-04-14 at 5 36 02 PM

Screenshot 2023-04-14 at 5 36 09 PM

  1. From Finder drag the out/ios_debug_sim_unopt/Flutter.xcframework (or ios_debug_sim_unopt_arm64) into the FlutterShare target General > Frameworks and Libraries section. Make sure Embed & Sign is selected in the drop down

Screenshot 2023-04-14 at 5 40 18 PM

  1. In test_extension/ios/FlutterShare/Info.plist remove the NSExtensionMainStoryboard key. Add a new key-value NSExtensionPrincipalClass = FlutterViewController.

Screenshot 2023-04-14 at 5 47 18 PM

  1. Tell the app to link on Flutter. You can either add @import Flutter; to the totally unused ShareViewController.m or in the FlutterShare target build settings add -framework Flutter to Other Linker Flags (you only need to do one of these options).

Screenshot 2023-04-14 at 5 49 09 PM

or

Screenshot 2023-04-14 at 5 49 48 PM

  1. In Xcode run the Runner scheme on a simulator to install the app, or flutter run from the test_extension project directory. See the demo app run.
  2. In the simulator go to the home screen and open Photos. Choose a Photo and hit the Share button.

Screenshot 2023-04-14 at 5 51 18 PM

Choose Test Extension
Screenshot 2023-04-14 at 5 51 18 PM

See the Flutter counter app run in the Share Extension:

Screenshot 2023-04-14 at 5 51 18 PM

Validate

  1. In your DerivedProducts build products directory the Flutter engine in your app framework directory Build/Products/Debug-iphonesimulator/Runner.app/Frameworks/Flutter.framework/Info.plist the FlutterEngine key should be 4965437, which is the engine in your Flutter SDK.
  2. In your DerivedProducts build products directory the Flutter engine in your app extension framework directory Build/Products/Debug-iphonesimulator/Runner.app/Frameworks/Flutter.framework/Info.plist the FlutterEngine key should be 575105f, which is the commit of this PR.

#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

}

- (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
#if !APPLICATION_EXTENSION_API_ONLY
Copy link
Member Author

Choose a reason for hiding this comment

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

Might be able to fall back to changing UIWindowSceneGeometryPreferencesIOS on self.viewIfLoaded.window.windowScene instead, if that works.

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.

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.

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.

@jmagman
Copy link
Member Author

jmagman commented Apr 11, 2023

Closing in favor of #40972

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants