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

Side by Side Diff: sdk/lib/js/dartium/js_dartium.dart

Issue 1194643002: Enhance dart:js interop in a backwards compatible manner. List objects can now be passed back and f… (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 5 years, 5 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 | « sdk/lib/html/dartium/html_dartium.dart ('k') | tests/html/html.status » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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 }
92 465
93 JsObject _cachedContext; 466 JsObject _cachedContext;
94 467
95 JsObject get _context native "Js_context_Callback"; 468 JsObject get _context native "Js_context_Callback";
96 469
470 bool get _finalized native "Js_interfacesFinalized_Callback";
471
97 JsObject get context { 472 JsObject get context {
98 if (_cachedContext == null) { 473 if (_cachedContext == null) {
99 _cachedContext = _context; 474 _cachedContext = _context;
100 } 475 }
101 return _cachedContext; 476 return _cachedContext;
102 } 477 }
103 478
104 /** 479 /**
105 * Proxies a JavaScript object to Dart. 480 * Proxies a JavaScript object to Dart.
106 * 481 *
107 * The properties of the JavaScript object are accessible via the `[]` and 482 * The properties of the JavaScript object are accessible via the `[]` and
108 * `[]=` operators. Methods are callable via [callMethod]. 483 * `[]=` operators. Methods are callable via [callMethod].
109 */ 484 */
110 class JsObject extends NativeFieldWrapperClass2 { 485 class JsObject extends NativeFieldWrapperClass2 {
111 JsObject.internal(); 486 JsObject.internal();
112 487
113 /** 488 /**
114 * Constructs a new JavaScript object from [constructor] and returns a proxy 489 * Constructs a new JavaScript object from [constructor] and returns a proxy
115 * to it. 490 * to it.
116 */ 491 */
117 factory JsObject(JsFunction constructor, [List arguments]) => _create(construc tor, arguments); 492 factory JsObject(JsFunction constructor, [List arguments]) =>
493 _create(constructor, arguments);
118 494
119 static JsObject _create(JsFunction constructor, arguments) native "JsObject_co nstructorCallback"; 495 static JsObject _create(
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 }
120 510
121 /** 511 /**
122 * Constructs a [JsObject] that proxies a native Dart object; _for expert use 512 * Constructs a [JsObject] that proxies a native Dart object; _for expert use
123 * only_. 513 * only_.
124 * 514 *
125 * Use this constructor only if you wish to get access to JavaScript 515 * Use this constructor only if you wish to get access to JavaScript
126 * properties attached to a browser host object, such as a Node or Blob, that 516 * properties attached to a browser host object, such as a Node or Blob, that
127 * is normally automatically converted into a native Dart object. 517 * is normally automatically converted into a native Dart object.
128 * 518 *
129 * An exception will be thrown if [object] either is `null` or has the type 519 * An exception will be thrown if [object] either is `null` or has the type
130 * `bool`, `num`, or `String`. 520 * `bool`, `num`, or `String`.
131 */ 521 */
132 factory JsObject.fromBrowserObject(object) { 522 factory JsObject.fromBrowserObject(object) {
133 if (object is num || object is String || object is bool || object == null) { 523 if (object is num || object is String || object is bool || object == null) {
134 throw new ArgumentError( 524 throw new ArgumentError("object cannot be a num, string, bool, or null");
135 "object cannot be a num, string, bool, or null");
136 } 525 }
137 return _fromBrowserObject(object); 526 return _fromBrowserObject(object);
138 } 527 }
139 528
140 /** 529 /**
141 * Recursively converts a JSON-like collection of Dart objects to a 530 * Recursively converts a JSON-like collection of Dart objects to a
142 * collection of JavaScript objects and returns a [JsObject] proxy to it. 531 * collection of JavaScript objects and returns a [JsObject] proxy to it.
143 * 532 *
144 * [object] must be a [Map] or [Iterable], the contents of which are also 533 * [object] must be a [Map] or [Iterable], the contents of which are also
145 * converted. Maps and Iterables are copied to a new JavaScript object. 534 * converted. Maps and Iterables are copied to a new JavaScript object.
146 * Primitives and other transferrable values are directly converted to their 535 * Primitives and other transferrable values are directly converted to their
147 * JavaScript type, and all other objects are proxied. 536 * JavaScript type, and all other objects are proxied.
148 */ 537 */
149 factory JsObject.jsify(object) { 538 factory JsObject.jsify(object) {
150 if ((object is! Map) && (object is! Iterable)) { 539 if ((object is! Map) && (object is! Iterable)) {
151 throw new ArgumentError("object must be a Map or Iterable"); 540 throw new ArgumentError("object must be a Map or Iterable");
152 } 541 }
153 return _jsify(object); 542 return _jsify(object);
154 } 543 }
155 544
156 static JsObject _jsify(object) native "JsObject_jsify"; 545 static JsObject _jsify(object) native "JsObject_jsify";
157 546
158 static JsObject _fromBrowserObject(object) native "JsObject_fromBrowserObject" ; 547 static JsObject _fromBrowserObject(
548 object) native "JsObject_fromBrowserObject";
159 549
160 /** 550 /**
161 * Returns the value associated with [property] from the proxied JavaScript 551 * Returns the value associated with [property] from the proxied JavaScript
162 * object. 552 * object.
163 * 553 *
164 * The type of [property] must be either [String] or [num]. 554 * The type of [property] must be either [String] or [num].
165 */ 555 */
166 operator[](property) native "JsObject_[]"; 556 operator [](property) native "JsObject_[]";
167 557
168 /** 558 /**
169 * Sets the value associated with [property] on the proxied JavaScript 559 * Sets the value associated with [property] on the proxied JavaScript
170 * object. 560 * object.
171 * 561 *
172 * The type of [property] must be either [String] or [num]. 562 * The type of [property] must be either [String] or [num].
173 */ 563 */
174 operator[]=(property, value) native "JsObject_[]="; 564 operator []=(property, value) native "JsObject_[]=";
175 565
176 int get hashCode native "JsObject_hashCode"; 566 int get hashCode native "JsObject_hashCode";
177 567
178 operator==(other) => other is JsObject && _identityEquality(this, other); 568 operator ==(other) => other is JsObject && _identityEquality(this, other);
179 569
180 static bool _identityEquality(JsObject a, JsObject b) native "JsObject_identit yEquality"; 570 static bool _identityEquality(
571 JsObject a, JsObject b) native "JsObject_identityEquality";
181 572
182 /** 573 /**
183 * Returns `true` if the JavaScript object contains the specified property 574 * Returns `true` if the JavaScript object contains the specified property
184 * either directly or though its prototype chain. 575 * either directly or though its prototype chain.
185 * 576 *
186 * This is the equivalent of the `in` operator in JavaScript. 577 * This is the equivalent of the `in` operator in JavaScript.
187 */ 578 */
188 bool hasProperty(String property) native "JsObject_hasProperty"; 579 bool hasProperty(String property) native "JsObject_hasProperty";
189 580
190 /** 581 /**
191 * Removes [property] from the JavaScript object. 582 * Removes [property] from the JavaScript object.
192 * 583 *
193 * This is the equivalent of the `delete` operator in JavaScript. 584 * This is the equivalent of the `delete` operator in JavaScript.
194 */ 585 */
195 void deleteProperty(String property) native "JsObject_deleteProperty"; 586 void deleteProperty(String property) native "JsObject_deleteProperty";
196 587
197 /** 588 /**
198 * Returns `true` if the JavaScript object has [type] in its prototype chain. 589 * Returns `true` if the JavaScript object has [type] in its prototype chain.
199 * 590 *
200 * This is the equivalent of the `instanceof` operator in JavaScript. 591 * This is the equivalent of the `instanceof` operator in JavaScript.
201 */ 592 */
202 bool instanceof(JsFunction type) native "JsObject_instanceof"; 593 bool instanceof(JsFunction type) native "JsObject_instanceof";
203 594
204 /** 595 /**
205 * Returns the result of the JavaScript objects `toString` method. 596 * Returns the result of the JavaScript objects `toString` method.
206 */ 597 */
207 String toString() { 598 String toString() {
208 try { 599 try {
209 return _toString(); 600 return _toString();
210 } catch(e) { 601 } catch (e) {
211 return super.toString(); 602 return super.toString();
212 } 603 }
213 } 604 }
214 605
215 String _toString() native "JsObject_toString"; 606 String _toString() native "JsObject_toString";
216 607
217 /** 608 /**
218 * Calls [method] on the JavaScript object with the arguments [args] and 609 * Calls [method] on the JavaScript object with the arguments [args] and
219 * returns the result. 610 * returns the result.
220 * 611 *
221 * The type of [method] must be either [String] or [num]. 612 * The type of [method] must be either [String] or [num].
222 */ 613 */
223 callMethod(String method, [List args]) { 614 callMethod(String method, [List args]) {
224 try { 615 try {
225 return _callMethod(method, args); 616 return _callMethod(method, args);
226 } catch(e) { 617 } catch (e) {
227 if (hasProperty(method)) { 618 if (hasProperty(method)) {
228 rethrow; 619 rethrow;
229 } else { 620 } else {
230 throw new NoSuchMethodError(this, new Symbol(method), args, null); 621 throw new NoSuchMethodError(this, new Symbol(method), args, null);
231 } 622 }
232 } 623 }
233 } 624 }
234 625
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
235 _callMethod(String name, List args) native "JsObject_callMethod"; 676 _callMethod(String name, List args) native "JsObject_callMethod";
236 } 677 }
237 678
238 /** 679 /**
239 * Proxies a JavaScript Function object. 680 * Proxies a JavaScript Function object.
240 */ 681 */
241 class JsFunction extends JsObject { 682 class JsFunction extends JsObject implements Function {
242 JsFunction.internal() : super.internal(); 683 JsFunction.internal() : super.internal();
243 684
244 /** 685 /**
245 * Returns a [JsFunction] that captures its 'this' binding and calls [f] 686 * Returns a [JsFunction] that captures its 'this' binding and calls [f]
246 * with the value of this passed as the first argument. 687 * with the value of this passed as the first argument.
247 */ 688 */
248 factory JsFunction.withThis(Function f) => _withThis(f); 689 factory JsFunction.withThis(Function f) => _withThis(f);
249 690
250 /** 691 /**
251 * Invokes the JavaScript function with arguments [args]. If [thisArg] is 692 * Invokes the JavaScript function with arguments [args]. If [thisArg] is
252 * supplied it is the value of `this` for the invocation. 693 * supplied it is the value of `this` for the invocation.
253 */ 694 */
254 dynamic apply(List args, {thisArg}) native "JsFunction_apply"; 695 dynamic apply(List args, {thisArg}) native "JsFunction_apply";
255 696
697 noSuchMethod(Invocation invocation) {
698 if (invocation.isMethod && invocation.memberName == #call) {
699 return apply(_buildArgs(invocation));
700 }
701 return super.noSuchMethod(invocation);
702 }
703
256 /** 704 /**
257 * Internal only version of apply which uses debugger proxies of Dart objects 705 * Internal only version of apply which uses debugger proxies of Dart objects
258 * rather than opaque handles. This method is private because it cannot be 706 * rather than opaque handles. This method is private because it cannot be
259 * efficiently implemented in Dart2Js so should only be used by internal 707 * efficiently implemented in Dart2Js so should only be used by internal
260 * tools. 708 * tools.
261 */ 709 */
262 _applyDebuggerOnly(List args, {thisArg}) native "JsFunction_applyDebuggerOnly" ; 710 _applyDebuggerOnly(List args,
711 {thisArg}) native "JsFunction_applyDebuggerOnly";
263 712
264 static JsFunction _withThis(Function f) native "JsFunction_withThis"; 713 static JsFunction _withThis(Function f) native "JsFunction_withThis";
265 } 714 }
266 715
267 /** 716 /**
268 * A [List] proxying a JavaScript Array. 717 * A [List] proxying a JavaScript Array.
269 */ 718 */
270 class JsArray<E> extends JsObject with ListMixin<E> { 719 class JsArray<E> extends JsObject with ListMixin<E> {
720 JsArray.internal() : super.internal();
271 721
272 factory JsArray() => _newJsArray(); 722 factory JsArray() => _newJsArray();
273 723
274 static JsArray _newJsArray() native "JsArray_newJsArray"; 724 static JsArray _newJsArray() native "JsArray_newJsArray";
275 725
276 factory JsArray.from(Iterable<E> other) => _newJsArrayFromSafeList(new List.fr om(other)); 726 factory JsArray.from(Iterable<E> other) =>
727 _newJsArrayFromSafeList(new List.from(other));
277 728
278 static JsArray _newJsArrayFromSafeList(List list) native "JsArray_newJsArrayFr omSafeList"; 729 static JsArray _newJsArrayFromSafeList(
730 List list) native "JsArray_newJsArrayFromSafeList";
279 731
280 _checkIndex(int index, {bool insert: false}) { 732 _checkIndex(int index, {bool insert: false}) {
281 int length = insert ? this.length + 1 : this.length; 733 int length = insert ? this.length + 1 : this.length;
282 if (index is int && (index < 0 || index >= length)) { 734 if (index is int && (index < 0 || index >= length)) {
283 throw new RangeError.range(index, 0, length); 735 throw new RangeError.range(index, 0, length);
284 } 736 }
285 } 737 }
286 738
287 _checkRange(int start, int end) { 739 _checkRange(int start, int end) {
288 int cachedLength = this.length; 740 int cachedLength = this.length;
289 if (start < 0 || start > cachedLength) { 741 if (start < 0 || start > cachedLength) {
290 throw new RangeError.range(start, 0, cachedLength); 742 throw new RangeError.range(start, 0, cachedLength);
291 } 743 }
292 if (end < start || end > cachedLength) { 744 if (end < start || end > cachedLength) {
293 throw new RangeError.range(end, start, cachedLength); 745 throw new RangeError.range(end, start, cachedLength);
294 } 746 }
295 } 747 }
296 748
297 // Methods required by ListMixin 749 // Methods required by ListMixin
298 750
299 E operator [](index) { 751 E operator [](index) {
300 if (index is int) { 752 if (index is int) {
301 _checkIndex(index); 753 _checkIndex(index);
302 } 754 }
303 return super[index]; 755 return super[index];
304 } 756 }
305 757
306 void operator []=(index, E value) { 758 void operator []=(index, E value) {
307 if(index is int) { 759 if (index is int) {
308 _checkIndex(index); 760 _checkIndex(index);
309 } 761 }
310 super[index] = value; 762 super[index] = value;
311 } 763 }
312 764
313 int get length native "JsArray_length"; 765 int get length native "JsArray_length";
314 766
315 void set length(int length) { super['length'] = length; } 767 void set length(int length) {
768 super['length'] = length;
769 }
316 770
317 // Methods overriden for better performance 771 // Methods overriden for better performance
318 772
319 void add(E value) { 773 void add(E value) {
320 callMethod('push', [value]); 774 callMethod('push', [value]);
321 } 775 }
322 776
323 void addAll(Iterable<E> iterable) { 777 void addAll(Iterable<E> iterable) {
324 // TODO(jacobr): this can be optimized slightly. 778 // TODO(jacobr): this can be optimized slightly.
325 callMethod('push', new List.from(iterable)); 779 callMethod('push', new List.from(iterable));
326 } 780 }
327 781
328 void insert(int index, E element) { 782 void insert(int index, E element) {
329 _checkIndex(index, insert:true); 783 _checkIndex(index, insert: true);
330 callMethod('splice', [index, 0, element]); 784 callMethod('splice', [index, 0, element]);
331 } 785 }
332 786
333 E removeAt(int index) { 787 E removeAt(int index) {
334 _checkIndex(index); 788 _checkIndex(index);
335 return callMethod('splice', [index, 1])[0]; 789 return callMethod('splice', [index, 1])[0];
336 } 790 }
337 791
338 E removeLast() { 792 E removeLast() {
339 if (length == 0) throw new RangeError(-1); 793 if (length == 0) throw new RangeError(-1);
(...skipping 18 matching lines...) Expand all
358 callMethod('sort', [compare]); 812 callMethod('sort', [compare]);
359 } 813 }
360 } 814 }
361 815
362 /** 816 /**
363 * Placeholder object for cases where we need to determine exactly how many 817 * Placeholder object for cases where we need to determine exactly how many
364 * args were passed to a function. 818 * args were passed to a function.
365 */ 819 */
366 const _UNDEFINED = const Object(); 820 const _UNDEFINED = const Object();
367 821
368 // FIXME(jacobr): this method is a hack to work around the lack of proper dart 822 // TODO(jacobr): this method is a hack to work around the lack of proper dart
369 // support for varargs methods. 823 // support for varargs methods.
370 List _stripUndefinedArgs(List args) => 824 List _stripUndefinedArgs(List args) =>
371 args.takeWhile((i) => i != _UNDEFINED).toList(); 825 args.takeWhile((i) => i != _UNDEFINED).toList();
372 826
373 /** 827 /**
374 * Returns a method that can be called with an arbitrary number (for n less 828 * Returns a method that can be called with an arbitrary number (for n less
375 * than 11) of arguments without violating Dart type checks. 829 * than 11) of arguments without violating Dart type checks.
376 */ 830 */
377 Function _wrapAsDebuggerVarArgsFunction(JsFunction jsFunction) => 831 Function _wrapAsDebuggerVarArgsFunction(JsFunction jsFunction) =>
378 ([a1=_UNDEFINED, a2=_UNDEFINED, a3=_UNDEFINED, a4=_UNDEFINED, 832 ([a1 = _UNDEFINED, a2 = _UNDEFINED, a3 = _UNDEFINED, a4 = _UNDEFINED,
379 a5=_UNDEFINED, a6=_UNDEFINED, a7=_UNDEFINED, a8=_UNDEFINED, 833 a5 = _UNDEFINED, a6 = _UNDEFINED, a7 = _UNDEFINED, a8 = _UNDEFINED,
380 a9=_UNDEFINED, a10=_UNDEFINED]) => 834 a9 = _UNDEFINED, a10 = _UNDEFINED]) => jsFunction._applyDebuggerOnly(
381 jsFunction._applyDebuggerOnly(_stripUndefinedArgs( 835 _stripUndefinedArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10]));
382 [a1,a2,a3,a4,a5,a6,a7,a8,a9,a10]));
OLDNEW
« no previous file with comments | « sdk/lib/html/dartium/html_dartium.dart ('k') | tests/html/html.status » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698