Browse garbage collected items. (#5305)

diff --git a/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/heap_diff.dart b/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/heap_diff.dart
index 871d5f4..776c988 100644
--- a/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/heap_diff.dart
+++ b/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/heap_diff.dart
@@ -83,8 +83,8 @@
 class DiffHeapClasses extends HeapClasses<DiffClassStats>
     with FilterableHeapClasses<DiffClassStats> {
   DiffHeapClasses(_HeapCouple couple)
-      : before = couple.younger.data,
-        after = couple.older.data {
+      : before = couple.older.data,
+        after = couple.younger.data {
     classesByName = subtractMaps<HeapClassName, SingleClassStats,
         SingleClassStats, DiffClassStats>(
       from: couple.younger.classes.classesByName,
@@ -133,7 +133,10 @@
 
     final result = DiffClassStats._(
       heapClass: heapClass,
-      total: ObjectSetDiff(before: before?.objects, after: after?.objects),
+      total: ObjectSetDiff(
+        setBefore: before?.objects,
+        setAfter: after?.objects,
+      ),
       statsByPath: subtractMaps<ClassOnlyHeapPath, ObjectSetStats,
           ObjectSetStats, ObjectSetStats>(
         from: after?.statsByPath,
@@ -152,44 +155,42 @@
 
 /// Comparison between two sets of objects.
 class ObjectSetDiff {
-  ObjectSetDiff({ObjectSet? before, ObjectSet? after}) {
-    before ??= ObjectSet.empty;
-    after ??= ObjectSet.empty;
+  ObjectSetDiff({ObjectSet? setBefore, ObjectSet? setAfter}) {
+    setBefore ??= ObjectSet.empty;
+    setAfter ??= ObjectSet.empty;
 
-    final codesBefore = before.objectsByCodes.keys.toSet();
-    final codesAfter = after.objectsByCodes.keys.toSet();
+    final allCodes = _unionCodes(setBefore, setAfter);
 
-    final allCodes = codesBefore.union(codesAfter);
     for (var code in allCodes) {
-      final inBefore = codesBefore.contains(code);
-      final inAfter = codesAfter.contains(code);
+      final before = setBefore.objectsByCodes[code];
+      final after = setAfter.objectsByCodes[code];
 
-      final object = before.objectsByCodes[code] ?? after.objectsByCodes[code]!;
-
-      if (inAfter && inBefore) {
-        // We assume that state 'after' is what is most interesting for user
+      if (before != null && after != null) {
+        // When an object exists both before and after
+        // the state 'after' is more interesting for user
         // about the retained size.
         final excludeFromRetained =
-            after.objectsExcludedFromRetainedSize.contains(object.code);
+            setAfter.objectsExcludedFromRetainedSize.contains(after.code);
         persisted.countInstance(
-          object,
+          after,
           excludeFromRetained: excludeFromRetained,
         );
         continue;
       }
 
-      if (inBefore) {
+      if (before != null) {
         final excludeFromRetained =
-            before.objectsExcludedFromRetainedSize.contains(object.code);
-        deleted.countInstance(object, excludeFromRetained: excludeFromRetained);
-        delta.uncountInstance(object, excludeFromRetained: excludeFromRetained);
+            setBefore.objectsExcludedFromRetainedSize.contains(before.code);
+        deleted.countInstance(before, excludeFromRetained: excludeFromRetained);
+        delta.uncountInstance(before, excludeFromRetained: excludeFromRetained);
         continue;
       }
-      if (inAfter) {
+
+      if (after != null) {
         final excludeFromRetained =
-            after.objectsExcludedFromRetainedSize.contains(object.code);
-        created.countInstance(object, excludeFromRetained: excludeFromRetained);
-        delta.countInstance(object, excludeFromRetained: excludeFromRetained);
+            setAfter.objectsExcludedFromRetainedSize.contains(after.code);
+        created.countInstance(after, excludeFromRetained: excludeFromRetained);
+        delta.countInstance(after, excludeFromRetained: excludeFromRetained);
         continue;
       }
 
@@ -204,6 +205,13 @@
     );
   }
 
+  static Set<IdentityHashCode> _unionCodes(ObjectSet set1, ObjectSet set2) {
+    final codesBefore = set1.objectsByCodes.keys.toSet();
+    final codesAfter = set2.objectsByCodes.keys.toSet();
+
+    return codesBefore.union(codesAfter);
+  }
+
   final created = ObjectSet();
   final deleted = ObjectSet();
   final persisted = ObjectSet();
diff --git a/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/sampler.dart b/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/sampler.dart
index b26905a..99814a7 100644
--- a/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/sampler.dart
+++ b/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/sampler.dart
@@ -13,7 +13,8 @@
 import '../../../shared/primitives/instance_set_button.dart';
 
 class HeapClassSampler extends ClassSampler {
-  HeapClassSampler(this.objects, this.heap, this.heapClass);
+  HeapClassSampler(this.objects, this.heap, this.heapClass)
+      : assert(objects.objectsByCodes.isNotEmpty);
 
   final HeapClassName heapClass;
   final ObjectSet objects;
@@ -77,4 +78,17 @@
       isolateRef: _mainIsolateRef,
     );
   }
+
+  @override
+  Future<void> oneStaticToConsole() async {
+    final heapObject = objects.objectsByCodes.values.first;
+    final heapSelection = HeapObjectSelection(heap, object: heapObject);
+
+    // drop to console
+    serviceManager.consoleService.appendBrowsableInstance(
+      instanceRef: null,
+      isolateRef: _mainIsolateRef,
+      heapSelection: heapSelection,
+    );
+  }
 }
diff --git a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_diff.dart b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_diff.dart
index 8d308f7..f8a2720 100644
--- a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_diff.dart
+++ b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_diff.dart
@@ -168,6 +168,7 @@
       data.heapClass,
       isSelected: isRowSelected,
       gaContext: gac.MemoryAreas.snapshotDiff,
+      liveItemsEnabled: dataPart != _DataPart.deleted,
     );
   }
 }
diff --git a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/instances.dart b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/instances.dart
index c474705..6091219 100644
--- a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/instances.dart
+++ b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/instances.dart
@@ -23,6 +23,7 @@
     HeapClassName heapClass, {
     required this.isSelected,
     required this.gaContext,
+    this.liveItemsEnabled = true,
   })  : _showMenu = _shouldShowMenu(isSelected, objects),
         _sampleObtainer = _shouldShowMenu(isSelected, objects)
             ? HeapClassSampler(objects, heap, heapClass)
@@ -37,6 +38,7 @@
   final bool isSelected;
   final MemoryAreas gaContext;
   final int _count;
+  final bool liveItemsEnabled;
 
   @override
   Widget build(BuildContext context) {
@@ -52,6 +54,7 @@
           gaContext: gaContext,
           sampleObtainer: _sampleObtainer,
           showMenu: _showMenu,
+          liveItemsEnabled: liveItemsEnabled,
         ),
       ],
     );
diff --git a/packages/devtools_app/lib/src/screens/memory/shared/primitives/instance_set_button.dart b/packages/devtools_app/lib/src/screens/memory/shared/primitives/instance_set_button.dart
index a79558b..fdcb9cb 100644
--- a/packages/devtools_app/lib/src/screens/memory/shared/primitives/instance_set_button.dart
+++ b/packages/devtools_app/lib/src/screens/memory/shared/primitives/instance_set_button.dart
@@ -15,6 +15,9 @@
   /// Drop one variable, which exists in static set and still alive in app, to console.
   Future<void> oneLiveStaticToConsole();
 
+  /// Drop one variable from static set, to console.
+  Future<void> oneStaticToConsole();
+
   /// Drop all live instances to console.
   Future<void> manyLiveToConsole();
 
@@ -31,6 +34,7 @@
     required this.sampleObtainer,
     required this.showMenu,
     required this.gaContext,
+    required this.liveItemsEnabled,
   })  : assert(showMenu == (sampleObtainer != null)),
         assert(count >= 0);
 
@@ -40,6 +44,9 @@
   final TextStyle? textStyle;
   final MemoryAreas gaContext;
 
+  /// If true, menu items that show live objects, will be enabled.
+  final bool liveItemsEnabled;
+
   @override
   Widget build(BuildContext context) {
     final shouldShowMenu = showMenu && count > 0;
@@ -53,7 +60,10 @@
         if (shouldShowMenu)
           ContextMenuButton(
             style: textStyle,
-            menu: _menu(sampleObtainer!),
+            menu: _menu(
+              sampleObtainer!,
+              liveItemsEnabled: liveItemsEnabled,
+            ),
           ),
         if (!shouldShowMenu) const SizedBox(width: ContextMenuButton.width),
       ],
@@ -61,33 +71,38 @@
   }
 }
 
