// 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 'package:dwds/src/config/tool_configuration.dart'; import 'package:dwds/src/debugging/chrome_inspector.dart'; import 'package:dwds/src/debugging/metadata/class.dart'; import 'package:dwds/src/services/chrome/chrome_debug_exception.dart'; import 'package:dwds/src/utilities/shared.dart'; import 'package:vm_service/vm_service.dart'; import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; /// Keeps track of Dart classes available in the running application. class ChromeAppClassHelper { /// Map of class ID to [Class]. final _classes = {}; final ChromeAppInspector inspector; ChromeAppClassHelper(this.inspector) { final staticClasses = [ classRefForClosure, classRefForString, classRefForUnknown, ]; for (final classRef in staticClasses) { final classId = classRef.id; if (classId != null) { _classes[classId] = Class( name: classRef.name, isAbstract: false, isConst: false, library: null, interfaces: [], fields: [], functions: [], subclasses: [], id: classId, traceAllocations: false, ); } } } /// Returns the [Class] that corresponds to the provided [objectId]. /// /// If a corresponding class does not exist it will return null. Future forObjectId(String objectId) async { if (!objectId.startsWith('classes|')) return null; var clazz = _classes[objectId]; if (clazz != null) return clazz; final splitId = objectId.split('|'); final libraryId = splitId[1]; if (libraryId == 'null') { throw UnsupportedError('unknown library: $libraryId'); } final libraryRef = await inspector.libraryRefFor(libraryId); if (libraryRef == null) { throw Exception('Could not find library: $libraryId'); } final classRef = classRefFor(libraryId, splitId.last); clazz = await _constructClass(libraryRef, classRef); if (clazz == null) { throw Exception('Could not construct class: $classRef'); } return _classes[objectId] = clazz; } /// Constructs a [Class] instance for the provided [LibraryRef] and /// [ClassRef]. Future _constructClass( LibraryRef libraryRef, ClassRef classRef, ) async { final libraryUri = libraryRef.uri; final className = classRef.name; final classId = classRef.id; if (libraryUri == null || classId == null || className == null) return null; final expression = globalToolConfiguration.loadStrategy.dartRuntimeDebugger .getClassMetadataJsExpression(libraryUri, className); RemoteObject result; try { result = await inspector.remoteDebugger.evaluate( expression, returnByValue: true, contextId: await inspector.contextId, ); } on ExceptionDetails catch (e) { throw ChromeDebugException(e.json, evalContents: expression); } final classDescriptor = _mapify(result.value); final methodRefs = []; final methodDescriptors = _mapify(classDescriptor['methods']); methodDescriptors.forEach((name, descriptor) { final methodId = 'methods|$classId|$name'; methodRefs.add( FuncRef( id: methodId, name: name, owner: classRef, isConst: descriptor['isConst'] as bool? ?? false, isStatic: descriptor['isStatic'] as bool? ?? false, implicit: descriptor['isImplicit'] as bool? ?? false, isAbstract: descriptor['isAbstract'] as bool? ?? false, isGetter: descriptor['isGetter'] as bool? ?? false, isSetter: descriptor['isSetter'] as bool? ?? false, ), ); }); final fieldRefs = []; final fieldDescriptors = _mapify(classDescriptor['fields']); fieldDescriptors.forEach((name, descriptor) { final classMetaData = ClassMetaData( runtimeKind: RuntimeObjectKind.type, classRef: classRefFor( descriptor['classLibraryId'], descriptor['className'], ), ); fieldRefs.add( FieldRef( name: name, owner: classRef, declaredType: InstanceRef( identityHashCode: createId().hashCode, id: createId(), kind: classMetaData.kind, classRef: classMetaData.classRef, ), isConst: descriptor['isConst'] as bool? ?? false, isFinal: descriptor['isFinal'] as bool? ?? false, isStatic: descriptor['isStatic'] as bool? ?? false, id: createId(), ), ); }); final superClassLibraryId = classDescriptor['superClassLibraryId']; final superClassName = classDescriptor['superClassName']; final superClassRef = superClassName == null ? null : classRefFor(superClassLibraryId, superClassName); // TODO: Implement the rest of these // https://github.com/dart-lang/webdev/issues/176. return Class( name: classRef.name, isAbstract: false, isConst: false, library: libraryRef, interfaces: [], fields: fieldRefs, functions: methodRefs, subclasses: [], id: classId, traceAllocations: false, superClass: superClassRef, ); } Map _mapify(dynamic map) => (map as Map?) ?? {}; }