Skip to content

Commit

Permalink
Fix tap/drag callbacks firing when TapAndDragGestureRecognizer has no…
Browse files Browse the repository at this point in the history
…t won the arena (#118342)

* Prevent drag and tap from accepting when a tap down exceeds the recognizers deadline but the recognizer has not won the arena

* Add test

* make analyzer happy

Co-authored-by: Renzo Olivares <roliv@google.com>
  • Loading branch information
Renzo-Olivares and Renzo Olivares committed Jan 13, 2023
1 parent 3e00520 commit bd938b0
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 4 deletions.
15 changes: 11 additions & 4 deletions packages/flutter/lib/src/widgets/tap_and_drag_gestures.dart
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,7 @@ class TapAndDragGestureRecognizer extends OneSequenceGestureRecognizer with _Tap
// Tap related state.
bool _pastSlopTolerance = false;
bool _sentTapDown = false;
bool _wonArenaForPrimaryPointer = false;

// Primary pointer being tracked by this recognizer.
int? _primaryPointer;
Expand Down Expand Up @@ -934,7 +935,7 @@ class TapAndDragGestureRecognizer extends OneSequenceGestureRecognizer with _Tap
void handleNonAllowedPointer(PointerDownEvent event) {
// There can be multiple drags simultaneously. Their effects are combined.
if (event.buttons != kPrimaryButton) {
if (!_sentTapDown) {
if (!_wonArenaForPrimaryPointer) {
super.handleNonAllowedPointer(event);
}
}
Expand All @@ -956,6 +957,8 @@ class TapAndDragGestureRecognizer extends OneSequenceGestureRecognizer with _Tap
_checkTapDown(currentDown!);
}

_wonArenaForPrimaryPointer = true;

if (_start != null) {
_acceptDrag(_start!);
}
Expand All @@ -976,7 +979,7 @@ class TapAndDragGestureRecognizer extends OneSequenceGestureRecognizer with _Tap
case _DragState.possible:
if (_pastSlopTolerance) {
// This means the pointer was not accepted as a tap.
if (_sentTapDown) {
if (_wonArenaForPrimaryPointer) {
// If the recognizer has already won the arena for the primary pointer being tracked
// but the pointer has exceeded the tap tolerance, then the pointer is accepted as a
// drag gesture.
Expand Down Expand Up @@ -1043,7 +1046,7 @@ class TapAndDragGestureRecognizer extends OneSequenceGestureRecognizer with _Tap

// This can occur when the recognizer is accepted before a [PointerMoveEvent] has been
// received that moves the pointer a sufficient global distance to be considered a drag.
if (_start != null && _sentTapDown) {
if (_start != null) {
_acceptDrag(_start!);
}
}
Expand Down Expand Up @@ -1085,6 +1088,9 @@ class TapAndDragGestureRecognizer extends OneSequenceGestureRecognizer with _Tap
String get debugDescription => 'tap_and_drag';

void _acceptDrag(PointerEvent event) {
if (!_wonArenaForPrimaryPointer) {
return;
}
_dragState = _DragState.accepted;
if (dragStartBehavior == DragStartBehavior.start) {
_initialPosition = _initialPosition + OffsetPair(global: event.delta, local: event.localDelta);
Expand Down Expand Up @@ -1138,7 +1144,7 @@ class TapAndDragGestureRecognizer extends OneSequenceGestureRecognizer with _Tap
}

void _checkTapUp(PointerUpEvent event) {
if (!_sentTapDown) {
if (!_wonArenaForPrimaryPointer) {
return;
}

Expand Down Expand Up @@ -1265,6 +1271,7 @@ class TapAndDragGestureRecognizer extends OneSequenceGestureRecognizer with _Tap

void _resetTaps() {
_sentTapDown = false;
_wonArenaForPrimaryPointer = false;
_primaryPointer = null;
}

Expand Down
51 changes: 51 additions & 0 deletions packages/flutter/test/material/text_field_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12452,6 +12452,57 @@ void main() {
});
});

testWidgets('TextField does not leak touch events when deadline has exceeded', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/118340.
int textFieldTapCount = 0;
int prefixTapCount = 0;
int suffixTapCount = 0;

await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: TextField(
onTap: () { textFieldTapCount += 1; },
decoration: InputDecoration(
labelText: 'Label',
prefix: ElevatedButton(
onPressed: () { prefixTapCount += 1; },
child: const Text('prefix'),
),
suffix: ElevatedButton(
onPressed: () { suffixTapCount += 1; },
child: const Text('suffix'),
),
),
),
),
),
);

TestGesture gesture =
await tester.startGesture(
tester.getRect(find.text('prefix')).center,
pointer: 7,
kind: PointerDeviceKind.mouse,
);
await tester.pumpAndSettle();
await gesture.up();
expect(textFieldTapCount, 0);
expect(prefixTapCount, 1);
expect(suffixTapCount, 0);

gesture = await tester.startGesture(
tester.getRect(find.text('suffix')).center,
pointer: 7,
kind: PointerDeviceKind.mouse,
);
await tester.pumpAndSettle();
await gesture.up();
expect(textFieldTapCount, 0);
expect(prefixTapCount, 1);
expect(suffixTapCount, 1);
});

testWidgets('prefix/suffix buttons do not leak touch events', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/39376.

Expand Down

0 comments on commit bd938b0

Please sign in to comment.