You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
FlutterViewController and FlutterEngine unconditionally intercept UIScene lifecycle notifications (e.g., UISceneDidEnterBackgroundNotification, UISceneWillEnterForegroundNotification) broadcast by any scene in the process.
In multi-scene environments (such as iPadOS/macOS multi-window configurations or environments running out-of-process auxiliary scenes like App Extensions),
Because these observers don't filter notifications by their target scene, lifecycle changes in secondary or auxiliary scenes will incorrectly trigger lifescycle state changes (deactivation, pausing, surface destruction) in the main Flutter view, which triggers frozen views, missing surfaces (and potential crashes). I believe this may also be the cause of #187844 when iOS sees the main app surface get destroyed and lifecycle state change while the app is still focused.
Steps to Trigger
To trigger this bug, the following conditions are required:
The app must be running in a scene-notification-enabled environment, such as an App Extension target or really any environment where FlutterSharedApplication.isAvailable evaluates to NO.
The app's Info.plist must enable multi-scene support (UIApplicationSupportsMultipleScenes is true).
The app must programmatically request and open any kind of secondary window scene (e.g., using requestSceneSessionActivation).
I've hacked up a repro app that meets these criteria (though it's not a particularly sane example of an app extension). Run this on an iPad simulator or device. The interesting code is in SceneDelegate.
Here are the steps to repro:
Launch the application.
Tap the "Open New Window" button. This triggers UIApplication.shared.requestSceneSessionActivation which opens a secondary window scene side-by-side with the original.
Swipe away/dismiss the secondary window scene to send it to the background.
Actual: frozen, unresponsive to taps on click counter increment button, displays orange splash screen:
uiscene_lifecycle_broken.mov
Expected: With embedder patches applied:
uiscene_lifecycle_fixed.mov
Expected Results
The secondary window opens/closes/backgrounds normally. The main window remains fully interactive, responsive to click events, and keeps rendering.
Actual Results
Upon opening the second window, both windows immediately freeze and become completely unresponsive to click/touch events. The secondary window remains stuck displaying the (orange) launch screen placeholder. The lifecycle of both engines is broken due to cross-talk of scene activation/deactivation notifications.
Root Cause
In FlutterViewController.mm and FlutterEngine.mm, when registering for scene notifications (such as UISceneDidEnterBackgroundNotification), the engine adds observers to the default notification center with object:nil :
By passing object:nil , the notification center dispatches the notification to the observer whenever any scene in the process changes its state. The event handlers (e.g. sceneDidEnterBackground:) then call appOrSceneDidEnterBackground
directly without verifying if the notification's target scene matches the FlutterViewController's hosting UIWindowScene.
Proposed Fix
Check whether the scene notification matches the current view controller's scene context. Before executing deactivation or backgrounding code, compare the notification's object (which holds the UIScene instance that triggered the transition) with our FlutterViewController's hosting UIWindowScene:
If the notification's object is nil, process it normally.
If the notification's object is not nil, verify if it matches the current view controller's scene. If it doesn't match, ignore the notification and bail out.
FlutterViewControllerandFlutterEngineunconditionally interceptUIScenelifecycle notifications (e.g.,UISceneDidEnterBackgroundNotification,UISceneWillEnterForegroundNotification) broadcast by any scene in the process.In multi-scene environments (such as iPadOS/macOS multi-window configurations or environments running out-of-process auxiliary scenes like App Extensions),
Because these observers don't filter notifications by their target scene, lifecycle changes in secondary or auxiliary scenes will incorrectly trigger lifescycle state changes (deactivation, pausing, surface destruction) in the main Flutter view, which triggers frozen views, missing surfaces (and potential crashes). I believe this may also be the cause of #187844 when iOS sees the main app surface get destroyed and lifecycle state change while the app is still focused.
Steps to Trigger
To trigger this bug, the following conditions are required:
FlutterSharedApplication.isAvailableevaluates toNO.Info.plistmust enable multi-scene support (UIApplicationSupportsMultipleScenesis true).Repro Steps
Repo: https://github.com/cbracken/repro_uiscene
I've hacked up a repro app that meets these criteria (though it's not a particularly sane example of an app extension). Run this on an iPad simulator or device. The interesting code is in SceneDelegate.
Here are the steps to repro:
UIApplication.shared.requestSceneSessionActivationwhich opens a secondary window scene side-by-side with the original.Actual: frozen, unresponsive to taps on click counter increment button, displays orange splash screen:
uiscene_lifecycle_broken.mov
Expected: With embedder patches applied:
uiscene_lifecycle_fixed.mov
Expected Results
The secondary window opens/closes/backgrounds normally. The main window remains fully interactive, responsive to click events, and keeps rendering.
Actual Results
Upon opening the second window, both windows immediately freeze and become completely unresponsive to click/touch events. The secondary window remains stuck displaying the (orange) launch screen placeholder. The lifecycle of both engines is broken due to cross-talk of scene activation/deactivation notifications.
Root Cause
In FlutterViewController.mm and FlutterEngine.mm, when registering for scene notifications (such as
UISceneDidEnterBackgroundNotification), the engine adds observers to the default notification center with object:nil :By passing
object:nil, the notification center dispatches the notification to the observer whenever any scene in the process changes its state. The event handlers (e.g.sceneDidEnterBackground:) then callappOrSceneDidEnterBackgrounddirectly without verifying if the notification's target scene matches the
FlutterViewController's hostingUIWindowScene.Proposed Fix
Check whether the scene notification matches the current view controller's scene context. Before executing deactivation or backgrounding code, compare the notification's object (which holds the
UISceneinstance that triggered the transition) with ourFlutterViewController's hostingUIWindowScene: