Reject unaccepted pointers in Drag recognizer (#75943)
diff --git a/packages/flutter/lib/src/gestures/monodrag.dart b/packages/flutter/lib/src/gestures/monodrag.dart
index 82ab77d..15eaf30 100644
--- a/packages/flutter/lib/src/gestures/monodrag.dart
+++ b/packages/flutter/lib/src/gestures/monodrag.dart
@@ -309,15 +309,16 @@
}
}
if (event is PointerUpEvent || event is PointerCancelEvent) {
- _giveUpPointer(
- event.pointer,
- reject: event is PointerCancelEvent || _state ==_DragState.possible,
- );
+ _giveUpPointer(event.pointer);
}
}
+ final Set<int> _acceptedActivePointers = <int>{};
+
@override
void acceptGesture(int pointer) {
+ assert(!_acceptedActivePointers.contains(pointer));
+ _acceptedActivePointers.add(pointer);
if (_state != _DragState.accepted) {
_state = _DragState.accepted;
final OffsetPair delta = _pendingDragOffset;
@@ -384,32 +385,36 @@
_state = _DragState.ready;
}
- void _giveUpPointer(int pointer, {bool reject = true}) {
+ void _giveUpPointer(int pointer) {
stopTrackingPointer(pointer);
- if (reject)
+ // If we never accepted the pointer, we reject it since we are no longer
+ // interested in winning the gesture arena for it.
+ if (!_acceptedActivePointers.remove(pointer))
resolvePointer(pointer, GestureDisposition.rejected);
}
void _checkDown() {
assert(_initialButtons == kPrimaryButton);
- final DragDownDetails details = DragDownDetails(
- globalPosition: _initialPosition.global,
- localPosition: _initialPosition.local,
- );
- if (onDown != null)
+ if (onDown != null) {
+ final DragDownDetails details = DragDownDetails(
+ globalPosition: _initialPosition.global,
+ localPosition: _initialPosition.local,
+ );
invokeCallback<void>('onDown', () => onDown!(details));
+ }
}
void _checkStart(Duration timestamp, int pointer) {
assert(_initialButtons == kPrimaryButton);
- final DragStartDetails details = DragStartDetails(
- sourceTimeStamp: timestamp,
- globalPosition: _initialPosition.global,
- localPosition: _initialPosition.local,
- kind: getKindForPointer(pointer),
- );
- if (onStart != null)
+ if (onStart != null) {
+ final DragStartDetails details = DragStartDetails(
+ sourceTimeStamp: timestamp,
+ globalPosition: _initialPosition.global,
+ localPosition: _initialPosition.local,
+ kind: getKindForPointer(pointer),
+ );
invokeCallback<void>('onStart', () => onStart!(details));
+ }
}
void _checkUpdate({
@@ -420,15 +425,16 @@
Offset? localPosition,
}) {
assert(_initialButtons == kPrimaryButton);
- final DragUpdateDetails details = DragUpdateDetails(
- sourceTimeStamp: sourceTimeStamp,
- delta: delta,
- primaryDelta: primaryDelta,
- globalPosition: globalPosition,
- localPosition: localPosition,
- );
- if (onUpdate != null)
+ if (onUpdate != null) {
+ final DragUpdateDetails details = DragUpdateDetails(
+ sourceTimeStamp: sourceTimeStamp,
+ delta: delta,
+ primaryDelta: primaryDelta,
+ globalPosition: globalPosition,
+ localPosition: localPosition,
+ );
invokeCallback<void>('onUpdate', () => onUpdate!(details));
+ }
}
void _checkEnd(int pointer) {
diff --git a/packages/flutter/test/gestures/monodrag_test.dart b/packages/flutter/test/gestures/monodrag_test.dart
new file mode 100644
index 0000000..3ca3b07
--- /dev/null
+++ b/packages/flutter/test/gestures/monodrag_test.dart
@@ -0,0 +1,56 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/gestures.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'gesture_tester.dart';
+
+void main() {
+ setUp(ensureGestureBinding);
+
+ testGesture('do not crash on up event for a pending pointer after winning arena for another pointer', (GestureTester tester) {
+ // Regression test for https://github.com/flutter/flutter/issues/75061.
+
+ final VerticalDragGestureRecognizer v = VerticalDragGestureRecognizer()
+ ..onStart = (_) { };
+ final HorizontalDragGestureRecognizer h = HorizontalDragGestureRecognizer()
+ ..onStart = (_) { };
+
+ const PointerDownEvent down90 = PointerDownEvent(
+ pointer: 90,
+ position: Offset(10.0, 10.0),
+ );
+
+ const PointerUpEvent up90 = PointerUpEvent(
+ pointer: 90,
+ position: Offset(10.0, 10.0),
+ );
+
+ const PointerDownEvent down91 = PointerDownEvent(
+ pointer: 91,
+ position: Offset(20.0, 20.0),
+ );
+
+ const PointerUpEvent up91 = PointerUpEvent(
+ pointer: 91,
+ position: Offset(20.0, 20.0),
+ );
+
+ v.addPointer(down90);
+ GestureBinding.instance!.gestureArena.close(90);
+ h.addPointer(down91);
+ v.addPointer(down91);
+ GestureBinding.instance!.gestureArena.close(91);
+ tester.async.flushMicrotasks();
+
+ GestureBinding.instance!.handleEvent(up90, HitTestEntry(MockHitTestTarget()));
+ GestureBinding.instance!.handleEvent(up91, HitTestEntry(MockHitTestTarget()));
+ });
+}
+
+class MockHitTestTarget implements HitTestTarget {
+ @override
+ void handleEvent(PointerEvent event, HitTestEntry entry) { }
+}