OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * Support for interoperating with JavaScript. | 6 * Support for interoperating with JavaScript. |
7 * | 7 * |
8 * This library provides access to JavaScript objects from Dart, allowing | 8 * This library provides access to JavaScript objects from Dart, allowing |
9 * Dart code to get and set properties, and call methods of JavaScript objects | 9 * Dart code to get and set properties, and call methods of JavaScript objects |
10 * and invoke JavaScript functions. The library takes care of converting | 10 * and invoke JavaScript functions. The library takes care of converting |
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
82 * var jsMap = new JsObject.jsify({'a': 1, 'b': 2}); | 82 * var jsMap = new JsObject.jsify({'a': 1, 'b': 2}); |
83 * | 83 * |
84 * This expression creates a JavaScript array: | 84 * This expression creates a JavaScript array: |
85 * | 85 * |
86 * var jsArray = new JsObject.jsify([1, 2, 3]); | 86 * var jsArray = new JsObject.jsify([1, 2, 3]); |
87 */ | 87 */ |
88 library dart.js; | 88 library dart.js; |
89 | 89 |
90 import 'dart:collection' show ListMixin; | 90 import 'dart:collection' show ListMixin; |
91 import 'dart:nativewrappers'; | 91 import 'dart:nativewrappers'; |
92 import 'dart:math' as math; | |
93 import 'dart:mirrors' as mirrors; | |
94 | |
95 // Pretend we are always in checked mode as we aren't interested in users | |
96 // running Dartium code outside of checked mode. | |
97 final bool CHECK_JS_INVOCATIONS = true; | |
98 | |
99 final _allowedMethods = new Map<Symbol, _DeclarationSet>(); | |
100 final _allowedGetters = new Map<Symbol, _DeclarationSet>(); | |
101 final _allowedSetters = new Map<Symbol, _DeclarationSet>(); | |
102 | |
103 final _jsInterfaceTypes = new Set<Type>(); | |
104 Iterable<Type> get jsInterfaceTypes => _jsInterfaceTypes; | |
105 | |
106 /// A collection of methods where all methods have the same name. | |
107 /// This class is intended to optimize whether a specific invocation is | |
108 /// appropritate for at least some of the methods in the collection. | |
109 class _DeclarationSet { | |
110 _DeclarationSet() : _members = <mirrors.DeclarationMirror>[]; | |
111 | |
112 static bool _checkType(obj, mirrors.TypeMirror type) { | |
113 if (obj == null) return true; | |
114 return mirrors.reflectType(obj.runtimeType).isSubtypeOf(type); | |
115 } | |
116 | |
117 /// Returns whether the return [value] has a type is consistent with the | |
118 /// return type from at least one of the members matching the DeclarationSet. | |
119 bool _checkReturnType(value) { | |
120 if (value == null) return true; | |
121 var valueMirror = mirrors.reflectType(value.runtimeType); | |
122 for (var member in _members) { | |
123 if (member is mirrors.VariableMirror || member.isGetter) { | |
124 // TODO(jacobr): actually check return types for getters that return | |
125 // function types. | |
126 return true; | |
127 } else { | |
128 if (valueMirror.isSubtypeOf(member.returnType)) return true; | |
129 } | |
130 } | |
131 return false; | |
132 } | |
133 | |
134 /** | |
135 * Check whether the [invocation] is consistent with the [member] mirror. | |
136 */ | |
137 bool _checkDeclaration( | |
138 Invocation invocation, mirrors.DeclarationMirror member) { | |
139 if (member is mirrors.VariableMirror || member.isGetter) { | |
140 // TODO(jacobr): actually check method types against the function type | |
141 // returned by the getter or field. | |
142 return true; | |
143 } | |
144 var parameters = member.parameters; | |
145 var positionalArguments = invocation.positionalArguments; | |
146 // Too many arguments | |
147 if (parameters.length < positionalArguments.length) return false; | |
148 // Too few required arguments. | |
149 if (parameters.length > positionalArguments.length && | |
150 !parameters[positionalArguments.length].isOptional) return false; | |
151 for (var i = 0; i < positionalArguments.length; i++) { | |
152 if (parameters[i].isNamed) { | |
153 // Not enough positional arguments. | |
154 return false; | |
155 } | |
156 if (!_checkType( | |
157 invocation.positionalArguments[i], parameters[i].type)) return false; | |
158 } | |
159 if (invocation.namedArguments.isNotEmpty) { | |
160 var startNamed; | |
161 for (startNamed = parameters.length - 1; startNamed >= 0; startNamed--) { | |
162 if (!parameters[startNamed].isNamed) break; | |
163 } | |
164 startNamed++; | |
165 | |
166 // TODO(jacobr): we are unneccessarily using an O(n^2) algorithm here. | |
167 // If we have JS APIs with a lange number of named parameters we should | |
168 // optimize this. Either use a HashSet or invert this, walking over | |
169 // parameters, querying invocation, and making sure we match | |
170 //invocation.namedArguments.size keys. | |
171 for (var name in invocation.namedArguments.keys) { | |
172 bool match = false; | |
173 for (var j = startNamed; j < parameters.length; j++) { | |
174 var p = parameters[j]; | |
175 if (p.simpleName == name) { | |
176 if (!_checkType(invocation.namedArguments[name], | |
177 parameters[j].type)) return false; | |
178 match = true; | |
179 break; | |
180 } | |
181 } | |
182 if (match == false) return false; | |
183 } | |
184 } | |
185 return true; | |
186 } | |
187 | |
188 bool checkInvocation(Invocation invocation) { | |
189 for (var member in _members) { | |
190 if (_checkDeclaration(invocation, member)) return true; | |
191 } | |
192 return false; | |
193 } | |
194 | |
195 void add(mirrors.DeclarationMirror mirror) { | |
196 _members.add(mirror); | |
197 } | |
198 | |
199 final List<mirrors.DeclarationMirror> _members; | |
200 } | |
201 | |
202 /** | |
203 * Temporary method that we hope to remove at some point. This method should | |
204 * generally only be called by machine generated code. | |
205 */ | |
206 void registerJsInterfaces(List<Type> classes) { | |
207 if (_finalized == true) { | |
208 throw 'JSInterop class registration already finalized'; | |
209 } | |
210 for (Type type in classes) { | |
211 if (!_jsInterfaceTypes.add(type)) continue; // Already registered. | |
212 mirrors.ClassMirror typeMirror = mirrors.reflectType(type); | |
213 typeMirror.declarations.forEach((symbol, declaration) { | |
214 if (declaration is mirrors.MethodMirror || | |
215 declaration is mirrors.VariableMirror && !declaration.isStatic) { | |
216 bool treatAsGetter = false; | |
217 bool treatAsSetter = false; | |
218 if (declaration is mirrors.VariableMirror) { | |
219 treatAsGetter = true; | |
220 if (!declaration.isConst && !declaration.isFinal) { | |
221 treatAsSetter = true; | |
222 } | |
223 } else { | |
224 if (declaration.isGetter) { | |
225 treatAsGetter = true; | |
226 } else if (declaration.isSetter) { | |
227 treatAsSetter = true; | |
228 } else if (!declaration.isConstructor) { | |
229 _allowedMethods | |
230 .putIfAbsent(symbol, () => new _DeclarationSet()) | |
231 .add(declaration); | |
232 } | |
233 } | |
234 if (treatAsGetter) { | |
235 _allowedGetters | |
236 .putIfAbsent(symbol, () => new _DeclarationSet()) | |
237 .add(declaration); | |
238 _allowedMethods | |
239 .putIfAbsent(symbol, () => new _DeclarationSet()) | |
240 .add(declaration); | |
241 } | |
242 if (treatAsSetter) { | |
243 _allowedSetters | |
244 .putIfAbsent(symbol, () => new _DeclarationSet()) | |
245 .add(declaration); | |
246 } | |
247 } | |
248 }); | |
249 } | |
250 } | |
251 | |
252 _finalizeJsInterfaces() native "Js_finalizeJsInterfaces"; | |
253 | |
254 /** | |
255 * Generates a part file defining source code for JsObjectImpl and related | |
256 * classes. This calass is needed so that type checks for all registered JavaScr
ipt | |
257 * interop classes pass. | |
258 */ | |
259 String _generateJsObjectImplPart() { | |
260 Iterable<Type> types = jsInterfaceTypes; | |
261 var libraryPrefixes = new Map<mirrors.LibraryMirror, String>(); | |
262 var prefixNames = new Set<String>(); | |
263 var sb = new StringBuffer(); | |
264 | |
265 var implements = <String>[]; | |
266 for (var type in types) { | |
267 mirrors.ClassMirror typeMirror = mirrors.reflectType(type); | |
268 mirrors.LibraryMirror libraryMirror = typeMirror.owner; | |
269 var prefixName; | |
270 if (libraryPrefixes.containsKey(libraryMirror)) { | |
271 prefixName = libraryPrefixes[libraryMirror]; | |
272 } else { | |
273 var basePrefixName = | |
274 mirrors.MirrorSystem.getName(libraryMirror.simpleName); | |
275 basePrefixName = basePrefixName.replaceAll('.', '_'); | |
276 if (basePrefixName.isEmpty) basePrefixName = "lib"; | |
277 prefixName = basePrefixName; | |
278 var i = 1; | |
279 while (prefixNames.contains(prefixName)) { | |
280 prefixName = '$basePrefixName$i'; | |
281 i++; | |
282 } | |
283 prefixNames.add(prefixName); | |
284 libraryPrefixes[libraryMirror] = prefixName; | |
285 } | |
286 implements.add( | |
287 '${prefixName}.${mirrors.MirrorSystem.getName(typeMirror.simpleName)}'); | |
288 } | |
289 libraryPrefixes.forEach((libraryMirror, prefix) { | |
290 sb.writeln('import "${libraryMirror.uri}" as $prefix;'); | |
291 }); | |
292 var implementsClause = | |
293 implements.isEmpty ? "" : "implements ${implements.join(', ')}"; | |
294 // TODO(jacobr): only certain classes need to be implemented by | |
295 // Function and Array. | |
296 sb.write(''' | |
297 class JsObjectImpl extends JsObject $implementsClause { | |
298 JsObjectImpl.internal() : super.internal(); | |
299 } | |
300 | |
301 class JsFunctionImpl extends JsFunction $implementsClause { | |
302 JsFunctionImpl.internal() : super.internal(); | |
303 } | |
304 | |
305 class JsArrayImpl<E> extends JsArray<E> $implementsClause { | |
306 JsArrayImpl.internal() : super.internal(); | |
307 } | |
308 '''); | |
309 return sb.toString(); | |
310 } | |
311 | |
312 // Start of block of helper methods facilitating emulating JavaScript Array | |
313 // methods on Dart List objects passed to JavaScript via JS interop. | |
314 // TODO(jacobr): match JS more closely. | |
315 String _toStringJs(obj) => '$obj'; | |
316 | |
317 // TODO(jacobr): this might not exactly match JS semantics but should be | |
318 // adequate for now. | |
319 int _toIntJs(obj) { | |
320 if (obj is int) return obj; | |
321 if (obj is num) return obj.toInt(); | |
322 return num.parse('$obj'.trim(), (_) => 0).toInt(); | |
323 } | |
324 | |
325 // TODO(jacobr): this might not exactly match JS semantics but should be | |
326 // adequate for now. | |
327 num _toNumJs(obj) { | |
328 return obj is num ? obj : num.parse('$obj'.trim(), (_) => 0); | |
329 } | |
330 | |
331 /// Match the behavior of setting List length in JavaScript with the exception | |
332 /// that Dart does not distinguish undefined and null. | |
333 _setListLength(List list, rawlen) { | |
334 num len = _toNumJs(rawlen); | |
335 if (len is! int || len < 0) { | |
336 throw new RangeError("Invalid array length"); | |
337 } | |
338 if (len > list.length) { | |
339 _arrayExtend(list, len); | |
340 } else if (len < list.length) { | |
341 list.removeRange(len, list.length); | |
342 } | |
343 return rawlen; | |
344 } | |
345 | |
346 // TODO(jacobr): should we really bother with this method instead of just | |
347 // shallow copying to a JS array and calling the JavaScript join method? | |
348 String _arrayJoin(List list, sep) { | |
349 if (sep == null) { | |
350 sep = ","; | |
351 } | |
352 return list.map((e) => e == null ? "" : e.toString()).join(sep.toString()); | |
353 } | |
354 | |
355 // TODO(jacobr): should we really bother with this method instead of just | |
356 // shallow copying to a JS array and using the toString method? | |
357 String _arrayToString(List list) => _arrayJoin(list, ","); | |
358 | |
359 int _arrayPush(List list, e) { | |
360 list.add(e); | |
361 return list.length; | |
362 } | |
363 | |
364 _arrayPop(List list) { | |
365 if (list.length > 0) return list.removeLast(); | |
366 } | |
367 | |
368 // TODO(jacobr): would it be better to just copy input to a JS List | |
369 // and call Array.concat? | |
370 List _arrayConcat(List input, List args) { | |
371 var ret = new List.from(input); | |
372 for (var e in args) { | |
373 // TODO(jacobr): technically in ES6 we should use | |
374 // Symbol.isConcatSpreadable to determine whether call addAll. Once v8 | |
375 // supports it, we can make all Dart classes implementing Iterable | |
376 // specify isConcatSpreadable and tweak this behavior to allow Iterable. | |
377 if (e is List) { | |
378 ret.addAll(e); | |
379 } else { | |
380 ret.add(e); | |
381 } | |
382 } | |
383 return ret; | |
384 } | |
385 | |
386 List _arraySplice(List input, List args) { | |
387 int start = 0; | |
388 if (args.length > 0) { | |
389 var rawStart = _toIntJs(args[0]); | |
390 if (rawStart < 0) { | |
391 start = math.max(0, input.length - rawStart); | |
392 } else { | |
393 start = math.min(input.length, rawStart); | |
394 } | |
395 } | |
396 var end = start; | |
397 if (args.length > 1) { | |
398 var rawDeleteCount = _toIntJs(args[1]); | |
399 if (rawDeleteCount < 0) rawDeleteCount = 0; | |
400 end = math.min(input.length, start + rawDeleteCount); | |
401 } | |
402 var replacement = []; | |
403 var removedElements = input.getRange(start, end).toList(); | |
404 if (args.length > 2) { | |
405 replacement = args.getRange(2, args.length); | |
406 } | |
407 input.replaceRange(start, end, replacement); | |
408 return removedElements; | |
409 } | |
410 | |
411 List _arrayReverse(List l) { | |
412 for (var i = 0, j = l.length - 1; i < j; i++, j--) { | |
413 var tmp = l[i]; | |
414 l[i] = l[j]; | |
415 l[j] = tmp; | |
416 } | |
417 return l; | |
418 } | |
419 | |
420 _arrayShift(List l) { | |
421 if (l.isEmpty) return null; // Technically we should return undefined. | |
422 return l.removeAt(0); | |
423 } | |
424 | |
425 int _arrayUnshift(List l, List args) { | |
426 l.insertAll(0, args); | |
427 return l.length; | |
428 } | |
429 | |
430 _arrayExtend(List l, int newLength) { | |
431 for (var i = l.length; i < newLength; i++) { | |
432 // TODO(jacobr): we'd really like to add undefined to better match | |
433 // JavaScript semantics. | |
434 l.add(null); | |
435 } | |
436 } | |
437 | |
438 List _arraySort(List l, rawCompare) { | |
439 // TODO(jacobr): alternately we could just copy the Array to JavaScript, | |
440 // invoke the JS sort method and then copy the result back to Dart. | |
441 Comparator compare; | |
442 if (rawCompare == null) { | |
443 compare = (a, b) => _toStringJs(a).compareTo(_toStringJs(b)); | |
444 } else if (rawCompare is JsFunction) { | |
445 compare = (a, b) => rawCompare.apply([a, b]); | |
446 } else { | |
447 compare = rawCompare; | |
448 } | |
449 l.sort(compare); | |
450 return l; | |
451 } | |
452 // End of block of helper methods to emulate JavaScript Array methods on Dart Li
st. | |
453 | |
454 /** | |
455 * Can be called to provide a predictable point where no more JS interfaces can | |
456 * be added. Creating an instance of JsObject will also automatically trigger | |
457 * all JsObjects to be finalized. | |
458 */ | |
459 void finalizeJsInterfaces() { | |
460 if (_finalized == true) { | |
461 throw 'JSInterop class registration already finalized'; | |
462 } | |
463 _finalizeJsInterfaces(); | |
464 } | |
465 | 92 |
466 JsObject _cachedContext; | 93 JsObject _cachedContext; |
467 | 94 |
468 JsObject get _context native "Js_context_Callback"; | 95 JsObject get _context native "Js_context_Callback"; |
469 | 96 |
470 bool get _finalized native "Js_interfacesFinalized_Callback"; | |
471 | |
472 JsObject get context { | 97 JsObject get context { |
473 if (_cachedContext == null) { | 98 if (_cachedContext == null) { |
474 _cachedContext = _context; | 99 _cachedContext = _context; |
475 } | 100 } |
476 return _cachedContext; | 101 return _cachedContext; |
477 } | 102 } |
478 | 103 |
479 /** | 104 /** |
480 * Proxies a JavaScript object to Dart. | 105 * Proxies a JavaScript object to Dart. |
481 * | 106 * |
482 * The properties of the JavaScript object are accessible via the `[]` and | 107 * The properties of the JavaScript object are accessible via the `[]` and |
483 * `[]=` operators. Methods are callable via [callMethod]. | 108 * `[]=` operators. Methods are callable via [callMethod]. |
484 */ | 109 */ |
485 class JsObject extends NativeFieldWrapperClass2 { | 110 class JsObject extends NativeFieldWrapperClass2 { |
486 JsObject.internal(); | 111 JsObject.internal(); |
487 | 112 |
488 /** | 113 /** |
489 * Constructs a new JavaScript object from [constructor] and returns a proxy | 114 * Constructs a new JavaScript object from [constructor] and returns a proxy |
490 * to it. | 115 * to it. |
491 */ | 116 */ |
492 factory JsObject(JsFunction constructor, [List arguments]) => | 117 factory JsObject(JsFunction constructor, [List arguments]) => _create(construc
tor, arguments); |
493 _create(constructor, arguments); | |
494 | 118 |
495 static JsObject _create( | 119 static JsObject _create(JsFunction constructor, arguments) native "JsObject_co
nstructorCallback"; |
496 JsFunction constructor, arguments) native "JsObject_constructorCallback"; | |
497 | |
498 _buildArgs(Invocation invocation) { | |
499 if (invocation.namedArguments.isEmpty) { | |
500 return invocation.positionalArguments; | |
501 } else { | |
502 var varArgs = new Map<String, Object>(); | |
503 invocation.namedArguments.forEach((symbol, val) { | |
504 varArgs[mirrors.MirrorSystem.getName(symbol)] = val; | |
505 }); | |
506 return invocation.positionalArguments.toList() | |
507 ..add(new JsObject.jsify(varArgs)); | |
508 } | |
509 } | |
510 | 120 |
511 /** | 121 /** |
512 * Constructs a [JsObject] that proxies a native Dart object; _for expert use | 122 * Constructs a [JsObject] that proxies a native Dart object; _for expert use |
513 * only_. | 123 * only_. |
514 * | 124 * |
515 * Use this constructor only if you wish to get access to JavaScript | 125 * Use this constructor only if you wish to get access to JavaScript |
516 * properties attached to a browser host object, such as a Node or Blob, that | 126 * properties attached to a browser host object, such as a Node or Blob, that |
517 * is normally automatically converted into a native Dart object. | 127 * is normally automatically converted into a native Dart object. |
518 * | 128 * |
519 * An exception will be thrown if [object] either is `null` or has the type | 129 * An exception will be thrown if [object] either is `null` or has the type |
520 * `bool`, `num`, or `String`. | 130 * `bool`, `num`, or `String`. |
521 */ | 131 */ |
522 factory JsObject.fromBrowserObject(object) { | 132 factory JsObject.fromBrowserObject(object) { |
523 if (object is num || object is String || object is bool || object == null) { | 133 if (object is num || object is String || object is bool || object == null) { |
524 throw new ArgumentError("object cannot be a num, string, bool, or null"); | 134 throw new ArgumentError( |
| 135 "object cannot be a num, string, bool, or null"); |
525 } | 136 } |
526 return _fromBrowserObject(object); | 137 return _fromBrowserObject(object); |
527 } | 138 } |
528 | 139 |
529 /** | 140 /** |
530 * Recursively converts a JSON-like collection of Dart objects to a | 141 * Recursively converts a JSON-like collection of Dart objects to a |
531 * collection of JavaScript objects and returns a [JsObject] proxy to it. | 142 * collection of JavaScript objects and returns a [JsObject] proxy to it. |
532 * | 143 * |
533 * [object] must be a [Map] or [Iterable], the contents of which are also | 144 * [object] must be a [Map] or [Iterable], the contents of which are also |
534 * converted. Maps and Iterables are copied to a new JavaScript object. | 145 * converted. Maps and Iterables are copied to a new JavaScript object. |
535 * Primitives and other transferrable values are directly converted to their | 146 * Primitives and other transferrable values are directly converted to their |
536 * JavaScript type, and all other objects are proxied. | 147 * JavaScript type, and all other objects are proxied. |
537 */ | 148 */ |
538 factory JsObject.jsify(object) { | 149 factory JsObject.jsify(object) { |
539 if ((object is! Map) && (object is! Iterable)) { | 150 if ((object is! Map) && (object is! Iterable)) { |
540 throw new ArgumentError("object must be a Map or Iterable"); | 151 throw new ArgumentError("object must be a Map or Iterable"); |
541 } | 152 } |
542 return _jsify(object); | 153 return _jsify(object); |
543 } | 154 } |
544 | 155 |
545 static JsObject _jsify(object) native "JsObject_jsify"; | 156 static JsObject _jsify(object) native "JsObject_jsify"; |
546 | 157 |
547 static JsObject _fromBrowserObject( | 158 static JsObject _fromBrowserObject(object) native "JsObject_fromBrowserObject"
; |
548 object) native "JsObject_fromBrowserObject"; | |
549 | 159 |
550 /** | 160 /** |
551 * Returns the value associated with [property] from the proxied JavaScript | 161 * Returns the value associated with [property] from the proxied JavaScript |
552 * object. | 162 * object. |
553 * | 163 * |
554 * The type of [property] must be either [String] or [num]. | 164 * The type of [property] must be either [String] or [num]. |
555 */ | 165 */ |
556 operator [](property) native "JsObject_[]"; | 166 operator[](property) native "JsObject_[]"; |
557 | 167 |
558 /** | 168 /** |
559 * Sets the value associated with [property] on the proxied JavaScript | 169 * Sets the value associated with [property] on the proxied JavaScript |
560 * object. | 170 * object. |
561 * | 171 * |
562 * The type of [property] must be either [String] or [num]. | 172 * The type of [property] must be either [String] or [num]. |
563 */ | 173 */ |
564 operator []=(property, value) native "JsObject_[]="; | 174 operator[]=(property, value) native "JsObject_[]="; |
565 | 175 |
566 int get hashCode native "JsObject_hashCode"; | 176 int get hashCode native "JsObject_hashCode"; |
567 | 177 |
568 operator ==(other) => other is JsObject && _identityEquality(this, other); | 178 operator==(other) => other is JsObject && _identityEquality(this, other); |
569 | 179 |
570 static bool _identityEquality( | 180 static bool _identityEquality(JsObject a, JsObject b) native "JsObject_identit
yEquality"; |
571 JsObject a, JsObject b) native "JsObject_identityEquality"; | |
572 | 181 |
573 /** | 182 /** |
574 * Returns `true` if the JavaScript object contains the specified property | 183 * Returns `true` if the JavaScript object contains the specified property |
575 * either directly or though its prototype chain. | 184 * either directly or though its prototype chain. |
576 * | 185 * |
577 * This is the equivalent of the `in` operator in JavaScript. | 186 * This is the equivalent of the `in` operator in JavaScript. |
578 */ | 187 */ |
579 bool hasProperty(String property) native "JsObject_hasProperty"; | 188 bool hasProperty(String property) native "JsObject_hasProperty"; |
580 | 189 |
581 /** | 190 /** |
582 * Removes [property] from the JavaScript object. | 191 * Removes [property] from the JavaScript object. |
583 * | 192 * |
584 * This is the equivalent of the `delete` operator in JavaScript. | 193 * This is the equivalent of the `delete` operator in JavaScript. |
585 */ | 194 */ |
586 void deleteProperty(String property) native "JsObject_deleteProperty"; | 195 void deleteProperty(String property) native "JsObject_deleteProperty"; |
587 | 196 |
588 /** | 197 /** |
589 * Returns `true` if the JavaScript object has [type] in its prototype chain. | 198 * Returns `true` if the JavaScript object has [type] in its prototype chain. |
590 * | 199 * |
591 * This is the equivalent of the `instanceof` operator in JavaScript. | 200 * This is the equivalent of the `instanceof` operator in JavaScript. |
592 */ | 201 */ |
593 bool instanceof(JsFunction type) native "JsObject_instanceof"; | 202 bool instanceof(JsFunction type) native "JsObject_instanceof"; |
594 | 203 |
595 /** | 204 /** |
596 * Returns the result of the JavaScript objects `toString` method. | 205 * Returns the result of the JavaScript objects `toString` method. |
597 */ | 206 */ |
598 String toString() { | 207 String toString() { |
599 try { | 208 try { |
600 return _toString(); | 209 return _toString(); |
601 } catch (e) { | 210 } catch(e) { |
602 return super.toString(); | 211 return super.toString(); |
603 } | 212 } |
604 } | 213 } |
605 | 214 |
606 String _toString() native "JsObject_toString"; | 215 String _toString() native "JsObject_toString"; |
607 | 216 |
608 /** | 217 /** |
609 * Calls [method] on the JavaScript object with the arguments [args] and | 218 * Calls [method] on the JavaScript object with the arguments [args] and |
610 * returns the result. | 219 * returns the result. |
611 * | 220 * |
612 * The type of [method] must be either [String] or [num]. | 221 * The type of [method] must be either [String] or [num]. |
613 */ | 222 */ |
614 callMethod(String method, [List args]) { | 223 callMethod(String method, [List args]) { |
615 try { | 224 try { |
616 return _callMethod(method, args); | 225 return _callMethod(method, args); |
617 } catch (e) { | 226 } catch(e) { |
618 if (hasProperty(method)) { | 227 if (hasProperty(method)) { |
619 rethrow; | 228 rethrow; |
620 } else { | 229 } else { |
621 throw new NoSuchMethodError(this, new Symbol(method), args, null); | 230 throw new NoSuchMethodError(this, new Symbol(method), args, null); |
622 } | 231 } |
623 } | 232 } |
624 } | 233 } |
625 | 234 |
626 noSuchMethod(Invocation invocation) { | |
627 throwError() { | |
628 throw new NoSuchMethodError(this, invocation.memberName, | |
629 invocation.positionalArguments, invocation.namedArguments); | |
630 } | |
631 | |
632 String name = mirrors.MirrorSystem.getName(invocation.memberName); | |
633 if (invocation.isGetter) { | |
634 if (CHECK_JS_INVOCATIONS) { | |
635 var matches = _allowedGetters[invocation.memberName]; | |
636 if (matches == null && | |
637 !_allowedMethods.containsKey(invocation.memberName)) { | |
638 throwError(); | |
639 } | |
640 var ret = this[name]; | |
641 if (matches != null && matches._checkReturnType(ret)) return ret; | |
642 if (ret is Function || | |
643 (ret is JsFunction /* shouldn't be needed in the future*/) && | |
644 _allowedMethods.containsKey( | |
645 invocation.memberName)) return ret; // Warning: we have not
bound "this"... we could type check on the Function but that is of little value
in Dart. | |
646 throwError(); | |
647 } else { | |
648 // TODO(jacobr): should we throw if the JavaScript object doesn't have t
he property? | |
649 return this[name]; | |
650 } | |
651 } else if (invocation.isSetter) { | |
652 if (CHECK_JS_INVOCATIONS) { | |
653 var matches = _allowedSetters[invocation.memberName]; | |
654 if (matches == null || | |
655 !matches.checkInvocation(invocation)) throwError(); | |
656 } | |
657 assert(name.endsWith("=")); | |
658 name = name.substring(0, name.length - 1); | |
659 return this[name] = invocation.positionalArguments.first; | |
660 } else { | |
661 // TODO(jacobr): also allow calling getters that look like functions. | |
662 var matches; | |
663 if (CHECK_JS_INVOCATIONS) { | |
664 matches = _allowedMethods[invocation.memberName]; | |
665 if (matches == null || | |
666 !matches.checkInvocation(invocation)) throwError(); | |
667 } | |
668 var ret = this.callMethod(name, _buildArgs(invocation)); | |
669 if (CHECK_JS_INVOCATIONS) { | |
670 if (!matches._checkReturnType(ret)) throwError(); | |
671 } | |
672 return ret; | |
673 } | |
674 } | |
675 | |
676 _callMethod(String name, List args) native "JsObject_callMethod"; | 235 _callMethod(String name, List args) native "JsObject_callMethod"; |
677 } | 236 } |
678 | 237 |
679 /** | 238 /** |
680 * Proxies a JavaScript Function object. | 239 * Proxies a JavaScript Function object. |
681 */ | 240 */ |
682 class JsFunction extends JsObject implements Function { | 241 class JsFunction extends JsObject { |
683 JsFunction.internal() : super.internal(); | 242 JsFunction.internal() : super.internal(); |
684 | 243 |
685 /** | 244 /** |
686 * Returns a [JsFunction] that captures its 'this' binding and calls [f] | 245 * Returns a [JsFunction] that captures its 'this' binding and calls [f] |
687 * with the value of this passed as the first argument. | 246 * with the value of this passed as the first argument. |
688 */ | 247 */ |
689 factory JsFunction.withThis(Function f) => _withThis(f); | 248 factory JsFunction.withThis(Function f) => _withThis(f); |
690 | 249 |
691 /** | 250 /** |
692 * Invokes the JavaScript function with arguments [args]. If [thisArg] is | 251 * Invokes the JavaScript function with arguments [args]. If [thisArg] is |
693 * supplied it is the value of `this` for the invocation. | 252 * supplied it is the value of `this` for the invocation. |
694 */ | 253 */ |
695 dynamic apply(List args, {thisArg}) native "JsFunction_apply"; | 254 dynamic apply(List args, {thisArg}) native "JsFunction_apply"; |
696 | 255 |
697 noSuchMethod(Invocation invocation) { | |
698 if (invocation.isMethod && invocation.memberName == #call) { | |
699 return apply(_buildArgs(invocation)); | |
700 } | |
701 return super.noSuchMethod(invocation); | |
702 } | |
703 | |
704 /** | 256 /** |
705 * Internal only version of apply which uses debugger proxies of Dart objects | 257 * Internal only version of apply which uses debugger proxies of Dart objects |
706 * rather than opaque handles. This method is private because it cannot be | 258 * rather than opaque handles. This method is private because it cannot be |
707 * efficiently implemented in Dart2Js so should only be used by internal | 259 * efficiently implemented in Dart2Js so should only be used by internal |
708 * tools. | 260 * tools. |
709 */ | 261 */ |
710 _applyDebuggerOnly(List args, | 262 _applyDebuggerOnly(List args, {thisArg}) native "JsFunction_applyDebuggerOnly"
; |
711 {thisArg}) native "JsFunction_applyDebuggerOnly"; | |
712 | 263 |
713 static JsFunction _withThis(Function f) native "JsFunction_withThis"; | 264 static JsFunction _withThis(Function f) native "JsFunction_withThis"; |
714 } | 265 } |
715 | 266 |
716 /** | 267 /** |
717 * A [List] proxying a JavaScript Array. | 268 * A [List] proxying a JavaScript Array. |
718 */ | 269 */ |
719 class JsArray<E> extends JsObject with ListMixin<E> { | 270 class JsArray<E> extends JsObject with ListMixin<E> { |
720 JsArray.internal() : super.internal(); | |
721 | 271 |
722 factory JsArray() => _newJsArray(); | 272 factory JsArray() => _newJsArray(); |
723 | 273 |
724 static JsArray _newJsArray() native "JsArray_newJsArray"; | 274 static JsArray _newJsArray() native "JsArray_newJsArray"; |
725 | 275 |
726 factory JsArray.from(Iterable<E> other) => | 276 factory JsArray.from(Iterable<E> other) => _newJsArrayFromSafeList(new List.fr
om(other)); |
727 _newJsArrayFromSafeList(new List.from(other)); | |
728 | 277 |
729 static JsArray _newJsArrayFromSafeList( | 278 static JsArray _newJsArrayFromSafeList(List list) native "JsArray_newJsArrayFr
omSafeList"; |
730 List list) native "JsArray_newJsArrayFromSafeList"; | |
731 | 279 |
732 _checkIndex(int index, {bool insert: false}) { | 280 _checkIndex(int index, {bool insert: false}) { |
733 int length = insert ? this.length + 1 : this.length; | 281 int length = insert ? this.length + 1 : this.length; |
734 if (index is int && (index < 0 || index >= length)) { | 282 if (index is int && (index < 0 || index >= length)) { |
735 throw new RangeError.range(index, 0, length); | 283 throw new RangeError.range(index, 0, length); |
736 } | 284 } |
737 } | 285 } |
738 | 286 |
739 _checkRange(int start, int end) { | 287 _checkRange(int start, int end) { |
740 int cachedLength = this.length; | 288 int cachedLength = this.length; |
741 if (start < 0 || start > cachedLength) { | 289 if (start < 0 || start > cachedLength) { |
742 throw new RangeError.range(start, 0, cachedLength); | 290 throw new RangeError.range(start, 0, cachedLength); |
743 } | 291 } |
744 if (end < start || end > cachedLength) { | 292 if (end < start || end > cachedLength) { |
745 throw new RangeError.range(end, start, cachedLength); | 293 throw new RangeError.range(end, start, cachedLength); |
746 } | 294 } |
747 } | 295 } |
748 | 296 |
749 // Methods required by ListMixin | 297 // Methods required by ListMixin |
750 | 298 |
751 E operator [](index) { | 299 E operator [](index) { |
752 if (index is int) { | 300 if (index is int) { |
753 _checkIndex(index); | 301 _checkIndex(index); |
754 } | 302 } |
755 return super[index]; | 303 return super[index]; |
756 } | 304 } |
757 | 305 |
758 void operator []=(index, E value) { | 306 void operator []=(index, E value) { |
759 if (index is int) { | 307 if(index is int) { |
760 _checkIndex(index); | 308 _checkIndex(index); |
761 } | 309 } |
762 super[index] = value; | 310 super[index] = value; |
763 } | 311 } |
764 | 312 |
765 int get length native "JsArray_length"; | 313 int get length native "JsArray_length"; |
766 | 314 |
767 void set length(int length) { | 315 void set length(int length) { super['length'] = length; } |
768 super['length'] = length; | |
769 } | |
770 | 316 |
771 // Methods overriden for better performance | 317 // Methods overriden for better performance |
772 | 318 |
773 void add(E value) { | 319 void add(E value) { |
774 callMethod('push', [value]); | 320 callMethod('push', [value]); |
775 } | 321 } |
776 | 322 |
777 void addAll(Iterable<E> iterable) { | 323 void addAll(Iterable<E> iterable) { |
778 // TODO(jacobr): this can be optimized slightly. | 324 // TODO(jacobr): this can be optimized slightly. |
779 callMethod('push', new List.from(iterable)); | 325 callMethod('push', new List.from(iterable)); |
780 } | 326 } |
781 | 327 |
782 void insert(int index, E element) { | 328 void insert(int index, E element) { |
783 _checkIndex(index, insert: true); | 329 _checkIndex(index, insert:true); |
784 callMethod('splice', [index, 0, element]); | 330 callMethod('splice', [index, 0, element]); |
785 } | 331 } |
786 | 332 |
787 E removeAt(int index) { | 333 E removeAt(int index) { |
788 _checkIndex(index); | 334 _checkIndex(index); |
789 return callMethod('splice', [index, 1])[0]; | 335 return callMethod('splice', [index, 1])[0]; |
790 } | 336 } |
791 | 337 |
792 E removeLast() { | 338 E removeLast() { |
793 if (length == 0) throw new RangeError(-1); | 339 if (length == 0) throw new RangeError(-1); |
(...skipping 18 matching lines...) Expand all Loading... |
812 callMethod('sort', [compare]); | 358 callMethod('sort', [compare]); |
813 } | 359 } |
814 } | 360 } |
815 | 361 |
816 /** | 362 /** |
817 * Placeholder object for cases where we need to determine exactly how many | 363 * Placeholder object for cases where we need to determine exactly how many |
818 * args were passed to a function. | 364 * args were passed to a function. |
819 */ | 365 */ |
820 const _UNDEFINED = const Object(); | 366 const _UNDEFINED = const Object(); |
821 | 367 |
822 // TODO(jacobr): this method is a hack to work around the lack of proper dart | 368 // FIXME(jacobr): this method is a hack to work around the lack of proper dart |
823 // support for varargs methods. | 369 // support for varargs methods. |
824 List _stripUndefinedArgs(List args) => | 370 List _stripUndefinedArgs(List args) => |
825 args.takeWhile((i) => i != _UNDEFINED).toList(); | 371 args.takeWhile((i) => i != _UNDEFINED).toList(); |
826 | 372 |
827 /** | 373 /** |
828 * Returns a method that can be called with an arbitrary number (for n less | 374 * Returns a method that can be called with an arbitrary number (for n less |
829 * than 11) of arguments without violating Dart type checks. | 375 * than 11) of arguments without violating Dart type checks. |
830 */ | 376 */ |
831 Function _wrapAsDebuggerVarArgsFunction(JsFunction jsFunction) => | 377 Function _wrapAsDebuggerVarArgsFunction(JsFunction jsFunction) => |
832 ([a1 = _UNDEFINED, a2 = _UNDEFINED, a3 = _UNDEFINED, a4 = _UNDEFINED, | 378 ([a1=_UNDEFINED, a2=_UNDEFINED, a3=_UNDEFINED, a4=_UNDEFINED, |
833 a5 = _UNDEFINED, a6 = _UNDEFINED, a7 = _UNDEFINED, a8 = _UNDEFINED, | 379 a5=_UNDEFINED, a6=_UNDEFINED, a7=_UNDEFINED, a8=_UNDEFINED, |
834 a9 = _UNDEFINED, a10 = _UNDEFINED]) => jsFunction._applyDebuggerOnly( | 380 a9=_UNDEFINED, a10=_UNDEFINED]) => |
835 _stripUndefinedArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10])); | 381 jsFunction._applyDebuggerOnly(_stripUndefinedArgs( |
| 382 [a1,a2,a3,a4,a5,a6,a7,a8,a9,a10])); |
OLD | NEW |