Dispose OverlayEntry in TooltipState. (#117291)

diff --git a/packages/flutter/lib/src/material/tooltip.dart b/packages/flutter/lib/src/material/tooltip.dart
index 12f80d6..fd0eda9 100644
--- a/packages/flutter/lib/src/material/tooltip.dart
+++ b/packages/flutter/lib/src/material/tooltip.dart
@@ -630,6 +630,7 @@
       _entry?.remove();
     }
     _isConcealed = false;
+    _entry?.dispose();
     _entry = null;
     if (_mouseIsConnected) {
       Tooltip._revealLastTooltip();
diff --git a/packages/flutter/test/foundation/leak_tracking.dart b/packages/flutter/test/foundation/leak_tracking.dart
new file mode 100644
index 0000000..caa2347
--- /dev/null
+++ b/packages/flutter/test/foundation/leak_tracking.dart
@@ -0,0 +1,72 @@
+// 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/foundation.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:leak_tracker/leak_tracker.dart';
+
+typedef LeaksObtainer = void Function(Leaks foundLeaks);
+
+/// Wrapper for [withLeakTracking] with Flutter specific functionality.
+///
+/// The method will fail if wrapped code contains memory leaks.
+///
+/// See details in documentation for `withLeakTracking` at
+/// https://github.com/dart-lang/leak_tracker/blob/main/lib/src/orchestration.dart#withLeakTracking
+///
+/// The Flutter related enhancements are:
+/// 1. Listens to [MemoryAllocations] events.
+/// 2. Uses `tester.runAsync` for leak detection if [tester] is provided.
+///
+/// If you use [testWidgets], pass [tester] to avoid async issues in leak processing.
+/// Pass null otherwise.
+///
+/// Pass [leaksObtainer] if you want to get leak information before
+/// the method failure.
+Future<void> withFlutterLeakTracking(
+  DartAsyncCallback callback, {
+  required WidgetTester? tester,
+  StackTraceCollectionConfig stackTraceCollectionConfig =
+      const StackTraceCollectionConfig(),
+  Duration? timeoutForFinalGarbageCollection,
+  LeaksObtainer? leaksObtainer,
+}) async {
+  // The method is copied (with improvements) from
+  // `package:leak_tracker/test/test_infra/flutter_helpers.dart`.
+
+  // The method is not combined with [testWidgets], because the combining will
+  // impact VSCode's ability to recognize tests.
+
+  // Leak tracker does not work for web platform.
+  if (kIsWeb) {
+    await callback();
+    return;
+  }
+
+  void flutterEventToLeakTracker(ObjectEvent event) {
+    return dispatchObjectEvent(event.toMap());
+  }
+
+  return TestAsyncUtils.guard<void>(() async {
+    MemoryAllocations.instance.addListener(flutterEventToLeakTracker);
+    final AsyncCodeRunner asyncCodeRunner = tester == null
+        ? (DartAsyncCallback action) async => action()
+        : (DartAsyncCallback action) async => tester.runAsync(action);
+    try {
+      final Leaks leaks = await withLeakTracking(
+        callback,
+        asyncCodeRunner: asyncCodeRunner,
+        stackTraceCollectionConfig: stackTraceCollectionConfig,
+        shouldThrowOnLeaks: false,
+        timeoutForFinalGarbageCollection: timeoutForFinalGarbageCollection,
+      );
+      if (leaksObtainer != null) {
+        leaksObtainer(leaks);
+      }
+      expect(leaks, isLeakFree);
+    } finally {
+      MemoryAllocations.instance.removeListener(flutterEventToLeakTracker);
+    }
+  });
+}
diff --git a/packages/flutter/test/material/tooltip_test.dart b/packages/flutter/test/material/tooltip_test.dart
index 0af5899..6c176eb 100644
--- a/packages/flutter/test/material/tooltip_test.dart
+++ b/packages/flutter/test/material/tooltip_test.dart
@@ -4,12 +4,14 @@
 
 import 'dart:ui';
 
+import 'package:flutter/foundation.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/rendering.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
 
+import '../foundation/leak_tracking.dart';
 import '../rendering/mock_canvas.dart';
 import '../widgets/semantics_tester.dart';
 import 'feedback_tester.dart';
@@ -649,38 +651,43 @@
   });
 
   testWidgets('Custom tooltip message textAlign', (WidgetTester tester) async {
-    Future<void> pumpTooltipWithTextAlign({TextAlign? textAlign}) async {
-      final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>();
-      await tester.pumpWidget(
-        MaterialApp(
-          home: Tooltip(
-            key: tooltipKey,
-            textAlign: textAlign,
-            message: tooltipText,
-            child: Container(
-              width: 100.0,
-              height: 100.0,
-              color: Colors.green[500],
+    await withFlutterLeakTracking(
+      () async {
+        Future<void> pumpTooltipWithTextAlign({TextAlign? textAlign}) async {
+          final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>();
+          await tester.pumpWidget(
+            MaterialApp(
+              home: Tooltip(
+                key: tooltipKey,
+                textAlign: textAlign,
+                message: tooltipText,
+                child: Container(
+                  width: 100.0,
+                  height: 100.0,
+                  color: Colors.green[500],
+                ),
+              ),
             ),
-          ),
-        ),
-      );
-      tooltipKey.currentState?.ensureTooltipVisible();
-      await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
-    }
+          );
+          tooltipKey.currentState?.ensureTooltipVisible();
+          await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
+        }
 
-    // Default value should be TextAlign.start
-    await pumpTooltipWithTextAlign();
-    TextAlign textAlign = tester.widget<Text>(find.text(tooltipText)).textAlign!;
-    expect(textAlign, TextAlign.start);
+        // Default value should be TextAlign.start
+        await pumpTooltipWithTextAlign();
+        TextAlign textAlign = tester.widget<Text>(find.text(tooltipText)).textAlign!;
+        expect(textAlign, TextAlign.start);
 
-    await pumpTooltipWithTextAlign(textAlign: TextAlign.center);
-    textAlign = tester.widget<Text>(find.text(tooltipText)).textAlign!;
-    expect(textAlign, TextAlign.center);
+        await pumpTooltipWithTextAlign(textAlign: TextAlign.center);
+        textAlign = tester.widget<Text>(find.text(tooltipText)).textAlign!;
+        expect(textAlign, TextAlign.center);
 
-    await pumpTooltipWithTextAlign(textAlign: TextAlign.end);
-    textAlign = tester.widget<Text>(find.text(tooltipText)).textAlign!;
-    expect(textAlign, TextAlign.end);
+        await pumpTooltipWithTextAlign(textAlign: TextAlign.end);
+        textAlign = tester.widget<Text>(find.text(tooltipText)).textAlign!;
+        expect(textAlign, TextAlign.end);
+      },
+      tester: tester,
+    );
   });
 
   testWidgets('Tooltip overlay respects ambient Directionality', (WidgetTester tester) async {
@@ -922,7 +929,7 @@
     final Finder tooltip = find.byType(Tooltip);
     expect(find.text(tooltipText), findsNothing);
 
-    await testGestureTap(tester, tooltip);
+    await _testGestureTap(tester, tooltip);
     expect(find.text(tooltipText), findsOneWidget);
 
     // Tooltip is dismissed after showDuration expired
@@ -1697,7 +1704,7 @@
     expect(semanticEvents, unorderedEquals(<dynamic>[
       <String, dynamic>{
         'type': 'longPress',
-        'nodeId': findDebugSemantics(object).id,
+        'nodeId': _findDebugSemantics(object).id,
         'data': <String, dynamic>{},
       },
       <String, dynamic>{
@@ -1790,7 +1797,7 @@
     final Finder tooltip = find.byType(Tooltip);
     expect(find.text(tooltipText), findsNothing);
 
-    await testGestureTap(tester, tooltip);
+    await _testGestureTap(tester, tooltip);
     expect(find.text(tooltipText), findsOneWidget);
   });
 
@@ -1800,10 +1807,10 @@
     final Finder tooltip = find.byType(Tooltip);
     expect(find.text(tooltipText), findsNothing);
 
-    await testGestureTap(tester, tooltip);
+    await _testGestureTap(tester, tooltip);
     expect(find.text(tooltipText), findsNothing);
 
-    await testGestureLongPress(tester, tooltip);
+    await _testGestureLongPress(tester, tooltip);
     expect(find.text(tooltipText), findsOneWidget);
   });
 
@@ -1813,7 +1820,7 @@
     final Finder tooltip = find.byType(Tooltip);
     expect(find.text(tooltipText), findsNothing);
 
-    await testGestureTap(tester, tooltip);
+    await _testGestureTap(tester, tooltip);
     expect(find.text(tooltipText), findsNothing);
   });
 
@@ -1823,10 +1830,10 @@
     final Finder tooltip = find.byType(Tooltip);
     expect(find.text(tooltipText), findsNothing);
 
-    await testGestureTap(tester, tooltip);
+    await _testGestureTap(tester, tooltip);
     expect(find.text(tooltipText), findsNothing);
 
-    await testGestureLongPress(tester, tooltip);
+    await _testGestureLongPress(tester, tooltip);
     expect(find.text(tooltipText), findsNothing);
   });
 