-class _StoreAsVariableMenu extends StatelessWidget {
-  const _StoreAsVariableMenu(this.sampleObtainer);
+class _StoreAsOneVariableMenu extends StatelessWidget {
+  const _StoreAsOneVariableMenu(
+    this.sampleObtainer, {
+    required this.liveItemsEnabled,
+  });
 
   final ClassSampler sampleObtainer;
+  final bool liveItemsEnabled;
 
   @override
   Widget build(BuildContext context) {
     final enabled = sampleObtainer.isEvalEnabled;
-    const menuText = 'Store as a console variable';
-    final limit = preferences.memory.refLimit.value;
+    const menuText = 'Store one instance as a console variable';
 
     if (!enabled) {
       return const MenuItemButton(child: Text(menuText));
     }
 
     return SubmenuButton(
-      // TODO(polina-c): change structure and review texts before opening the feature.
       menuChildren: <Widget>[
         MenuItemButton(
-          onPressed: sampleObtainer.oneLiveStaticToConsole,
+          onPressed: sampleObtainer.oneStaticToConsole,
           child: const Text(
-            'One instance that exists in snapshot, and is alive in application',
+            'Any from snapshot',
           ),
         ),
         MenuItemButton(
-          onPressed: sampleObtainer.manyLiveToConsole,
-          child: Text('Up to $limit instances, currently alive in application'),
+          onPressed:
+              liveItemsEnabled ? sampleObtainer.oneLiveStaticToConsole : null,
+          child: const Text(
+            'Any from snapshot, not garbage collected',
+          ),
         ),
       ],
       child: const Text(menuText),
@@ -95,6 +110,19 @@
   }
 }
 
-List<Widget> _menu(ClassSampler sampleObtainer) => [
-      _StoreAsVariableMenu(sampleObtainer),
-    ];
+// TODO(polina-c): review structure/texts and add ga, before opening the feature.
+List<Widget> _menu(
+  ClassSampler sampleObtainer, {
+  required bool liveItemsEnabled,
+}) {
+  final limit = preferences.memory.refLimit.value;
+  return [
+    _StoreAsOneVariableMenu(sampleObtainer, liveItemsEnabled: liveItemsEnabled),
+    MenuItemButton(
+      onPressed: sampleObtainer.manyLiveToConsole,
+      child: Text(
+        'Store up to $limit instances, currently alive in application',
+      ),
+    ),
+  ];
+}
diff --git a/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart b/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart
index 1751cd1..fd254d7 100644
--- a/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart
+++ b/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart
@@ -261,6 +261,7 @@
     if (treeInitializeComplete || children.isNotEmpty || childCount > 0) {
       return children.isNotEmpty || childCount > 0;
     }
+    if (ref?.heapSelection != null) return true;
     final diagnostic = ref?.diagnostic;
     if (diagnostic != null &&
         ((diagnostic.inlineProperties.isNotEmpty) || diagnostic.hasChildren))
diff --git a/packages/devtools_app/lib/src/shared/diagnostics/tree_builder.dart b/packages/devtools_app/lib/src/shared/diagnostics/tree_builder.dart
index 552da74..b3b8c24 100644
--- a/packages/devtools_app/lib/src/shared/diagnostics/tree_builder.dart
+++ b/packages/devtools_app/lib/src/shared/diagnostics/tree_builder.dart
@@ -163,12 +163,6 @@
   );
 
   if (result is Instance) {
-    if (FeatureFlags.evalAndBrowse &&
-        ref != null &&
-        ref.heapSelection != null) {
-      addReferencesRoot(variable, ref);
-    }
-
     await _addChildrenToInstanceVariable(
       variable: variable,
       value: result,
@@ -406,6 +400,12 @@
     variable.addChild(DartObjectNode.text('error: $ex\n$stack'));
   }
 
+  if (FeatureFlags.evalAndBrowse &&
+      ref.heapSelection != null &&
+      ref is! ObjectReferences) {
+    addReferencesRoot(variable, ref);
+  }
+
   await _addDiagnosticChildrenIfNeeded(
     variable,
     diagnostic,