From 6afbb25a487aaa16409722602fd4be23feeb0193 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Tue, 16 Feb 2021 10:26:03 -0800 Subject: [PATCH] Reject unaccepted pointers in Drag recognizer (#75943) --- .../flutter/lib/src/gestures/monodrag.dart | 58 ++++++++++--------- .../flutter/test/gestures/monodrag_test.dart | 56 ++++++++++++++++++ 2 files changed, 88 insertions(+), 26 deletions(-) create mode 100644 packages/flutter/test/gestures/monodrag_test.dart diff --git a/packages/flutter/lib/src/gestures/monodrag.dart b/packages/flutter/lib/src/gestures/monodrag.dart index 82ab77ddb91..15eaf304ae4 100644 --- a/packages/flutter/lib/src/gestures/monodrag.dart +++ b/packages/flutter/lib/src/gestures/monodrag.dart @@ -309,15 +309,16 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { } } if (event is PointerUpEvent || event is PointerCancelEvent) { - _giveUpPointer( - event.pointer, - reject: event is PointerCancelEvent || _state ==_DragState.possible, - ); + _giveUpPointer(event.pointer); } } + final Set _acceptedActivePointers = {}; + @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 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { _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('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('onStart', () => onStart!(details)); + } } void _checkUpdate({ @@ -420,15 +425,16 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { 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('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 00000000000..3ca3b0712d6 --- /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) { } +}