Skip to content

Commit bc472b4

Browse files
[CP-stable] Fix BaseTapAndDragGestureRecognizer should reset drag state after losing gesture arena (#151989) (#153935)
Fixes an issue where Flutter TextField may crash on iOS after a horizontal drag that does not meet the drag threshold to win the gesture arena, leaving the TextField in a weird state that does not accept tap gestures. Fixes #153939 Cherry-pick of: flutter/flutter#151989 Related issue: #153458
1 parent bc7fd79 commit bc472b4

File tree

2 files changed

+73
-1
lines changed

2 files changed

+73
-1
lines changed

packages/flutter/lib/src/gestures/tap_and_drag.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,6 +1065,7 @@ sealed class BaseTapAndDragGestureRecognizer extends OneSequenceGestureRecognize
10651065
}
10661066

10671067
_stopDeadlineTimer();
1068+
_start = null;
10681069
_dragState = _DragState.ready;
10691070
_pastSlopTolerance = false;
10701071
}

packages/flutter/test/gestures/tap_and_drag_test.dart

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,12 @@ void main() {
4545
addTearDown(tapAndDrag.dispose);
4646
}
4747

48-
void setUpTapAndHorizontalDragGestureRecognizer() {
48+
void setUpTapAndHorizontalDragGestureRecognizer({
49+
bool eagerVictoryOnDrag = true, // This is the default for [BaseTapAndDragGestureRecognizer].
50+
}) {
4951
tapAndDrag = TapAndHorizontalDragGestureRecognizer()
5052
..dragStartBehavior = DragStartBehavior.down
53+
..eagerVictoryOnDrag = eagerVictoryOnDrag
5154
..maxConsecutiveTap = 3
5255
..onTapDown = (TapDragDownDetails details) {
5356
events.add('down#${details.consecutiveTapCount}');
@@ -692,6 +695,74 @@ void main() {
692695
);
693696
});
694697

698+
testGesture('Drag state is properly reset after losing GestureArena', (GestureTester tester) {
699+
setUpTapAndHorizontalDragGestureRecognizer(eagerVictoryOnDrag: false);
700+
final HorizontalDragGestureRecognizer horizontalDrag = HorizontalDragGestureRecognizer()
701+
..onStart = (DragStartDetails details) {
702+
events.add('basichorizontalstart');
703+
}
704+
..onUpdate = (DragUpdateDetails details) {
705+
events.add('basichorizontalupdate');
706+
}
707+
..onEnd = (DragEndDetails details) {
708+
events.add('basichorizontalend');
709+
}
710+
..onCancel = () {
711+
events.add('basichorizontalcancel');
712+
};
713+
addTearDown(horizontalDrag.dispose);
714+
715+
final LongPressGestureRecognizer longpress = LongPressGestureRecognizer()
716+
..onLongPressStart = (LongPressStartDetails details) {
717+
events.add('longpressstart');
718+
}
719+
..onLongPressMoveUpdate = (LongPressMoveUpdateDetails details) {
720+
events.add('longpressmoveupdate');
721+
}
722+
..onLongPressEnd = (LongPressEndDetails details) {
723+
events.add('longpressend');
724+
}
725+
..onLongPressCancel = () {
726+
events.add('longpresscancel');
727+
};
728+
addTearDown(longpress.dispose);
729+
730+
FlutterErrorDetails? errorDetails;
731+
final FlutterExceptionHandler? oldHandler = FlutterError.onError;
732+
FlutterError.onError = (FlutterErrorDetails details) {
733+
errorDetails = details;
734+
};
735+
736+
final TestPointer pointer = TestPointer(5);
737+
final PointerDownEvent downB = pointer.down(const Offset(10.0, 10.0));
738+
// When competing against another [DragGestureRecognizer], the [TapAndPanGestureRecognizer]
739+
// will only win when it is the last recognizer in the arena.
740+
tapAndDrag.addPointer(downB);
741+
horizontalDrag.addPointer(downB);
742+
longpress.addPointer(downB);
743+
tester.closeArena(5);
744+
tester.route(downB);
745+
tester.route(pointer.move(const Offset(28.1, 10.0)));
746+
tester.route(pointer.up());
747+
expect(
748+
events,
749+
<String>[
750+
'basichorizontalstart',
751+
'basichorizontalend',
752+
],
753+
);
754+
755+
final PointerDownEvent downC = pointer.down(const Offset(10.0, 10.0));
756+
tapAndDrag.addPointer(downC);
757+
horizontalDrag.addPointer(downC);
758+
longpress.addPointer(downC);
759+
tester.closeArena(5);
760+
tester.route(downC);
761+
tester.route(pointer.up());
762+
FlutterError.onError = oldHandler;
763+
expect(errorDetails, isNull);
764+
});
765+
695766
testGesture('Beats LongPressGestureRecognizer on a consecutive tap greater than one', (GestureTester tester) {
696767
setUpTapAndPanGestureRecognizer();
697768

0 commit comments

Comments
 (0)