Add support for inspecting instance of WeakArray (#5316)

Also refactored VmSubtypeTestCacheDisplay into a more generic VmSimpleListDisplay.
diff --git a/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/object_inspector_view_controller.dart b/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/object_inspector_view_controller.dart
index 039076e..8b992f2 100644
--- a/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/object_inspector_view_controller.dart
+++ b/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/object_inspector_view_controller.dart
@@ -179,6 +179,10 @@
       object = SubtypeTestCacheObject(
         ref: objRef,
       );
+    } else if (objRef.isWeakArray) {
+      object = WeakArrayObject(
+        ref: objRef,
+      );
     }
 
     await object?.initialize();
diff --git a/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/object_viewport.dart b/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/object_viewport.dart
index bad3a4c..6b03929 100644
--- a/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/object_viewport.dart
+++ b/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/object_viewport.dart
@@ -19,7 +19,7 @@
 import 'vm_object_model.dart';
 import 'vm_object_pool_display.dart';
 import 'vm_script_display.dart';
-import 'vm_subtype_test_cache_display.dart';
+import 'vm_simple_list_display.dart';
 
 /// Displays the VM information for the currently selected object in the
 /// program explorer.
@@ -132,10 +132,10 @@
         icData: obj,
       );
     }
-    if (obj is SubtypeTestCacheObject) {
-      return VmSubtypeTestCacheDisplay(
+    if (obj is VmListObject) {
+      return VmSimpleListDisplay(
         controller: controller,
-        subtypeTestCache: obj,
+        vmObject: obj,
       );
     }
     return const SizedBox.shrink();
diff --git a/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/vm_instance_display.dart b/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/vm_instance_display.dart
index 2ac7ce0..a41c0c7 100644
--- a/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/vm_instance_display.dart
+++ b/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/vm_instance_display.dart
@@ -196,7 +196,7 @@
           ),
           onTap: onTap,
         ),
-        if (variable.ref!.value is! Sentinel)
+        if (variable.ref!.value is! Sentinel && variable.ref!.value is ObjRef?)
           VmServiceObjectLink(
             object: variable.ref!.value as ObjRef?,
             textBuilder: (object) {
@@ -208,6 +208,11 @@
             },
             onTap: controller.findAndSelectNodeForObject,
           )
+        else
+          Text(
+            variable.ref!.value.toString(),
+            style: Theme.of(context).subtleFixedFontStyle,
+          )
       ],
     );
   }
diff --git a/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/vm_object_model.dart b/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/vm_object_model.dart
index 668fe7c..0bccf4e 100644
--- a/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/vm_object_model.dart
+++ b/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/vm_object_model.dart
@@ -106,6 +106,22 @@
   }
 }
 
