@@ -12,11 +12,14 @@ import 'package:dwds/src/utilities/conversions.dart';
12
12
import 'package:dwds/src/utilities/domain.dart' ;
13
13
import 'package:dwds/src/utilities/objects.dart' ;
14
14
import 'package:dwds/src/utilities/shared.dart' ;
15
+ import 'package:logging/logging.dart' ;
15
16
import 'package:vm_service/vm_service.dart' ;
16
17
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart' ;
17
18
18
19
/// Contains a set of methods for getting [Instance] s and [InstanceRef] s.
19
20
class InstanceHelper extends Domain {
21
+ final _logger = Logger ('InstanceHelper' );
22
+
20
23
InstanceHelper (AppInspectorInterface appInspector, this .debugger) {
21
24
inspector = appInspector;
22
25
}
@@ -112,7 +115,7 @@ class InstanceHelper extends Domain {
112
115
113
116
final classRef = metaData? .classRef;
114
117
if (metaData == null || classRef == null ) return null ;
115
- if (metaData.jsName == 'Function' ) {
118
+ if (metaData.isFunction ) {
116
119
return _closureInstanceFor (remoteObject);
117
120
}
118
121
@@ -122,6 +125,9 @@ class InstanceHelper extends Domain {
122
125
} else if (metaData.isSystemMap) {
123
126
return await _mapInstanceFor (classRef, remoteObject,
124
127
offset: offset, count: count, length: metaData.length);
128
+ } else if (metaData.isRecord) {
129
+ return await _recordInstanceFor (classRef, remoteObject,
130
+ offset: offset, count: count, length: metaData.length);
125
131
} else {
126
132
return await _plainInstanceFor (classRef, remoteObject,
127
133
offset: offset, count: count, length: metaData.length);
@@ -150,6 +156,7 @@ class InstanceHelper extends Domain {
150
156
Future <BoundField > _fieldFor (Property property, ClassRef classRef) async {
151
157
final instance = await _instanceRefForRemote (property.value);
152
158
return BoundField (
159
+ name: property.name,
153
160
decl: FieldRef (
154
161
// TODO(grouma) - Convert JS name to Dart.
155
162
name: property.name,
@@ -207,6 +214,12 @@ class InstanceHelper extends Domain {
207
214
}
208
215
209
216
/// The associations for a Dart Map or IdentityMap.
217
+ ///
218
+ /// Returns a range of [count] associations, if available, starting from
219
+ /// the [offset] .
220
+ ///
221
+ /// If [offset] is `null` , assumes 0 offset.
222
+ /// If [count] is `null` , return all fields starting from the offset.
210
223
Future <List <MapAssociation >> _mapAssociations (RemoteObject map,
211
224
{int ? offset, int ? count}) async {
212
225
// We do this in in awkward way because we want the keys and values, but we
@@ -247,6 +260,14 @@ class InstanceHelper extends Domain {
247
260
}
248
261
249
262
/// Create a Map instance with class [classRef] from [remoteObject] .
263
+ ///
264
+ /// Returns an instance containing [count] associations, if available,
265
+ /// starting from the [offset] .
266
+ ///
267
+ /// If [offset] is `null` , assumes 0 offset.
268
+ /// If [count] is `null` , return all fields starting from the offset.
269
+ /// [length] is the expected length of the whole object, read from
270
+ /// the [ClassMetaData] .
250
271
Future <Instance ?> _mapInstanceFor (
251
272
ClassRef classRef,
252
273
RemoteObject remoteObject, {
@@ -273,8 +294,15 @@ class InstanceHelper extends Domain {
273
294
..associations = associations;
274
295
}
275
296
276
- /// Create a List instance of [classRef] from [remoteObject] with the JS
277
- /// properties [properties] .
297
+ /// Create a List instance of [classRef] from [remoteObject] .
298
+ ///
299
+ /// Returns an instance containing [count] elements, if available,
300
+ /// starting from the [offset] .
301
+ ///
302
+ /// If [offset] is `null` , assumes 0 offset.
303
+ /// If [count] is `null` , return all fields starting from the offset.
304
+ /// [length] is the expected length of the whole object, read from
305
+ /// the [ClassMetaData] .
278
306
Future <Instance ?> _listInstanceFor (
279
307
ClassRef classRef,
280
308
RemoteObject remoteObject, {
@@ -301,6 +329,14 @@ class InstanceHelper extends Domain {
301
329
}
302
330
303
331
/// The elements for a Dart List.
332
+ ///
333
+ /// Returns a range of [count] elements, if available, starting from
334
+ /// the [offset] .
335
+ ///
336
+ /// If [offset] is `null` , assumes 0 offset.
337
+ /// If [count] is `null` , return all fields starting from the offset.
338
+ /// [length] is the expected length of the whole object, read from
339
+ /// the [ClassMetaData] .
304
340
Future <List <InstanceRef ?>> _listElements (
305
341
RemoteObject list, {
306
342
int ? offset,
@@ -322,6 +358,139 @@ class InstanceHelper extends Domain {
322
358
.map ((element) async => await _instanceRefForRemote (element.value)));
323
359
}
324
360
361
+ /// Return elements of the list from [properties] .
362
+ ///
363
+ /// Ignore any non-elements like 'length', 'fixed$length', etc.
364
+ static List <Property > _indexedListProperties (List <Property > properties) =>
365
+ properties
366
+ .where ((p) => p.name != null && int .tryParse (p.name! ) != null )
367
+ .toList ();
368
+
369
+ /// The fields for a Dart Record.
370
+ ///
371
+ /// Returns a range of [count] fields, if available, starting from
372
+ /// the [offset] .
373
+ ///
374
+ /// If [offset] is `null` , assumes 0 offset.
375
+ /// If [count] is `null` , return all fields starting from the offset.
376
+ Future <List <BoundField >> _recordFields (RemoteObject map,
377
+ {int ? offset, int ? count}) async {
378
+ // We do this in in awkward way because we want the keys and values, but we
379
+ // can't return things by value or some Dart objects will come back as
380
+ // values that we need to be RemoteObject, e.g. a List of int.
381
+ final expression = '''
382
+ function() {
383
+ var sdkUtils = ${globalLoadStrategy .loadModuleSnippet }('dart_sdk').dart;
384
+ var shape = sdkUtils.dloadRepl(this, "shape");
385
+ var positionalCount = sdkUtils.dloadRepl(shape, "positionals");
386
+ var named = sdkUtils.dloadRepl(shape, "named");
387
+ named = named == null? null: sdkUtils.dsendRepl(named, "toList", []);
388
+ var values = sdkUtils.dloadRepl(this, "values");
389
+ values = sdkUtils.dsendRepl(values, "toList", []);
390
+
391
+ return {
392
+ positionalCount: positionalCount,
393
+ named: named,
394
+ values: values
395
+ };
396
+ }
397
+ ''' ;
398
+ final result = await inspector.jsCallFunctionOn (map, expression, []);
399
+ final positionalCountObject =
400
+ await inspector.loadField (result, 'positionalCount' );
401
+ if (positionalCountObject == null || positionalCountObject.value is ! int ) {
402
+ _logger.warning (
403
+ 'Unexpected positional count from record: $positionalCountObject ' );
404
+ return [];
405
+ }
406
+
407
+ final namedObject = await inspector.loadField (result, 'named' );
408
+ final valuesObject = await inspector.loadField (result, 'values' );
409
+
410
+ // Collect positional fields in the requested range.
411
+ final positionalCount = positionalCountObject.value as int ;
412
+ final positionalOffset = offset ?? 0 ;
413
+ final positionalAvailable =
414
+ _remainingCount (positionalOffset, positionalCount);
415
+ final positionalRangeCount =
416
+ min (positionalAvailable, count ?? positionalAvailable);
417
+ final positionalElements = [
418
+ for (var i = positionalOffset + 1 ;
419
+ i <= positionalOffset + positionalRangeCount;
420
+ i++ )
421
+ i
422
+ ];
423
+
424
+ // Collect named fields in the requested range.
425
+ // Account for already collected positional fields.
426
+ final namedRangeOffset =
427
+ offset == null ? null : _remainingCount (positionalCount, offset);
428
+ final namedRangeCount =
429
+ count == null ? null : _remainingCount (positionalRangeCount, count);
430
+ final namedInstance = await instanceFor (namedObject,
431
+ offset: namedRangeOffset, count: namedRangeCount);
432
+ final namedElements =
433
+ namedInstance? .elements? .map ((e) => e.valueAsString) ?? [];
434
+
435
+ final fieldNameElements = [
436
+ ...positionalElements,
437
+ ...namedElements,
438
+ ];
439
+
440
+ final valuesInstance =
441
+ await instanceFor (valuesObject, offset: offset, count: count);
442
+ final valueElements = valuesInstance? .elements ?? [];
443
+
444
+ if (fieldNameElements.length != valueElements.length) {
445
+ _logger.warning ('Record fields and values are not the same length.' );
446
+ return [];
447
+ }
448
+
449
+ final fields = < BoundField > [];
450
+ Map .fromIterables (fieldNameElements, valueElements).forEach ((key, value) {
451
+ fields.add (BoundField (name: key, value: value));
452
+ });
453
+ return fields;
454
+ }
455
+
456
+ static int _remainingCount (int collected, int requested) {
457
+ return requested < collected ? 0 : requested - collected;
458
+ }
459
+
460
+ /// Create a Record instance with class [classRef] from [remoteObject] .
461
+ ///
462
+ /// Returns an instance containing [count] fields, if available,
463
+ /// starting from the [offset] .
464
+ ///
465
+ /// If [offset] is `null` , assumes 0 offset.
466
+ /// If [count] is `null` , return all fields starting from the offset.
467
+ /// [length] is the expected length of the whole object, read from
468
+ /// the [ClassMetaData] .
469
+ Future <Instance ?> _recordInstanceFor (
470
+ ClassRef classRef,
471
+ RemoteObject remoteObject, {
472
+ int ? offset,
473
+ int ? count,
474
+ int ? length,
475
+ }) async {
476
+ final objectId = remoteObject.objectId;
477
+ if (objectId == null ) return null ;
478
+ // Records are complicated, do an eval to get names and values.
479
+ final fields =
480
+ await _recordFields (remoteObject, offset: offset, count: count);
481
+ final rangeCount = _calculateRangeCount (
482
+ count: count, elementCount: fields.length, length: length);
483
+ return Instance (
484
+ identityHashCode: remoteObject.objectId.hashCode,
485
+ kind: InstanceKind .kRecord,
486
+ id: objectId,
487
+ classRef: classRef)
488
+ ..length = length
489
+ ..offset = offset
490
+ ..count = rangeCount
491
+ ..fields = fields;
492
+ }
493
+
325
494
/// Return the available count of elements in the requested range.
326
495
/// Return `null` if the range includes the whole object.
327
496
/// [count] is the range length requested by the `getObject` call.
@@ -336,14 +505,6 @@ class InstanceHelper extends Domain {
336
505
return min (count, elementCount);
337
506
}
338
507
339
- /// Return elements of the list from [properties] .
340
- ///
341
- /// Ignore any non-elements like 'length', 'fixed$length', etc.
342
- static List <Property > _indexedListProperties (List <Property > properties) =>
343
- properties
344
- .where ((p) => p.name != null && int .tryParse (p.name! ) != null )
345
- .toList ();
346
-
347
508
/// Filter [allJsProperties] and return a list containing only those
348
509
/// that correspond to Dart fields on [remoteObject] .
349
510
///
@@ -461,6 +622,14 @@ class InstanceHelper extends Domain {
461
622
classRef: metaData.classRef)
462
623
..length = metaData.length;
463
624
}
625
+ if (metaData.isRecord) {
626
+ return InstanceRef (
627
+ kind: InstanceKind .kRecord,
628
+ id: objectId,
629
+ identityHashCode: remoteObject.objectId.hashCode,
630
+ classRef: metaData.classRef)
631
+ ..length = metaData.length;
632
+ }
464
633
return InstanceRef (
465
634
kind: InstanceKind .kPlainInstance,
466
635
id: objectId,
0 commit comments