Skip to content

Reland #179643, only scroll hit-testable primary scroll views on status bar tap#182391

Merged
auto-submit[bot] merged 4 commits intoflutter:masterfrom
LongCatIsLooong:reland-179643
Feb 20, 2026
Merged

Reland #179643, only scroll hit-testable primary scroll views on status bar tap#182391
auto-submit[bot] merged 4 commits intoflutter:masterfrom
LongCatIsLooong:reland-179643

Conversation

@LongCatIsLooong
Copy link
Contributor

@LongCatIsLooong LongCatIsLooong commented Feb 13, 2026

Reland #179643, with changes to fix #182233. The reland closes #177992, closes #175606.

The crash happened because Scaffold.handleStatusBar (and subsequently ScrollController.animateTo) is called on a scaffold that has never been laid out. Before #179643, the iOS embedder sends a fake touch down pointer event at (0, 0) to the framework, followed by a touch up pointer event at the same location. That ensured only the foreground Scaffold could claim the tap gesture and handle the status bar tap.

The fix tries to restore the previous behavior (where the iOS behavior sends fake touches instead of platform messages) by performing a hit-test at (0, 0) before trying to scroll the primary controller of a Scaffold to the top, this should make sure that only the foreground scaffold (in theory it's still possible for more than one Scaffold to receive the hittest and handle the status bar tap event, but the foreground scaffold should block the hit-test on background ones instead of relying on gesture disambiguation).

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

@github-actions github-actions bot added a: tests "flutter test", flutter_test, or one of our tests platform-ios iOS applications specifically framework flutter/packages/flutter repository. See also f: labels. engine flutter/engine related. See also e: labels. f: material design flutter/packages/flutter/material repository. f: cupertino flutter/packages/flutter/cupertino repository team-ios Owned by iOS platform team labels Feb 13, 2026
@LongCatIsLooong LongCatIsLooong force-pushed the reland-179643 branch 2 times, most recently from 7f45eb7 to 3f7d345 Compare February 14, 2026 03:05
@LongCatIsLooong LongCatIsLooong marked this pull request as ready for review February 16, 2026 00:35
@LongCatIsLooong LongCatIsLooong requested a review from a team as a code owner February 16, 2026 00:35
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the handling of status bar taps on iOS to fix a crash. Previously, fake touch events were sent, which could cause issues if the scaffold was not laid out. The new implementation introduces a dedicated flutter/status_bar method channel to communicate the tap event from the native side to the framework. In the framework, Scaffold and CupertinoPageScaffold now use a hit-test at the origin to ensure only the foreground, hit-testable scroll view responds to the tap, preventing crashes and incorrect behavior. The changes also include updated tests and new testing infrastructure to support this new mechanism. There is one area of code duplication that should be addressed.

Comment on lines +3485 to +3521
final class _HitTestableAtOrigin extends StatelessWidget {
const _HitTestableAtOrigin(this.globalKey);

final GlobalKey globalKey;

/// Whether the render box of the [_HitTestableAtOrigin] widget associated
/// with the given global `key` is hit-testable at [Offset.zero].
///
/// This is used by the `handleStatusBarTap` implementation to avoid sending
/// status bar tap events to scroll views in offscreen subtrees.
static bool hitTestableAtOrigin(GlobalKey key) {
final context = key.currentContext as Element?;
if (context == null) {
assert(
false,
'BuildContext associated with $key is not mounted. '
'If you see this in a test, this is likely because the test was trying '
'to simulate status bar tap on a non-iOS platform',
);
return false;
}
final renderObject = context.renderObject! as RenderMetaData;
final int viewId = View.of(context).viewId;
final result = HitTestResult();
WidgetsBinding.instance.hitTestInView(result, Offset.zero, viewId);
return result.path.any((HitTestEntry entry) => entry.target == renderObject);
}

@override
Widget build(BuildContext context) {
return MetaData(
key: globalKey,
behavior: HitTestBehavior.translucent,
child: const SizedBox.expand(),
);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This _HitTestableAtOrigin class is a duplicate of the one defined in packages/flutter/lib/src/cupertino/page_scaffold.dart. To avoid code duplication and improve maintainability, consider moving this class to a shared location.

A possible approach would be to create a new file, for example lib/src/widgets/hit_testable_at_origin.dart, and define a public HitTestableAtOrigin widget there. This file would not be exported by widgets.dart, keeping it as an internal utility. Both material/scaffold.dart and cupertino/page_scaffold.dart could then import and use this shared widget.

When refactoring, please consider keeping the more descriptive assert message from this material/scaffold.dart version.

References
  1. This class is duplicated in another file. To adhere to the principle of avoiding duplication and keeping a single source of truth, it should be refactored into a shared utility. (link)

@LongCatIsLooong
Copy link
Contributor Author

I've tested the patch that this works on iPad / iPhone simulators.

}
final Widget? statusBar = switch (themeData.platform) {
TargetPlatform.iOS ||
TargetPlatform.macOS => widget.primary ? _HitTestableAtOrigin(_statusBarKey) : null,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ideally this should be for iOS only, as on macOS the _HitTestableAtOrigin widget will never be used.

case TargetPlatform.windows:
break;
}
final Widget? statusBar = switch (themeData.platform) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The change here (and in cupertino page scaffold) only replaces the tap gesture recognizer with _HitTestableAtOrigin.

///
/// * [Scaffold] and [CupertinoPageScaffold] which use this callback to implement
/// iOS scroll-to-top.
void handleStatusBarTap() {}
Copy link
Contributor

@chunhtai chunhtai Feb 17, 2026

Choose a reason for hiding this comment

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

If this need to have the prevent bubble up , this should return bool similar to handleStartBackGesture

Copy link
Contributor Author

@LongCatIsLooong LongCatIsLooong Feb 17, 2026

Choose a reason for hiding this comment

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

You mean we stop delivering a status bar tap event to the remaining listeners, as soon as one of the callbacks returned true? Unfortunately we deliver status bar tap events to listeners in registration order, and since the registration will most likely happen in initState / activate, I don't think we can guarantee that States with higher precedence for handling status bar taps will be mounted before low precedence ones (especially when there're LayoutBuilders in the app). Also there could be use cases that two scrollables should handle a single status bar tap simultaneously.

Copy link
Contributor

Choose a reason for hiding this comment

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

looking at the code, the _HitTestableAtOrigin.hitTestableAtOrigin decided whether it should scroll to the top for a scaffold? can that be used to determine whether to return true or false?

Also there could be use cases that two scrollables should handle a single status bar tap simultaneously.

Can you talk more about this? does that mean there are two scaffold that has overlapping hittest origin?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also there could be use cases that two scrollables should handle a single status bar tap simultaneously.

The case I have in mind is that there are two scrollables placed next to each other and the app developer decided that both of them should respond to the same status bar tap (maybe for something like text diff UI). This is the WidgetsBindingObserver API so it's possible for app developers to add their own observers that unconditionally handle status bar taps, not just Scaffolds.

Copy link
Contributor

Choose a reason for hiding this comment

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

In that case, why just do this when its top is align with view's top? is this the existing behavior before the change?

other than that I think using void is fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah the Offset.zero hit-test is from the old implementation (fake pointer down + up events at the origin).

// the status bar tap is handled by the same Scaffolds as before.
// Switch to a better solution when available:
// https://github.com/flutter/flutter/issues/182403
_HitTestableAtOrigin.hitTestableAtOrigin(_statusBarKey)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

can you talk more about this extra check? what is a counter example if we don't have this check?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I put the relevant code comment at the declaration site of the hitTestableAtOrigin (because there's already a TODO here). Do you prefer moving that comment up to the call site?

