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

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, 6 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
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 // TODO(jacobr): if we care about unchecked mode in Dartium we need to set this
96 // to false in unchecked 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 Set<Type> _jsInterfaceTypes = new Set<Type>();
104 Iterable<Type> get jsInterfaceTypes => _jsInterfaceTypes.toList();
105
106 bool _finalized = false;
107
108 class _DeclarationSet {
109 _DeclarationSet() : _methods = <mirrors.MethodMirror>[];
110
111 bool checkType(obj, type) {
112 if (obj == null) return true;
113 return mirrors.reflectType(obj.runtimeType).isSubtypeOf(type);
114 }
115
116 bool checkReturnType(value) {
117 if (value == null) return true;
118 var valueMirror = mirrors.reflectType(value.runtimeType);
119 for (var method in _methods) {
120 if (method.isGetter) {
121 // FIXME: actually check return types for getters that return function
122 // types.
123 return true;
124 } else {
125 if (valueMirror.isSubtypeOf(method.returnType)) return true;
126 }
127 }
128 return false;
129 }
130
131 bool _checkDeclaration(Invocation invocation, mirrors.MethodMirror method) {
132 if (method.isGetter) {
133 // FIXME: actually check method types against the function type returned
134 // by the getter.
135 return true;
136 }
137 var parameters = method.parameters;
138 var positionalArguments = invocation.positionalArguments;
139 // Too many arguments
140 if (parameters.length < positionalArguments.length) return false;
141 // Too few required arguments.
142 if (parameters.length > positionalArguments.length &&
143 !parameters[positionalArguments.length].isOptional) return false;
144 var endPositional = math.min(parameters.length, positionalArguments.length);
145 for (var i = 0; i < endPositional; i++) {
146 if (parameters[i].isNamed) {
147 // Not enough positional arguments.
148 return false;
149 }
150 if (!checkType(invocation.positionalArguments[i], parameters[i].type))
151 return false;
152 }
153 if (invocation.namedArguments.isNotEmpty) {
154 var startPositional;
155 for (startPositional = parameters.length -1 ; startPositional >= 0; startP ositional--) {
156 if (!parameters[startPositional].isNamed) break;
157 }
158 startPositional++;
159
160 for (var name in invocation.namedArguments.keys) {
161 bool match = false;
162 for (var j = startPositional; j < parameters.length; j++) {
163 var p = parameters[j];
164 if (p.simpleName == name) {
165 if (!checkType(invocation.namedArguments[name], parameters[j].type))
166 return false;
167 match = true;
168 break;
169 }
170 }
171 if (match == false)
172 return false;
173 }
174
175 }
176 return true;
177 }
178
179 bool checkInvocation(Invocation invocation) {
180 for (var method in _methods) {
181 if (_checkDeclaration(invocation, method)) return true;
182 }
183 return false;
184 }
185
186 void add(mirrors.MethodMirror mirror) {
187 _methods.add(mirror);
188 }
189
190 final List<mirrors.MethodMirror> _methods;
191 }
192
193 /**
194 * Temporary method that we hope to remove at some point. Should only be called by codegen.
195 */
196 void registerJsInterfaces(List<Type> classes) {
197 if (_finalized == true) {
198 throw 'JSInterop class registration already finalized';
199 }
200 Map<Symbol, List<mirrors.DeclarationMirror>> allMembers;
201 for (Type type in classes) {
202 if (!_jsInterfaceTypes.add(type)) continue; // Already registered.
203 mirrors.ClassMirror typeMirror = mirrors.reflectType(type);
204 typeMirror.declarations.forEach((symbol, declaration) {
205 if (declaration is mirrors.MethodMirror && !declaration.isStatic) {
206 if (declaration.isGetter) {
207 _allowedGetters.putIfAbsent(symbol, () => new _DeclarationSet()).add(d eclaration);
208 _allowedMethods.putIfAbsent(symbol, () => new _DeclarationSet()).add(d eclaration);
209 } else if (declaration.isSetter) {
210 _allowedSetters.putIfAbsent(symbol, () => new _DeclarationSet()).add(d eclaration);
211 } else if (!declaration.isConstructor) {
212 _allowedMethods.putIfAbsent(symbol, () => new _DeclarationSet()).add(d eclaration);
213 }
214 }
215 });
216 }
217 }
218
219 _finalizeJsInterfaces() native "Js_finalizeJsInterfaces";
220
221 /**
222 * Generates a part file defining source code for JsObjectImpl and related
223 * classes. This is needed so that type checks for all registered JavaScript
224 * interop classes pass.
225 */
226 String _generateJsObjectImplPart() {
227 Iterable<Type> types = jsInterfaceTypes;
228 var libraryPrefixes = new Map<mirrors.LibraryMirror, String>();
229 var prefixNames = new Set<String>();
230 var sb = new StringBuffer();
231
232 var implements = <String>[];
233 for (var type in types) {
234 mirrors.ClassMirror typeMirror = mirrors.reflectType(type);
235 mirrors.LibraryMirror libraryMirror = typeMirror.owner;
236 var prefixName;
237 if (libraryPrefixes.containsKey(libraryMirror)) {
238 prefixName = libraryPrefixes[libraryMirror];
239 } else {
240 var basePrefixName = mirrors.MirrorSystem.getName(libraryMirror.simpleName );
241 basePrefixName = basePrefixName.replaceAll('.', '_');
242 if (basePrefixName.isEmpty) basePrefixName = "lib";
243 prefixName = basePrefixName;
244 var i = 1;
245 while (prefixNames.contains(prefixName)) {
246 prefixName = '$basePrefixName$i';
247 i++;
248 }
249 prefixNames.add(prefixName);
250 libraryPrefixes[libraryMirror] = prefixName;
251 }
252 implements.add('${prefixName}.${mirrors.MirrorSystem.getName(typeMirror.simp leName)}');
253 }
254 libraryPrefixes.forEach((libraryMirror, prefix) {
255 sb.writeln('import "${libraryMirror.uri}" as $prefix;');
256 });
257 var implementsClause = implements.isEmpty ? "" : "implements ${implements.joi n(', ')}";
258 // TODO(jacobr): only certain classes need to be implemented by
259 // Function and Array.
260 sb.write('''
261 class JsObjectImpl extends JsObject $implementsClause {
262 JsObjectImpl.internal() : super.internal();
263 }
264
265 class JsFunctionImpl extends JsFunction $implementsClause {
266 JsFunctionImpl.internal() : super.internal();
267 }
268
269 class JsArrayImpl<E> extends JsArray<E> $implementsClause {
270 JsArrayImpl.internal() : super.internal();
271 }
272 ''');
273 return sb.toString();
274 }
275
276 // Start of block of helper methods to emulate JavaScript Array methods on Dart List.
277 // TODO(jacobr): match JS more closely.
278 String _toStringJs(obj) => '$obj';
279
280 // TODO(jacobr): this might not exactly match JS semantics.
281 int _toIntJs(obj) {
282 if (obj is int) return obj;
283 if (obj is num) return obj.toInt();
284 return num.parse('$obj'.trim(), (_) => 0).toInt();
285 }
286
287 // Helper to match the behavior of setting List length in JavaScript.
288 int _setListLength(List list, int len) {
289 for (var i = list.length; i < len; i++) {
290 list.add(null);
291 }
292 return len;
293 }
294
295 // TODO(jacobr): should we really bother with this method instead of just
296 // shallow copying to a JS array and using the join method?
297 String _arrayJoin(List list, sep) {
298 if (sep == null) {
299 sep = ",";
300 }
301 return list.map((e) => e == null ? "" : e.toString()).join(sep.toString());
302 }
303
304 // TODO(jacobr): should we really bother with this method instead of just
305 // shallow copying to a JS array and using the toString method?
306 String _arrayToString(List list) => _arrayJoin(list, ",");
307
308 int _arrayPush(List list, e) {
309 list.add(e);
310 return list.length;
311 }
312
313 _arrayPop(List list) => list.removeLast();
314
315 // TODO(jacobr): would it be better to just copy input to a JS List
316 // and call Array.concat?
317 List _arrayConcat(List input, List args) {
318 var ret = new List.from(input);
319 for (var e in args) {
320 // TODO(jacobr): should we use Iterable ?
321 if (e is List) { // FIXME use Symbol.isConcatSpreadable
322 ret.addAll(e);
323 } else {
324 ret.add(e);
325 }
326 }
327 return ret;
328 }
329
330 List _arraySplice(List input, List args) {
331 int start = 0;
332 if (args.length > 0) {
333 var rawStart = _toIntJs(args[0]);
334 if (rawStart < 0) {
335 start = math.max(0, input.length - rawStart);
336 } else {
337 start = math.min(input.length, rawStart);
338 }
339 }
340 var end = start;
341 if (args.length > 1) {
342 var rawDeleteCount = _toIntJs(args[1]);
343 if (rawDeleteCount < 0) rawDeleteCount = 0;
344 end = math.min(input.length, start + rawDeleteCount);
345 }
346 var replacement = [];
347 var removedElements = input.getRange(start, end).toList();
348 if (args.length > 2) {
349 replacement = args.getRange(2, args.length);
350 }
351 input.replaceRange(start, end, replacement);
352 return removedElements;
353 }
354
355 List _arrayReverse(List l) {
356 for (var i = 0, j = l.length - 1; i < j; i++, j--) {
357 var tmp = l[i];
358 l[i] = l[j];
359 l[j] = tmp;
360 }
361 return l;
362 }
363
364 _arrayShift(List l) {
365 if (l.isEmpty) return null; // Really want to return JS undefined.
366 return l.removeAt(0);
367 }
368
369 int _arrayUnshift(List l, List args) {
370 l.insertAll(0, args);
371 return l.length;
372 }
373
374 _arrayExtend(List l, int newLength) {
375 for (int i = l.length; i < newLength; i++) {
376 // TODO(jacobr): we'd really like to add undefined to better match
377 // JavaScript semantics.
378 l.add(null);
379 }
380 }
381
382 List _arraySort(List l, rawCompare) {
383 // TODO(jacobr): alternately we could just copy the Array to JavaScript,
384 // invoke the JS sort method and then copy the result back to Dart.
385 Comparator compare;
386 if (rawCompare == null) {
387 compare = (a, b) => _toStringJs(a).compareTo(_toStringJs(b));
388 } else if (rawCompare is JsFunction) {
389 compare = (a, b) => rawCompare.apply([a, b]);
390 } else {
391 compare = rawCompare;
392 }
393 l.sort(compare);
394 return l;
395 }
396 // End of block of helper methods to emulate JavaScript Array methods on Dart Li st.
397
398 /**
399 * Must be called before JS Interfaces can be used safely used and cross cast.
400 */
401 void finalizeJsInterfaces() {
402 if (_finalized == true) {
403 throw 'JSInterop class registration already finalized';
404 }
405 _finalized = true;
406 _finalizeJsInterfaces();
407 }
92 408
93 JsObject _cachedContext; 409 JsObject _cachedContext;
94 410
95 JsObject get _context native "Js_context_Callback"; 411 JsObject get _context native "Js_context_Callback";
96 412
97 JsObject get context { 413 JsObject get context {
98 if (_cachedContext == null) { 414 if (_cachedContext == null) {
99 _cachedContext = _context; 415 _cachedContext = _context;
100 } 416 }
101 return _cachedContext; 417 return _cachedContext;
102 } 418 }
103 419
104 /** 420 /**
105 * Proxies a JavaScript object to Dart. 421 * Proxies a JavaScript object to Dart.
106 * 422 *
107 * The properties of the JavaScript object are accessible via the `[]` and 423 * The properties of the JavaScript object are accessible via the `[]` and
108 * `[]=` operators. Methods are callable via [callMethod]. 424 * `[]=` operators. Methods are callable via [callMethod].
109 */ 425 */
110 class JsObject extends NativeFieldWrapperClass2 { 426 class JsObject extends NativeFieldWrapperClass2 {
111 JsObject.internal(); 427 JsObject.internal();
112 428
113 /** 429 /**
114 * Constructs a new JavaScript object from [constructor] and returns a proxy 430 * Constructs a new JavaScript object from [constructor] and returns a proxy
115 * to it. 431 * to it.
116 */ 432 */
117 factory JsObject(JsFunction constructor, [List arguments]) => _create(construc tor, arguments); 433 factory JsObject(JsFunction constructor, [List arguments]) => _create(construc tor, arguments);
118 434
119 static JsObject _create(JsFunction constructor, arguments) native "JsObject_co nstructorCallback"; 435 static JsObject _create(JsFunction constructor, arguments) native "JsObject_co nstructorCallback";
120 436
437 _buildArgs(Invocation invocation) {
438 if (invocation.namedArguments.isEmpty) {
439 return invocation.positionalArguments;
440 } else {
441 var varArgs = new Map<String,Object>();
442 invocation.namedArguments.forEach((symbol, val) {
443 varArgs[mirrors.MirrorSystem.getName(symbol)] = val;
444 });
445 return invocation.positionalArguments.toList()..add(new JsObject.jsify(var Args));
446 }
447 }
448
121 /** 449 /**
122 * Constructs a [JsObject] that proxies a native Dart object; _for expert use 450 * Constructs a [JsObject] that proxies a native Dart object; _for expert use
123 * only_. 451 * only_.
124 * 452 *
125 * Use this constructor only if you wish to get access to JavaScript 453 * 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 454 * properties attached to a browser host object, such as a Node or Blob, that
127 * is normally automatically converted into a native Dart object. 455 * is normally automatically converted into a native Dart object.
128 * 456 *
129 * An exception will be thrown if [object] either is `null` or has the type 457 * An exception will be thrown if [object] either is `null` or has the type
130 * `bool`, `num`, or `String`. 458 * `bool`, `num`, or `String`.
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after
225 return _callMethod(method, args); 553 return _callMethod(method, args);
226 } catch(e) { 554 } catch(e) {
227 if (hasProperty(method)) { 555 if (hasProperty(method)) {
228 rethrow; 556 rethrow;
229 } else { 557 } else {
230 throw new NoSuchMethodError(this, new Symbol(method), args, null); 558 throw new NoSuchMethodError(this, new Symbol(method), args, null);
231 } 559 }
232 } 560 }
233 } 561 }
234 562
563 noSuchMethod(Invocation invocation) {
564 throwError() {
565 throw new NoSuchMethodError(this, invocation.memberName, invocation.positi onalArguments, invocation.namedArguments);
566 }
567
568 String name = mirrors.MirrorSystem.getName(invocation.memberName);
569 if (invocation.isGetter) {
570 if (_CHECK_JS_INVOCATIONS ) {
571 var matches = _allowedGetters[invocation.memberName];
572 if (matches == null && !_allowedMethods.containsKey(invocation.memberNam e)) {
573 throwError();
574 }
575 var ret = this[name];
576 if (matches != null && matches.checkReturnType(ret)) return ret;
577 if (ret is Function || (ret is JsFunction /* shouldn't be needed in the future*/) && _allowedMethods.containsKey(invocation.memberName))
578 return ret; // Warning: we have not bound "this"... we could type chec k on the Function but that is of little value in Dart.
579 throwError();
580 } else {
581 // TODO(jacobr): should we throw if the JavaScript object doesn't have t he property?
582 return this[name];
583 }
584 } else if (invocation.isSetter) {
585 if (_CHECK_JS_INVOCATIONS ) {
586 var matches = _allowedSetters[invocation.memberName];
587 if (!matches.checkInvocation(invocation)) throwError();
588 }
589 return this[name] = invocation.positionalArguments.first;
590 } else {
591 // TODO(jacobr): also allow calling getters that look like functions.
592 var matches;
593 if (_CHECK_JS_INVOCATIONS ) {
594 matches = _allowedMethods[invocation.memberName];
595 if (matches == null || !matches.checkInvocation(invocation)) throwError( );
596 }
597 var ret = this.callMethod(name, _buildArgs(invocation));
598 if (_CHECK_JS_INVOCATIONS ) {
599 if (!matches.checkReturnType(ret)) throwError();
600 }
601 return ret;
602 }
603 }
604
235 _callMethod(String name, List args) native "JsObject_callMethod"; 605 _callMethod(String name, List args) native "JsObject_callMethod";
236 } 606 }
237 607
238 /** 608 /**
239 * Proxies a JavaScript Function object. 609 * Proxies a JavaScript Function object.
240 */ 610 */
241 class JsFunction extends JsObject { 611 class JsFunction extends JsObject implements Function {
242 JsFunction.internal() : super.internal(); 612 JsFunction.internal() : super.internal();
243 613
244 /** 614 /**
245 * Returns a [JsFunction] that captures its 'this' binding and calls [f] 615 * Returns a [JsFunction] that captures its 'this' binding and calls [f]
246 * with the value of this passed as the first argument. 616 * with the value of this passed as the first argument.
247 */ 617 */
248 factory JsFunction.withThis(Function f) => _withThis(f); 618 factory JsFunction.withThis(Function f) => _withThis(f);
249 619
250 /** 620 /**
251 * Invokes the JavaScript function with arguments [args]. If [thisArg] is 621 * Invokes the JavaScript function with arguments [args]. If [thisArg] is
252 * supplied it is the value of `this` for the invocation. 622 * supplied it is the value of `this` for the invocation.
253 */ 623 */
254 dynamic apply(List args, {thisArg}) native "JsFunction_apply"; 624 dynamic apply(List args, {thisArg}) native "JsFunction_apply";
255 625
626 noSuchMethod(Invocation invocation) {
627 if (invocation.isMethod && invocation.memberName == #call) {
628 return apply(_buildArgs(invocation));
629 }
630 return super.noSuchMethod(invocation);
631 }
632
256 /** 633 /**
257 * Internal only version of apply which uses debugger proxies of Dart objects 634 * 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 635 * rather than opaque handles. This method is private because it cannot be
259 * efficiently implemented in Dart2Js so should only be used by internal 636 * efficiently implemented in Dart2Js so should only be used by internal
260 * tools. 637 * tools.
261 */ 638 */
262 _applyDebuggerOnly(List args, {thisArg}) native "JsFunction_applyDebuggerOnly" ; 639 _applyDebuggerOnly(List args, {thisArg}) native "JsFunction_applyDebuggerOnly" ;
263 640
264 static JsFunction _withThis(Function f) native "JsFunction_withThis"; 641 static JsFunction _withThis(Function f) native "JsFunction_withThis";
265 } 642 }
266 643
267 /** 644 /**
268 * A [List] proxying a JavaScript Array. 645 * A [List] proxying a JavaScript Array.
269 */ 646 */
270 class JsArray<E> extends JsObject with ListMixin<E> { 647 class JsArray<E> extends JsObject with ListMixin<E> {
648 JsArray.internal() : super.internal();
271 649
272 factory JsArray() => _newJsArray(); 650 factory JsArray() => _newJsArray();
273 651
274 static JsArray _newJsArray() native "JsArray_newJsArray"; 652 static JsArray _newJsArray() native "JsArray_newJsArray";
275 653
276 factory JsArray.from(Iterable<E> other) => _newJsArrayFromSafeList(new List.fr om(other)); 654 factory JsArray.from(Iterable<E> other) => _newJsArrayFromSafeList(new List.fr om(other));
277 655
278 static JsArray _newJsArrayFromSafeList(List list) native "JsArray_newJsArrayFr omSafeList"; 656 static JsArray _newJsArrayFromSafeList(List list) native "JsArray_newJsArrayFr omSafeList";
279 657
280 _checkIndex(int index, {bool insert: false}) { 658 _checkIndex(int index, {bool insert: false}) {
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after
373 /** 751 /**
374 * Returns a method that can be called with an arbitrary number (for n less 752 * Returns a method that can be called with an arbitrary number (for n less
375 * than 11) of arguments without violating Dart type checks. 753 * than 11) of arguments without violating Dart type checks.
376 */ 754 */
377 Function _wrapAsDebuggerVarArgsFunction(JsFunction jsFunction) => 755 Function _wrapAsDebuggerVarArgsFunction(JsFunction jsFunction) =>
378 ([a1=_UNDEFINED, a2=_UNDEFINED, a3=_UNDEFINED, a4=_UNDEFINED, 756 ([a1=_UNDEFINED, a2=_UNDEFINED, a3=_UNDEFINED, a4=_UNDEFINED,
379 a5=_UNDEFINED, a6=_UNDEFINED, a7=_UNDEFINED, a8=_UNDEFINED, 757 a5=_UNDEFINED, a6=_UNDEFINED, a7=_UNDEFINED, a8=_UNDEFINED,
380 a9=_UNDEFINED, a10=_UNDEFINED]) => 758 a9=_UNDEFINED, a10=_UNDEFINED]) =>
381 jsFunction._applyDebuggerOnly(_stripUndefinedArgs( 759 jsFunction._applyDebuggerOnly(_stripUndefinedArgs(
382 [a1,a2,a3,a4,a5,a6,a7,a8,a9,a10])); 760 [a1,a2,a3,a4,a5,a6,a7,a8,a9,a10]));
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698