From c8aa37d1ea65b18ad8359af8e40afa3dc477a264 Mon Sep 17 00:00:00 2001 From: Dan Field Date: Tue, 28 Feb 2023 12:14:33 -0800 Subject: [PATCH] Fix for #112403 and b/249091367 (#121615) Fix monodrag gestures for #112403 and b/249091367 --- .../flutter/lib/src/gestures/monodrag.dart | 22 ++++++++++++++-- .../flutter/test/gestures/monodrag_test.dart | 25 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/gestures/monodrag.dart b/packages/flutter/lib/src/gestures/monodrag.dart index 08082c8651..f7d868ca9b 100644 --- a/packages/flutter/lib/src/gestures/monodrag.dart +++ b/packages/flutter/lib/src/gestures/monodrag.dart @@ -222,6 +222,24 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { late OffsetPair _initialPosition; late OffsetPair _pendingDragOffset; Duration? _lastPendingEventTimestamp; + + /// When asserts are enabled, returns the last tracked pending event timestamp + /// for this recognizer. + /// + /// Otherwise, returns null. + /// + /// This getter is intended for use in framework unit tests. Applications must + /// not depend on its value. + @visibleForTesting + Duration? get debugLastPendingEventTimestamp { + Duration? lastPendingEventTimestamp; + assert(() { + lastPendingEventTimestamp = _lastPendingEventTimestamp; + return true; + }()); + return lastPendingEventTimestamp; + } + // The buttons sent by `PointerDownEvent`. If a `PointerMoveEvent` comes with a // different set of buttons, the gesture is canceled. int? _initialButtons; @@ -363,7 +381,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { if (_state != _DragState.accepted) { _state = _DragState.accepted; final OffsetPair delta = _pendingDragOffset; - final Duration timestamp = _lastPendingEventTimestamp!; + final Duration? timestamp = _lastPendingEventTimestamp; final Matrix4? transform = _lastTransform; final Offset localUpdateDelta; switch (dragStartBehavior) { @@ -449,7 +467,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { } } - void _checkStart(Duration timestamp, int pointer) { + void _checkStart(Duration? timestamp, int pointer) { if (onStart != null) { final DragStartDetails details = DragStartDetails( sourceTimeStamp: timestamp, diff --git a/packages/flutter/test/gestures/monodrag_test.dart b/packages/flutter/test/gestures/monodrag_test.dart index eaf5b6d29a..584af6f88b 100644 --- a/packages/flutter/test/gestures/monodrag_test.dart +++ b/packages/flutter/test/gestures/monodrag_test.dart @@ -10,6 +10,31 @@ import 'gesture_tester.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); + test('acceptGesture tolerates a null lastPendingEventTimestamp', () { + // Regression test for https://github.com/flutter/flutter/issues/112403 + // and b/249091367 + final DragGestureRecognizer recognizer = VerticalDragGestureRecognizer(); + const PointerDownEvent event = PointerDownEvent(timeStamp: Duration(days: 10)); + + expect(recognizer.debugLastPendingEventTimestamp, null); + + recognizer.addAllowedPointer(event); + expect(recognizer.debugLastPendingEventTimestamp, event.timeStamp); + + // Normal case: acceptGesture called and we have a last timestamp set. + recognizer.acceptGesture(event.pointer); + expect(recognizer.debugLastPendingEventTimestamp, null); + + // Reject the gesture to reset state and allow accepting it again. + recognizer.rejectGesture(event.pointer); + expect(recognizer.debugLastPendingEventTimestamp, null); + + // Not entirely clear how this can happen, but the bugs mentioned above show + // we can end up in this state empircally. + recognizer.acceptGesture(event.pointer); + expect(recognizer.debugLastPendingEventTimestamp, null); + }); + 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.