+/// A class of [VmObject] for VM objects that simply provide a list of elements
+/// to be displayed.
+///
+/// All instances of [VmListObject] will be displaying using the
+/// [VmSimpleListDisplay] view.
+///
+/// Implementers of this interface must have a non-null return value for one of
+/// `elementsAsList` or `elementsAsInstance`.
+abstract class VmListObject extends VmObject {
+  VmListObject({required super.ref});
+
+  List<Response?>? get elementsAsList;
+
+  InstanceRef? get elementsAsInstance;
+}
+
 /// Stores a 'Class' VM object and provides an interface for obtaining the
 /// Dart VM information related to this object.
 class ClassObject extends VmObject {
@@ -335,7 +351,7 @@
 
 /// Stores a 'SubtypeTestCache' VM object and provides an interface for
 /// obtaining the Dart VM information related to this object.
-class SubtypeTestCacheObject extends VmObject {
+class SubtypeTestCacheObject extends VmListObject {
   SubtypeTestCacheObject({required super.ref});
 
   @override
@@ -346,4 +362,31 @@
 
   @override
   SubtypeTestCache get obj => _obj.asSubtypeTestCache;
+
+  @override
+  InstanceRef get elementsAsInstance => obj.cache;
+
+  @override
+  List<Response?>? get elementsAsList => null;
+}
+
+/// Stores a 'WeakArray' VM object and provides an interface for
+/// obtaining the Dart VM information related to this object.
+class WeakArrayObject extends VmListObject {
+  WeakArrayObject({required super.ref});
+
+  @override
+  SourceLocation? get _sourceLocation => null;
+
+  @override
+  String? get name => null;
+
+  @override
+  WeakArray get obj => _obj.asWeakArray;
+
+  @override
+  InstanceRef? get elementsAsInstance => null;
+
+  @override
+  List<Response?>? get elementsAsList => obj.asWeakArray.elements;
 }
diff --git a/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/vm_simple_list_display.dart b/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/vm_simple_list_display.dart
new file mode 100644
index 0000000..8d23f2f
--- /dev/null
+++ b/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/vm_simple_list_display.dart
@@ -0,0 +1,105 @@
+// Copyright 2023 The Chromium 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 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:vm_service/vm_service.dart';
+
+import '../../../shared/common_widgets.dart';
+import '../../../shared/globals.dart';
+import '../vm_developer_common_widgets.dart';
+import 'object_inspector_view_controller.dart';
+import 'vm_object_model.dart';
+
+/// A widget for the object inspector historyViewport displaying information
+/// related to list-like VM objects (e.g., subtype test cache, WeakArray, etc).
+class VmSimpleListDisplay<T extends VmListObject> extends StatefulWidget {
+  const VmSimpleListDisplay({
+    required this.controller,
+    required this.vmObject,
+  });
+
+  final ObjectInspectorViewController controller;
+  final T vmObject;
+
+  @override
+  State<VmSimpleListDisplay> createState() => _VmSimpleListDisplayState();
+}
+
+class _VmSimpleListDisplayState extends State<VmSimpleListDisplay> {
+  final entries = <Response?>[];
+
+  late Future<void> _initialized;
+
+  @override
+  void initState() {
+    super.initState();
+    _initialize();
+  }
+
+  @override
+  void didUpdateWidget(VmSimpleListDisplay oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.vmObject == oldWidget.vmObject) {
+      return;
+    }
+    _initialize();
+  }
+
+  void _initialize() async {
+    entries.clear();
+    final elementsInstance = widget.vmObject.elementsAsInstance;
+    if (elementsInstance != null) {
+      if (elementsInstance is Instance) {
+        entries.addAll(elementsInstance.elements!.cast<Response?>());
+        _initialized = Future.value();
+        return;
+      }
+
+      final isolateId =
+          serviceManager.isolateManager.selectedIsolate.value!.id!;
+      final service = serviceManager.service!;
+      _initialized = service.getObject(isolateId, elementsInstance.id!).then(
+            (e) => entries.addAll((e as Instance).elements!.cast<Response?>()),
+          );
+      return;
+    }
+    final elementsList = widget.vmObject.elementsAsList;
+    assert(
+      elementsList != null,
+      'One of elementsAsList or elementsAsInstance must be non-null',
+    );
+    entries.addAll(elementsList!);
+    _initialized = Future.value();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return FutureBuilder<void>(
+      future: _initialized,
+      builder: (context, snapshot) {
+        if (snapshot.connectionState != ConnectionState.done)
+          return const CenteredCircularProgressIndicator();
+        return VmObjectDisplayBasicLayout(
+          controller: widget.controller,
+          object: widget.vmObject,
+          generalDataRows: [
+            ...vmObjectGeneralDataRows(
+              widget.controller,
+              widget.vmObject,
+            ),
+          ],
+          expandableWidgets: [
+            ExpansionTileInstanceList(
+              controller: widget.controller,
+              title: 'Entries',
+              elements: entries,
+            ),
+          ],
+        );
+      },
+    );
+  }
+}
diff --git a/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/vm_subtype_test_cache_display.dart b/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/vm_subtype_test_cache_display.dart
deleted file mode 100644
index 7bb7d8a..0000000
--- a/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/vm_subtype_test_cache_display.dart
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2023 The Chromium 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 'dart:async';
-
-import 'package:flutter/material.dart';
-import 'package:vm_service/vm_service.dart';
-
-import '../../../shared/common_widgets.dart';
-import '../../../shared/globals.dart';
-import '../vm_developer_common_widgets.dart';
-import 'object_inspector_view_controller.dart';
-import 'vm_object_model.dart';
-
-/// A widget for the object inspector historyViewport displaying information
-/// related to ICData objects in the Dart VM.
-class VmSubtypeTestCacheDisplay extends StatefulWidget {
-  const VmSubtypeTestCacheDisplay({
-    required this.controller,
-    required this.subtypeTestCache,
-  });
-
-  final ObjectInspectorViewController controller;
-  final SubtypeTestCacheObject subtypeTestCache;
-
-  @override
-  State<VmSubtypeTestCacheDisplay> createState() =>
-      _VmSubtypeTestCacheDisplayState();
-}
-
-class _VmSubtypeTestCacheDisplayState extends State<VmSubtypeTestCacheDisplay> {
-  final entries = <ObjRef?>[];
-
-  late Future<void> _initialized;
-
-  @override
-  void initState() {
-    super.initState();
-    _initialize();
-  }
-
-  @override
-  void didUpdateWidget(VmSubtypeTestCacheDisplay oldWidget) {
-    super.didUpdateWidget(oldWidget);
-    if (widget.subtypeTestCache == oldWidget.subtypeTestCache) {
-      return;
-    }
-    _initialize();
-  }
-
-  void _initialize() async {
-    entries.clear();
-
-    final subtypeTestCache = widget.subtypeTestCache.obj;
-    final cache = subtypeTestCache.cache;
-    if (cache is Instance) {
-      entries.addAll(cache.elements!.cast<ObjRef?>());
-      _initialized = Future.value();
-      return;
-    }
-
-    final isolateId = serviceManager.isolateManager.selectedIsolate.value!.id!;
-    final service = serviceManager.service!;
-    _initialized = service
-        .getObject(isolateId, cache.id!)
-        .then((e) => entries.addAll((e as Instance).elements!.cast<ObjRef?>()));
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return FutureBuilder<void>(
-      future: _initialized,
-      builder: (context, snapshot) {
-        if (snapshot.connectionState != ConnectionState.done)
-          return const CenteredCircularProgressIndicator();
-        return VmObjectDisplayBasicLayout(
-          controller: widget.controller,
-          object: widget.subtypeTestCache,
-          generalDataRows: [
-            ...vmObjectGeneralDataRows(
-              widget.controller,
-              widget.subtypeTestCache,
-            ),
-          ],
-          expandableWidgets: [
-            ExpansionTileInstanceList(
-              controller: widget.controller,
-              title: 'Entries',
-              elements: entries,
-            ),
-          ],
-        );
-      },
-    );
-  }
-}
diff --git a/packages/devtools_app/lib/src/screens/vm_developer/vm_developer_common_widgets.dart b/packages/devtools_app/lib/src/screens/vm_developer/vm_developer_common_widgets.dart
index 46fe3b3..42558f4 100644
--- a/packages/devtools_app/lib/src/screens/vm_developer/vm_developer_common_widgets.dart
+++ b/packages/devtools_app/lib/src/screens/vm_developer/vm_developer_common_widgets.dart
@@ -381,7 +381,7 @@
 
   final ObjectInspectorViewController controller;
   final String title;
-  final List<ObjRef?> elements;
+  final List<Response?> elements;
 
   @override
   Widget build(BuildContext context) {
@@ -810,6 +810,9 @@
         object is ObjRef &&
         object.isSubtypeTestCache) {
       text = 'SubtypeTestCache';
+    } else if (object != null && object is ObjRef && object.isWeakArray) {
+      final weakArray = object.asWeakArray;
+      text = 'WeakArray(length: ${weakArray.length})';
     }
     return text;
   }
