Skip to content

[iOS] FlutterViewController globally intercepts scene notifications from other scenes #187986

Description

@cbracken

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:

  1. 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.
  2. The app's Info.plist must enable multi-scene support (UIApplicationSupportsMultipleScenes is true).
  3. The app must programmatically request and open any kind of secondary window scene (e.g., using requestSceneSessionActivation).

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:

  1. Launch the application.
  2. Tap the "Open New Window" button. This triggers UIApplication.shared.requestSceneSessionActivation which opens a secondary window scene side-by-side with the original.
  3. 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 :

    [center addObserver:self
               selector:@selector(sceneDidEnterBackground:)
                   name:UISceneDidEnterBackgroundNotification
                 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:

  1. If the notification's object is nil, process it normally.
  2. 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.

Metadata

Metadata

Assignees

Labels

platform-iosiOS applications specificallyteam-iosOwned by iOS platform team

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions