| // Copyright (c) 2013, 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 _js_helper; |
| |
| import 'dart:_js_embedded_names' show |
| GET_TYPE_FROM_NAME, |
| GET_ISOLATE_TAG, |
| INTERCEPTED_NAMES, |
| INTERCEPTORS_BY_TAG, |
| LEAF_TAGS, |
| METADATA, |
| DEFERRED_LIBRARY_URIS, |
| DEFERRED_LIBRARY_HASHES, |
| INITIALIZE_LOADED_HUNK, |
| IS_HUNK_LOADED, |
| IS_HUNK_INITIALIZED, |
| NATIVE_SUPERCLASS_TAG_NAME; |
| |
| import 'dart:collection'; |
| import 'dart:_isolate_helper' show |
| IsolateNatives, |
| enterJsAsync, |
| isWorker, |
| leaveJsAsync; |
| |
| import 'dart:async' show |
| Future, |
| DeferredLoadException, |
| Completer, |
| StreamController, |
| Stream, |
| StreamSubscription, |
| scheduleMicrotask; |
| |
| import 'dart:_foreign_helper' show |
| DART_CLOSURE_TO_JS, |
| JS, |
| JS_CALL_IN_ISOLATE, |
| JS_CONST, |
| JS_CURRENT_ISOLATE, |
| JS_CURRENT_ISOLATE_CONTEXT, |
| JS_DART_OBJECT_CONSTRUCTOR, |
| JS_EFFECT, |
| JS_EMBEDDED_GLOBAL, |
| JS_FUNCTION_CLASS_NAME, |
| JS_FUNCTION_TYPE_NAMED_PARAMETERS_TAG, |
| JS_FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG, |
| JS_FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG, |
| JS_FUNCTION_TYPE_RETURN_TYPE_TAG, |
| JS_FUNCTION_TYPE_TAG, |
| JS_FUNCTION_TYPE_VOID_RETURN_TAG, |
| JS_GET_NAME, |
| JS_GET_FLAG, |
| JS_HAS_EQUALS, |
| JS_IS_INDEXABLE_FIELD_NAME, |
| JS_NULL_CLASS_NAME, |
| JS_OBJECT_CLASS_NAME, |
| JS_OPERATOR_AS_PREFIX, |
| JS_OPERATOR_IS_PREFIX, |
| JS_SIGNATURE_NAME, |
| JS_STRING_CONCAT, |
| RAW_DART_FUNCTION_REF; |
| |
| import 'dart:_interceptors'; |
| import 'dart:_internal' as _symbol_dev; |
| import 'dart:_internal' show MappedIterable; |
| |
| import 'dart:_native_typed_data'; |
| |
| import 'dart:_js_names' show |
| extractKeys, |
| mangledNames, |
| unmangleGlobalNameIfPreservedAnyways, |
| unmangleAllIdentifiersIfPreservedAnyways; |
| |
| part 'annotations.dart'; |
| part 'constant_map.dart'; |
| part 'native_helper.dart'; |
| part 'regexp_helper.dart'; |
| part 'string_helper.dart'; |
| part 'js_rti.dart'; |
| |
| /// Marks the internal map in dart2js, so that internal libraries can is-check |
| /// them. |
| abstract class InternalMap { |
| } |
| |
| /// No-op method that is called to inform the compiler that preambles might |
| /// be needed when executing the resulting JS file in a command-line |
| /// JS engine. |
| requiresPreamble() {} |
| |
| bool isJsIndexable(var object, var record) { |
| if (record != null) { |
| var result = dispatchRecordIndexability(record); |
| if (result != null) return result; |
| } |
| return object is JavaScriptIndexingBehavior; |
| } |
| |
| String S(value) { |
| if (value is String) return value; |
| if (value is num) { |
| if (value != 0) { |
| // ""+x is faster than String(x) for integers on most browsers. |
| return JS('String', r'"" + (#)', value); |
| } |
| } else if (true == value) { |
| return 'true'; |
| } else if (false == value) { |
| return 'false'; |
| } else if (value == null) { |
| return 'null'; |
| } |
| var res = value.toString(); |
| if (res is !String) throw new ArgumentError(value); |
| return res; |
| } |
| |
| createInvocationMirror(String name, internalName, kind, arguments, |
| argumentNames) { |
| return new JSInvocationMirror(name, |
| internalName, |
| kind, |
| arguments, |
| argumentNames); |
| } |
| |
| createUnmangledInvocationMirror(Symbol symbol, internalName, kind, arguments, |
| argumentNames) { |
| return new JSInvocationMirror(symbol, |
| internalName, |
| kind, |
| arguments, |
| argumentNames); |
| } |
| |
| void throwInvalidReflectionError(String memberName) { |
| throw new UnsupportedError("Can't use '$memberName' in reflection " |
| "because it is not included in a @MirrorsUsed annotation."); |
| } |
| |
| /// Helper to print the given method information to the console the first |
| /// time it is called with it. |
| @NoInline() |
| void traceHelper(String method) { |
| if (JS('bool', '!this.cache')) { |
| JS('', 'this.cache = Object.create(null)'); |
| } |
| if (JS('bool', '!this.cache[#]', method)) { |
| JS('', 'console.log(#)', method); |
| JS('', 'this.cache[#] = true', method); |
| } |
| } |
| |
| class JSInvocationMirror implements Invocation { |
| static const METHOD = 0; |
| static const GETTER = 1; |
| static const SETTER = 2; |
| |
| /// When [_memberName] is a String, it holds the mangled name of this |
| /// invocation. When it is a Symbol, it holds the unmangled name. |
| var /* String or Symbol */ _memberName; |
| final String _internalName; |
| final int _kind; |
| final List _arguments; |
| final List _namedArgumentNames; |
| /** Map from argument name to index in _arguments. */ |
| Map<String, dynamic> _namedIndices = null; |
| |
| JSInvocationMirror(this._memberName, |
| this._internalName, |
| this._kind, |
| this._arguments, |
| this._namedArgumentNames); |
| |
| Symbol get memberName { |
| if (_memberName is Symbol) return _memberName; |
| String name = _memberName; |
| String unmangledName = mangledNames[name]; |
| if (unmangledName != null) { |
| name = unmangledName.split(':')[0]; |
| } else { |
| if (mangledNames[_internalName] == null) { |
| print("Warning: '$name' is used reflectively but not in MirrorsUsed. " |
| "This will break minified code."); |
| } |
| } |
| _memberName = new _symbol_dev.Symbol.unvalidated(name); |
| return _memberName; |
| } |
| |
| bool get isMethod => _kind == METHOD; |
| bool get isGetter => _kind == GETTER; |
| bool get isSetter => _kind == SETTER; |
| bool get isAccessor => _kind != METHOD; |
| |
| List get positionalArguments { |
| if (isGetter) return const []; |
| var argumentCount = _arguments.length - _namedArgumentNames.length; |
| if (argumentCount == 0) return const []; |
| var list = []; |
| for (var index = 0 ; index < argumentCount ; index++) { |
| list.add(_arguments[index]); |
| } |
| return makeLiteralListConst(list); |
| } |
| |
| Map<Symbol, dynamic> get namedArguments { |
| // TODO: Make maps const (issue 10471) |
| if (isAccessor) return <Symbol, dynamic>{}; |
| int namedArgumentCount = _namedArgumentNames.length; |
| int namedArgumentsStartIndex = _arguments.length - namedArgumentCount; |
| if (namedArgumentCount == 0) return <Symbol, dynamic>{}; |
| var map = new Map<Symbol, dynamic>(); |
| for (int i = 0; i < namedArgumentCount; i++) { |
| map[new _symbol_dev.Symbol.unvalidated(_namedArgumentNames[i])] = |
| _arguments[namedArgumentsStartIndex + i]; |
| } |
| return map; |
| } |
| |
| _getCachedInvocation(Object object) { |
| var interceptor = getInterceptor(object); |
| var receiver = object; |
| var name = _internalName; |
| var arguments = _arguments; |
| var interceptedNames = JS_EMBEDDED_GLOBAL('', INTERCEPTED_NAMES); |
| bool isIntercepted = |
| JS("bool", 'Object.prototype.hasOwnProperty.call(#, #)', |
| interceptedNames, name); |
| if (isIntercepted) { |
| receiver = interceptor; |
| if (JS('bool', '# === #', object, interceptor)) { |
| interceptor = null; |
| } |
| } else { |
| interceptor = null; |
| } |
| bool isCatchAll = false; |
| var method = JS('var', '#[#]', receiver, name); |
| if (JS('bool', 'typeof # != "function"', method) ) { |
| String baseName = _symbol_dev.Symbol.getName(memberName); |
| method = JS('', '#[# + "*"]', receiver, baseName); |
| if (method == null) { |
| interceptor = getInterceptor(object); |
| method = JS('', '#[# + "*"]', interceptor, baseName); |
| if (method != null) { |
| isIntercepted = true; |
| receiver = interceptor; |
| } else { |
| interceptor = null; |
| } |
| } |
| isCatchAll = true; |
| } |
| if (JS('bool', 'typeof # == "function"', method)) { |
| if (isCatchAll) { |
| return new CachedCatchAllInvocation( |
| name, method, isIntercepted, interceptor); |
| } else { |
| return new CachedInvocation(name, method, isIntercepted, interceptor); |
| } |
| } else { |
| // In this case, receiver doesn't implement name. So we should |
| // invoke noSuchMethod instead (which will often throw a |
| // NoSuchMethodError). |
| return new CachedNoSuchMethodInvocation(interceptor); |
| } |
| } |
| |
| /// This method is called by [InstanceMirror.delegate]. |
| static invokeFromMirror(JSInvocationMirror invocation, Object victim) { |
| var cached = invocation._getCachedInvocation(victim); |
| if (cached.isNoSuchMethod) { |
| return cached.invokeOn(victim, invocation); |
| } else { |
| return cached.invokeOn(victim, invocation._arguments); |
| } |
| } |
| |
| static getCachedInvocation(JSInvocationMirror invocation, Object victim) { |
| return invocation._getCachedInvocation(victim); |
| } |
| } |
| |
| class CachedInvocation { |
| // The mangled name of this invocation. |
| String mangledName; |
| |
| /// The JS function to call. |
| var jsFunction; |
| |
| /// True if this is an intercepted call. |
| bool isIntercepted; |
| |
| /// Non-null interceptor if this is an intercepted call through an |
| /// [Interceptor]. |
| Interceptor cachedInterceptor; |
| |
| CachedInvocation(this.mangledName, |
| this.jsFunction, |
| this.isIntercepted, |
| this.cachedInterceptor); |
| |
| bool get isNoSuchMethod => false; |
| bool get isGetterStub => JS("bool", "!!#.\$getterStub", jsFunction); |
| |
| /// Applies [jsFunction] to [victim] with [arguments]. |
| /// Users of this class must take care to check the arguments first. |
| invokeOn(Object victim, List arguments) { |
| var receiver = victim; |
| if (!isIntercepted) { |
| if (arguments is! JSArray) arguments = new List.from(arguments); |
| } else { |
| arguments = [victim]..addAll(arguments); |
| if (cachedInterceptor != null) receiver = cachedInterceptor; |
| } |
| return JS("var", "#.apply(#, #)", jsFunction, receiver, arguments); |
| } |
| } |
| |
| class CachedCatchAllInvocation extends CachedInvocation { |
| final ReflectionInfo info; |
| |
| CachedCatchAllInvocation(String name, |
| jsFunction, |
| bool isIntercepted, |
| Interceptor cachedInterceptor) |
| : info = new ReflectionInfo(jsFunction), |
| super(name, jsFunction, isIntercepted, cachedInterceptor); |
| |
| bool get isGetterStub => false; |
| |
| invokeOn(Object victim, List arguments) { |
| var receiver = victim; |
| int providedArgumentCount; |
| int fullParameterCount = |
| info.requiredParameterCount + info.optionalParameterCount; |
| if (!isIntercepted) { |
| if (arguments is JSArray) { |
| providedArgumentCount = arguments.length; |
| // If we need to add extra arguments before calling, we have |
| // to copy the arguments array. |
| if (providedArgumentCount < fullParameterCount) { |
| arguments = new List.from(arguments); |
| } |
| } else { |
| arguments = new List.from(arguments); |
| providedArgumentCount = arguments.length; |
| } |
| } else { |
| arguments = [victim]..addAll(arguments); |
| if (cachedInterceptor != null) receiver = cachedInterceptor; |
| providedArgumentCount = arguments.length - 1; |
| } |
| if (info.areOptionalParametersNamed && |
| (providedArgumentCount > info.requiredParameterCount)) { |
| throw new UnimplementedNoSuchMethodError( |
| "Invocation of unstubbed method '${info.reflectionName}'" |
| " with ${arguments.length} arguments."); |
| } else if (providedArgumentCount < info.requiredParameterCount) { |
| throw new UnimplementedNoSuchMethodError( |
| "Invocation of unstubbed method '${info.reflectionName}'" |
| " with $providedArgumentCount arguments (too few)."); |
| } else if (providedArgumentCount > fullParameterCount) { |
| throw new UnimplementedNoSuchMethodError( |
| "Invocation of unstubbed method '${info.reflectionName}'" |
| " with $providedArgumentCount arguments (too many)."); |
| } |
| for (int i = providedArgumentCount; i < fullParameterCount; i++) { |
| arguments.add(getMetadata(info.defaultValue(i))); |
| } |
| return JS("var", "#.apply(#, #)", jsFunction, receiver, arguments); |
| } |
| } |
| |
| class CachedNoSuchMethodInvocation { |
| /// Non-null interceptor if this is an intercepted call through an |
| /// [Interceptor]. |
| var interceptor; |
| |
| CachedNoSuchMethodInvocation(this.interceptor); |
| |
| bool get isNoSuchMethod => true; |
| bool get isGetterStub => false; |
| |
| invokeOn(Object victim, Invocation invocation) { |
| var receiver = (interceptor == null) ? victim : interceptor; |
| return receiver.noSuchMethod(invocation); |
| } |
| } |
| |
| class ReflectionInfo { |
| static const int REQUIRED_PARAMETERS_INFO = 0; |
| static const int OPTIONAL_PARAMETERS_INFO = 1; |
| static const int FUNCTION_TYPE_INDEX = 2; |
| static const int FIRST_DEFAULT_ARGUMENT = 3; |
| |
| /// A JavaScript function object. |
| final jsFunction; |
| |
| /// Raw reflection information. |
| final List data; |
| |
| /// Is this a getter or a setter. |
| final bool isAccessor; |
| |
| /// Number of required parameters. |
| final int requiredParameterCount; |
| |
| /// Number of optional parameters. |
| final int optionalParameterCount; |
| |
| /// Are optional parameters named. |
| final bool areOptionalParametersNamed; |
| |
| /// Either an index to the function type in the embedded `metadata` global or |
| /// a JavaScript function object which can compute such a type (presumably |
| /// due to free type variables). |
| final functionType; |
| |
| List cachedSortedIndices; |
| |
| ReflectionInfo.internal(this.jsFunction, |
| this.data, |
| this.isAccessor, |
| this.requiredParameterCount, |
| this.optionalParameterCount, |
| this.areOptionalParametersNamed, |
| this.functionType); |
| |
| factory ReflectionInfo(jsFunction) { |
| List data = JS('JSExtendableArray|Null', r'#.$reflectionInfo', jsFunction); |
| if (data == null) return null; |
| data = JSArray.markFixedList(data); |
| |
| int requiredParametersInfo = |
| JS('int', '#[#]', data, REQUIRED_PARAMETERS_INFO); |
| int requiredParameterCount = JS('int', '# >> 1', requiredParametersInfo); |
| bool isAccessor = (requiredParametersInfo & 1) == 1; |
| |
| int optionalParametersInfo = |
| JS('int', '#[#]', data, OPTIONAL_PARAMETERS_INFO); |
| int optionalParameterCount = JS('int', '# >> 1', optionalParametersInfo); |
| bool areOptionalParametersNamed = (optionalParametersInfo & 1) == 1; |
| |
| var functionType = JS('', '#[#]', data, FUNCTION_TYPE_INDEX); |
| return new ReflectionInfo.internal( |
| jsFunction, data, isAccessor, requiredParameterCount, |
| optionalParameterCount, areOptionalParametersNamed, functionType); |
| } |
| |
| String parameterName(int parameter) { |
| int metadataIndex; |
| if (JS_GET_FLAG('MUST_RETAIN_METADATA')) { |
| metadataIndex = JS('int', '#[2 * # + # + #]', data, |
| parameter, optionalParameterCount, FIRST_DEFAULT_ARGUMENT); |
| } else { |
| metadataIndex = JS('int', '#[# + # + #]', data, |
| parameter, optionalParameterCount, FIRST_DEFAULT_ARGUMENT); |
| } |
| var metadata = JS_EMBEDDED_GLOBAL('', METADATA); |
| return JS('String', '#[#]', metadata, metadataIndex); |
| } |
| |
| List<int> parameterMetadataAnnotations(int parameter) { |
| if (!JS_GET_FLAG('MUST_RETAIN_METADATA')) { |
| throw new StateError('metadata has not been preserved'); |
| } else { |
| return JS('', '#[2 * # + # + # + 1]', data, parameter, |
| optionalParameterCount, FIRST_DEFAULT_ARGUMENT); |
| } |
| } |
| |
| int defaultValue(int parameter) { |
| if (parameter < requiredParameterCount) return null; |
| return JS('int', '#[# + # - #]', data, |
| FIRST_DEFAULT_ARGUMENT, parameter, requiredParameterCount); |
| } |
| |
| /// Returns the default value of the [parameter]th entry of the list of |
| /// parameters sorted by name. |
| int defaultValueInOrder(int parameter) { |
| if (parameter < requiredParameterCount) return null; |
| |
| if (!areOptionalParametersNamed || optionalParameterCount == 1) { |
| return defaultValue(parameter); |
| } |
| |
| int index = sortedIndex(parameter - requiredParameterCount); |
| return defaultValue(index); |
| } |
| |
| /// Returns the default value of the [parameter]th entry of the list of |
| /// parameters sorted by name. |
| String parameterNameInOrder(int parameter) { |
| if (parameter < requiredParameterCount) return null; |
| |
| if (!areOptionalParametersNamed || |
| optionalParameterCount == 1) { |
| return parameterName(parameter); |
| } |
| |
| int index = sortedIndex(parameter - requiredParameterCount); |
| return parameterName(index); |
| } |
| |
| /// Computes the index of the parameter in the list of named parameters sorted |
| /// by their name. |
| int sortedIndex(int unsortedIndex) { |
| if (cachedSortedIndices == null) { |
| // TODO(karlklose): cache this between [ReflectionInfo] instances or cache |
| // [ReflectionInfo] instances by [jsFunction]. |
| cachedSortedIndices = new List(optionalParameterCount); |
| Map<String, int> positions = <String, int>{}; |
| for (int i = 0; i < optionalParameterCount; i++) { |
| int index = requiredParameterCount + i; |
| positions[parameterName(index)] = index; |
| } |
| int index = 0; |
| (positions.keys.toList()..sort()).forEach((String name) { |
| cachedSortedIndices[index++] = positions[name]; |
| }); |
| } |
| return cachedSortedIndices[unsortedIndex]; |
| } |
| |
| @NoInline() |
| computeFunctionRti(jsConstructor) { |
| if (JS('bool', 'typeof # == "number"', functionType)) { |
| return getMetadata(functionType); |
| } else if (JS('bool', 'typeof # == "function"', functionType)) { |
| var fakeInstance = JS('', 'new #()', jsConstructor); |
| setRuntimeTypeInfo( |
| fakeInstance, JS('JSExtendableArray', '#["<>"]', fakeInstance)); |
| return JS('=Object|Null', r'#.apply({$receiver:#})', |
| functionType, fakeInstance); |
| } else { |
| throw new RuntimeError('Unexpected function type'); |
| } |
| } |
| |
| String get reflectionName => JS('String', r'#.$reflectionName', jsFunction); |
| } |
| |
| getMetadata(int index) { |
| var metadata = JS_EMBEDDED_GLOBAL('', METADATA); |
| return JS('', '#[#]', metadata, index); |
| } |
| |
| class Primitives { |
| /// Isolate-unique ID for caching [JsClosureMirror.function]. |
| /// Note the initial value is used by the first isolate (or if there are no |
| /// isolates), new isolates will update this value to avoid conflicts by |
| /// calling [initializeStatics]. |
| static String mirrorFunctionCacheName = '\$cachedFunction'; |
| |
| /// Isolate-unique ID for caching [JsInstanceMirror._invoke]. |
| static String mirrorInvokeCacheName = '\$cachedInvocation'; |
| |
| /// Called when creating a new isolate (see _IsolateContext constructor in |
| /// isolate_helper.dart). |
| /// Please don't add complicated code to this method, as it will impact |
| /// start-up performance. |
| static void initializeStatics(int id) { |
| // Benchmarking shows significant performance improvements if this is a |
| // fixed value. |
| mirrorFunctionCacheName += '_$id'; |
| mirrorInvokeCacheName += '_$id'; |
| } |
| |
| static int objectHashCode(object) { |
| int hash = JS('int|Null', r'#.$identityHash', object); |
| if (hash == null) { |
| hash = JS('int', '(Math.random() * 0x3fffffff) | 0'); |
| JS('void', r'#.$identityHash = #', object, hash); |
| } |
| return JS('int', '#', hash); |
| } |
| |
| static _throwFormatException(String string) { |
| throw new FormatException(string); |
| } |
| |
| static int parseInt(String source, |
| int radix, |
| int handleError(String source)) { |
| if (handleError == null) handleError = _throwFormatException; |
| |
| checkString(source); |
| var match = JS('JSExtendableArray|Null', |
| r'/^\s*[+-]?((0x[a-f0-9]+)|(\d+)|([a-z0-9]+))\s*$/i.exec(#)', |
| source); |
| int digitsIndex = 1; |
| int hexIndex = 2; |
| int decimalIndex = 3; |
| int nonDecimalHexIndex = 4; |
| if (radix == null) { |
| radix = 10; |
| if (match != null) { |
| if (match[hexIndex] != null) { |
| // Cannot fail because we know that the digits are all hex. |
| return JS('num', r'parseInt(#, 16)', source); |
| } |
| if (match[decimalIndex] != null) { |
| // Cannot fail because we know that the digits are all decimal. |
| return JS('num', r'parseInt(#, 10)', source); |
| } |
| return handleError(source); |
| } |
| } else { |
| if (radix is! int) throw new ArgumentError("Radix is not an integer"); |
| if (radix < 2 || radix > 36) { |
| throw new RangeError("Radix $radix not in range 2..36"); |
| } |
| if (match != null) { |
| if (radix == 10 && match[decimalIndex] != null) { |
| // Cannot fail because we know that the digits are all decimal. |
| return JS('num', r'parseInt(#, 10)', source); |
| } |
| if (radix < 10 || match[decimalIndex] == null) { |
| // We know that the characters must be ASCII as otherwise the |
| // regexp wouldn't have matched. Lowercasing by doing `| 0x20` is thus |
| // guaranteed to be a safe operation, since it preserves digits |
| // and lower-cases ASCII letters. |
| int maxCharCode; |
| if (radix <= 10) { |
| // Allow all digits less than the radix. For example 0, 1, 2 for |
| // radix 3. |
| // "0".codeUnitAt(0) + radix - 1; |
| maxCharCode = 0x30 + radix - 1; |
| } else { |
| // Letters are located after the digits in ASCII. Therefore we |
| // only check for the character code. The regexp above made already |
| // sure that the string does not contain anything but digits or |
| // letters. |
| // "a".codeUnitAt(0) + (radix - 10) - 1; |
| maxCharCode = 0x61 + radix - 10 - 1; |
| } |
| String digitsPart = match[digitsIndex]; |
| for (int i = 0; i < digitsPart.length; i++) { |
| int characterCode = digitsPart.codeUnitAt(0) | 0x20; |
| if (digitsPart.codeUnitAt(i) > maxCharCode) { |
| return handleError(source); |
| } |
| } |
| } |
| } |
| } |
| if (match == null) return handleError(source); |
| return JS('num', r'parseInt(#, #)', source, radix); |
| } |
| |
| static double parseDouble(String source, double handleError(String source)) { |
| checkString(source); |
| if (handleError == null) handleError = _throwFormatException; |
| // Notice that JS parseFloat accepts garbage at the end of the string. |
| // Accept only: |
| // - [+/-]NaN |
| // - [+/-]Infinity |
| // - a Dart double literal |
| // We do allow leading or trailing whitespace. |
| if (!JS('bool', |
| r'/^\s*[+-]?(?:Infinity|NaN|' |
| r'(?:\.\d+|\d+(?:\.\d*)?)(?:[eE][+-]?\d+)?)\s*$/.test(#)', |
| source)) { |
| return handleError(source); |
| } |
| var result = JS('num', r'parseFloat(#)', source); |
| if (result.isNaN) { |
| var trimmed = source.trim(); |
| if (trimmed == 'NaN' || trimmed == '+NaN' || trimmed == '-NaN') { |
| return result; |
| } |
| return handleError(source); |
| } |
| return result; |
| } |
| |
| /** [: r"$".codeUnitAt(0) :] */ |
| static const int DOLLAR_CHAR_VALUE = 36; |
| |
| /// Creates a string containing the complete type for the class [className] |
| /// with the given type arguments. |
| /// |
| /// In minified mode, uses the unminified names if available. |
| static String formatType(String className, List typeArguments) { |
| return unmangleAllIdentifiersIfPreservedAnyways |
| ('$className${joinArguments(typeArguments, 0)}'); |
| } |
| |
| /// Returns the type of [object] as a string (including type arguments). |
| /// |
| /// In minified mode, uses the unminified names if available. |
| static String objectTypeName(Object object) { |
| String name = constructorNameFallback(getInterceptor(object)); |
| if (name == 'Object') { |
| // Try to decompile the constructor by turning it into a string and get |
| // the name out of that. If the decompiled name is a string containing an |
| // identifier, we use that instead of the very generic 'Object'. |
| var decompiled = |
| JS('var', r'#.match(/^\s*function\s*(\S*)\s*\(/)[1]', |
| JS('var', r'String(#.constructor)', object)); |
| if (decompiled is String) |
| if (JS('bool', r'/^\w+$/.test(#)', decompiled)) |
| name = decompiled; |
| } |
| // TODO(kasperl): If the namer gave us a fresh global name, we may |
| // want to remove the numeric suffix that makes it unique too. |
| if (name.length > 1 && identical(name.codeUnitAt(0), DOLLAR_CHAR_VALUE)) { |
| name = name.substring(1); |
| } |
| return formatType(name, getRuntimeTypeInfo(object)); |
| } |
| |
| /// In minified mode, uses the unminified names if available. |
| static String objectToString(Object object) { |
| String name = objectTypeName(object); |
| return "Instance of '$name'"; |
| } |
| |
| static num dateNow() => JS('int', r'Date.now()'); |
| |
| static void initTicker() { |
| if (timerFrequency != null) return; |
| // Start with low-resolution. We overwrite the fields if we find better. |
| timerFrequency = 1000; |
| timerTicks = dateNow; |
| if (JS('bool', 'typeof window == "undefined"')) return; |
| var window = JS('var', 'window'); |
| if (window == null) return; |
| var performance = JS('var', '#.performance', window); |
| if (performance == null) return; |
| if (JS('bool', 'typeof #.now != "function"', performance)) return; |
| timerFrequency = 1000000; |
| timerTicks = () => (1000 * JS('num', '#.now()', performance)).floor(); |
| } |
| |
| static int timerFrequency; |
| static Function timerTicks; |
| |
| static bool get isD8 { |
| return JS('bool', |
| 'typeof version == "function"' |
| ' && typeof os == "object" && "system" in os'); |
| } |
| |
| static bool get isJsshell { |
| return JS('bool', |
| 'typeof version == "function" && typeof system == "function"'); |
| } |
| |
| static String currentUri() { |
| requiresPreamble(); |
| // In a browser return self.location.href. |
| if (JS('bool', '!!self.location')) { |
| return JS('String', 'self.location.href'); |
| } |
| |
| return null; |
| } |
| |
| // This is to avoid stack overflows due to very large argument arrays in |
| // apply(). It fixes http://dartbug.com/6919 |
| static String _fromCharCodeApply(List<int> array) { |
| const kMaxApply = 500; |
| int end = array.length; |
| if (end <= kMaxApply) { |
| return JS('String', r'String.fromCharCode.apply(null, #)', array); |
| } |
| String result = ''; |
| for (int i = 0; i < end; i += kMaxApply) { |
| int chunkEnd = (i + kMaxApply < end) ? i + kMaxApply : end; |
| result = JS('String', |
| r'# + String.fromCharCode.apply(null, #.slice(#, #))', |
| result, array, i, chunkEnd); |
| } |
| return result; |
| } |
| |
| static String stringFromCodePoints(codePoints) { |
| List<int> a = <int>[]; |
| for (var i in codePoints) { |
| if (i is !int) throw new ArgumentError(i); |
| if (i <= 0xffff) { |
| a.add(i); |
| } else if (i <= 0x10ffff) { |
| a.add(0xd800 + ((((i - 0x10000) >> 10) & 0x3ff))); |
| a.add(0xdc00 + (i & 0x3ff)); |
| } else { |
| throw new ArgumentError(i); |
| } |
| } |
| return _fromCharCodeApply(a); |
| } |
| |
| static String stringFromCharCodes(charCodes) { |
| for (var i in charCodes) { |
| if (i is !int) throw new ArgumentError(i); |
| if (i < 0) throw new ArgumentError(i); |
| if (i > 0xffff) return stringFromCodePoints(charCodes); |
| } |
| return _fromCharCodeApply(charCodes); |
| } |
| |
| // [start] and [end] are validated. |
| static String stringFromNativeUint8List( |
| NativeUint8List charCodes, int start, int end) { |
| const kMaxApply = 500; |
| if (end <= kMaxApply && start == 0 && end == charCodes.length) { |
| return JS('String', r'String.fromCharCode.apply(null, #)', charCodes); |
| } |
| String result = ''; |
| for (int i = start; i < end; i += kMaxApply) { |
| int chunkEnd = (i + kMaxApply < end) ? i + kMaxApply : end; |
| result = JS('String', |
| r'# + String.fromCharCode.apply(null, #.subarray(#, #))', |
| result, charCodes, i, chunkEnd); |
| } |
| return result; |
| } |
| |
| |
| static String stringFromCharCode(charCode) { |
| if (0 <= charCode) { |
| if (charCode <= 0xffff) { |
| return JS('String', 'String.fromCharCode(#)', charCode); |
| } |
| if (charCode <= 0x10ffff) { |
| var bits = charCode - 0x10000; |
| var low = 0xDC00 | (bits & 0x3ff); |
| var high = 0xD800 | (bits >> 10); |
| return JS('String', 'String.fromCharCode(#, #)', high, low); |
| } |
| } |
| throw new RangeError.range(charCode, 0, 0x10ffff); |
| } |
| |
| static String stringConcatUnchecked(String string1, String string2) { |
| return JS_STRING_CONCAT(string1, string2); |
| } |
| |
| static String flattenString(String str) { |
| return JS('String', "#.charCodeAt(0) == 0 ? # : #", str, str, str); |
| } |
| |
| static String getTimeZoneName(receiver) { |
| // Firefox and Chrome emit the timezone in parenthesis. |
| // Example: "Wed May 16 2012 21:13:00 GMT+0200 (CEST)". |
| // We extract this name using a regexp. |
| var d = lazyAsJsDate(receiver); |
| List match = JS('JSArray|Null', r'/\((.*)\)/.exec(#.toString())', d); |
| if (match != null) return match[1]; |
| |
| // Internet Explorer 10+ emits the zone name without parenthesis: |
| // Example: Thu Oct 31 14:07:44 PDT 2013 |
| match = JS('JSArray|Null', |
| // Thu followed by a space. |
| r'/^[A-Z,a-z]{3}\s' |
| // Oct 31 followed by space. |
| r'[A-Z,a-z]{3}\s\d+\s' |
| // Time followed by a space. |
| r'\d{2}:\d{2}:\d{2}\s' |
| // The time zone name followed by a space. |
| r'([A-Z]{3,5})\s' |
| // The year. |
| r'\d{4}$/' |
| '.exec(#.toString())', |
| d); |
| if (match != null) return match[1]; |
| |
| // IE 9 and Opera don't provide the zone name. We fall back to emitting the |
| // UTC/GMT offset. |
| // Example (IE9): Wed Nov 20 09:51:00 UTC+0100 2013 |
| // (Opera): Wed Nov 20 2013 11:03:38 GMT+0100 |
| match = JS('JSArray|Null', r'/(?:GMT|UTC)[+-]\d{4}/.exec(#.toString())', d); |
| if (match != null) return match[0]; |
| return ""; |
| } |
| |
| static int getTimeZoneOffsetInMinutes(receiver) { |
| // Note that JS and Dart disagree on the sign of the offset. |
| return -JS('int', r'#.getTimezoneOffset()', lazyAsJsDate(receiver)); |
| } |
| |
| static valueFromDecomposedDate(years, month, day, hours, minutes, seconds, |
| milliseconds, isUtc) { |
| final int MAX_MILLISECONDS_SINCE_EPOCH = 8640000000000000; |
| checkInt(years); |
| checkInt(month); |
| checkInt(day); |
| checkInt(hours); |
| checkInt(minutes); |
| checkInt(seconds); |
| checkInt(milliseconds); |
| checkBool(isUtc); |
| var jsMonth = month - 1; |
| var value; |
| if (isUtc) { |
| value = JS('num', r'Date.UTC(#, #, #, #, #, #, #)', |
| years, jsMonth, day, hours, minutes, seconds, milliseconds); |
| } else { |
| value = JS('num', r'new Date(#, #, #, #, #, #, #).valueOf()', |
| years, jsMonth, day, hours, minutes, seconds, milliseconds); |
| } |
| if (value.isNaN || |
| value < -MAX_MILLISECONDS_SINCE_EPOCH || |
| value > MAX_MILLISECONDS_SINCE_EPOCH) { |
| return null; |
| } |
| if (years <= 0 || years < 100) return patchUpY2K(value, years, isUtc); |
| return value; |
| } |
| |
| static patchUpY2K(value, years, isUtc) { |
| var date = JS('', r'new Date(#)', value); |
| if (isUtc) { |
| JS('num', r'#.setUTCFullYear(#)', date, years); |
| } else { |
| JS('num', r'#.setFullYear(#)', date, years); |
| } |
| return JS('num', r'#.valueOf()', date); |
| } |
| |
| // Lazily keep a JS Date stored in the JS object. |
| static lazyAsJsDate(receiver) { |
| if (JS('bool', r'#.date === (void 0)', receiver)) { |
| JS('void', r'#.date = new Date(#)', receiver, |
| receiver.millisecondsSinceEpoch); |
| } |
| return JS('var', r'#.date', receiver); |
| } |
| |
| // The getters for date and time parts below add a positive integer to ensure |
| // that the result is really an integer, because the JavaScript implementation |
| // may return -0.0 instead of 0. |
| |
| static getYear(receiver) { |
| return (receiver.isUtc) |
| ? JS('int', r'(#.getUTCFullYear() + 0)', lazyAsJsDate(receiver)) |
| : JS('int', r'(#.getFullYear() + 0)', lazyAsJsDate(receiver)); |
| } |
| |
| static getMonth(receiver) { |
| return (receiver.isUtc) |
| ? JS('int', r'#.getUTCMonth() + 1', lazyAsJsDate(receiver)) |
| : JS('int', r'#.getMonth() + 1', lazyAsJsDate(receiver)); |
| } |
| |
| static getDay(receiver) { |
| return (receiver.isUtc) |
| ? JS('int', r'(#.getUTCDate() + 0)', lazyAsJsDate(receiver)) |
| : JS('int', r'(#.getDate() + 0)', lazyAsJsDate(receiver)); |
| } |
| |
| static getHours(receiver) { |
| return (receiver.isUtc) |
| ? JS('int', r'(#.getUTCHours() + 0)', lazyAsJsDate(receiver)) |
| : JS('int', r'(#.getHours() + 0)', lazyAsJsDate(receiver)); |
| } |
| |
| static getMinutes(receiver) { |
| return (receiver.isUtc) |
| ? JS('int', r'(#.getUTCMinutes() + 0)', lazyAsJsDate(receiver)) |
| : JS('int', r'(#.getMinutes() + 0)', lazyAsJsDate(receiver)); |
| } |
| |
| static getSeconds(receiver) { |
| return (receiver.isUtc) |
| ? JS('int', r'(#.getUTCSeconds() + 0)', lazyAsJsDate(receiver)) |
| : JS('int', r'(#.getSeconds() + 0)', lazyAsJsDate(receiver)); |
| } |
| |
| static getMilliseconds(receiver) { |
| return (receiver.isUtc) |
| ? JS('int', r'(#.getUTCMilliseconds() + 0)', lazyAsJsDate(receiver)) |
| : JS('int', r'(#.getMilliseconds() + 0)', lazyAsJsDate(receiver)); |
| } |
| |
| static getWeekday(receiver) { |
| int weekday = (receiver.isUtc) |
| ? JS('int', r'#.getUTCDay() + 0', lazyAsJsDate(receiver)) |
| : JS('int', r'#.getDay() + 0', lazyAsJsDate(receiver)); |
| // Adjust by one because JS weeks start on Sunday. |
| return (weekday + 6) % 7 + 1; |
| } |
| |
| static valueFromDateString(str) { |
| if (str is !String) throw new ArgumentError(str); |
| var value = JS('num', r'Date.parse(#)', str); |
| if (value.isNaN) throw new ArgumentError(str); |
| return value; |
| } |
| |
| static getProperty(object, key) { |
| if (object == null || object is bool || object is num || object is String) { |
| throw new ArgumentError(object); |
| } |
| return JS('var', '#[#]', object, key); |
| } |
| |
| static void setProperty(object, key, value) { |
| if (object == null || object is bool || object is num || object is String) { |
| throw new ArgumentError(object); |
| } |
| JS('void', '#[#] = #', object, key, value); |
| } |
| |
| static functionNoSuchMethod(function, |
| List positionalArguments, |
| Map<String, dynamic> namedArguments) { |
| int argumentCount = 0; |
| List arguments = []; |
| List namedArgumentList = []; |
| |
| if (positionalArguments != null) { |
| argumentCount += positionalArguments.length; |
| arguments.addAll(positionalArguments); |
| } |
| |
| String names = ''; |
| if (namedArguments != null && !namedArguments.isEmpty) { |
| namedArguments.forEach((String name, argument) { |
| names = '$names\$$name'; |
| namedArgumentList.add(name); |
| arguments.add(argument); |
| argumentCount++; |
| }); |
| } |
| |
| String selectorName = |
| '${JS_GET_NAME("CALL_PREFIX")}\$$argumentCount$names'; |
| |
| return function.noSuchMethod( |
| createUnmangledInvocationMirror( |
| #call, |
| selectorName, |
| JSInvocationMirror.METHOD, |
| arguments, |
| namedArgumentList)); |
| } |
| |
| static applyFunction(Function function, |
| List positionalArguments, |
| Map<String, dynamic> namedArguments) { |
| // Dispatch on presence of named arguments to improve tree-shaking. |
| // |
| // This dispatch is as simple as possible to help the compiler detect the |
| // common case of `null` namedArguments, either via inlining or |
| // specialization. |
| return namedArguments == null |
| ? applyFunctionWithPositionalArguments( |
| function, positionalArguments) |
| : applyFunctionWithNamedArguments( |
| function, positionalArguments, namedArguments); |
| } |
| |
| static applyFunctionWithPositionalArguments(Function function, |
| List positionalArguments) { |
| int argumentCount = 0; |
| List arguments; |
| |
| if (positionalArguments != null) { |
| if (JS('bool', '# instanceof Array', positionalArguments)) { |
| arguments = positionalArguments; |
| } else { |
| arguments = new List.from(positionalArguments); |
| } |
| argumentCount = JS('int', '#.length', arguments); |
| } else { |
| arguments = []; |
| } |
| |
| String selectorName = '${JS_GET_NAME("CALL_PREFIX")}\$$argumentCount'; |
| var jsFunction = JS('var', '#[#]', function, selectorName); |
| if (jsFunction == null) { |
| |
| // TODO(ahe): This might occur for optional arguments if there is no call |
| // selector with that many arguments. |
| |
| return functionNoSuchMethod(function, positionalArguments, null); |
| } |
| // We bound 'this' to [function] because of how we compile |
| // closures: escaped local variables are stored and accessed through |
| // [function]. |
| return JS('var', '#.apply(#, #)', jsFunction, function, arguments); |
| } |
| |
| static applyFunctionWithNamedArguments(Function function, |
| List positionalArguments, |
| Map<String, dynamic> namedArguments) { |
| if (namedArguments.isEmpty) { |
| return applyFunctionWithPositionalArguments( |
| function, positionalArguments); |
| } |
| // TODO(ahe): The following code can be shared with |
| // JsInstanceMirror.invoke. |
| var interceptor = getInterceptor(function); |
| var jsFunction = JS('', '#["call*"]', interceptor); |
| |
| if (jsFunction == null) { |
| return functionNoSuchMethod( |
| function, positionalArguments, namedArguments); |
| } |
| ReflectionInfo info = new ReflectionInfo(jsFunction); |
| if (info == null || !info.areOptionalParametersNamed) { |
| return functionNoSuchMethod( |
| function, positionalArguments, namedArguments); |
| } |
| |
| if (positionalArguments != null) { |
| positionalArguments = new List.from(positionalArguments); |
| } else { |
| positionalArguments = []; |
| } |
| // Check the number of positional arguments is valid. |
| if (info.requiredParameterCount != positionalArguments.length) { |
| return functionNoSuchMethod( |
| function, positionalArguments, namedArguments); |
| } |
| var defaultArguments = new Map(); |
| for (int i = 0; i < info.optionalParameterCount; i++) { |
| int index = i + info.requiredParameterCount; |
| var parameterName = info.parameterNameInOrder(index); |
| var value = info.defaultValueInOrder(index); |
| var defaultValue = getMetadata(value); |
| defaultArguments[parameterName] = defaultValue; |
| } |
| bool bad = false; |
| namedArguments.forEach((String parameter, value) { |
| if (defaultArguments.containsKey(parameter)) { |
| defaultArguments[parameter] = value; |
| } else { |
| // Extraneous named argument. |
| bad = true; |
| } |
| }); |
| if (bad) { |
| return functionNoSuchMethod( |
| function, positionalArguments, namedArguments); |
| } |
| positionalArguments.addAll(defaultArguments.values); |
| return JS('', '#.apply(#, #)', jsFunction, function, positionalArguments); |
| } |
| |
| static _mangledNameMatchesType(String mangledName, TypeImpl type) { |
| return JS('bool', '# == #', mangledName, type._typeName); |
| } |
| |
| static bool identicalImplementation(a, b) { |
| return JS('bool', '# == null', a) |
| ? JS('bool', '# == null', b) |
| : JS('bool', '# === #', a, b); |
| } |
| |
| static StackTrace extractStackTrace(Error error) { |
| return getTraceFromException(JS('', r'#.$thrownJsError', error)); |
| } |
| } |
| |
| /// Helper class for allocating and using JS object literals as caches. |
| class JsCache { |
| /// Returns a JavaScript object suitable for use as a cache. |
| static allocate() { |
| var result = JS('=Object', 'Object.create(null)'); |
| // Deleting a property makes V8 assume that it shouldn't create a hidden |
| // class for [result] and map transitions. Although these map transitions |
| // pay off if there are many cache hits for the same keys, it becomes |
| // really slow when there aren't many repeated hits. |
| JS('void', '#.x=0', result); |
| JS('void', 'delete #.x', result); |
| return result; |
| } |
| |
| static fetch(cache, String key) { |
| return JS('', '#[#]', cache, key); |
| } |
| |
| static void update(cache, String key, value) { |
| JS('void', '#[#] = #', cache, key, value); |
| } |
| } |
| |
| /** |
| * Called by generated code to throw an illegal-argument exception, |
| * for example, if a non-integer index is given to an optimized |
| * indexed access. |
| */ |
| iae(argument) { |
| throw new ArgumentError(argument); |
| } |
| |
| /** |
| * Called by generated code to throw an index-out-of-range exception, |
| * for example, if a bounds check fails in an optimized indexed |
| * access. This may also be called when the index is not an integer, in |
| * which case it throws an illegal-argument exception instead, like |
| * [iae], or when the receiver is null. |
| */ |
| ioore(receiver, index) { |
| if (receiver == null) receiver.length; // Force a NoSuchMethodError. |
| if (index is !int) iae(index); |
| throw new RangeError.value(index); |
| } |
| |
| stringLastIndexOfUnchecked(receiver, element, start) |
| => JS('int', r'#.lastIndexOf(#, #)', receiver, element, start); |
| |
| |
| checkNull(object) { |
| if (object == null) throw new ArgumentError(null); |
| return object; |
| } |
| |
| checkNum(value) { |
| if (value is !num) { |
| throw new ArgumentError(value); |
| } |
| return value; |
| } |
| |
| checkInt(value) { |
| if (value is !int) { |
| throw new ArgumentError(value); |
| } |
| return value; |
| } |
| |
| checkBool(value) { |
| if (value is !bool) { |
| throw new ArgumentError(value); |
| } |
| return value; |
| } |
| |
| checkString(value) { |
| if (value is !String) { |
| throw new ArgumentError(value); |
| } |
| return value; |
| } |
| |
| /** |
| * Wrap the given Dart object and record a stack trace. |
| * |
| * The code in [unwrapException] deals with getting the original Dart |
| * object out of the wrapper again. |
| */ |
| @NoInline() |
| wrapException(ex) { |
| if (ex == null) ex = new NullThrownError(); |
| var wrapper = JS('', 'new Error()'); |
| // [unwrapException] looks for the property 'dartException'. |
| JS('void', '#.dartException = #', wrapper, ex); |
| |
| if (JS('bool', '"defineProperty" in Object')) { |
| // Define a JavaScript getter for 'message'. This is to work around V8 bug |
| // (https://code.google.com/p/v8/issues/detail?id=2519). The default |
| // toString on Error returns the value of 'message' if 'name' is |
| // empty. Setting toString directly doesn't work, see the bug. |
| JS('void', 'Object.defineProperty(#, "message", { get: # })', |
| wrapper, DART_CLOSURE_TO_JS(toStringWrapper)); |
| JS('void', '#.name = ""', wrapper); |
| } else { |
| // In the unlikely event the browser doesn't support Object.defineProperty, |
| // hope that it just calls toString. |
| JS('void', '#.toString = #', wrapper, DART_CLOSURE_TO_JS(toStringWrapper)); |
| } |
| |
| return wrapper; |
| } |
| |
| /// Do not call directly. |
| toStringWrapper() { |
| // This method gets installed as toString on a JavaScript object. Due to the |
| // weird scope rules of JavaScript, JS 'this' will refer to that object. |
| return JS('', r'this.dartException').toString(); |
| } |
| |
| /** |
| * This wraps the exception and does the throw. It is possible to call this in |
| * a JS expression context, where the throw statement is not allowed. Helpers |
| * are never inlined, so we don't risk inlining the throw statement into an |
| * expression context. |
| */ |
| throwExpression(ex) { |
| JS('void', 'throw #', wrapException(ex)); |
| } |
| |
| makeLiteralListConst(list) { |
| JS('bool', r'#.immutable$list = #', list, true); |
| JS('bool', r'#.fixed$length = #', list, true); |
| return list; |
| } |
| |
| throwRuntimeError(message) { |
| throw new RuntimeError(message); |
| } |
| |
| throwAbstractClassInstantiationError(className) { |
| throw new AbstractClassInstantiationError(className); |
| } |
| |
| |
| /** |
| * Helper class for building patterns recognizing native type errors. |
| */ |
| class TypeErrorDecoder { |
| // Field names are private to help tree-shaking. |
| |
| /// A regular expression which matches is matched against an error message. |
| final String _pattern; |
| |
| /// The group index of "arguments" in [_pattern], or -1 if _pattern has no |
| /// match for "arguments". |
| final int _arguments; |
| |
| /// The group index of "argumentsExpr" in [_pattern], or -1 if _pattern has |
| /// no match for "argumentsExpr". |
| final int _argumentsExpr; |
| |
| /// The group index of "expr" in [_pattern], or -1 if _pattern has no match |
| /// for "expr". |
| final int _expr; |
| |
| /// The group index of "method" in [_pattern], or -1 if _pattern has no match |
| /// for "method". |
| final int _method; |
| |
| /// The group index of "receiver" in [_pattern], or -1 if _pattern has no |
| /// match for "receiver". |
| final int _receiver; |
| |
| /// Pattern used to recognize a NoSuchMethodError error (and |
| /// possibly extract the method name). |
| static final TypeErrorDecoder noSuchMethodPattern = |
| extractPattern(provokeCallErrorOn(buildJavaScriptObject())); |
| |
| /// Pattern used to recognize an "object not a closure" error (and |
| /// possibly extract the method name). |
| static final TypeErrorDecoder notClosurePattern = |
| extractPattern(provokeCallErrorOn(buildJavaScriptObjectWithNonClosure())); |
| |
| /// Pattern used to recognize a NoSuchMethodError on JavaScript null |
| /// call. |
| static final TypeErrorDecoder nullCallPattern = |
| extractPattern(provokeCallErrorOn(JS('', 'null'))); |
| |
| /// Pattern used to recognize a NoSuchMethodError on JavaScript literal null |
| /// call. |
| static final TypeErrorDecoder nullLiteralCallPattern = |
| extractPattern(provokeCallErrorOnNull()); |
| |
| /// Pattern used to recognize a NoSuchMethodError on JavaScript |
| /// undefined call. |
| static final TypeErrorDecoder undefinedCallPattern = |
| extractPattern(provokeCallErrorOn(JS('', 'void 0'))); |
| |
| /// Pattern used to recognize a NoSuchMethodError on JavaScript literal |
| /// undefined call. |
| static final TypeErrorDecoder undefinedLiteralCallPattern = |
| extractPattern(provokeCallErrorOnUndefined()); |
| |
| /// Pattern used to recognize a NoSuchMethodError on JavaScript null |
| /// property access. |
| static final TypeErrorDecoder nullPropertyPattern = |
| extractPattern(provokePropertyErrorOn(JS('', 'null'))); |
| |
| /// Pattern used to recognize a NoSuchMethodError on JavaScript literal null |
| /// property access. |
| static final TypeErrorDecoder nullLiteralPropertyPattern = |
| extractPattern(provokePropertyErrorOnNull()); |
| |
| /// Pattern used to recognize a NoSuchMethodError on JavaScript |
| /// undefined property access. |
| static final TypeErrorDecoder undefinedPropertyPattern = |
| extractPattern(provokePropertyErrorOn(JS('', 'void 0'))); |
| |
| /// Pattern used to recognize a NoSuchMethodError on JavaScript literal |
| /// undefined property access. |
| static final TypeErrorDecoder undefinedLiteralPropertyPattern = |
| extractPattern(provokePropertyErrorOnUndefined()); |
| |
| TypeErrorDecoder(this._arguments, |
| this._argumentsExpr, |
| this._expr, |
| this._method, |
| this._receiver, |
| this._pattern); |
| |
| /// Returns a JavaScript object literal (map) with at most the |
| /// following keys: |
| /// |
| /// * arguments: The arguments as formatted by the JavaScript |
| /// engine. No browsers are known to provide this information. |
| /// |
| /// * argumentsExpr: The syntax of the arguments (JavaScript source |
| /// code). No browsers are known to provide this information. |
| /// |
| /// * expr: The syntax of the receiver expression (JavaScript source |
| /// code). Firefox provides this information, for example: "$expr$.$method$ |
| /// is not a function". |
| /// |
| /// * method: The name of the called method (mangled name). At least Firefox |
| /// and Chrome/V8 provides this information, for example, "Object [object |
| /// Object] has no method '$method$'". |
| /// |
| /// * receiver: The string representation of the receiver. Chrome/V8 |
| /// used to provide this information (by calling user-defined |
| /// JavaScript toString on receiver), but it has degenerated into |
| /// "[object Object]" in recent versions. |
| matchTypeError(message) { |
| var match = JS('JSExtendableArray|Null', |
| 'new RegExp(#).exec(#)', _pattern, message); |
| if (match == null) return null; |
| var result = JS('', 'Object.create(null)'); |
| if (_arguments != -1) { |
| JS('', '#.arguments = #[# + 1]', result, match, _arguments); |
| } |
| if (_argumentsExpr != -1) { |
| JS('', '#.argumentsExpr = #[# + 1]', result, match, _argumentsExpr); |
| } |
| if (_expr != -1) { |
| JS('', '#.expr = #[# + 1]', result, match, _expr); |
| } |
| if (_method != -1) { |
| JS('', '#.method = #[# + 1]', result, match, _method); |
| } |
| if (_receiver != -1) { |
| JS('', '#.receiver = #[# + 1]', result, match, _receiver); |
| } |
| |
| return result; |
| } |
| |
| /// Builds a JavaScript Object with a toString method saying |
| /// r"$receiver$". |
| static buildJavaScriptObject() { |
| return JS('', r'{ toString: function() { return "$receiver$"; } }'); |
| } |
| |
| /// Builds a JavaScript Object with a toString method saying |
| /// r"$receiver$". The property "$method" is defined, but is not a function. |
| static buildJavaScriptObjectWithNonClosure() { |
| return JS('', r'{ $method$: null, ' |
| r'toString: function() { return "$receiver$"; } }'); |
| } |
| |
| /// Extract a pattern from a JavaScript TypeError message. |
| /// |
| /// The patterns are extracted by forcing TypeErrors on known |
| /// objects thus forcing known strings into the error message. The |
| /// known strings are then replaced with wildcards which in theory |
| /// makes it possible to recognize the desired information even if |
| /// the error messages are reworded or translated. |
| static extractPattern(String message) { |
| // Some JavaScript implementations (V8 at least) include a |
| // representation of the receiver in the error message, however, |
| // this representation is not always [: receiver.toString() :], |
| // sometimes it is [: Object.prototype.toString(receiver) :], and |
| // sometimes it is an implementation specific method (but that |
| // doesn't seem to happen for object literals). So sometimes we |
| // get the text "[object Object]". The shortest way to get that |
| // string is using "String({})". |
| // See: http://code.google.com/p/v8/issues/detail?id=2519. |
| message = JS('String', r"#.replace(String({}), '$receiver$')", message); |
| |
| // Since we want to create a new regular expression from an unknown string, |
| // we must escape all regular expression syntax. |
| message = JS('String', r"#.replace(new RegExp(#, 'g'), '\\$&')", |
| message, ESCAPE_REGEXP); |
| |
| // Look for the special pattern \$camelCase\$ (all the $ symbols |
| // have been escaped already), as we will soon be inserting |
| // regular expression syntax that we want interpreted by RegExp. |
| List<String> match = |
| JS('JSExtendableArray|Null', r"#.match(/\\\$[a-zA-Z]+\\\$/g)", message); |
| if (match == null) match = []; |
| |
| // Find the positions within the substring matches of the error message |
| // components. This will help us extract information later, such as the |
| // method name. |
| int arguments = JS('int', '#.indexOf(#)', match, r'\$arguments\$'); |
| int argumentsExpr = JS('int', '#.indexOf(#)', match, r'\$argumentsExpr\$'); |
| int expr = JS('int', '#.indexOf(#)', match, r'\$expr\$'); |
| int method = JS('int', '#.indexOf(#)', match, r'\$method\$'); |
| int receiver = JS('int', '#.indexOf(#)', match, r'\$receiver\$'); |
| |
| // Replace the patterns with a regular expression wildcard. |
| // Note: in a perfect world, one would use "(.*)", but not in |
| // JavaScript, "." does not match newlines. |
| String pattern = JS('String', |
| r"#.replace('\\$arguments\\$', '((?:x|[^x])*)')" |
| r".replace('\\$argumentsExpr\\$', '((?:x|[^x])*)')" |
| r".replace('\\$expr\\$', '((?:x|[^x])*)')" |
| r".replace('\\$method\\$', '((?:x|[^x])*)')" |
| r".replace('\\$receiver\\$', '((?:x|[^x])*)')", |
| message); |
| |
| return new TypeErrorDecoder(arguments, |
| argumentsExpr, |
| expr, |
| method, |
| receiver, |
| pattern); |
| } |
| |
| /// Provokes a TypeError and returns its message. |
| /// |
| /// The error is provoked so all known variable content can be recognized and |
| /// a pattern can be inferred. |
| static String provokeCallErrorOn(expression) { |
| // This function is carefully created to maximize the possibility |
| // of decoding the TypeError message and turning it into a general |
| // pattern. |
| // |
| // The idea is to inject something known into something unknown. The |
| // unknown entity is the error message that the browser provides with a |
| // TypeError. It is a human readable message, possibly localized in a |
| // language no dart2js engineer understand. We assume that $name$ would |
| // never naturally occur in a human readable error message, yet it is easy |
| // to decode. |
| // |
| // For example, evaluate this in V8 version 3.13.7.6: |
| // |
| // var $expr$ = null; $expr$.$method$() |
| // |
| // The VM throws an instance of TypeError whose message property contains |
| // "Cannot call method '$method$' of null". We can then reasonably assume |
| // that if the string contains $method$, that's where the method name will |
| // be in general. Call this automatically reverse engineering the error |
| // format string in V8. |
| // |
| // So the error message from V8 is turned into this regular expression: |
| // |
| // "Cannot call method '(.*)' of null" |
| // |
| // Similarly, if we evaluate: |
| // |
| // var $expr$ = {toString: function() { return '$receiver$'; }}; |
| // $expr$.$method$() |
| // |
| // We get this message: "Object $receiver$ has no method '$method$'" |
| // |
| // Which is turned into this regular expression: |
| // |
| // "Object (.*) has no method '(.*)'" |
| // |
| // Firefox/jsshell is slightly different, it tries to include the source |
| // code that caused the exception, so we get this message: "$expr$.$method$ |
| // is not a function" which is turned into this regular expression: |
| // |
| // "(.*)\\.(.*) is not a function" |
| |
| var function = JS('', r"""function($expr$) { |
| var $argumentsExpr$ = '$arguments$'; |
| try { |
| $expr$.$method$($argumentsExpr$); |
| } catch (e) { |
| return e.message; |
| } |
| }"""); |
| return JS('String', '(#)(#)', function, expression); |
| } |
| |
| /// Similar to [provokeCallErrorOn], but provokes an error directly on |
| /// literal "null" expression. |
| static String provokeCallErrorOnNull() { |
| // See [provokeCallErrorOn] for a detailed explanation. |
| var function = JS('', r"""function() { |
| var $argumentsExpr$ = '$arguments$'; |
| try { |
| null.$method$($argumentsExpr$); |
| } catch (e) { |
| return e.message; |
| } |
| }"""); |
| return JS('String', '(#)()', function); |
| } |
| |
| /// Similar to [provokeCallErrorOnNull], but provokes an error directly on |
| /// (void 0), that is, "undefined". |
| static String provokeCallErrorOnUndefined() { |
| // See [provokeCallErrorOn] for a detailed explanation. |
| var function = JS('', r"""function() { |
| var $argumentsExpr$ = '$arguments$'; |
| try { |
| (void 0).$method$($argumentsExpr$); |
| } catch (e) { |
| return e.message; |
| } |
| }"""); |
| return JS('String', '(#)()', function); |
| } |
| |
| /// Similar to [provokeCallErrorOn], but provokes a property access |
| /// error. |
| static String provokePropertyErrorOn(expression) { |
| // See [provokeCallErrorOn] for a detailed explanation. |
| var function = JS('', r"""function($expr$) { |
| try { |
| $expr$.$method$; |
| } catch (e) { |
| return e.message; |
| } |
| }"""); |
| return JS('String', '(#)(#)', function, expression); |
| } |
| |
| /// Similar to [provokePropertyErrorOn], but provokes an property access |
| /// error directly on literal "null" expression. |
| static String provokePropertyErrorOnNull() { |
| // See [provokeCallErrorOn] for a detailed explanation. |
| var function = JS('', r"""function() { |
| try { |
| null.$method$; |
| } catch (e) { |
| return e.message; |
| } |
| }"""); |
| return JS('String', '(#)()', function); |
| } |
| |
| /// Similar to [provokePropertyErrorOnNull], but provokes an property access |
| /// error directly on (void 0), that is, "undefined". |
| static String provokePropertyErrorOnUndefined() { |
| // See [provokeCallErrorOn] for a detailed explanation. |
| var function = JS('', r"""function() { |
| try { |
| (void 0).$method$; |
| } catch (e) { |
| return e.message; |
| } |
| }"""); |
| return JS('String', '(#)()', function); |
| } |
| } |
| |
| class NullError extends Error implements NoSuchMethodError { |
| final String _message; |
| final String _method; |
| |
| NullError(this._message, match) |
| : _method = match == null ? null : JS('', '#.method', match); |
| |
| String toString() { |
| if (_method == null) return 'NullError: $_message'; |
| return 'NullError: Cannot call "$_method" on null'; |
| } |
| } |
| |
| class JsNoSuchMethodError extends Error implements NoSuchMethodError { |
| final String _message; |
| final String _method; |
| final String _receiver; |
| |
| JsNoSuchMethodError(this._message, match) |
| : _method = match == null ? null : JS('String|Null', '#.method', match), |
| _receiver = |
| match == null ? null : JS('String|Null', '#.receiver', match); |
| |
| String toString() { |
| if (_method == null) return 'NoSuchMethodError: $_message'; |
| if (_receiver == null) { |
| return 'NoSuchMethodError: Cannot call "$_method" ($_message)'; |
| } |
| return 'NoSuchMethodError: Cannot call "$_method" on "$_receiver" ' |
| '($_message)'; |
| } |
| } |
| |
| class UnknownJsTypeError extends Error { |
| final String _message; |
| |
| UnknownJsTypeError(this._message); |
| |
| String toString() => _message.isEmpty ? 'Error' : 'Error: $_message'; |
| } |
| |
| /** |
| * Called from catch blocks in generated code to extract the Dart |
| * exception from the thrown value. The thrown value may have been |
| * created by [wrapException] or it may be a 'native' JS exception. |
| * |
| * Some native exceptions are mapped to new Dart instances, others are |
| * returned unmodified. |
| */ |
| unwrapException(ex) { |
| /// If error implements Error, save [ex] in [error.$thrownJsError]. |
| /// Otherwise, do nothing. Later, the stack trace can then be extraced from |
| /// [ex]. |
| saveStackTrace(error) { |
| if (error is Error) { |
| var thrownStackTrace = JS('', r'#.$thrownJsError', error); |
| if (thrownStackTrace == null) { |
| JS('void', r'#.$thrownJsError = #', error, ex); |
| } |
| } |
| return error; |
| } |
| |
| // Note that we are checking if the object has the property. If it |
| // has, it could be set to null if the thrown value is null. |
| if (ex == null) return null; |
| if (JS('bool', 'typeof # !== "object"', ex)) return ex; |
| |
| if (JS('bool', r'"dartException" in #', ex)) { |
| return saveStackTrace(JS('', r'#.dartException', ex)); |
| } else if (!JS('bool', r'"message" in #', ex)) { |
| return ex; |
| } |
| |
| // Grab hold of the exception message. This field is available on |
| // all supported browsers. |
| var message = JS('var', r'#.message', ex); |
| |
| // Internet Explorer has an error number. This is the most reliable way to |
| // detect specific errors, so check for this first. |
| if (JS('bool', '"number" in #', ex) |
| && JS('bool', 'typeof #.number == "number"', ex)) { |
| int number = JS('int', '#.number', ex); |
| |
| // From http://msdn.microsoft.com/en-us/library/ie/hc53e755(v=vs.94).aspx |
| // "number" is a 32-bit word. The error code is the low 16 bits, and the |
| // facility code is the upper 16 bits. |
| var ieErrorCode = number & 0xffff; |
| var ieFacilityNumber = (number >> 16) & 0x1fff; |
| |
| // http://msdn.microsoft.com/en-us/library/aa264975(v=vs.60).aspx |
| // http://msdn.microsoft.com/en-us/library/ie/1dk3k160(v=vs.94).aspx |
| if (ieFacilityNumber == 10) { |
| switch (ieErrorCode) { |
| case 438: |
| return saveStackTrace( |
| new JsNoSuchMethodError('$message (Error $ieErrorCode)', null)); |
| case 445: |
| case 5007: |
| return saveStackTrace( |
| new NullError('$message (Error $ieErrorCode)', null)); |
| } |
| } |
| } |
| |
| if (JS('bool', r'# instanceof TypeError', ex)) { |
| var match; |
| // Using JS to give type hints to the compiler to help tree-shaking. |
| // TODO(ahe): That should be unnecessary due to type inference. |
| var nsme = |
| JS('TypeErrorDecoder', '#', TypeErrorDecoder.noSuchMethodPattern); |
| var notClosure = |
| JS('TypeErrorDecoder', '#', TypeErrorDecoder.notClosurePattern); |
| var nullCall = |
| JS('TypeErrorDecoder', '#', TypeErrorDecoder.nullCallPattern); |
| var nullLiteralCall = |
| JS('TypeErrorDecoder', '#', TypeErrorDecoder.nullLiteralCallPattern); |
| var undefCall = |
| JS('TypeErrorDecoder', '#', TypeErrorDecoder.undefinedCallPattern); |
| var undefLiteralCall = |
| JS('TypeErrorDecoder', '#', |
| TypeErrorDecoder.undefinedLiteralCallPattern); |
| var nullProperty = |
| JS('TypeErrorDecoder', '#', TypeErrorDecoder.nullPropertyPattern); |
| var nullLiteralProperty = |
| JS('TypeErrorDecoder', '#', |
| TypeErrorDecoder.nullLiteralPropertyPattern); |
| var undefProperty = |
| JS('TypeErrorDecoder', '#', TypeErrorDecoder.undefinedPropertyPattern); |
| var undefLiteralProperty = |
| JS('TypeErrorDecoder', '#', |
| TypeErrorDecoder.undefinedLiteralPropertyPattern); |
| if ((match = nsme.matchTypeError(message)) != null) { |
| return saveStackTrace(new JsNoSuchMethodError(message, match)); |
| } else if ((match = notClosure.matchTypeError(message)) != null) { |
| // notClosure may match "({c:null}).c()" or "({c:1}).c()", so we |
| // cannot tell if this an attempt to invoke call on null or a |
| // non-function object. |
| // But we do know the method name is "call". |
| JS('', '#.method = "call"', match); |
| return saveStackTrace(new JsNoSuchMethodError(message, match)); |
| } else if ((match = nullCall.matchTypeError(message)) != null || |
| (match = nullLiteralCall.matchTypeError(message)) != null || |
| (match = undefCall.matchTypeError(message)) != null || |
| (match = undefLiteralCall.matchTypeError(message)) != null || |
| (match = nullProperty.matchTypeError(message)) != null || |
| (match = nullLiteralCall.matchTypeError(message)) != null || |
| (match = undefProperty.matchTypeError(message)) != null || |
| (match = undefLiteralProperty.matchTypeError(message)) != null) { |
| return saveStackTrace(new NullError(message, match)); |
| } |
| |
| // If we cannot determine what kind of error this is, we fall back |
| // to reporting this as a generic error. It's probably better than |
| // nothing. |
| return saveStackTrace( |
| new UnknownJsTypeError(message is String ? message : '')); |
| } |
| |
| if (JS('bool', r'# instanceof RangeError', ex)) { |
| if (message is String && contains(message, 'call stack')) { |
| return new StackOverflowError(); |
| } |
| |
| // In general, a RangeError is thrown when trying to pass a number |
| // as an argument to a function that does not allow a range that |
| // includes that number. |
| return saveStackTrace(new ArgumentError()); |
| } |
| |
| // Check for the Firefox specific stack overflow signal. |
| if (JS('bool', |
| r'typeof InternalError == "function" && # instanceof InternalError', |
| ex)) { |
| if (message is String && message == 'too much recursion') { |
| return new StackOverflowError(); |
| } |
| } |
| |
| // Just return the exception. We should not wrap it because in case |
| // the exception comes from the DOM, it is a JavaScript |
| // object backed by a native Dart class. |
| return ex; |
| } |
| |
| /** |
| * Called by generated code to fetch the stack trace from an |
| * exception. Should never return null. |
| */ |
| StackTrace getTraceFromException(exception) => new _StackTrace(exception); |
| |
| class _StackTrace implements StackTrace { |
| var _exception; |
| String _trace; |
| _StackTrace(this._exception); |
| |
| String toString() { |
| if (_trace != null) return JS('String', '#', _trace); |
| |
| String trace; |
| if (JS('bool', 'typeof # === "object"', _exception)) { |
| trace = JS("String|Null", r"#.stack", _exception); |
| } |
| return _trace = (trace == null) ? '' : trace; |
| } |
| } |
| |
| int objectHashCode(var object) { |
| if (object == null || JS('bool', "typeof # != 'object'", object)) { |
| return object.hashCode; |
| } else { |
| return Primitives.objectHashCode(object); |
| } |
| } |
| |
| /** |
| * Called by generated code to build a map literal. [keyValuePairs] is |
| * a list of key, value, key, value, ..., etc. |
| */ |
| fillLiteralMap(keyValuePairs, Map result) { |
| // TODO(johnniwinther): Use JSArray to optimize this code instead of calling |
| // [getLength] and [getIndex]. |
| int index = 0; |
| int length = getLength(keyValuePairs); |
| while (index < length) { |
| var key = getIndex(keyValuePairs, index++); |
| var value = getIndex(keyValuePairs, index++); |
| result[key] = value; |
| } |
| return result; |
| } |
| |
| invokeClosure(Function closure, |
| var isolate, |
| int numberOfArguments, |
| var arg1, |
| var arg2, |
| var arg3, |
| var arg4) { |
| if (numberOfArguments == 0) { |
| return JS_CALL_IN_ISOLATE(isolate, () => closure()); |
| } else if (numberOfArguments == 1) { |
| return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1)); |
| } else if (numberOfArguments == 2) { |
| return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1, arg2)); |
| } else if (numberOfArguments == 3) { |
| return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1, arg2, arg3)); |
| } else if (numberOfArguments == 4) { |
| return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1, arg2, arg3, arg4)); |
| } else { |
| throw new Exception( |
| 'Unsupported number of arguments for wrapped closure'); |
| } |
| } |
| |
| /** |
| * Called by generated code to convert a Dart closure to a JS |
| * closure when the Dart closure is passed to the DOM. |
| */ |
| convertDartClosureToJS(closure, int arity) { |
| if (closure == null) return null; |
| var function = JS('var', r'#.$identity', closure); |
| if (JS('bool', r'!!#', function)) return function; |
| |
| // We use $0 and $1 to not clash with variable names used by the |
| // compiler and/or minifier. |
| function = JS('var', |
| '(function(closure, arity, context, invoke) {' |
| ' return function(a1, a2, a3, a4) {' |
| ' return invoke(closure, context, arity, a1, a2, a3, a4);' |
| ' };' |
| '})(#,#,#,#)', |
| closure, |
| arity, |
| // Capture the current isolate now. Remember that "#" |
| // in JS is simply textual substitution of compiled |
| // expressions. |
| JS_CURRENT_ISOLATE_CONTEXT(), |
| DART_CLOSURE_TO_JS(invokeClosure)); |
| |
| JS('void', r'#.$identity = #', closure, function); |
| return function; |
| } |
| |
| /** |
| * Super class for Dart closures. |
| */ |
| abstract class Closure implements Function { |
| // TODO(ahe): These constants must be in sync with |
| // reflection_data_parser.dart. |
| static const FUNCTION_INDEX = 0; |
| static const NAME_INDEX = 1; |
| static const CALL_NAME_INDEX = 2; |
| static const REQUIRED_PARAMETER_INDEX = 3; |
| static const OPTIONAL_PARAMETER_INDEX = 4; |
| static const DEFAULT_ARGUMENTS_INDEX = 5; |
| |
| /** |
| * Global counter to prevent reusing function code objects. |
| * |
| * V8 will share the underlying function code objects when the same string is |
| * passed to "new Function". Shared function code objects can lead to |
| * sub-optimal performance due to polymorhism, and can be prevented by |
| * ensuring the strings are different. |
| */ |
| static int functionCounter = 0; |
| |
| Closure(); |
| |
| /** |
| * Creates a new closure class for use by implicit getters associated with a |
| * method. |
| * |
| * In other words, creates a tear-off closure. |
| * |
| * Called from [closureFromTearOff] as well as from reflection when tearing |
| * of a method via [:getField:]. |
| * |
| * This method assumes that [functions] was created by the JavaScript function |
| * `addStubs` in `reflection_data_parser.dart`. That is, a list of JavaScript |
| * function objects with properties `$stubName` and `$callName`. |
| * |
| * Further assumes that [reflectionInfo] is the end of the array created by |
| * [dart2js.js_emitter.ContainerBuilder.addMemberMethod] starting with |
| * required parameter count or, in case of the new emitter, the runtime |
| * representation of the function's type. |
| * |
| * Caution: this function may be called when building constants. |
| * TODO(ahe): Don't call this function when building constants. |
| */ |
| static fromTearOff(receiver, |
| List functions, |
| var reflectionInfo, |
| bool isStatic, |
| jsArguments, |
| String propertyName) { |
| JS_EFFECT(() { |
| BoundClosure.receiverOf(JS('BoundClosure', 'void 0')); |
| BoundClosure.selfOf(JS('BoundClosure', 'void 0')); |
| }); |
| // TODO(ahe): All the place below using \$ should be rewritten to go |
| // through the namer. |
| var function = JS('', '#[#]', functions, 0); |
| String name = JS('String|Null', '#.\$stubName', function); |
| String callName = JS('String|Null', '#.\$callName', function); |
| |
| var functionType; |
| if (reflectionInfo is List) { |
| JS('', '#.\$reflectionInfo = #', function, reflectionInfo); |
| ReflectionInfo info = new ReflectionInfo(function); |
| functionType = info.functionType; |
| } else { |
| functionType = reflectionInfo; |
| } |
| |
| |
| // function tmp() {}; |
| // tmp.prototype = BC.prototype; |
| // var proto = new tmp; |
| // for each computed prototype property: |
| // proto[property] = ...; |
| // proto._init = BC; |
| // var dynClosureConstructor = |
| // new Function('self', 'target', 'receiver', 'name', |
| // 'this._init(self, target, receiver, name)'); |
| // proto.constructor = dynClosureConstructor; |
| // dynClosureConstructor.prototype = proto; |
| // return dynClosureConstructor; |
| |
| // We need to create a new subclass of either TearOffClosure or |
| // BoundClosure. For this, we need to create an object whose prototype is |
| // the prototype is either TearOffClosure.prototype or |
| // BoundClosure.prototype, respectively in pseudo JavaScript code. The |
| // simplest way to access the JavaScript construction function of a Dart |
| // class is to create an instance and access its constructor property. The |
| // newly created instance could in theory be used directly as the |
| // prototype, but it might include additional fields that we don't need. |
| // So we only use the new instance to access the constructor property and |
| // use Object.create to create the desired prototype. |
| var prototype = isStatic |
| ? JS('TearOffClosure', 'Object.create(#.constructor.prototype)', |
| new TearOffClosure()) |
| : JS('BoundClosure', 'Object.create(#.constructor.prototype)', |
| new BoundClosure(null, null, null, null)); |
| |
| JS('', '#.\$initialize = #', prototype, JS('', '#.constructor', prototype)); |
| var constructor = isStatic |
| ? JS('', 'function(){this.\$initialize()}') |
| : isCsp |
| ? JS('', 'function(a,b,c,d) {this.\$initialize(a,b,c,d)}') |
| : JS('', |
| 'new Function("a","b","c","d",' |
| '"this.\$initialize(a,b,c,d);"+#)', |
| functionCounter++); |
| |
| // It is necessary to set the constructor property, otherwise it will be |
| // "Object". |
| JS('', '#.constructor = #', prototype, constructor); |
| |
| JS('', '#.prototype = #', constructor, prototype); |
| |
| // Create a closure and "monkey" patch it with call stubs. |
| var trampoline = function; |
| var isIntercepted = false; |
| if (!isStatic) { |
| if (JS('bool', '#.length == 1', jsArguments)) { |
| // Intercepted call. |
| isIntercepted = true; |
| } |
| trampoline = forwardCallTo(receiver, function, isIntercepted); |
| JS('', '#.\$reflectionInfo = #', trampoline, reflectionInfo); |
| } else { |
| JS('', '#.\$name = #', prototype, propertyName); |
| } |
| |
| var signatureFunction; |
| if (JS('bool', 'typeof # == "number"', functionType)) { |
| var metadata = JS_EMBEDDED_GLOBAL('', METADATA); |
| // It is ok, if the access is inlined into the JS. The access is safe in |
| // and outside the function. In fact we prefer if there is a textual |
| // inlining. |
| signatureFunction = |
| JS('', '(function(s){return function(){return #[s]}})(#)', |
| metadata, |
| functionType); |
| } else if (!isStatic |
| && JS('bool', 'typeof # == "function"', functionType)) { |
| var getReceiver = isIntercepted |
| ? RAW_DART_FUNCTION_REF(BoundClosure.receiverOf) |
| : RAW_DART_FUNCTION_REF(BoundClosure.selfOf); |
| signatureFunction = JS( |
| '', |
| 'function(f,r){' |
| 'return function(){' |
| 'return f.apply({\$receiver:r(this)},arguments)' |
| '}' |
| '}(#,#)', functionType, getReceiver); |
| } else { |
| throw 'Error in reflectionInfo.'; |
| } |
| |
| JS('', '#[#] = #', prototype, JS_SIGNATURE_NAME(), signatureFunction); |
| |
| JS('', '#[#] = #', prototype, callName, trampoline); |
| for (int i = 1; i < functions.length; i++) { |
| var stub = functions[i]; |
| var stubCallName = JS('String|Null', '#.\$callName', stub); |
| if (stubCallName != null) { |
| JS('', '#[#] = #', prototype, stubCallName, |
| isStatic ? stub : forwardCallTo(receiver, stub, isIntercepted)); |
| } |
| } |
| |
| JS('', '#["call*"] = #', prototype, trampoline); |
| |
| return constructor; |
| } |
| |
| static cspForwardCall(int arity, bool isSuperCall, String stubName, |
| function) { |
| var getSelf = RAW_DART_FUNCTION_REF(BoundClosure.selfOf); |
| // Handle intercepted stub-names with the default slow case. |
| if (isSuperCall) arity = -1; |
| switch (arity) { |
| case 0: |
| return JS( |
| '', |
| 'function(n,S){' |
| 'return function(){' |
| 'return S(this)[n]()' |
| '}' |
| '}(#,#)', stubName, getSelf); |
| case 1: |
| return JS( |
| '', |
| 'function(n,S){' |
| 'return function(a){' |
| 'return S(this)[n](a)' |
| '}' |
| '}(#,#)', stubName, getSelf); |
| case 2: |
| return JS( |
| '', |
| 'function(n,S){' |
| 'return function(a,b){' |
| 'return S(this)[n](a,b)' |
| '}' |
| '}(#,#)', stubName, getSelf); |
| case 3: |
| return JS( |
| '', |
| 'function(n,S){' |
| 'return function(a,b,c){' |
| 'return S(this)[n](a,b,c)' |
| '}' |
| '}(#,#)', stubName, getSelf); |
| case 4: |
| return JS( |
| '', |
| 'function(n,S){' |
| 'return function(a,b,c,d){' |
| 'return S(this)[n](a,b,c,d)' |
| '}' |
| '}(#,#)', stubName, getSelf); |
| case 5: |
| return JS( |
| '', |
| 'function(n,S){' |
| 'return function(a,b,c,d,e){' |
| 'return S(this)[n](a,b,c,d,e)' |
| '}' |
| '}(#,#)', stubName, getSelf); |
| default: |
| return JS( |
| '', |
| 'function(f,s){' |
| 'return function(){' |
| 'return f.apply(s(this),arguments)' |
| '}' |
| '}(#,#)', function, getSelf); |
| } |
| } |
| |
| static bool get isCsp => JS('bool', 'typeof dart_precompiled == "function"'); |
| |
| static forwardCallTo(receiver, function, bool isIntercepted) { |
| if (isIntercepted) return forwardInterceptedCallTo(receiver, function); |
| String stubName = JS('String|Null', '#.\$stubName', function); |
| int arity = JS('int', '#.length', function); |
| var lookedUpFunction = JS("", "#[#]", receiver, stubName); |
| // The receiver[stubName] may not be equal to the function if we try to |
| // forward to a super-method. Especially when we create a bound closure |
| // of a super-call we need to make sure that we don't forward back to the |
| // dynamically looked up function. |
| bool isSuperCall = !identical(function, lookedUpFunction); |
| |
| if (isCsp || isSuperCall || arity >= 27) { |
| return cspForwardCall(arity, isSuperCall, stubName, function); |
| } |
| |
| if (arity == 0) { |
| return JS( |
| '', |
| '(new Function(#))()', |
| 'return function(){' |
| 'return this.${BoundClosure.selfFieldName()}.$stubName();' |
| '${functionCounter++}' |
| '}'); |
| } |
| assert (1 <= arity && arity < 27); |
| String arguments = JS( |
| 'String', |
| '"abcdefghijklmnopqrstuvwxyz".split("").splice(0,#).join(",")', |
| arity); |
| return JS( |
| '', |
| '(new Function(#))()', |
| 'return function($arguments){' |
| 'return this.${BoundClosure.selfFieldName()}.$stubName($arguments);' |
| '${functionCounter++}' |
| '}'); |
| } |
| |
| static cspForwardInterceptedCall(int arity, bool isSuperCall, |
| String name, function) { |
| var getSelf = RAW_DART_FUNCTION_REF(BoundClosure.selfOf); |
| var getReceiver = RAW_DART_FUNCTION_REF(BoundClosure.receiverOf); |
| // Handle intercepted stub-names with the default slow case. |
| if (isSuperCall) arity = -1; |
| switch (arity) { |
| case 0: |
| // Intercepted functions always takes at least one argument (the |
| // receiver). |
| throw new RuntimeError('Intercepted function with no arguments.'); |
| case 1: |
| return JS( |
| '', |
| 'function(n,s,r){' |
| 'return function(){' |
| 'return s(this)[n](r(this))' |
| '}' |
| '}(#,#,#)', name, getSelf, getReceiver); |
| case 2: |
| return JS( |
| '', |
| 'function(n,s,r){' |
| 'return function(a){' |
| 'return s(this)[n](r(this),a)' |
| '}' |
| '}(#,#,#)', name, getSelf, getReceiver); |
| case 3: |
| return JS( |
| '', |
| 'function(n,s,r){' |
| 'return function(a,b){' |
| 'return s(this)[n](r(this),a,b)' |
| '}' |
| '}(#,#,#)', name, getSelf, getReceiver); |
| case 4: |
| return JS( |
| '', |
| 'function(n,s,r){' |
| 'return function(a,b,c){' |
| 'return s(this)[n](r(this),a,b,c)' |
| '}' |
| '}(#,#,#)', name, getSelf, getReceiver); |
| case 5: |
| return JS( |
| '', |
| 'function(n,s,r){' |
| 'return function(a,b,c,d){' |
| 'return s(this)[n](r(this),a,b,c,d)' |
| '}' |
| '}(#,#,#)', name, getSelf, getReceiver); |
| case 6: |
| return JS( |
| '', |
| 'function(n,s,r){' |
| 'return function(a,b,c,d,e){' |
| 'return s(this)[n](r(this),a,b,c,d,e)' |
| '}' |
| '}(#,#,#)', name, getSelf, getReceiver); |
| default: |
| return JS( |
| '', |
| 'function(f,s,r,a){' |
| 'return function(){' |
| 'a=[r(this)];' |
| 'Array.prototype.push.apply(a,arguments);' |
| 'return f.apply(s(this),a)' |
| '}' |
| '}(#,#,#)', function, getSelf, getReceiver); |
| } |
| } |
| |
| static forwardInterceptedCallTo(receiver, function) { |
| String selfField = BoundClosure.selfFieldName(); |
| String receiverField = BoundClosure.receiverFieldName(); |
| String stubName = JS('String|Null', '#.\$stubName', function); |
| int arity = JS('int', '#.length', function); |
| bool isCsp = JS('bool', 'typeof dart_precompiled == "function"'); |
| var lookedUpFunction = JS("", "#[#]", receiver, stubName); |
| // The receiver[stubName] may not be equal to the function if we try to |
| // forward to a super-method. Especially when we create a bound closure |
| // of a super-call we need to make sure that we don't forward back to the |
| // dynamically looked up function. |
| bool isSuperCall = !identical(function, lookedUpFunction); |
| |
| if (isCsp || isSuperCall || arity >= 28) { |
| return cspForwardInterceptedCall(arity, isSuperCall, stubName, |
| function); |
| } |
| if (arity == 1) { |
| return JS( |
| '', |
| '(new Function(#))()', |
| 'return function(){' |
| 'return this.$selfField.$stubName(this.$receiverField);' |
| '${functionCounter++}' |
| '}'); |
| } |
| assert(1 < arity && arity < 28); |
| String arguments = JS( |
| 'String', |
| '"abcdefghijklmnopqrstuvwxyz".split("").splice(0,#).join(",")', |
| arity - 1); |
| return JS( |
| '', |
| '(new Function(#))()', |
| 'return function($arguments){' |
| 'return this.$selfField.$stubName(this.$receiverField, $arguments);' |
| '${functionCounter++}' |
| '}'); |
| } |
| |
| // The backend adds a special getter of the form |
| // |
| // Closure get call => this; |
| // |
| // to allow tearing off a closure from itself. We do this magically in the |
| // backend rather than simply adding it here, as we do not want this getter |
| // to be visible to resolution and the generation of extra stubs. |
| |
| String toString() => "Closure"; |
| } |
| |
| /// Called from implicit method getter (aka tear-off). |
| closureFromTearOff(receiver, |
| functions, |
| reflectionInfo, |
| isStatic, |
| jsArguments, |
| name) { |
| return Closure.fromTearOff( |
| receiver, |
| JSArray.markFixedList(functions), |
| reflectionInfo is List ? JSArray.markFixedList(reflectionInfo) |
| : reflectionInfo, |
| JS('bool', '!!#', isStatic), |
| jsArguments, |
| JS('String', '#', name)); |
| } |
| |
| /// Represents an implicit closure of a function. |
| class TearOffClosure extends Closure { |
| } |
| |
| /// Represents a 'tear-off' closure, that is an instance method bound |
| /// to a specific receiver (instance). |
| class BoundClosure extends TearOffClosure { |
| /// The receiver or interceptor. |
| // TODO(ahe): This could just be the interceptor, we always know if |
| // we need the interceptor when generating the call method. |
| final _self; |
| |
| /// The method. |
| final _target; |
| |
| /// The receiver. Null if [_self] is not an interceptor. |
| final _receiver; |
| |
| /// The name of the function. Only used by the mirror system. |
| final String _name; |
| |
| BoundClosure(this._self, this._target, this._receiver, this._name); |
| |
| bool operator==(other) { |
| if (identical(this, other)) return true; |
| if (other is! BoundClosure) return false; |
| return JS('bool', '# === # && # === # && # === #', |
| _self, other._self, |
| _target, other._target, |
| _receiver, other._receiver); |
| } |
| |
| int get hashCode { |
| int receiverHashCode; |
| if (_receiver == null) { |
| // A bound closure on a regular Dart object, just use the |
| // identity hash code. |
| receiverHashCode = Primitives.objectHashCode(_self); |
| } else if (JS('String', 'typeof #', _receiver) != 'object') { |
| // A bound closure on a primitive JavaScript type. We |
| // use the hashCode method we define for those primitive types. |
| receiverHashCode = _receiver.hashCode; |
| } else { |
| // A bound closure on an intercepted native class, just use the |
| // identity hash code. |
| receiverHashCode = Primitives.objectHashCode(_receiver); |
| } |
| return receiverHashCode ^ Primitives.objectHashCode(_target); |
| } |
| |
| @NoInline() |
| static selfOf(BoundClosure closure) => closure._self; |
| |
| static targetOf(BoundClosure closure) => closure._target; |
| |
| @NoInline() |
| static receiverOf(BoundClosure closure) => closure._receiver; |
| |
| static nameOf(BoundClosure closure) => closure._name; |
| |
| static String selfFieldNameCache; |
| |
| static String selfFieldName() { |
| if (selfFieldNameCache == null) { |
| selfFieldNameCache = computeFieldNamed('self'); |
| } |
| return selfFieldNameCache; |
| } |
| |
| static String receiverFieldNameCache; |
| |
| static String receiverFieldName() { |
| if (receiverFieldNameCache == null) { |
| receiverFieldNameCache = computeFieldNamed('receiver'); |
| } |
| return receiverFieldNameCache; |
| } |
| |
| @NoInline() @NoSideEffects() |
| static String computeFieldNamed(String fieldName) { |
| var template = new BoundClosure('self', 'target', 'receiver', 'name'); |
| var names = JSArray.markFixedList( |
| JS('', 'Object.getOwnPropertyNames(#)', template)); |
| for (int i = 0; i < names.length; i++) { |
| var name = names[i]; |
| if (JS('bool', '#[#] === #', template, name, fieldName)) { |
| return JS('String', '#', name); |
| } |
| } |
| } |
| } |
| |
| bool jsHasOwnProperty(var jsObject, String property) { |
| return JS('bool', r'#.hasOwnProperty(#)', jsObject, property); |
| } |
| |
| jsPropertyAccess(var jsObject, String property) { |
| return JS('var', r'#[#]', jsObject, property); |
| } |
| |
| /** |
| * Called at the end of unaborted switch cases to get the singleton |
| * FallThroughError exception that will be thrown. |
| */ |
| getFallThroughError() => new FallThroughErrorImplementation(); |
| |
| /** |
| * A metadata annotation describing the types instantiated by a native element. |
| * |
| * The annotation is valid on a native method and a field of a native class. |
| * |
| * By default, a field of a native class is seen as an instantiation point for |
| * all native classes that are a subtype of the field's type, and a native |
| * method is seen as an instantiation point fo all native classes that are a |
| * subtype of the method's return type, or the argument types of the declared |
| * type of the method's callback parameter. |
| * |
| * An @[Creates] annotation overrides the default set of instantiated types. If |
| * one or more @[Creates] annotations are present, the type of the native |
| * element is ignored, and the union of @[Creates] annotations is used instead. |
| * The names in the strings are resolved and the program will fail to compile |
| * with dart2js if they do not name types. |
| * |
| * The argument to [Creates] is a string. The string is parsed as the names of |
| * one or more types, separated by vertical bars `|`. There are some special |
| * names: |
| * |
| * * `=Object`. This means 'exactly Object', which is a plain JavaScript object |
| * with properties and none of the subtypes of Object. |
| * |
| * Example: we may know that a method always returns a specific implementation: |
| * |
| * @Creates('_NodeList') |
| * List<Node> getElementsByTagName(String tag) native; |
| * |
| * Useful trick: A method can be marked as not instantiating any native classes |
| * with the annotation `@Creates('Null')`. This is useful for fields on native |
| * classes that are used only in Dart code. |
| * |
| * @Creates('Null') |
| * var _cachedFoo; |
| */ |
| class Creates { |
| final String types; |
| const Creates(this.types); |
| } |
| |
| /** |
| * A metadata annotation describing the types returned or yielded by a native |
| * element. |
| * |
| * The annotation is valid on a native method and a field of a native class. |
| * |
| * By default, a native method or field is seen as returning or yielding all |
| * subtypes if the method return type or field type. This annotation allows a |
| * more precise set of types to be specified. |
| * |
| * See [Creates] for the syntax of the argument. |
| * |
| * Example: IndexedDB keys are numbers, strings and JavaScript Arrays of keys. |
| * |
| * @Returns('String|num|JSExtendableArray') |
| * dynamic key; |
| * |
| * // Equivalent: |
| * @Returns('String') @Returns('num') @Returns('JSExtendableArray') |
| * dynamic key; |
| */ |
| class Returns { |
| final String types; |
| const Returns(this.types); |
| } |
| |
| /** |
| * A metadata annotation placed on native methods and fields of native classes |
| * to specify the JavaScript name. |
| * |
| * This example declares a Dart field + getter + setter called `$dom_title` that |
| * corresponds to the JavaScript property `title`. |
| * |
| * class Docmument native "*Foo" { |
| * @JSName('title') |
| * String $dom_title; |
| * } |
| */ |
| class JSName { |
| final String name; |
| const JSName(this.name); |
| } |
| |
| /** |
| * The following methods are called by the runtime to implement |
| * checked mode and casts. We specialize each primitive type (eg int, bool), and |
| * use the compiler's convention to do is-checks on regular objects. |
| */ |
| boolConversionCheck(value) { |
| if (value is bool) return value; |
| // One of the following checks will always fail. |
| boolTypeCheck(value); |
| assert(value != null); |
| return false; |
| } |
| |
| stringTypeCheck(value) { |
| if (value == null) return value; |
| if (value is String) return value; |
| throw new TypeErrorImplementation(value, 'String'); |
| } |
| |
| stringTypeCast(value) { |
| if (value is String || value == null) return value; |
| // TODO(lrn): When reified types are available, pass value.class and String. |
| throw new CastErrorImplementation( |
| Primitives.objectTypeName(value), 'String'); |
| } |
| |
| doubleTypeCheck(value) { |
| if (value == null) return value; |
| if (value is double) return value; |
| throw new TypeErrorImplementation(value, 'double'); |
| } |
| |
| doubleTypeCast(value) { |
| if (value is double || value == null) return value; |
| throw new CastErrorImplementation( |
| Primitives.objectTypeName(value), 'double'); |
| } |
| |
| numTypeCheck(value) { |
| if (value == null) return value; |
| if (value is num) return value; |
| throw new TypeErrorImplementation(value, 'num'); |
| } |
| |
| numTypeCast(value) { |
| if (value is num || value == null) return value; |
| throw new CastErrorImplementation( |
| Primitives.objectTypeName(value), 'num'); |
| } |
| |
| boolTypeCheck(value) { |
| if (value == null) return value; |
| if (value is bool) return value; |
| throw new TypeErrorImplementation(value, 'bool'); |
| } |
| |
| boolTypeCast(value) { |
| if (value is bool || value == null) return value; |
| throw new CastErrorImplementation( |
| Primitives.objectTypeName(value), 'bool'); |
| } |
| |
| intTypeCheck(value) { |
| if (value == null) return value; |
| if (value is int) return value; |
| throw new TypeErrorImplementation(value, 'int'); |
| } |
| |
| intTypeCast(value) { |
| if (value is int || value == null) return value; |
| throw new CastErrorImplementation( |
| Primitives.objectTypeName(value), 'int'); |
| } |
| |
| void propertyTypeError(value, property) { |
| // Cuts the property name to the class name. |
| String name = property.substring(3, property.length); |
| throw new TypeErrorImplementation(value, name); |
| } |
| |
| void propertyTypeCastError(value, property) { |
| // Cuts the property name to the class name. |
| String actualType = Primitives.objectTypeName(value); |
| String expectedType = property.substring(3, property.length); |
| throw new CastErrorImplementation(actualType, expectedType); |
| } |
| |
| /** |
| * For types that are not supertypes of native (eg DOM) types, |
| * we emit a simple property check to check that an object implements |
| * that type. |
| */ |
| propertyTypeCheck(value, property) { |
| if (value == null) return value; |
| if (JS('bool', '!!#[#]', value, property)) return value; |
| propertyTypeError(value, property); |
| } |
| |
| /** |
| * For types that are not supertypes of native (eg DOM) types, |
| * we emit a simple property check to check that an object implements |
| * that type. |
| */ |
| propertyTypeCast(value, property) { |
| if (value == null || JS('bool', '!!#[#]', value, property)) return value; |
| propertyTypeCastError(value, property); |
| } |
| |
| /** |
| * For types that are supertypes of native (eg DOM) types, we use the |
| * interceptor for the class because we cannot add a JS property to the |
| * prototype at load time. |
| */ |
| interceptedTypeCheck(value, property) { |
| if (value == null) return value; |
| if ((identical(JS('String', 'typeof #', value), 'object')) |
| && JS('bool', '#[#]', getInterceptor(value), property)) { |
| return value; |
| } |
| propertyTypeError(value, property); |
| } |
| |
| /** |
| * For types that are supertypes of native (eg DOM) types, we use the |
| * interceptor for the class because we cannot add a JS property to the |
| * prototype at load time. |
| */ |
| interceptedTypeCast(value, property) { |
| if (value == null |
| || ((JS('bool', 'typeof # === "object"', value)) |
| && JS('bool', '#[#]', getInterceptor(value), property))) { |
| return value; |
| } |
| propertyTypeCastError(value, property); |
| } |
| |
| /** |
| * Specialization of the type check for num and String and their |
| * supertype since [value] can be a JS primitive. |
| */ |
| numberOrStringSuperTypeCheck(value, property) { |
| if (value == null) return value; |
| if (value is String) return value; |
| if (value is num) return value; |
| if (JS('bool', '!!#[#]', value, property)) return value; |
| propertyTypeError(value, property); |
| } |
| |
| numberOrStringSuperTypeCast(value, property) { |
| if (value is String) return value; |
| if (value is num) return value; |
| return propertyTypeCast(value, property); |
| } |
| |
| numberOrStringSuperNativeTypeCheck(value, property) { |
| if (value == null) return value; |
| if (value is String) return value; |
| if (value is num) return value; |
| if (JS('bool', '#[#]', getInterceptor(value), property)) return value; |
| propertyTypeError(value, property); |
| } |
| |
| numberOrStringSuperNativeTypeCast(value, property) { |
| if (value == null) return value; |
| if (value is String) return value; |
| if (value is num) return value; |
| if (JS('bool', '#[#]', getInterceptor(value), property)) return value; |
| propertyTypeCastError(value, property); |
| } |
| |
| /** |
| * Specialization of the type check for String and its supertype |
| * since [value] can be a JS primitive. |
| */ |
| stringSuperTypeCheck(value, property) { |
| if (value == null) return value; |
| if (value is String) return value; |
| if (JS('bool', '!!#[#]', value, property)) return value; |
| propertyTypeError(value, property); |
| } |
| |
| stringSuperTypeCast(value, property) { |
| if (value is String) return value; |
| return propertyTypeCast(value, property); |
| } |
| |
| stringSuperNativeTypeCheck(value, property) { |
| if (value == null) return value; |
| if (value is String) return value; |
| if (JS('bool', '#[#]', getInterceptor(value), property)) return value; |
| propertyTypeError(value, property); |
| } |
| |
| stringSuperNativeTypeCast(value, property) { |
| if (value is String || value == null) return value; |
| if (JS('bool', '#[#]', getInterceptor(value), property)) return value; |
| propertyTypeCastError(value, property); |
| } |
| |
| /** |
| * Specialization of the type check for List and its supertypes, |
| * since [value] can be a JS array. |
| */ |
| listTypeCheck(value) { |
| if (value == null) return value; |
| if (value is List) return value; |
| throw new TypeErrorImplementation(value, 'List'); |
| } |
| |
| listTypeCast(value) { |
| if (value is List || value == null) return value; |
| throw new CastErrorImplementation( |
| Primitives.objectTypeName(value), 'List'); |
| } |
| |
| listSuperTypeCheck(value, property) { |
| if (value == null) return value; |
| if (value is List) return value; |
| if (JS('bool', '!!#[#]', value, property)) return value; |
| propertyTypeError(value, property); |
| } |
| |
| listSuperTypeCast(value, property) { |
| if (value is List) return value; |
| return propertyTypeCast(value, property); |
| } |
| |
| listSuperNativeTypeCheck(value, property) { |
| if (value == null) return value; |
| if (value is List) return value; |
| if (JS('bool', '#[#]', getInterceptor(value), property)) return value; |
| propertyTypeError(value, property); |
| } |
| |
| listSuperNativeTypeCast(value, property) { |
| if (value is List || value == null) return value; |
| if (JS('bool', '#[#]', getInterceptor(value), property)) return value; |
| propertyTypeCastError(value, property); |
| } |
| |
| voidTypeCheck(value) { |
| if (value == null) return value; |
| throw new TypeErrorImplementation(value, 'void'); |
| } |
| |
| checkMalformedType(value, message) { |
| if (value == null) return value; |
| throw new TypeErrorImplementation.fromMessage(message); |
| } |
| |
| @NoInline() |
| void checkDeferredIsLoaded(String loadId, String uri) { |
| if (!_loadedLibraries.contains(loadId)) { |
| throw new DeferredNotLoadedError(uri); |
| } |
| } |
| |
| /** |
| * Special interface recognized by the compiler and implemented by DOM |
| * objects that support integer indexing. This interface is not |
| * visible to anyone, and is only injected into special libraries. |
| */ |
| abstract class JavaScriptIndexingBehavior extends JSMutableIndexable { |
| } |
| |
| // TODO(lrn): These exceptions should be implemented in core. |
| // When they are, remove the 'Implementation' here. |
| |
| /** Thrown by type assertions that fail. */ |
| class TypeErrorImplementation extends Error implements TypeError { |
| final String message; |
| |
| /** |
| * Normal type error caused by a failed subtype test. |
| */ |
| TypeErrorImplementation(Object value, String type) |
| : message = "type '${Primitives.objectTypeName(value)}' is not a subtype " |
| "of type '$type'"; |
| |
| TypeErrorImplementation.fromMessage(String this.message); |
| |
| String toString() => message; |
| } |
| |
| /** Thrown by the 'as' operator if the cast isn't valid. */ |
| class CastErrorImplementation extends Error implements CastError { |
| // TODO(lrn): Rename to CastError (and move implementation into core). |
| final String message; |
| |
| /** |
| * Normal cast error caused by a failed type cast. |
| */ |
| CastErrorImplementation(Object actualType, Object expectedType) |
| : message = "CastError: Casting value of type $actualType to" |
| " incompatible type $expectedType"; |
| |
| String toString() => message; |
| } |
| |
| class FallThroughErrorImplementation extends FallThroughError { |
| FallThroughErrorImplementation(); |
| String toString() => "Switch case fall-through."; |
| } |
| |
| /** |
| * Helper function for implementing asserts. The compiler treats this specially. |
| */ |
| void assertHelper(condition) { |
| // Do a bool check first because it is common and faster than 'is Function'. |
| if (condition is !bool) { |
| if (condition is Function) condition = condition(); |
| if (condition is !bool) { |
| throw new TypeErrorImplementation(condition, 'bool'); |
| } |
| } |
| // Compare to true to avoid boolean conversion check in checked |
| // mode. |
| if (true != condition) throw new AssertionError(); |
| } |
| |
| /** |
| * Called by generated code when a method that must be statically |
| * resolved cannot be found. |
| */ |
| void throwNoSuchMethod(obj, name, arguments, expectedArgumentNames) { |
| Symbol memberName = new _symbol_dev.Symbol.unvalidated(name); |
| throw new NoSuchMethodError(obj, memberName, arguments, |
| new Map<Symbol, dynamic>(), |
| expectedArgumentNames); |
| } |
| |
| /** |
| * Called by generated code when a static field's initializer references the |
| * field that is currently being initialized. |
| */ |
| void throwCyclicInit(String staticName) { |
| throw new CyclicInitializationError( |
| "Cyclic initialization for static $staticName"); |
| } |
| |
| /** |
| * Error thrown when a runtime error occurs. |
| */ |
| class RuntimeError extends Error { |
| final message; |
| RuntimeError(this.message); |
| String toString() => "RuntimeError: $message"; |
| } |
| |
| class DeferredNotLoadedError extends Error implements NoSuchMethodError { |
| String libraryName; |
| |
| DeferredNotLoadedError(this.libraryName); |
| |
| String toString() { |
| return "Deferred library $libraryName was not loaded."; |
| } |
| } |
| |
| abstract class RuntimeType { |
| const RuntimeType(); |
| |
| toRti(); |
| } |
| |
| class RuntimeFunctionType extends RuntimeType { |
| final RuntimeType returnType; |
| final List<RuntimeType> parameterTypes; |
| final List<RuntimeType> optionalParameterTypes; |
| final namedParameters; |
| |
| static var /* bool */ inAssert = false; |
| |
| RuntimeFunctionType(this.returnType, |
| this.parameterTypes, |
| this.optionalParameterTypes, |
| this.namedParameters); |
| |
| bool get isVoid => returnType is VoidRuntimeType; |
| |
| /// Called from generated code. [expression] is a Dart object and this method |
| /// returns true if [this] is a supertype of [expression]. |
| @NoInline() @NoSideEffects() |
| bool _isTest(expression) { |
| var functionTypeObject = _extractFunctionTypeObjectFrom(expression); |
| return functionTypeObject == null |
| ? false |
| : isFunctionSubtype(functionTypeObject, toRti()); |
| } |
| |
| @NoInline() @NoSideEffects() |
| _asCheck(expression) { |
| // Type inferrer doesn't think this is called with dynamic arguments. |
| return _check(JS('', '#', expression), true); |
| } |
| |
| @NoInline() @NoSideEffects() |
| _assertCheck(expression) { |
| if (inAssert) return null; |
| inAssert = true; // Don't try to check this library itself. |
| try { |
| // Type inferrer don't think this is called with dynamic arguments. |
| return _check(JS('', '#', expression), false); |
| } finally { |
| inAssert = false; |
| } |
| } |
| |
| _check(expression, bool isCast) { |
| if (expression == null) return null; |
| if (_isTest(expression)) return expression; |
| |
| var self = new FunctionTypeInfoDecoderRing(toRti()).toString(); |
| if (isCast) { |
| var functionTypeObject = _extractFunctionTypeObjectFrom(expression); |
| var pretty; |
| if (functionTypeObject != null) { |
| pretty = new FunctionTypeInfoDecoderRing(functionTypeObject).toString(); |
| } else { |
| pretty = Primitives.objectTypeName(expression); |
| } |
| throw new CastErrorImplementation(pretty, self); |
| } else { |
| // TODO(ahe): Pass "pretty" function-type to TypeErrorImplementation? |
| throw new TypeErrorImplementation(expression, self); |
| } |
| } |
| |
| _extractFunctionTypeObjectFrom(o) { |
| var interceptor = getInterceptor(o); |
| return JS('bool', '# in #', JS_SIGNATURE_NAME(), interceptor) |
| ? JS('', '#[#]()', interceptor, JS_SIGNATURE_NAME()) |
| : null; |
| } |
| |
| toRti() { |
| var result = JS('=Object', '{ #: "dynafunc" }', JS_FUNCTION_TYPE_TAG()); |
| if (isVoid) { |
| JS('', '#[#] = true', result, JS_FUNCTION_TYPE_VOID_RETURN_TAG()); |
| } else { |
| if (returnType is! DynamicRuntimeType) { |
| JS('', '#[#] = #', result, JS_FUNCTION_TYPE_RETURN_TYPE_TAG(), |
| returnType.toRti()); |
| } |
| } |
| if (parameterTypes != null && !parameterTypes.isEmpty) { |
| JS('', '#[#] = #', result, JS_FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG(), |
| listToRti(parameterTypes)); |
| } |
| |
| if (optionalParameterTypes != null && !optionalParameterTypes.isEmpty) { |
| JS('', '#[#] = #', result, JS_FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG(), |
| listToRti(optionalParameterTypes)); |
| } |
| |
| if (namedParameters != null) { |
| var namedRti = JS('=Object', 'Object.create(null)'); |
| var keys = extractKeys(namedParameters); |
| for (var i = 0; i < keys.length; i++) { |
| var name = keys[i]; |
| var rti = JS('', '#[#]', namedParameters, name).toRti(); |
| JS('', '#[#] = #', namedRti, name, rti); |
| } |
| JS('', '#[#] = #', result, JS_FUNCTION_TYPE_NAMED_PARAMETERS_TAG(), |
| namedRti); |
| } |
| |
| return result; |
| } |
| |
| static listToRti(list) { |
| list = JS('JSFixedArray', '#', list); |
| var result = JS('JSExtendableArray', '[]'); |
| for (var i = 0; i < list.length; i++) { |
| JS('', '#.push(#)', result, list[i].toRti()); |
| } |
| return result; |
| } |
| |
| String toString() { |
| String result = '('; |
| bool needsComma = false; |
| if (parameterTypes != null) { |
| for (var i = 0; i < parameterTypes.length; i++) { |
| RuntimeType type = parameterTypes[i]; |
| if (needsComma) result += ', '; |
| result += '$type'; |
| needsComma = true; |
| } |
| } |
| if (optionalParameterTypes != null && !optionalParameterTypes.isEmpty) { |
| if (needsComma) result += ', '; |
| needsComma = false; |
| result += '['; |
| for (var i = 0; i < optionalParameterTypes.length; i++) { |
| RuntimeType type = optionalParameterTypes[i]; |
| if (needsComma) result += ', '; |
| result += '$type'; |
| needsComma = true; |
| } |
| result += ']'; |
| } else if (namedParameters != null) { |
| if (needsComma) result += ', '; |
| needsComma = false; |
| result += '{'; |
| var keys = extractKeys(namedParameters); |
| for (var i = 0; i < keys.length; i++) { |
| var name = keys[i]; |
| if (needsComma) result += ', '; |
| var rti = JS('', '#[#]', namedParameters, name).toRti(); |
| result += '$rti ${JS("String", "#", name)}'; |
| needsComma = true; |
| } |
| result += '}'; |
| } |
| |
| result += ') -> $returnType'; |
| return result; |
| } |
| } |
| |
| RuntimeFunctionType buildFunctionType(returnType, |
| parameterTypes, |
| optionalParameterTypes) { |
| return new RuntimeFunctionType( |
| returnType, |
| parameterTypes, |
| optionalParameterTypes, |
| null); |
| } |
| |
| RuntimeFunctionType buildNamedFunctionType(returnType, |
| parameterTypes, |
| namedParameters) { |
| return new RuntimeFunctionType( |
| returnType, |
| parameterTypes, |
| null, |
| namedParameters); |
| } |
| |
| RuntimeType buildInterfaceType(rti, typeArguments) { |
| String name = JS('String|Null', r'#.name', rti); |
| if (typeArguments == null || typeArguments.isEmpty) { |
| return new RuntimeTypePlain(name); |
| } |
| return new RuntimeTypeGeneric(name, typeArguments, null); |
| } |
| |
| class DynamicRuntimeType extends RuntimeType { |
| const DynamicRuntimeType(); |
| |
| String toString() => 'dynamic'; |
| |
| toRti() => null; |
| } |
| |
| RuntimeType getDynamicRuntimeType() => const DynamicRuntimeType(); |
| |
| class VoidRuntimeType extends RuntimeType { |
| const VoidRuntimeType(); |
| |
| String toString() => 'void'; |
| |
| toRti() => throw 'internal error'; |
| } |
| |
| RuntimeType getVoidRuntimeType() => const VoidRuntimeType(); |
| |
| /** |
| * Meta helper for function type tests. |
| * |
| * A "meta helper" is a helper function that is never called but simulates how |
| * generated code behaves as far as resolution and type inference is concerned. |
| */ |
| functionTypeTestMetaHelper() { |
| var dyn = JS('', 'x'); |
| var dyn2 = JS('', 'x'); |
| List fixedListOrNull = JS('JSFixedArray|Null', 'x'); |
| List fixedListOrNull2 = JS('JSFixedArray|Null', 'x'); |
| List fixedList = JS('JSFixedArray', 'x'); |
| // TODO(ahe): Can we use [UnknownJavaScriptObject] below? |
| var /* UnknownJavaScriptObject */ jsObject = JS('=Object', 'x'); |
| |
| buildFunctionType(dyn, fixedListOrNull, fixedListOrNull2); |
| buildNamedFunctionType(dyn, fixedList, jsObject); |
| buildInterfaceType(dyn, fixedListOrNull); |
| getDynamicRuntimeType(); |
| getVoidRuntimeType(); |
| convertRtiToRuntimeType(dyn); |
| dyn._isTest(dyn2); |
| dyn._asCheck(dyn2); |
| dyn._assertCheck(dyn2); |
| } |
| |
| RuntimeType convertRtiToRuntimeType(rti) { |
| if (rti == null) { |
| return getDynamicRuntimeType(); |
| } else if (JS('bool', 'typeof # == "function"', rti)) { |
| return new RuntimeTypePlain(JS('String', r'rti.name')); |
| } else if (JS('bool', '#.constructor == Array', rti)) { |
| List list = JS('JSFixedArray', '#', rti); |
| String name = JS('String', r'#.name', list[0]); |
| List arguments = []; |
| for (int i = 1; i < list.length; i++) { |
| arguments.add(convertRtiToRuntimeType(list[i])); |
| } |
| return new RuntimeTypeGeneric(name, arguments, rti); |
| } else if (JS('bool', '"func" in #', rti)) { |
| return new FunctionTypeInfoDecoderRing(rti).toRuntimeType(); |
| } else { |
| throw new RuntimeError( |
| "Cannot convert " |
| "'${JS('String', 'JSON.stringify(#)', rti)}' to RuntimeType."); |
| } |
| } |
| |
| class RuntimeTypePlain extends RuntimeType { |
| final String name; |
| |
| RuntimeTypePlain(this.name); |
| |
| toRti() { |
| var getTypeFromName = JS_EMBEDDED_GLOBAL('', GET_TYPE_FROM_NAME); |
| var rti = JS('', '#(#)', getTypeFromName, name); |
| if (rti == null) throw "no type for '$name'"; |
| return rti; |
| } |
| |
| String toString() => name; |
| } |
| |
| class RuntimeTypeGeneric extends RuntimeType { |
| final String name; |
| final List<RuntimeType> arguments; |
| var rti; |
| |
| RuntimeTypeGeneric(this.name, this.arguments, this.rti); |
| |
| toRti() { |
| if (rti != null) return rti; |
| var getTypeFromName = JS_EMBEDDED_GLOBAL('', GET_TYPE_FROM_NAME); |
| var result = JS('JSExtendableArray', '[#(#)]', getTypeFromName, name); |
| if (result[0] == null) { |
| throw "no type for '$name<...>'"; |
| } |
| for (RuntimeType argument in arguments) { |
| JS('', '#.push(#)', result, argument.toRti()); |
| } |
| return rti = result; |
| } |
| |
| String toString() => '$name<${arguments.join(", ")}>'; |
| } |
| |
| class FunctionTypeInfoDecoderRing { |
| final _typeData; |
| String _cachedToString; |
| |
| FunctionTypeInfoDecoderRing(this._typeData); |
| |
| bool get _hasReturnType => JS('bool', '"ret" in #', _typeData); |
| get _returnType => JS('', '#.ret', _typeData); |
| |
| bool get _isVoid => JS('bool', '!!#.void', _typeData); |
| |
| bool get _hasArguments => JS('bool', '"args" in #', _typeData); |
| List get _arguments => JS('JSExtendableArray', '#.args', _typeData); |
| |
| bool get _hasOptionalArguments => JS('bool', '"opt" in #', _typeData); |
| List get _optionalArguments => JS('JSExtendableArray', '#.opt', _typeData); |
| |
| bool get _hasNamedArguments => JS('bool', '"named" in #', _typeData); |
| get _namedArguments => JS('=Object', '#.named', _typeData); |
| |
| RuntimeType toRuntimeType() { |
| // TODO(ahe): Implement this (and update return type). |
| return const DynamicRuntimeType(); |
| } |
| |
| String _convert(type) { |
| String result = runtimeTypeToString(type); |
| if (result != null) return result; |
| if (JS('bool', '"func" in #', type)) { |
| return new FunctionTypeInfoDecoderRing(type).toString(); |
| } else { |
| throw 'bad type'; |
| } |
| } |
| |
| String toString() { |
| if (_cachedToString != null) return _cachedToString; |
| var s = "("; |
| var sep = ''; |
| if (_hasArguments) { |
| for (var argument in _arguments) { |
| s += sep; |
| s += _convert(argument); |
| sep = ', '; |
| } |
| } |
| if (_hasOptionalArguments) { |
| s += '$sep['; |
| sep = ''; |
| for (var argument in _optionalArguments) { |
| s += sep; |
| s += _convert(argument); |
| sep = ', '; |
| } |
| s += ']'; |
| } |
| if (_hasNamedArguments) { |
| s += '$sep{'; |
| sep = ''; |
| for (var name in extractKeys(_namedArguments)) { |
| s += sep; |
| s += '$name: '; |
| s += _convert(JS('', '#[#]', _namedArguments, name)); |
| sep = ', '; |
| } |
| s += '}'; |
| } |
| s += ') -> '; |
| if (_isVoid) { |
| s += 'void'; |
| } else if (_hasReturnType) { |
| s += _convert(_returnType); |
| } else { |
| s += 'dynamic'; |
| } |
| return _cachedToString = "$s"; |
| } |
| } |
| |
| // TODO(ahe): Remove this class and call noSuchMethod instead. |
| class UnimplementedNoSuchMethodError extends Error |
| implements NoSuchMethodError { |
| final String _message; |
| |
| UnimplementedNoSuchMethodError(this._message); |
| |
| String toString() => "Unsupported operation: $_message"; |
| } |
| |
| /** |
| * Creates a random number with 64 bits of randomness. |
| * |
| * This will be truncated to the 53 bits available in a double. |
| */ |
| int random64() { |
| // TODO(lrn): Use a secure random source. |
| int int32a = JS("int", "(Math.random() * 0x100000000) >>> 0"); |
| int int32b = JS("int", "(Math.random() * 0x100000000) >>> 0"); |
| return int32a + int32b * 0x100000000; |
| } |
| |
| String jsonEncodeNative(String string) { |
| return JS("String", "JSON.stringify(#)", string); |
| } |
| |
| /** |
| * Returns a property name for placing data on JavaScript objects shared between |
| * DOM isolates. This happens when multiple programs are loaded in the same |
| * JavaScript context (i.e. page). The name is based on [name] but with an |
| * additional part that is unique for each isolate. |
| * |
| * The form of the name is '___dart_$name_$id'. |
| */ |
| String getIsolateAffinityTag(String name) { |
| var isolateTagGetter = |
| JS_EMBEDDED_GLOBAL('', GET_ISOLATE_TAG); |
| return JS('String', '#(#)', isolateTagGetter, name); |
| } |
| |
| typedef Future<Null> LoadLibraryFunctionType(); |
| |
| LoadLibraryFunctionType _loadLibraryWrapper(String loadId) { |
| return () => loadDeferredLibrary(loadId); |
| } |
| |
| final Map<String, Future<Null>> _loadingLibraries = <String, Future<Null>>{}; |
| final Set<String> _loadedLibraries = new Set<String>(); |
| |
| typedef void DeferredLoadCallback(); |
| |
| // Function that will be called every time a new deferred import is loaded. |
| DeferredLoadCallback deferredLoadHook; |
| |
| Future<Null> loadDeferredLibrary(String loadId) { |
| // For each loadId there is a list of hunk-uris to load, and a corresponding |
| // list of hashes. These are stored in the app-global scope. |
| var urisMap = JS_EMBEDDED_GLOBAL('', DEFERRED_LIBRARY_URIS); |
| List<String> uris = JS('JSExtendableArray|Null', '#[#]', urisMap, loadId); |
| var hashesMap = JS_EMBEDDED_GLOBAL('', DEFERRED_LIBRARY_HASHES); |
| List<String> hashes = JS('JSExtendableArray|Null', '#[#]', hashesMap, loadId); |
| if (uris == null) return new Future.value(null); |
| // The indices into `uris` and `hashes` that we want to load. |
| List<int> indices = new List.generate(uris.length, (i) => i); |
| var isHunkLoaded = JS_EMBEDDED_GLOBAL('', IS_HUNK_LOADED); |
| var isHunkInitialized = JS_EMBEDDED_GLOBAL('', IS_HUNK_INITIALIZED); |
| // Filter away indices for hunks that have already been loaded. |
| List<int> indicesToLoad = indices |
| .where((int i) => !JS('bool','#(#)', isHunkLoaded, hashes[i])) |
| .toList(); |
| return Future.wait(indicesToLoad |
| .map((int i) => _loadHunk(uris[i]))).then((_) { |
| // Now all hunks have been loaded, we run the needed initializers. |
| List<int> indicesToInitialize = indices |
| .where((int i) => !JS('bool','#(#)', isHunkInitialized, hashes[i])) |
| .toList(); // Load the needed hunks. |
| for (int i in indicesToInitialize) { |
| var initializer = JS_EMBEDDED_GLOBAL('', INITIALIZE_LOADED_HUNK); |
| JS('void', '#(#)', initializer, hashes[i]); |
| } |
| bool updated = _loadedLibraries.add(loadId); |
| if (updated && deferredLoadHook != null) { |
| deferredLoadHook(); |
| } |
| }); |
| } |
| |
| Future<Null> _loadHunk(String hunkName) { |
| // TODO(ahe): Validate libraryName. Kasper points out that you want |
| // to be able to experiment with the effect of toggling @DeferLoad, |
| // so perhaps we should silently ignore "bad" library names. |
| Future<Null> future = _loadingLibraries[hunkName]; |
| if (future != null) { |
| return future.then((_) => null); |
| } |
| |
| String uri = IsolateNatives.thisScript; |
| |
| int index = uri.lastIndexOf('/'); |
| uri = '${uri.substring(0, index + 1)}$hunkName'; |
| |
| if (Primitives.isJsshell || Primitives.isD8) { |
| // TODO(ahe): Move this code to a JavaScript command helper script that is |
| // not included in generated output. |
| return _loadingLibraries[hunkName] = new Future<Null>(() { |
| try { |
| // Create a new function to avoid getting access to current function |
| // context. |
| JS('void', '(new Function(#))()', 'load("$uri")'); |
| } catch (error, stackTrace) { |
| _loadingLibraries[hunkName] = null; |
| throw new DeferredLoadException("Loading $uri failed."); |
| } |
| return null; |
| }); |
| } else if (isWorker()) { |
| // We are in a web worker. Load the code with an XMLHttpRequest. |
| return _loadingLibraries[hunkName] = new Future<Null>(() { |
| Completer completer = new Completer<Null>(); |
| enterJsAsync(); |
| Future<Null> leavingFuture = completer.future.whenComplete(() { |
| leaveJsAsync(); |
| }); |
| |
| int index = uri.lastIndexOf('/'); |
| uri = '${uri.substring(0, index + 1)}$hunkName'; |
| var xhr = JS('dynamic', 'new XMLHttpRequest()'); |
| JS('void', '#.open("GET", #)', xhr, uri); |
| JS('void', '#.addEventListener("load", #, false)', |
| xhr, convertDartClosureToJS((event) { |
| if (JS('int', '#.status', xhr) != 200) { |
| _loadingLibraries[hunkName] = null; |
| completer.completeError( |
| new DeferredLoadException("Loading $uri failed.")); |
| return; |
| } |
| String code = JS('String', '#.responseText', xhr); |
| try { |
| // Create a new function to avoid getting access to current function |
| // context. |
| JS('void', '(new Function(#))()', code); |
| } catch (error, stackTrace) { |
| _loadingLibraries[hunkName] = null; |
| completer.completeError( |
| new DeferredLoadException("Evaluating $uri failed.")); |
| return; |
| } |
| completer.complete(null); |
| }, 1)); |
| |
| var fail = convertDartClosureToJS((event) { |
| _loadingLibraries[hunkName] = null; |
| new DeferredLoadException("Loading $uri failed."); |
| }, 1); |
| JS('void', '#.addEventListener("error", #, false)', xhr, fail); |
| JS('void', '#.addEventListener("abort", #, false)', xhr, fail); |
| |
| JS('void', '#.send()', xhr); |
| return leavingFuture; |
| }); |
| } |
| // We are in a dom-context. |
| return _loadingLibraries[hunkName] = new Future<Null>(() { |
| Completer completer = new Completer<Null>(); |
| // Inject a script tag. |
| var script = JS('', 'document.createElement("script")'); |
| JS('', '#.type = "text/javascript"', script); |
| JS('', '#.src = #', script, uri); |
| JS('', '#.addEventListener("load", #, false)', |
| script, convertDartClosureToJS((event) { |
| completer.complete(null); |
| }, 1)); |
| JS('', '#.addEventListener("error", #, false)', |
| script, convertDartClosureToJS((event) { |
| _loadingLibraries[hunkName] = null; |
| completer.completeError( |
| new DeferredLoadException("Loading $uri failed.")); |
| }, 1)); |
| JS('', 'document.body.appendChild(#)', script); |
| |
| return completer.future; |
| }); |
| } |
| |
| class MainError extends Error implements NoSuchMethodError { |
| final String _message; |
| |
| MainError(this._message); |
| |
| String toString() => 'NoSuchMethodError: $_message'; |
| } |
| |
| void missingMain() { |
| throw new MainError("No top-level function named 'main'."); |
| } |
| |
| void badMain() { |
| throw new MainError("'main' is not a function."); |
| } |
| |
| void mainHasTooManyParameters() { |
| throw new MainError("'main' expects too many parameters."); |
| } |
| |
| /// Runtime support for async-await transformation. |
| /// |
| /// This function is called by a transformed function on each await and return |
| /// in the untransformed function, and before starting. |
| /// |
| /// If [object] is not a future it will be wrapped in a `new Future.value`. |
| /// |
| /// If [helperCallback] is null it indicates a return from the async function, |
| /// and we complete the completer with object. |
| /// |
| /// Otherwise [helperCallback] is set up to be called when the future is |
| /// successfull and [errorCallback] if it is completed with an error. |
| /// |
| /// If helperCallback or errorCallback throws we complete the completer with the |
| /// error. |
| /// |
| /// Returns the future of the completer for convenience of the first call. |
| dynamic thenHelper(dynamic object, |
| dynamic /* js function */ helperCallback, |
| Completer completer, |
| dynamic /* js function */ errorCallback) { |
| if (helperCallback == null) { |
| completer.complete(object); |
| return; |
| } |
| Future future = object is Future ? object : new Future.value(object); |
| future.then(_wrapJsFunctionForThenHelper(helperCallback, completer), |
| onError: (errorCallback == null) |
| ? null |
| : _wrapJsFunctionForThenHelper(errorCallback, completer)); |
| return completer.future; |
| } |
| |
| Function _wrapJsFunctionForThenHelper(dynamic /* js function */ function, |
| Completer completer) { |
| return (result) { |
| try { |
| JS('', '#(#)', function, result); |
| } catch (e, st) { |
| completer.completeError(e, st); |
| } |
| }; |
| } |
| |
| |
| /// Implements the runtime support for async* functions. |
| /// |
| /// Called by the transformed function for each original return, await, yield, |
| /// yield* and before starting the function. |
| /// |
| /// When the async* function wants to return it calls this function. with |
| /// [helperCallback] == null, the streamHelper takes this as signal to close the |
| /// stream. |
| /// |
| /// If the async* function wants to do a yield or yield* it calls this function |
| /// with [object] being an [IterationMarker]. In this case [errorCallback] has a |
| /// special meaning; it is a callback that will run all enclosing finalizers. |
| /// |
| /// In the case of a yield or yield*, if the stream subscription has been |
| /// canceled [errorCallback] is scheduled. |
| /// |
| /// If [object] is a single-yield [IterationMarker], adds the value of the |
| /// [IterationMarker] to the stream. If the stream subscription has been |
| /// paused, return early. Otherwise schedule the helper function to be |
| /// executed again. |
| /// |
| /// If [object] is a yield-star [IterationMarker], starts listening to the |
| /// yielded stream, and adds all events and errors to our own controller (taking |
| /// care if the subscription has been paused or canceled) - when the sub-stream |
| /// is done, schedules [helperCallback] again. |
| /// |
| /// If the async* function wants to do an await it calls this function with |
| /// [object] not and [IterationMarker]. |
| /// |
| /// If [object] is not a [Future], it is wrapped in a `Future.value`. |
| /// The [helperCallback] is called on successfull completion of the |
| /// future. |
| /// |
| /// If [helperCallback] or [errorCallback] throws the error is added to the |
| /// stream. |
| void streamHelper(dynamic object, |
| dynamic /* js function */ helperCallback, |
| AsyncStarStreamController controller, |
| dynamic /* js function */ errorCallback) { |
| if (helperCallback == null) { |
| // This happens on return from the async* function. |
| controller.close(); |
| return; |
| } |
| |
| if (object is IterationMarker) { |
| if (controller.stopRunning) { |
| _wrapJsFunctionForStream(errorCallback, controller)(); |
| return; |
| } |
| if (object.state == IterationMarker.YIELD_SINGLE) { |
| controller.add(object.value); |
| // If the controller is paused we stop producing more values. |
| if (controller.isPaused) { |
| return; |
| } |
| // TODO(sigurdm): We should not suspend here according to the spec. |
| scheduleMicrotask(() { |
| _wrapJsFunctionForStream(helperCallback, controller)(null); |
| }); |
| return; |
| } else if (object.state == IterationMarker.YIELD_STAR) { |
| Stream stream = object.value; |
| controller.isAdding = true; |
| // Errors of [stream] are passed though to the main stream. (see |
| // [AsyncStreamController.addStream]. |
| // TODO(sigurdm): The spec is not very clear here. Clarify with Gilad. |
| controller.addStream(stream).then((_) { |
| controller.isAdding = false; |
| _wrapJsFunctionForStream(helperCallback, controller)(null); |
| }); |
| return; |
| } |
| } |
| |
| Future future = object is Future ? object : new Future.value(object); |
| future.then(_wrapJsFunctionForStream(helperCallback, controller), |
| onError: errorCallback == null |
| ? null |
| : _wrapJsFunctionForStream(errorCallback, controller)); |
| } |
| |
| Stream streamOfController(AsyncStarStreamController controller) { |
| return controller.stream; |
| } |
| |
| /// A wrapper around a [StreamController] that remembers if that controller |
| /// got a cancel. |
| /// |
| /// Also has a subSubscription that when not null will provide events for the |
| /// stream, and will be paused and resumed along with this controller. |
| class AsyncStarStreamController { |
| StreamController controller; |
| Stream get stream => controller.stream; |
| bool stopRunning = false; |
| bool isAdding = false; |
| bool get isPaused => controller.isPaused; |
| add(event) => controller.add(event); |
| addStream(Stream stream) { |
| return controller.addStream(stream, cancelOnError: false); |
| } |
| addError(error, stackTrace) => controller.addError(error, stackTrace); |
| close() => controller.close(); |
| |
| AsyncStarStreamController(helperCallback) { |
| controller = new StreamController( |
| onListen: () { |
| scheduleMicrotask(() => JS('', '#(null)', helperCallback)); |
| }, |
| onResume: () { |
| if (!isAdding) { |
| streamHelper(null, helperCallback, this, null); |
| } |
| }, onCancel: () { |
| stopRunning = true; |
| }); |
| } |
| } |
| |
| makeAsyncStarController(helperCallback) { |
| return new AsyncStarStreamController(helperCallback); |
| } |
| |
| Function _wrapJsFunctionForStream(dynamic /* js function */ function, |
| AsyncStarStreamController controller) { |
| return (result) { |
| try { |
| JS('', '#(#)', function, result); |
| } catch (e, st) { |
| controller.addError(e, st); |
| } |
| }; |
| } |
| |
| |
| class IterationMarker { |
| static const YIELD_SINGLE = 0; |
| static const YIELD_STAR = 1; |
| static const ITERATION_ENDED = 2; |
| |
| final value; |
| final int state; |
| |
| IterationMarker._(this.state, this.value); |
| |
| static yieldStar(dynamic /* Iterable or Stream */ values) { |
| return new IterationMarker._(YIELD_STAR, values); |
| } |
| |
| static endOfIteration() { |
| return new IterationMarker._(ITERATION_ENDED, null); |
| } |
| |
| static yieldSingle(dynamic value) { |
| return new IterationMarker._(YIELD_SINGLE, value); |
| } |
| |
| toString() => "IterationMarker($state, $value)"; |
| } |
| |
| class SyncStarIterator implements Iterator { |
| final Function _helper; |
| |
| // If [runningNested] this is the nested iterator, otherwise it is the |
| // current value. |
| dynamic _current = null; |
| bool _runningNested = false; |
| |
| get current => _runningNested ? _current.current : _current; |
| |
| SyncStarIterator(helper) |
| : _helper = ((arg) => JS('', '#(#)', helper, arg)); |
| |
| bool moveNext() { |
| if (_runningNested) { |
| if (_current.moveNext()) { |
| return true; |
| } else { |
| _runningNested = false; |
| } |
| } |
| _current = _helper(null); |
| if (_current is IterationMarker) { |
| if (_current.state == IterationMarker.ITERATION_ENDED) { |
| _current = null; |
| // Rely on [_helper] to repeatedly return `ITERATION_ENDED`. |
| return false; |
| } else { |
| assert(_current.state == IterationMarker.YIELD_STAR); |
| _current = _current.value.iterator; |
| _runningNested = true; |
| return moveNext(); |
| } |
| } |
| return true; |
| } |
| } |
| |
| /// An Iterable corresponding to a sync* method. |
| /// |
| /// Each invocation of a sync* method will return a new instance of this class. |
| class SyncStarIterable extends IterableBase { |
| // This is a function that will return a helper function that does the |
| // iteration of the sync*. |
| // |
| // Each invocation should give a helper with fresh state. |
| final dynamic /* js function */ _outerHelper; |
| |
| SyncStarIterable(this._outerHelper); |
| |
| Iterator get iterator => new SyncStarIterator(JS('', '#()', _outerHelper)); |
| } |