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 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
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; | 92 import 'dart:math' as math; |
93 import 'dart:mirrors' as mirrors; | 93 import 'dart:mirrors' as mirrors; |
94 import 'dart:html' as html; | 94 import 'dart:html' as html; |
| 95 import 'dart:indexed_db' as indexed_db; |
| 96 import 'dart:typed_data'; |
95 | 97 |
96 // Pretend we are always in checked mode as we aren't interested in users | 98 // Pretend we are always in checked mode as we aren't interested in users |
97 // running Dartium code outside of checked mode. | 99 // running Dartium code outside of checked mode. |
98 final bool CHECK_JS_INVOCATIONS = true; | 100 final bool CHECK_JS_INVOCATIONS = true; |
99 | 101 |
100 final _allowedMethods = new Map<Symbol, _DeclarationSet>(); | 102 final _allowedMethods = new Map<Symbol, _DeclarationSet>(); |
101 final _allowedGetters = new Map<Symbol, _DeclarationSet>(); | 103 final _allowedGetters = new Map<Symbol, _DeclarationSet>(); |
102 final _allowedSetters = new Map<Symbol, _DeclarationSet>(); | 104 final _allowedSetters = new Map<Symbol, _DeclarationSet>(); |
103 | 105 |
104 final _jsInterfaceTypes = new Set<Type>(); | 106 final _jsInterfaceTypes = new Set<Type>(); |
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
197 _members.add(mirror); | 199 _members.add(mirror); |
198 } | 200 } |
199 | 201 |
200 final List<mirrors.DeclarationMirror> _members; | 202 final List<mirrors.DeclarationMirror> _members; |
201 } | 203 } |
202 | 204 |
203 /** | 205 /** |
204 * Temporary method that we hope to remove at some point. This method should | 206 * Temporary method that we hope to remove at some point. This method should |
205 * generally only be called by machine generated code. | 207 * generally only be called by machine generated code. |
206 */ | 208 */ |
207 void registerJsInterfaces(List<Type> classes) { | 209 |
208 if (_finalized == true) { | 210 void registerJsInterfaces([List<Type> classes]) { |
209 throw 'JSInterop class registration already finalized'; | 211 // This method is now obsolete in Dartium. |
210 } | 212 } |
| 213 |
| 214 void _registerJsInterfaces(List<Type> classes) { |
211 for (Type type in classes) { | 215 for (Type type in classes) { |
212 if (!_jsInterfaceTypes.add(type)) continue; // Already registered. | 216 if (!_jsInterfaceTypes.add(type)) continue; // Already registered. |
213 mirrors.ClassMirror typeMirror = mirrors.reflectType(type); | 217 mirrors.ClassMirror typeMirror = mirrors.reflectType(type); |
214 typeMirror.declarations.forEach((symbol, declaration) { | 218 typeMirror.declarations.forEach((symbol, declaration) { |
215 if (declaration is mirrors.MethodMirror || | 219 if (declaration is mirrors.MethodMirror || |
216 declaration is mirrors.VariableMirror && !declaration.isStatic) { | 220 declaration is mirrors.VariableMirror && !declaration.isStatic) { |
217 bool treatAsGetter = false; | 221 bool treatAsGetter = false; |
218 bool treatAsSetter = false; | 222 bool treatAsSetter = false; |
219 if (declaration is mirrors.VariableMirror) { | 223 if (declaration is mirrors.VariableMirror) { |
220 treatAsGetter = true; | 224 treatAsGetter = true; |
(...skipping 24 matching lines...) Expand all Loading... |
245 .putIfAbsent(symbol, () => new _DeclarationSet()) | 249 .putIfAbsent(symbol, () => new _DeclarationSet()) |
246 .add(declaration); | 250 .add(declaration); |
247 } | 251 } |
248 } | 252 } |
249 }); | 253 }); |
250 } | 254 } |
251 } | 255 } |
252 | 256 |
253 _finalizeJsInterfaces() native "Js_finalizeJsInterfaces"; | 257 _finalizeJsInterfaces() native "Js_finalizeJsInterfaces"; |
254 | 258 |
| 259 String _getJsName(mirrors.DeclarationMirror mirror) { |
| 260 for (var annotation in mirror.metadata) { |
| 261 if (mirrors.MirrorSystem.getName(annotation.type.simpleName) == "Js") { |
| 262 mirrors.LibraryMirror library = annotation.type.owner; |
| 263 var uri = library.uri; |
| 264 // make sure the annotation is from package://js |
| 265 if (uri.scheme == 'package' && uri.path == 'js/js.dart') { |
| 266 try { |
| 267 var name = annotation.reflectee.name; |
| 268 return name != null ? name : ""; |
| 269 } catch (e) { |
| 270 } |
| 271 } |
| 272 } |
| 273 } |
| 274 return null; |
| 275 } |
| 276 |
| 277 bool _hasJsName(mirrors.DeclarationMirror mirror) => |
| 278 _getJsName(mirror) != null; |
| 279 |
| 280 |
| 281 _getJsMemberName(mirrors.DeclarationMirror mirror) { |
| 282 var name = _getJsName(mirror); |
| 283 return name == null || name.isEmpty ? _getDeclarationName(mirror) : |
| 284 name; |
| 285 } |
| 286 |
| 287 // TODO(jacobr): handle setters correctyl. |
| 288 String _getDeclarationName(mirrors.DeclarationMirror declaration) { |
| 289 var name = mirrors.MirrorSystem.getName(declaration.simpleName); |
| 290 if (declaration is mirrors.MethodMirror && declaration.isSetter) { |
| 291 assert(name.endsWith("=")); |
| 292 name = name.substring(0, name.length - 1); |
| 293 } |
| 294 return name; |
| 295 } |
| 296 |
| 297 final _JS_LIBRARY_PREFIX = "js_library"; |
| 298 final _UNDEFINED_VAR = "_UNDEFINED_JS_CONST"; |
| 299 |
| 300 String _accessJsPath(String path) => |
| 301 "${_JS_LIBRARY_PREFIX}.context${path.split(".").map((p) => "['$p']").join(''
)}"; |
| 302 |
| 303 void addMemberHelper(mirrors.MethodMirror declaration, String path, StringBuffer
sb, {bool isStatic: false, String memberName}) { |
| 304 var jsName = _getJsMemberName(declaration); |
| 305 path = (path != null && path.isNotEmpty) ? "${path}.${jsName}" : jsName; |
| 306 var name = memberName != null ? memberName : _getDeclarationName(declaration); |
| 307 if (declaration.isConstructor) { |
| 308 sb.write("factory"); |
| 309 } else if (isStatic) { |
| 310 sb.write("static"); |
| 311 } else { |
| 312 sb.write("patch"); |
| 313 } |
| 314 sb.write(" "); |
| 315 if (declaration.isGetter) { |
| 316 sb.write("get $name => ${_accessJsPath(path)};"); |
| 317 } else if (declaration.isSetter) { |
| 318 sb.write("set $name(v) => ${_accessJsPath(path)} = v;"); |
| 319 } else { |
| 320 sb.write("$name("); |
| 321 bool hasOptional = false; |
| 322 int i = 0; |
| 323 var args = <String>[]; |
| 324 for (var p in declaration.parameters) { |
| 325 assert(!p.isNamed); // XXX throw |
| 326 assert(!p.hasDefaultValue); |
| 327 if (i > 0) { |
| 328 sb.write(", "); |
| 329 } |
| 330 if (p.isOptional && !hasOptional) { |
| 331 sb.write("["); |
| 332 hasOptional = true; |
| 333 } |
| 334 var arg = "p$i"; |
| 335 args.add(arg); |
| 336 sb.write(arg); |
| 337 if (p.isOptional) { |
| 338 sb.write("=${_UNDEFINED_VAR}"); |
| 339 } |
| 340 i++; |
| 341 } |
| 342 if (hasOptional) { |
| 343 sb.write("]"); |
| 344 } |
| 345 // TODO(jacobr): |
| 346 sb.write(") => "); |
| 347 if (declaration.isConstructor) { |
| 348 sb.write("new ${_JS_LIBRARY_PREFIX}.JsObject("); |
| 349 } |
| 350 sb |
| 351 ..write(_accessJsPath(path)) |
| 352 ..write(declaration.isConstructor ? "," : ".apply(") |
| 353 ..write("[${args.join(",")}]"); |
| 354 |
| 355 if (hasOptional) { |
| 356 sb.write(".takeWhile((i) => i != ${_UNDEFINED_VAR}).toList()"); |
| 357 } |
| 358 sb.write(");"); |
| 359 } |
| 360 sb.write("\n"); |
| 361 } |
| 362 |
| 363 // TODO(jacobr): make this check more robust. |
| 364 bool _isExternal(mirrors.MethodMirror mirror) => |
| 365 mirror.source != null && mirror.source.startsWith("external "); |
| 366 |
| 367 List<String> _generateExternalMethods() { |
| 368 var staticCodegen = <String>[]; |
| 369 mirrors.currentMirrorSystem().libraries.forEach((uri, library) { |
| 370 var sb = new StringBuffer(); |
| 371 String jsLibraryName = _getJsName(library); |
| 372 library.declarations.forEach((name, declaration) { |
| 373 if (declaration is mirrors.MethodMirror) { |
| 374 if (_isExternal(declaration) && (_hasJsName(declaration) || jsLibraryNam
e != null)) { |
| 375 addMemberHelper(declaration, jsLibraryName, sb); |
| 376 } |
| 377 } else if (declaration is mirrors.ClassMirror) { |
| 378 mirrors.ClassMirror clazz = declaration; |
| 379 if (_hasJsName(clazz)) { |
| 380 // TODO(jacobr): verify class implements JavaScriptObject. |
| 381 assert(clazz.hasReflectedType); |
| 382 jsInterfaceTypes.add(clazz.reflectedType); |
| 383 String jsClassName = _getJsMemberName(clazz); |
| 384 var className = mirrors.MirrorSystem.getName(clazz.simpleName); |
| 385 var sbPatch = new StringBuffer(); |
| 386 clazz.declarations.forEach((name, declaration) { |
| 387 if (declaration is! mirrors.MethodMirror || !_isExternal(declaration
)) return; |
| 388 if (declaration.isFactoryConstructor) { |
| 389 sbPatch.write(" factory ${className}({"); |
| 390 int i = 0; |
| 391 var args = <String>[]; |
| 392 for (var p in declaration.parameters) { |
| 393 assert(p.isNamed); // XXX throw |
| 394 args.add(mirrors.MirrorSystem.getName(p.simpleName)); |
| 395 i++; |
| 396 } |
| 397 sbPatch |
| 398 ..write(args.map((name) => '$name:${_UNDEFINED_VAR}').join(", ")
) |
| 399 ..write("}) {\n" |
| 400 " var ret = new ${_JS_LIBRARY_PREFIX}.JsObject.jsify(
{});\n"); |
| 401 i = 0; |
| 402 for (var p in declaration.parameters) { |
| 403 assert(p.isNamed); // XXX throw |
| 404 var name = args[i]; |
| 405 var jsName = mirrors.MirrorSystem.getName(p.simpleName); |
| 406 // XXX apply name conversion rules. |
| 407 sbPatch.write(" if($name != ${_UNDEFINED_VAR}) ret['$jsName']
= $name;\n"); |
| 408 i++; |
| 409 } |
| 410 |
| 411 sbPatch.write(" return ret;\n" |
| 412 " }\n"); |
| 413 } else if (declaration.isConstructor) { |
| 414 sbPatch.write(" "); |
| 415 addMemberHelper(declaration, |
| 416 (jsLibraryName != null && jsLibraryName.isNotEmpty) ? "${jsLib
raryName}" : "", |
| 417 sbPatch, |
| 418 isStatic: true, |
| 419 memberName: className); |
| 420 } |
| 421 }); |
| 422 |
| 423 clazz.staticMembers.forEach((memberName, member) { |
| 424 if (_isExternal(member)) { |
| 425 sbPatch.write(" "); |
| 426 addMemberHelper(member, |
| 427 (jsLibraryName != null && jsLibraryName.isNotEmpty) ? "${jsLib
raryName}.${jsClassName}" : jsClassName, |
| 428 sbPatch, |
| 429 isStatic: true); |
| 430 } |
| 431 }); |
| 432 if (sbPatch.isNotEmpty) { |
| 433 sb.write(""" |
| 434 patch class $className { |
| 435 $sbPatch |
| 436 } |
| 437 """); |
| 438 } |
| 439 } |
| 440 } |
| 441 }); |
| 442 if (sb.isNotEmpty) { |
| 443 staticCodegen |
| 444 ..add(uri.toString()) |
| 445 ..add("${uri}_js_interop_patch.dart") |
| 446 ..add(""" |
| 447 import 'dart:js' as ${_JS_LIBRARY_PREFIX}; |
| 448 |
| 449 /** |
| 450 * Placeholder object for cases where we need to determine exactly how many |
| 451 * args were passed to a function. |
| 452 */ |
| 453 const ${_UNDEFINED_VAR} = const Object(); |
| 454 |
| 455 ${sb} |
| 456 """); |
| 457 } |
| 458 }); |
| 459 |
| 460 return staticCodegen; |
| 461 } |
| 462 |
255 /** | 463 /** |
256 * Generates a part file defining source code for JsObjectImpl and related | 464 * Generates a part file defining source code for JsObjectImpl and related |
257 * classes. This calass is needed so that type checks for all registered JavaScr
ipt | 465 * classes. This calass is needed so that type checks for all registered JavaScr
ipt |
258 * interop classes pass. | 466 * interop classes pass. |
259 */ | 467 */ |
260 String _generateJsObjectImplPart() { | 468 List<String> _generateInteropPatchFiles() { |
261 Iterable<Type> types = jsInterfaceTypes; | 469 var ret = _generateExternalMethods(); |
262 var libraryPrefixes = new Map<mirrors.LibraryMirror, String>(); | 470 var libraryPrefixes = new Map<mirrors.LibraryMirror, String>(); |
263 var prefixNames = new Set<String>(); | 471 var prefixNames = new Set<String>(); |
264 var sb = new StringBuffer(); | 472 var sb = new StringBuffer(); |
265 | 473 |
266 var implements = <String>[]; | 474 var implements = <String>[]; |
267 for (var type in types) { | 475 var implementsArray = <String>[]; |
| 476 var listMirror = mirrors.reflectType(List); |
| 477 |
| 478 for (var type in jsInterfaceTypes) { |
268 mirrors.ClassMirror typeMirror = mirrors.reflectType(type); | 479 mirrors.ClassMirror typeMirror = mirrors.reflectType(type); |
269 mirrors.LibraryMirror libraryMirror = typeMirror.owner; | 480 mirrors.LibraryMirror libraryMirror = typeMirror.owner; |
270 var prefixName; | 481 var prefixName; |
271 if (libraryPrefixes.containsKey(libraryMirror)) { | 482 if (libraryPrefixes.containsKey(libraryMirror)) { |
272 prefixName = libraryPrefixes[libraryMirror]; | 483 prefixName = libraryPrefixes[libraryMirror]; |
273 } else { | 484 } else { |
274 var basePrefixName = | 485 var basePrefixName = |
275 mirrors.MirrorSystem.getName(libraryMirror.simpleName); | 486 mirrors.MirrorSystem.getName(libraryMirror.simpleName); |
276 basePrefixName = basePrefixName.replaceAll('.', '_'); | 487 basePrefixName = basePrefixName.replaceAll('.', '_'); |
277 if (basePrefixName.isEmpty) basePrefixName = "lib"; | 488 if (basePrefixName.isEmpty) basePrefixName = "lib"; |
278 prefixName = basePrefixName; | 489 prefixName = basePrefixName; |
279 var i = 1; | 490 var i = 1; |
280 while (prefixNames.contains(prefixName)) { | 491 while (prefixNames.contains(prefixName)) { |
281 prefixName = '$basePrefixName$i'; | 492 prefixName = '$basePrefixName$i'; |
282 i++; | 493 i++; |
283 } | 494 } |
284 prefixNames.add(prefixName); | 495 prefixNames.add(prefixName); |
285 libraryPrefixes[libraryMirror] = prefixName; | 496 libraryPrefixes[libraryMirror] = prefixName; |
286 } | 497 } |
287 implements.add( | 498 var isArray = typeMirror.isSubtypeOf(listMirror); |
| 499 (isArray ? implementsArray : implements).add( |
288 '${prefixName}.${mirrors.MirrorSystem.getName(typeMirror.simpleName)}'); | 500 '${prefixName}.${mirrors.MirrorSystem.getName(typeMirror.simpleName)}'); |
289 } | 501 } |
290 libraryPrefixes.forEach((libraryMirror, prefix) { | 502 libraryPrefixes.forEach((libraryMirror, prefix) { |
291 sb.writeln('import "${libraryMirror.uri}" as $prefix;'); | 503 sb.writeln('import "${libraryMirror.uri}" as $prefix;'); |
292 }); | 504 }); |
293 var implementsClause = | 505 buildImplementsClause(classes) => |
294 implements.isEmpty ? "" : "implements ${implements.join(', ')}"; | 506 classes.isEmpty ? "" : "implements ${classes.join(', ')}" |
| 507 var implementsClause = buildImplementsClause(implements); |
295 // TODO(jacobr): only certain classes need to be implemented by | 508 // TODO(jacobr): only certain classes need to be implemented by |
296 // Function and Array. | 509 // JsFunctionImpl. |
| 510 var allTypes = []..addAll(implements)..addAll(implementsArray); |
297 sb.write(''' | 511 sb.write(''' |
298 class JsObjectImpl extends JsObject $implementsClause { | 512 class JsObjectImpl extends JsObject $implementsClause { |
299 JsObjectImpl.internal() : super.internal(); | 513 JsObjectImpl.internal() : super.internal(); |
300 } | 514 } |
301 | 515 |
302 class JsFunctionImpl extends JsFunction $implementsClause { | 516 class JsFunctionImpl extends JsFunction $implementsClause { |
303 JsFunctionImpl.internal() : super.internal(); | 517 JsFunctionImpl.internal() : super.internal(); |
304 } | 518 } |
305 | 519 |
306 class JsArrayImpl<E> extends JsArray<E> $implementsClause { | 520 class JsArrayImpl<E> extends JsArray<E> ${buildImplementsClause(implementsArray)
} { |
307 JsArrayImpl.internal() : super.internal(); | 521 JsArrayImpl.internal() : super.internal(); |
308 } | 522 } |
| 523 |
| 524 _registerAllJsInterfaces() { |
| 525 _registerJsInterfaces([${allTypes.join(", ")}]); |
| 526 } |
| 527 |
309 '''); | 528 '''); |
310 return sb.toString(); | 529 ret..addAll(["dart:js", "JsInteropImpl.dart", sb.toString()]); |
| 530 return ret; |
311 } | 531 } |
312 | 532 |
313 // Start of block of helper methods facilitating emulating JavaScript Array | 533 // Start of block of helper methods facilitating emulating JavaScript Array |
314 // methods on Dart List objects passed to JavaScript via JS interop. | 534 // methods on Dart List objects passed to JavaScript via JS interop. |
315 // TODO(jacobr): match JS more closely. | 535 // TODO(jacobr): match JS more closely. |
316 String _toStringJs(obj) => '$obj'; | 536 String _toStringJs(obj) => '$obj'; |
317 | 537 |
318 // TODO(jacobr): this might not exactly match JS semantics but should be | 538 // TODO(jacobr): this might not exactly match JS semantics but should be |
319 // adequate for now. | 539 // adequate for now. |
320 int _toIntJs(obj) { | 540 int _toIntJs(obj) { |
(...skipping 151 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
472 | 692 |
473 bool get _finalized native "Js_interfacesFinalized_Callback"; | 693 bool get _finalized native "Js_interfacesFinalized_Callback"; |
474 | 694 |
475 JsObject get context { | 695 JsObject get context { |
476 if (_cachedContext == null) { | 696 if (_cachedContext == null) { |
477 _cachedContext = _context; | 697 _cachedContext = _context; |
478 } | 698 } |
479 return _cachedContext; | 699 return _cachedContext; |
480 } | 700 } |
481 | 701 |
| 702 _maybeWrap(o) { |
| 703 var wrapped = html.wrap_jso_no_SerializedScriptvalue(o); |
| 704 if (identical(wrapped, o)) return o; |
| 705 return (wrapped is html.Blob |
| 706 || wrapped is html.Event |
| 707 || wrapped is indexed_db.KeyRange |
| 708 || wrapped is html.ImageData |
| 709 || wrapped is html.Node |
| 710 || wrapped is TypedData |
| 711 || wrapped is html.Window) ? wrapped : o; |
| 712 } |
| 713 |
482 /** | 714 /** |
483 * Get the dart wrapper object for object. Top-level so we | 715 * Get the dart wrapper object for object. Top-level so we |
484 * we can access it from other libraries without it being | 716 * we can access it from other libraries without it being |
485 * a public instance field on JsObject. | 717 * a public instance field on JsObject. |
486 */ | 718 */ |
487 getDartHtmlWrapperFor(JsObject object) => object._dartHtmlWrapper; | 719 getDartHtmlWrapperFor(JsObject object) => object._dartHtmlWrapper; |
488 | 720 |
489 /** | 721 /** |
490 * Set the dart wrapper object for object. Top-level so we | 722 * Set the dart wrapper object for object. Top-level so we |
491 * we can access it from other libraries without it being | 723 * we can access it from other libraries without it being |
492 * a public instance field on JsObject. | 724 * a public instance field on JsObject. |
493 */ | 725 */ |
494 void setDartHtmlWrapperFor(JsObject object, wrapper) { | 726 void setDartHtmlWrapperFor(JsObject object, wrapper) { |
495 object._dartHtmlWrapper = wrapper; | 727 object._dartHtmlWrapper = wrapper; |
496 } | 728 } |
497 | 729 |
498 /** | 730 /** |
499 * Used by callMethod to get the JS object for each argument passed if the | 731 * Used by callMethod to get the JS object for each argument passed if the |
500 * argument is a Dart class instance that delegates to a DOM object. See | 732 * argument is a Dart class instance that delegates to a DOM object. See |
501 * wrap_jso defined in dart:html. | 733 * wrap_jso defined in dart:html. |
502 */ | 734 */ |
503 unwrap_jso(dartClass_instance) { | 735 unwrap_jso(dartClass_instance) { |
504 try { | 736 if (dartClass_instance is NativeFieldWrapperClass2 && dartClass_instance is! J
sObject) |
505 if (dartClass_instance != null) | 737 return dartClass_instance.blink_jsObject; |
506 return dartClass_instance is NativeFieldWrapperClass2 ? | 738 else |
507 dartClass_instance.blink_jsObject : dartClass_instance; | |
508 else | |
509 return null; | |
510 } catch(NoSuchMethodException) { | |
511 // No blink_jsObject then return the dartClass_instance is probably an | |
512 // array that was already converted to a Dart class e.g., Uint8ClampedList. | |
513 return dartClass_instance; | 739 return dartClass_instance; |
514 } | |
515 } | 740 } |
516 | 741 |
517 /** | 742 /** |
518 * Proxies a JavaScript object to Dart. | 743 * Proxies a JavaScript object to Dart. |
519 * | 744 * |
520 * The properties of the JavaScript object are accessible via the `[]` and | 745 * The properties of the JavaScript object are accessible via the `[]` and |
521 * `[]=` operators. Methods are callable via [callMethod]. | 746 * `[]=` operators. Methods are callable via [callMethod]. |
522 */ | 747 */ |
523 class JsObject extends NativeFieldWrapperClass2 { | 748 class JsObject extends NativeFieldWrapperClass2 { |
524 JsObject.internal(); | 749 JsObject.internal(); |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
597 static JsObject _fromBrowserObject(object) => html.unwrap_jso(object); | 822 static JsObject _fromBrowserObject(object) => html.unwrap_jso(object); |
598 | 823 |
599 /** | 824 /** |
600 * Returns the value associated with [property] from the proxied JavaScript | 825 * Returns the value associated with [property] from the proxied JavaScript |
601 * object. | 826 * object. |
602 * | 827 * |
603 * The type of [property] must be either [String] or [num]. | 828 * The type of [property] must be either [String] or [num]. |
604 */ | 829 */ |
605 operator [](property) { | 830 operator [](property) { |
606 try { | 831 try { |
607 return _operator_getter(property); | 832 return _maybeWrap(_operator_getter(property)); |
608 } catch (e) { | 833 } catch (e) { |
609 // Re-throw any errors (returned as a string) as a DomException. | 834 // Re-throw any errors (returned as a string) as a DomException. |
610 throw new html.DomException.jsInterop(e); | 835 throw new html.DomException.jsInterop(e); |
611 } | 836 } |
612 } | 837 } |
613 _operator_getter(property) native "JsObject_[]"; | 838 _operator_getter(property) native "JsObject_[]"; |
614 | 839 |
615 /** | 840 /** |
616 * Sets the value associated with [property] on the proxied JavaScript | 841 * Sets the value associated with [property] on the proxied JavaScript |
617 * object. | 842 * object. |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
678 String _toString() native "JsObject_toString"; | 903 String _toString() native "JsObject_toString"; |
679 | 904 |
680 /** | 905 /** |
681 * Calls [method] on the JavaScript object with the arguments [args] and | 906 * Calls [method] on the JavaScript object with the arguments [args] and |
682 * returns the result. | 907 * returns the result. |
683 * | 908 * |
684 * The type of [method] must be either [String] or [num]. | 909 * The type of [method] must be either [String] or [num]. |
685 */ | 910 */ |
686 callMethod(String method, [List args]) { | 911 callMethod(String method, [List args]) { |
687 try { | 912 try { |
688 if (args != null) { | 913 return _maybeWrap(_callMethod(method, args)); |
689 for (var i = 0; i < args.length; i++) | |
690 args[i] = unwrap_jso(args[i]); | |
691 } | |
692 return _callMethod(method, args); | |
693 } catch (e) { | 914 } catch (e) { |
694 if (hasProperty(method)) { | 915 if (hasProperty(method)) { |
695 // Return a DomException if DOM call returned an error. | 916 // Return a DomException if DOM call returned an error. |
696 throw new html.DomException.jsInterop(e); | 917 throw new html.DomException.jsInterop(e); |
697 } else { | 918 } else { |
698 throw new NoSuchMethodError(this, new Symbol(method), args, null); | 919 throw new NoSuchMethodError(this, new Symbol(method), args, null); |
699 } | 920 } |
700 } | 921 } |
701 } | 922 } |
702 | 923 |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
746 if (CHECK_JS_INVOCATIONS) { | 967 if (CHECK_JS_INVOCATIONS) { |
747 if (!matches._checkReturnType(ret)) throwError(); | 968 if (!matches._checkReturnType(ret)) throwError(); |
748 } | 969 } |
749 return ret; | 970 return ret; |
750 } | 971 } |
751 } | 972 } |
752 | 973 |
753 _callMethod(String name, List args) native "JsObject_callMethod"; | 974 _callMethod(String name, List args) native "JsObject_callMethod"; |
754 } | 975 } |
755 | 976 |
| 977 // JavaScript interop methods that do not automatically wrap to dart:html types. |
| 978 // Warning: this API is not exposed to dart:js. |
| 979 class JsNative { |
| 980 static getProperty(JsObject o, name) { |
| 981 return o._operator_getter(name); |
| 982 } |
| 983 |
| 984 static callMethod(JsObject o, String method, List args) { |
| 985 return o._callMethod(method, args); |
| 986 } |
| 987 |
| 988 static getArrayIndex(JsArray array, int index) { |
| 989 array._checkIndex(index); |
| 990 return getProperty(array, index); |
| 991 } |
| 992 |
| 993 /** |
| 994 * Same behavior as new JsFunction.withThis except that JavaScript "this" is n
ot |
| 995 * wrapped. |
| 996 */ |
| 997 static JsFunction withThis(Function f) native "JsFunction_withThisNoWrap"; |
| 998 } |
| 999 |
756 /** | 1000 /** |
757 * Proxies a JavaScript Function object. | 1001 * Proxies a JavaScript Function object. |
758 */ | 1002 */ |
759 class JsFunction extends JsObject implements Function { | 1003 class JsFunction extends JsObject implements Function { |
760 JsFunction.internal() : super.internal(); | 1004 JsFunction.internal() : super.internal(); |
761 | 1005 |
762 /** | 1006 /** |
763 * Returns a [JsFunction] that captures its 'this' binding and calls [f] | 1007 * Returns a [JsFunction] that captures its 'this' binding and calls [f] |
764 * with the value of this passed as the first argument. | 1008 * with the value of this passed as the first argument. |
765 */ | 1009 */ |
766 factory JsFunction.withThis(Function f) => _withThis(f); | 1010 factory JsFunction.withThis(Function f) => _withThis(f); |
767 | 1011 |
768 /** | 1012 /** |
769 * Invokes the JavaScript function with arguments [args]. If [thisArg] is | 1013 * Invokes the JavaScript function with arguments [args]. If [thisArg] is |
770 * supplied it is the value of `this` for the invocation. | 1014 * supplied it is the value of `this` for the invocation. |
771 */ | 1015 */ |
772 dynamic apply(List args, {thisArg}) native "JsFunction_apply"; | 1016 dynamic apply(List args, {thisArg}) => |
| 1017 _maybeWrap(_apply(args, thisArg: thisArg)); |
| 1018 |
| 1019 dynamic _apply(List args, {thisArg}) native "JsFunction_apply"; |
773 | 1020 |
774 noSuchMethod(Invocation invocation) { | 1021 noSuchMethod(Invocation invocation) { |
775 if (invocation.isMethod && invocation.memberName == #call) { | 1022 if (invocation.isMethod && invocation.memberName == #call) { |
776 return apply(_buildArgs(invocation)); | 1023 return apply(_buildArgs(invocation)); |
777 } | 1024 } |
778 return super.noSuchMethod(invocation); | 1025 return super.noSuchMethod(invocation); |
779 } | 1026 } |
780 | 1027 |
781 /** | 1028 /** |
782 * Internal only version of apply which uses debugger proxies of Dart objects | 1029 * Internal only version of apply which uses debugger proxies of Dart objects |
(...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
904 | 1151 |
905 /** | 1152 /** |
906 * Returns a method that can be called with an arbitrary number (for n less | 1153 * Returns a method that can be called with an arbitrary number (for n less |
907 * than 11) of arguments without violating Dart type checks. | 1154 * than 11) of arguments without violating Dart type checks. |
908 */ | 1155 */ |
909 Function _wrapAsDebuggerVarArgsFunction(JsFunction jsFunction) => | 1156 Function _wrapAsDebuggerVarArgsFunction(JsFunction jsFunction) => |
910 ([a1 = _UNDEFINED, a2 = _UNDEFINED, a3 = _UNDEFINED, a4 = _UNDEFINED, | 1157 ([a1 = _UNDEFINED, a2 = _UNDEFINED, a3 = _UNDEFINED, a4 = _UNDEFINED, |
911 a5 = _UNDEFINED, a6 = _UNDEFINED, a7 = _UNDEFINED, a8 = _UNDEFINED, | 1158 a5 = _UNDEFINED, a6 = _UNDEFINED, a7 = _UNDEFINED, a8 = _UNDEFINED, |
912 a9 = _UNDEFINED, a10 = _UNDEFINED]) => jsFunction._applyDebuggerOnly( | 1159 a9 = _UNDEFINED, a10 = _UNDEFINED]) => jsFunction._applyDebuggerOnly( |
913 _stripUndefinedArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10])); | 1160 _stripUndefinedArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10])); |
| 1161 |
| 1162 // This method is a no-op in Dartium. |
| 1163 // TODO(jacobr): tag methods so we can throw if a Dart method is passed to |
| 1164 // JavaScript using the new interop without calling allowInterop. |
| 1165 Function allowInterop(Function f) => f; |
| 1166 |
| 1167 Expando<JsFunction> _interopCaptureThisExpando = new Expando<JsFunction>(); |
| 1168 |
| 1169 Function allowInteropCaptureThis(Function f) { |
| 1170 if (f is JsFunction) { |
| 1171 // Behavior when the function is already a JS function is unspecified. |
| 1172 throw new ArgumentError( |
| 1173 "Function is already a JS function so cannot capture this."); |
| 1174 return f; |
| 1175 } else { |
| 1176 var ret = _interopCaptureThisExpando[f]; |
| 1177 if (ret == null) { |
| 1178 ret = new JsFunction.withThis(f); |
| 1179 _interopCaptureThisExpando[f] = ret; |
| 1180 } |
| 1181 return ret; |
| 1182 } |
| 1183 } |
OLD | NEW |