blob: d71b434ed068d665f95c89791ee0a6439c153f1e [file] [log] [blame]
// Copyright (c) 2015, 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.
part of vmservice_io;
_sanitizeWindowsPath(path) {
// For Windows we need to massage the paths a bit according to
// http://blogs.msdn.com/b/ie/archive/2006/12/06/file-uris-in-windows.aspx
//
// Convert
// C:\one\two\three
// to
// /C:/one/two/three
if (_isWindows == false) {
// Do nothing when not running Windows.
return path;
}
var fixedPath = "${path.replaceAll('\\', '/')}";
if ((path.length > 2) && (path[1] == ':')) {
// Path begins with a drive letter.
return '/$fixedPath';
}
return fixedPath;
}
_trimWindowsPath(path) {
// Convert /X:/ to X:/.
if (_isWindows == false) {
// Do nothing when not running Windows.
return path;
}
if (!path.startsWith('/') || (path.length < 3)) {
return path;
}
// Match '/?:'.
if ((path[0] == '/') && (path[2] == ':')) {
// Remove leading '/'.
return path.substring(1);
}
return path;
}
// Ensure we have a trailing slash character.
_enforceTrailingSlash(uri) {
if (!uri.endsWith('/')) {
return '$uri/';
}
return uri;
}
class FileRequest {
final SendPort sp;
final int tag;
final Uri uri;
final Uri resolvedUri;
final String libraryUrl;
FileRequest(this.sp, this.tag, this.uri, this.resolvedUri, this.libraryUrl);
}
@pragma("vm.entry-point")
bool _traceLoading = false;
@pragma("vm.entry-point")
bool _deterministic = false;
// State associated with the isolate that is used for loading.
class IsolateLoaderState extends IsolateEmbedderData {
IsolateLoaderState(this.isolateId);
final int isolateId;
bool _dead = false;
SendPort sp;
void init(String packageRootFlag, String packagesConfigFlag,
String workingDirectory, String rootScript) {
if (_dead) {
return;
}
// _workingDirectory must be set first.
_workingDirectory = new Uri.directory(workingDirectory);
if (rootScript != null) {
_rootScript = Uri.parse(rootScript);
}
// If the --package-root flag was passed.
if (packageRootFlag != null) {
_setPackageRoot(packageRootFlag);
}
// If the --packages flag was passed.
if (packagesConfigFlag != null) {
_setPackagesConfig(packagesConfigFlag);
}
}
void updatePackageMap(String packagesConfigFlag) {
if (packagesConfigFlag == null) {
return;
}
_packageMap = null;
_setPackagesConfig(packagesConfigFlag);
}
void cleanup() {
_dead = true;
if (_packagesPort != null) {
_packagesPort.close();
_packagesPort = null;
}
}
// The working directory when the embedder started.
Uri _workingDirectory;
// The root script's uri.
Uri _rootScript;
// Packages are either resolved looking up in a map or resolved from within a
// package root.
bool get _packagesReady =>
(_packageRoot != null) ||
(_packageMap != null) ||
(_packageError != null);
// Error string set if there was an error resolving package configuration.
// For example not finding a .packages file or packages/ directory, malformed
// .packages file or any other related error.
String _packageError = null;
// The directory to look in to resolve "package:" scheme URIs. By default it
// is the 'packages' directory right next to the script.
Uri _packageRoot = null;
// The map describing how certain package names are mapped to Uris.
Uri _packageConfig = null;
Map<String, Uri> _packageMap = null;
// We issue only 16 concurrent calls to File.readAsBytes() to stay within
// platform-specific resource limits (e.g. max open files). The rest go on
// _fileRequestQueue and are processed when we can safely issue them.
static final int _maxFileRequests = _deterministic ? 1 : 16;
int currentFileRequests = 0;
final List<FileRequest> _fileRequestQueue = new List<FileRequest>();
bool get shouldIssueFileRequest => currentFileRequests < _maxFileRequests;
void enqueueFileRequest(FileRequest fr) {
_fileRequestQueue.add(fr);
}
FileRequest dequeueFileRequest() {
if (_fileRequestQueue.length == 0) {
return null;
}
return _fileRequestQueue.removeAt(0);
}
_setPackageRoot(String packageRoot) {
packageRoot = _sanitizeWindowsPath(packageRoot);
if (packageRoot.startsWith('file:') ||
packageRoot.startsWith('http:') ||
packageRoot.startsWith('https:')) {
packageRoot = _enforceTrailingSlash(packageRoot);
_packageRoot = _workingDirectory.resolve(packageRoot);
} else {
packageRoot = _sanitizeWindowsPath(packageRoot);
packageRoot = _trimWindowsPath(packageRoot);
_packageRoot =
_workingDirectory.resolveUri(new Uri.directory(packageRoot));
}
}
_setPackagesConfig(String packagesParam) {
var packagesName = _sanitizeWindowsPath(packagesParam);
var packagesUri = Uri.parse(packagesName);
if (packagesUri.scheme == '') {
// Script does not have a scheme, assume that it is a path,
// resolve it against the working directory.
packagesUri = _workingDirectory.resolveUri(packagesUri);
}
_requestPackagesMap(packagesUri);
_pendingPackageLoads.add(() {
// Dummy action.
});
}
// Handling of access to the package root or package map from user code.
_triggerPackageResolution(action) {
if (_packagesReady) {
// Packages are ready. Execute the action now.
action();
} else {
if (_pendingPackageLoads.isEmpty) {
// Package resolution has not been setup yet, and this is the first
// request for package resolution & loading.
_requestPackagesMap();
}
// Register the action for when the package resolution is ready.
_pendingPackageLoads.add(action);
}
}
// A list of callbacks which should be invoked after the package map has been
// loaded.
List<Function> _pendingPackageLoads = [];
// Given a uri with a 'package' scheme, return a Uri that is prefixed with
// the package root or resolved relative to the package configuration.
Uri _resolvePackageUri(Uri uri) {
assert(uri.scheme == "package");
assert(_packagesReady);
if (uri.host.isNotEmpty) {
var path = '${uri.host}${uri.path}';
var right = 'package:$path';
var wrong = 'package://$path';
throw "URIs using the 'package:' scheme should look like "
"'$right', not '$wrong'.";
}
var packageNameEnd = uri.path.indexOf('/');
if (packageNameEnd == 0) {
// Package URIs must have a non-empty package name (not start with "/").
throw "URIS using the 'package:' scheme should look like "
"'package:packageName${uri.path}', not 'package:${uri.path}'";
}
if (_traceLoading) {
_log('Resolving package with uri path: ${uri.path}');
}
var resolvedUri;
if (_packageError != null) {
if (_traceLoading) {
_log("Resolving package with pending resolution error: $_packageError");
}
throw _packageError;
} else if (_packageRoot != null) {
resolvedUri = _packageRoot.resolve(uri.path);
} else {
if (packageNameEnd < 0) {
// Package URIs must have a path after the package name, even if it's
// just "/".
throw "URIS using the 'package:' scheme should look like "
"'package:${uri.path}/', not 'package:${uri.path}'";
}
var packageName = uri.path.substring(0, packageNameEnd);
var mapping = _packageMap[packageName];
if (_traceLoading) {
_log("Mapped '$packageName' package to '$mapping'");
}
if (mapping == null) {
throw "No mapping for '$packageName' package when resolving '$uri'.";
}
var path;
assert(uri.path.length > packageName.length);
path = uri.path.substring(packageName.length + 1);
if (_traceLoading) {
_log("Path to be resolved in package: $path");
}
resolvedUri = mapping.resolve(path);
}
if (_traceLoading) {
_log("Resolved '$uri' to '$resolvedUri'.");
}
return resolvedUri;
}
RawReceivePort _packagesPort;
void _requestPackagesMap([Uri packageConfig]) {
if (_packagesPort != null) {
// Already scheduled.
return;
}
// Create a port to receive the packages map on.
_packagesPort = new RawReceivePort(_handlePackagesReply);
var sp = _packagesPort.sendPort;
if (packageConfig != null) {
// Explicitly specified .packages path.
_handlePackagesRequest(sp, _traceLoading, -2, packageConfig);
} else {
// Search for .packages starting at the root script.
_handlePackagesRequest(sp, _traceLoading, -1, _rootScript);
}
if (_traceLoading) {
_log("Requested packages map for '$_rootScript'.");
}
}
void _handlePackagesReply(msg) {
if (_packagesPort == null) {
return;
}
// Make sure to close the _packagePort before any other action.
_packagesPort.close();
_packagesPort = null;
if (_traceLoading) {
_log("Got packages reply: $msg");
}
if (msg is String) {
if (_traceLoading) {
_log("Got failure response on package port: '$msg'");
}
// Remember the error message.
_packageError = msg;
} else if (msg is List) {
if (msg.length == 1) {
if (_traceLoading) {
_log("Received package root: '${msg[0]}'");
}
_packageRoot = Uri.parse(msg[0]);
} else {
// First entry contains the location of the loaded .packages file.
assert((msg.length % 2) == 0);
assert(msg.length >= 2);
assert(msg[1] == null);
_packageConfig = Uri.parse(msg[0]);
_packageMap = new Map<String, Uri>();
for (var i = 2; i < msg.length; i += 2) {
// TODO(iposva): Complain about duplicate entries.
_packageMap[msg[i]] = Uri.parse(msg[i + 1]);
}
if (_traceLoading) {
_log("Setup package map: $_packageMap");
}
}
} else {
_packageError = "Bad type of packages reply: ${msg.runtimeType}";
if (_traceLoading) {
_log(_packageError);
}
}
// Resolve all pending package loads now that we know how to resolve them.
while (_pendingPackageLoads.length > 0) {
// Order does not matter as we queue all of the requests up right now.
var req = _pendingPackageLoads.removeLast();
// Call the registered closure, to handle the delayed action.
req();
}
// Reset the pending package loads to empty. So that we eventually can
// finish loading.
_pendingPackageLoads = [];
}
}
_log(msg) {
print("% $msg");
}
var _httpClient;
// Send a response to the requesting isolate.
void _sendResourceResponse(SendPort sp, int tag, Uri uri, Uri resolvedUri,
String libraryUrl, dynamic data) {
assert((data is List<int>) || (data is String));
var msg = new List(5);
if (data is String) {
// We encountered an error, flip the sign of the tag to indicate that.
tag = -tag;
if (libraryUrl == null) {
data = 'Could not load "$uri": $data';
} else {
data = 'Could not import "$uri" from "$libraryUrl": $data';
}
}
msg[0] = tag;
msg[1] = uri.toString();
msg[2] = resolvedUri.toString();
msg[3] = libraryUrl;
msg[4] = data;
sp.send(msg);
}
// Send a response to the requesting isolate.
void _sendExtensionImportResponse(
SendPort sp, Uri uri, String libraryUrl, String resolvedUri) {
var msg = new List(5);
int tag = _Dart_kImportExtension;
if (resolvedUri == null) {
// We could not resolve the dart-ext: uri.
tag = -tag;
resolvedUri = 'Could not resolve "$uri" from "$libraryUrl"';
}
msg[0] = tag;
msg[1] = uri.toString();
msg[2] = resolvedUri;
msg[3] = libraryUrl;
msg[4] = resolvedUri;
sp.send(msg);
}
void _loadHttp(
SendPort sp, int tag, Uri uri, Uri resolvedUri, String libraryUrl) {
if (_httpClient == null) {
_httpClient = new HttpClient()..maxConnectionsPerHost = 6;
}
_httpClient
.getUrl(resolvedUri)
.then((HttpClientRequest request) => request.close())
.then((HttpClientResponse response) {
var builder = new BytesBuilder(copy: false);
response.listen(builder.add, onDone: () {
if (response.statusCode != 200) {
var msg = "Failure getting $resolvedUri:\n"
" ${response.statusCode} ${response.reasonPhrase}";
_sendResourceResponse(sp, tag, uri, resolvedUri, libraryUrl, msg);
} else {
_sendResourceResponse(
sp, tag, uri, resolvedUri, libraryUrl, builder.takeBytes());
}
}, onError: (e) {
_sendResourceResponse(
sp, tag, uri, resolvedUri, libraryUrl, e.toString());
});
}).catchError((e) {
_sendResourceResponse(sp, tag, uri, resolvedUri, libraryUrl, e.toString());
});
// It's just here to push an event on the event loop so that we invoke the
// scheduled microtasks.
Timer.run(() {});
}
void _loadFile(IsolateLoaderState loaderState, SendPort sp, int tag, Uri uri,
Uri resolvedUri, String libraryUrl) {
var path = resolvedUri.toFilePath();
var sourceFile = new File(path);
sourceFile.readAsBytes().then((data) {
_sendResourceResponse(sp, tag, uri, resolvedUri, libraryUrl, data);
}, onError: (e) {
_sendResourceResponse(sp, tag, uri, resolvedUri, libraryUrl, e.toString());
}).whenComplete(() {
loaderState.currentFileRequests--;
while (loaderState.shouldIssueFileRequest) {
FileRequest fr = loaderState.dequeueFileRequest();
if (fr == null) {
break;
}
_loadFile(
loaderState, fr.sp, fr.tag, fr.uri, fr.resolvedUri, fr.libraryUrl);
loaderState.currentFileRequests++;
}
});
}
void _loadDataUri(
SendPort sp, int tag, Uri uri, Uri resolvedUri, String libraryUrl) {
try {
var mime = uri.data.mimeType;
if ((mime != "application/dart") && (mime != "text/plain")) {
throw "MIME-type must be application/dart or text/plain: $mime given.";
}
var charset = uri.data.charset;
if ((charset != "utf-8") && (charset != "US-ASCII")) {
// The C++ portion of the embedder assumes UTF-8.
throw "Only utf-8 or US-ASCII encodings are supported: $charset given.";
}
_sendResourceResponse(
sp, tag, uri, resolvedUri, libraryUrl, uri.data.contentAsBytes());
} catch (e) {
_sendResourceResponse(sp, tag, uri, resolvedUri, libraryUrl,
"Invalid data uri ($uri):\n $e");
}
}
// Loading a package URI needs to first map the package name to a loadable
// URI.
_loadPackage(IsolateLoaderState loaderState, SendPort sp, bool traceLoading,
int tag, Uri uri, Uri resolvedUri, String libraryUrl) {
if (loaderState._packagesReady) {
var resolvedUri;
try {
resolvedUri = loaderState._resolvePackageUri(uri);
} catch (e, s) {
if (traceLoading) {
_log("Exception ($e) when resolving package URI: $uri");
}
// Report error.
_sendResourceResponse(
sp, tag, uri, resolvedUri, libraryUrl, e.toString());
return;
}
// Recursively call with the new resolved uri.
_handleResourceRequest(
loaderState, sp, traceLoading, tag, uri, resolvedUri, libraryUrl);
} else {
if (loaderState._pendingPackageLoads.isEmpty) {
// Package resolution has not been setup yet, and this is the first
// request for package resolution & loading.
loaderState._requestPackagesMap();
}
// Register the action of loading this package once the package resolution
// is ready.
loaderState._pendingPackageLoads.add(() {
_handleResourceRequest(
loaderState, sp, traceLoading, tag, uri, uri, libraryUrl);
});
if (traceLoading) {
_log("Pending package load of '$uri': "
"${loaderState._pendingPackageLoads.length} pending");
}
}
}
// TODO(johnmccutchan): This and most other top level functions in this file
// should be turned into methods on the IsolateLoaderState class.
_handleResourceRequest(IsolateLoaderState loaderState, SendPort sp,
bool traceLoading, int tag, Uri uri, Uri resolvedUri, String libraryUrl) {
if (resolvedUri.scheme == '' || resolvedUri.scheme == 'file') {
if (loaderState.shouldIssueFileRequest) {
_loadFile(loaderState, sp, tag, uri, resolvedUri, libraryUrl);
loaderState.currentFileRequests++;
} else {
FileRequest fr = new FileRequest(sp, tag, uri, resolvedUri, libraryUrl);
loaderState.enqueueFileRequest(fr);
}
} else if ((resolvedUri.scheme == 'http') ||
(resolvedUri.scheme == 'https')) {
_loadHttp(sp, tag, uri, resolvedUri, libraryUrl);
} else if ((resolvedUri.scheme == 'data')) {
_loadDataUri(sp, tag, uri, resolvedUri, libraryUrl);
} else if ((resolvedUri.scheme == 'package')) {
_loadPackage(
loaderState, sp, traceLoading, tag, uri, resolvedUri, libraryUrl);
} else {
_sendResourceResponse(
sp,
tag,
uri,
resolvedUri,
libraryUrl,
'Unknown scheme (${resolvedUri.scheme}) for '
'$resolvedUri');
}
}
// Handling of packages requests. Finding and parsing of .packages file or
// packages/ directories.
const _LF = 0x0A;
const _CR = 0x0D;
const _SPACE = 0x20;
const _HASH = 0x23;
const _DOT = 0x2E;
const _COLON = 0x3A;
const _DEL = 0x7F;
const _invalidPackageNameChars = const [
true, // space
false, // !
true, // "
true, // #
false, // $
true, // %
false, // &
false, // '
false, // (
false, // )
false, // *
false, // +
false, // ,
false, // -
false, // .
true, // /
false, // 0
false, // 1
false, // 2
false, // 3
false, // 4
false, // 5
false, // 6
false, // 7
false, // 8
false, // 9
true, // :
false, // ;
true, // <
false, // =
true, // >
true, // ?
false, // @
false, // A
false, // B
false, // C
false, // D
false, // E
false, // F
false, // G
false, // H
false, // I
false, // J
false, // K
false, // L
false, // M
false, // N
false, // O
false, // P
false, // Q
false, // R
false, // S
false, // T
false, // U
false, // V
false, // W
false, // X
false, // Y
false, // Z
true, // [
true, // \
true, // ]
true, // ^
false, // _
true, // `
false, // a
false, // b
false, // c
false, // d
false, // e
false, // f
false, // g
false, // h
false, // i
false, // j
false, // k
false, // l
false, // m
false, // n
false, // o
false, // p
false, // q
false, // r
false, // s
false, // t
false, // u
false, // v
false, // w
false, // x
false, // y
false, // z
true, // {
true, // |
true, // }
false, // ~
true, // DEL
];
_parsePackagesFile(
SendPort sp, bool traceLoading, Uri packagesFile, List<int> data) {
// The first entry contains the location of the identified .packages file
// instead of a mapping.
var result = [packagesFile.toString(), null];
var index = 0;
var len = data.length;
while (index < len) {
var start = index;
var char = data[index];
if ((char == _CR) || (char == _LF)) {
// Skipping empty lines.
index++;
continue;
}
// Identify split within the line and end of the line.
var separator = -1;
var end = len;
// Verifying validity of package name while scanning the line.
var nonDot = false;
var invalidPackageName = false;
// Scan to the end of the line or data.
while (index < len) {
char = data[index++];
// If we have not reached the separator yet, determine whether we are
// scanning legal package name characters.
if (separator == -1) {
if ((char == _COLON)) {
// The first colon on a line is the separator between package name and
// related URI.
separator = index - 1;
} else {
// Still scanning the package name part. Check for the validity of
// the characters.
nonDot = nonDot || (char != _DOT);
invalidPackageName = invalidPackageName ||
(char < _SPACE) ||
(char > _DEL) ||
_invalidPackageNameChars[char - _SPACE];
}
}
// Identify end of line.
if ((char == _CR) || (char == _LF)) {
end = index - 1;
break;
}
}
// No further handling needed for comment lines.
if (data[start] == _HASH) {
if (traceLoading) {
_log("Skipping comment in $packagesFile:\n"
"${new String.fromCharCodes(data, start, end)}");
}
continue;
}
// Check for a badly formatted line, starting with a ':'.
if (separator == start) {
var line = new String.fromCharCodes(data, start, end);
if (traceLoading) {
_log("Line starts with ':' in $packagesFile:\n"
"$line");
}
sp.send("Missing package name in $packagesFile:\n"
"$line");
return;
}
// Ensure there is a separator on the line.
if (separator == -1) {
var line = new String.fromCharCodes(data, start, end);
if (traceLoading) {
_log("Line has no ':' in $packagesFile:\n"
"$line");
}
sp.send("Missing ':' separator in $packagesFile:\n"
"$line");
return;
}
var packageName = new String.fromCharCodes(data, start, separator);
// Check for valid package name.
if (invalidPackageName || !nonDot) {
var line = new String.fromCharCodes(data, start, end);
if (traceLoading) {
_log("Invalid package name $packageName in $packagesFile");
}
sp.send("Invalid package name '$packageName' in $packagesFile:\n"
"$line");
return;
}
if (traceLoading) {
_log("packageName: $packageName");
}
var packageUri = new String.fromCharCodes(data, separator + 1, end);
if (traceLoading) {
_log("original packageUri: $packageUri");
}
// Ensure the package uri ends with a /.
if (!packageUri.endsWith("/")) {
packageUri = "$packageUri/";
}
packageUri = packagesFile.resolve(packageUri).toString();
if (traceLoading) {
_log("mapping: $packageName -> $packageUri");
}
result.add(packageName);
result.add(packageUri);
}
if (traceLoading) {
_log("Parsed packages file at $packagesFile. Sending:\n$result");
}
sp.send(result);
}
_loadPackagesFile(SendPort sp, bool traceLoading, Uri packagesFile) async {
try {
var data = await new File.fromUri(packagesFile).readAsBytes();
if (traceLoading) {
_log("Loaded packages file from $packagesFile:\n"
"${new String.fromCharCodes(data)}");
}
_parsePackagesFile(sp, traceLoading, packagesFile, data);
} catch (e, s) {
if (traceLoading) {
_log("Error loading packages: $e\n$s");
}
sp.send("Uncaught error ($e) loading packages file.");
}
}
_findPackagesFile(SendPort sp, bool traceLoading, Uri base) async {
try {
// Walk up the directory hierarchy to check for the existence of
// .packages files in parent directories and for the existence of a
// packages/ directory on the first iteration.
var dir = new File.fromUri(base).parent;
var prev = null;
// Keep searching until we reach the root.
while ((prev == null) || (prev.path != dir.path)) {
// Check for the existence of a .packages file and if it exists try to
// load and parse it.
var dirUri = dir.uri;
var packagesFile = dirUri.resolve(".packages");
if (traceLoading) {
_log("Checking for $packagesFile file.");
}
var exists = await new File.fromUri(packagesFile).exists();
if (traceLoading) {
_log("$packagesFile exists: $exists");
}
if (exists) {
_loadPackagesFile(sp, traceLoading, packagesFile);
return;
}
// Move up one level.
prev = dir;
dir = dir.parent;
}
// No .packages file was found.
if (traceLoading) {
_log("Could not resolve a package location from $base");
}
sp.send("Could not resolve a package location for base at $base");
} catch (e, s) {
if (traceLoading) {
_log("Error loading packages: $e\n$s");
}
sp.send("Uncaught error ($e) loading packages file.");
}
}
Future<bool> _loadHttpPackagesFile(
SendPort sp, bool traceLoading, Uri resource) async {
try {
if (_httpClient == null) {
_httpClient = new HttpClient()..maxConnectionsPerHost = 6;
}
if (traceLoading) {
_log("Fetching packages file from '$resource'.");
}
var req = await _httpClient.getUrl(resource);
var rsp = await req.close();
var builder = new BytesBuilder(copy: false);
await for (var bytes in rsp) {
builder.add(bytes);
}
if (rsp.statusCode != 200) {
if (traceLoading) {
_log("Got status ${rsp.statusCode} fetching '$resource'.");
}
return false;
}
var data = builder.takeBytes();
if (traceLoading) {
_log("Loaded packages file from '$resource':\n"
"${new String.fromCharCodes(data)}");
}
_parsePackagesFile(sp, traceLoading, resource, data);
} catch (e, s) {
if (traceLoading) {
_log("Error loading packages file from '$resource': $e\n$s");
}
sp.send("Uncaught error ($e) loading packages file from '$resource'.");
}
return false;
}
_loadPackagesData(sp, traceLoading, resource) {
try {
var data = resource.data;
var mime = data.mimeType;
if (mime != "text/plain") {
throw "MIME-type must be text/plain: $mime given.";
}
var charset = data.charset;
if ((charset != "utf-8") && (charset != "US-ASCII")) {
// The C++ portion of the embedder assumes UTF-8.
throw "Only utf-8 or US-ASCII encodings are supported: $charset given.";
}
_parsePackagesFile(sp, traceLoading, resource, data.contentAsBytes());
} catch (e) {
sp.send("Uncaught error ($e) loading packages data.");
}
}
// This code used to exist in a second isolate and so it uses a SendPort to
// report it's return value. This could be refactored so that it returns it's
// value and the caller could wait on the future rather than a message on
// SendPort.
_handlePackagesRequest(
SendPort sp, bool traceLoading, int tag, Uri resource) async {
try {
if (tag == -1) {
if (resource.scheme == '' || resource.scheme == 'file') {
_findPackagesFile(sp, traceLoading, resource);
} else if ((resource.scheme == 'http') || (resource.scheme == 'https')) {
// Try to load the .packages file next to the resource.
var packagesUri = resource.resolve(".packages");
var exists = await _loadHttpPackagesFile(sp, traceLoading, packagesUri);
if (!exists) {
// Loading of the .packages file failed for http/https based scripts
sp.send([null]);
}
} else {
sp.send("Unsupported scheme used to locate .packages file: "
"'$resource'.");
}
} else if (tag == -2) {
if (traceLoading) {
_log("Handling load of packages map: '$resource'.");
}
if (resource.scheme == '' || resource.scheme == 'file') {
var exists = await new File.fromUri(resource).exists();
if (exists) {
_loadPackagesFile(sp, traceLoading, resource);
} else {
sp.send("Packages file '$resource' not found.");
}
} else if ((resource.scheme == 'http') || (resource.scheme == 'https')) {
var exists = await _loadHttpPackagesFile(sp, traceLoading, resource);
if (!exists) {
sp.send("Packages file '$resource' not found.");
}
} else if (resource.scheme == 'data') {
_loadPackagesData(sp, traceLoading, resource);
} else {
sp.send("Unknown scheme (${resource.scheme}) for package file at "
"'$resource'.");
}
} else {
sp.send("Unknown packages request tag: $tag for '$resource'.");
}
} catch (e, s) {
if (traceLoading) {
_log("Error handling packages request: $e\n$s");
}
sp.send("Uncaught error ($e) handling packages request.");
}
}
// Shutdown all active loaders by sending an error message.
void shutdownLoaders() {
String message = 'Service shutdown';
if (_httpClient != null) {
_httpClient.close(force: true);
_httpClient = null;
}
isolateEmbedderData.values.toList().forEach((ied) {
IsolateLoaderState ils = ied;
ils.cleanup();
assert(ils.sp != null);
_sendResourceResponse(ils.sp, 1, null, null, null, message);
});
}
// See Dart_LibraryTag in dart_api.h
const _Dart_kCanonicalizeUrl = 0; // Canonicalize the URL.
const _Dart_kScriptTag = 1; // Load the root script.
const _Dart_kSourceTag = 2; // Load a part source.
const _Dart_kImportTag = 3; // Import a library.
// Extra requests. Keep these in sync between loader.dart and builtin.dart.
const _Dart_kInitLoader = 4; // Initialize the loader.
const _Dart_kResourceLoad = 5; // Resource class support.
const _Dart_kGetPackageRootUri = 6; // Uri of the packages/ directory.
const _Dart_kGetPackageConfigUri = 7; // Uri of the .packages file.
const _Dart_kResolvePackageUri = 8; // Resolve a package: uri.
// Extra requests. Keep these in sync between loader.dart and loader.cc.
const _Dart_kImportExtension = 9; // Import a dart-ext: file.
const _Dart_kResolveAsFilePath = 10; // Resolve uri to file path.
// External entry point for loader requests.
_processLoadRequest(request) {
assert(request is List);
assert(request.length > 4);
// Should we trace loading?
bool traceLoading = request[0];
// This is the sending isolate's Dart_GetMainPortId().
int isolateId = request[1];
// The tag describing the operation.
int tag = request[2];
// The send port to send the response on.
SendPort sp = request[3];
// Grab the loader state for the requesting isolate.
IsolateLoaderState loaderState = isolateEmbedderData[isolateId];
// We are either about to initialize the loader, or, we already have.
assert((tag == _Dart_kInitLoader) || (loaderState != null));
// Handle the request specified in the tag.
switch (tag) {
case _Dart_kScriptTag:
{
Uri uri = Uri.parse(request[4]);
// Remember the root script.
loaderState._rootScript = uri;
_handleResourceRequest(
loaderState, sp, traceLoading, tag, uri, uri, null);
}
break;
case _Dart_kSourceTag:
case _Dart_kImportTag:
{
// The url of the file being loaded.
var uri = Uri.parse(request[4]);
// The library that is importing/parting the file.
String libraryUrl = request[5];
_handleResourceRequest(
loaderState, sp, traceLoading, tag, uri, uri, libraryUrl);
}
break;
case _Dart_kInitLoader:
{
String packageRoot = request[4];
String packagesFile = request[5];
String workingDirectory = request[6];
String rootScript = request[7];
bool isReloading = request[8];
if (loaderState == null) {
loaderState = new IsolateLoaderState(isolateId);
isolateEmbedderData[isolateId] = loaderState;
loaderState.init(
packageRoot, packagesFile, workingDirectory, rootScript);
} else if (isReloading) {
loaderState.updatePackageMap(packagesFile);
}
loaderState.sp = sp;
assert(isolateEmbedderData[isolateId] == loaderState);
}
break;
case _Dart_kResourceLoad:
{
Uri uri = Uri.parse(request[4]);
_handleResourceRequest(
loaderState, sp, traceLoading, tag, uri, uri, null);
}
break;
case _Dart_kGetPackageRootUri:
loaderState._triggerPackageResolution(() {
// The package root is deprecated and now always returns null.
sp.send(null);
});
break;
case _Dart_kGetPackageConfigUri:
loaderState._triggerPackageResolution(() {
// Respond with the packages config (if any) after package resolution.
sp.send(loaderState._packageConfig);
});
break;
case _Dart_kResolvePackageUri:
Uri uri = Uri.parse(request[4]);
loaderState._triggerPackageResolution(() {
// Respond with the resolved package uri after package resolution.
Uri resolvedUri;
try {
resolvedUri = loaderState._resolvePackageUri(uri);
} catch (e, s) {
if (traceLoading) {
_log("Exception ($e) when resolving package URI: $uri");
}
resolvedUri = null;
}
sp.send(resolvedUri);
});
break;
case _Dart_kImportExtension:
Uri uri = Uri.parse(request[4]);
String libraryUri = request[5];
// Strip any filename off of the libraryUri's path.
int index = libraryUri.lastIndexOf('/');
var path;
if (index == -1) {
path = './';
} else {
path = libraryUri.substring(0, index + 1);
}
var pathUri = Uri.parse(path);
switch (pathUri.scheme) {
case '':
case 'file':
_sendExtensionImportResponse(
sp, uri, libraryUri, pathUri.toFilePath());
break;
case 'data':
case 'http':
case 'https':
_sendExtensionImportResponse(sp, uri, libraryUri, pathUri.toString());
break;
case 'package':
// Start package resolution.
loaderState._triggerPackageResolution(() {
// Attempt to find the fully resolved uri of [path].
Uri resolvedUri;
try {
resolvedUri = loaderState._resolvePackageUri(pathUri);
} catch (e, s) {
if (traceLoading) {
_log("Exception ($e) when resolving package URI: $uri");
}
resolvedUri = null;
}
_sendExtensionImportResponse(
sp, uri, libraryUri, resolvedUri.toString());
});
break;
default:
if (traceLoading) {
_log('Unknown scheme (${pathUri.scheme}) in $pathUri.');
}
_sendExtensionImportResponse(sp, uri, libraryUri, null);
break;
}
break;
case _Dart_kResolveAsFilePath:
loaderState._triggerPackageResolution(() {
String uri = request[4];
Uri resolvedUri = Uri.parse(_sanitizeWindowsPath(uri));
try {
if (resolvedUri.scheme == 'package') {
resolvedUri = loaderState._resolvePackageUri(resolvedUri);
}
if (resolvedUri.scheme == '' || resolvedUri.scheme == 'file') {
resolvedUri = loaderState._workingDirectory.resolveUri(resolvedUri);
var msg = new List(5);
msg[0] = tag;
msg[1] = uri;
msg[2] = resolvedUri.toString();
msg[3] = null;
msg[4] = resolvedUri.toFilePath();
sp.send(msg);
} else {
throw "Cannot resolve scheme (${resolvedUri.scheme}) to file path"
" for $resolvedUri";
}
} catch (e) {
var msg = new List(5);
msg[0] = -tag;
msg[1] = uri;
msg[2] = resolvedUri.toString();
msg[3] = null;
msg[4] = e.toString();
sp.send(msg);
}
});
break;
default:
_log('Unknown loader request tag=$tag from $isolateId');
}
}