| Index: runtime/bin/vmservice/loader.dart
 | 
| diff --git a/runtime/bin/vmservice/loader.dart b/runtime/bin/vmservice/loader.dart
 | 
| index 7bec4a1cda1c23147d5e19bd897ff7deeea1a677..42bc6f92c948ef4bfcb5b8ad151b30c284c7afce 100644
 | 
| --- a/runtime/bin/vmservice/loader.dart
 | 
| +++ b/runtime/bin/vmservice/loader.dart
 | 
| @@ -4,6 +4,303 @@
 | 
|  
 | 
|  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;
 | 
| +}
 | 
| +
 | 
| +// State associated with the isolate that is used for loading.
 | 
| +class IsolateLoaderState extends IsolateEmbedderData {
 | 
| +  IsolateLoaderState(this.isolateId);
 | 
| +
 | 
| +  final int isolateId;
 | 
| +
 | 
| +  SendPort sp;
 | 
| +
 | 
| +  void init(String packageRootFlag,
 | 
| +            String packagesConfigFlag,
 | 
| +            String workingDirectory,
 | 
| +            String rootScript) {
 | 
| +    // _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 cleanup() {
 | 
| +    if (_packagesPort != null) {
 | 
| +      _packagesPort.close();
 | 
| +      _packagesPort = null;
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  // The working directory when the embedder started.
 | 
| +  Uri _workingDirectory;
 | 
| +
 | 
| +  // The root script's uri.
 | 
| +  Uri _rootScript;
 | 
| +
 | 
| +  bool _traceLoading = false;
 | 
| +
 | 
| +  // 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;
 | 
| +
 | 
| +  _setPackageRoot(String packageRoot) {
 | 
| +    packageRoot = _enforceTrailingSlash(packageRoot);
 | 
| +    if (packageRoot.startsWith('file:') ||
 | 
| +        packageRoot.startsWith('http:') ||
 | 
| +        packageRoot.startsWith('https:')) {
 | 
| +      _packageRoot = _workingDirectory.resolve(packageRoot);
 | 
| +    } else {
 | 
| +      packageRoot = _sanitizeWindowsPath(packageRoot);
 | 
| +      packageRoot = _trimWindowsPath(packageRoot);
 | 
| +      _packageRoot = _workingDirectory.resolveUri(new Uri.file(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.
 | 
| +  Uri _resolvePackageUri(Uri uri) {
 | 
| +    assert(uri.scheme == "package");
 | 
| +    assert(_packagesReady);
 | 
| +
 | 
| +    if (!uri.host.isEmpty) {
 | 
| +      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'.";
 | 
| +    }
 | 
| +
 | 
| +    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 {
 | 
| +      var packageName = uri.pathSegments[0];
 | 
| +      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;
 | 
| +      if (uri.path.length > packageName.length) {
 | 
| +        path = uri.path.substring(packageName.length + 1);
 | 
| +      } else {
 | 
| +        // Handle naked package resolution to the default package name:
 | 
| +        // package:foo is equivalent to package:foo/foo.dart
 | 
| +        assert(uri.path.length == packageName.length);
 | 
| +        path = "$packageName.dart";
 | 
| +      }
 | 
| +      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]) {
 | 
| +    assert(_packagesPort == null);
 | 
| +    assert(_rootScript != null);
 | 
| +    // 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 or packages/ starting at the root script.
 | 
| +      _handlePackagesRequest(sp,
 | 
| +                             _traceLoading,
 | 
| +                             -1,
 | 
| +                             _rootScript);
 | 
| +    }
 | 
| +
 | 
| +    if (_traceLoading) {
 | 
| +      _log("Requested packages map for '$_rootScript'.");
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  void _handlePackagesReply(msg) {
 | 
| +    assert(_packagesPort != null);
 | 
| +    // 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");
 | 
|  }
 | 
| @@ -11,19 +308,38 @@ _log(msg) {
 | 
|  var _httpClient;
 | 
|  
 | 
|  // Send a response to the requesting isolate.
 | 
| -void _sendResourceResponse(SendPort sp, int id, dynamic data) {
 | 
| +void _sendResourceResponse(SendPort sp,
 | 
| +                           int tag,
 | 
| +                           Uri uri,
 | 
| +                           String libraryUrl,
 | 
| +                           dynamic data) {
 | 
|    assert((data is List<int>) || (data is String));
 | 
| -  var msg = new List(2);
 | 
| -  msg[0] = id;
 | 
| -  msg[1] = data;
 | 
| +  var msg = new List(4);
 | 
| +  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] = libraryUrl;
 | 
| +  msg[3] = data;
 | 
|    sp.send(msg);
 | 
|  }
 | 
|  
 | 
| -void _loadHttp(SendPort sp, int id, Uri uri) {
 | 
| +void _loadHttp(SendPort sp,
 | 
| +               int tag,
 | 
| +               Uri uri,
 | 
| +               Uri resolvedUri,
 | 
| +               String libraryUrl) {
 | 
|    if (_httpClient == null) {
 | 
|      _httpClient = new HttpClient()..maxConnectionsPerHost = 6;
 | 
|    }
 | 
| -  _httpClient.getUrl(uri)
 | 
| +  _httpClient.getUrl(resolvedUri)
 | 
|      .then((HttpClientRequest request) => request.close())
 | 
|      .then((HttpClientResponse response) {
 | 
|        var builder = new BytesBuilder(copy: false);
 | 
| @@ -31,37 +347,46 @@ void _loadHttp(SendPort sp, int id, Uri uri) {
 | 
|            builder.add,
 | 
|            onDone: () {
 | 
|              if (response.statusCode != 200) {
 | 
| -              var msg = "Failure getting $uri:\n"
 | 
| +              var msg = "Failure getting $resolvedUri:\n"
 | 
|                          "  ${response.statusCode} ${response.reasonPhrase}";
 | 
| -              _sendResourceResponse(sp, id, msg);
 | 
| +              _sendResourceResponse(sp, tag, uri, libraryUrl, msg);
 | 
|              } else {
 | 
| -              _sendResourceResponse(sp, id, builder.takeBytes());
 | 
| +              _sendResourceResponse(sp, tag, uri, libraryUrl,
 | 
| +                                    builder.takeBytes());
 | 
|              }
 | 
|            },
 | 
|            onError: (e) {
 | 
| -            _sendResourceResponse(sp, id, e.toString());
 | 
| +            _sendResourceResponse(sp, tag, uri, libraryUrl, e.toString());
 | 
|            });
 | 
|      })
 | 
|      .catchError((e) {
 | 
| -      _sendResourceResponse(sp, id, e.toString());
 | 
| +      _sendResourceResponse(sp, tag, uri, 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(SendPort sp, int id, Uri uri) {
 | 
| -  var path = uri.toFilePath();
 | 
| +void _loadFile(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, id, data);
 | 
| +    _sendResourceResponse(sp, tag, uri, libraryUrl, data);
 | 
|    },
 | 
|    onError: (e) {
 | 
| -    _sendResourceResponse(sp, id, e.toString());
 | 
| +    _sendResourceResponse(sp, tag, uri, libraryUrl, e.toString());
 | 
|    });
 | 
|  }
 | 
|  
 | 
| -void _loadDataUri(SendPort sp, int id, Uri uri) {
 | 
| +void _loadDataUri(SendPort sp,
 | 
| +                  int tag,
 | 
| +                  Uri uri,
 | 
| +                  Uri resolvedUri,
 | 
| +                  String libraryUrl) {
 | 
|    try {
 | 
|      var mime = uri.data.mimeType;
 | 
|      if ((mime != "application/dart") &&
 | 
| @@ -74,25 +399,102 @@ void _loadDataUri(SendPort sp, int id, Uri uri) {
 | 
|        // The C++ portion of the embedder assumes UTF-8.
 | 
|        throw "Only utf-8 or US-ASCII encodings are supported: $charset given.";
 | 
|      }
 | 
| -    _sendResourceResponse(sp, id, uri.data.contentAsBytes());
 | 
| +    _sendResourceResponse(sp, tag, uri, libraryUrl, uri.data.contentAsBytes());
 | 
|    } catch (e) {
 | 
| -    _sendResourceResponse(sp, id, "Invalid data uri ($uri):\n  $e");
 | 
| +    _sendResourceResponse(sp, tag, uri, libraryUrl,
 | 
| +                          "Invalid data uri ($uri):\n  $e");
 | 
|    }
 | 
|  }
 | 
|  
 | 
| -_handleResourceRequest(SendPort sp, bool traceLoading, int id, Uri resource) {
 | 
| -  if (resource.scheme == 'file') {
 | 
| -    _loadFile(sp, id, resource);
 | 
| -  } else if ((resource.scheme == 'http') || (resource.scheme == 'https')) {
 | 
| -    _loadHttp(sp, id, resource);
 | 
| -  } else if ((resource.scheme == 'data')) {
 | 
| -    _loadDataUri(sp, id, resource);
 | 
| +// 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,
 | 
| +                            libraryUrl,
 | 
| +                            e.toString());
 | 
| +      return;
 | 
| +    }
 | 
| +    // Recursively call with the new resolved uri.
 | 
| +    _handleResourceRequest(loaderState,
 | 
| +                           sp,
 | 
| +                           traceLoading,
 | 
| +                           tag,
 | 
| +                           uri,
 | 
| +                           resolvedUri,
 | 
| +                           libraryUrl);
 | 
|    } else {
 | 
| -    _sendResourceResponse(sp, id,
 | 
| -                          'Unknown scheme (${resource.scheme}) for $resource');
 | 
| +    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 == 'file') {
 | 
| +    _loadFile(sp, tag, uri, resolvedUri, libraryUrl);
 | 
| +  } 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,
 | 
| +                          libraryUrl,
 | 
| +                          'Unknown scheme (${resolvedUri.scheme}) for '
 | 
| +                          '$resolvedUri');
 | 
| +  }
 | 
| +}
 | 
|  
 | 
|  // Handling of packages requests. Finding and parsing of .packages file or
 | 
|  // packages/ directories.
 | 
| @@ -329,7 +731,6 @@ _findPackagesFile(SendPort sp, bool traceLoading, Uri base) async {
 | 
|    }
 | 
|  }
 | 
|  
 | 
| -
 | 
|  Future<bool> _loadHttpPackagesFile(SendPort sp,
 | 
|                                     bool traceLoading,
 | 
|                                     Uri resource) async {
 | 
| @@ -386,13 +787,16 @@ _loadPackagesData(sp, traceLoading, resource){
 | 
|    }
 | 
|  }
 | 
|  
 | 
| -
 | 
| +// 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 id,
 | 
| +                       int tag,
 | 
|                         Uri resource) async {
 | 
|    try {
 | 
| -    if (id == -1) {
 | 
| +    if (tag == -1) {
 | 
|        if (resource.scheme == 'file') {
 | 
|          _findPackagesFile(sp, traceLoading, resource);
 | 
|        } else if ((resource.scheme == 'http') || (resource.scheme == 'https')) {
 | 
| @@ -409,7 +813,7 @@ _handlePackagesRequest(SendPort sp,
 | 
|          sp.send("Unsupported scheme used to locate .packages file: "
 | 
|                  "'$resource'.");
 | 
|        }
 | 
| -    } else if (id == -2) {
 | 
| +    } else if (tag == -2) {
 | 
|        if (traceLoading) {
 | 
|          _log("Handling load of packages map: '$resource'.");
 | 
|        }
 | 
| @@ -432,7 +836,7 @@ _handlePackagesRequest(SendPort sp,
 | 
|                  "'$resource'.");
 | 
|        }
 | 
|      } else {
 | 
| -      sp.send("Unknown packages request id: $id for '$resource'.");
 | 
| +      sp.send("Unknown packages request tag: $tag for '$resource'.");
 | 
|      }
 | 
|    } catch (e, s) {
 | 
|      if (traceLoading) {
 | 
| @@ -442,21 +846,143 @@ _handlePackagesRequest(SendPort sp,
 | 
|    }
 | 
|  }
 | 
|  
 | 
| +// 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((IsolateLoaderState ils) {
 | 
| +    ils.cleanup();
 | 
| +    assert(ils.sp != null);
 | 
| +    _sendResourceResponse(ils.sp, 1, 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.
 | 
|  
 | 
|  // External entry point for loader requests.
 | 
|  _processLoadRequest(request) {
 | 
| -  SendPort sp = request[0];
 | 
| -  assert(sp != null);
 | 
| -  bool traceLoading = request[1];
 | 
| -  assert(traceLoading != null);
 | 
| -  int id = request[2];
 | 
| -  assert(id != null);
 | 
| -  String resource = request[3];
 | 
| -  assert(resource != null);
 | 
| -  var uri = Uri.parse(resource);
 | 
| -  if (id >= 0) {
 | 
| -    _handleResourceRequest(sp, traceLoading, id, uri);
 | 
| -  } else {
 | 
| -    _handlePackagesRequest(sp, traceLoading, id, uri);
 | 
| +  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];
 | 
| +      if (loaderState == null) {
 | 
| +        loaderState = new IsolateLoaderState(isolateId);
 | 
| +        isolateEmbedderData[isolateId] = loaderState;
 | 
| +        loaderState.init(packageRoot,
 | 
| +                         packagesFile,
 | 
| +                         workingDirectory,
 | 
| +                         rootScript);
 | 
| +      }
 | 
| +      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(() {
 | 
| +        // Respond with the package root (if any) after package resolution.
 | 
| +        sp.send(loaderState._packageRoot);
 | 
| +      });
 | 
| +    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;
 | 
| +    default:
 | 
| +      _log('Unknown loader request tag=$tag from $isolateId');
 | 
|    }
 | 
|  }
 | 
| 
 |