blob: f43f9c5524f1544550b17bdf923e2b17c250163c [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:path/path.dart' as p;
import '../services/chrome_proxy_service.dart';
import '../utilities/dart_uri.dart';
import 'remote_debugger.dart';
/// Contains meta data and helpful methods for DDC modules.
class Modules {
final String _root;
final RemoteDebugger _remoteDebugger;
// The Dart server path to containing module.
final _sourceToModule = <String, String>{};
// The Chrome script ID to corresponding module.
final _scriptIdToModule = <String, String>{};
// The module to corresponding Chrome script ID.
final _moduleToScriptId = <String, String>{};
final _moduleExtensionCompleter = Completer<String>();
var _initializedCompleter = Completer();
Modules(this._remoteDebugger, this._root);
/// Completes with the module extension i.e. `.ddc.js` or `.ddk.js`.
///
/// We use the script parsed events from Chrome to determine this information.
// TODO(grouma) - Do something better here.
Future<String> get moduleExtension => _moduleExtensionCompleter.future;
/// Initializes the mapping from source to module.
///
/// Intended to be called multiple times throughout the development workflow,
/// e.g. after a hot-reload.
void initialize() {
_initializedCompleter = Completer();
_initializeMapping();
}
/// Returns the module for the Chrome script ID.
Future<String> moduleForScriptId(String scriptId) async =>
_scriptIdToModule[scriptId];
/// Returns the Chrome script ID for the provided module.
Future<String> scriptIdForModule(String module) async =>
_moduleToScriptId[module];
/// Returns the containing module for the provided Dart server path.
Future<String> moduleForSource(String serverPath) async {
await _initializedCompleter.future;
return _sourceToModule[serverPath];
}
/// Checks if the [url] correspond to a module and stores meta data.
Future<Null> noteModule(String url, String scriptId) async {
if (url == null || !(url.endsWith('.ddc.js') || url.endsWith('.ddk.js'))) {
return;
}
// TODO(grouma) - This is wonky. Find a better way.
if (!_moduleExtensionCompleter.isCompleted) {
if (url.endsWith('.ddc.js')) {
_moduleExtensionCompleter.complete('.ddc.js');
} else {
_moduleExtensionCompleter.complete('.ddk.js');
}
}
// Remove the DDC extension (e.g. .ddc.js) from the path.
var module = p
.withoutExtension(p.withoutExtension(Uri.parse(url).path))
.substring(1);
_scriptIdToModule[scriptId] = module;
_moduleToScriptId[module] = scriptId;
}
/// Initializes [_sourceToModule].
Future<void> _initializeMapping() async {
var expression = '''
(function() {
var dart = require('dart_sdk').dart;
var result = {};
dart.getModuleNames().forEach(function(module){
Object.keys(dart.getModuleLibraries(module)).forEach(
function(script){
result[script] = '/' + module;
});
});
return result;
})();
''';
var response = await _remoteDebugger.sendCommand('Runtime.evaluate',
params: {'expression': expression, 'returnByValue': true});
handleErrorIfPresent(response);
var value = response.result['result']['value'] as Map<String, dynamic>;
for (var dartScript in value.keys) {
if (!dartScript.endsWith('.dart')) continue;
var scriptUri = Uri.parse(dartScript);
var moduleUri = Uri.parse(value[dartScript] as String);
// The module uris returned by the expression contain the root. Rewrite
// the uris so that DartUri properly accounts for this fact.
if (scriptUri.scheme == 'org-dartlang-app') {
moduleUri = moduleUri.replace(scheme: 'org-dartlang-app');
} else if (scriptUri.scheme == 'package') {
moduleUri = moduleUri.replace(
scheme: 'package', path: moduleUri.path.split('/packages/').last);
}
// TODO(grouma) - handle G3 scheme.
_sourceToModule[DartUri(dartScript, _root).serverPath] =
DartUri(moduleUri.toString()).serverPath;
}
_initializedCompleter.complete();
}
}