| // Copyright (c) 2012, 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. |
| |
| library builtin; |
| import 'dart:async'; |
| import 'dart:convert'; |
| import 'dart:isolate'; |
| |
| |
| ////////////////////// |
| /* Support for loading within the isolate via dart:io */ |
| import 'dart:io'; |
| |
| // Enable by setting the #define LOAD_VIA_SERVICE_ISOLATE (see dartutils.cc) |
| bool _load_via_service_isolate = false; |
| |
| var _httpClient; |
| void _httpGet(int tag, |
| Uri uri, |
| String libraryUri, |
| loadCallback(List<int> data)) { |
| if (_httpClient == null) { |
| _httpClient = new HttpClient()..maxConnectionsPerHost = 6; |
| } |
| _httpClient.getUrl(uri) |
| .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 $uri: ' |
| '${response.statusCode} ${response.reasonPhrase}'; |
| _asyncLoadError(tag, uri.toString(), libraryUri, msg); |
| } |
| loadCallback(builder.takeBytes()); |
| }, |
| onError: (error) { |
| _asyncLoadError(tag, uri.toString(), libraryUri, error); |
| }); |
| }) |
| .catchError((error) { |
| _asyncLoadError(tag, uri.toString(), libraryUri, error); |
| }); |
| // TODO(floitsch): remove this line. It's just here to push an event on the |
| // event loop so that we invoke the scheduled microtasks. Also remove the |
| // import of dart:async when this line is not needed anymore. |
| Timer.run(() {}); |
| } |
| |
| |
| void _cleanup() { |
| if (_httpClient != null) { |
| _httpClient.close(); |
| _httpClient = null; |
| } |
| } |
| |
| _loadDataAsyncDartIO(int tag, |
| String uri, |
| String libraryUri, |
| Uri resourceUri) { |
| _startingOneLoadRequest(uri); |
| if ((resourceUri.scheme == 'http') || (resourceUri.scheme == 'https')) { |
| _httpGet(tag, resourceUri, libraryUri, (data) { |
| _loadScript(tag, uri, libraryUri, data); |
| }); |
| } else { |
| var sourceFile = new File(resourceUri.toFilePath()); |
| sourceFile.readAsBytes().then((data) { |
| _loadScript(tag, uri, libraryUri, data); |
| }, |
| onError: (e) { |
| _asyncLoadError(tag, uri, libraryUri, e); |
| }); |
| } |
| } |
| |
| ////////////////////// |
| |
| /* See Dart_LibraryTag in dart_api.h */ |
| const Dart_kScriptTag = null; |
| const Dart_kImportTag = 0; |
| const Dart_kSourceTag = 1; |
| const Dart_kCanonicalizeUrl = 2; |
| |
| // Dart native extension scheme. |
| const _DART_EXT = 'dart-ext:'; |
| |
| // import 'root_library'; happens here from C Code |
| |
| // The root library (aka the script) is imported into this library. The |
| // standalone embedder uses this to lookup the main entrypoint in the |
| // root library's namespace. |
| Function _getMainClosure() => main; |
| |
| // A port for communicating with the service isolate for I/O. |
| SendPort _loadPort; |
| |
| const _logBuiltin = false; |
| |
| // Corelib 'print' implementation. |
| void _print(arg) { |
| _Logger._printString(arg.toString()); |
| } |
| |
| class _Logger { |
| static void _printString(String s) native "Logger_PrintString"; |
| } |
| |
| _getPrintClosure() => _print; |
| |
| _getCurrentDirectoryPath() native "Directory_Current"; |
| |
| // Corelib 'Uri.base' implementation. |
| Uri _uriBase() { |
| return new Uri.file(_getCurrentDirectoryPath() + "/"); |
| } |
| |
| _getUriBaseClosure() => _uriBase; |
| |
| |
| // Are we running on Windows? |
| var _isWindows; |
| var _workingWindowsDrivePrefix; |
| // The current working directory |
| var _workingDirectoryUri; |
| // The URI that the entry point script was loaded from. Remembered so that |
| // package imports can be resolved relative to it. |
| var _entryPointScript; |
| // The directory to look in to resolve "package:" scheme URIs. |
| var _packageRoot; |
| |
| _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; |
| } |
| |
| _enforceTrailingSlash(uri) { |
| // Ensure we have a trailing slash character. |
| if (!uri.endsWith('/')) { |
| return '$uri/'; |
| } |
| return uri; |
| } |
| |
| |
| _extractDriveLetterPrefix(cwd) { |
| if (!_isWindows) { |
| return null; |
| } |
| if (cwd.length > 1 && cwd[1] == ':') { |
| return '/${cwd[0]}:'; |
| } |
| return null; |
| } |
| |
| |
| void _setWorkingDirectory(cwd) { |
| _workingWindowsDrivePrefix = _extractDriveLetterPrefix(cwd); |
| cwd = _sanitizeWindowsPath(cwd); |
| cwd = _enforceTrailingSlash(cwd); |
| _workingDirectoryUri = new Uri(scheme: 'file', path: cwd); |
| if (_logBuiltin) { |
| _print('# Working Directory: $cwd'); |
| } |
| } |
| |
| |
| _setPackageRoot(String packageRoot) { |
| packageRoot = _enforceTrailingSlash(packageRoot); |
| if (packageRoot.startsWith('file:') || |
| packageRoot.startsWith('http:') || |
| packageRoot.startsWith('https:')) { |
| _packageRoot = _workingDirectoryUri.resolve(packageRoot); |
| } else { |
| packageRoot = _sanitizeWindowsPath(packageRoot); |
| packageRoot = _trimWindowsPath(packageRoot); |
| _packageRoot = _workingDirectoryUri.resolveUri(new Uri.file(packageRoot)); |
| } |
| if (_logBuiltin) { |
| _print('# Package root: $packageRoot -> $_packageRoot'); |
| } |
| } |
| |
| |
| // Given a uri with a 'package' scheme, return a Uri that is prefixed with |
| // the package root. |
| Uri _resolvePackageUri(Uri uri) { |
| 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'."; |
| } |
| |
| var packageRoot = _packageRoot == null ? |
| _entryPointScript.resolve('packages/') : |
| _packageRoot; |
| return packageRoot.resolve(uri.path); |
| } |
| |
| |
| |
| String _resolveScriptUri(String scriptName) { |
| if (_workingDirectoryUri == null) { |
| throw 'No current working directory set.'; |
| } |
| scriptName = _sanitizeWindowsPath(scriptName); |
| |
| var scriptUri = Uri.parse(scriptName); |
| if (scriptUri.scheme != '') { |
| // Script has a scheme, assume that it is fully formed. |
| _entryPointScript = scriptUri; |
| } else { |
| // Script does not have a scheme, assume that it is a path, |
| // resolve it against the working directory. |
| _entryPointScript = _workingDirectoryUri.resolve(scriptName); |
| } |
| if (_logBuiltin) { |
| _print('# Resolved entry point to: $_entryPointScript'); |
| } |
| return _entryPointScript.toString(); |
| } |
| |
| |
| // Function called by standalone embedder to resolve uris. |
| String _resolveUri(String base, String userString) { |
| if (_logBuiltin) { |
| _print('# Resolving: $userString from $base'); |
| } |
| var baseUri = Uri.parse(base); |
| if (userString.startsWith(_DART_EXT)) { |
| var uri = userString.substring(_DART_EXT.length); |
| return '$_DART_EXT${baseUri.resolve(uri)}'; |
| } else { |
| return baseUri.resolve(userString).toString(); |
| } |
| } |
| |
| Uri _createUri(String userUri) { |
| var uri = Uri.parse(userUri); |
| switch (uri.scheme) { |
| case '': |
| case 'file': |
| case 'http': |
| case 'https': |
| return uri; |
| case 'package': |
| return _resolvePackageUri(uri); |
| default: |
| // Only handling file, http[s], and package URIs |
| // in standalone binary. |
| if (_logBuiltin) { |
| _print('# Unknown scheme (${uri.scheme}) in $uri.'); |
| } |
| throw 'Not a known scheme: $uri'; |
| } |
| } |
| |
| int _numOutstandingLoadRequests = 0; |
| void _finishedOneLoadRequest(String uri) { |
| assert(_numOutstandingLoadRequests > 0); |
| _numOutstandingLoadRequests--; |
| if (_logBuiltin) { |
| _print("Loading of $uri finished, " |
| "${_numOutstandingLoadRequests} requests remaining"); |
| } |
| if (_numOutstandingLoadRequests == 0) { |
| _signalDoneLoading(); |
| _cleanup(); |
| } |
| } |
| |
| void _startingOneLoadRequest(String uri) { |
| assert(_numOutstandingLoadRequests >= 0); |
| _numOutstandingLoadRequests++; |
| if (_logBuiltin) { |
| _print("Loading of $uri started, " |
| "${_numOutstandingLoadRequests} requests outstanding"); |
| } |
| } |
| |
| class LoadError extends Error { |
| final String message; |
| LoadError(this.message); |
| |
| String toString() => 'Load Error: $message'; |
| } |
| |
| void _signalDoneLoading() native "Builtin_DoneLoading"; |
| void _loadScriptCallback(int tag, String uri, String libraryUri, List<int> data) |
| native "Builtin_LoadScript"; |
| void _asyncLoadErrorCallback(uri, libraryUri, error) |
| native "Builtin_AsyncLoadError"; |
| |
| void _loadScript(int tag, String uri, String libraryUri, List<int> data) { |
| // TODO: Currently a compilation error while loading the script is |
| // fatal for the isolate. _loadScriptCallback() does not return and |
| // the _numOutstandingLoadRequests counter remains out of sync. |
| _loadScriptCallback(tag, uri, libraryUri, data); |
| _finishedOneLoadRequest(uri); |
| } |
| |
| void _asyncLoadError(tag, uri, libraryUri, error) { |
| if (_logBuiltin) { |
| _print("_asyncLoadError($uri), error: $error"); |
| } |
| if (tag == Dart_kImportTag) { |
| // When importing a library, the libraryUri is the imported |
| // uri. |
| libraryUri = uri; |
| } |
| _asyncLoadErrorCallback(uri, libraryUri, new LoadError(error)); |
| _finishedOneLoadRequest(uri); |
| } |
| |
| |
| _loadDataAsyncLoadPort(int tag, |
| String uri, |
| String libraryUri, |
| Uri resourceUri) { |
| var receivePort = new ReceivePort(); |
| receivePort.first.then((dataOrError) { |
| if (dataOrError is List<int>) { |
| _loadScript(tag, uri, libraryUri, dataOrError); |
| } else { |
| _asyncLoadError(tag, uri, libraryUri, dataOrError); |
| } |
| }).catchError((e) { |
| _asyncLoadError(tag, uri, libraryUri, e.toString()); |
| }); |
| |
| try { |
| var msg = [receivePort.sendPort, resourceUri.toString()]; |
| _loadPort.send(msg); |
| _startingOneLoadRequest(uri); |
| } catch (e) { |
| if (_logBuiltin) { |
| _print("Exception when communicating with service isolate: $e"); |
| } |
| _asyncLoadError(tag, uri, libraryUri, e.toString()); |
| receivePort.close(); |
| } |
| } |
| |
| // Asynchronously loads script data through a http[s] or file uri. |
| _loadDataAsync(int tag, String uri, String libraryUri) { |
| if (tag == Dart_kScriptTag) { |
| uri = _resolveScriptUri(uri); |
| } |
| |
| Uri resourceUri = _createUri(uri); |
| |
| if (_load_via_service_isolate) { |
| _loadDataAsyncLoadPort(tag, uri, libraryUri, resourceUri); |
| } else { |
| _loadDataAsyncDartIO(tag, uri, libraryUri, resourceUri); |
| } |
| |
| } |
| |
| // Returns either a file path or a URI starting with http[s]:, as a String. |
| String _filePathFromUri(String userUri) { |
| var uri = Uri.parse(userUri); |
| if (_logBuiltin) { |
| _print('# Getting file path from: $uri'); |
| } |
| |
| var path; |
| switch (uri.scheme) { |
| case '': |
| case 'file': |
| return uri.toFilePath(); |
| case 'package': |
| return _filePathFromUri(_resolvePackageUri(uri).toString()); |
| case 'http': |
| case 'https': |
| return uri.toString(); |
| default: |
| // Only handling file, http, and package URIs |
| // in standalone binary. |
| if (_logBuiltin) { |
| _print('# Unknown scheme (${uri.scheme}) in $uri.'); |
| } |
| throw 'Not a known scheme: $uri'; |
| } |
| } |
| |
| String _nativeLibraryExtension() native "Builtin_NativeLibraryExtension"; |
| |
| String _platformExtensionFileName(String name) { |
| var extension = _nativeLibraryExtension(); |
| |
| if (_isWindows) { |
| return '$name.$extension'; |
| } else { |
| return 'lib$name.$extension'; |
| } |
| } |
| |
| // Returns the directory part, the filename part, and the name |
| // of a native extension URL as a list [directory, filename, name]. |
| // The directory part is either a file system path or an HTTP(S) URL. |
| // The filename part is the extension name, with the platform-dependent |
| // prefixes and extensions added. |
| _extensionPathFromUri(String userUri) { |
| if (!userUri.startsWith(_DART_EXT)) { |
| throw 'Unexpected internal error: Extension URI $userUri missing dart-ext:'; |
| } |
| userUri = userUri.substring(_DART_EXT.length); |
| |
| if (userUri.contains('\\')) { |
| throw 'Unexpected internal error: Extension URI $userUri contains \\'; |
| } |
| |
| |
| String name; |
| String path; // Will end in '/'. |
| int index = userUri.lastIndexOf('/'); |
| if (index == -1) { |
| name = userUri; |
| path = './'; |
| } else if (index == userUri.length - 1) { |
| throw 'Extension name missing in $extensionUri'; |
| } else { |
| name = userUri.substring(index + 1); |
| path = userUri.substring(0, index + 1); |
| } |
| |
| path = _filePathFromUri(path); |
| var filename = _platformExtensionFileName(name); |
| |
| return [path, filename, name]; |
| } |