From 839bd6317fb9eb5d7fe6d7441836938dff1d5906 Mon Sep 17 00:00:00 2001 From: xubaolin Date: Fri, 25 Feb 2022 19:21:19 +0800 Subject: [PATCH] Do not crash if dispatch the mouse events before the tooltip overlay detached (#97268) --- .../flutter/lib/src/material/tooltip.dart | 10 +++- .../flutter/test/material/tooltip_test.dart | 56 +++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/packages/flutter/lib/src/material/tooltip.dart b/packages/flutter/lib/src/material/tooltip.dart index 94fc688442..ccafd50a0d 100644 --- a/packages/flutter/lib/src/material/tooltip.dart +++ b/packages/flutter/lib/src/material/tooltip.dart @@ -522,12 +522,16 @@ class _TooltipState extends State with SingleTickerProviderStateMixin { static final Set<_TooltipState> _mouseIn = <_TooltipState>{}; void _handleMouseEnter() { - _showTooltip(); + if (mounted) { + _showTooltip(); + } } void _handleMouseExit({bool immediately = false}) { - // If the tip is currently covered, we can just remove it without waiting. - _dismissTooltip(immediately: _isConcealed || immediately); + if (mounted) { + // If the tip is currently covered, we can just remove it without waiting. + _dismissTooltip(immediately: _isConcealed || immediately); + } } void _createNewEntry() { diff --git a/packages/flutter/test/material/tooltip_test.dart b/packages/flutter/test/material/tooltip_test.dart index 8226eebe9d..151ae0d49e 100644 --- a/packages/flutter/test/material/tooltip_test.dart +++ b/packages/flutter/test/material/tooltip_test.dart @@ -863,6 +863,62 @@ void main() { await gesture.up(); }); + testWidgets('Dispatch the mouse events before tip overlay detached', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/96890 + const Duration waitDuration = Duration.zero; + TestGesture? gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + addTearDown(() async { + if (gesture != null) + return gesture.removePointer(); + }); + await gesture.addPointer(); + await gesture.moveTo(const Offset(1.0, 1.0)); + await tester.pump(); + await gesture.moveTo(Offset.zero); + + await tester.pumpWidget( + const MaterialApp( + home: Center( + child: Tooltip( + message: tooltipText, + waitDuration: waitDuration, + child: SizedBox( + width: 100.0, + height: 100.0, + ), + ), + ), + ), + ); + + // Trigger the tip overlay. + final Finder tooltip = find.byType(Tooltip); + await gesture.moveTo(tester.getCenter(tooltip)); + await tester.pump(); + // Wait for it to appear. + await tester.pump(waitDuration); + + // Remove the `Tooltip` widget. + await tester.pumpWidget( + const MaterialApp( + home: Center( + child: SizedBox.shrink(), + ), + ), + ); + + // The tooltip overlay still on the tree and it will removed in the next frame. + + // Dispatch the mouse in and out events before the overlay detached. + await gesture.moveTo(tester.getCenter(find.text(tooltipText))); + await gesture.moveTo(Offset.zero); + await tester.pumpAndSettle(); + + // Go without crashes. + await gesture.removePointer(); + gesture = null; + }); + testWidgets('Tooltip shows/hides when hovered', (WidgetTester tester) async { const Duration waitDuration = Duration.zero; TestGesture? gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);