///
/// * [Scaffold] and [CupertinoPageScaffold] which use this callback to implement
/// iOS scroll-to-top.
void handleStatusBarTap() {}
Copy link
Contributor

Choose a reason for hiding this comment

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

looking at the code, the _HitTestableAtOrigin.hitTestableAtOrigin decided whether it should scroll to the top for a scaffold? can that be used to determine whether to return true or false?

Also there could be use cases that two scrollables should handle a single status bar tap simultaneously.

Can you talk more about this? does that mean there are two scaffold that has overlapping hittest origin?

primaryScrollController.hasClients &&
// TODO(LongCatIsLooong): the iOS embedder used to send status bar tap
// evets as fake touches at Offset.zero. The hit-test here makes sure
// the status bar tap is handled by the same Scaffolds as before.
Copy link
Contributor

Choose a reason for hiding this comment

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

What does the as before mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

rephrased the todo to make it clear that hit test is to keep the legacy behavior.

Copy link
Contributor

Choose a reason for hiding this comment

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

this is not help either, reader won't know what legacy behavior is, we should just state what the behavior is

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The comment goes like this:

        // TODO(LongCatIsLooong): the iOS embedder used to send status bar tap
        // evets as fake touches at Offset.zero, such that at most one Scaffold
        // (usually the foreground primary Scaffold) can handle the status bar
        // tap event, thanks to hit-testing and gesture disambiguation.
        // To keep that behavior, this widget performs an additional hit-test here
        // to make sure the status bar tap is only handled if the scaffold is
        // hit-testable (thus in the foreground)
        // Switch to a better solution when available:
        // https://github.com/flutter/flutter/issues/182403

