Instrument RestorationBucket, _RouteEntry and DisposableBuildContext for leak tracking. (#137477)

diff --git a/packages/flutter/lib/src/services/restoration.dart b/packages/flutter/lib/src/services/restoration.dart
index b188ec1..a5c37c5 100644
--- a/packages/flutter/lib/src/services/restoration.dart
+++ b/packages/flutter/lib/src/services/restoration.dart
@@ -420,6 +420,12 @@
     _doSerialization();
     assert(!_serializationScheduled);
   }
+
+  @override
+  void dispose() {
+    _rootBucket?.dispose();
+    super.dispose();
+  }
 }
 
 /// A [RestorationBucket] holds pieces of the restoration data that a part of
@@ -507,6 +513,9 @@
       _debugOwner = debugOwner;
       return true;
     }());
+    if (kFlutterMemoryAllocationsEnabled) {
+      _maybeDispatchObjectCreation();
+    }
   }
 
   /// Creates the root [RestorationBucket] for the provided restoration
@@ -540,6 +549,9 @@
       _debugOwner = manager;
       return true;
     }());
+    if (kFlutterMemoryAllocationsEnabled) {
+      _maybeDispatchObjectCreation();
+    }
   }
 
   /// Creates a child bucket initialized with the data that the provided
@@ -563,6 +575,9 @@
       _debugOwner = debugOwner;
       return true;
     }());
+    if (kFlutterMemoryAllocationsEnabled) {
+      _maybeDispatchObjectCreation();
+    }
   }
 
   static const String _childrenMapKey = 'c';
@@ -934,6 +949,19 @@
     _parent?._addChildData(this);
   }
 
+  // TODO(polina-c): stop duplicating code across disposables
+  // https://github.com/flutter/flutter/issues/137435
+  /// Dispatches event of object creation to [MemoryAllocations.instance].
+  void _maybeDispatchObjectCreation() {
+    if (kFlutterMemoryAllocationsEnabled) {
+      MemoryAllocations.instance.dispatchObjectCreated(
+        library: 'package:flutter/services.dart',
+        className: '$RestorationBucket',
+        object: this,
+      );
+    }
+  }
+
   /// Deletes the bucket and all the data stored in it from the bucket
   /// hierarchy.
   ///
@@ -948,6 +976,11 @@
   /// This method must only be called by the object's owner.
   void dispose() {
     assert(_debugAssertNotDisposed());
+    // TODO(polina-c): stop duplicating code across disposables
+    // https://github.com/flutter/flutter/issues/137435
+    if (kFlutterMemoryAllocationsEnabled) {
+      MemoryAllocations.instance.dispatchObjectDisposed(object: this);
+    }
     _visitChildren(_dropChild, concurrentModification: true);
     _claimedChildren.clear();
     _childrenToAdd.clear();
diff --git a/packages/flutter/lib/src/widgets/disposable_build_context.dart b/packages/flutter/lib/src/widgets/disposable_build_context.dart
index f3bcf6a..bc9d869 100644
--- a/packages/flutter/lib/src/widgets/disposable_build_context.dart
+++ b/packages/flutter/lib/src/widgets/disposable_build_context.dart
@@ -2,6 +2,8 @@
 // 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 'framework.dart';
 
 /// Provides non-leaking access to a [BuildContext].
@@ -28,7 +30,17 @@
   ///
   /// [State.mounted] must be true.
   DisposableBuildContext(T this._state)
-      : assert(_state.mounted, 'A DisposableBuildContext was given a BuildContext for an Element that is not mounted.');
+      : assert(_state.mounted, 'A DisposableBuildContext was given a BuildContext for an Element that is not mounted.')  {
+    // TODO(polina-c): stop duplicating code across disposables
+    // https://github.com/flutter/flutter/issues/137435
+    if (kFlutterMemoryAllocationsEnabled) {
+      MemoryAllocations.instance.dispatchObjectCreated(
+        library: 'package:flutter/widgets.dart',
+        className: '$DisposableBuildContext',
+        object: this,
+      );
+    }
+  }
 
   T? _state;
 
@@ -66,6 +78,11 @@
   /// Creators of this object must call [dispose] when their [Element] is
   /// unmounted, i.e. when [State.dispose] is called.
   void dispose() {
+    // TODO(polina-c): stop duplicating code across disposables
+    // https://github.com/flutter/flutter/issues/137435
+    if (kFlutterMemoryAllocationsEnabled) {
+      MemoryAllocations.instance.dispatchObjectDisposed(object: this);
+    }
     _state = null;
   }
 }
diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart
index 7fc2966..0de4cf1 100644
--- a/packages/flutter/lib/src/widgets/navigator.dart
+++ b/packages/flutter/lib/src/widgets/navigator.dart
@@ -2913,7 +2913,17 @@
            initialState == _RouteLifecycle.pushReplace ||
            initialState == _RouteLifecycle.replace,
          ),
-         currentState = initialState;
+        currentState = initialState {
+    // TODO(polina-c): stop duplicating code across disposables
+    // https://github.com/flutter/flutter/issues/137435
+    if (kFlutterMemoryAllocationsEnabled) {
+      MemoryAllocations.instance.dispatchObjectCreated(
+        library: 'package:flutter/widgets.dart',
+        className: '$_RouteEntry',
+        object: this,
+      );
+    }
+  }
 
   @override
   final Route<dynamic> route;
@@ -3125,6 +3135,11 @@
   /// before disposing.
   void forcedDispose() {
     assert(currentState.index < _RouteLifecycle.disposed.index);
+    // TODO(polina-c): stop duplicating code across disposables
+    // https://github.com/flutter/flutter/issues/137435
+    if (kFlutterMemoryAllocationsEnabled) {
+      MemoryAllocations.instance.dispatchObjectDisposed(object: this);
+    }
     currentState = _RouteLifecycle.disposed;
     route.dispose();
   }
diff --git a/packages/flutter/test/services/restoration_bucket_test.dart b/packages/flutter/test/services/restoration_bucket_test.dart
index 49383e4..67058ae 100644
--- a/packages/flutter/test/services/restoration_bucket_test.dart
+++ b/packages/flutter/test/services/restoration_bucket_test.dart
@@ -6,6 +6,7 @@
 import 'package:flutter/foundation.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
+import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
 
 import 'restoration.dart';
 
@@ -562,6 +563,51 @@
     expect(() => bucket.rename('bar'), throwsFlutterError);
     expect(() => bucket.dispose(), throwsFlutterError);
   });
+
+  test('$RestorationBucket dispatches memory events', () async {
+    await expectLater(
+      await memoryEvents(
+        () => RestorationBucket.empty(
+          restorationId: 'child1',
+          debugOwner: null,
+        ).dispose(),
+        RestorationBucket,
+      ),
+      areCreateAndDispose,
+    );
+
+    final MockRestorationManager manager1 = MockRestorationManager();
+    addTearDown(manager1.dispose);
+    await expectLater(
+      await memoryEvents(
+        () => RestorationBucket.root(
+          manager: manager1,
+          rawData: null,
+        ).dispose(),
+        RestorationBucket,
+      ),
+      areCreateAndDispose,
+    );
+
+    final MockRestorationManager manager2 = MockRestorationManager();
+    addTearDown(manager2.dispose);
+    final RestorationBucket parent = RestorationBucket.root(
+      manager: manager2,
+      rawData: _createRawDataSet()
+    );
+    addTearDown(parent.dispose);
+    await expectLater(
+      await memoryEvents(
+        () => RestorationBucket.child(
+          restorationId: 'child1',
+          parent: parent,
+          debugOwner: null,
+        ).dispose(),
+        RestorationBucket,
+      ),
+      areCreateAndDispose,
+    );
+  });
 }
 
 Map<String, dynamic> _createRawDataSet() {
diff --git a/packages/flutter/test/services/restoration_test.dart b/packages/flutter/test/services/restoration_test.dart
index ce52f5f..413917b 100644
--- a/packages/flutter/test/services/restoration_test.dart
+++ b/packages/flutter/test/services/restoration_test.dart
@@ -57,6 +57,7 @@
       expect(rootBucket!.read<int>('value1'), 10);
       expect(rootBucket!.read<String>('value2'), 'Hello');
       final RestorationBucket child = rootBucket!.claimChild('child1', debugOwner: null);
+      addTearDown(child.dispose);
       expect(child.read<int>('another value'), 22);
 
       // Accessing the root bucket again completes synchronously with same bucket.
@@ -157,6 +158,7 @@
       expect(newRoot!.read<int>('foo'), 33);
       expect(newRoot!.read<int>('value1'), null);
       final RestorationBucket newChild = newRoot!.claimChild('childFoo', debugOwner: null);
+      addTearDown(newChild.dispose);
       expect(newChild.read<String>('bar'), 'Hello');
     });
 
