Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(374)

Side by Side Diff: tool/input_sdk_patch/js_helper.dart

Issue 955513008: cleans up sdk patching so we no longer have unresolved names (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tool/input_sdk_patch/js_array.dart ('k') | tool/input_sdk_patch/js_mirrors.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 library _js_helper;
6
7 import 'dart:_js_embedded_names' show
8 ALL_CLASSES,
9 GET_ISOLATE_TAG,
10 INTERCEPTED_NAMES,
11 INTERCEPTORS_BY_TAG,
12 LEAF_TAGS,
13 METADATA,
14 DEFERRED_LIBRARY_URIS,
15 DEFERRED_LIBRARY_HASHES,
16 INITIALIZE_LOADED_HUNK,
17 IS_HUNK_LOADED,
18 IS_HUNK_INITIALIZED,
19 NATIVE_SUPERCLASS_TAG_NAME;
20
21 import 'dart:collection';
22 import 'dart:_isolate_helper' show
23 IsolateNatives,
24 leaveJsAsync,
25 enterJsAsync,
26 isWorker;
27
28 import 'dart:async' show Future, DeferredLoadException, Completer;
29
30 import 'dart:_foreign_helper' show
31 DART_CLOSURE_TO_JS,
32 JS,
33 JS_CALL_IN_ISOLATE,
34 JS_CONST,
35 JS_CURRENT_ISOLATE,
36 JS_CURRENT_ISOLATE_CONTEXT,
37 JS_DART_OBJECT_CONSTRUCTOR,
38 JS_EFFECT,
39 JS_EMBEDDED_GLOBAL,
40 JS_FUNCTION_CLASS_NAME,
41 JS_FUNCTION_TYPE_NAMED_PARAMETERS_TAG,
42 JS_FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG,
43 JS_FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG,
44 JS_FUNCTION_TYPE_RETURN_TYPE_TAG,
45 JS_FUNCTION_TYPE_TAG,
46 JS_FUNCTION_TYPE_VOID_RETURN_TAG,
47 JS_GET_NAME,
48 JS_GET_FLAG,
49 JS_HAS_EQUALS,
50 JS_IS_INDEXABLE_FIELD_NAME,
51 JS_NULL_CLASS_NAME,
52 JS_OBJECT_CLASS_NAME,
53 JS_OPERATOR_AS_PREFIX,
54 JS_OPERATOR_IS_PREFIX,
55 JS_SIGNATURE_NAME,
56 JS_STRING_CONCAT,
57 RAW_DART_FUNCTION_REF;
58
59 import 'dart:_interceptors';
60 import 'dart:_internal' as _symbol_dev;
61 import 'dart:_internal' show MappedIterable;
62
63 import 'dart:_js_names' show
64 extractKeys,
65 mangledNames,
66 unmangleGlobalNameIfPreservedAnyways,
67 unmangleAllIdentifiersIfPreservedAnyways;
68
69 part 'annotations.dart';
70 part 'constant_map.dart';
71 part 'native_helper.dart';
72 part 'regexp_helper.dart';
73 part 'string_helper.dart';
74 part 'js_rti.dart';
75
76 class _Patch {
77 const _Patch();
78 }
79
80 const _Patch patch = const _Patch();
81
82
83 /// Marks the internal map in dart2js, so that internal libraries can is-check
84 // them.
85 abstract class InternalMap {
86 }
87
88 /// No-op method that is called to inform the compiler that preambles might
89 /// be needed when executing the resulting JS file in a command-line
90 /// JS engine.
91 requiresPreamble() {}
92
93 bool isJsIndexable(var object, var record) {
94 if (record != null) {
95 var result = dispatchRecordIndexability(record);
96 if (result != null) return result;
97 }
98 return object is JavaScriptIndexingBehavior;
99 }
100
101 String S(value) {
102 if (value is String) return value;
103 if (value is num) {
104 if (value != 0) {
105 // ""+x is faster than String(x) for integers on most browsers.
106 return JS('String', r'"" + (#)', value);
107 }
108 } else if (true == value) {
109 return 'true';
110 } else if (false == value) {
111 return 'false';
112 } else if (value == null) {
113 return 'null';
114 }
115 var res = value.toString();
116 if (res is !String) throw new ArgumentError(value);
117 return res;
118 }
119
120 createInvocationMirror(String name, internalName, kind, arguments,
121 argumentNames) {
122 return new JSInvocationMirror(name,
123 internalName,
124 kind,
125 arguments,
126 argumentNames);
127 }
128
129 createUnmangledInvocationMirror(Symbol symbol, internalName, kind, arguments,
130 argumentNames) {
131 return new JSInvocationMirror(symbol,
132 internalName,
133 kind,
134 arguments,
135 argumentNames);
136 }
137
138 void throwInvalidReflectionError(String memberName) {
139 throw new UnsupportedError("Can't use '$memberName' in reflection "
140 "because it is not included in a @MirrorsUsed annotation.");
141 }
142
143 /// Helper to print the given method information to the console the first
144 /// time it is called with it.
145 @NoInline()
146 void traceHelper(String method) {
147 if (JS('bool', '!this.cache')) {
148 JS('', 'this.cache = Object.create(null)');
149 }
150 if (JS('bool', '!this.cache[#]', method)) {
151 JS('', 'console.log(#)', method);
152 JS('', 'this.cache[#] = true', method);
153 }
154 }
155
156 class JSInvocationMirror implements Invocation {
157 static const METHOD = 0;
158 static const GETTER = 1;
159 static const SETTER = 2;
160
161 /// When [_memberName] is a String, it holds the mangled name of this
162 /// invocation. When it is a Symbol, it holds the unmangled name.
163 var /* String or Symbol */ _memberName;
164 final String _internalName;
165 final int _kind;
166 final List _arguments;
167 final List _namedArgumentNames;
168 /** Map from argument name to index in _arguments. */
169 Map<String, dynamic> _namedIndices = null;
170
171 JSInvocationMirror(this._memberName,
172 this._internalName,
173 this._kind,
174 this._arguments,
175 this._namedArgumentNames);
176
177 Symbol get memberName {
178 if (_memberName is Symbol) return _memberName;
179 String name = _memberName;
180 String unmangledName = mangledNames[name];
181 if (unmangledName != null) {
182 name = unmangledName.split(':')[0];
183 } else {
184 if (mangledNames[_internalName] == null) {
185 print("Warning: '$name' is used reflectively but not in MirrorsUsed. "
186 "This will break minified code.");
187 }
188 }
189 _memberName = new _symbol_dev.Symbol.unvalidated(name);
190 return _memberName;
191 }
192
193 bool get isMethod => _kind == METHOD;
194 bool get isGetter => _kind == GETTER;
195 bool get isSetter => _kind == SETTER;
196 bool get isAccessor => _kind != METHOD;
197
198 List get positionalArguments {
199 if (isGetter) return const [];
200 var argumentCount = _arguments.length - _namedArgumentNames.length;
201 if (argumentCount == 0) return const [];
202 var list = [];
203 for (var index = 0 ; index < argumentCount ; index++) {
204 list.add(_arguments[index]);
205 }
206 return makeLiteralListConst(list);
207 }
208
209 Map<Symbol, dynamic> get namedArguments {
210 // TODO: Make maps const (issue 10471)
211 if (isAccessor) return <Symbol, dynamic>{};
212 int namedArgumentCount = _namedArgumentNames.length;
213 int namedArgumentsStartIndex = _arguments.length - namedArgumentCount;
214 if (namedArgumentCount == 0) return <Symbol, dynamic>{};
215 var map = new Map<Symbol, dynamic>();
216 for (int i = 0; i < namedArgumentCount; i++) {
217 map[new _symbol_dev.Symbol.unvalidated(_namedArgumentNames[i])] =
218 _arguments[namedArgumentsStartIndex + i];
219 }
220 return map;
221 }
222
223 _getCachedInvocation(Object object) {
224 var interceptor = getInterceptor(object);
225 var receiver = object;
226 var name = _internalName;
227 var arguments = _arguments;
228 var interceptedNames = JS_EMBEDDED_GLOBAL('', INTERCEPTED_NAMES);
229 bool isIntercepted =
230 JS("bool", 'Object.prototype.hasOwnProperty.call(#, #)',
231 interceptedNames, name);
232 if (isIntercepted) {
233 receiver = interceptor;
234 if (JS('bool', '# === #', object, interceptor)) {
235 interceptor = null;
236 }
237 } else {
238 interceptor = null;
239 }
240 bool isCatchAll = false;
241 var method = JS('var', '#[#]', receiver, name);
242 if (JS('bool', 'typeof # != "function"', method) ) {
243 String baseName = _symbol_dev.Symbol.getName(memberName);
244 method = JS('', '#[# + "*"]', receiver, baseName);
245 if (method == null) {
246 interceptor = getInterceptor(object);
247 method = JS('', '#[# + "*"]', interceptor, baseName);
248 if (method != null) {
249 isIntercepted = true;
250 receiver = interceptor;
251 } else {
252 interceptor = null;
253 }
254 }
255 isCatchAll = true;
256 }
257 if (JS('bool', 'typeof # == "function"', method)) {
258 if (isCatchAll) {
259 return new CachedCatchAllInvocation(
260 name, method, isIntercepted, interceptor);
261 } else {
262 return new CachedInvocation(name, method, isIntercepted, interceptor);
263 }
264 } else {
265 // In this case, receiver doesn't implement name. So we should
266 // invoke noSuchMethod instead (which will often throw a
267 // NoSuchMethodError).
268 return new CachedNoSuchMethodInvocation(interceptor);
269 }
270 }
271
272 /// This method is called by [InstanceMirror.delegate].
273 static invokeFromMirror(JSInvocationMirror invocation, Object victim) {
274 var cached = invocation._getCachedInvocation(victim);
275 if (cached.isNoSuchMethod) {
276 return cached.invokeOn(victim, invocation);
277 } else {
278 return cached.invokeOn(victim, invocation._arguments);
279 }
280 }
281
282 static getCachedInvocation(JSInvocationMirror invocation, Object victim) {
283 return invocation._getCachedInvocation(victim);
284 }
285 }
286
287 class CachedInvocation {
288 // The mangled name of this invocation.
289 String mangledName;
290
291 /// The JS function to call.
292 var jsFunction;
293
294 /// True if this is an intercepted call.
295 bool isIntercepted;
296
297 /// Non-null interceptor if this is an intercepted call through an
298 /// [Interceptor].
299 Interceptor cachedInterceptor;
300
301 CachedInvocation(this.mangledName,
302 this.jsFunction,
303 this.isIntercepted,
304 this.cachedInterceptor);
305
306 bool get isNoSuchMethod => false;
307 bool get isGetterStub => JS("bool", "!!#.\$getterStub", jsFunction);
308
309 /// Applies [jsFunction] to [victim] with [arguments].
310 /// Users of this class must take care to check the arguments first.
311 invokeOn(Object victim, List arguments) {
312 var receiver = victim;
313 if (!isIntercepted) {
314 if (arguments is! JSArray) arguments = new List.from(arguments);
315 } else {
316 arguments = [victim]..addAll(arguments);
317 if (cachedInterceptor != null) receiver = cachedInterceptor;
318 }
319 return JS("var", "#.apply(#, #)", jsFunction, receiver, arguments);
320 }
321 }
322
323 class CachedCatchAllInvocation extends CachedInvocation {
324 final ReflectionInfo info;
325
326 CachedCatchAllInvocation(String name,
327 jsFunction,
328 bool isIntercepted,
329 Interceptor cachedInterceptor)
330 : info = new ReflectionInfo(jsFunction),
331 super(name, jsFunction, isIntercepted, cachedInterceptor);
332
333 bool get isGetterStub => false;
334
335 invokeOn(Object victim, List arguments) {
336 var receiver = victim;
337 int providedArgumentCount;
338 int fullParameterCount =
339 info.requiredParameterCount + info.optionalParameterCount;
340 if (!isIntercepted) {
341 if (arguments is JSArray) {
342 providedArgumentCount = arguments.length;
343 // If we need to add extra arguments before calling, we have
344 // to copy the arguments array.
345 if (providedArgumentCount < fullParameterCount) {
346 arguments = new List.from(arguments);
347 }
348 } else {
349 arguments = new List.from(arguments);
350 providedArgumentCount = arguments.length;
351 }
352 } else {
353 arguments = [victim]..addAll(arguments);
354 if (cachedInterceptor != null) receiver = cachedInterceptor;
355 providedArgumentCount = arguments.length - 1;
356 }
357 if (info.areOptionalParametersNamed &&
358 (providedArgumentCount > info.requiredParameterCount)) {
359 throw new UnimplementedNoSuchMethodError(
360 "Invocation of unstubbed method '${info.reflectionName}'"
361 " with ${arguments.length} arguments.");
362 } else if (providedArgumentCount < info.requiredParameterCount) {
363 throw new UnimplementedNoSuchMethodError(
364 "Invocation of unstubbed method '${info.reflectionName}'"
365 " with $providedArgumentCount arguments (too few).");
366 } else if (providedArgumentCount > fullParameterCount) {
367 throw new UnimplementedNoSuchMethodError(
368 "Invocation of unstubbed method '${info.reflectionName}'"
369 " with $providedArgumentCount arguments (too many).");
370 }
371 for (int i = providedArgumentCount; i < fullParameterCount; i++) {
372 arguments.add(getMetadata(info.defaultValue(i)));
373 }
374 return JS("var", "#.apply(#, #)", jsFunction, receiver, arguments);
375 }
376 }
377
378 class CachedNoSuchMethodInvocation {
379 /// Non-null interceptor if this is an intercepted call through an
380 /// [Interceptor].
381 var interceptor;
382
383 CachedNoSuchMethodInvocation(this.interceptor);
384
385 bool get isNoSuchMethod => true;
386 bool get isGetterStub => false;
387
388 invokeOn(Object victim, Invocation invocation) {
389 var receiver = (interceptor == null) ? victim : interceptor;
390 return receiver.noSuchMethod(invocation);
391 }
392 }
393
394 class ReflectionInfo {
395 static const int REQUIRED_PARAMETERS_INFO = 0;
396 static const int OPTIONAL_PARAMETERS_INFO = 1;
397 static const int FUNCTION_TYPE_INDEX = 2;
398 static const int FIRST_DEFAULT_ARGUMENT = 3;
399
400 /// A JavaScript function object.
401 final jsFunction;
402
403 /// Raw reflection information.
404 final List data;
405
406 /// Is this a getter or a setter.
407 final bool isAccessor;
408
409 /// Number of required parameters.
410 final int requiredParameterCount;
411
412 /// Number of optional parameters.
413 final int optionalParameterCount;
414
415 /// Are optional parameters named.
416 final bool areOptionalParametersNamed;
417
418 /// Either an index to the function type in the embedded `metadata` global or
419 /// a JavaScript function object which can compute such a type (presumably
420 /// due to free type variables).
421 final functionType;
422
423 List cachedSortedIndices;
424
425 ReflectionInfo.internal(this.jsFunction,
426 this.data,
427 this.isAccessor,
428 this.requiredParameterCount,
429 this.optionalParameterCount,
430 this.areOptionalParametersNamed,
431 this.functionType);
432
433 factory ReflectionInfo(jsFunction) {
434 List data = JS('JSExtendableArray|Null', r'#.$reflectionInfo', jsFunction);
435 if (data == null) return null;
436 data = JSArray.markFixedList(data);
437
438 int requiredParametersInfo =
439 JS('int', '#[#]', data, REQUIRED_PARAMETERS_INFO);
440 int requiredParameterCount = JS('int', '# >> 1', requiredParametersInfo);
441 bool isAccessor = (requiredParametersInfo & 1) == 1;
442
443 int optionalParametersInfo =
444 JS('int', '#[#]', data, OPTIONAL_PARAMETERS_INFO);
445 int optionalParameterCount = JS('int', '# >> 1', optionalParametersInfo);
446 bool areOptionalParametersNamed = (optionalParametersInfo & 1) == 1;
447
448 var functionType = JS('', '#[#]', data, FUNCTION_TYPE_INDEX);
449 return new ReflectionInfo.internal(
450 jsFunction, data, isAccessor, requiredParameterCount,
451 optionalParameterCount, areOptionalParametersNamed, functionType);
452 }
453
454 String parameterName(int parameter) {
455 int metadataIndex;
456 if (JS_GET_FLAG('MUST_RETAIN_METADATA')) {
457 metadataIndex = JS('int', '#[2 * # + # + #]', data,
458 parameter, optionalParameterCount, FIRST_DEFAULT_ARGUMENT);
459 } else {
460 metadataIndex = JS('int', '#[# + # + #]', data,
461 parameter, optionalParameterCount, FIRST_DEFAULT_ARGUMENT);
462 }
463 var metadata = JS_EMBEDDED_GLOBAL('', METADATA);
464 return JS('String', '#[#]', metadata, metadataIndex);
465 }
466
467 List<int> parameterMetadataAnnotations(int parameter) {
468 if (!JS_GET_FLAG('MUST_RETAIN_METADATA')) {
469 throw new StateError('metadata has not been preserved');
470 } else {
471 return JS('', '#[2 * # + # + # + 1]', data, parameter,
472 optionalParameterCount, FIRST_DEFAULT_ARGUMENT);
473 }
474 }
475
476 int defaultValue(int parameter) {
477 if (parameter < requiredParameterCount) return null;
478 return JS('int', '#[# + # - #]', data,
479 FIRST_DEFAULT_ARGUMENT, parameter, requiredParameterCount);
480 }
481
482 /// Returns the default value of the [parameter]th entry of the list of
483 /// parameters sorted by name.
484 int defaultValueInOrder(int parameter) {
485 if (parameter < requiredParameterCount) return null;
486
487 if (!areOptionalParametersNamed || optionalParameterCount == 1) {
488 return defaultValue(parameter);
489 }
490
491 int index = sortedIndex(parameter - requiredParameterCount);
492 return defaultValue(index);
493 }
494
495 /// Returns the default value of the [parameter]th entry of the list of
496 /// parameters sorted by name.
497 String parameterNameInOrder(int parameter) {
498 if (parameter < requiredParameterCount) return null;
499
500 if (!areOptionalParametersNamed ||
501 optionalParameterCount == 1) {
502 return parameterName(parameter);
503 }
504
505 int index = sortedIndex(parameter - requiredParameterCount);
506 return parameterName(index);
507 }
508
509 /// Computes the index of the parameter in the list of named parameters sorted
510 /// by their name.
511 int sortedIndex(int unsortedIndex) {
512 if (cachedSortedIndices == null) {
513 // TODO(karlklose): cache this between [ReflectionInfo] instances or cache
514 // [ReflectionInfo] instances by [jsFunction].
515 cachedSortedIndices = new List(optionalParameterCount);
516 Map<String, int> positions = <String, int>{};
517 for (int i = 0; i < optionalParameterCount; i++) {
518 int index = requiredParameterCount + i;
519 positions[parameterName(index)] = index;
520 }
521 int index = 0;
522 (positions.keys.toList()..sort()).forEach((String name) {
523 cachedSortedIndices[index++] = positions[name];
524 });
525 }
526 return cachedSortedIndices[unsortedIndex];
527 }
528
529 @NoInline()
530 computeFunctionRti(jsConstructor) {
531 if (JS('bool', 'typeof # == "number"', functionType)) {
532 return getMetadata(functionType);
533 } else if (JS('bool', 'typeof # == "function"', functionType)) {
534 var fakeInstance = JS('', 'new #()', jsConstructor);
535 setRuntimeTypeInfo(
536 fakeInstance, JS('JSExtendableArray', '#["<>"]', fakeInstance));
537 return JS('=Object|Null', r'#.apply({$receiver:#})',
538 functionType, fakeInstance);
539 } else {
540 throw new RuntimeError('Unexpected function type');
541 }
542 }
543
544 String get reflectionName => JS('String', r'#.$reflectionName', jsFunction);
545 }
546
547 getMetadata(int index) {
548 var metadata = JS_EMBEDDED_GLOBAL('', METADATA);
549 return JS('', '#[#]', metadata, index);
550 }
551
552 class Primitives {
553 /// Isolate-unique ID for caching [JsClosureMirror.function].
554 /// Note the initial value is used by the first isolate (or if there are no
555 /// isolates), new isolates will update this value to avoid conflicts by
556 /// calling [initializeStatics].
557 static String mirrorFunctionCacheName = '\$cachedFunction';
558
559 /// Isolate-unique ID for caching [JsInstanceMirror._invoke].
560 static String mirrorInvokeCacheName = '\$cachedInvocation';
561
562 /// Called when creating a new isolate (see _IsolateContext constructor in
563 /// isolate_helper.dart).
564 /// Please don't add complicated code to this method, as it will impact
565 /// start-up performance.
566 static void initializeStatics(int id) {
567 // Benchmarking shows significant performance improvements if this is a
568 // fixed value.
569 mirrorFunctionCacheName += '_$id';
570 mirrorInvokeCacheName += '_$id';
571 }
572
573 static int objectHashCode(object) {
574 int hash = JS('int|Null', r'#.$identityHash', object);
575 if (hash == null) {
576 hash = JS('int', '(Math.random() * 0x3fffffff) | 0');
577 JS('void', r'#.$identityHash = #', object, hash);
578 }
579 return JS('int', '#', hash);
580 }
581
582 static _throwFormatException(String string) {
583 throw new FormatException(string);
584 }
585
586 static int parseInt(String source,
587 int radix,
588 int handleError(String source)) {
589 if (handleError == null) handleError = _throwFormatException;
590
591 checkString(source);
592 var match = JS('JSExtendableArray|Null',
593 r'/^\s*[+-]?((0x[a-f0-9]+)|(\d+)|([a-z0-9]+))\s*$/i.exec(#)',
594 source);
595 int digitsIndex = 1;
596 int hexIndex = 2;
597 int decimalIndex = 3;
598 int nonDecimalHexIndex = 4;
599 if (radix == null) {
600 radix = 10;
601 if (match != null) {
602 if (match[hexIndex] != null) {
603 // Cannot fail because we know that the digits are all hex.
604 return JS('num', r'parseInt(#, 16)', source);
605 }
606 if (match[decimalIndex] != null) {
607 // Cannot fail because we know that the digits are all decimal.
608 return JS('num', r'parseInt(#, 10)', source);
609 }
610 return handleError(source);
611 }
612 } else {
613 if (radix is! int) throw new ArgumentError("Radix is not an integer");
614 if (radix < 2 || radix > 36) {
615 throw new RangeError("Radix $radix not in range 2..36");
616 }
617 if (match != null) {
618 if (radix == 10 && match[decimalIndex] != null) {
619 // Cannot fail because we know that the digits are all decimal.
620 return JS('num', r'parseInt(#, 10)', source);
621 }
622 if (radix < 10 || match[decimalIndex] == null) {
623 // We know that the characters must be ASCII as otherwise the
624 // regexp wouldn't have matched. Lowercasing by doing `| 0x20` is thus
625 // guaranteed to be a safe operation, since it preserves digits
626 // and lower-cases ASCII letters.
627 int maxCharCode;
628 if (radix <= 10) {
629 // Allow all digits less than the radix. For example 0, 1, 2 for
630 // radix 3.
631 // "0".codeUnitAt(0) + radix - 1;
632 maxCharCode = 0x30 + radix - 1;
633 } else {
634 // Letters are located after the digits in ASCII. Therefore we
635 // only check for the character code. The regexp above made already
636 // sure that the string does not contain anything but digits or
637 // letters.
638 // "a".codeUnitAt(0) + (radix - 10) - 1;
639 maxCharCode = 0x61 + radix - 10 - 1;
640 }
641 String digitsPart = match[digitsIndex];
642 for (int i = 0; i < digitsPart.length; i++) {
643 int characterCode = digitsPart.codeUnitAt(0) | 0x20;
644 if (digitsPart.codeUnitAt(i) > maxCharCode) {
645 return handleError(source);
646 }
647 }
648 }
649 }
650 }
651 if (match == null) return handleError(source);
652 return JS('num', r'parseInt(#, #)', source, radix);
653 }
654
655 static double parseDouble(String source, double handleError(String source)) {
656 checkString(source);
657 if (handleError == null) handleError = _throwFormatException;
658 // Notice that JS parseFloat accepts garbage at the end of the string.
659 // Accept only:
660 // - [+/-]NaN
661 // - [+/-]Infinity
662 // - a Dart double literal
663 // We do allow leading or trailing whitespace.
664 if (!JS('bool',
665 r'/^\s*[+-]?(?:Infinity|NaN|'
666 r'(?:\.\d+|\d+(?:\.\d*)?)(?:[eE][+-]?\d+)?)\s*$/.test(#)',
667 source)) {
668 return handleError(source);
669 }
670 var result = JS('num', r'parseFloat(#)', source);
671 if (result.isNaN) {
672 var trimmed = source.trim();
673 if (trimmed == 'NaN' || trimmed == '+NaN' || trimmed == '-NaN') {
674 return result;
675 }
676 return handleError(source);
677 }
678 return result;
679 }
680
681 /** [: r"$".codeUnitAt(0) :] */
682 static const int DOLLAR_CHAR_VALUE = 36;
683
684 /// Creates a string containing the complete type for the class [className]
685 /// with the given type arguments.
686 ///
687 /// In minified mode, uses the unminified names if available.
688 static String formatType(String className, List typeArguments) {
689 return unmangleAllIdentifiersIfPreservedAnyways
690 ('$className${joinArguments(typeArguments, 0)}');
691 }
692
693 /// Returns the type of [object] as a string (including type arguments).
694 ///
695 /// In minified mode, uses the unminified names if available.
696 static String objectTypeName(Object object) {
697 String name = constructorNameFallback(getInterceptor(object));
698 if (name == 'Object') {
699 // Try to decompile the constructor by turning it into a string and get
700 // the name out of that. If the decompiled name is a string containing an
701 // identifier, we use that instead of the very generic 'Object'.
702 var decompiled =
703 JS('var', r'#.match(/^\s*function\s*(\S*)\s*\(/)[1]',
704 JS('var', r'String(#.constructor)', object));
705 if (decompiled is String)
706 if (JS('bool', r'/^\w+$/.test(#)', decompiled))
707 name = decompiled;
708 }
709 // TODO(kasperl): If the namer gave us a fresh global name, we may
710 // want to remove the numeric suffix that makes it unique too.
711 if (name.length > 1 && identical(name.codeUnitAt(0), DOLLAR_CHAR_VALUE)) {
712 name = name.substring(1);
713 }
714 return formatType(name, getRuntimeTypeInfo(object));
715 }
716
717 /// In minified mode, uses the unminified names if available.
718 static String objectToString(Object object) {
719 String name = objectTypeName(object);
720 return "Instance of '$name'";
721 }
722
723 static num dateNow() => JS('int', r'Date.now()');
724
725 static void initTicker() {
726 if (timerFrequency != null) return;
727 // Start with low-resolution. We overwrite the fields if we find better.
728 timerFrequency = 1000;
729 timerTicks = dateNow;
730 if (JS('bool', 'typeof window == "undefined"')) return;
731 var window = JS('var', 'window');
732 if (window == null) return;
733 var performance = JS('var', '#.performance', window);
734 if (performance == null) return;
735 if (JS('bool', 'typeof #.now != "function"', performance)) return;
736 timerFrequency = 1000000;
737 timerTicks = () => (1000 * JS('num', '#.now()', performance)).floor();
738 }
739
740 static int timerFrequency;
741 static Function timerTicks;
742
743 static bool get isD8 {
744 return JS('bool',
745 'typeof version == "function"'
746 ' && typeof os == "object" && "system" in os');
747 }
748
749 static bool get isJsshell {
750 return JS('bool',
751 'typeof version == "function" && typeof system == "function"');
752 }
753
754 static String currentUri() {
755 requiresPreamble();
756 // In a browser return self.location.href.
757 if (JS('bool', '!!self.location')) {
758 return JS('String', 'self.location.href');
759 }
760
761 return null;
762 }
763
764 // This is to avoid stack overflows due to very large argument arrays in
765 // apply(). It fixes http://dartbug.com/6919
766 static String _fromCharCodeApply(List<int> array) {
767 String result = "";
768 const kMaxApply = 500;
769 int end = array.length;
770 for (var i = 0; i < end; i += kMaxApply) {
771 var subarray;
772 if (end <= kMaxApply) {
773 subarray = array;
774 } else {
775 subarray = JS('JSExtendableArray', r'#.slice(#, #)', array,
776 i, i + kMaxApply < end ? i + kMaxApply : end);
777 }
778 result = JS('String', '# + String.fromCharCode.apply(#, #)',
779 result, null, subarray);
780 }
781 return result;
782 }
783
784 static String stringFromCodePoints(codePoints) {
785 List<int> a = <int>[];
786 for (var i in codePoints) {
787 if (i is !int) throw new ArgumentError(i);
788 if (i <= 0xffff) {
789 a.add(i);
790 } else if (i <= 0x10ffff) {
791 a.add(0xd800 + ((((i - 0x10000) >> 10) & 0x3ff)));
792 a.add(0xdc00 + (i & 0x3ff));
793 } else {
794 throw new ArgumentError(i);
795 }
796 }
797 return _fromCharCodeApply(a);
798 }
799
800 static String stringFromCharCodes(charCodes) {
801 for (var i in charCodes) {
802 if (i is !int) throw new ArgumentError(i);
803 if (i < 0) throw new ArgumentError(i);
804 if (i > 0xffff) return stringFromCodePoints(charCodes);
805 }
806 return _fromCharCodeApply(charCodes);
807 }
808
809 static String stringFromCharCode(charCode) {
810 if (0 <= charCode) {
811 if (charCode <= 0xffff) {
812 return JS('String', 'String.fromCharCode(#)', charCode);
813 }
814 if (charCode <= 0x10ffff) {
815 var bits = charCode - 0x10000;
816 var low = 0xDC00 | (bits & 0x3ff);
817 var high = 0xD800 | (bits >> 10);
818 return JS('String', 'String.fromCharCode(#, #)', high, low);
819 }
820 }
821 throw new RangeError.range(charCode, 0, 0x10ffff);
822 }
823
824 static String stringConcatUnchecked(String string1, String string2) {
825 return JS_STRING_CONCAT(string1, string2);
826 }
827
828 static String flattenString(String str) {
829 return JS('String', "#.charCodeAt(0) == 0 ? # : #", str, str, str);
830 }
831
832 static String getTimeZoneName(receiver) {
833 // Firefox and Chrome emit the timezone in parenthesis.
834 // Example: "Wed May 16 2012 21:13:00 GMT+0200 (CEST)".
835 // We extract this name using a regexp.
836 var d = lazyAsJsDate(receiver);
837 List match = JS('JSArray|Null', r'/\((.*)\)/.exec(#.toString())', d);
838 if (match != null) return match[1];
839
840 // Internet Explorer 10+ emits the zone name without parenthesis:
841 // Example: Thu Oct 31 14:07:44 PDT 2013
842 match = JS('JSArray|Null',
843 // Thu followed by a space.
844 r'/^[A-Z,a-z]{3}\s'
845 // Oct 31 followed by space.
846 r'[A-Z,a-z]{3}\s\d+\s'
847 // Time followed by a space.
848 r'\d{2}:\d{2}:\d{2}\s'
849 // The time zone name followed by a space.
850 r'([A-Z]{3,5})\s'
851 // The year.
852 r'\d{4}$/'
853 '.exec(#.toString())',
854 d);
855 if (match != null) return match[1];
856
857 // IE 9 and Opera don't provide the zone name. We fall back to emitting the
858 // UTC/GMT offset.
859 // Example (IE9): Wed Nov 20 09:51:00 UTC+0100 2013
860 // (Opera): Wed Nov 20 2013 11:03:38 GMT+0100
861 match = JS('JSArray|Null', r'/(?:GMT|UTC)[+-]\d{4}/.exec(#.toString())', d);
862 if (match != null) return match[0];
863 return "";
864 }
865
866 static int getTimeZoneOffsetInMinutes(receiver) {
867 // Note that JS and Dart disagree on the sign of the offset.
868 return -JS('int', r'#.getTimezoneOffset()', lazyAsJsDate(receiver));
869 }
870
871 static valueFromDecomposedDate(years, month, day, hours, minutes, seconds,
872 milliseconds, isUtc) {
873 final int MAX_MILLISECONDS_SINCE_EPOCH = 8640000000000000;
874 checkInt(years);
875 checkInt(month);
876 checkInt(day);
877 checkInt(hours);
878 checkInt(minutes);
879 checkInt(seconds);
880 checkInt(milliseconds);
881 checkBool(isUtc);
882 var jsMonth = month - 1;
883 var value;
884 if (isUtc) {
885 value = JS('num', r'Date.UTC(#, #, #, #, #, #, #)',
886 years, jsMonth, day, hours, minutes, seconds, milliseconds);
887 } else {
888 value = JS('num', r'new Date(#, #, #, #, #, #, #).valueOf()',
889 years, jsMonth, day, hours, minutes, seconds, milliseconds);
890 }
891 if (value.isNaN ||
892 value < -MAX_MILLISECONDS_SINCE_EPOCH ||
893 value > MAX_MILLISECONDS_SINCE_EPOCH) {
894 return null;
895 }
896 if (years <= 0 || years < 100) return patchUpY2K(value, years, isUtc);
897 return value;
898 }
899
900 static patchUpY2K(value, years, isUtc) {
901 var date = JS('', r'new Date(#)', value);
902 if (isUtc) {
903 JS('num', r'#.setUTCFullYear(#)', date, years);
904 } else {
905 JS('num', r'#.setFullYear(#)', date, years);
906 }
907 return JS('num', r'#.valueOf()', date);
908 }
909
910 // Lazily keep a JS Date stored in the JS object.
911 static lazyAsJsDate(receiver) {
912 if (JS('bool', r'#.date === (void 0)', receiver)) {
913 JS('void', r'#.date = new Date(#)', receiver,
914 receiver.millisecondsSinceEpoch);
915 }
916 return JS('var', r'#.date', receiver);
917 }
918
919 // The getters for date and time parts below add a positive integer to ensure
920 // that the result is really an integer, because the JavaScript implementation
921 // may return -0.0 instead of 0.
922
923 static getYear(receiver) {
924 return (receiver.isUtc)
925 ? JS('int', r'(#.getUTCFullYear() + 0)', lazyAsJsDate(receiver))
926 : JS('int', r'(#.getFullYear() + 0)', lazyAsJsDate(receiver));
927 }
928
929 static getMonth(receiver) {
930 return (receiver.isUtc)
931 ? JS('int', r'#.getUTCMonth() + 1', lazyAsJsDate(receiver))
932 : JS('int', r'#.getMonth() + 1', lazyAsJsDate(receiver));
933 }
934
935 static getDay(receiver) {
936 return (receiver.isUtc)
937 ? JS('int', r'(#.getUTCDate() + 0)', lazyAsJsDate(receiver))
938 : JS('int', r'(#.getDate() + 0)', lazyAsJsDate(receiver));
939 }
940
941 static getHours(receiver) {
942 return (receiver.isUtc)
943 ? JS('int', r'(#.getUTCHours() + 0)', lazyAsJsDate(receiver))
944 : JS('int', r'(#.getHours() + 0)', lazyAsJsDate(receiver));
945 }
946
947 static getMinutes(receiver) {
948 return (receiver.isUtc)
949 ? JS('int', r'(#.getUTCMinutes() + 0)', lazyAsJsDate(receiver))
950 : JS('int', r'(#.getMinutes() + 0)', lazyAsJsDate(receiver));
951 }
952
953 static getSeconds(receiver) {
954 return (receiver.isUtc)
955 ? JS('int', r'(#.getUTCSeconds() + 0)', lazyAsJsDate(receiver))
956 : JS('int', r'(#.getSeconds() + 0)', lazyAsJsDate(receiver));
957 }
958
959 static getMilliseconds(receiver) {
960 return (receiver.isUtc)
961 ? JS('int', r'(#.getUTCMilliseconds() + 0)', lazyAsJsDate(receiver))
962 : JS('int', r'(#.getMilliseconds() + 0)', lazyAsJsDate(receiver));
963 }
964
965 static getWeekday(receiver) {
966 int weekday = (receiver.isUtc)
967 ? JS('int', r'#.getUTCDay() + 0', lazyAsJsDate(receiver))
968 : JS('int', r'#.getDay() + 0', lazyAsJsDate(receiver));
969 // Adjust by one because JS weeks start on Sunday.
970 return (weekday + 6) % 7 + 1;
971 }
972
973 static valueFromDateString(str) {
974 if (str is !String) throw new ArgumentError(str);
975 var value = JS('num', r'Date.parse(#)', str);
976 if (value.isNaN) throw new ArgumentError(str);
977 return value;
978 }
979
980 static getProperty(object, key) {
981 if (object == null || object is bool || object is num || object is String) {
982 throw new ArgumentError(object);
983 }
984 return JS('var', '#[#]', object, key);
985 }
986
987 static void setProperty(object, key, value) {
988 if (object == null || object is bool || object is num || object is String) {
989 throw new ArgumentError(object);
990 }
991 JS('void', '#[#] = #', object, key, value);
992 }
993
994 static functionNoSuchMethod(function,
995 List positionalArguments,
996 Map<String, dynamic> namedArguments) {
997 int argumentCount = 0;
998 List arguments = [];
999 List namedArgumentList = [];
1000
1001 if (positionalArguments != null) {
1002 argumentCount += positionalArguments.length;
1003 arguments.addAll(positionalArguments);
1004 }
1005
1006 String names = '';
1007 if (namedArguments != null && !namedArguments.isEmpty) {
1008 namedArguments.forEach((String name, argument) {
1009 names = '$names\$$name';
1010 namedArgumentList.add(name);
1011 arguments.add(argument);
1012 argumentCount++;
1013 });
1014 }
1015
1016 String selectorName =
1017 '${JS_GET_NAME("CALL_PREFIX")}\$$argumentCount$names';
1018
1019 return function.noSuchMethod(
1020 createUnmangledInvocationMirror(
1021 #call,
1022 selectorName,
1023 JSInvocationMirror.METHOD,
1024 arguments,
1025 namedArgumentList));
1026 }
1027
1028 static applyFunction(Function function,
1029 List positionalArguments,
1030 Map<String, dynamic> namedArguments) {
1031 // Dispatch on presence of named arguments to improve tree-shaking.
1032 //
1033 // This dispatch is as simple as possible to help the compiler detect the
1034 // common case of `null` namedArguments, either via inlining or
1035 // specialization.
1036 return namedArguments == null
1037 ? applyFunctionWithPositionalArguments(
1038 function, positionalArguments)
1039 : applyFunctionWithNamedArguments(
1040 function, positionalArguments, namedArguments);
1041 }
1042
1043 static applyFunctionWithPositionalArguments(Function function,
1044 List positionalArguments) {
1045 int argumentCount = 0;
1046 List arguments;
1047
1048 if (positionalArguments != null) {
1049 if (JS('bool', '# instanceof Array', positionalArguments)) {
1050 arguments = positionalArguments;
1051 } else {
1052 arguments = new List.from(positionalArguments);
1053 }
1054 argumentCount = JS('int', '#.length', arguments);
1055 } else {
1056 arguments = [];
1057 }
1058
1059 String selectorName = '${JS_GET_NAME("CALL_PREFIX")}\$$argumentCount';
1060 var jsFunction = JS('var', '#[#]', function, selectorName);
1061 if (jsFunction == null) {
1062
1063 // TODO(ahe): This might occur for optional arguments if there is no call
1064 // selector with that many arguments.
1065
1066 return functionNoSuchMethod(function, positionalArguments, null);
1067 }
1068 // We bound 'this' to [function] because of how we compile
1069 // closures: escaped local variables are stored and accessed through
1070 // [function].
1071 return JS('var', '#.apply(#, #)', jsFunction, function, arguments);
1072 }
1073
1074 static applyFunctionWithNamedArguments(Function function,
1075 List positionalArguments,
1076 Map<String, dynamic> namedArguments) {
1077 if (namedArguments.isEmpty) {
1078 return applyFunctionWithPositionalArguments(
1079 function, positionalArguments);
1080 }
1081 // TODO(ahe): The following code can be shared with
1082 // JsInstanceMirror.invoke.
1083 var interceptor = getInterceptor(function);
1084 var jsFunction = JS('', '#["call*"]', interceptor);
1085
1086 if (jsFunction == null) {
1087 return functionNoSuchMethod(
1088 function, positionalArguments, namedArguments);
1089 }
1090 ReflectionInfo info = new ReflectionInfo(jsFunction);
1091 if (info == null || !info.areOptionalParametersNamed) {
1092 return functionNoSuchMethod(
1093 function, positionalArguments, namedArguments);
1094 }
1095
1096 if (positionalArguments != null) {
1097 positionalArguments = new List.from(positionalArguments);
1098 } else {
1099 positionalArguments = [];
1100 }
1101 // Check the number of positional arguments is valid.
1102 if (info.requiredParameterCount != positionalArguments.length) {
1103 return functionNoSuchMethod(
1104 function, positionalArguments, namedArguments);
1105 }
1106 var defaultArguments = new Map();
1107 for (int i = 0; i < info.optionalParameterCount; i++) {
1108 int index = i + info.requiredParameterCount;
1109 var parameterName = info.parameterNameInOrder(index);
1110 var value = info.defaultValueInOrder(index);
1111 var defaultValue = getMetadata(value);
1112 defaultArguments[parameterName] = defaultValue;
1113 }
1114 bool bad = false;
1115 namedArguments.forEach((String parameter, value) {
1116 if (defaultArguments.containsKey(parameter)) {
1117 defaultArguments[parameter] = value;
1118 } else {
1119 // Extraneous named argument.
1120 bad = true;
1121 }
1122 });
1123 if (bad) {
1124 return functionNoSuchMethod(
1125 function, positionalArguments, namedArguments);
1126 }
1127 positionalArguments.addAll(defaultArguments.values);
1128 return JS('', '#.apply(#, #)', jsFunction, function, positionalArguments);
1129 }
1130
1131 static _mangledNameMatchesType(String mangledName, TypeImpl type) {
1132 return JS('bool', '# == #', mangledName, type._typeName);
1133 }
1134
1135 static bool identicalImplementation(a, b) {
1136 return JS('bool', '# == null', a)
1137 ? JS('bool', '# == null', b)
1138 : JS('bool', '# === #', a, b);
1139 }
1140
1141 static StackTrace extractStackTrace(Error error) {
1142 return getTraceFromException(JS('', r'#.$thrownJsError', error));
1143 }
1144 }
1145
1146 /// Helper class for allocating and using JS object literals as caches.
1147 class JsCache {
1148 /// Returns a JavaScript object suitable for use as a cache.
1149 static allocate() {
1150 var result = JS('=Object', 'Object.create(null)');
1151 // Deleting a property makes V8 assume that it shouldn't create a hidden
1152 // class for [result] and map transitions. Although these map transitions
1153 // pay off if there are many cache hits for the same keys, it becomes
1154 // really slow when there aren't many repeated hits.
1155 JS('void', '#.x=0', result);
1156 JS('void', 'delete #.x', result);
1157 return result;
1158 }
1159
1160 static fetch(cache, String key) {
1161 return JS('', '#[#]', cache, key);
1162 }
1163
1164 static void update(cache, String key, value) {
1165 JS('void', '#[#] = #', cache, key, value);
1166 }
1167 }
1168
1169 /**
1170 * Called by generated code to throw an illegal-argument exception,
1171 * for example, if a non-integer index is given to an optimized
1172 * indexed access.
1173 */
1174 iae(argument) {
1175 throw new ArgumentError(argument);
1176 }
1177
1178 /**
1179 * Called by generated code to throw an index-out-of-range exception,
1180 * for example, if a bounds check fails in an optimized indexed
1181 * access. This may also be called when the index is not an integer, in
1182 * which case it throws an illegal-argument exception instead, like
1183 * [iae], or when the receiver is null.
1184 */
1185 ioore(receiver, index) {
1186 if (receiver == null) receiver.length; // Force a NoSuchMethodError.
1187 if (index is !int) iae(index);
1188 throw new RangeError.value(index);
1189 }
1190
1191 stringLastIndexOfUnchecked(receiver, element, start)
1192 => JS('int', r'#.lastIndexOf(#, #)', receiver, element, start);
1193
1194
1195 checkNull(object) {
1196 if (object == null) throw new ArgumentError(null);
1197 return object;
1198 }
1199
1200 checkNum(value) {
1201 if (value is !num) {
1202 throw new ArgumentError(value);
1203 }
1204 return value;
1205 }
1206
1207 checkInt(value) {
1208 if (value is !int) {
1209 throw new ArgumentError(value);
1210 }
1211 return value;
1212 }
1213
1214 checkBool(value) {
1215 if (value is !bool) {
1216 throw new ArgumentError(value);
1217 }
1218 return value;
1219 }
1220
1221 checkString(value) {
1222 if (value is !String) {
1223 throw new ArgumentError(value);
1224 }
1225 return value;
1226 }
1227
1228 /**
1229 * Wrap the given Dart object and record a stack trace.
1230 *
1231 * The code in [unwrapException] deals with getting the original Dart
1232 * object out of the wrapper again.
1233 */
1234 @NoInline()
1235 wrapException(ex) {
1236 if (ex == null) ex = new NullThrownError();
1237 var wrapper = JS('', 'new Error()');
1238 // [unwrapException] looks for the property 'dartException'.
1239 JS('void', '#.dartException = #', wrapper, ex);
1240
1241 if (JS('bool', '"defineProperty" in Object')) {
1242 // Define a JavaScript getter for 'message'. This is to work around V8 bug
1243 // (https://code.google.com/p/v8/issues/detail?id=2519). The default
1244 // toString on Error returns the value of 'message' if 'name' is
1245 // empty. Setting toString directly doesn't work, see the bug.
1246 JS('void', 'Object.defineProperty(#, "message", { get: # })',
1247 wrapper, DART_CLOSURE_TO_JS(toStringWrapper));
1248 JS('void', '#.name = ""', wrapper);
1249 } else {
1250 // In the unlikely event the browser doesn't support Object.defineProperty,
1251 // hope that it just calls toString.
1252 JS('void', '#.toString = #', wrapper, DART_CLOSURE_TO_JS(toStringWrapper));
1253 }
1254
1255 return wrapper;
1256 }
1257
1258 /// Do not call directly.
1259 toStringWrapper() {
1260 // This method gets installed as toString on a JavaScript object. Due to the
1261 // weird scope rules of JavaScript, JS 'this' will refer to that object.
1262 return JS('', r'this.dartException').toString();
1263 }
1264
1265 /**
1266 * This wraps the exception and does the throw. It is possible to call this in
1267 * a JS expression context, where the throw statement is not allowed. Helpers
1268 * are never inlined, so we don't risk inlining the throw statement into an
1269 * expression context.
1270 */
1271 throwExpression(ex) {
1272 JS('void', 'throw #', wrapException(ex));
1273 }
1274
1275 makeLiteralListConst(list) {
1276 JS('bool', r'#.immutable$list = #', list, true);
1277 JS('bool', r'#.fixed$length = #', list, true);
1278 return list;
1279 }
1280
1281 throwRuntimeError(message) {
1282 throw new RuntimeError(message);
1283 }
1284
1285 throwAbstractClassInstantiationError(className) {
1286 throw new AbstractClassInstantiationError(className);
1287 }
1288
1289
1290 /**
1291 * Helper class for building patterns recognizing native type errors.
1292 */
1293 class TypeErrorDecoder {
1294 // Field names are private to help tree-shaking.
1295
1296 /// A regular expression which matches is matched against an error message.
1297 final String _pattern;
1298
1299 /// The group index of "arguments" in [_pattern], or -1 if _pattern has no
1300 /// match for "arguments".
1301 final int _arguments;
1302
1303 /// The group index of "argumentsExpr" in [_pattern], or -1 if _pattern has
1304 /// no match for "argumentsExpr".
1305 final int _argumentsExpr;
1306
1307 /// The group index of "expr" in [_pattern], or -1 if _pattern has no match
1308 /// for "expr".
1309 final int _expr;
1310
1311 /// The group index of "method" in [_pattern], or -1 if _pattern has no match
1312 /// for "method".
1313 final int _method;
1314
1315 /// The group index of "receiver" in [_pattern], or -1 if _pattern has no
1316 /// match for "receiver".
1317 final int _receiver;
1318
1319 /// Pattern used to recognize a NoSuchMethodError error (and
1320 /// possibly extract the method name).
1321 static final TypeErrorDecoder noSuchMethodPattern =
1322 extractPattern(provokeCallErrorOn(buildJavaScriptObject()));
1323
1324 /// Pattern used to recognize an "object not a closure" error (and
1325 /// possibly extract the method name).
1326 static final TypeErrorDecoder notClosurePattern =
1327 extractPattern(provokeCallErrorOn(buildJavaScriptObjectWithNonClosure()));
1328
1329 /// Pattern used to recognize a NoSuchMethodError on JavaScript null
1330 /// call.
1331 static final TypeErrorDecoder nullCallPattern =
1332 extractPattern(provokeCallErrorOn(JS('', 'null')));
1333
1334 /// Pattern used to recognize a NoSuchMethodError on JavaScript literal null
1335 /// call.
1336 static final TypeErrorDecoder nullLiteralCallPattern =
1337 extractPattern(provokeCallErrorOnNull());
1338
1339 /// Pattern used to recognize a NoSuchMethodError on JavaScript
1340 /// undefined call.
1341 static final TypeErrorDecoder undefinedCallPattern =
1342 extractPattern(provokeCallErrorOn(JS('', 'void 0')));
1343
1344 /// Pattern used to recognize a NoSuchMethodError on JavaScript literal
1345 /// undefined call.
1346 static final TypeErrorDecoder undefinedLiteralCallPattern =
1347 extractPattern(provokeCallErrorOnUndefined());
1348
1349 /// Pattern used to recognize a NoSuchMethodError on JavaScript null
1350 /// property access.
1351 static final TypeErrorDecoder nullPropertyPattern =
1352 extractPattern(provokePropertyErrorOn(JS('', 'null')));
1353
1354 /// Pattern used to recognize a NoSuchMethodError on JavaScript literal null
1355 /// property access.
1356 static final TypeErrorDecoder nullLiteralPropertyPattern =
1357 extractPattern(provokePropertyErrorOnNull());
1358
1359 /// Pattern used to recognize a NoSuchMethodError on JavaScript
1360 /// undefined property access.
1361 static final TypeErrorDecoder undefinedPropertyPattern =
1362 extractPattern(provokePropertyErrorOn(JS('', 'void 0')));
1363
1364 /// Pattern used to recognize a NoSuchMethodError on JavaScript literal
1365 /// undefined property access.
1366 static final TypeErrorDecoder undefinedLiteralPropertyPattern =
1367 extractPattern(provokePropertyErrorOnUndefined());
1368
1369 TypeErrorDecoder(this._arguments,
1370 this._argumentsExpr,
1371 this._expr,
1372 this._method,
1373 this._receiver,
1374 this._pattern);
1375
1376 /// Returns a JavaScript object literal (map) with at most the
1377 /// following keys:
1378 ///
1379 /// * arguments: The arguments as formatted by the JavaScript
1380 /// engine. No browsers are known to provide this information.
1381 ///
1382 /// * argumentsExpr: The syntax of the arguments (JavaScript source
1383 /// code). No browsers are known to provide this information.
1384 ///
1385 /// * expr: The syntax of the receiver expression (JavaScript source
1386 /// code). Firefox provides this information, for example: "$expr$.$method$
1387 /// is not a function".
1388 ///
1389 /// * method: The name of the called method (mangled name). At least Firefox
1390 /// and Chrome/V8 provides this information, for example, "Object [object
1391 /// Object] has no method '$method$'".
1392 ///
1393 /// * receiver: The string representation of the receiver. Chrome/V8
1394 /// used to provide this information (by calling user-defined
1395 /// JavaScript toString on receiver), but it has degenerated into
1396 /// "[object Object]" in recent versions.
1397 matchTypeError(message) {
1398 var match = JS('JSExtendableArray|Null',
1399 'new RegExp(#).exec(#)', _pattern, message);
1400 if (match == null) return null;
1401 var result = JS('', 'Object.create(null)');
1402 if (_arguments != -1) {
1403 JS('', '#.arguments = #[# + 1]', result, match, _arguments);
1404 }
1405 if (_argumentsExpr != -1) {
1406 JS('', '#.argumentsExpr = #[# + 1]', result, match, _argumentsExpr);
1407 }
1408 if (_expr != -1) {
1409 JS('', '#.expr = #[# + 1]', result, match, _expr);
1410 }
1411 if (_method != -1) {
1412 JS('', '#.method = #[# + 1]', result, match, _method);
1413 }
1414 if (_receiver != -1) {
1415 JS('', '#.receiver = #[# + 1]', result, match, _receiver);
1416 }
1417
1418 return result;
1419 }
1420
1421 /// Builds a JavaScript Object with a toString method saying
1422 /// r"$receiver$".
1423 static buildJavaScriptObject() {
1424 return JS('', r'{ toString: function() { return "$receiver$"; } }');
1425 }
1426
1427 /// Builds a JavaScript Object with a toString method saying
1428 /// r"$receiver$". The property "$method" is defined, but is not a function.
1429 static buildJavaScriptObjectWithNonClosure() {
1430 return JS('', r'{ $method$: null, '
1431 r'toString: function() { return "$receiver$"; } }');
1432 }
1433
1434 /// Extract a pattern from a JavaScript TypeError message.
1435 ///
1436 /// The patterns are extracted by forcing TypeErrors on known
1437 /// objects thus forcing known strings into the error message. The
1438 /// known strings are then replaced with wildcards which in theory
1439 /// makes it possible to recognize the desired information even if
1440 /// the error messages are reworded or translated.
1441 static extractPattern(String message) {
1442 // Some JavaScript implementations (V8 at least) include a
1443 // representation of the receiver in the error message, however,
1444 // this representation is not always [: receiver.toString() :],
1445 // sometimes it is [: Object.prototype.toString(receiver) :], and
1446 // sometimes it is an implementation specific method (but that
1447 // doesn't seem to happen for object literals). So sometimes we
1448 // get the text "[object Object]". The shortest way to get that
1449 // string is using "String({})".
1450 // See: http://code.google.com/p/v8/issues/detail?id=2519.
1451 message = JS('String', r"#.replace(String({}), '$receiver$')", message);
1452
1453 // Since we want to create a new regular expression from an unknown string,
1454 // we must escape all regular expression syntax.
1455 message = JS('String', r"#.replace(new RegExp(#, 'g'), '\\$&')",
1456 message, ESCAPE_REGEXP);
1457
1458 // Look for the special pattern \$camelCase\$ (all the $ symbols
1459 // have been escaped already), as we will soon be inserting
1460 // regular expression syntax that we want interpreted by RegExp.
1461 List<String> match =
1462 JS('JSExtendableArray|Null', r"#.match(/\\\$[a-zA-Z]+\\\$/g)", message);
1463 if (match == null) match = [];
1464
1465 // Find the positions within the substring matches of the error message
1466 // components. This will help us extract information later, such as the
1467 // method name.
1468 int arguments = JS('int', '#.indexOf(#)', match, r'\$arguments\$');
1469 int argumentsExpr = JS('int', '#.indexOf(#)', match, r'\$argumentsExpr\$');
1470 int expr = JS('int', '#.indexOf(#)', match, r'\$expr\$');
1471 int method = JS('int', '#.indexOf(#)', match, r'\$method\$');
1472 int receiver = JS('int', '#.indexOf(#)', match, r'\$receiver\$');
1473
1474 // Replace the patterns with a regular expression wildcard.
1475 // Note: in a perfect world, one would use "(.*)", but not in
1476 // JavaScript, "." does not match newlines.
1477 String pattern = JS('String',
1478 r"#.replace('\\$arguments\\$', '((?:x|[^x])*)')"
1479 r".replace('\\$argumentsExpr\\$', '((?:x|[^x])*)')"
1480 r".replace('\\$expr\\$', '((?:x|[^x])*)')"
1481 r".replace('\\$method\\$', '((?:x|[^x])*)')"
1482 r".replace('\\$receiver\\$', '((?:x|[^x])*)')",
1483 message);
1484
1485 return new TypeErrorDecoder(arguments,
1486 argumentsExpr,
1487 expr,
1488 method,
1489 receiver,
1490 pattern);
1491 }
1492
1493 /// Provokes a TypeError and returns its message.
1494 ///
1495 /// The error is provoked so all known variable content can be recognized and
1496 /// a pattern can be inferred.
1497 static String provokeCallErrorOn(expression) {
1498 // This function is carefully created to maximize the possibility
1499 // of decoding the TypeError message and turning it into a general
1500 // pattern.
1501 //
1502 // The idea is to inject something known into something unknown. The
1503 // unknown entity is the error message that the browser provides with a
1504 // TypeError. It is a human readable message, possibly localized in a
1505 // language no dart2js engineer understand. We assume that $name$ would
1506 // never naturally occur in a human readable error message, yet it is easy
1507 // to decode.
1508 //
1509 // For example, evaluate this in V8 version 3.13.7.6:
1510 //
1511 // var $expr$ = null; $expr$.$method$()
1512 //
1513 // The VM throws an instance of TypeError whose message property contains
1514 // "Cannot call method '$method$' of null". We can then reasonably assume
1515 // that if the string contains $method$, that's where the method name will
1516 // be in general. Call this automatically reverse engineering the error
1517 // format string in V8.
1518 //
1519 // So the error message from V8 is turned into this regular expression:
1520 //
1521 // "Cannot call method '(.*)' of null"
1522 //
1523 // Similarly, if we evaluate:
1524 //
1525 // var $expr$ = {toString: function() { return '$receiver$'; }};
1526 // $expr$.$method$()
1527 //
1528 // We get this message: "Object $receiver$ has no method '$method$'"
1529 //
1530 // Which is turned into this regular expression:
1531 //
1532 // "Object (.*) has no method '(.*)'"
1533 //
1534 // Firefox/jsshell is slightly different, it tries to include the source
1535 // code that caused the exception, so we get this message: "$expr$.$method$
1536 // is not a function" which is turned into this regular expression:
1537 //
1538 // "(.*)\\.(.*) is not a function"
1539
1540 var function = JS('', r"""function($expr$) {
1541 var $argumentsExpr$ = '$arguments$';
1542 try {
1543 $expr$.$method$($argumentsExpr$);
1544 } catch (e) {
1545 return e.message;
1546 }
1547 }""");
1548 return JS('String', '(#)(#)', function, expression);
1549 }
1550
1551 /// Similar to [provokeCallErrorOn], but provokes an error directly on
1552 /// literal "null" expression.
1553 static String provokeCallErrorOnNull() {
1554 // See [provokeCallErrorOn] for a detailed explanation.
1555 var function = JS('', r"""function() {
1556 var $argumentsExpr$ = '$arguments$';
1557 try {
1558 null.$method$($argumentsExpr$);
1559 } catch (e) {
1560 return e.message;
1561 }
1562 }""");
1563 return JS('String', '(#)()', function);
1564 }
1565
1566 /// Similar to [provokeCallErrorOnNull], but provokes an error directly on
1567 /// (void 0), that is, "undefined".
1568 static String provokeCallErrorOnUndefined() {
1569 // See [provokeCallErrorOn] for a detailed explanation.
1570 var function = JS('', r"""function() {
1571 var $argumentsExpr$ = '$arguments$';
1572 try {
1573 (void 0).$method$($argumentsExpr$);
1574 } catch (e) {
1575 return e.message;
1576 }
1577 }""");
1578 return JS('String', '(#)()', function);
1579 }
1580
1581 /// Similar to [provokeCallErrorOn], but provokes a property access
1582 /// error.
1583 static String provokePropertyErrorOn(expression) {
1584 // See [provokeCallErrorOn] for a detailed explanation.
1585 var function = JS('', r"""function($expr$) {
1586 try {
1587 $expr$.$method$;
1588 } catch (e) {
1589 return e.message;
1590 }
1591 }""");
1592 return JS('String', '(#)(#)', function, expression);
1593 }
1594
1595 /// Similar to [provokePropertyErrorOn], but provokes an property access
1596 /// error directly on literal "null" expression.
1597 static String provokePropertyErrorOnNull() {
1598 // See [provokeCallErrorOn] for a detailed explanation.
1599 var function = JS('', r"""function() {
1600 try {
1601 null.$method$;
1602 } catch (e) {
1603 return e.message;
1604 }
1605 }""");
1606 return JS('String', '(#)()', function);
1607 }
1608
1609 /// Similar to [provokePropertyErrorOnNull], but provokes an property access
1610 /// error directly on (void 0), that is, "undefined".
1611 static String provokePropertyErrorOnUndefined() {
1612 // See [provokeCallErrorOn] for a detailed explanation.
1613 var function = JS('', r"""function() {
1614 try {
1615 (void 0).$method$;
1616 } catch (e) {
1617 return e.message;
1618 }
1619 }""");
1620 return JS('String', '(#)()', function);
1621 }
1622 }
1623
1624 class NullError extends Error implements NoSuchMethodError {
1625 final String _message;
1626 final String _method;
1627
1628 NullError(this._message, match)
1629 : _method = match == null ? null : JS('', '#.method', match);
1630
1631 String toString() {
1632 if (_method == null) return 'NullError: $_message';
1633 return 'NullError: Cannot call "$_method" on null';
1634 }
1635 }
1636
1637 class JsNoSuchMethodError extends Error implements NoSuchMethodError {
1638 final String _message;
1639 final String _method;
1640 final String _receiver;
1641
1642 JsNoSuchMethodError(this._message, match)
1643 : _method = match == null ? null : JS('String|Null', '#.method', match),
1644 _receiver =
1645 match == null ? null : JS('String|Null', '#.receiver', match);
1646
1647 String toString() {
1648 if (_method == null) return 'NoSuchMethodError: $_message';
1649 if (_receiver == null) {
1650 return 'NoSuchMethodError: Cannot call "$_method" ($_message)';
1651 }
1652 return 'NoSuchMethodError: Cannot call "$_method" on "$_receiver" '
1653 '($_message)';
1654 }
1655 }
1656
1657 class UnknownJsTypeError extends Error {
1658 final String _message;
1659
1660 UnknownJsTypeError(this._message);
1661
1662 String toString() => _message.isEmpty ? 'Error' : 'Error: $_message';
1663 }
1664
1665 /**
1666 * Called from catch blocks in generated code to extract the Dart
1667 * exception from the thrown value. The thrown value may have been
1668 * created by [wrapException] or it may be a 'native' JS exception.
1669 *
1670 * Some native exceptions are mapped to new Dart instances, others are
1671 * returned unmodified.
1672 */
1673 unwrapException(ex) {
1674 /// If error implements Error, save [ex] in [error.$thrownJsError].
1675 /// Otherwise, do nothing. Later, the stack trace can then be extraced from
1676 /// [ex].
1677 saveStackTrace(error) {
1678 if (error is Error) {
1679 var thrownStackTrace = JS('', r'#.$thrownJsError', error);
1680 if (thrownStackTrace == null) {
1681 JS('void', r'#.$thrownJsError = #', error, ex);
1682 }
1683 }
1684 return error;
1685 }
1686
1687 // Note that we are checking if the object has the property. If it
1688 // has, it could be set to null if the thrown value is null.
1689 if (ex == null) return null;
1690 if (JS('bool', 'typeof # !== "object"', ex)) return ex;
1691
1692 if (JS('bool', r'"dartException" in #', ex)) {
1693 return saveStackTrace(JS('', r'#.dartException', ex));
1694 } else if (!JS('bool', r'"message" in #', ex)) {
1695 return ex;
1696 }
1697
1698 // Grab hold of the exception message. This field is available on
1699 // all supported browsers.
1700 var message = JS('var', r'#.message', ex);
1701
1702 // Internet Explorer has an error number. This is the most reliable way to
1703 // detect specific errors, so check for this first.
1704 if (JS('bool', '"number" in #', ex)
1705 && JS('bool', 'typeof #.number == "number"', ex)) {
1706 int number = JS('int', '#.number', ex);
1707
1708 // From http://msdn.microsoft.com/en-us/library/ie/hc53e755(v=vs.94).aspx
1709 // "number" is a 32-bit word. The error code is the low 16 bits, and the
1710 // facility code is the upper 16 bits.
1711 var ieErrorCode = number & 0xffff;
1712 var ieFacilityNumber = (number >> 16) & 0x1fff;
1713
1714 // http://msdn.microsoft.com/en-us/library/aa264975(v=vs.60).aspx
1715 // http://msdn.microsoft.com/en-us/library/ie/1dk3k160(v=vs.94).aspx
1716 if (ieFacilityNumber == 10) {
1717 switch (ieErrorCode) {
1718 case 438:
1719 return saveStackTrace(
1720 new JsNoSuchMethodError('$message (Error $ieErrorCode)', null));
1721 case 445:
1722 case 5007:
1723 return saveStackTrace(
1724 new NullError('$message (Error $ieErrorCode)', null));
1725 }
1726 }
1727 }
1728
1729 if (JS('bool', r'# instanceof TypeError', ex)) {
1730 var match;
1731 // Using JS to give type hints to the compiler to help tree-shaking.
1732 // TODO(ahe): That should be unnecessary due to type inference.
1733 var nsme =
1734 JS('TypeErrorDecoder', '#', TypeErrorDecoder.noSuchMethodPattern);
1735 var notClosure =
1736 JS('TypeErrorDecoder', '#', TypeErrorDecoder.notClosurePattern);
1737 var nullCall =
1738 JS('TypeErrorDecoder', '#', TypeErrorDecoder.nullCallPattern);
1739 var nullLiteralCall =
1740 JS('TypeErrorDecoder', '#', TypeErrorDecoder.nullLiteralCallPattern);
1741 var undefCall =
1742 JS('TypeErrorDecoder', '#', TypeErrorDecoder.undefinedCallPattern);
1743 var undefLiteralCall =
1744 JS('TypeErrorDecoder', '#',
1745 TypeErrorDecoder.undefinedLiteralCallPattern);
1746 var nullProperty =
1747 JS('TypeErrorDecoder', '#', TypeErrorDecoder.nullPropertyPattern);
1748 var nullLiteralProperty =
1749 JS('TypeErrorDecoder', '#',
1750 TypeErrorDecoder.nullLiteralPropertyPattern);
1751 var undefProperty =
1752 JS('TypeErrorDecoder', '#', TypeErrorDecoder.undefinedPropertyPattern);
1753 var undefLiteralProperty =
1754 JS('TypeErrorDecoder', '#',
1755 TypeErrorDecoder.undefinedLiteralPropertyPattern);
1756 if ((match = nsme.matchTypeError(message)) != null) {
1757 return saveStackTrace(new JsNoSuchMethodError(message, match));
1758 } else if ((match = notClosure.matchTypeError(message)) != null) {
1759 // notClosure may match "({c:null}).c()" or "({c:1}).c()", so we
1760 // cannot tell if this an attempt to invoke call on null or a
1761 // non-function object.
1762 // But we do know the method name is "call".
1763 JS('', '#.method = "call"', match);
1764 return saveStackTrace(new JsNoSuchMethodError(message, match));
1765 } else if ((match = nullCall.matchTypeError(message)) != null ||
1766 (match = nullLiteralCall.matchTypeError(message)) != null ||
1767 (match = undefCall.matchTypeError(message)) != null ||
1768 (match = undefLiteralCall.matchTypeError(message)) != null ||
1769 (match = nullProperty.matchTypeError(message)) != null ||
1770 (match = nullLiteralCall.matchTypeError(message)) != null ||
1771 (match = undefProperty.matchTypeError(message)) != null ||
1772 (match = undefLiteralProperty.matchTypeError(message)) != null) {
1773 return saveStackTrace(new NullError(message, match));
1774 }
1775
1776 // If we cannot determine what kind of error this is, we fall back
1777 // to reporting this as a generic error. It's probably better than
1778 // nothing.
1779 return saveStackTrace(
1780 new UnknownJsTypeError(message is String ? message : ''));
1781 }
1782
1783 if (JS('bool', r'# instanceof RangeError', ex)) {
1784 if (message is String && contains(message, 'call stack')) {
1785 return new StackOverflowError();
1786 }
1787
1788 // In general, a RangeError is thrown when trying to pass a number
1789 // as an argument to a function that does not allow a range that
1790 // includes that number.
1791 return saveStackTrace(new ArgumentError());
1792 }
1793
1794 // Check for the Firefox specific stack overflow signal.
1795 if (JS('bool',
1796 r'typeof InternalError == "function" && # instanceof InternalError',
1797 ex)) {
1798 if (message is String && message == 'too much recursion') {
1799 return new StackOverflowError();
1800 }
1801 }
1802
1803 // Just return the exception. We should not wrap it because in case
1804 // the exception comes from the DOM, it is a JavaScript
1805 // object backed by a native Dart class.
1806 return ex;
1807 }
1808
1809 /**
1810 * Called by generated code to fetch the stack trace from an
1811 * exception. Should never return null.
1812 */
1813 StackTrace getTraceFromException(exception) => new _StackTrace(exception);
1814
1815 class _StackTrace implements StackTrace {
1816 var _exception;
1817 String _trace;
1818 _StackTrace(this._exception);
1819
1820 String toString() {
1821 if (_trace != null) return _trace;
1822
1823 String trace;
1824 if (JS('bool', 'typeof # === "object"', _exception)) {
1825 trace = JS("String|Null", r"#.stack", _exception);
1826 }
1827 return _trace = (trace == null) ? '' : trace;
1828 }
1829 }
1830
1831 int objectHashCode(var object) {
1832 if (object == null || JS('bool', "typeof # != 'object'", object)) {
1833 return object.hashCode;
1834 } else {
1835 return Primitives.objectHashCode(object);
1836 }
1837 }
1838
1839 /**
1840 * Called by generated code to build a map literal. [keyValuePairs] is
1841 * a list of key, value, key, value, ..., etc.
1842 */
1843 fillLiteralMap(keyValuePairs, Map result) {
1844 // TODO(johnniwinther): Use JSArray to optimize this code instead of calling
1845 // [getLength] and [getIndex].
1846 int index = 0;
1847 int length = getLength(keyValuePairs);
1848 while (index < length) {
1849 var key = getIndex(keyValuePairs, index++);
1850 var value = getIndex(keyValuePairs, index++);
1851 result[key] = value;
1852 }
1853 return result;
1854 }
1855
1856 invokeClosure(Function closure,
1857 var isolate,
1858 int numberOfArguments,
1859 var arg1,
1860 var arg2,
1861 var arg3,
1862 var arg4) {
1863 if (numberOfArguments == 0) {
1864 return JS_CALL_IN_ISOLATE(isolate, () => closure());
1865 } else if (numberOfArguments == 1) {
1866 return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1));
1867 } else if (numberOfArguments == 2) {
1868 return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1, arg2));
1869 } else if (numberOfArguments == 3) {
1870 return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1, arg2, arg3));
1871 } else if (numberOfArguments == 4) {
1872 return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1, arg2, arg3, arg4));
1873 } else {
1874 throw new Exception(
1875 'Unsupported number of arguments for wrapped closure');
1876 }
1877 }
1878
1879 /**
1880 * Called by generated code to convert a Dart closure to a JS
1881 * closure when the Dart closure is passed to the DOM.
1882 */
1883 convertDartClosureToJS(closure, int arity) {
1884 if (closure == null) return null;
1885 var function = JS('var', r'#.$identity', closure);
1886 if (JS('bool', r'!!#', function)) return function;
1887
1888 // We use $0 and $1 to not clash with variable names used by the
1889 // compiler and/or minifier.
1890 function = JS('var',
1891 '(function(closure, arity, context, invoke) {'
1892 ' return function(a1, a2, a3, a4) {'
1893 ' return invoke(closure, context, arity, a1, a2, a3, a4);'
1894 ' };'
1895 '})(#,#,#,#)',
1896 closure,
1897 arity,
1898 // Capture the current isolate now. Remember that "#"
1899 // in JS is simply textual substitution of compiled
1900 // expressions.
1901 JS_CURRENT_ISOLATE_CONTEXT(),
1902 DART_CLOSURE_TO_JS(invokeClosure));
1903
1904 JS('void', r'#.$identity = #', closure, function);
1905 return function;
1906 }
1907
1908 /**
1909 * Super class for Dart closures.
1910 */
1911 abstract class Closure implements Function {
1912 // TODO(ahe): These constants must be in sync with
1913 // reflection_data_parser.dart.
1914 static const FUNCTION_INDEX = 0;
1915 static const NAME_INDEX = 1;
1916 static const CALL_NAME_INDEX = 2;
1917 static const REQUIRED_PARAMETER_INDEX = 3;
1918 static const OPTIONAL_PARAMETER_INDEX = 4;
1919 static const DEFAULT_ARGUMENTS_INDEX = 5;
1920
1921 /**
1922 * Global counter to prevent reusing function code objects.
1923 *
1924 * V8 will share the underlying function code objects when the same string is
1925 * passed to "new Function". Shared function code objects can lead to
1926 * sub-optimal performance due to polymorhism, and can be prevented by
1927 * ensuring the strings are different.
1928 */
1929 static int functionCounter = 0;
1930
1931 Closure();
1932
1933 /**
1934 * Creates a new closure class for use by implicit getters associated with a
1935 * method.
1936 *
1937 * In other words, creates a tear-off closure.
1938 *
1939 * Called from [closureFromTearOff] as well as from reflection when tearing
1940 * of a method via [:getField:].
1941 *
1942 * This method assumes that [functions] was created by the JavaScript function
1943 * `addStubs` in `reflection_data_parser.dart`. That is, a list of JavaScript
1944 * function objects with properties `$stubName` and `$callName`.
1945 *
1946 * Further assumes that [reflectionInfo] is the end of the array created by
1947 * [dart2js.js_emitter.ContainerBuilder.addMemberMethod] starting with
1948 * required parameter count.
1949 *
1950 * Caution: this function may be called when building constants.
1951 * TODO(ahe): Don't call this function when building constants.
1952 */
1953 static fromTearOff(receiver,
1954 List functions,
1955 List reflectionInfo,
1956 bool isStatic,
1957 jsArguments,
1958 String propertyName) {
1959 JS_EFFECT(() {
1960 BoundClosure.receiverOf(JS('BoundClosure', 'void 0'));
1961 BoundClosure.selfOf(JS('BoundClosure', 'void 0'));
1962 });
1963 // TODO(ahe): All the place below using \$ should be rewritten to go
1964 // through the namer.
1965 var function = JS('', '#[#]', functions, 0);
1966 String name = JS('String|Null', '#.\$stubName', function);
1967 String callName = JS('String|Null', '#.\$callName', function);
1968
1969 JS('', '#.\$reflectionInfo = #', function, reflectionInfo);
1970 ReflectionInfo info = new ReflectionInfo(function);
1971
1972 var functionType = info.functionType;
1973
1974 // function tmp() {};
1975 // tmp.prototype = BC.prototype;
1976 // var proto = new tmp;
1977 // for each computed prototype property:
1978 // proto[property] = ...;
1979 // proto._init = BC;
1980 // var dynClosureConstructor =
1981 // new Function('self', 'target', 'receiver', 'name',
1982 // 'this._init(self, target, receiver, name)');
1983 // proto.constructor = dynClosureConstructor;
1984 // dynClosureConstructor.prototype = proto;
1985 // return dynClosureConstructor;
1986
1987 // We need to create a new subclass of either TearOffClosure or
1988 // BoundClosure. For this, we need to create an object whose prototype is
1989 // the prototype is either TearOffClosure.prototype or
1990 // BoundClosure.prototype, respectively in pseudo JavaScript code. The
1991 // simplest way to access the JavaScript construction function of a Dart
1992 // class is to create an instance and access its constructor property. The
1993 // newly created instance could in theory be used directly as the
1994 // prototype, but it might include additional fields that we don't need.
1995 // So we only use the new instance to access the constructor property and
1996 // use Object.create to create the desired prototype.
1997 var prototype = isStatic
1998 ? JS('TearOffClosure', 'Object.create(#.constructor.prototype)',
1999 new TearOffClosure())
2000 : JS('BoundClosure', 'Object.create(#.constructor.prototype)',
2001 new BoundClosure(null, null, null, null));
2002
2003 JS('', '#.\$initialize = #', prototype, JS('', '#.constructor', prototype));
2004 var constructor = isStatic
2005 ? JS('', 'function(){this.\$initialize()}')
2006 : isCsp
2007 ? JS('', 'function(a,b,c,d) {this.\$initialize(a,b,c,d)}')
2008 : JS('',
2009 'new Function("a","b","c","d",'
2010 '"this.\$initialize(a,b,c,d);"+#)',
2011 functionCounter++);
2012
2013 // It is necessary to set the constructor property, otherwise it will be
2014 // "Object".
2015 JS('', '#.constructor = #', prototype, constructor);
2016
2017 JS('', '#.prototype = #', constructor, prototype);
2018
2019 // Create a closure and "monkey" patch it with call stubs.
2020 var trampoline = function;
2021 var isIntercepted = false;
2022 if (!isStatic) {
2023 if (JS('bool', '#.length == 1', jsArguments)) {
2024 // Intercepted call.
2025 isIntercepted = true;
2026 }
2027 trampoline = forwardCallTo(receiver, function, isIntercepted);
2028 JS('', '#.\$reflectionInfo = #', trampoline, reflectionInfo);
2029 } else {
2030 JS('', '#.\$name = #', prototype, propertyName);
2031 }
2032
2033 var signatureFunction;
2034 if (JS('bool', 'typeof # == "number"', functionType)) {
2035 var metadata = JS_EMBEDDED_GLOBAL('', METADATA);
2036 // It is ok, if the access is inlined into the JS. The access is safe in
2037 // and outside the function. In fact we prefer if there is a textual
2038 // inlining.
2039 signatureFunction =
2040 JS('', '(function(s){return function(){return #[s]}})(#)',
2041 metadata,
2042 functionType);
2043 } else if (!isStatic
2044 && JS('bool', 'typeof # == "function"', functionType)) {
2045 var getReceiver = isIntercepted
2046 ? RAW_DART_FUNCTION_REF(BoundClosure.receiverOf)
2047 : RAW_DART_FUNCTION_REF(BoundClosure.selfOf);
2048 signatureFunction = JS(
2049 '',
2050 'function(f,r){'
2051 'return function(){'
2052 'return f.apply({\$receiver:r(this)},arguments)'
2053 '}'
2054 '}(#,#)', functionType, getReceiver);
2055 } else {
2056 throw 'Error in reflectionInfo.';
2057 }
2058
2059 JS('', '#[#] = #', prototype, JS_SIGNATURE_NAME(), signatureFunction);
2060
2061 JS('', '#[#] = #', prototype, callName, trampoline);
2062 for (int i = 1; i < functions.length; i++) {
2063 var stub = functions[i];
2064 var stubCallName = JS('String|Null', '#.\$callName', stub);
2065 if (stubCallName != null) {
2066 JS('', '#[#] = #', prototype, stubCallName,
2067 isStatic ? stub : forwardCallTo(receiver, stub, isIntercepted));
2068 }
2069 }
2070
2071 JS('', '#["call*"] = #', prototype, trampoline);
2072
2073 return constructor;
2074 }
2075
2076 static cspForwardCall(int arity, bool isSuperCall, String stubName,
2077 function) {
2078 var getSelf = RAW_DART_FUNCTION_REF(BoundClosure.selfOf);
2079 // Handle intercepted stub-names with the default slow case.
2080 if (isSuperCall) arity = -1;
2081 switch (arity) {
2082 case 0:
2083 return JS(
2084 '',
2085 'function(n,S){'
2086 'return function(){'
2087 'return S(this)[n]()'
2088 '}'
2089 '}(#,#)', stubName, getSelf);
2090 case 1:
2091 return JS(
2092 '',
2093 'function(n,S){'
2094 'return function(a){'
2095 'return S(this)[n](a)'
2096 '}'
2097 '}(#,#)', stubName, getSelf);
2098 case 2:
2099 return JS(
2100 '',
2101 'function(n,S){'
2102 'return function(a,b){'
2103 'return S(this)[n](a,b)'
2104 '}'
2105 '}(#,#)', stubName, getSelf);
2106 case 3:
2107 return JS(
2108 '',
2109 'function(n,S){'
2110 'return function(a,b,c){'
2111 'return S(this)[n](a,b,c)'
2112 '}'
2113 '}(#,#)', stubName, getSelf);
2114 case 4:
2115 return JS(
2116 '',
2117 'function(n,S){'
2118 'return function(a,b,c,d){'
2119 'return S(this)[n](a,b,c,d)'
2120 '}'
2121 '}(#,#)', stubName, getSelf);
2122 case 5:
2123 return JS(
2124 '',
2125 'function(n,S){'
2126 'return function(a,b,c,d,e){'
2127 'return S(this)[n](a,b,c,d,e)'
2128 '}'
2129 '}(#,#)', stubName, getSelf);
2130 default:
2131 return JS(
2132 '',
2133 'function(f,s){'
2134 'return function(){'
2135 'return f.apply(s(this),arguments)'
2136 '}'
2137 '}(#,#)', function, getSelf);
2138 }
2139 }
2140
2141 static bool get isCsp => JS('bool', 'typeof dart_precompiled == "function"');
2142
2143 static forwardCallTo(receiver, function, bool isIntercepted) {
2144 if (isIntercepted) return forwardInterceptedCallTo(receiver, function);
2145 String stubName = JS('String|Null', '#.\$stubName', function);
2146 int arity = JS('int', '#.length', function);
2147 var lookedUpFunction = JS("", "#[#]", receiver, stubName);
2148 // The receiver[stubName] may not be equal to the function if we try to
2149 // forward to a super-method. Especially when we create a bound closure
2150 // of a super-call we need to make sure that we don't forward back to the
2151 // dynamically looked up function.
2152 bool isSuperCall = !identical(function, lookedUpFunction);
2153
2154 if (isCsp || isSuperCall || arity >= 27) {
2155 return cspForwardCall(arity, isSuperCall, stubName, function);
2156 }
2157
2158 if (arity == 0) {
2159 return JS(
2160 '',
2161 '(new Function(#))()',
2162 'return function(){'
2163 'return this.${BoundClosure.selfFieldName()}.$stubName();'
2164 '${functionCounter++}'
2165 '}');
2166 }
2167 assert (1 <= arity && arity < 27);
2168 String arguments = JS(
2169 'String',
2170 '"abcdefghijklmnopqrstuvwxyz".split("").splice(0,#).join(",")',
2171 arity);
2172 return JS(
2173 '',
2174 '(new Function(#))()',
2175 'return function($arguments){'
2176 'return this.${BoundClosure.selfFieldName()}.$stubName($arguments);'
2177 '${functionCounter++}'
2178 '}');
2179 }
2180
2181 static cspForwardInterceptedCall(int arity, bool isSuperCall,
2182 String name, function) {
2183 var getSelf = RAW_DART_FUNCTION_REF(BoundClosure.selfOf);
2184 var getReceiver = RAW_DART_FUNCTION_REF(BoundClosure.receiverOf);
2185 // Handle intercepted stub-names with the default slow case.
2186 if (isSuperCall) arity = -1;
2187 switch (arity) {
2188 case 0:
2189 // Intercepted functions always takes at least one argument (the
2190 // receiver).
2191 throw new RuntimeError('Intercepted function with no arguments.');
2192 case 1:
2193 return JS(
2194 '',
2195 'function(n,s,r){'
2196 'return function(){'
2197 'return s(this)[n](r(this))'
2198 '}'
2199 '}(#,#,#)', name, getSelf, getReceiver);
2200 case 2:
2201 return JS(
2202 '',
2203 'function(n,s,r){'
2204 'return function(a){'
2205 'return s(this)[n](r(this),a)'
2206 '}'
2207 '}(#,#,#)', name, getSelf, getReceiver);
2208 case 3:
2209 return JS(
2210 '',
2211 'function(n,s,r){'
2212 'return function(a,b){'
2213 'return s(this)[n](r(this),a,b)'
2214 '}'
2215 '}(#,#,#)', name, getSelf, getReceiver);
2216 case 4:
2217 return JS(
2218 '',
2219 'function(n,s,r){'
2220 'return function(a,b,c){'
2221 'return s(this)[n](r(this),a,b,c)'
2222 '}'
2223 '}(#,#,#)', name, getSelf, getReceiver);
2224 case 5:
2225 return JS(
2226 '',
2227 'function(n,s,r){'
2228 'return function(a,b,c,d){'
2229 'return s(this)[n](r(this),a,b,c,d)'
2230 '}'
2231 '}(#,#,#)', name, getSelf, getReceiver);
2232 case 6:
2233 return JS(
2234 '',
2235 'function(n,s,r){'
2236 'return function(a,b,c,d,e){'
2237 'return s(this)[n](r(this),a,b,c,d,e)'
2238 '}'
2239 '}(#,#,#)', name, getSelf, getReceiver);
2240 default:
2241 return JS(
2242 '',
2243 'function(f,s,r,a){'
2244 'return function(){'
2245 'a=[r(this)];'
2246 'Array.prototype.push.apply(a,arguments);'
2247 'return f.apply(s(this),a)'
2248 '}'
2249 '}(#,#,#)', function, getSelf, getReceiver);
2250 }
2251 }
2252
2253 static forwardInterceptedCallTo(receiver, function) {
2254 String selfField = BoundClosure.selfFieldName();
2255 String receiverField = BoundClosure.receiverFieldName();
2256 String stubName = JS('String|Null', '#.\$stubName', function);
2257 int arity = JS('int', '#.length', function);
2258 bool isCsp = JS('bool', 'typeof dart_precompiled == "function"');
2259 var lookedUpFunction = JS("", "#[#]", receiver, stubName);
2260 // The receiver[stubName] may not be equal to the function if we try to
2261 // forward to a super-method. Especially when we create a bound closure
2262 // of a super-call we need to make sure that we don't forward back to the
2263 // dynamically looked up function.
2264 bool isSuperCall = !identical(function, lookedUpFunction);
2265
2266 if (isCsp || isSuperCall || arity >= 28) {
2267 return cspForwardInterceptedCall(arity, isSuperCall, stubName,
2268 function);
2269 }
2270 if (arity == 1) {
2271 return JS(
2272 '',
2273 '(new Function(#))()',
2274 'return function(){'
2275 'return this.$selfField.$stubName(this.$receiverField);'
2276 '${functionCounter++}'
2277 '}');
2278 }
2279 assert(1 < arity && arity < 28);
2280 String arguments = JS(
2281 'String',
2282 '"abcdefghijklmnopqrstuvwxyz".split("").splice(0,#).join(",")',
2283 arity - 1);
2284 return JS(
2285 '',
2286 '(new Function(#))()',
2287 'return function($arguments){'
2288 'return this.$selfField.$stubName(this.$receiverField, $arguments);'
2289 '${functionCounter++}'
2290 '}');
2291 }
2292
2293 // The backend adds a special getter of the form
2294 //
2295 // Closure get call => this;
2296 //
2297 // to allow tearing off a closure from itself. We do this magically in the
2298 // backend rather than simply adding it here, as we do not want this getter
2299 // to be visible to resolution and the generation of extra stubs.
2300
2301 String toString() => "Closure";
2302 }
2303
2304 /// Called from implicit method getter (aka tear-off).
2305 closureFromTearOff(receiver,
2306 functions,
2307 reflectionInfo,
2308 isStatic,
2309 jsArguments,
2310 name) {
2311 return Closure.fromTearOff(
2312 receiver,
2313 JSArray.markFixedList(functions),
2314 JSArray.markFixedList(reflectionInfo),
2315 JS('bool', '!!#', isStatic),
2316 jsArguments,
2317 JS('String', '#', name));
2318 }
2319
2320 /// Represents an implicit closure of a function.
2321 class TearOffClosure extends Closure {
2322 }
2323
2324 /// Represents a 'tear-off' closure, that is an instance method bound
2325 /// to a specific receiver (instance).
2326 class BoundClosure extends TearOffClosure {
2327 /// The receiver or interceptor.
2328 // TODO(ahe): This could just be the interceptor, we always know if
2329 // we need the interceptor when generating the call method.
2330 final _self;
2331
2332 /// The method.
2333 final _target;
2334
2335 /// The receiver. Null if [_self] is not an interceptor.
2336 final _receiver;
2337
2338 /// The name of the function. Only used by the mirror system.
2339 final String _name;
2340
2341 BoundClosure(this._self, this._target, this._receiver, this._name);
2342
2343 bool operator==(other) {
2344 if (identical(this, other)) return true;
2345 if (other is! BoundClosure) return false;
2346 return JS('bool', '# === # && # === # && # === #',
2347 _self, other._self,
2348 _target, other._target,
2349 _receiver, other._receiver);
2350 }
2351
2352 int get hashCode {
2353 int receiverHashCode;
2354 if (_receiver == null) {
2355 // A bound closure on a regular Dart object, just use the
2356 // identity hash code.
2357 receiverHashCode = Primitives.objectHashCode(_self);
2358 } else if (JS('String', 'typeof #', _receiver) != 'object') {
2359 // A bound closure on a primitive JavaScript type. We
2360 // use the hashCode method we define for those primitive types.
2361 receiverHashCode = _receiver.hashCode;
2362 } else {
2363 // A bound closure on an intercepted native class, just use the
2364 // identity hash code.
2365 receiverHashCode = Primitives.objectHashCode(_receiver);
2366 }
2367 return receiverHashCode ^ Primitives.objectHashCode(_target);
2368 }
2369
2370 @NoInline()
2371 static selfOf(BoundClosure closure) => closure._self;
2372
2373 static targetOf(BoundClosure closure) => closure._target;
2374
2375 @NoInline()
2376 static receiverOf(BoundClosure closure) => closure._receiver;
2377
2378 static nameOf(BoundClosure closure) => closure._name;
2379
2380 static String selfFieldNameCache;
2381
2382 static String selfFieldName() {
2383 if (selfFieldNameCache == null) {
2384 selfFieldNameCache = computeFieldNamed('self');
2385 }
2386 return selfFieldNameCache;
2387 }
2388
2389 static String receiverFieldNameCache;
2390
2391 static String receiverFieldName() {
2392 if (receiverFieldNameCache == null) {
2393 receiverFieldNameCache = computeFieldNamed('receiver');
2394 }
2395 return receiverFieldNameCache;
2396 }
2397
2398 @NoInline() @NoSideEffects()
2399 static String computeFieldNamed(String fieldName) {
2400 var template = new BoundClosure('self', 'target', 'receiver', 'name');
2401 var names = JSArray.markFixedList(
2402 JS('', 'Object.getOwnPropertyNames(#)', template));
2403 for (int i = 0; i < names.length; i++) {
2404 var name = names[i];
2405 if (JS('bool', '#[#] === #', template, name, fieldName)) {
2406 return JS('String', '#', name);
2407 }
2408 }
2409 }
2410 }
2411
2412 bool jsHasOwnProperty(var jsObject, String property) {
2413 return JS('bool', r'#.hasOwnProperty(#)', jsObject, property);
2414 }
2415
2416 jsPropertyAccess(var jsObject, String property) {
2417 return JS('var', r'#[#]', jsObject, property);
2418 }
2419
2420 /**
2421 * Called at the end of unaborted switch cases to get the singleton
2422 * FallThroughError exception that will be thrown.
2423 */
2424 getFallThroughError() => new FallThroughErrorImplementation();
2425
2426 /**
2427 * A metadata annotation describing the types instantiated by a native element.
2428 *
2429 * The annotation is valid on a native method and a field of a native class.
2430 *
2431 * By default, a field of a native class is seen as an instantiation point for
2432 * all native classes that are a subtype of the field's type, and a native
2433 * method is seen as an instantiation point fo all native classes that are a
2434 * subtype of the method's return type, or the argument types of the declared
2435 * type of the method's callback parameter.
2436 *
2437 * An @[Creates] annotation overrides the default set of instantiated types. If
2438 * one or more @[Creates] annotations are present, the type of the native
2439 * element is ignored, and the union of @[Creates] annotations is used instead.
2440 * The names in the strings are resolved and the program will fail to compile
2441 * with dart2js if they do not name types.
2442 *
2443 * The argument to [Creates] is a string. The string is parsed as the names of
2444 * one or more types, separated by vertical bars `|`. There are some special
2445 * names:
2446 *
2447 * * `=Object`. This means 'exactly Object', which is a plain JavaScript object
2448 * with properties and none of the subtypes of Object.
2449 *
2450 * Example: we may know that a method always returns a specific implementation:
2451 *
2452 * @Creates('_NodeList')
2453 * List<Node> getElementsByTagName(String tag) native;
2454 *
2455 * Useful trick: A method can be marked as not instantiating any native classes
2456 * with the annotation `@Creates('Null')`. This is useful for fields on native
2457 * classes that are used only in Dart code.
2458 *
2459 * @Creates('Null')
2460 * var _cachedFoo;
2461 */
2462 class Creates {
2463 final String types;
2464 const Creates(this.types);
2465 }
2466
2467 /**
2468 * A metadata annotation describing the types returned or yielded by a native
2469 * element.
2470 *
2471 * The annotation is valid on a native method and a field of a native class.
2472 *
2473 * By default, a native method or field is seen as returning or yielding all
2474 * subtypes if the method return type or field type. This annotation allows a
2475 * more precise set of types to be specified.
2476 *
2477 * See [Creates] for the syntax of the argument.
2478 *
2479 * Example: IndexedDB keys are numbers, strings and JavaScript Arrays of keys.
2480 *
2481 * @Returns('String|num|JSExtendableArray')
2482 * dynamic key;
2483 *
2484 * // Equivalent:
2485 * @Returns('String') @Returns('num') @Returns('JSExtendableArray')
2486 * dynamic key;
2487 */
2488 class Returns {
2489 final String types;
2490 const Returns(this.types);
2491 }
2492
2493 /**
2494 * A metadata annotation placed on native methods and fields of native classes
2495 * to specify the JavaScript name.
2496 *
2497 * This example declares a Dart field + getter + setter called `$dom_title` that
2498 * corresponds to the JavaScript property `title`.
2499 *
2500 * class Docmument native "*Foo" {
2501 * @JSName('title')
2502 * String $dom_title;
2503 * }
2504 */
2505 class JSName {
2506 final String name;
2507 const JSName(this.name);
2508 }
2509
2510 /**
2511 * The following methods are called by the runtime to implement
2512 * checked mode and casts. We specialize each primitive type (eg int, bool), and
2513 * use the compiler's convention to do is-checks on regular objects.
2514 */
2515 boolConversionCheck(value) {
2516 if (value is bool) return value;
2517 // One of the following checks will always fail.
2518 boolTypeCheck(value);
2519 assert(value != null);
2520 return false;
2521 }
2522
2523 stringTypeCheck(value) {
2524 if (value == null) return value;
2525 if (value is String) return value;
2526 throw new TypeErrorImplementation(value, 'String');
2527 }
2528
2529 stringTypeCast(value) {
2530 if (value is String || value == null) return value;
2531 // TODO(lrn): When reified types are available, pass value.class and String.
2532 throw new CastErrorImplementation(
2533 Primitives.objectTypeName(value), 'String');
2534 }
2535
2536 doubleTypeCheck(value) {
2537 if (value == null) return value;
2538 if (value is double) return value;
2539 throw new TypeErrorImplementation(value, 'double');
2540 }
2541
2542 doubleTypeCast(value) {
2543 if (value is double || value == null) return value;
2544 throw new CastErrorImplementation(
2545 Primitives.objectTypeName(value), 'double');
2546 }
2547
2548 numTypeCheck(value) {
2549 if (value == null) return value;
2550 if (value is num) return value;
2551 throw new TypeErrorImplementation(value, 'num');
2552 }
2553
2554 numTypeCast(value) {
2555 if (value is num || value == null) return value;
2556 throw new CastErrorImplementation(
2557 Primitives.objectTypeName(value), 'num');
2558 }
2559
2560 boolTypeCheck(value) {
2561 if (value == null) return value;
2562 if (value is bool) return value;
2563 throw new TypeErrorImplementation(value, 'bool');
2564 }
2565
2566 boolTypeCast(value) {
2567 if (value is bool || value == null) return value;
2568 throw new CastErrorImplementation(
2569 Primitives.objectTypeName(value), 'bool');
2570 }
2571
2572 intTypeCheck(value) {
2573 if (value == null) return value;
2574 if (value is int) return value;
2575 throw new TypeErrorImplementation(value, 'int');
2576 }
2577
2578 intTypeCast(value) {
2579 if (value is int || value == null) return value;
2580 throw new CastErrorImplementation(
2581 Primitives.objectTypeName(value), 'int');
2582 }
2583
2584 void propertyTypeError(value, property) {
2585 // Cuts the property name to the class name.
2586 String name = property.substring(3, property.length);
2587 throw new TypeErrorImplementation(value, name);
2588 }
2589
2590 void propertyTypeCastError(value, property) {
2591 // Cuts the property name to the class name.
2592 String actualType = Primitives.objectTypeName(value);
2593 String expectedType = property.substring(3, property.length);
2594 throw new CastErrorImplementation(actualType, expectedType);
2595 }
2596
2597 /**
2598 * For types that are not supertypes of native (eg DOM) types,
2599 * we emit a simple property check to check that an object implements
2600 * that type.
2601 */
2602 propertyTypeCheck(value, property) {
2603 if (value == null) return value;
2604 if (JS('bool', '!!#[#]', value, property)) return value;
2605 propertyTypeError(value, property);
2606 }
2607
2608 /**
2609 * For types that are not supertypes of native (eg DOM) types,
2610 * we emit a simple property check to check that an object implements
2611 * that type.
2612 */
2613 propertyTypeCast(value, property) {
2614 if (value == null || JS('bool', '!!#[#]', value, property)) return value;
2615 propertyTypeCastError(value, property);
2616 }
2617
2618 /**
2619 * For types that are supertypes of native (eg DOM) types, we use the
2620 * interceptor for the class because we cannot add a JS property to the
2621 * prototype at load time.
2622 */
2623 interceptedTypeCheck(value, property) {
2624 if (value == null) return value;
2625 if ((identical(JS('String', 'typeof #', value), 'object'))
2626 && JS('bool', '#[#]', getInterceptor(value), property)) {
2627 return value;
2628 }
2629 propertyTypeError(value, property);
2630 }
2631
2632 /**
2633 * For types that are supertypes of native (eg DOM) types, we use the
2634 * interceptor for the class because we cannot add a JS property to the
2635 * prototype at load time.
2636 */
2637 interceptedTypeCast(value, property) {
2638 if (value == null
2639 || ((JS('bool', 'typeof # === "object"', value))
2640 && JS('bool', '#[#]', getInterceptor(value), property))) {
2641 return value;
2642 }
2643 propertyTypeCastError(value, property);
2644 }
2645
2646 /**
2647 * Specialization of the type check for num and String and their
2648 * supertype since [value] can be a JS primitive.
2649 */
2650 numberOrStringSuperTypeCheck(value, property) {
2651 if (value == null) return value;
2652 if (value is String) return value;
2653 if (value is num) return value;
2654 if (JS('bool', '!!#[#]', value, property)) return value;
2655 propertyTypeError(value, property);
2656 }
2657
2658 numberOrStringSuperTypeCast(value, property) {
2659 if (value is String) return value;
2660 if (value is num) return value;
2661 return propertyTypeCast(value, property);
2662 }
2663
2664 numberOrStringSuperNativeTypeCheck(value, property) {
2665 if (value == null) return value;
2666 if (value is String) return value;
2667 if (value is num) return value;
2668 if (JS('bool', '#[#]', getInterceptor(value), property)) return value;
2669 propertyTypeError(value, property);
2670 }
2671
2672 numberOrStringSuperNativeTypeCast(value, property) {
2673 if (value == null) return value;
2674 if (value is String) return value;
2675 if (value is num) return value;
2676 if (JS('bool', '#[#]', getInterceptor(value), property)) return value;
2677 propertyTypeCastError(value, property);
2678 }
2679
2680 /**
2681 * Specialization of the type check for String and its supertype
2682 * since [value] can be a JS primitive.
2683 */
2684 stringSuperTypeCheck(value, property) {
2685 if (value == null) return value;
2686 if (value is String) return value;
2687 if (JS('bool', '!!#[#]', value, property)) return value;
2688 propertyTypeError(value, property);
2689 }
2690
2691 stringSuperTypeCast(value, property) {
2692 if (value is String) return value;
2693 return propertyTypeCast(value, property);
2694 }
2695
2696 stringSuperNativeTypeCheck(value, property) {
2697 if (value == null) return value;
2698 if (value is String) return value;
2699 if (JS('bool', '#[#]', getInterceptor(value), property)) return value;
2700 propertyTypeError(value, property);
2701 }
2702
2703 stringSuperNativeTypeCast(value, property) {
2704 if (value is String || value == null) return value;
2705 if (JS('bool', '#[#]', getInterceptor(value), property)) return value;
2706 propertyTypeCastError(value, property);
2707 }
2708
2709 /**
2710 * Specialization of the type check for List and its supertypes,
2711 * since [value] can be a JS array.
2712 */
2713 listTypeCheck(value) {
2714 if (value == null) return value;
2715 if (value is List) return value;
2716 throw new TypeErrorImplementation(value, 'List');
2717 }
2718
2719 listTypeCast(value) {
2720 if (value is List || value == null) return value;
2721 throw new CastErrorImplementation(
2722 Primitives.objectTypeName(value), 'List');
2723 }
2724
2725 listSuperTypeCheck(value, property) {
2726 if (value == null) return value;
2727 if (value is List) return value;
2728 if (JS('bool', '!!#[#]', value, property)) return value;
2729 propertyTypeError(value, property);
2730 }
2731
2732 listSuperTypeCast(value, property) {
2733 if (value is List) return value;
2734 return propertyTypeCast(value, property);
2735 }
2736
2737 listSuperNativeTypeCheck(value, property) {
2738 if (value == null) return value;
2739 if (value is List) return value;
2740 if (JS('bool', '#[#]', getInterceptor(value), property)) return value;
2741 propertyTypeError(value, property);
2742 }
2743
2744 listSuperNativeTypeCast(value, property) {
2745 if (value is List || value == null) return value;
2746 if (JS('bool', '#[#]', getInterceptor(value), property)) return value;
2747 propertyTypeCastError(value, property);
2748 }
2749
2750 voidTypeCheck(value) {
2751 if (value == null) return value;
2752 throw new TypeErrorImplementation(value, 'void');
2753 }
2754
2755 checkMalformedType(value, message) {
2756 if (value == null) return value;
2757 throw new TypeErrorImplementation.fromMessage(message);
2758 }
2759
2760 @NoInline()
2761 void checkDeferredIsLoaded(String loadId, String uri) {
2762 if (!_loadedLibraries.contains(loadId)) {
2763 throw new DeferredNotLoadedError(uri);
2764 }
2765 }
2766
2767 /**
2768 * Special interface recognized by the compiler and implemented by DOM
2769 * objects that support integer indexing. This interface is not
2770 * visible to anyone, and is only injected into special libraries.
2771 */
2772 abstract class JavaScriptIndexingBehavior extends JSMutableIndexable {
2773 }
2774
2775 // TODO(lrn): These exceptions should be implemented in core.
2776 // When they are, remove the 'Implementation' here.
2777
2778 /** Thrown by type assertions that fail. */
2779 class TypeErrorImplementation extends Error implements TypeError {
2780 final String message;
2781
2782 /**
2783 * Normal type error caused by a failed subtype test.
2784 */
2785 TypeErrorImplementation(Object value, String type)
2786 : message = "type '${Primitives.objectTypeName(value)}' is not a subtype "
2787 "of type '$type'";
2788
2789 TypeErrorImplementation.fromMessage(String this.message);
2790
2791 String toString() => message;
2792 }
2793
2794 /** Thrown by the 'as' operator if the cast isn't valid. */
2795 class CastErrorImplementation extends Error implements CastError {
2796 // TODO(lrn): Rename to CastError (and move implementation into core).
2797 final String message;
2798
2799 /**
2800 * Normal cast error caused by a failed type cast.
2801 */
2802 CastErrorImplementation(Object actualType, Object expectedType)
2803 : message = "CastError: Casting value of type $actualType to"
2804 " incompatible type $expectedType";
2805
2806 String toString() => message;
2807 }
2808
2809 class FallThroughErrorImplementation extends FallThroughError {
2810 FallThroughErrorImplementation();
2811 String toString() => "Switch case fall-through.";
2812 }
2813
2814 /**
2815 * Helper function for implementing asserts. The compiler treats this specially.
2816 */
2817 void assertHelper(condition) {
2818 // Do a bool check first because it is common and faster than 'is Function'.
2819 if (condition is !bool) {
2820 if (condition is Function) condition = condition();
2821 if (condition is !bool) {
2822 throw new TypeErrorImplementation(condition, 'bool');
2823 }
2824 }
2825 // Compare to true to avoid boolean conversion check in checked
2826 // mode.
2827 if (true != condition) throw new AssertionError();
2828 }
2829
2830 /**
2831 * Called by generated code when a method that must be statically
2832 * resolved cannot be found.
2833 */
2834 void throwNoSuchMethod(obj, name, arguments, expectedArgumentNames) {
2835 Symbol memberName = new _symbol_dev.Symbol.unvalidated(name);
2836 throw new NoSuchMethodError(obj, memberName, arguments,
2837 new Map<Symbol, dynamic>(),
2838 expectedArgumentNames);
2839 }
2840
2841 /**
2842 * Called by generated code when a static field's initializer references the
2843 * field that is currently being initialized.
2844 */
2845 void throwCyclicInit(String staticName) {
2846 throw new CyclicInitializationError(
2847 "Cyclic initialization for static $staticName");
2848 }
2849
2850 /**
2851 * Error thrown when a runtime error occurs.
2852 */
2853 class RuntimeError extends Error {
2854 final message;
2855 RuntimeError(this.message);
2856 String toString() => "RuntimeError: $message";
2857 }
2858
2859 class DeferredNotLoadedError extends Error implements NoSuchMethodError {
2860 String libraryName;
2861
2862 DeferredNotLoadedError(this.libraryName);
2863
2864 String toString() {
2865 return "Deferred library $libraryName was not loaded.";
2866 }
2867 }
2868
2869 abstract class RuntimeType {
2870 const RuntimeType();
2871
2872 toRti();
2873 }
2874
2875 class RuntimeFunctionType extends RuntimeType {
2876 final RuntimeType returnType;
2877 final List<RuntimeType> parameterTypes;
2878 final List<RuntimeType> optionalParameterTypes;
2879 final namedParameters;
2880
2881 static var /* bool */ inAssert = false;
2882
2883 RuntimeFunctionType(this.returnType,
2884 this.parameterTypes,
2885 this.optionalParameterTypes,
2886 this.namedParameters);
2887
2888 bool get isVoid => returnType is VoidRuntimeType;
2889
2890 /// Called from generated code. [expression] is a Dart object and this method
2891 /// returns true if [this] is a supertype of [expression].
2892 @NoInline() @NoSideEffects()
2893 bool _isTest(expression) {
2894 var functionTypeObject = _extractFunctionTypeObjectFrom(expression);
2895 return functionTypeObject == null
2896 ? false
2897 : isFunctionSubtype(functionTypeObject, toRti());
2898 }
2899
2900 @NoInline() @NoSideEffects()
2901 _asCheck(expression) {
2902 // Type inferrer doesn't think this is called with dynamic arguments.
2903 return _check(JS('', '#', expression), true);
2904 }
2905
2906 @NoInline() @NoSideEffects()
2907 _assertCheck(expression) {
2908 if (inAssert) return null;
2909 inAssert = true; // Don't try to check this library itself.
2910 try {
2911 // Type inferrer don't think this is called with dynamic arguments.
2912 return _check(JS('', '#', expression), false);
2913 } finally {
2914 inAssert = false;
2915 }
2916 }
2917
2918 _check(expression, bool isCast) {
2919 if (expression == null) return null;
2920 if (_isTest(expression)) return expression;
2921
2922 var self = new FunctionTypeInfoDecoderRing(toRti()).toString();
2923 if (isCast) {
2924 var functionTypeObject = _extractFunctionTypeObjectFrom(expression);
2925 var pretty;
2926 if (functionTypeObject != null) {
2927 pretty = new FunctionTypeInfoDecoderRing(functionTypeObject).toString();
2928 } else {
2929 pretty = Primitives.objectTypeName(expression);
2930 }
2931 throw new CastErrorImplementation(pretty, self);
2932 } else {
2933 // TODO(ahe): Pass "pretty" function-type to TypeErrorImplementation?
2934 throw new TypeErrorImplementation(expression, self);
2935 }
2936 }
2937
2938 _extractFunctionTypeObjectFrom(o) {
2939 var interceptor = getInterceptor(o);
2940 return JS('bool', '# in #', JS_SIGNATURE_NAME(), interceptor)
2941 ? JS('', '#[#]()', interceptor, JS_SIGNATURE_NAME())
2942 : null;
2943 }
2944
2945 toRti() {
2946 var result = JS('=Object', '{ #: "dynafunc" }', JS_FUNCTION_TYPE_TAG());
2947 if (isVoid) {
2948 JS('', '#[#] = true', result, JS_FUNCTION_TYPE_VOID_RETURN_TAG());
2949 } else {
2950 if (returnType is! DynamicRuntimeType) {
2951 JS('', '#[#] = #', result, JS_FUNCTION_TYPE_RETURN_TYPE_TAG(),
2952 returnType.toRti());
2953 }
2954 }
2955 if (parameterTypes != null && !parameterTypes.isEmpty) {
2956 JS('', '#[#] = #', result, JS_FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG(),
2957 listToRti(parameterTypes));
2958 }
2959
2960 if (optionalParameterTypes != null && !optionalParameterTypes.isEmpty) {
2961 JS('', '#[#] = #', result, JS_FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG(),
2962 listToRti(optionalParameterTypes));
2963 }
2964
2965 if (namedParameters != null) {
2966 var namedRti = JS('=Object', 'Object.create(null)');
2967 var keys = extractKeys(namedParameters);
2968 for (var i = 0; i < keys.length; i++) {
2969 var name = keys[i];
2970 var rti = JS('', '#[#]', namedParameters, name).toRti();
2971 JS('', '#[#] = #', namedRti, name, rti);
2972 }
2973 JS('', '#[#] = #', result, JS_FUNCTION_TYPE_NAMED_PARAMETERS_TAG(),
2974 namedRti);
2975 }
2976
2977 return result;
2978 }
2979
2980 static listToRti(list) {
2981 list = JS('JSFixedArray', '#', list);
2982 var result = JS('JSExtendableArray', '[]');
2983 for (var i = 0; i < list.length; i++) {
2984 JS('', '#.push(#)', result, list[i].toRti());
2985 }
2986 return result;
2987 }
2988
2989 String toString() {
2990 String result = '(';
2991 bool needsComma = false;
2992 if (parameterTypes != null) {
2993 for (var i = 0; i < parameterTypes.length; i++) {
2994 RuntimeType type = parameterTypes[i];
2995 if (needsComma) result += ', ';
2996 result += '$type';
2997 needsComma = true;
2998 }
2999 }
3000 if (optionalParameterTypes != null && !optionalParameterTypes.isEmpty) {
3001 if (needsComma) result += ', ';
3002 needsComma = false;
3003 result += '[';
3004 for (var i = 0; i < optionalParameterTypes.length; i++) {
3005 RuntimeType type = optionalParameterTypes[i];
3006 if (needsComma) result += ', ';
3007 result += '$type';
3008 needsComma = true;
3009 }
3010 result += ']';
3011 } else if (namedParameters != null) {
3012 if (needsComma) result += ', ';
3013 needsComma = false;
3014 result += '{';
3015 var keys = extractKeys(namedParameters);
3016 for (var i = 0; i < keys.length; i++) {
3017 var name = keys[i];
3018 if (needsComma) result += ', ';
3019 var rti = JS('', '#[#]', namedParameters, name).toRti();
3020 result += '$rti ${JS("String", "#", name)}';
3021 needsComma = true;
3022 }
3023 result += '}';
3024 }
3025
3026 result += ') -> $returnType';
3027 return result;
3028 }
3029 }
3030
3031 RuntimeFunctionType buildFunctionType(returnType,
3032 parameterTypes,
3033 optionalParameterTypes) {
3034 return new RuntimeFunctionType(
3035 returnType,
3036 parameterTypes,
3037 optionalParameterTypes,
3038 null);
3039 }
3040
3041 RuntimeFunctionType buildNamedFunctionType(returnType,
3042 parameterTypes,
3043 namedParameters) {
3044 return new RuntimeFunctionType(
3045 returnType,
3046 parameterTypes,
3047 null,
3048 namedParameters);
3049 }
3050
3051 RuntimeType buildInterfaceType(rti, typeArguments) {
3052 String name = JS('String|Null', r'#.name', rti);
3053 if (typeArguments == null || typeArguments.isEmpty) {
3054 return new RuntimeTypePlain(name);
3055 }
3056 return new RuntimeTypeGeneric(name, typeArguments, null);
3057 }
3058
3059 class DynamicRuntimeType extends RuntimeType {
3060 const DynamicRuntimeType();
3061
3062 String toString() => 'dynamic';
3063
3064 toRti() => null;
3065 }
3066
3067 RuntimeType getDynamicRuntimeType() => const DynamicRuntimeType();
3068
3069 class VoidRuntimeType extends RuntimeType {
3070 const VoidRuntimeType();
3071
3072 String toString() => 'void';
3073
3074 toRti() => throw 'internal error';
3075 }
3076
3077 RuntimeType getVoidRuntimeType() => const VoidRuntimeType();
3078
3079 /**
3080 * Meta helper for function type tests.
3081 *
3082 * A "meta helper" is a helper function that is never called but simulates how
3083 * generated code behaves as far as resolution and type inference is concerned.
3084 */
3085 functionTypeTestMetaHelper() {
3086 var dyn = JS('', 'x');
3087 var dyn2 = JS('', 'x');
3088 List fixedListOrNull = JS('JSFixedArray|Null', 'x');
3089 List fixedListOrNull2 = JS('JSFixedArray|Null', 'x');
3090 List fixedList = JS('JSFixedArray', 'x');
3091 // TODO(ahe): Can we use [UnknownJavaScriptObject] below?
3092 var /* UnknownJavaScriptObject */ jsObject = JS('=Object', 'x');
3093
3094 buildFunctionType(dyn, fixedListOrNull, fixedListOrNull2);
3095 buildNamedFunctionType(dyn, fixedList, jsObject);
3096 buildInterfaceType(dyn, fixedListOrNull);
3097 getDynamicRuntimeType();
3098 getVoidRuntimeType();
3099 convertRtiToRuntimeType(dyn);
3100 dyn._isTest(dyn2);
3101 dyn._asCheck(dyn2);
3102 dyn._assertCheck(dyn2);
3103 }
3104
3105 RuntimeType convertRtiToRuntimeType(rti) {
3106 if (rti == null) {
3107 return getDynamicRuntimeType();
3108 } else if (JS('bool', 'typeof # == "function"', rti)) {
3109 return new RuntimeTypePlain(JS('String', r'rti.name'));
3110 } else if (JS('bool', '#.constructor == Array', rti)) {
3111 List list = JS('JSFixedArray', '#', rti);
3112 String name = JS('String', r'#.name', list[0]);
3113 List arguments = [];
3114 for (int i = 1; i < list.length; i++) {
3115 arguments.add(convertRtiToRuntimeType(list[i]));
3116 }
3117 return new RuntimeTypeGeneric(name, arguments, rti);
3118 } else if (JS('bool', '"func" in #', rti)) {
3119 return new FunctionTypeInfoDecoderRing(rti).toRuntimeType();
3120 } else {
3121 throw new RuntimeError(
3122 "Cannot convert "
3123 "'${JS('String', 'JSON.stringify(#)', rti)}' to RuntimeType.");
3124 }
3125 }
3126
3127 class RuntimeTypePlain extends RuntimeType {
3128 final String name;
3129
3130 RuntimeTypePlain(this.name);
3131
3132 toRti() {
3133 var allClasses = JS_EMBEDDED_GLOBAL('', ALL_CLASSES);
3134 var rti = JS('', '#[#]', allClasses, name);
3135 if (rti == null) throw "no type for '$name'";
3136 return rti;
3137 }
3138
3139 String toString() => name;
3140 }
3141
3142 class RuntimeTypeGeneric extends RuntimeType {
3143 final String name;
3144 final List<RuntimeType> arguments;
3145 var rti;
3146
3147 RuntimeTypeGeneric(this.name, this.arguments, this.rti);
3148
3149 toRti() {
3150 if (rti != null) return rti;
3151 var allClasses = JS_EMBEDDED_GLOBAL('', ALL_CLASSES);
3152 var result = JS('JSExtendableArray', '[#[#]]', allClasses, name);
3153 if (result[0] == null) {
3154 throw "no type for '$name<...>'";
3155 }
3156 for (RuntimeType argument in arguments) {
3157 JS('', '#.push(#)', result, argument.toRti());
3158 }
3159 return rti = result;
3160 }
3161
3162 String toString() => '$name<${arguments.join(", ")}>';
3163 }
3164
3165 class FunctionTypeInfoDecoderRing {
3166 final _typeData;
3167 String _cachedToString;
3168
3169 FunctionTypeInfoDecoderRing(this._typeData);
3170
3171 bool get _hasReturnType => JS('bool', '"ret" in #', _typeData);
3172 get _returnType => JS('', '#.ret', _typeData);
3173
3174 bool get _isVoid => JS('bool', '!!#.void', _typeData);
3175
3176 bool get _hasArguments => JS('bool', '"args" in #', _typeData);
3177 List get _arguments => JS('JSExtendableArray', '#.args', _typeData);
3178
3179 bool get _hasOptionalArguments => JS('bool', '"opt" in #', _typeData);
3180 List get _optionalArguments => JS('JSExtendableArray', '#.opt', _typeData);
3181
3182 bool get _hasNamedArguments => JS('bool', '"named" in #', _typeData);
3183 get _namedArguments => JS('=Object', '#.named', _typeData);
3184
3185 RuntimeType toRuntimeType() {
3186 // TODO(ahe): Implement this (and update return type).
3187 return const DynamicRuntimeType();
3188 }
3189
3190 String _convert(type) {
3191 String result = runtimeTypeToString(type);
3192 if (result != null) return result;
3193 if (JS('bool', '"func" in #', type)) {
3194 return new FunctionTypeInfoDecoderRing(type).toString();
3195 } else {
3196 throw 'bad type';
3197 }
3198 }
3199
3200 String toString() {
3201 if (_cachedToString != null) return _cachedToString;
3202 var s = "(";
3203 var sep = '';
3204 if (_hasArguments) {
3205 for (var argument in _arguments) {
3206 s += sep;
3207 s += _convert(argument);
3208 sep = ', ';
3209 }
3210 }
3211 if (_hasOptionalArguments) {
3212 s += '$sep[';
3213 sep = '';
3214 for (var argument in _optionalArguments) {
3215 s += sep;
3216 s += _convert(argument);
3217 sep = ', ';
3218 }
3219 s += ']';
3220 }
3221 if (_hasNamedArguments) {
3222 s += '$sep{';
3223 sep = '';
3224 for (var name in extractKeys(_namedArguments)) {
3225 s += sep;
3226 s += '$name: ';
3227 s += _convert(JS('', '#[#]', _namedArguments, name));
3228 sep = ', ';
3229 }
3230 s += '}';
3231 }
3232 s += ') -> ';
3233 if (_isVoid) {
3234 s += 'void';
3235 } else if (_hasReturnType) {
3236 s += _convert(_returnType);
3237 } else {
3238 s += 'dynamic';
3239 }
3240 return _cachedToString = "$s";
3241 }
3242 }
3243
3244 // TODO(ahe): Remove this class and call noSuchMethod instead.
3245 class UnimplementedNoSuchMethodError extends Error
3246 implements NoSuchMethodError {
3247 final String _message;
3248
3249 UnimplementedNoSuchMethodError(this._message);
3250
3251 String toString() => "Unsupported operation: $_message";
3252 }
3253
3254 /**
3255 * Creates a random number with 64 bits of randomness.
3256 *
3257 * This will be truncated to the 53 bits available in a double.
3258 */
3259 int random64() {
3260 // TODO(lrn): Use a secure random source.
3261 int int32a = JS("int", "(Math.random() * 0x100000000) >>> 0");
3262 int int32b = JS("int", "(Math.random() * 0x100000000) >>> 0");
3263 return int32a + int32b * 0x100000000;
3264 }
3265
3266 String jsonEncodeNative(String string) {
3267 return JS("String", "JSON.stringify(#)", string);
3268 }
3269
3270 /**
3271 * Returns a property name for placing data on JavaScript objects shared between
3272 * DOM isolates. This happens when multiple programs are loaded in the same
3273 * JavaScript context (i.e. page). The name is based on [name] but with an
3274 * additional part that is unique for each isolate.
3275 *
3276 * The form of the name is '___dart_$name_$id'.
3277 */
3278 String getIsolateAffinityTag(String name) {
3279 var isolateTagGetter =
3280 JS_EMBEDDED_GLOBAL('', GET_ISOLATE_TAG);
3281 return JS('String', '#(#)', isolateTagGetter, name);
3282 }
3283
3284 typedef Future<Null> LoadLibraryFunctionType();
3285
3286 LoadLibraryFunctionType _loadLibraryWrapper(String loadId) {
3287 return () => loadDeferredLibrary(loadId);
3288 }
3289
3290 final Map<String, Future<Null>> _loadingLibraries = <String, Future<Null>>{};
3291 final Set<String> _loadedLibraries = new Set<String>();
3292
3293 typedef void DeferredLoadCallback();
3294
3295 // Function that will be called every time a new deferred import is loaded.
3296 DeferredLoadCallback deferredLoadHook;
3297
3298 Future<Null> loadDeferredLibrary(String loadId) {
3299 // For each loadId there is a list of hunk-uris to load, and a corresponding
3300 // list of hashes. These are stored in the app-global scope.
3301 var urisMap = JS_EMBEDDED_GLOBAL('', DEFERRED_LIBRARY_URIS);
3302 List<String> uris = JS('JSExtendableArray|Null', '#[#]', urisMap, loadId);
3303 var hashesMap = JS_EMBEDDED_GLOBAL('', DEFERRED_LIBRARY_HASHES);
3304 List<String> hashes = JS('JSExtendableArray|Null', '#[#]', hashesMap, loadId);
3305 if (uris == null) return new Future.value(null);
3306 // The indices into `uris` and `hashes` that we want to load.
3307 List<int> indices = new List.generate(uris.length, (i) => i);
3308 var isHunkLoaded = JS_EMBEDDED_GLOBAL('', IS_HUNK_LOADED);
3309 var isHunkInitialized = JS_EMBEDDED_GLOBAL('', IS_HUNK_INITIALIZED);
3310 // Filter away indices for hunks that have already been loaded.
3311 List<int> indicesToLoad = indices
3312 .where((int i) => !JS('bool','#(#)', isHunkLoaded, hashes[i]))
3313 .toList();
3314 return Future.wait(indicesToLoad
3315 .map((int i) => _loadHunk(uris[i]))).then((_) {
3316 // Now all hunks have been loaded, we run the needed initializers.
3317 List<int> indicesToInitialize = indices
3318 .where((int i) => !JS('bool','#(#)', isHunkInitialized, hashes[i]))
3319 .toList(); // Load the needed hunks.
3320 for (int i in indicesToInitialize) {
3321 var initializer = JS_EMBEDDED_GLOBAL('', INITIALIZE_LOADED_HUNK);
3322 JS('void', '#(#)', initializer, hashes[i]);
3323 }
3324 bool updated = _loadedLibraries.add(loadId);
3325 if (updated && deferredLoadHook != null) {
3326 deferredLoadHook();
3327 }
3328 });
3329 }
3330
3331 Future<Null> _loadHunk(String hunkName) {
3332 // TODO(ahe): Validate libraryName. Kasper points out that you want
3333 // to be able to experiment with the effect of toggling @DeferLoad,
3334 // so perhaps we should silently ignore "bad" library names.
3335 Future<Null> future = _loadingLibraries[hunkName];
3336 if (future != null) {
3337 return future.then((_) => null);
3338 }
3339
3340 String uri = IsolateNatives.thisScript;
3341
3342 int index = uri.lastIndexOf('/');
3343 uri = '${uri.substring(0, index + 1)}$hunkName';
3344
3345 if (Primitives.isJsshell || Primitives.isD8) {
3346 // TODO(ahe): Move this code to a JavaScript command helper script that is
3347 // not included in generated output.
3348 return _loadingLibraries[hunkName] = new Future<Null>(() {
3349 try {
3350 // Create a new function to avoid getting access to current function
3351 // context.
3352 JS('void', '(new Function(#))()', 'load("$uri")');
3353 } catch (error, stackTrace) {
3354 throw new DeferredLoadException("Loading $uri failed.");
3355 }
3356 return null;
3357 });
3358 } else if (isWorker()) {
3359 // We are in a web worker. Load the code with an XMLHttpRequest.
3360 return _loadingLibraries[hunkName] = new Future<Null>(() {
3361 Completer completer = new Completer<Null>();
3362 enterJsAsync();
3363 Future<Null> leavingFuture = completer.future.whenComplete(() {
3364 leaveJsAsync();
3365 });
3366
3367 int index = uri.lastIndexOf('/');
3368 uri = '${uri.substring(0, index + 1)}$hunkName';
3369 var xhr = JS('dynamic', 'new XMLHttpRequest()');
3370 JS('void', '#.open("GET", #)', xhr, uri);
3371 JS('void', '#.addEventListener("load", #, false)',
3372 xhr, convertDartClosureToJS((event) {
3373 if (JS('int', '#.status', xhr) != 200) {
3374 completer.completeError(
3375 new DeferredLoadException("Loading $uri failed."));
3376 return;
3377 }
3378 String code = JS('String', '#.responseText', xhr);
3379 try {
3380 // Create a new function to avoid getting access to current function
3381 // context.
3382 JS('void', '(new Function(#))()', code);
3383 } catch (error, stackTrace) {
3384 completer.completeError(
3385 new DeferredLoadException("Evaluating $uri failed."));
3386 return;
3387 }
3388 completer.complete(null);
3389 }, 1));
3390
3391 var fail = convertDartClosureToJS((event) {
3392 new DeferredLoadException("Loading $uri failed.");
3393 }, 1);
3394 JS('void', '#.addEventListener("error", #, false)', xhr, fail);
3395 JS('void', '#.addEventListener("abort", #, false)', xhr, fail);
3396
3397 JS('void', '#.send()', xhr);
3398 return leavingFuture;
3399 });
3400 }
3401 // We are in a dom-context.
3402 return _loadingLibraries[hunkName] = new Future<Null>(() {
3403 Completer completer = new Completer<Null>();
3404 // Inject a script tag.
3405 var script = JS('', 'document.createElement("script")');
3406 JS('', '#.type = "text/javascript"', script);
3407 JS('', '#.src = #', script, uri);
3408 JS('', '#.addEventListener("load", #, false)',
3409 script, convertDartClosureToJS((event) {
3410 completer.complete(null);
3411 }, 1));
3412 JS('', '#.addEventListener("error", #, false)',
3413 script, convertDartClosureToJS((event) {
3414 completer.completeError(
3415 new DeferredLoadException("Loading $uri failed."));
3416 }, 1));
3417 JS('', 'document.body.appendChild(#)', script);
3418
3419 return completer.future;
3420 });
3421 }
3422
3423 class MainError extends Error implements NoSuchMethodError {
3424 final String _message;
3425
3426 MainError(this._message);
3427
3428 String toString() => 'NoSuchMethodError: $_message';
3429 }
3430
3431 void missingMain() {
3432 throw new MainError("No top-level function named 'main'.");
3433 }
3434
3435 void badMain() {
3436 throw new MainError("'main' is not a function.");
3437 }
3438
3439 void mainHasTooManyParameters() {
3440 throw new MainError("'main' expects too many parameters.");
3441 }
OLDNEW
« no previous file with comments | « tool/input_sdk_patch/js_array.dart ('k') | tool/input_sdk_patch/js_mirrors.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698