Repository navigation
[web] Cancel touch pointers WebKit abandons mid-gesture - #189608
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements a safety mechanism for iOS WebKit to handle abandoned touch pointers that fail to dispatch 'pointerup' or 'pointercancel' events. It introduces a tracking set for active touch devices and a reconciliation method that identifies and cancels stale pointers by comparing them against the browser's 'touches' list. The changes include updates to 'dom.dart' for touch list access, 'pointer_binding.dart' for tracking and cancellation logic, and comprehensive regression tests in 'pointer_binding_test.dart'. The reviewer suggested improving the robustness of the tests by explicitly asserting both 'cancel' and 'remove' events.
| void _removePointerIfUnhoverable(DomPointerEvent event) { | ||
| if (event.pointerType == 'touch') { | ||
| _sanitizers.remove(event.pointerId); | ||
| _downTouchDevices.remove(_getPointerId(event)); |
There was a problem hiding this comment.
Should getPointerId(event) just be event.pointerId like the line above?
There was a problem hiding this comment.
Thinking about it more maybe the line above should become _sanitizers.remove(_getPointerId(event)).
There was a problem hiding this comment.
Good catch. updated.
|
|
||
| // The abandoned touch is reconciled while debouncing is active. Its cancel | ||
| // must bypass the queue and reach the framework immediately. | ||
| rootElement.dispatchEvent(_createTouchEvent('touchend', <int>[3], remaining: <int>[300])); |
There was a problem hiding this comment.
I don't think we define a touch for device 300 in this test.
Maybe the above event
tappable.dispatchEvent(context.primaryDown(clientX: 300, clientY: 300));should be changed, defining a device id and making it a touch event.
tappable.dispatchEvent(
context.multiTouchDown(const <_TouchDetails>[
_TouchDetails(pointer: 5, clientX: 300, clientY: 300),
]).single,
);and this line can become
rootElement.dispatchEvent(_createTouchEvent('touchend', <int>[3], remaining: <int>[5]));There was a problem hiding this comment.
Good catch. updated.
| /// For synthetic events that must not be queued or dropped by an in-progress | ||
| /// debounce, such as cancels that repair a pointer the browser abandoned. | ||
| void sendImmediately(List<ui.PointerData> data) { | ||
| _sendToFramework(null, data); |
There was a problem hiding this comment.
Should we flush the events currently in the queue during debouncing here before forwarding to framework?
There was a problem hiding this comment.
In the debouncing case, if there is a down event queued and we immediately dispatch the cancel/touchend event to the framework, then the queued down event may come after that and put the framework in a stuck state again.
There was a problem hiding this comment.
Good catch. updated.
Renzo-Olivares
left a comment
There was a problem hiding this comment.
Overall the approach looks good to me, just had a few small comments.
Renzo-Olivares
left a comment
There was a problem hiding this comment.
LGTM, thanks for the fix!
|
autosubmit label was removed for flutter/flutter/189608, because - The status or check suite Dashboard Checks has failed. Please fix the issues identified (or deflake) before re-applying this label. |
flutter/flutter@b65f4d9...e292197 2026-07-25 [email protected] Roll Skia from 2c014dc494f1 to 62442d6cf0ec (1 revision) (flutter/flutter#190022) 2026-07-25 [email protected] Roll Skia from d8d20eef4dbd to 2c014dc494f1 (1 revision) (flutter/flutter#190020) 2026-07-25 [email protected] [Impeller] Add 2D texture array support (kTexture2DArray) (flutter/flutter#188778) 2026-07-24 [email protected] Bumped Engine AGP Dependencies to 9.1.0 for 3.50 (flutter/flutter#189417) 2026-07-24 [email protected] Roll Dart SDK from 153abd9a8296 to 28e63ac22d8d (2 revisions) (flutter/flutter#190013) 2026-07-24 [email protected] ci: add mac verify binaries workflow (flutter/flutter#190010) 2026-07-24 [email protected] Roll Skia from f2f9b52fe1ee to d8d20eef4dbd (21 revisions) (flutter/flutter#190011) 2026-07-24 [email protected] fix(ci): quote labels to fix yaml mapping error (flutter/flutter#190002) 2026-07-24 [email protected] Update contributing guidelines for commit message format (flutter/flutter#189922) 2026-07-24 [email protected] Migrate a11y_assessments to uiscene (flutter/flutter#189960) 2026-07-24 [email protected] docs(contributing): add git worktree and rebase workflow guide (flutter/flutter#189992) 2026-07-24 [email protected] Roll Dart SDK from e3fc57eae9eb to 153abd9a8296 (3 revisions) (flutter/flutter#189987) 2026-07-24 [email protected] Rename RegularWindow* to Window* in the windowing API + rename sizedToContent to shrinkWrap in the windowing API (flutter/flutter#189363) 2026-07-24 [email protected] Remove ancient `tools:tools` dep from `android_sdk` bundle (flutter/flutter#189962) 2026-07-24 [email protected] Reduce the number of gtest-parallel workers when running Impeller tests on Mac Minis used by CI (flutter/flutter#189813) 2026-07-24 [email protected] Roll Packages from 8260a1e to 6969329 (21 revisions) (flutter/flutter#189983) 2026-07-24 [email protected] Roll Fuchsia Linux SDK from 9org0yL3yZkp80x5S... to Cdb1RGKCqpcz9z4cl... (flutter/flutter#189975) 2026-07-24 [email protected] Roll Skia from 6e9c4687c001 to f2f9b52fe1ee (3 revisions) (flutter/flutter#189959) 2026-07-24 [email protected] [web] Cancel touch pointers WebKit abandons mid-gesture (flutter/flutter#189608) 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: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
This pull request cherry-picks #189608 into the beta release branch (flutter-3.47-candidate.0) so the fix ships in the next stable release. ### Issue Link: #188781 ### Impact Description: P0 regression. On iOS 27, which affects every browser since they all run WebKit, a text field in a Flutter web app can become permanently unresponsive after a common gesture. When the user long-press-drags the selection caret and lifts, WebKit abandons the touch without delivering `pointerup` or `pointercancel`, leaving the framework holding a pointer that never lifts. This wedges the tap-and-drag gesture recognizer, so every later tap is silently dropped: the field never regains focus and the keyboard never returns, and the user can no longer type. It breaks text input in shipping production web apps. iOS 26 is unaffected. ### Changelog Description: [flutter/188781] When dragging the text selection caret on iOS 27 web browsers, text fields could become permanently unresponsive to taps and never regain focus; they now recover. ### Workaround: No in-app workaround. Reloading the page temporarily restores the field, but it breaks again the next time the gesture occurs. ### Risk: The change is small and strictly gated to iOS WebKit. It only cancels touch pointers the browser has already dropped from its `touches` list, so it cannot affect other platforms or normal pointer handling. ### Test Coverage: The fix adds 341 lines of engine unit tests in `pointer_binding_test.dart` covering the touch-cancel logic. ### Validation Steps: 1. Open a Flutter web app containing a TextField on an iOS 27 device, any browser. 2. Tap the field to focus it and type some text. 3. Long-press the selection caret, drag it, then lift your finger. 4. Tap the field again, and repeat a few times. 5. Before the fix the field becomes permanently dead: taps do nothing, no focus, no keyboard. After the fix the field keeps responding and recovers. Demo before: https://flutter-demo-47-before.web.app Demo after: https://flutter-demo-47-after.web.app
|
Failed to create CP due to merge conflicts. |
Fixes #188781
Problem
On iOS 27, WebKit stops dispatching pointer events for a touch it promotes to a native gesture, such as dragging the caret in a text field, and delivers neither
pointerupnorpointercancel. The framework is left holding a pointer that never lifts, which wedgesBaseTapAndDragGestureRecognizerso every later tap is silently dropped: the field never regains focus and the keyboard never returns. Affects every iOS 27 browser, since they all run WebKit; iOS 26 is unaffected.Fix
_PointerAdaptertracks which touch devices are down and, ontouchendandtouchcancel, cancels any the browser no longer lists intouches. Gated to iOSWebKit.Demo
Before: https://flutter-demo-47-before.web.app
After: https://flutter-demo-47-after.web.app
Repro steps:
type in the field, long-press the caret and drag it, lift, then tap the field.
Before, the field is permanently dead after a few tries.
After, it recovers.