@vashworth
Copy link
Contributor

The iOS parts of this are a direct reland and the changes are all part of the framework, so I'll leave the approval up @chunhtai

Copy link
Contributor

@chunhtai chunhtai left a comment

Choose a reason for hiding this comment

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

LGTM, given we have plan to migrate to dart:ui #182509

@LongCatIsLooong
Copy link
Contributor Author

LongCatIsLooong commented Feb 19, 2026

Merging this after verifying that the error handler is catching the user callback error in case the callback throws again.

Update: it is not, because it's an async call. #182233 (comment)

@LongCatIsLooong LongCatIsLooong added the autosubmit Merge PR when tree becomes green via auto submit App label Feb 19, 2026
@auto-submit auto-submit bot added this pull request to the merge queue Feb 19, 2026
Merged via the queue into flutter:master with commit 91b2d41 Feb 20, 2026
184 checks passed
@flutter-dashboard flutter-dashboard bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Feb 20, 2026
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 20, 2026
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 20, 2026
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 20, 2026
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 20, 2026
@LongCatIsLooong LongCatIsLooong added cp: beta cherry pick this pull request to beta release candidate branch cp: stable cherry pick this pull request to stable release candidate branch labels Feb 20, 2026
@flutteractionsbot
Copy link

Failed to create CP due to merge conflicts.
You will need to create the PR manually. See the cherrypick wiki for more info.

1 similar comment
@flutteractionsbot
Copy link

Failed to create CP due to merge conflicts.
You will need to create the PR manually. See the cherrypick wiki for more info.

@LongCatIsLooong LongCatIsLooong deleted the reland-179643 branch February 20, 2026 20:13
auto-submit bot pushed a commit to flutter/packages that referenced this pull request Feb 20, 2026
…11088)

Manual roll Flutter from c023e5b2474f to 91b2d41a66d1 (31 revisions)

Manual roll requested by [email protected]

flutter/flutter@c023e5b...91b2d41