diff --git a/packages/devtools_app/lib/src/screens/vm_developer/vm_service_private_extensions.dart b/packages/devtools_app/lib/src/screens/vm_developer/vm_service_private_extensions.dart
index cc1059b..7d5ba96 100644
--- a/packages/devtools_app/lib/src/screens/vm_developer/vm_service_private_extensions.dart
+++ b/packages/devtools_app/lib/src/screens/vm_developer/vm_service_private_extensions.dart
@@ -158,6 +158,7 @@
   static const _icDataType = 'ICData';
   static const _objectPoolType = 'ObjectPool';
   static const _subtypeTestCache = 'SubtypeTestCache';
+  static const _weakArrayType = 'WeakArray';
 
   /// The internal type of the object.
   ///
@@ -183,6 +184,12 @@
   /// Casts the current [ObjRef] into an instance of [SubtypeTestCacheRef].
   SubtypeTestCacheRef get asSubtypeTestCache =>
       SubtypeTestCacheRef.parse(json!);
+
+  /// `true` if this object is an instance of [WeakArrayRef].
+  bool get isWeakArray => vmType == _weakArrayType;
+
+  /// Casts the current [ObjRef] into an instance of [WeakArrayRef].
+  WeakArrayRef get asWeakArray => WeakArrayRef.parse(json!);
 }
 
 /// An extension on [Obj] which allows for access to VM internal fields.
@@ -195,6 +202,87 @@
 
   /// Casts the current [Obj] into an instance of [SubtypeTestCache].
   SubtypeTestCache get asSubtypeTestCache => SubtypeTestCache.parse(json!);