@@ -1836,13 +1843,13 @@
 
     await setWidgetForTooltipMode(tester, TooltipTriggerMode.longPress, onTriggered: onTriggered);
     Finder tooltip = find.byType(Tooltip);
-    await testGestureLongPress(tester, tooltip);
+    await _testGestureLongPress(tester, tooltip);
     expect(onTriggeredCalled, true);
 
     onTriggeredCalled = false;
     await setWidgetForTooltipMode(tester, TooltipTriggerMode.tap, onTriggered: onTriggered);
     tooltip = find.byType(Tooltip);
-    await testGestureTap(tester, tooltip);
+    await _testGestureTap(tester, tooltip);
     expect(onTriggeredCalled, true);
   });
 
@@ -1925,7 +1932,7 @@
   );
 }
 
-Future<void> testGestureLongPress(WidgetTester tester, Finder tooltip) async {
+Future<void> _testGestureLongPress(WidgetTester tester, Finder tooltip) async {
   final TestGesture gestureLongPress = await tester.startGesture(tester.getCenter(tooltip));
   await tester.pump();
   await tester.pump(kLongPressTimeout);
@@ -1933,14 +1940,14 @@
   await tester.pump();
 }
 
-Future<void> testGestureTap(WidgetTester tester, Finder tooltip) async {
+Future<void> _testGestureTap(WidgetTester tester, Finder tooltip) async {
   await tester.tap(tooltip);
   await tester.pump(const Duration(milliseconds: 10));
 }
 
-SemanticsNode findDebugSemantics(RenderObject object) {
+SemanticsNode _findDebugSemantics(RenderObject object) {
   if (object.debugSemantics != null) {
     return object.debugSemantics!;
   }
-  return findDebugSemantics(object.parent! as RenderObject);
+  return _findDebugSemantics(object.parent! as RenderObject);
 }