diff --git a/packages/flutter/test/widgets/disposable_build_context_test.dart b/packages/flutter/test/widgets/disposable_build_context_test.dart
index c947366..463de7d 100644
--- a/packages/flutter/test/widgets/disposable_build_context_test.dart
+++ b/packages/flutter/test/widgets/disposable_build_context_test.dart
@@ -30,6 +30,21 @@
 
     expect(() => DisposableBuildContext(state), throwsAssertionError);
   });
+
+  testWidgetsWithLeakTracking('DisposableBuildContext dispatches memory events', (WidgetTester tester) async {
+    final GlobalKey<TestWidgetState> key = GlobalKey<TestWidgetState>();
+    await tester.pumpWidget(TestWidget(key));
+
+    final TestWidgetState state = key.currentState!;
+
+    await expectLater(
+      await memoryEvents(
+        () => DisposableBuildContext<TestWidgetState>(state).dispose(),
+        DisposableBuildContext<TestWidgetState>,
+      ),
+      areCreateAndDispose,
+    );
+  });
 }
 
 class TestWidget extends StatefulWidget {
diff --git a/packages/flutter/test/widgets/restoration_mixin_test.dart b/packages/flutter/test/widgets/restoration_mixin_test.dart
index 7fb55fe..45ddb45 100644
--- a/packages/flutter/test/widgets/restoration_mixin_test.dart
+++ b/packages/flutter/test/widgets/restoration_mixin_test.dart
@@ -15,6 +15,7 @@
     addTearDown(manager.dispose);
     final Map<String, dynamic> rawData = <String, dynamic>{};
     final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
+    addTearDown(root.dispose);
     expect(rawData, isEmpty);
 
     await tester.pumpWidget(
@@ -41,6 +42,7 @@
     final MockRestorationManager manager = MockRestorationManager();
     addTearDown(manager.dispose);
     final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: _createRawDataSet());
+    addTearDown(root.dispose);
 
     await tester.pumpWidget(
       UnmanagedRestorationScope(
@@ -64,6 +66,7 @@
     final MockRestorationManager manager = MockRestorationManager();
     addTearDown(manager.dispose);
     final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: _createRawDataSet());
+    addTearDown(root.dispose);
 
     await tester.pumpWidget(
       UnmanagedRestorationScope(
@@ -107,6 +110,7 @@
     final MockRestorationManager manager = MockRestorationManager();
     addTearDown(manager.dispose);
     final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: _createRawDataSet());
+    addTearDown(root.dispose);
 
     await tester.pumpWidget(
       UnmanagedRestorationScope(
@@ -144,6 +148,7 @@
     addTearDown(manager.dispose);
     final Map<String, dynamic> rawData = _createRawDataSet();
     final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
+    addTearDown(root.dispose);
 
     expect((rawData[childrenMapKey] as Map<String, dynamic>).containsKey('child1'), isTrue);
     await tester.pumpWidget(
@@ -173,6 +178,7 @@
     addTearDown(manager.dispose);
     final Map<String, dynamic> rawData = _createRawDataSet();
     final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
+    addTearDown(root.dispose);
 
     await tester.pumpWidget(
       UnmanagedRestorationScope(
@@ -235,6 +241,7 @@
     addTearDown(manager.dispose);
     final Map<String, dynamic> rawData = _createRawDataSet();
     final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
+    addTearDown(root.dispose);
 
     await tester.pumpWidget(
       _TestRestorableWidget(
@@ -297,6 +304,7 @@
     addTearDown(manager.dispose);
     final Map<String, dynamic> rawData = <String, dynamic>{};
     final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
+    addTearDown(root.dispose);
     final Key key = GlobalKey();
 
     await tester.pumpWidget(
diff --git a/packages/flutter/test/widgets/restoration_scope_test.dart b/packages/flutter/test/widgets/restoration_scope_test.dart
index aaabc66..892b2f7 100644
--- a/packages/flutter/test/widgets/restoration_scope_test.dart
+++ b/packages/flutter/test/widgets/restoration_scope_test.dart
@@ -15,6 +15,7 @@
         restorationId: 'foo',
         debugOwner: 'owner',
       );
+      addTearDown(bucket1.dispose);
 
       await tester.pumpWidget(
         UnmanagedRestorationScope(
@@ -31,6 +32,8 @@
         restorationId: 'foo2',
         debugOwner: 'owner',
       );
+      addTearDown(bucket2.dispose);
+
       await tester.pumpWidget(
         UnmanagedRestorationScope(
           bucket: bucket2,
@@ -104,6 +107,7 @@
       addTearDown(manager.dispose);
       final Map<String, dynamic> rawData = <String, dynamic>{};
       final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
+      addTearDown(root.dispose);
       expect(rawData, isEmpty);
 
       await tester.pumpWidget(
@@ -126,6 +130,7 @@
       final MockRestorationManager manager = MockRestorationManager();
       addTearDown(manager.dispose);
       final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: _createRawDataSet());
+      addTearDown(root.dispose);
 
       await tester.pumpWidget(
         UnmanagedRestorationScope(
@@ -147,6 +152,7 @@
       final MockRestorationManager manager = MockRestorationManager();
       addTearDown(manager.dispose);
       final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: _createRawDataSet());
+      addTearDown(root.dispose);
 
       await tester.pumpWidget(
         UnmanagedRestorationScope(
@@ -187,6 +193,7 @@
       addTearDown(manager.dispose);
       final Map<String, dynamic> rawData = _createRawDataSet();
       final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
+      addTearDown(root.dispose);
 
       expect((rawData[childrenMapKey] as Map<String, dynamic>).containsKey('child1'), isTrue);
       await tester.pumpWidget(
@@ -216,6 +223,7 @@
       final MockRestorationManager manager = MockRestorationManager();
       addTearDown(manager.dispose);
       final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: <String, dynamic>{});
+      addTearDown(root.dispose);
 
       await tester.pumpWidget(
         UnmanagedRestorationScope(
@@ -274,6 +282,8 @@
       final MockRestorationManager manager = MockRestorationManager();
       addTearDown(manager.dispose);
       final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: <String, dynamic>{});
+      addTearDown(root.dispose);
+
       await tester.pumpWidget(
         UnmanagedRestorationScope(
           bucket: root,
@@ -316,6 +326,7 @@
       addTearDown(manager.dispose);
       final Map<String, dynamic> rawData = <String, dynamic>{};
       final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
+      addTearDown(root.dispose);
       final Key scopeKey = GlobalKey();
 
       await tester.pumpWidget(
diff --git a/packages/flutter/test/widgets/root_restoration_scope_test.dart b/packages/flutter/test/widgets/root_restoration_scope_test.dart
index 26db826..07e2e04 100644
--- a/packages/flutter/test/widgets/root_restoration_scope_test.dart
+++ b/packages/flutter/test/widgets/root_restoration_scope_test.dart
@@ -27,6 +27,7 @@
     addTearDown(manager.dispose);
     final Map<String, dynamic> rawData = <String, dynamic>{};
     final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
+    addTearDown(root.dispose);
     expect(rawData, isEmpty);
 
     await tester.pumpWidget(
@@ -77,6 +78,7 @@
     // Complete the future.
     final Map<String, dynamic> rawData = <String, dynamic>{};
     final RestorationBucket root = RestorationBucket.root(manager: binding.restorationManager, rawData: rawData);
+    addTearDown(root.dispose);
     bucketCompleter.complete(root);
     await tester.pump(const Duration(milliseconds: 100));
 
@@ -92,6 +94,7 @@
   testWidgetsWithLeakTracking('no delay when root is available synchronously', (WidgetTester tester) async {
     final Map<String, dynamic> rawData = <String, dynamic>{};
     final RestorationBucket root = RestorationBucket.root(manager: binding.restorationManager, rawData: rawData);
+    addTearDown(root.dispose);
     binding.restorationManager.rootBucket = SynchronousFuture<RestorationBucket>(root);
 
     await tester.pumpWidget(
@@ -156,6 +159,7 @@
 
     // Complete the future.
     final RestorationBucket root = RestorationBucket.root(manager: binding.restorationManager, rawData: <String, dynamic>{});
+    addTearDown(root.dispose);
     bucketCompleter.complete(root);
     await tester.pump(const Duration(milliseconds: 100));
 
@@ -187,6 +191,7 @@
     addTearDown(manager.dispose);
     final Map<String, dynamic> inScopeRawData = <String, dynamic>{};
     final RestorationBucket inScopeRootBucket = RestorationBucket.root(manager: manager, rawData: inScopeRawData);
+    addTearDown(inScopeRootBucket.dispose);
 
     await tester.pumpWidget(
       Directionality(
@@ -231,6 +236,7 @@
 
     final Map<String, dynamic> outOfScopeRawData = <String, dynamic>{};
     final RestorationBucket outOfScopeRootBucket = RestorationBucket.root(manager: binding.restorationManager, rawData: outOfScopeRawData);
+    addTearDown(outOfScopeRootBucket.dispose);
     bucketCompleter.complete(outOfScopeRootBucket);
     await tester.pump(const Duration(milliseconds: 100));
 
@@ -267,6 +273,7 @@
   testWidgetsWithLeakTracking('injects new root when old one is decommissioned', (WidgetTester tester) async {
     final Map<String, dynamic> firstRawData = <String, dynamic>{};
     final RestorationBucket firstRoot = RestorationBucket.root(manager: binding.restorationManager, rawData: firstRawData);
+    addTearDown(firstRoot.dispose);
     binding.restorationManager.rootBucket = SynchronousFuture<RestorationBucket>(firstRoot);
 
     await tester.pumpWidget(
@@ -299,9 +306,9 @@
       },
     };
     final RestorationBucket secondRoot = RestorationBucket.root(manager: binding.restorationManager, rawData: secondRawData);
+    addTearDown(secondRoot.dispose);
     binding.restorationManager.rootBucket = SynchronousFuture<RestorationBucket>(secondRoot);
     await tester.pump();
-    firstRoot.dispose();
 
     expect(state.bucket, isNot(same(firstBucket)));
     expect(state.bucket!.read<int>('foo'), 22);
@@ -336,6 +343,7 @@
     expect(state.bucket, isNull);
 
     final RestorationBucket root = RestorationBucket.root(manager: binding.restorationManager, rawData: null);
+    addTearDown(root.dispose);
     binding.restorationManager.rootBucket = SynchronousFuture<RestorationBucket>(root);
     await tester.pump();
 
@@ -346,6 +354,7 @@
 
   testWidgetsWithLeakTracking('can switch to null', (WidgetTester tester) async {
     final RestorationBucket root = RestorationBucket.root(manager: binding.restorationManager, rawData: null);
+    addTearDown(root.dispose);
     binding.restorationManager.rootBucket = SynchronousFuture<RestorationBucket>(root);
 
     await tester.pumpWidget(
@@ -367,7 +376,6 @@
 
     binding.restorationManager.rootBucket = SynchronousFuture<RestorationBucket?>(null);
     await tester.pump();
-    root.dispose();
 
     expect(binding.restorationManager.rootBucketAccessed, 2);
     expect(find.text('Hello'), findsOneWidget);
diff --git a/packages/flutter/test/widgets/scroll_aware_image_provider_test.dart b/packages/flutter/test/widgets/scroll_aware_image_provider_test.dart
index 8dcba52..926484b 100644
--- a/packages/flutter/test/widgets/scroll_aware_image_provider_test.dart
+++ b/packages/flutter/test/widgets/scroll_aware_image_provider_test.dart
@@ -39,6 +39,7 @@
     await tester.pumpWidget(TestWidget(key));
 
     final DisposableBuildContext context = DisposableBuildContext(key.currentState!);
+    addTearDown(context.dispose);
     final TestImageProvider testImageProvider = TestImageProvider(testImage.clone());
     final ScrollAwareImageProvider<TestImageProvider> imageProvider = ScrollAwareImageProvider<TestImageProvider>(
       context: context,
@@ -74,6 +75,7 @@
     ));
 
     final DisposableBuildContext context = DisposableBuildContext(key.currentState!);
+    addTearDown(context.dispose);
     final TestImageProvider testImageProvider = TestImageProvider(testImage.clone());
     final ScrollAwareImageProvider<TestImageProvider> imageProvider = ScrollAwareImageProvider<TestImageProvider>(
       context: context,
@@ -115,6 +117,7 @@
     ));
 
     final DisposableBuildContext context = DisposableBuildContext(keys.last.currentState!);
+    addTearDown(context.dispose);
     final TestImageProvider testImageProvider = TestImageProvider(testImage.clone());
     final ScrollAwareImageProvider<TestImageProvider> imageProvider = ScrollAwareImageProvider<TestImageProvider>(
       context: context,
@@ -173,6 +176,7 @@
     ));
 
     final DisposableBuildContext context = DisposableBuildContext(keys.last.currentState!);
+    addTearDown(context.dispose);
     final TestImageProvider testImageProvider = TestImageProvider(testImage.clone());
     final ScrollAwareImageProvider<TestImageProvider> imageProvider = ScrollAwareImageProvider<TestImageProvider>(
       context: context,
@@ -241,6 +245,7 @@
     ));
 
     final DisposableBuildContext context = DisposableBuildContext(keys.last.currentState!);
+    addTearDown(context.dispose);
     final TestImageProvider testImageProvider = TestImageProvider(testImage.clone());
     final ScrollAwareImageProvider<TestImageProvider> imageProvider = ScrollAwareImageProvider<TestImageProvider>(
       context: context,
@@ -307,6 +312,7 @@
     ));
 
     final DisposableBuildContext context = DisposableBuildContext(key.currentState!);
+    addTearDown(context.dispose);
     final TestImageProvider testImageProvider = TestImageProvider(testImage.clone());
     final ScrollAwareImageProvider<TestImageProvider> imageProvider = ScrollAwareImageProvider<TestImageProvider>(
       context: context,
@@ -359,6 +365,7 @@
     ));
 
     final DisposableBuildContext context = DisposableBuildContext(key.currentState!);
+    addTearDown(context.dispose);
     final TestImageProvider testImageProvider = TestImageProvider(testImage.clone());
     final ScrollAwareImageProvider<TestImageProvider> imageProvider = ScrollAwareImageProvider<TestImageProvider>(
       context: context,