| // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file |
| // for details. 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:math'; |
| |
| import 'package:dwds/src/config/tool_configuration.dart'; |
| import 'package:dwds/src/debugging/inspector.dart'; |
| import 'package:dwds/src/debugging/metadata/class.dart'; |
| import 'package:dwds/src/debugging/metadata/function.dart'; |
| import 'package:dwds/src/utilities/conversions.dart'; |
| import 'package:dwds/src/utilities/domain.dart'; |
| import 'package:dwds/src/utilities/objects.dart'; |
| import 'package:dwds/src/utilities/shared.dart'; |
| import 'package:logging/logging.dart'; |
| import 'package:vm_service/vm_service.dart'; |
| import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; |
| |
| /// Contains a set of methods for getting [Instance]s and [InstanceRef]s. |
| class InstanceHelper extends Domain { |
| final _logger = Logger('InstanceHelper'); |
| final ClassMetaDataHelper metadataHelper; |
| |
| InstanceHelper(AppInspector appInspector) |
| : metadataHelper = ClassMetaDataHelper(appInspector) { |
| inspector = appInspector; |
| } |
| |
| static final InstanceRef kNullInstanceRef = |
| _primitiveInstanceRef(InstanceKind.kNull, null); |
| |
| /// Creates an [InstanceRef] for a primitive [RemoteObject]. |
| static InstanceRef _primitiveInstanceRef( |
| String kind, |
| RemoteObject? remoteObject, |
| ) { |
| final classRef = classRefFor('dart:core', kind); |
| return InstanceRef( |
| identityHashCode: dartIdFor(remoteObject?.value).hashCode, |
| kind: kind, |
| classRef: classRef, |
| id: dartIdFor(remoteObject?.value), |
| valueAsString: '${remoteObject?.value}', |
| ); |
| } |
| |
| /// Creates an [Instance] for a primitive [RemoteObject]. |
| Instance? _primitiveInstance(String kind, RemoteObject? remoteObject) { |
| final objectId = remoteObject?.objectId; |
| if (objectId == null) return null; |
| return Instance( |
| identityHashCode: objectId.hashCode, |
| id: objectId, |
| kind: kind, |
| classRef: classRefFor('dart:core', kind), |
| valueAsString: '${remoteObject?.value}', |
| ); |
| } |
| |
| Instance? _stringInstanceFor( |
| RemoteObject? remoteObject, |
| int? offset, |
| int? count, |
| ) { |
| // TODO(#777) Consider a way of not passing the whole string around (in the |
| // ID) in order to find a substring. |
| final objectId = remoteObject?.objectId; |
| if (objectId == null) return null; |
| final fullString = stringFromDartId(objectId); |
| var preview = fullString; |
| var truncated = false; |
| if (offset != null || count != null) { |
| truncated = true; |
| final start = offset ?? 0; |
| final end = count == null ? null : min(start + count, fullString.length); |
| preview = fullString.substring(start, end); |
| } |
| return Instance( |
| identityHashCode: createId().hashCode, |
| kind: InstanceKind.kString, |
| classRef: classRefForString, |
| id: createId(), |
| valueAsString: preview, |
| valueAsStringIsTruncated: truncated, |
| length: fullString.length, |
| count: (truncated ? preview.length : null), |
| offset: (truncated ? offset : null), |
| ); |
| } |
| |
| Instance? _closureInstanceFor(RemoteObject remoteObject) { |
| final objectId = remoteObject.objectId; |
| if (objectId == null) return null; |
| final result = Instance( |
| kind: InstanceKind.kClosure, |
| id: objectId, |
| identityHashCode: remoteObject.objectId.hashCode, |
| classRef: classRefForClosure, |
| ); |
| return result; |
| } |
| |
| /// Create an [Instance] for the given [remoteObject]. |
| /// |
| /// Does a remote eval to get instance information. Returns null if there |
| /// isn't a corresponding instance. For enumerable objects, [offset] and |
| /// [count] allow retrieving a sub-range of properties. |
| Future<Instance?> instanceFor( |
| RemoteObject? remoteObject, { |
| int? offset, |
| int? count, |
| }) async { |
| final primitive = _primitiveInstanceOrNull(remoteObject, offset, count); |
| if (primitive != null) { |
| return primitive; |
| } |
| final objectId = remoteObject?.objectId; |
| if (remoteObject == null || objectId == null) return null; |
| |
| final metaData = await metadataHelper.metaDataFor(remoteObject); |
| |
| final classRef = metaData?.classRef; |
| if (metaData == null || classRef == null) return null; |
| |
| switch (metaData.runtimeKind) { |
| case RuntimeObjectKind.function: |
| return _closureInstanceFor(remoteObject); |
| case RuntimeObjectKind.recordType: |
| return await _recordTypeInstanceFor( |
| metaData, |
| remoteObject, |
| offset: offset, |
| count: count, |
| ); |
| case RuntimeObjectKind.type: |
| return await _plainTypeInstanceFor( |
| metaData, |
| remoteObject, |
| offset: offset, |
| count: count, |
| ); |
| case RuntimeObjectKind.list: |
| return await _listInstanceFor( |
| metaData, |
| remoteObject, |
| offset: offset, |
| count: count, |
| ); |
| case RuntimeObjectKind.set: |
| return await _setInstanceFor( |
| metaData, |
| remoteObject, |
| offset: offset, |
| count: count, |
| ); |
| case RuntimeObjectKind.map: |
| return await _mapInstanceFor( |
| metaData, |
| remoteObject, |
| offset: offset, |
| count: count, |
| ); |
| case RuntimeObjectKind.record: |
| return await _recordInstanceFor( |
| metaData, |
| remoteObject, |
| offset: offset, |
| count: count, |
| ); |
| case RuntimeObjectKind.object: |
| case RuntimeObjectKind.nativeError: |
| case RuntimeObjectKind.nativeObject: |
| default: |
| return await _plainInstanceFor( |
| metaData, |
| remoteObject, |
| offset: offset, |
| count: count, |
| ); |
| } |
| } |
| |
| /// If [remoteObject] represents a primitive, return an [Instance] for it, |
| /// otherwise return null. |
| Instance? _primitiveInstanceOrNull( |
| RemoteObject? remoteObject, |
| int? offset, |
| int? count, |
| ) { |
| switch (remoteObject?.type ?? 'undefined') { |
| case 'string': |
| return _stringInstanceFor(remoteObject, offset, count); |
| case 'number': |
| return _primitiveInstance(InstanceKind.kDouble, remoteObject); |
| case 'boolean': |
| return _primitiveInstance(InstanceKind.kBool, remoteObject); |
| case 'undefined': |
| return _primitiveInstance(InstanceKind.kNull, remoteObject); |
| default: |
| return null; |
| } |
| } |
| |
| /// Create a bound field for [property] in an instance of [classRef]. |
| Future<BoundField> _fieldFor(Property property, ClassRef classRef) async { |
| final instance = await _instanceRefForRemote(property.value); |
| // TODO(annagrin): convert JS name to dart and fill missing information. |
| //https://github.com/dart-lang/sdk/issues/46723 |
| return BoundField( |
| name: property.name, |
| decl: FieldRef( |
| // TODO(grouma) - Convert JS name to Dart. |
| name: property.name, |
| declaredType: InstanceRef( |
| kind: InstanceKind.kType, |
| classRef: instance?.classRef, |
| identityHashCode: createId().hashCode, |
| id: createId(), |
| ), |
| owner: classRef, |
| isConst: false, |
| isFinal: false, |
| isStatic: false, |
| id: createId(), |
| ), |
| value: instance, |
| ); |
| } |
| |
| /// Create a plain instance of [classRef] from [remoteObject] and the JS |
| /// properties [properties]. |
| Future<Instance?> _plainInstanceFor( |
| ClassMetaData metaData, |
| RemoteObject remoteObject, { |
| int? offset, |
| int? count, |
| }) async { |
| final objectId = remoteObject.objectId; |
| if (objectId == null) return null; |
| |
| final fields = await _getInstanceFields( |
| metaData, |
| remoteObject, |
| offset: offset, |
| count: count, |
| ); |
| |
| final result = Instance( |
| kind: InstanceKind.kPlainInstance, |
| id: objectId, |
| identityHashCode: remoteObject.objectId.hashCode, |
| classRef: metaData.classRef, |
| fields: fields, |
| ); |
| return result; |
| } |
| |
| Future<List<BoundField>> _getInstanceFields( |
| ClassMetaData metaData, |
| RemoteObject remoteObject, { |
| int? offset, |
| int? count, |
| }) async { |
| final objectId = remoteObject.objectId; |
| if (objectId == null) throw StateError('Object id is null for instance'); |
| |
| final properties = await inspector.getProperties( |
| objectId, |
| offset: offset, |
| count: count, |
| length: metaData.length, |
| ); |
| |
| final dartProperties = await _dartFieldsFor(properties, remoteObject); |
| final boundFields = await Future.wait( |
| dartProperties |
| .map<Future<BoundField>>((p) => _fieldFor(p, metaData.classRef)), |
| ); |
| |
| return boundFields |
| .where((bv) => inspector.isDisplayableObject(bv.value)) |
| .toList() |
| ..sort(_compareBoundFields); |
| } |
| |
| int _compareBoundFields(BoundField a, BoundField b) { |
| final aName = a.decl?.name; |
| final bName = b.decl?.name; |
| if (aName == null) return bName == null ? 0 : -1; |
| if (bName == null) return 1; |
| return aName.compareTo(bName); |
| } |
| |
| /// The associations for a Dart Map or IdentityMap. |
| /// |
| /// Returns a range of [count] associations, if available, starting from |
| /// the [offset]. |
| /// |
| /// If [offset] is `null`, assumes 0 offset. |
| /// If [count] is `null`, return all fields starting from the offset. |
| Future<List<MapAssociation>> _mapAssociations( |
| RemoteObject map, { |
| int? offset, |
| int? count, |
| }) async { |
| // We do this in in awkward way because we want the keys and values, but we |
| // can't return things by value or some Dart objects will come back as |
| // values that we need to be RemoteObject, e.g. a List of int. |
| final expression = _jsRuntimeFunctionCall('getMapElements(this)'); |
| |
| final keysAndValues = await inspector.jsCallFunctionOn(map, expression, []); |
| final keys = await inspector.loadField(keysAndValues, 'keys'); |
| final values = await inspector.loadField(keysAndValues, 'values'); |
| final keysInstance = await instanceFor(keys, offset: offset, count: count); |
| final valuesInstance = |
| await instanceFor(values, offset: offset, count: count); |
| final associations = <MapAssociation>[]; |
| final keyElements = keysInstance?.elements; |
| final valueElements = valuesInstance?.elements; |
| if (keyElements != null && valueElements != null) { |
| Map.fromIterables(keyElements, valueElements).forEach((key, value) { |
| associations.add(MapAssociation(key: key, value: value)); |
| }); |
| } |
| return associations; |
| } |
| |
| /// Create a Map instance with class [classRef] from [remoteObject]. |
| /// |
| /// Returns an instance containing [count] associations, if available, |
| /// starting from the [offset]. |
| /// |
| /// If [offset] is `null`, assumes 0 offset. |
| /// If [count] is `null`, return all fields starting from the offset. |
| /// [length] is the expected length of the whole object, read from |
| /// the [ClassMetaData]. |
| Future<Instance?> _mapInstanceFor( |
| ClassMetaData metaData, |
| RemoteObject remoteObject, { |
| int? offset, |
| int? count, |
| }) async { |
| final objectId = remoteObject.objectId; |
| if (objectId == null) return null; |
| |
| // Maps are complicated, do an eval to get keys and values. |
| final associations = |
| await _mapAssociations(remoteObject, offset: offset, count: count); |
| final rangeCount = _calculateRangeCount( |
| count: count, |
| elementCount: associations.length, |
| length: metaData.length, |
| ); |
| return Instance( |
| identityHashCode: remoteObject.objectId.hashCode, |
| kind: InstanceKind.kMap, |
| id: objectId, |
| classRef: metaData.classRef, |
| length: metaData.length, |
| offset: offset, |
| count: rangeCount, |
| associations: associations, |
| ); |
| } |
| |
| /// Create a List instance of [classRef] from [remoteObject]. |
| /// |
| /// Returns an instance containing [count] elements, if available, |
| /// starting from the [offset]. |
| /// |
| /// If [offset] is `null`, assumes 0 offset. |
| /// If [count] is `null`, return all fields starting from the offset. |
| /// [length] is the expected length of the whole object, read from |
| /// the [ClassMetaData]. |
| Future<Instance?> _listInstanceFor( |
| ClassMetaData metaData, |
| RemoteObject remoteObject, { |
| int? offset, |
| int? count, |
| }) async { |
| final objectId = remoteObject.objectId; |
| if (objectId == null) return null; |
| |
| final elements = await _listElements( |
| remoteObject, |
| offset: offset, |
| count: count, |
| length: metaData.length, |
| ); |
| final rangeCount = _calculateRangeCount( |
| count: count, |
| elementCount: elements.length, |
| length: metaData.length, |
| ); |
| return Instance( |
| identityHashCode: remoteObject.objectId.hashCode, |
| kind: InstanceKind.kList, |
| id: objectId, |
| classRef: metaData.classRef, |
| length: metaData.length, |
| elements: elements, |
| offset: offset, |
| count: rangeCount, |
| ); |
| } |
| |
| /// The elements for a Dart List. |
| /// |
| /// Returns a range of [count] elements, if available, starting from |
| /// the [offset]. |
| /// |
| /// If [offset] is `null`, assumes 0 offset. |
| /// If [count] is `null`, return all fields starting from the offset. |
| /// [length] is the expected length of the whole object, read from |
| /// the [ClassMetaData]. |
| Future<List<InstanceRef?>> _listElements( |
| RemoteObject list, { |
| int? offset, |
| int? count, |
| int? length, |
| }) async { |
| final properties = await inspector.getProperties( |
| list.objectId!, |
| offset: offset, |
| count: count, |
| length: length, |
| ); |
| |
| // Filter out all non-indexed properties |
| final elements = _indexedListProperties(properties); |
| |
| final rangeCount = _calculateRangeCount( |
| count: count, |
| elementCount: elements.length, |
| length: length, |
| ); |
| final range = elements.sublist(0, rangeCount); |
| |
| return Future.wait( |
| range.map((element) => _instanceRefForRemote(element.value)), |
| ); |
| } |
| |
| /// Return elements of the list from [properties]. |
| /// |
| /// Ignore any non-elements like 'length', 'fixed$length', etc. |
| static List<Property> _indexedListProperties(List<Property> properties) => |
| properties |
| .where((p) => p.name != null && int.tryParse(p.name!) != null) |
| .toList(); |
| |
| /// The field names for a Dart record shape. |
| /// |
| /// Returns a range of [count] fields, if available, starting from |
| /// the [offset]. |
| /// |
| /// If [offset] is `null`, assumes 0 offset. |
| /// If [count] is `null`, return all fields starting from the offset. |
| /// The [shape] object describes the shape using `positionalCount` |
| /// and `named` fields. |
| /// |
| /// Returns list of field names for the record shape. |
| Future<List<dynamic>> _recordShapeFields( |
| RemoteObject shape, { |
| int? offset, |
| int? count, |
| }) async { |
| final positionalCountObject = |
| await inspector.loadField(shape, 'positionalCount'); |
| if (positionalCountObject == null || positionalCountObject.value is! int) { |
| _logger.warning( |
| 'Unexpected positional count from record: $positionalCountObject', |
| ); |
| return []; |
| } |
| |
| final namedObject = await inspector.loadField(shape, 'named'); |
| final positionalCount = positionalCountObject.value as int; |
| final positionalOffset = offset ?? 0; |
| final positionalAvailable = |
| _remainingCount(positionalOffset, positionalCount); |
| final positionalRangeCount = |
| min(positionalAvailable, count ?? positionalAvailable); |
| final positionalElements = [ |
| for (var i = positionalOffset + 1; |
| i <= positionalOffset + positionalRangeCount; |
| i++) |
| i, |
| ]; |
| |
| // Collect named fields in the requested range. |
| // Account for already collected positional fields. |
| final namedRangeOffset = |
| offset == null ? null : _remainingCount(positionalCount, offset); |
| final namedRangeCount = |
| count == null ? null : _remainingCount(positionalRangeCount, count); |
| final namedInstance = await instanceFor( |
| namedObject, |
| offset: namedRangeOffset, |
| count: namedRangeCount, |
| ); |
| final namedElements = |
| namedInstance?.elements?.map((e) => e.valueAsString) ?? []; |
| |
| return [ |
| ...positionalElements, |
| ...namedElements, |
| ]; |
| } |
| |
| /// The fields for a Dart Record. |
| /// |
| /// Returns a range of [count] fields, if available, starting from |
| /// the [offset]. |
| /// |
| /// If [offset] is `null`, assumes 0 offset. |
| /// If [count] is `null`, return all fields starting from the offset. |
| Future<List<BoundField>> _recordFields( |
| RemoteObject record, { |
| int? offset, |
| int? count, |
| }) async { |
| // We do this in in awkward way because we want the keys and values, but we |
| // can't return things by value or some Dart objects will come back as |
| // values that we need to be RemoteObject, e.g. a List of int. |
| final expression = _jsRuntimeFunctionCall('getRecordFields(this)'); |
| |
| final result = await inspector.jsCallFunctionOn(record, expression, []); |
| final fieldNameElements = |
| await _recordShapeFields(result, offset: offset, count: count); |
| |
| final valuesObject = await inspector.loadField(result, 'values'); |
| final valuesInstance = |
| await instanceFor(valuesObject, offset: offset, count: count); |
| final valueElements = valuesInstance?.elements ?? []; |
| |
| return _elementsToBoundFields(fieldNameElements, valueElements); |
| } |
| |
| /// Create a list of `BoundField`s from field [names] and [values]. |
| List<BoundField> _elementsToBoundFields( |
| List<dynamic> names, |
| List<dynamic> values, |
| ) { |
| if (names.length != values.length) { |
| _logger.warning('Bound field names and values are not the same length.'); |
| return []; |
| } |
| |
| final boundFields = <BoundField>[]; |
| Map.fromIterables(names, values).forEach((name, value) { |
| boundFields.add(BoundField(name: name, value: value)); |
| }); |
| return boundFields; |
| } |
| |
| static int _remainingCount(int collected, int requested) { |
| return requested < collected ? 0 : requested - collected; |
| } |
| |
| /// Create a Record instance with class [classRef] from [remoteObject]. |
| /// |
| /// Returns an instance containing [count] fields, if available, |
| /// starting from the [offset]. |
| /// |
| /// If [offset] is `null`, assumes 0 offset. |
| /// If [count] is `null`, return all fields starting from the offset. |
| /// [length] is the expected length of the whole object, read from |
| /// the [ClassMetaData]. |
| Future<Instance?> _recordInstanceFor( |
| ClassMetaData metaData, |
| RemoteObject remoteObject, { |
| int? offset, |
| int? count, |
| }) async { |
| final objectId = remoteObject.objectId; |
| if (objectId == null) return null; |
| // Records are complicated, do an eval to get names and values. |
| final fields = |
| await _recordFields(remoteObject, offset: offset, count: count); |
| final rangeCount = _calculateRangeCount( |
| count: count, |
| elementCount: fields.length, |
| length: metaData.length, |
| ); |
| return Instance( |
| identityHashCode: remoteObject.objectId.hashCode, |
| kind: InstanceKind.kRecord, |
| id: objectId, |
| classRef: metaData.classRef, |
| length: metaData.length, |
| offset: offset, |
| count: rangeCount, |
| fields: fields, |
| ); |
| } |
| |
| /// Create a RecordType instance with class [classRef] from [remoteObject]. |
| /// |
| /// Returns an instance containing [count] fields, if available, |
| /// starting from the [offset]. |
| /// |
| /// If [offset] is `null`, assumes 0 offset. |
| /// If [count] is `null`, return all fields starting from the offset. |
| /// [length] is the expected length of the whole object, read from |
| /// the [ClassMetaData]. |
| Future<Instance?> _recordTypeInstanceFor( |
| ClassMetaData metaData, |
| RemoteObject remoteObject, { |
| int? offset, |
| int? count, |
| }) async { |
| final objectId = remoteObject.objectId; |
| if (objectId == null) return null; |
| |
| // Records are complicated, do an eval to get names and values. |
| final fields = await _recordTypeFields( |
| remoteObject, |
| offset: offset, |
| count: count, |
| ); |
| final rangeCount = _calculateRangeCount( |
| count: count, |
| elementCount: fields.length, |
| length: metaData.length, |
| ); |
| return Instance( |
| identityHashCode: remoteObject.objectId.hashCode, |
| kind: InstanceKind.kRecordType, |
| id: objectId, |
| classRef: metaData.classRef, |
| length: metaData.length, |
| offset: offset, |
| count: rangeCount, |
| fields: fields, |
| ); |
| } |
| |
| /// The field types for a Dart RecordType. |
| /// |
| /// Returns a range of [count] field types, if available, starting from |
| /// the [offset]. |
| /// |
| /// If [offset] is `null`, assumes 0 offset. |
| /// If [count] is `null`, return all field types starting from the offset. |
| Future<List<BoundField>> _recordTypeFields( |
| RemoteObject record, { |
| int? offset, |
| int? count, |
| }) async { |
| // We do this in in awkward way because we want the names and types, but we |
| // can't return things by value or some Dart objects will come back as |
| // values that we need to be RemoteObject, e.g. a List of int. |
| final expression = _jsRuntimeFunctionCall('getRecordTypeFields(this)'); |
| |
| final result = await inspector.jsCallFunctionOn(record, expression, []); |
| final fieldNameElements = |
| await _recordShapeFields(result, offset: offset, count: count); |
| |
| final typesObject = await inspector.loadField(result, 'types'); |
| final typesInstance = |
| await instanceFor(typesObject, offset: offset, count: count); |
| final typeElements = typesInstance?.elements ?? []; |
| |
| return _elementsToBoundFields(fieldNameElements, typeElements); |
| } |
| |
| Future<Instance?> _setInstanceFor( |
| ClassMetaData metaData, |
| RemoteObject remoteObject, { |
| int? offset, |
| int? count, |
| }) async { |
| final length = metaData.length; |
| final objectId = remoteObject.objectId; |
| if (objectId == null) return null; |
| |
| final expression = _jsRuntimeFunctionCall('getSetElements(this)'); |
| |
| final result = |
| await inspector.jsCallFunctionOn(remoteObject, expression, []); |
| final entriesObject = await inspector.loadField(result, 'entries'); |
| final entriesInstance = |
| await instanceFor(entriesObject, offset: offset, count: count); |
| final elements = entriesInstance?.elements ?? []; |
| |
| final setInstance = Instance( |
| identityHashCode: remoteObject.objectId.hashCode, |
| kind: InstanceKind.kSet, |
| id: objectId, |
| classRef: metaData.classRef, |
| length: length, |
| elements: elements, |
| ); |
| |
| if (offset != null && offset > 0) { |
| setInstance.offset = offset; |
| } |
| if (length != null && elements.length < length) { |
| setInstance.count = elements.length; |
| } |
| |
| return setInstance; |
| } |
| |
| /// Create Type instance with class [classRef] from [remoteObject]. |
| /// |
| /// Collect information from the internal [remoteObject] and present |
| /// it as an instance of [Type] class. |
| /// |
| /// Returns an instance containing `hashCode` and `runtimeType` fields. |
| /// [length] is the expected length of the whole object, read from |
| /// the [ClassMetaData]. |
| Future<Instance?> _plainTypeInstanceFor( |
| ClassMetaData metaData, |
| RemoteObject remoteObject, { |
| int? offset, |
| int? count, |
| }) async { |
| final objectId = remoteObject.objectId; |
| if (objectId == null) return null; |
| |
| final fields = await _getInstanceFields( |
| metaData, |
| remoteObject, |
| offset: offset, |
| count: count, |
| ); |
| |
| return Instance( |
| identityHashCode: objectId.hashCode, |
| kind: InstanceKind.kType, |
| id: objectId, |
| classRef: metaData.classRef, |
| name: metaData.typeName, |
| length: metaData.length, |
| offset: offset, |
| count: count, |
| fields: fields, |
| ); |
| } |
| |
| /// Return the available count of elements in the requested range. |
| /// Return `null` if the range includes the whole object. |
| /// [count] is the range length requested by the `getObject` call. |
| /// [elementCount] is the number of elements in the runtime object. |
| /// [length] is the expected length of the whole object, read from |
| /// the [ClassMetaData]. |
| static int? _calculateRangeCount({ |
| int? count, |
| int? elementCount, |
| int? length, |
| }) { |
| if (count == null) return null; |
| if (elementCount == null) return null; |
| if (length == elementCount) return null; |
| return min(count, elementCount); |
| } |
| |
| /// Filter [allJsProperties] and return a list containing only those |
| /// that correspond to Dart fields on [remoteObject]. |
| /// |
| /// This only applies to objects with named fields, not Lists or Maps. |
| Future<List<Property>> _dartFieldsFor( |
| List<Property> allJsProperties, |
| RemoteObject remoteObject, |
| ) async { |
| // An expression to find the field names from the types, extract both |
| // private (named by symbols) and public (named by strings) and return them |
| // as a comma-separated single string, so we can return it by value and not |
| // need to make multiple round trips. |
| // |
| // For maps and lists it's more complicated. Treat the actual SDK versions |
| // of these as special. |
| final fieldNameExpression = |
| _jsRuntimeFunctionCall('getObjectFieldNames(this)'); |
| |
| final result = await inspector.jsCallFunctionOn( |
| remoteObject, |
| fieldNameExpression, |
| [], |
| returnByValue: true, |
| ); |
| final names = List<String>.from(result.value as List); |
| // TODO(#761): Better support for large collections. |
| return allJsProperties |
| .where((property) => names.contains(property.name)) |
| .toList(); |
| } |
| |
| /// Create an InstanceRef for an object, which may be a RemoteObject, or may |
| /// be something returned by value from Chrome, e.g. number, boolean, or |
| /// String. |
| Future<InstanceRef?> instanceRefFor(Object value) { |
| final remote = value is RemoteObject |
| ? value |
| : RemoteObject({'value': value, 'type': _chromeType(value)}); |
| return _instanceRefForRemote(remote); |
| } |
| |
| /// The Chrome type for a value. |
| String? _chromeType(Object? value) { |
| if (value == null) return null; |
| if (value is String) return 'string'; |
| if (value is num) return 'number'; |
| if (value is bool) return 'boolean'; |
| if (value is Function) return 'function'; |
| return 'object'; |
| } |
| |
| /// Create an [InstanceRef] for the given Chrome [remoteObject]. |
| Future<InstanceRef?> _instanceRefForRemote(RemoteObject? remoteObject) async { |
| // If we have a null result, treat it as a reference to null. |
| if (remoteObject == null) { |
| return kNullInstanceRef; |
| } |
| |
| switch (remoteObject.type) { |
| case 'string': |
| final stringValue = remoteObject.value as String?; |
| // TODO: Support truncation for long strings. |
| // TODO(#777): dartIdFor() will return an ID containing the entire |
| // string, even if we're truncating the string value here. |
| return InstanceRef( |
| identityHashCode: dartIdFor(remoteObject.value).hashCode, |
| id: dartIdFor(remoteObject.value), |
| classRef: classRefForString, |
| kind: InstanceKind.kString, |
| valueAsString: stringValue, |
| length: stringValue?.length, |
| ); |
| case 'number': |
| return _primitiveInstanceRef(InstanceKind.kDouble, remoteObject); |
| case 'boolean': |
| return _primitiveInstanceRef(InstanceKind.kBool, remoteObject); |
| case 'undefined': |
| return _primitiveInstanceRef(InstanceKind.kNull, remoteObject); |
| case 'object': |
| final objectId = remoteObject.objectId; |
| if (objectId == null) { |
| return _primitiveInstanceRef(InstanceKind.kNull, remoteObject); |
| } |
| final metaData = await metadataHelper.metaDataFor(remoteObject); |
| if (metaData == null) return null; |
| |
| return InstanceRef( |
| kind: metaData.kind, |
| id: objectId, |
| identityHashCode: objectId.hashCode, |
| classRef: metaData.classRef, |
| length: metaData.length, |
| name: metaData.typeName, |
| ); |
| case 'function': |
| final objectId = remoteObject.objectId; |
| if (objectId == null) { |
| return _primitiveInstanceRef(InstanceKind.kNull, remoteObject); |
| } |
| final functionMetaData = await FunctionMetaData.metaDataFor( |
| inspector.remoteDebugger, |
| remoteObject, |
| ); |
| // TODO(annagrin) - fill missing information. |
| // https://github.com/dart-lang/sdk/issues/46723 |
| return InstanceRef( |
| kind: InstanceKind.kClosure, |
| id: objectId, |
| identityHashCode: objectId.hashCode, |
| classRef: classRefForClosure, |
| closureFunction: FuncRef( |
| name: functionMetaData.name, |
| id: createId(), |
| owner: classRefForUnknown, |
| isConst: false, |
| isStatic: false, |
| implicit: false, |
| isGetter: false, |
| isSetter: false, |
| ), |
| closureContext: ContextRef(length: 0, id: createId()), |
| ); |
| default: |
| // Return null for an unsupported type. This is likely a JS construct. |
| return null; |
| } |
| } |
| } |
| |
| String _jsRuntimeFunctionCall(String expression) => ''' |
| function() { |
| const sdk = ${globalToolConfiguration.loadStrategy.loadModuleSnippet}('dart_sdk'); |
| const dart = sdk.dart; |
| return dart.$expression; |
| } |
| '''; |