blob: 1ee856eba057876466d763226d7e559b02a663a0 [file] [log] [blame]
// 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:collection';
import 'dart:_isolate_helper' show
IsolateNatives,
leaveJsAsync,
enterJsAsync,
isWorker;
import 'dart:async' show Future, DeferredLoadException, Completer;
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_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,
RAW_DART_FUNCTION_REF;
import 'dart:_interceptors';
import 'dart:_internal' as _symbol_dev;
import 'dart:_internal' show MappedIterable;
import 'dart:_js_names' show
extractKeys,
mangledNames,
unmangleGlobalNameIfPreservedAnyways;
part 'annotations.dart';
part 'constant_map.dart';
part 'native_helper.dart';
part 'regexp_helper.dart';
part 'string_helper.dart';
part 'js_rti.dart';
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.");
}
bool hasReflectableProperty(var jsFunction) {
return JS('bool', '# in #', JS_GET_NAME("REFLECTABLE"), jsFunction);
}
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];
}
_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;
// TODO(ngeoffray): If this functionality ever become performance
// critical, we might want to dynamically change [interceptedNames]
// to be a JavaScript object with intercepted names as property
// instead of a JavaScript array.
// TODO(floitsch): we already add stubs (tear-off getters) as properties
// in init.interceptedNames.
// Finish the transition and always use the object as hashtable.
bool isIntercepted =
JS("bool",
'Object.prototype.hasOwnProperty.call(init.interceptedNames, #) ||'
'#.indexOf(#) !== -1',
name, 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)) {
// TODO(floitsch): bound or tear-off closure does not guarantee that the
// function is reflectable.
bool isReflectable = hasReflectableProperty(method) ||
object is BoundClosure ||
object is TearOffClosure;
if (!isReflectable) {
throwInvalidReflectionError(_symbol_dev.Symbol.getName(memberName));
}
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;
/// 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);
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;
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 [:init.metadata:] 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);
}
return JS('String', 'init.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) => JS('', 'init.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 computeGlobalThis() => JS('', 'function() { return this; }()');
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.
static String formatType(String className, List typeArguments) {
return '$className${joinArguments(typeArguments, 0)}';
}
/// Returns the type of [object] as a string (including type arguments).
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, 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) 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 (identical(name.codeUnitAt(0), DOLLAR_CHAR_VALUE)) {
name = name.substring(1);
}
return formatType(name, getRuntimeTypeInfo(object));
}
static String objectToString(Object object) {
String name = objectTypeName(object);
return "Instance of '$name'";
}
static num dateNow() => JS('num', r'Date.now()');
static num numMicroseconds() {
if (JS('bool', 'typeof window != "undefined" && window !== null')) {
var performance = JS('var', 'window.performance');
if (performance != null &&
JS('bool', 'typeof #.webkitNow == "function"', performance)) {
return (1000 * JS('num', '#.webkitNow()', performance)).floor();
}
}
return 1000 * dateNow();
}
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() {
// In a browser return self.location.href.
if (JS('bool', 'typeof self != "undefined"')) {
return JS('String', 'self.location.href');
}
// In JavaScript shells try to determine the current working
// directory.
var workingDirectory;
if (isD8) {
// TODO(sgjesse): This does not work on Windows.
workingDirectory = JS('String', 'os.system("pwd")');
var length = workingDirectory.length;
if (workingDirectory[length - 1] == '\n') {
workingDirectory = workingDirectory.substring(0, length - 1);
}
}
if (isJsshell) {
// TODO(sgjesse): This does not work on Windows.
workingDirectory = JS('String', 'environment["PWD"]');
}
return workingDirectory != null
? "file://" + workingDirectory + "/"
: 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) {
String result = "";
const kMaxApply = 500;
int end = array.length;
for (var i = 0; i < end; i += kMaxApply) {
var subarray;
if (end <= kMaxApply) {
subarray = array;
} else {
subarray = JS('JSExtendableArray', r'#.slice(#, #)', array,
i, i + kMaxApply < end ? i + kMaxApply : end);
}
result = JS('String', '# + String.fromCharCode.apply(#, #)',
result, null, subarray);
}
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);
}
static String stringConcatUnchecked(String string1, String string2) {
return JS('String', r'# + #', string1, string2);
}
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) {
throw new ArgumentError();
}
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 = 'call\$$argumentCount$names';
return function.noSuchMethod(
createUnmangledInvocationMirror(
#call,
selectorName,
JSInvocationMirror.METHOD,
arguments,
namedArgumentList));
}
static applyFunction(Function function,
List positionalArguments,
Map<String, dynamic> namedArguments) {
if (namedArguments != null && !namedArguments.isEmpty) {
// 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);
}
int argumentCount = 0;
List arguments = [];
if (positionalArguments != null) {
argumentCount += positionalArguments.length;
arguments.addAll(positionalArguments);
}
String selectorName = 'call\$$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, namedArguments);
}
// 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 getConstructorOrInterceptorToken(String className) {
// TODO(ahe): Generalize this and improve test coverage of
// reflecting on intercepted classes.
// We should probably not be mappling the dart:core interface names to the
// interceptor library implementation classes like this. `JSArray` is just
// one implementation of `List`, there are others that have no relationship
// with JSArray other than implementing a common interface.
//
// For now `List` in dart:core and `JSArray` is in dart:_interceptors. We
// need to maintain a distinction to get the correct library mirror.
//
// TODO(17394): Short term: Refactor to avoid two copies of the list of
// known interceptor implementations.
//
// TODO(17394): Longer term: The proper interfaces with abstract methods
// should be emitted.
if (JS('bool', '# == "String"', className)) return const JSString();
if (JS('bool', '# == "int"', className)) return const JSInt();
if (JS('bool', '# == "double"', className)) return const JSDouble();
if (JS('bool', '# == "num"', className)) return const JSNumber();
if (JS('bool', '# == "bool"', className)) return const JSBool();
if (JS('bool', '# == "List"', className)) return const JSArray();
if (JS('bool', '# == "Null"', className)) return const JSNull();
return JS('var', 'init.allClasses[#]', className);
}
static bool isInterceptorToken(var object) {
// This must match the list of tokens returned by
// [getConstructorOrInterceptorToken] above.
return JS('bool', '# === #', object, const JSString())
|| JS('bool', '# === #', object, const JSInt())
|| JS('bool', '# === #', object, const JSDouble())
|| JS('bool', '# === #', object, const JSNumber())
|| JS('bool', '# === #', object, const JSBool())
|| JS('bool', '# === #', object, const JSArray())
|| JS('bool', '# === #', object, const JSNull());
}
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', '{x:0}');
// 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', 'delete #.x', result);
return result;
}
static fetch(cache, String key) => 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.
*/
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('', '{}');
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 _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.
*/
makeLiteralMap(keyValuePairs) {
return fillLiteralMap(keyValuePairs, new LinkedHashMap());
}
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.
*
* Caution: this function may be called when building constants.
* TODO(ahe): Don't call this function when building constants.
*/
static fromTearOff(receiver,
List functions,
List 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);
JS('', '#.\$reflectionInfo = #', function, reflectionInfo);
ReflectionInfo info = new ReflectionInfo(function);
var functionType = info.functionType;
// 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; // Necessary?
// 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
// TODO(ahe): Safe to use Object.create?
? 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++);
// TODO(ahe): Is it necessary to set the constructor property?
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)) {
signatureFunction =
JS('', '(function(s){return function(){return init.metadata[s]}})(#)',
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('', '#.\$signature = #', prototype, 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++}'
'}');
}
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),
JSArray.markFixedList(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();
/**
* Represents the type dynamic. The compiler treats this specially.
*/
abstract class Dynamic_ {
}
/**
* 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);
}
/**
* 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";
}
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', '{}');
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 rti = JS('', 'init.allClasses[#]', 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 result = JS('JSExtendableArray', '[init.allClasses[#]]', 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;
}
/**
* 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) {
return JS('String', 'init.getIsolateTag(#)', name);
}
typedef Future<bool> LoadLibraryFunctionType();
LoadLibraryFunctionType _loadLibraryWrapper(String loadId) {
return () => loadDeferredLibrary(loadId);
}
final Map<String, Future<bool>> _loadedLibraries = <String, Future<bool>>{};
Future<bool> loadDeferredLibrary(String loadId, [String uri]) {
List hunkNames = new List();
if (JS('bool', '\$.libraries_to_load[#] === undefined', loadId)) {
return new Future(() => false);
}
for (int index = 0;
index < JS('int', '\$.libraries_to_load[#].length', loadId);
++index) {
hunkNames.add(JS('String', '\$.libraries_to_load[#][#]',
loadId, index));
}
Iterable<Future<bool>> allLoads =
hunkNames.map((hunkName) => _loadHunk(hunkName, uri));
return Future.wait(allLoads).then((results) {
return results.any((x) => x);
});
}
Future<bool> _loadHunk(String hunkName, String uri) {
// 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<bool> future = _loadedLibraries[hunkName];
if (future != null) {
return future.then((_) => false);
}
if (uri == null) {
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 _loadedLibraries[hunkName] = new Future<bool>(() {
try {
// Create a new function to avoid getting access to current function
// context.
JS('void', '(new Function(#))()', 'load("$uri")');
} catch (error, stackTrace) {
throw new DeferredLoadException("Loading $uri failed.");
}
return true;
});
} else if (isWorker()) {
// We are in a web worker. Load the code with an XMLHttpRequest.
return _loadedLibraries[hunkName] = new Future<bool>(() {
Completer completer = new Completer<bool>();
enterJsAsync();
Future<bool> 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) {
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) {
completer.completeError(
new DeferredLoadException("Evaluating $uri failed."));
return;
}
completer.complete(true);
}, 1));
var fail = convertDartClosureToJS((event) {
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 _loadedLibraries[hunkName] = new Future<bool>(() {
Completer completer = new Completer<bool>();
// 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(true);
}, 1));
JS('', '#.addEventListener("error", #, false)',
script, convertDartClosureToJS((event) {
completer.completeError(
new DeferredLoadException("Loading $uri failed."));
}, 1));
JS('', 'document.body.appendChild(#)', script);
return completer.future;
});
}