blob: 285dbc54c34a0eacddcdad1c6ab0542a33e6ab3e [file] [log] [blame]
// 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:async';
import 'package:dwds/src/utilities/shared.dart';
import 'package:vm_service/vm_service.dart';
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
import '../utilities/objects.dart';
import 'debugger.dart';
import 'metadata.dart';
import 'remote_debugger.dart';
/// Chrome doesn't give us an objectId for a String. So we use the string
/// as its own ID, with a prefix.
///
/// This should not be confused with any
/// other object Ids, as those will be Chrome objectIds, which are
/// opaque, but are JSON serialized objects of the form
/// "{\"injectedScriptId\":1,\"id\":1}".
const _prefixForStringIds = '#StringInstanceRef#';
/// Creates an [InstanceRef] for a primitive [RemoteObject].
InstanceRef _primitiveInstance(String kind, RemoteObject remoteObject) {
var classRef = ClassRef()
// TODO(grouma) - is this ID correct?
..id = 'dart:core:${remoteObject?.type}'
..name = kind;
return InstanceRef()
..valueAsString = '${remoteObject?.value}'
..classRef = classRef
..kind = kind;
}
/// A hard-coded ClassRef for the String class.
final _classRefForString = ClassRef()
..id = 'dart:core:String'
..name = InstanceKind.kString;
/// A hard-coded ClassRef for the Closure class.
// TODO(grouma) - orgnaize our static classRefs better.
final _classRefForClosure = ClassRef()
..name = 'Closure'
..id = createId();
/// Contains a set of methods for getting [Instance]s and [InstanceRef]s.
class InstanceHelper {
final Debugger _debugger;
final RemoteDebugger _remoteDebugger;
InstanceHelper(this._debugger, this._remoteDebugger);
Future<Instance> _stringInstanceFor(RemoteObject remoteObject) async {
var actualString =
remoteObject.objectId.substring(_prefixForStringIds.length);
return Instance()
..kind = InstanceKind.kString
..classRef = _classRefForString
..valueAsString = actualString
..length = actualString.length;
}
Future<Instance> _closureInstanceFor(RemoteObject remoteObject) async {
var functionMetaData =
await FunctionMetaData.metaDataFor(_remoteDebugger, remoteObject);
var result = Instance()
..kind = InstanceKind.kClosure
..id = remoteObject.objectId
..closureFunction = (FuncRef()
..name = functionMetaData.name
..id = createId()
// TODO(grouma) - fill these in.
//..owner = ?
//..isConst = ?
//..isStatic = ?
)
..closureContext = ContextRef()
..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.
Future<Instance> instanceFor(RemoteObject remoteObject) async {
if (remoteObject.objectId.startsWith(_prefixForStringIds)) {
return _stringInstanceFor(remoteObject);
}
var metaData =
await ClassMetaData.metaDataFor(_remoteDebugger, remoteObject);
if (metaData.name == 'Function') {
return _closureInstanceFor(remoteObject);
} else {
var classRef = ClassRef()
..id = metaData.id
..name = metaData.name;
var properties = await _debugger.getProperties(remoteObject.objectId);
var dartProperties = await dartPropertiesFor(properties, remoteObject);
var fields = await Future.wait(
dartProperties.map<Future<BoundField>>((property) async {
var instance = await instanceRefFor(property.value);
return BoundField()
..decl = (FieldRef()
// TODO(grouma) - Convert JS name to Dart.
..name = property.name
..declaredType = (InstanceRef()
..type = InstanceKind.kType
..classRef = instance.classRef)
..owner = classRef)
..value = instance;
}));
fields.sort((a, b) => a.decl.name.compareTo(b.decl.name));
var result = Instance()
..kind = InstanceKind.kPlainInstance
..id = remoteObject.objectId
..fields = fields
..classRef = classRef;
return result;
}
}
/// Filter [allJsProperties] and return a list containing only those
/// that correspond to Dart fields on the object.
Future<List<Property>> dartPropertiesFor(
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.
// TODO(alanknight): Handle superclass fields.
final fieldNameExpression = '''function() {
const sdk_utils = $loadModule("dart_sdk").dart;
const fields = sdk_utils.getFields(sdk_utils.getType(this));
const privateFields = Object.getOwnPropertySymbols(fields);
const nonSymbolNames = privateFields.map(sym => sym.description);
const publicFieldNames = Object.getOwnPropertyNames(fields);
return nonSymbolNames.concat(publicFieldNames).join(',');
}
''';
var allNames = (await _debugger.inspector
.callFunctionOn(remoteObject, fieldNameExpression, []))
.value as String;
var names = allNames.split(',');
return allJsProperties
.where((property) => names.contains(property.name))
.toList();
}
/// Create an [InstanceRef] for the given Chrome [remoteObject].
Future<InstanceRef> instanceRefFor(RemoteObject remoteObject) async {
// If we have a null result, treat it as a reference to null.
if (remoteObject == null) {
return _primitiveInstance(InstanceKind.kNull, remoteObject);
}
switch (remoteObject.type) {
case 'string':
return InstanceRef()
// See comment for [_prefixForStringIds].
..id = '$_prefixForStringIds${remoteObject.value}'
..valueAsString = remoteObject.value as String
..classRef = _classRefForString
..kind = InstanceKind.kString;
case 'number':
return _primitiveInstance(InstanceKind.kDouble, remoteObject);
case 'boolean':
return _primitiveInstance(InstanceKind.kBool, remoteObject);
case 'undefined':
return _primitiveInstance(InstanceKind.kNull, remoteObject);
case 'object':
if (remoteObject.type == 'object' && remoteObject.objectId == null) {
return _primitiveInstance(InstanceKind.kNull, remoteObject);
}
var metaData =
await ClassMetaData.metaDataFor(_remoteDebugger, remoteObject);
if (metaData == null) return null;
return InstanceRef()
..kind = InstanceKind.kPlainInstance
..id = remoteObject.objectId
..classRef = (ClassRef()
..name = metaData.name
..id = metaData.id);
case 'function':
return InstanceRef()
..kind = InstanceKind.kClosure
..id = remoteObject.objectId
..classRef = _classRefForClosure;
default:
// Return null for an unsupported type. This is likely a JS construct.
return null;
}
}
}