+
+  /// Casts the current [Obj] into an instance of [WeakArray].
+  WeakArray get asWeakArray => WeakArray.parse(json!);
+}
+
+/// A reference to a [WeakArray], which is an array consisting of weak
+/// persistent handles.
+///
+/// References to an object from a [WeakArray] are ignored by the GC and will
+/// not prevent referenced objects from being collected when all other
+/// references to the object disappear.
+class WeakArrayRef implements ObjRef {
+  WeakArrayRef({
+    required this.id,
+    required this.json,
+    required this.length,
+  });
+
+  factory WeakArrayRef.parse(Map<String, dynamic> json) => WeakArrayRef(
+        id: json['id'],
+        json: json,
+        length: json['length'],
+      );
+
+  @override
+  bool? fixedId;
+
+  @override
+  String? id;
+
+  @override
+  Map<String, dynamic>? json;
+
+  final int length;
+
+  @override
+  Map<String, dynamic> toJson() => json!;
+
+  @override
+  String get type => 'WeakArray';
+}
+
+/// A populated representation of a [WeakArray], which is an array consisting
+/// of weak persistent handles.
+///
+/// References to an object from a [WeakArray] are ignored by the GC and will
+/// not prevent referenced objects from being collected when all other
+/// references to the object disappear.
+class WeakArray extends WeakArrayRef implements Obj {
+  WeakArray({
+    required super.id,
+    required super.json,
+    required super.length,
+    required this.elements,
+    required this.size,
+    required this.classRef,
+  });
+
+  factory WeakArray.parse(Map<String, dynamic> json) => WeakArray(
+        id: json['id'],
+        json: json,
+        length: json['length'],
+        size: json['size'],
+        elements: (createServiceObject(json['elements'], []) as List)
+            .cast<Response?>(),
+        classRef: createServiceObject(json['class'], [])! as ClassRef,
+      );
+
+  final List<Response?> elements;
+
+  @override
+  Map<String, dynamic> toJson() => json!;
+
+  @override
+  String get type => 'WeakArray';
+
+  @override
+  ClassRef? classRef;
+
+  @override
+  int? size;
 }
 
 /// A partially-populated representation of the Dart VM's subtype test cache.
diff --git a/packages/devtools_app/test/vm_developer/object_inspector/vm_subtype_test_cache_display_test.dart b/packages/devtools_app/test/vm_developer/object_inspector/vm_simple_list_display_test.dart
similarity index 89%
rename from packages/devtools_app/test/vm_developer/object_inspector/vm_subtype_test_cache_display_test.dart
rename to packages/devtools_app/test/vm_developer/object_inspector/vm_simple_list_display_test.dart
index bb8a1ee..e52a6f9 100644
--- a/packages/devtools_app/test/vm_developer/object_inspector/vm_subtype_test_cache_display_test.dart
+++ b/packages/devtools_app/test/vm_developer/object_inspector/vm_simple_list_display_test.dart
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'package:devtools_app/devtools_app.dart';
-import 'package:devtools_app/src/screens/vm_developer/object_inspector/vm_subtype_test_cache_display.dart';
+import 'package:devtools_app/src/screens/vm_developer/object_inspector/vm_simple_list_display.dart';
 import 'package:devtools_app/src/screens/vm_developer/vm_developer_common_widgets.dart';
 import 'package:devtools_test/devtools_test.dart';
 import 'package:flutter/material.dart';
@@ -27,15 +27,17 @@
     mockSubtypeTestCacheObject = MockSubtypeTestCacheObject();
 
     mockVmObject(mockSubtypeTestCacheObject);
+    final cache = Instance(id: 'inst-2', length: 0, elements: []);
     when(mockSubtypeTestCacheObject.obj).thenReturn(
       SubtypeTestCache(
         id: 'subtype-test-cache-id',
         size: 64,
-        cache: Instance(id: 'inst-2', length: 0, elements: []),
+        cache: cache,
         classRef: ClassRef(id: 'cls-id-2', name: 'SubtypeTestCache'),
         json: {},
       ),
     );
+    when(mockSubtypeTestCacheObject.elementsAsInstance).thenReturn(cache);
   });
 
   group('Subtype test cache display test', () {
@@ -45,8 +47,8 @@
       (WidgetTester tester) async {
         await tester.pumpWidget(
           wrap(
-            VmSubtypeTestCacheDisplay(
-              subtypeTestCache: mockSubtypeTestCacheObject,
+            VmSimpleListDisplay(
+              vmObject: mockSubtypeTestCacheObject,
               controller: ObjectInspectorViewController(),
             ),
           ),