2026-02-19 [email protected] Reland #179643, only scroll hit-testable primary scroll views on status bar tap (flutter/flutter#182391)
2026-02-19 [email protected] Replace References to `flutter/engine` with `flutter/flutter` (flutter/flutter#182600)
2026-02-19 [email protected] Remove specific iOS extended attributes to fix code signing (flutter/flutter#180710)
2026-02-19 [email protected] Manual roll Skia from 7bbdc51ab0aa to ce5854495a3a (flutter/flutter#182637)
2026-02-19 [email protected] [pv]add integration test for original untappable web view link behind context menu bug (flutter/flutter#182111)
2026-02-19 [email protected] Roll pub packages (flutter/flutter#182579)
2026-02-19 [email protected] Remove Material import from scroll_view_test.dart (flutter/flutter#181281)
2026-02-19 [email protected] Add RawTooltip.ignorePointer (flutter/flutter#182527)
2026-02-19 [email protected] [web] Stop double loading fonts for WebParagraph (flutter/flutter#182026)
2026-02-19 [email protected] Migrate abi build paths to use new abi filtering api #AGP9 (flutter/flutter#181828)
2026-02-19 [email protected] [web] Flutter errors should be reported with console.error() (flutter/flutter#178886)
2026-02-19 [email protected] Manual roll Skia from dfe78d132e24 to 7bbdc51ab0aa (8 revisions) (flutter/flutter#182612)
2026-02-19 [email protected] Refactor autofill_group_test.dart to remove Material dependencies (flutter/flutter#181903)
2026-02-19 [email protected] Roll Packages from 59f905c to 9da22bf (8 revisions) (flutter/flutter#182611)
2026-02-19 [email protected] Roll Fuchsia Linux SDK from Ihau0pUz3u5ajw42u... to KfPgw04T0OEADLJA5... (flutter/flutter#182607)
2026-02-19 [email protected] Marks Mac_arm64_mokey entrypoint_dart_registrant unflaky (flutter/flutter#181648)
2026-02-19 [email protected] Remove material from Modal barrier tests (flutter/flutter#181708)
2026-02-19 [email protected] Remove material from ticker mode test (flutter/flutter#181696)
2026-02-19 [email protected] Remove material imports from Inherited Model, Magnifier, SafeArea, UndoHistory, Navigator and Layers test (flutter/flutter#181709)
2026-02-19 [email protected] docs: fix grammar in animation library documentation (flutter/flutter#182461)
2026-02-19 [email protected] Handle#6537 first grouped tests (flutter/flutter#182077)
2026-02-19 [email protected] Move SelectionArea web test from widgets to material folder (flutter/flutter#181951)
2026-02-19 [email protected] Roll Dart SDK from 44895e617182 to 2642761fca94 (6 revisions) (flutter/flutter#182572)
2026-02-19 [email protected] Update create template to always generate both SwiftPM and CocoaPods support for iOS/macOS plugins (flutter/flutter#181251)
2026-02-18 [email protected] Fix(Material): DateRangePicker ignores DatePickerTheme.dayShape (flutter/flutter#181658)
2026-02-18 [email protected] Fixing ExpansionTile expandedAlignment not Accepts AlignmentGeometry … (flutter/flutter#180814)
2026-02-18 [email protected] Give guided error message when CocoaPod and SwiftPM dependency conflicts (flutter/flutter#182392)
2026-02-18 [email protected] Remove material from interactive_viewer_test.dart (flutter/flutter#181465)
2026-02-18 [email protected] Bring Windows misc coverage out of bringup (flutter/flutter#182332)
2026-02-18 [email protected] Update android symbolication instructions (flutter/flutter#181267)
2026-02-18 [email protected] Unmark stable vulkan platform view tests as bringup (flutter/flutter#182554)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
...
LongCatIsLooong added a commit to LongCatIsLooong/flutter that referenced this pull request Feb 20, 2026
…on status bar tap (flutter#182391)

Reland flutter#179643, with
[changes](flutter@3f7d345)
to fix flutter#182233. The reland
closes flutter#177992, closes
flutter#175606.

The crash happened because `Scaffold.handleStatusBar` (and subsequently
`ScrollController.animateTo`) is called on a scaffold that [has never
been laid
out](flutter#182233 (comment)).
Before flutter#179643, the iOS embedder sends a fake touch down pointer event
at `(0, 0)` to the framework, followed by a touch up pointer event at
the same location. That ensured only the foreground `Scaffold` could
claim the tap gesture and handle the status bar tap.

The fix tries to restore the previous behavior (where the iOS behavior
sends fake touches instead of platform messages) by performing a
hit-test at `(0, 0)` before trying to scroll the primary controller of a
`Scaffold` to the top, this should make sure that only the foreground
scaffold (in theory it's still possible for more than one Scaffold to
receive the hittest and handle the status bar tap event, but the
foreground scaffold should block the hit-test on background ones instead
of relying on gesture disambiguation).

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
LongCatIsLooong added a commit to LongCatIsLooong/flutter that referenced this pull request Feb 20, 2026
…on status bar tap (flutter#182391)

Reland flutter#179643, with
[changes](flutter@3f7d345)
to fix flutter#182233. The reland
closes flutter#177992, closes
flutter#175606.

The crash happened because `Scaffold.handleStatusBar` (and subsequently
`ScrollController.animateTo`) is called on a scaffold that [has never
been laid
out](flutter#182233 (comment)).
Before flutter#179643, the iOS embedder sends a fake touch down pointer event
at `(0, 0)` to the framework, followed by a touch up pointer event at
the same location. That ensured only the foreground `Scaffold` could
claim the tap gesture and handle the status bar tap.

The fix tries to restore the previous behavior (where the iOS behavior
sends fake touches instead of platform messages) by performing a
hit-test at `(0, 0)` before trying to scroll the primary controller of a
`Scaffold` to the top, this should make sure that only the foreground
scaffold (in theory it's still possible for more than one Scaffold to
receive the hittest and handle the status bar tap event, but the
foreground scaffold should block the hit-test on background ones instead
of relying on gesture disambiguation).

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
LongCatIsLooong added a commit to LongCatIsLooong/flutter that referenced this pull request Feb 20, 2026
LongCatIsLooong added a commit to LongCatIsLooong/flutter that referenced this pull request Feb 20, 2026
…on status bar tap (flutter#182391)

Reland flutter#179643, with
[changes](flutter@3f7d345)
to fix flutter#182233. The reland
closes flutter#177992, closes
flutter#175606.

The crash happened because `Scaffold.handleStatusBar` (and subsequently
`ScrollController.animateTo`) is called on a scaffold that [has never
been laid
out](flutter#182233 (comment)).
Before flutter#179643, the iOS embedder sends a fake touch down pointer event
at `(0, 0)` to the framework, followed by a touch up pointer event at
the same location. That ensured only the foreground `Scaffold` could
claim the tap gesture and handle the status bar tap.

The fix tries to restore the previous behavior (where the iOS behavior
sends fake touches instead of platform messages) by performing a
hit-test at `(0, 0)` before trying to scroll the primary controller of a
`Scaffold` to the top, this should make sure that only the foreground
scaffold (in theory it's still possible for more than one Scaffold to
receive the hittest and handle the status bar tap event, but the
foreground scaffold should block the hit-test on background ones instead
of relying on gesture disambiguation).

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
auto-submit bot pushed a commit that referenced this pull request Feb 24, 2026
This pull request is manually created because there were merge conflicts. 
<details>
<summary><b>Click here for the conflicts</b></summary>

```diff
diff --cc packages/flutter/test/widgets/interactive_viewer_test.dart
index e4ef349,63b1c353d72..00000000000
--- a/packages/flutter/test/widgets/interactive_viewer_test.dart
+++ b/packages/flutter/test/widgets/interactive_viewer_test.dart
@@@ -1044,25 -956,22 +1044,43 @@@ void main() 
          var calledStart = false;
          var calledUpdate = false;
          var calledEnd = false;
+         const sizedBox = SizedBox(width: 200.0, height: 200.0);
          await tester.pumpWidget(
++<<<<<<< HEAD
 +          MaterialApp(
 +            home: Scaffold(
 +              body: Center(
 +                child: InteractiveViewer(
 +                  transformationController: transformationController,
 +                  scaleEnabled: false,
 +                  onInteractionStart: (ScaleStartDetails details) {
 +                    calledStart = true;
 +                  },
 +                  onInteractionUpdate: (ScaleUpdateDetails details) {
 +                    calledUpdate = true;
 +                  },
 +                  onInteractionEnd: (ScaleEndDetails details) {
 +                    calledEnd = true;
 +                  },
 +                  child: const SizedBox(width: 200.0, height: 200.0),
 +                ),
 +              ),
++=======
+           Center(
+             child: InteractiveViewer(
+               transformationController: transformationController,
+               scaleEnabled: false,
+               onInteractionStart: (ScaleStartDetails details) {
+                 calledStart = true;
+               },
+               onInteractionUpdate: (ScaleUpdateDetails details) {
+                 calledUpdate = true;
+               },
+               onInteractionEnd: (ScaleEndDetails details) {
+                 calledEnd = true;
+               },
+               child: sizedBox,
++>>>>>>> 91b2d41 (Reland #179643, only scroll hit-testable primary scroll views on status bar tap (#182391))
              ),
            ),
          );
@@@ -1119,25 -1028,22 +1137,43 @@@
          var calledStart = false;
          var calledUpdate = false;
          var calledEnd = false;
+         const sizedBox = SizedBox(width: 200.0, height: 200.0);
          await tester.pumpWidget(
++<<<<<<< HEAD
 +          MaterialApp(
 +            home: Scaffold(
 +              body: Center(
 +                child: InteractiveViewer(
 +                  transformationController: transformationController,
 +                  scaleEnabled: false,
 +                  onInteractionStart: (ScaleStartDetails details) {
 +                    calledStart = true;
 +                  },
 +                  onInteractionUpdate: (ScaleUpdateDetails details) {
 +                    calledUpdate = true;
 +                  },
 +                  onInteractionEnd: (ScaleEndDetails details) {
 +                    calledEnd = true;
 +                  },
 +                  child: const SizedBox(width: 200.0, height: 200.0),
 +                ),
 +              ),
++=======
+           Center(
+             child: InteractiveViewer(
+               transformationController: transformationController,
+               scaleEnabled: false,
+               onInteractionStart: (ScaleStartDetails details) {
+                 calledStart = true;
+               },
+               onInteractionUpdate: (ScaleUpdateDetails details) {
+                 calledUpdate = true;
+               },
+               onInteractionEnd: (ScaleEndDetails details) {
+                 calledEnd = true;
+               },
+               child: sizedBox,
++>>>>>>> 91b2d41 (Reland #179643, only scroll hit-testable primary scroll views on status bar tap (#182391))
              ),
            ),
          );
```

</details>

### Issue Link:
What is the link to the issue this cherry-pick is addressing?

#182233
The bug was introduced in #179643 which was cherry picked to 3.41 beta (so the crash should exist on both current beta and stable).

### Impact Description:

On iOS, the app may crash when the user taps on the status bar to scroll to top, when the app has a primary scroll view that has never been laid out (for example, in a route that has been obstructed by other routes since its creation).

### Changelog Description:

- [flutter/182233](#182233) - Tapping on the status bar may crash the app on iOS when there's a primary scroll view that has never been laid out.

Additionally this patch only dispatches the "scroll to top" command to the topmost Scaffold which is generally better UX for most apps (this doesn't have to be in the changlog)

### Workaround:

No known easy workarounds.

### Risk:
What is the risk level of this cherry-pick?

### Test Coverage:
Are you confident that your fix is well-tested by automated tests?

### Validation Steps:
What are the steps to validate that this fix works?

I've manually tested the fix on iOS/iPadOS simulators. There are also unit tests.
auto-submit bot pushed a commit that referenced this pull request Feb 24, 2026
This pull request is manually created because there were merge conflicts. 
<details>
<summary><b>Click here for the conflicts</b></summary>

```diff
diff --cc packages/flutter/test/widgets/interactive_viewer_test.dart
index e4ef349,63b1c353d72..00000000000
--- a/packages/flutter/test/widgets/interactive_viewer_test.dart
+++ b/packages/flutter/test/widgets/interactive_viewer_test.dart
@@@ -1044,25 -956,22 +1044,43 @@@ void main() 
          var calledStart = false;
          var calledUpdate = false;
          var calledEnd = false;
+         const sizedBox = SizedBox(width: 200.0, height: 200.0);
          await tester.pumpWidget(
++<<<<<<< HEAD
 +          MaterialApp(
 +            home: Scaffold(
 +              body: Center(
 +                child: InteractiveViewer(
 +                  transformationController: transformationController,
 +                  scaleEnabled: false,
 +                  onInteractionStart: (ScaleStartDetails details) {
 +                    calledStart = true;
 +                  },
 +                  onInteractionUpdate: (ScaleUpdateDetails details) {
 +                    calledUpdate = true;
 +                  },
 +                  onInteractionEnd: (ScaleEndDetails details) {
 +                    calledEnd = true;
 +                  },
 +                  child: const SizedBox(width: 200.0, height: 200.0),
 +                ),
 +              ),
++=======
+           Center(
+             child: InteractiveViewer(
+               transformationController: transformationController,
+               scaleEnabled: false,
+               onInteractionStart: (ScaleStartDetails details) {
+                 calledStart = true;
+               },
+               onInteractionUpdate: (ScaleUpdateDetails details) {
+                 calledUpdate = true;
+               },
+               onInteractionEnd: (ScaleEndDetails details) {
+                 calledEnd = true;
+               },
+               child: sizedBox,
++>>>>>>> 91b2d41 (Reland #179643, only scroll hit-testable primary scroll views on status bar tap (#182391))
              ),
            ),
          );
@@@ -1119,25 -1028,22 +1137,43 @@@
          var calledStart = false;
          var calledUpdate = false;
          var calledEnd = false;
+         const sizedBox = SizedBox(width: 200.0, height: 200.0);
          await tester.pumpWidget(
++<<<<<<< HEAD
 +          MaterialApp(
 +            home: Scaffold(
 +              body: Center(
 +                child: InteractiveViewer(
 +                  transformationController: transformationController,
 +                  scaleEnabled: false,
 +                  onInteractionStart: (ScaleStartDetails details) {
 +                    calledStart = true;
 +                  },
 +                  onInteractionUpdate: (ScaleUpdateDetails details) {
 +                    calledUpdate = true;
 +                  },
 +                  onInteractionEnd: (ScaleEndDetails details) {
 +                    calledEnd = true;
 +                  },
 +                  child: const SizedBox(width: 200.0, height: 200.0),
 +                ),
 +              ),
++=======
+           Center(
+             child: InteractiveViewer(
+               transformationController: transformationController,
+               scaleEnabled: false,
+               onInteractionStart: (ScaleStartDetails details) {
+                 calledStart = true;
+               },
+               onInteractionUpdate: (ScaleUpdateDetails details) {
+                 calledUpdate = true;
+               },
+               onInteractionEnd: (ScaleEndDetails details) {
+                 calledEnd = true;
+               },
+               child: sizedBox,
++>>>>>>> 91b2d41 (Reland #179643, only scroll hit-testable primary scroll views on status bar tap (#182391))
              ),
            ),
          );
```

</details>

### Issue Link:
What is the link to the issue this cherry-pick is addressing?

#182233
The bug was introduced in #179643 which was cherry picked to 3.41 beta (so the crash should exist on both current beta and stable).

### Impact Description:

On iOS, the app may crash when the user taps on the status bar to scroll to top, when the app has a primary scroll view that has never been laid out (for example, in a route that has been obstructed by other routes).

### Changelog Description:

- [flutter/182233](#182233) - Tapping on the status bar may crash the app on iOS when there's a primary scroll view that has never been laid out.

Additionally this patch only dispatches the "scroll to top" command to the topmost Scaffold which is generally better UX for most apps (this doesn't have to be in the changlog)

### Workaround:

No known easy workarounds.

### Risk:
What is the risk level of this cherry-pick?

### Test Coverage:
Are you confident that your fix is well-tested by automated tests?

### Validation Steps:
What are the steps to validate that this fix works?

I've manually tested the fix on iOS/iPadOS simulators. There are also unit tests.
ahmedsameha1 pushed a commit to ahmedsameha1/flutter that referenced this pull request Feb 27, 2026
…on status bar tap (flutter#182391)

Reland flutter#179643, with
[changes](flutter@3f7d345)
to fix flutter#182233. The reland
closes flutter#177992, closes
flutter#175606.

The crash happened because `Scaffold.handleStatusBar` (and subsequently
`ScrollController.animateTo`) is called on a scaffold that [has never
been laid
out](flutter#182233 (comment)).
Before flutter#179643, the iOS embedder sends a fake touch down pointer event
at `(0, 0)` to the framework, followed by a touch up pointer event at
the same location. That ensured only the foreground `Scaffold` could
claim the tap gesture and handle the status bar tap.

The fix tries to restore the previous behavior (where the iOS behavior
sends fake touches instead of platform messages) by performing a
hit-test at `(0, 0)` before trying to scroll the primary controller of a
`Scaffold` to the top, this should make sure that only the foreground
scaffold (in theory it's still possible for more than one Scaffold to
receive the hittest and handle the status bar tap event, but the
foreground scaffold should block the hit-test on background ones instead
of relying on gesture disambiguation).

## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: tests "flutter test", flutter_test, or one of our tests cp: beta cherry pick this pull request to beta release candidate branch cp: stable cherry pick this pull request to stable release candidate branch engine flutter/engine related. See also e: labels. f: cupertino flutter/packages/flutter/cupertino repository f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. platform-ios iOS applications specifically team-ios Owned by iOS platform team

Projects

None yet

4 participants