blob: 025b57f35eca6cfa47ea355d03bc364fedf2dc1f [file] [log] [blame] [edit]
// 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:collection/collection.dart';
import 'package:dwds/src/config/tool_configuration.dart';
import 'package:dwds/src/debugging/metadata/class.dart';
import 'package:dwds/src/services/chrome_debug_exception.dart';
import 'package:dwds/src/utilities/domain.dart';
import 'package:logging/logging.dart';
import 'package:vm_service/vm_service.dart';
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
/// Keeps track of Dart libraries available in the running application.
class LibraryHelper extends Domain {
final Logger _logger = Logger('LibraryHelper');
/// Map of library ID to [Library].
final _librariesById = <String, Library>{};
/// Map of libraryRef ID to [LibraryRef].
final _libraryRefsById = <String, LibraryRef>{};
LibraryRef? _rootLib;
LibraryHelper(AppInspectorInterface appInspector) {
inspector = appInspector;
}
Future<LibraryRef> get rootLib async {
if (_rootLib != null) return _rootLib!;
// TODO: read entrypoint from app metadata.
// Issue: https://github.com/dart-lang/webdev/issues/1290
final libraries = await libraryRefs;
if (globalToolConfiguration.loadStrategy.appEntrypoint != null) {
_rootLib = libraries.firstWhereOrNull(
(lib) =>
Uri.parse(lib.uri ?? '') ==
globalToolConfiguration.loadStrategy.appEntrypoint,
);
}
_rootLib = _rootLib ??
libraries.firstWhereOrNull(
(lib) => lib.name?.contains('org-dartlang') ?? false,
);
_rootLib = _rootLib ??
libraries
.firstWhereOrNull((lib) => lib.name?.contains('main') ?? false);
_rootLib = _rootLib ?? (libraries.isNotEmpty ? libraries.last : null);
return _rootLib!;
}
/// Returns all libraryRefs in the app.
///
/// Note this can return a cached result.
Future<List<LibraryRef>> get libraryRefs async {
if (_libraryRefsById.isNotEmpty) return _libraryRefsById.values.toList();
final libraries = await globalToolConfiguration.loadStrategy
.metadataProviderFor(inspector.appConnection.request.entrypointPath)
.libraries;
for (var library in libraries) {
_libraryRefsById[library] =
LibraryRef(id: library, name: library, uri: library);
}
return _libraryRefsById.values.toList();
}
Future<Library?> libraryFor(LibraryRef libraryRef) async {
final libraryId = libraryRef.id;
if (libraryId == null) return null;
final library =
_librariesById[libraryId] ?? await _constructLibrary(libraryRef);
if (library == null) return null;
return _librariesById[libraryId] = library;
}
Future<LibraryRef?> libraryRefFor(String objectId) async {
if (_libraryRefsById.isEmpty) await libraryRefs;
return _libraryRefsById[objectId];
}
Future<Library?> _constructLibrary(LibraryRef libraryRef) async {
final libraryId = libraryRef.id;
final libraryUri = libraryRef.uri;
if (libraryId == null || libraryUri == null) return null;
// Fetch information about all the classes in this library.
final expression = '''
(function() {
const sdk = ${globalToolConfiguration.loadStrategy.loadModuleSnippet}('dart_sdk');
const dart = sdk.dart;
return dart.getLibraryMetadata('$libraryUri');
})()
''';
RemoteObject? result;
try {
result = await inspector.jsEvaluate(expression, returnByValue: true);
} on ChromeDebugException catch (_) {
// Unreferenced libraries are not loaded at runtime,
// return empty library object for consistency among
// VM Service implementations.
// TODO: Collect library and class information from debug symbols.
_logger.warning('Library ${libraryRef.uri} is not loaded. '
'This can happen for unreferenced libraries.');
}
final classRefs = <ClassRef>[];
if (result != null) {
final classNames = result.value as List;
for (final className in classNames) {
final classMetaData = ClassMetaData(
runtimeKind: RuntimeObjectKind.type,
classRef: classRefFor(
libraryRef.id,
className,
),
);
classRefs.add(classMetaData.classRef);
}
}
return Library(
name: libraryRef.name,
uri: libraryRef.uri,
debuggable: true,
dependencies: [],
scripts: await inspector.scriptRefsForLibrary(libraryId),
variables: [],
functions: [],
classes: classRefs,
id: libraryId,
);
}
}