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 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
94 import 'dart:html' as html; | 94 import 'dart:html' as html; |
95 import 'dart:html_common' as html_common; | 95 import 'dart:html_common' as html_common; |
96 import 'dart:indexed_db' as indexed_db; | 96 import 'dart:indexed_db' as indexed_db; |
97 import 'dart:typed_data'; | 97 import 'dart:typed_data'; |
98 | 98 |
99 // Pretend we are always in checked mode as we aren't interested in users | 99 // Pretend we are always in checked mode as we aren't interested in users |
100 // running Dartium code outside of checked mode. | 100 // running Dartium code outside of checked mode. |
101 @Deprecated("Internal Use Only") | 101 @Deprecated("Internal Use Only") |
102 final bool CHECK_JS_INVOCATIONS = true; | 102 final bool CHECK_JS_INVOCATIONS = true; |
103 | 103 |
104 final String _DART_RESERVED_NAME_PREFIX = r'JS$'; | |
105 | |
106 String _stripReservedNamePrefix(String name) => | |
107 name.startsWith(_DART_RESERVED_NAME_PREFIX) | |
108 ? name.substring(_DART_RESERVED_NAME_PREFIX.length) | |
109 : name; | |
110 | |
111 _buildArgs(Invocation invocation) { | |
112 if (invocation.namedArguments.isEmpty) { | |
113 return invocation.positionalArguments; | |
114 } else { | |
115 var varArgs = new Map<String, Object>(); | |
116 invocation.namedArguments.forEach((symbol, val) { | |
117 varArgs[mirrors.MirrorSystem.getName(symbol)] = val; | |
118 }); | |
119 return invocation.positionalArguments.toList() | |
120 ..add(maybeWrapTypedInterop(new JsObject.jsify(varArgs))); | |
121 } | |
122 } | |
123 | |
104 final _allowedMethods = new Map<Symbol, _DeclarationSet>(); | 124 final _allowedMethods = new Map<Symbol, _DeclarationSet>(); |
105 final _allowedGetters = new Map<Symbol, _DeclarationSet>(); | 125 final _allowedGetters = new Map<Symbol, _DeclarationSet>(); |
106 final _allowedSetters = new Map<Symbol, _DeclarationSet>(); | 126 final _allowedSetters = new Map<Symbol, _DeclarationSet>(); |
107 | 127 |
108 final _jsInterfaceTypes = new Set<mirrors.ClassMirror>(); | 128 final _jsInterfaceTypes = new Set<mirrors.ClassMirror>(); |
109 @Deprecated("Internal Use Only") | 129 @Deprecated("Internal Use Only") |
110 Iterable<mirrors.ClassMirror> get jsInterfaceTypes => _jsInterfaceTypes; | 130 Iterable<mirrors.ClassMirror> get jsInterfaceTypes => _jsInterfaceTypes; |
111 | 131 |
112 /// A collection of methods where all methods have the same name. | 132 /// A collection of methods where all methods have the same name. |
113 /// This class is intended to optimize whether a specific invocation is | 133 /// This class is intended to optimize whether a specific invocation is |
(...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
285 if (uri.scheme == 'package' && uri.path == 'js/js.dart') { | 305 if (uri.scheme == 'package' && uri.path == 'js/js.dart') { |
286 return true; | 306 return true; |
287 } | 307 } |
288 } | 308 } |
289 } | 309 } |
290 return false; | 310 return false; |
291 } | 311 } |
292 | 312 |
293 bool _hasJsName(mirrors.DeclarationMirror mirror) => _getJsName(mirror) != null; | 313 bool _hasJsName(mirrors.DeclarationMirror mirror) => _getJsName(mirror) != null; |
294 | 314 |
315 bool hasDomName(mirrors.DeclarationMirror mirror) { | |
316 var location = mirror.location; | |
317 if (location == null || location.sourceUri.scheme != 'dart') return false; | |
318 for (var annotation in mirror.metadata) { | |
319 if (mirrors.MirrorSystem.getName(annotation.type.simpleName) == "DomName") { | |
320 // We can't make sure the annotation is in dart: as Dartium believes it | |
321 // is file://dart/sdk/lib/html/html_common/metadata.dart | |
322 // instead of a proper dart: location. | |
323 return true; | |
324 } | |
325 } | |
326 return false; | |
327 } | |
328 | |
295 _getJsMemberName(mirrors.DeclarationMirror mirror) { | 329 _getJsMemberName(mirrors.DeclarationMirror mirror) { |
296 var name = _getJsName(mirror); | 330 var name = _getJsName(mirror); |
297 return name == null || name.isEmpty ? _getDeclarationName(mirror) : name; | 331 return name == null || name.isEmpty ? _getDeclarationName(mirror) : name; |
298 } | 332 } |
299 | 333 |
300 // TODO(jacobr): handle setters correctyl. | 334 // TODO(jacobr): handle setters correctyl. |
301 String _getDeclarationName(mirrors.DeclarationMirror declaration) { | 335 String _getDeclarationName(mirrors.DeclarationMirror declaration) { |
302 var name = mirrors.MirrorSystem.getName(declaration.simpleName); | 336 var name = mirrors.MirrorSystem.getName(declaration.simpleName); |
303 if (declaration is mirrors.MethodMirror && declaration.isSetter) { | 337 if (declaration is mirrors.MethodMirror && declaration.isSetter) { |
304 assert(name.endsWith("=")); | 338 assert(name.endsWith("=")); |
305 name = name.substring(0, name.length - 1); | 339 name = name.substring(0, name.length - 1); |
306 } | 340 } |
307 return name; | 341 return _stripReservedNamePrefix(name); |
308 } | 342 } |
309 | 343 |
310 final _JS_LIBRARY_PREFIX = "js_library"; | 344 final _JS_LIBRARY_PREFIX = "js_library"; |
311 final _UNDEFINED_VAR = "_UNDEFINED_JS_CONST"; | 345 final _UNDEFINED_VAR = "_UNDEFINED_JS_CONST"; |
312 | 346 |
313 String _accessJsPath(String path) => _accessJsPathHelper(path.split(".")); | 347 String _accessJsPath(String path) => _accessJsPathHelper(path.split(".")); |
314 | 348 |
315 String _accessJsPathHelper(Iterable<String> parts) { | 349 String _accessJsPathHelper(Iterable<String> parts) { |
316 var sb = new StringBuffer(); | 350 var sb = new StringBuffer(); |
317 sb | 351 sb |
(...skipping 22 matching lines...) Expand all Loading... | |
340 var name = memberName != null ? memberName : _getDeclarationName(declaration); | 374 var name = memberName != null ? memberName : _getDeclarationName(declaration); |
341 if (declaration.isConstructor) { | 375 if (declaration.isConstructor) { |
342 sb.write("factory"); | 376 sb.write("factory"); |
343 } else if (isStatic) { | 377 } else if (isStatic) { |
344 sb.write("static"); | 378 sb.write("static"); |
345 } else { | 379 } else { |
346 sb.write("patch"); | 380 sb.write("patch"); |
347 } | 381 } |
348 sb.write(" "); | 382 sb.write(" "); |
349 if (declaration.isGetter) { | 383 if (declaration.isGetter) { |
350 sb.write("get $name => ${_JS_LIBRARY_PREFIX}.maybeWrapTypedInterop(${_access JsPath(path)});"); | 384 sb.write( |
385 "get $name => ${_JS_LIBRARY_PREFIX}.maybeWrapTypedInterop(${_accessJsPat h(path)});"); | |
351 } else if (declaration.isSetter) { | 386 } else if (declaration.isSetter) { |
352 sb.write("set $name(v) => ${_JS_LIBRARY_PREFIX}.maybeWrapTypedInterop(${_acc essJsPathSetter(path)});"); | 387 sb.write("set $name(v) {\n" |
388 " ${_JS_LIBRARY_PREFIX}.safeForTypedInterop(v);\n" | |
389 " return ${_JS_LIBRARY_PREFIX}.maybeWrapTypedInterop(${_accessJsPathSet ter(path)});\n" | |
390 "}\n"); | |
353 } else { | 391 } else { |
354 sb.write("$name("); | 392 sb.write("$name("); |
355 bool hasOptional = false; | 393 bool hasOptional = false; |
356 int i = 0; | 394 int i = 0; |
357 var args = <String>[]; | 395 var args = <String>[]; |
358 for (var p in declaration.parameters) { | 396 for (var p in declaration.parameters) { |
359 assert(!p.isNamed); // XXX throw | 397 assert(!p.isNamed); // TODO(jacobr): throw. |
360 assert(!p.hasDefaultValue); | 398 assert(!p.hasDefaultValue); |
361 if (i > 0) { | 399 if (i > 0) { |
362 sb.write(", "); | 400 sb.write(", "); |
363 } | 401 } |
364 if (p.isOptional && !hasOptional) { | 402 if (p.isOptional && !hasOptional) { |
365 sb.write("["); | 403 sb.write("["); |
366 hasOptional = true; | 404 hasOptional = true; |
367 } | 405 } |
368 var arg = "p$i"; | 406 var arg = "p$i"; |
369 args.add(arg); | 407 args.add(arg); |
370 sb.write(arg); | 408 sb.write(arg); |
371 if (p.isOptional) { | 409 if (p.isOptional) { |
372 sb.write("=${_UNDEFINED_VAR}"); | 410 sb.write("=${_UNDEFINED_VAR}"); |
373 } | 411 } |
374 i++; | 412 i++; |
375 } | 413 } |
376 if (hasOptional) { | 414 if (hasOptional) { |
377 sb.write("]"); | 415 sb.write("]"); |
378 } | 416 } |
379 // TODO(jacobr): | 417 // TODO(jacobr): |
380 sb.write(") => "); | 418 sb.write(") {\n"); |
381 sb.write('${_JS_LIBRARY_PREFIX}.maybeWrapTypedInterop('); | 419 for (var arg in args) { |
420 sb.write(" ${_JS_LIBRARY_PREFIX}.safeForTypedInterop($arg);\n"); | |
421 } | |
422 sb.write(" return ${_JS_LIBRARY_PREFIX}.maybeWrapTypedInterop("); | |
382 if (declaration.isConstructor) { | 423 if (declaration.isConstructor) { |
383 sb.write("new ${_JS_LIBRARY_PREFIX}.JsObject("); | 424 sb.write("new ${_JS_LIBRARY_PREFIX}.JsObject("); |
384 } | 425 } |
385 sb | 426 sb |
386 ..write(_accessJsPath(path)) | 427 ..write(_accessJsPath(path)) |
387 ..write(declaration.isConstructor ? "," : ".apply(") | 428 ..write(declaration.isConstructor ? "," : ".apply(") |
388 ..write("[${args.join(",")}]"); | 429 ..write("[${args.join(",")}]"); |
389 | 430 |
390 if (hasOptional) { | 431 if (hasOptional) { |
391 sb.write(".takeWhile((i) => i != ${_UNDEFINED_VAR}).toList()"); | 432 sb.write(".takeWhile((i) => i != ${_UNDEFINED_VAR}).toList()"); |
392 } | 433 } |
393 sb.write("));"); | 434 sb.write("));"); |
435 sb.write("}\n"); | |
394 } | 436 } |
395 sb.write("\n"); | 437 sb.write("\n"); |
396 } | 438 } |
397 | 439 |
398 bool _isExternal(mirrors.MethodMirror mirror) { | 440 bool _isExternal(mirrors.MethodMirror mirror) { |
399 // This try-catch block is a workaround for BUG:24834. | 441 // This try-catch block is a workaround for BUG:24834. |
400 try { | 442 try { |
401 return mirror.isExternal; | 443 return mirror.isExternal; |
402 } catch (e) { } | 444 } catch (e) {} |
403 return false; | 445 return false; |
404 } | 446 } |
405 | 447 |
406 List<String> _generateExternalMethods() { | 448 List<String> _generateExternalMethods() { |
407 var staticCodegen = <String>[]; | 449 var staticCodegen = <String>[]; |
408 mirrors.currentMirrorSystem().libraries.forEach((uri, library) { | 450 mirrors.currentMirrorSystem().libraries.forEach((uri, library) { |
409 var sb = new StringBuffer(); | 451 var sb = new StringBuffer(); |
410 String jsLibraryName = _getJsName(library); | 452 String jsLibraryName = _getJsName(library); |
411 library.declarations.forEach((name, declaration) { | 453 library.declarations.forEach((name, declaration) { |
412 if (declaration is mirrors.MethodMirror) { | 454 if (declaration is mirrors.MethodMirror) { |
413 if ((_hasJsName(declaration) || jsLibraryName != null) && | 455 if ((_hasJsName(declaration) || jsLibraryName != null) && |
414 _isExternal(declaration)) { | 456 _isExternal(declaration)) { |
415 addMemberHelper(declaration, jsLibraryName, sb); | 457 addMemberHelper(declaration, jsLibraryName, sb); |
416 } | 458 } |
417 } else if (declaration is mirrors.ClassMirror) { | 459 } else if (declaration is mirrors.ClassMirror) { |
418 mirrors.ClassMirror clazz = declaration; | 460 mirrors.ClassMirror clazz = declaration; |
419 if (_hasJsName(clazz)) { | 461 var isDom = hasDomName(clazz); |
462 var isJsInterop = _hasJsName(clazz); | |
463 if (isDom || isJsInterop) { | |
420 // TODO(jacobr): verify class implements JavaScriptObject. | 464 // TODO(jacobr): verify class implements JavaScriptObject. |
421 String jsClassName = _getJsMemberName(clazz); | |
422 var className = mirrors.MirrorSystem.getName(clazz.simpleName); | 465 var className = mirrors.MirrorSystem.getName(clazz.simpleName); |
466 var classNameImpl = '${className}Impl'; | |
423 var sbPatch = new StringBuffer(); | 467 var sbPatch = new StringBuffer(); |
424 jsInterfaceTypes.add(clazz); | 468 if (isJsInterop) { |
425 clazz.declarations.forEach((name, declaration) { | 469 String jsClassName = _getJsMemberName(clazz); |
426 if (declaration is! mirrors.MethodMirror || | 470 |
427 !_isExternal(declaration)) return; | 471 jsInterfaceTypes.add(clazz); |
428 if (declaration.isFactoryConstructor && _isAnonymousClass(clazz)) { | 472 clazz.declarations.forEach((name, declaration) { |
429 sbPatch.write(" factory ${className}("); | 473 if (declaration is! mirrors.MethodMirror || |
430 int i = 0; | 474 !_isExternal(declaration)) return; |
431 var args = <String>[]; | 475 if (declaration.isFactoryConstructor && |
432 for (var p in declaration.parameters) { | 476 _isAnonymousClass(clazz)) { |
433 args.add(mirrors.MirrorSystem.getName(p.simpleName)); | 477 sbPatch.write(" factory ${className}("); |
434 i++; | 478 int i = 0; |
479 var args = <String>[]; | |
480 for (var p in declaration.parameters) { | |
481 args.add(mirrors.MirrorSystem.getName(p.simpleName)); | |
482 i++; | |
483 } | |
484 if (args.isNotEmpty) { | |
485 sbPatch | |
486 ..write('{') | |
487 ..write(args | |
488 .map((name) => '$name:${_UNDEFINED_VAR}') | |
489 .join(", ")) | |
490 ..write('}'); | |
491 } | |
492 sbPatch.write(") {\n" | |
493 " var ret = new ${_JS_LIBRARY_PREFIX}.JsObject.jsify({}); \n"); | |
494 i = 0; | |
495 for (var p in declaration.parameters) { | |
496 assert(p.isNamed); // TODO(jacobr): throw. | |
497 var name = args[i]; | |
498 var jsName = _stripReservedNamePrefix( | |
499 mirrors.MirrorSystem.getName(p.simpleName)); | |
500 sbPatch.write(" if($name != ${_UNDEFINED_VAR}) {\n" | |
501 " ${_JS_LIBRARY_PREFIX}.safeForTypedInterop($name);\n " | |
502 " ret['$jsName'] = $name;\n" | |
503 " }\n"); | |
504 i++; | |
505 } | |
506 | |
507 sbPatch.write( | |
508 " return new ${_JS_LIBRARY_PREFIX}.JSObject.create(ret);\ n" | |
509 " }\n"); | |
510 } else if (declaration.isConstructor || | |
511 declaration.isFactoryConstructor) { | |
512 sbPatch.write(" "); | |
513 addMemberHelper( | |
514 declaration, | |
515 (jsLibraryName != null && jsLibraryName.isNotEmpty) | |
516 ? "${jsLibraryName}.${jsClassName}" | |
517 : jsClassName, | |
518 sbPatch, | |
519 isStatic: true, | |
520 memberName: className); | |
435 } | 521 } |
436 if (args.isNotEmpty) { | 522 }); |
437 sbPatch | 523 |
438 ..write('{') | 524 clazz.staticMembers.forEach((memberName, member) { |
439 ..write( | 525 if (_isExternal(member)) { |
440 args.map((name) => '$name:${_UNDEFINED_VAR}').join(", ")) | 526 sbPatch.write(" "); |
441 ..write('}'); | 527 addMemberHelper( |
528 member, | |
529 (jsLibraryName != null && jsLibraryName.isNotEmpty) | |
530 ? "${jsLibraryName}.${jsClassName}" | |
531 : jsClassName, | |
532 sbPatch, | |
533 isStatic: true); | |
442 } | 534 } |
443 sbPatch.write(") {\n" | 535 }); |
444 " var ret = new ${_JS_LIBRARY_PREFIX}.JsObject.jsify({}); \n"); | 536 } |
445 i = 0; | 537 if (isDom) { |
446 for (var p in declaration.parameters) { | 538 sbPatch.write(" factory ${className}._internalWrap() => " |
447 assert(p.isNamed); // XXX throw | 539 "new ${classNameImpl}.internal_();\n"); |
448 var name = args[i]; | |
449 var jsName = mirrors.MirrorSystem.getName(p.simpleName); | |
450 // XXX apply name conversion rules. | |
451 sbPatch.write( | |
452 " if($name != ${_UNDEFINED_VAR}) ret['$jsName'] = $name;\ n"); | |
453 i++; | |
454 } | |
455 | |
456 sbPatch.write(" return ret;\n" | |
457 " }\n"); | |
458 } else if (declaration.isConstructor || | |
459 declaration.isFactoryConstructor) { | |
460 sbPatch.write(" "); | |
461 addMemberHelper( | |
462 declaration, | |
463 (jsLibraryName != null && jsLibraryName.isNotEmpty) | |
464 ? "${jsLibraryName}.${jsClassName}" | |
465 : jsClassName, | |
466 sbPatch, | |
467 isStatic: true, | |
468 memberName: className); | |
469 } | |
470 }); | |
471 | |
472 clazz.staticMembers.forEach((memberName, member) { | |
473 if (_isExternal(member)) { | |
474 sbPatch.write(" "); | |
475 addMemberHelper( | |
476 member, | |
477 (jsLibraryName != null && jsLibraryName.isNotEmpty) | |
478 ? "${jsLibraryName}.${jsClassName}" | |
479 : jsClassName, | |
480 sbPatch, | |
481 isStatic: true); | |
482 } | |
483 }); | |
484 var typeVariablesClause = ''; | |
485 if (!clazz.typeVariables.isEmpty) { | |
486 typeVariablesClause = | |
487 '<${clazz.typeVariables.map((m) => mirrors.MirrorSystem.getName( m.simpleName)).join(',')}>'; | |
488 } | 540 } |
489 if (sbPatch.isNotEmpty) { | 541 if (sbPatch.isNotEmpty) { |
542 var typeVariablesClause = ''; | |
543 if (!clazz.typeVariables.isEmpty) { | |
544 typeVariablesClause = | |
545 '<${clazz.typeVariables.map((m) => mirrors.MirrorSystem.getNam e(m.simpleName)).join(',')}>'; | |
546 } | |
490 sb.write(""" | 547 sb.write(""" |
491 patch class $className$typeVariablesClause { | 548 patch class $className$typeVariablesClause { |
492 $sbPatch | 549 $sbPatch |
493 } | 550 } |
494 """); | 551 """); |
552 if (isDom) { | |
553 sb.write(""" | |
554 class $classNameImpl$typeVariablesClause extends $className implements ${_JS_LIB RARY_PREFIX}.JSObjectInterfacesDom { | |
555 ${classNameImpl}.internal_() : super.internal_(); | |
556 get runtimeType => $className; | |
557 toString() => super.toString(); | |
558 } | |
559 """); | |
560 } | |
495 } | 561 } |
496 } | 562 } |
497 } | 563 } |
498 }); | |
499 if (sb.isNotEmpty) { | |
500 staticCodegen | |
501 ..add(uri.toString()) | |
502 ..add("${uri}_js_interop_patch.dart") | |
503 ..add(""" | |
504 import 'dart:js' as ${_JS_LIBRARY_PREFIX}; | |
505 | |
506 /** | |
507 * Placeholder object for cases where we need to determine exactly how many | |
508 * args were passed to a function. | |
509 */ | |
510 const ${_UNDEFINED_VAR} = const Object(); | |
511 | |
512 ${sb} | |
513 """); | |
514 } | |
515 }); | |
516 | |
517 return staticCodegen; | |
518 } | |
519 | |
520 List<String> _generateExternalMethods2() { | |
521 var staticCodegen = <String>[]; | |
522 mirrors.currentMirrorSystem().libraries.forEach((uri, library) { | |
523 var sb = new StringBuffer(); | |
524 String jsLibraryName = _getJsName(library); | |
525 library.declarations.forEach((name, declaration) { | |
526 var isExternal = _isExternal(declaration); | |
527 if (declaration is mirrors.MethodMirror) { | |
528 if (isExternal && (_hasJsName(declaration) || jsLibraryName != null)) { | |
529 addMemberHelper(declaration, jsLibraryName, sb); | |
530 } | |
531 } else if (declaration is mirrors.ClassMirror) { | |
532 mirrors.ClassMirror clazz = declaration; | |
533 if (_hasJsName(clazz)) { | |
534 // TODO(jacobr): verify class implements JavaScriptObject. | |
535 String jsClassName = _getJsMemberName(clazz); | |
536 var className = mirrors.MirrorSystem.getName(clazz.simpleName); | |
537 var sbPatch = new StringBuffer(); | |
538 jsInterfaceTypes.add(clazz); | |
539 clazz.declarations.forEach((name, declaration) { | |
540 if (declaration is! mirrors.MethodMirror || | |
541 !declaration.isAbstract || | |
542 !isExternal) return; | |
543 if (_hasLiteralAnnotation(declaration) && | |
544 declaration.isFactoryConstructor) { | |
545 sbPatch.write(" factory ${className}({"); | |
546 int i = 0; | |
547 var args = <String>[]; | |
548 for (var p in declaration.parameters) { | |
549 assert(p.isNamed); // XXX throw | |
550 args.add(mirrors.MirrorSystem.getName(p.simpleName)); | |
551 i++; | |
552 } | |
553 sbPatch | |
554 ..write( | |
555 args.map((name) => '$name:${_UNDEFINED_VAR}').join(", ")) | |
556 ..write("}) {\n" | |
557 " var ret = new ${_JS_LIBRARY_PREFIX}.JsObject.jsify({}); \n"); | |
558 i = 0; | |
559 for (var p in declaration.parameters) { | |
560 assert(p.isNamed); // XXX throw | |
561 var name = args[i]; | |
562 var jsName = mirrors.MirrorSystem.getName(p.simpleName); | |
563 // XXX apply name conversion rules. | |
564 sbPatch.write( | |
565 " if($name != ${_UNDEFINED_VAR}) ret['$jsName'] = $name;\ n"); | |
566 i++; | |
567 } | |
568 | |
569 sbPatch.write(" return ret;\n" | |
570 " }\n"); | |
571 } else if (declaration.isConstructor || | |
572 declaration.isFactoryConstructor) { | |
573 sbPatch.write(" "); | |
574 addMemberHelper( | |
575 declaration, | |
576 (jsLibraryName != null && jsLibraryName.isNotEmpty) | |
577 ? "${jsLibraryName}.${jsClassName}" | |
578 : jsClassName, | |
579 sbPatch, | |
580 isStatic: true, | |
581 memberName: className); | |
582 } | |
583 }); | |
584 | |
585 clazz.staticMembers.forEach((memberName, member) { | |
586 if (_isExternal(member)) { | |
587 sbPatch.write(" "); | |
588 addMemberHelper( | |
589 member, | |
590 (jsLibraryName != null && jsLibraryName.isNotEmpty) | |
591 ? "${jsLibraryName}.${jsClassName}" | |
592 : jsClassName, | |
593 sbPatch, | |
594 isStatic: true); | |
595 } | |
596 }); | |
597 var typeVariablesClause = ''; | |
598 if (!clazz.typeVariables.isEmpty) { | |
599 typeVariablesClause = | |
600 '<${clazz.typeVariables.map((m) => mirrors.MirrorSystem.getName( m.simpleName)).join(',')}>'; | |
601 } | |
602 if (sbPatch.isNotEmpty) { | |
603 sb.write(""" | |
604 patch class $className$typeVariablesClause { | |
605 $sbPatch | |
606 } | |
607 """); | |
608 } | |
609 } | |
610 } | |
611 }); | 564 }); |
612 if (sb.isNotEmpty) { | 565 if (sb.isNotEmpty) { |
613 staticCodegen | 566 staticCodegen |
614 ..add(uri.toString()) | 567 ..add(uri.toString()) |
615 ..add("${uri}_js_interop_patch.dart") | 568 ..add("${uri}_js_interop_patch.dart") |
616 ..add(""" | 569 ..add(""" |
617 import 'dart:js' as ${_JS_LIBRARY_PREFIX}; | 570 import 'dart:js' as ${_JS_LIBRARY_PREFIX}; |
618 | 571 |
619 /** | 572 /** |
620 * Placeholder object for cases where we need to determine exactly how many | 573 * Placeholder object for cases where we need to determine exactly how many |
621 * args were passed to a function. | 574 * args were passed to a function. |
622 */ | 575 */ |
623 const ${_UNDEFINED_VAR} = const Object(); | 576 const ${_UNDEFINED_VAR} = const Object(); |
624 | 577 |
625 ${sb} | 578 ${sb} |
626 """); | 579 """); |
627 } | 580 } |
628 }); | 581 }); |
629 | 582 |
630 return staticCodegen; | 583 return staticCodegen; |
631 } | 584 } |
632 | 585 |
633 /** | 586 /** |
634 * Generates a part file defining source code for JsObjectImpl and related | 587 * Generates part files defining source code for JSObjectImpl, all DOM classes |
635 * classes. This calass is needed so that type checks for all registered JavaScr ipt | 588 * classes. This codegen is needed so that type checks for all registered |
636 * interop classes pass. | 589 * JavaScript interop classes pass. |
637 */ | 590 */ |
638 List<String> _generateInteropPatchFiles() { | 591 List<String> _generateInteropPatchFiles() { |
639 var ret = _generateExternalMethods(); | 592 var ret = _generateExternalMethods(); |
640 var libraryPrefixes = new Map<mirrors.LibraryMirror, String>(); | 593 var libraryPrefixes = new Map<mirrors.LibraryMirror, String>(); |
641 var prefixNames = new Set<String>(); | 594 var prefixNames = new Set<String>(); |
642 var sb = new StringBuffer(); | 595 var sb = new StringBuffer(); |
643 | 596 |
644 var implements = <String>[]; | 597 var implements = <String>[]; |
645 var implementsArray = <String>[]; | 598 var implementsArray = <String>[]; |
599 var implementsDom = <String>[]; | |
646 var listMirror = mirrors.reflectType(List); | 600 var listMirror = mirrors.reflectType(List); |
601 var functionMirror = mirrors.reflectType(Function); | |
602 var jsObjectMirror = mirrors.reflectType(JSObject); | |
647 | 603 |
648 for (var typeMirror in jsInterfaceTypes) { | 604 for (var typeMirror in jsInterfaceTypes) { |
649 mirrors.LibraryMirror libraryMirror = typeMirror.owner; | 605 mirrors.LibraryMirror libraryMirror = typeMirror.owner; |
650 var prefixName; | 606 var prefixName; |
651 if (libraryPrefixes.containsKey(libraryMirror)) { | 607 if (libraryPrefixes.containsKey(libraryMirror)) { |
652 prefixName = libraryPrefixes[libraryMirror]; | 608 prefixName = libraryPrefixes[libraryMirror]; |
653 } else { | 609 } else { |
654 var basePrefixName = | 610 var basePrefixName = |
655 mirrors.MirrorSystem.getName(libraryMirror.simpleName); | 611 mirrors.MirrorSystem.getName(libraryMirror.simpleName); |
656 basePrefixName = basePrefixName.replaceAll('.', '_'); | 612 basePrefixName = basePrefixName.replaceAll('.', '_'); |
657 if (basePrefixName.isEmpty) basePrefixName = "lib"; | 613 if (basePrefixName.isEmpty) basePrefixName = "lib"; |
658 prefixName = basePrefixName; | 614 prefixName = basePrefixName; |
659 var i = 1; | 615 var i = 1; |
660 while (prefixNames.contains(prefixName)) { | 616 while (prefixNames.contains(prefixName)) { |
661 prefixName = '$basePrefixName$i'; | 617 prefixName = '$basePrefixName$i'; |
662 i++; | 618 i++; |
663 } | 619 } |
664 prefixNames.add(prefixName); | 620 prefixNames.add(prefixName); |
665 libraryPrefixes[libraryMirror] = prefixName; | 621 libraryPrefixes[libraryMirror] = prefixName; |
666 } | 622 } |
667 var isArray = typeMirror.isSubtypeOf(listMirror); | 623 var isArray = typeMirror.isSubtypeOf(listMirror); |
668 (isArray ? implementsArray : implements).add( | 624 var isFunction = typeMirror.isSubtypeOf(functionMirror); |
669 '${prefixName}.${mirrors.MirrorSystem.getName(typeMirror.simpleName)}'); | 625 var isJSObject = typeMirror.isSubtypeOf(jsObjectMirror); |
626 var fullName = | |
627 '${prefixName}.${mirrors.MirrorSystem.getName(typeMirror.simpleName)}'; | |
628 (isArray ? implementsArray : implements).add(fullName); | |
629 if (!isArray && !isFunction && !isJSObject) { | |
630 // For DOM classes we need to be a bit more conservative at tagging them | |
631 // as implementing JS inteorp classes risks strange unintended | |
632 // consequences as unrleated code may have instanceof checks. Checking | |
633 // for isJSObject ensures we do not accidentally pull in existing | |
634 // dart:html classes as they all have JSObject as a base class. | |
635 // Note that methods from these classes can still be called on a | |
636 // dart:html instance but checked mode type checks will fail. This is | |
637 // not ideal but is better than causing strange breaks in existing | |
638 // code that uses dart:html. | |
639 // TODO(jacobr): consider throwing compile time errors if @JS classes | |
640 // extend JSObject as that case cannot be safely handled in Dartium. | |
641 implementsDom.add(fullName); | |
642 } | |
670 } | 643 } |
671 libraryPrefixes.forEach((libraryMirror, prefix) { | 644 libraryPrefixes.forEach((libraryMirror, prefix) { |
672 sb.writeln('import "${libraryMirror.uri}" as $prefix;'); | 645 sb.writeln('import "${libraryMirror.uri}" as $prefix;'); |
673 }); | 646 }); |
674 buildImplementsClause(classes) => | 647 buildImplementsClause(classes) => |
675 classes.isEmpty ? "" : "implements ${classes.join(', ')}"; | 648 classes.isEmpty ? "" : "implements ${classes.join(', ')}"; |
676 var implementsClause = buildImplementsClause(implements); | 649 var implementsClause = buildImplementsClause(implements); |
650 var implementsClauseDom = buildImplementsClause(implementsDom); | |
677 // TODO(jacobr): only certain classes need to be implemented by | 651 // TODO(jacobr): only certain classes need to be implemented by |
678 // JsFunctionImpl. | 652 // JsFunctionImpl. |
679 var allTypes = []..addAll(implements)..addAll(implementsArray); | 653 var allTypes = []..addAll(implements)..addAll(implementsArray); |
680 sb.write(''' | 654 sb.write(''' |
681 class JsObjectImpl extends JsObject $implementsClause { | 655 class JSObjectImpl extends JSObject $implementsClause { |
682 JsObjectImpl.internal() : super.internal(); | 656 JSObjectImpl.internal() : super.internal(); |
683 } | 657 } |
684 | 658 |
685 class JsFunctionImpl extends JsFunction $implementsClause { | 659 class JSFunctionImpl extends JSFunction $implementsClause { |
686 JsFunctionImpl.internal() : super.internal(); | 660 JSFunctionImpl.internal() : super.internal(); |
687 } | 661 } |
688 | 662 |
689 class JsArrayImpl<E> extends JsArray<E> ${buildImplementsClause(implementsArray) } { | 663 class JSArrayImpl extends JSArray ${buildImplementsClause(implementsArray)} { |
690 JsArrayImpl.internal() : super.internal(); | 664 JSArrayImpl.internal() : super.internal(); |
665 } | |
666 | |
667 // Interfaces that are safe to slam on all DOM classes. | |
668 // Adding implementsClause would be risky as it could contain Function which | |
669 // is likely to break a lot of instanceof checks. | |
670 abstract class JSObjectInterfacesDom $implementsClauseDom { | |
671 } | |
672 | |
673 patch class JSObject { | |
674 factory JSObject.create(JsObject jsObject) { | |
675 return new JSObjectImpl.internal()..blink_jsObject = jsObject; | |
676 } | |
677 } | |
678 | |
679 patch class JSFunction { | |
680 factory JSFunction.create(JsObject jsObject) { | |
681 return new JSFunctionImpl.internal()..blink_jsObject = jsObject; | |
682 } | |
683 } | |
684 | |
685 patch class JSArray { | |
686 factory JSArray.create(JsObject jsObject) { | |
687 return new JSArrayImpl.internal()..blink_jsObject = jsObject; | |
688 } | |
691 } | 689 } |
692 | 690 |
693 _registerAllJsInterfaces() { | 691 _registerAllJsInterfaces() { |
694 _registerJsInterfaces([${allTypes.join(", ")}]); | 692 _registerJsInterfaces([${allTypes.join(", ")}]); |
695 } | 693 } |
696 | 694 |
697 '''); | 695 '''); |
698 ret..addAll(["dart:js", "JsInteropImpl.dart", sb.toString()]); | 696 ret..addAll(["dart:js", "JSInteropImpl.dart", sb.toString()]); |
699 return ret; | 697 return ret; |
700 } | 698 } |
701 | 699 |
702 // Start of block of helper methods facilitating emulating JavaScript Array | 700 // Start of block of helper methods facilitating emulating JavaScript Array |
703 // methods on Dart List objects passed to JavaScript via JS interop. | 701 // methods on Dart List objects passed to JavaScript via JS interop. |
704 // TODO(jacobr): match JS more closely. | 702 // TODO(jacobr): match JS more closely. |
705 String _toStringJs(obj) => '$obj'; | 703 String _toStringJs(obj) => '$obj'; |
706 | 704 |
707 // TODO(jacobr): this might not exactly match JS semantics but should be | 705 // TODO(jacobr): this might not exactly match JS semantics but should be |
708 // adequate for now. | 706 // adequate for now. |
(...skipping 154 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
863 bool get _finalized native "Js_interfacesFinalized_Callback"; | 861 bool get _finalized native "Js_interfacesFinalized_Callback"; |
864 | 862 |
865 JsObject get context { | 863 JsObject get context { |
866 if (_cachedContext == null) { | 864 if (_cachedContext == null) { |
867 _cachedContext = _context; | 865 _cachedContext = _context; |
868 } | 866 } |
869 return _cachedContext; | 867 return _cachedContext; |
870 } | 868 } |
871 | 869 |
872 @Deprecated("Internal Use Only") | 870 @Deprecated("Internal Use Only") |
873 maybeWrapTypedInterop(o) => | 871 maybeWrapTypedInterop(o) => html_common.wrap_jso_no_SerializedScriptvalue(o); |
874 html_common.wrap_jso_no_SerializedScriptvalue(o); | |
875 | 872 |
876 _maybeWrap(o) { | 873 _maybeWrap(o) { |
877 var wrapped = html_common.wrap_jso_no_SerializedScriptvalue(o); | 874 var wrapped = html_common.wrap_jso_no_SerializedScriptvalue(o); |
878 if (identical(wrapped, o)) return o; | 875 if (identical(wrapped, o)) return o; |
879 return (wrapped is html.Blob || | 876 return (wrapped is html.Blob || |
880 wrapped is html.Event || | 877 wrapped is html.Event || |
881 wrapped is indexed_db.KeyRange || | 878 wrapped is indexed_db.KeyRange || |
882 wrapped is html.ImageData || | 879 wrapped is html.ImageData || |
883 wrapped is html.Node || | 880 wrapped is html.Node || |
884 wrapped is TypedData || | 881 wrapped is TypedData || |
(...skipping 18 matching lines...) Expand all Loading... | |
903 object._dartHtmlWrapper = wrapper; | 900 object._dartHtmlWrapper = wrapper; |
904 } | 901 } |
905 | 902 |
906 /** | 903 /** |
907 * Used by callMethod to get the JS object for each argument passed if the | 904 * Used by callMethod to get the JS object for each argument passed if the |
908 * argument is a Dart class instance that delegates to a DOM object. See | 905 * argument is a Dart class instance that delegates to a DOM object. See |
909 * wrap_jso defined in dart:html. | 906 * wrap_jso defined in dart:html. |
910 */ | 907 */ |
911 @Deprecated("Internal Use Only") | 908 @Deprecated("Internal Use Only") |
912 unwrap_jso(dartClass_instance) { | 909 unwrap_jso(dartClass_instance) { |
913 if (dartClass_instance is html.DartHtmlDomObject && | 910 if (dartClass_instance is JSObject && |
Alan Knight
2016/01/14 00:22:09
I suppose the ship has sailed that we have them as
| |
914 dartClass_instance is! JsObject) return dartClass_instance.blink_jsObject; | 911 dartClass_instance is! JsObject) return dartClass_instance.blink_jsObject; |
915 else return dartClass_instance; | 912 else return dartClass_instance; |
916 } | 913 } |
917 | 914 |
918 /** | 915 /** |
919 * Proxies a JavaScript object to Dart. | 916 * Proxies a JavaScript object to Dart. |
920 * | 917 * |
921 * The properties of the JavaScript object are accessible via the `[]` and | 918 * The properties of the JavaScript object are accessible via the `[]` and |
922 * `[]=` operators. Methods are callable via [callMethod]. | 919 * `[]=` operators. Methods are callable via [callMethod]. |
923 */ | 920 */ |
(...skipping 15 matching lines...) Expand all Loading... | |
939 return html_common.unwrap_jso(_create(constructor, arguments)); | 936 return html_common.unwrap_jso(_create(constructor, arguments)); |
940 } catch (e) { | 937 } catch (e) { |
941 // Re-throw any errors (returned as a string) as a DomException. | 938 // Re-throw any errors (returned as a string) as a DomException. |
942 throw new html.DomException.jsInterop(e); | 939 throw new html.DomException.jsInterop(e); |
943 } | 940 } |
944 } | 941 } |
945 | 942 |
946 static JsObject _create(JsFunction constructor, arguments) | 943 static JsObject _create(JsFunction constructor, arguments) |
947 native "JsObject_constructorCallback"; | 944 native "JsObject_constructorCallback"; |
948 | 945 |
949 _buildArgs(Invocation invocation) { | |
950 if (invocation.namedArguments.isEmpty) { | |
951 return invocation.positionalArguments; | |
952 } else { | |
953 var varArgs = new Map<String, Object>(); | |
954 invocation.namedArguments.forEach((symbol, val) { | |
955 varArgs[mirrors.MirrorSystem.getName(symbol)] = val; | |
956 }); | |
957 return invocation.positionalArguments.toList() | |
958 ..add(new JsObject.jsify(varArgs)); | |
959 } | |
960 } | |
961 | |
962 /** | 946 /** |
963 * Constructs a [JsObject] that proxies a native Dart object; _for expert use | 947 * Constructs a [JsObject] that proxies a native Dart object; _for expert use |
964 * only_. | 948 * only_. |
965 * | 949 * |
966 * Use this constructor only if you wish to get access to JavaScript | 950 * Use this constructor only if you wish to get access to JavaScript |
967 * properties attached to a browser host object, such as a Node or Blob, that | 951 * properties attached to a browser host object, such as a Node or Blob, that |
968 * is normally automatically converted into a native Dart object. | 952 * is normally automatically converted into a native Dart object. |
969 * | 953 * |
970 * An exception will be thrown if [object] either is `null` or has the type | 954 * An exception will be thrown if [object] either is `null` or has the type |
971 * `bool`, `num`, or `String`. | 955 * `bool`, `num`, or `String`. |
(...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1092 } catch (e) { | 1076 } catch (e) { |
1093 if (hasProperty(method)) { | 1077 if (hasProperty(method)) { |
1094 // Return a DomException if DOM call returned an error. | 1078 // Return a DomException if DOM call returned an error. |
1095 throw new html.DomException.jsInterop(e); | 1079 throw new html.DomException.jsInterop(e); |
1096 } else { | 1080 } else { |
1097 throw new NoSuchMethodError(this, new Symbol(method), args, null); | 1081 throw new NoSuchMethodError(this, new Symbol(method), args, null); |
1098 } | 1082 } |
1099 } | 1083 } |
1100 } | 1084 } |
1101 | 1085 |
1086 _callMethod(String name, List args) native "JsObject_callMethod"; | |
1087 } | |
1088 | |
1089 /// Base class for all JS objects used through dart:html and typed JS interop. | |
1090 @Deprecated("Internal Use Only") | |
1091 class JSObject { | |
1092 JSObject.internal() {} | |
1093 external factory JSObject.create(JsObject jsObject); | |
1094 | |
1095 @Deprecated("Internal Use Only") | |
1096 JsObject blink_jsObject; | |
1097 | |
1098 String toString() => blink_jsObject.toString(); | |
1099 | |
1102 noSuchMethod(Invocation invocation) { | 1100 noSuchMethod(Invocation invocation) { |
1103 throwError() { | 1101 throwError() { |
1104 throw new NoSuchMethodError(this, invocation.memberName, | 1102 super.noSuchMethod(invocation); |
1105 invocation.positionalArguments, invocation.namedArguments); | |
1106 } | 1103 } |
1107 | 1104 |
1108 String name = mirrors.MirrorSystem.getName(invocation.memberName); | 1105 String name = _stripReservedNamePrefix( |
1106 mirrors.MirrorSystem.getName(invocation.memberName)); | |
1107 argsSafeForTypedInterop(invocation.positionalArguments); | |
1109 if (invocation.isGetter) { | 1108 if (invocation.isGetter) { |
1110 if (CHECK_JS_INVOCATIONS) { | 1109 if (CHECK_JS_INVOCATIONS) { |
1111 var matches = _allowedGetters[invocation.memberName]; | 1110 var matches = _allowedGetters[invocation.memberName]; |
1112 if (matches == null && | 1111 if (matches == null && |
1113 !_allowedMethods.containsKey(invocation.memberName)) { | 1112 !_allowedMethods.containsKey(invocation.memberName)) { |
1114 throwError(); | 1113 throwError(); |
1115 } | 1114 } |
1116 var ret = this[name]; | 1115 var ret = maybeWrapTypedInterop(blink_jsObject._operator_getter(name)); |
1117 if (matches != null && matches._checkReturnType(ret)) return ret; | 1116 if (matches != null) return ret; |
1118 if (ret is Function || | 1117 if (ret is Function || |
1119 (ret is JsFunction /* shouldn't be needed in the future*/) && | 1118 (ret is JsFunction /* shouldn't be needed in the future*/) && |
1120 _allowedMethods.containsKey( | 1119 _allowedMethods.containsKey( |
1121 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. | 1120 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. |
1122 throwError(); | 1121 throwError(); |
1123 } else { | 1122 } else { |
1124 // TODO(jacobr): should we throw if the JavaScript object doesn't have t he property? | 1123 // TODO(jacobr): should we throw if the JavaScript object doesn't have t he property? |
1125 return maybeWrapTypedInterop(this._operator_getter(name)); | 1124 return maybeWrapTypedInterop(blink_jsObject._operator_getter(name)); |
1126 } | 1125 } |
1127 } else if (invocation.isSetter) { | 1126 } else if (invocation.isSetter) { |
1128 if (CHECK_JS_INVOCATIONS) { | 1127 if (CHECK_JS_INVOCATIONS) { |
1129 var matches = _allowedSetters[invocation.memberName]; | 1128 var matches = _allowedSetters[invocation.memberName]; |
1130 if (matches == null || | 1129 if (matches == null || |
1131 !matches.checkInvocation(invocation)) throwError(); | 1130 !matches.checkInvocation(invocation)) throwError(); |
1132 } | 1131 } |
1133 assert(name.endsWith("=")); | 1132 assert(name.endsWith("=")); |
1134 name = name.substring(0, name.length - 1); | 1133 name = name.substring(0, name.length - 1); |
1135 return maybeWrapTypedInterop(_operator_setter( | 1134 return maybeWrapTypedInterop(blink_jsObject._operator_setter( |
1136 name, invocation.positionalArguments.first)); | 1135 name, invocation.positionalArguments.first)); |
1137 } else { | 1136 } else { |
1138 // TODO(jacobr): also allow calling getters that look like functions. | 1137 // TODO(jacobr): also allow calling getters that look like functions. |
1139 var matches; | 1138 var matches; |
1140 if (CHECK_JS_INVOCATIONS) { | 1139 if (CHECK_JS_INVOCATIONS) { |
1141 matches = _allowedMethods[invocation.memberName]; | 1140 matches = _allowedMethods[invocation.memberName]; |
1142 if (matches == null || | 1141 if (matches == null || |
1143 !matches.checkInvocation(invocation)) throwError(); | 1142 !matches.checkInvocation(invocation)) throwError(); |
1144 } | 1143 } |
1145 var ret = maybeWrapTypedInterop(this._callMethod(name, _buildArgs(invocati on))); | 1144 var ret = maybeWrapTypedInterop( |
1145 blink_jsObject._callMethod(name, _buildArgs(invocation))); | |
1146 if (CHECK_JS_INVOCATIONS) { | 1146 if (CHECK_JS_INVOCATIONS) { |
1147 if (!matches._checkReturnType(ret)) throwError(); | 1147 if (!matches._checkReturnType(ret)) { |
1148 html.window.console.error("Return value for method: ${name} is " | |
1149 "${ret.runtimeType} which is inconsistent with all typed " | |
1150 "JS interop definitions for method ${name}."); | |
1151 } | |
1148 } | 1152 } |
1149 return ret; | 1153 return ret; |
1150 } | 1154 } |
1151 } | 1155 } |
1156 } | |
1152 | 1157 |
1153 _callMethod(String name, List args) native "JsObject_callMethod"; | 1158 @Deprecated("Internal Use Only") |
1159 class JSArray extends JSObject with ListMixin { | |
1160 JSArray.internal() : super.internal(); | |
1161 external factory JSArray.create(JsObject jsObject); | |
1162 operator [](int index) => | |
1163 maybeWrapTypedInterop(JsNative.getArrayIndex(blink_jsObject, index)); | |
1164 | |
1165 operator []=(int index, value) => blink_jsObject[index] = value; | |
1166 | |
1167 int get length => blink_jsObject.length; | |
1168 int set length(int newLength) => blink_jsObject.length = newLength; | |
1169 } | |
1170 | |
1171 @Deprecated("Internal Use Only") | |
1172 class JSFunction extends JSObject implements Function { | |
1173 JSFunction.internal() : super.internal(); | |
1174 | |
1175 external factory JSFunction.create(JsObject jsObject); | |
1176 | |
1177 call( | |
1178 [a1 = _UNDEFINED, | |
1179 a2 = _UNDEFINED, | |
1180 a3 = _UNDEFINED, | |
1181 a4 = _UNDEFINED, | |
1182 a5 = _UNDEFINED, | |
1183 a6 = _UNDEFINED, | |
1184 a7 = _UNDEFINED, | |
1185 a8 = _UNDEFINED, | |
1186 a9 = _UNDEFINED, | |
1187 a10 = _UNDEFINED]) { | |
1188 return maybeWrapTypedInterop(blink_jsObject | |
1189 .apply(_stripUndefinedArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10]))); | |
1190 } | |
1191 | |
1192 noSuchMethod(Invocation invocation) { | |
1193 if (invocation.isMethod && invocation.memberName == #call) { | |
1194 return maybeWrapTypedInterop( | |
1195 blink_jsObject.apply(_buildArgs(invocation))); | |
1196 } | |
1197 return super.noSuchMethod(invocation); | |
1198 } | |
1154 } | 1199 } |
1155 | 1200 |
1156 // JavaScript interop methods that do not automatically wrap to dart:html types. | 1201 // JavaScript interop methods that do not automatically wrap to dart:html types. |
1157 // Warning: this API is not exposed to dart:js. | 1202 // Warning: this API is not exposed to dart:js. |
1158 @Deprecated("Internal Use Only") | 1203 @Deprecated("Internal Use Only") |
1159 class JsNative { | 1204 class JsNative { |
1160 static getProperty(o, name) { | 1205 static getProperty(o, name) { |
1161 o = unwrap_jso(o); | 1206 o = unwrap_jso(o); |
1162 return o != null ? o._operator_getter(name) : null; | 1207 return o != null ? o._operator_getter(name) : null; |
1163 } | 1208 } |
(...skipping 14 matching lines...) Expand all Loading... | |
1178 /** | 1223 /** |
1179 * Same behavior as new JsFunction.withThis except that JavaScript "this" is n ot | 1224 * Same behavior as new JsFunction.withThis except that JavaScript "this" is n ot |
1180 * wrapped. | 1225 * wrapped. |
1181 */ | 1226 */ |
1182 static JsFunction withThis(Function f) native "JsFunction_withThisNoWrap"; | 1227 static JsFunction withThis(Function f) native "JsFunction_withThisNoWrap"; |
1183 } | 1228 } |
1184 | 1229 |
1185 /** | 1230 /** |
1186 * Proxies a JavaScript Function object. | 1231 * Proxies a JavaScript Function object. |
1187 */ | 1232 */ |
1188 class JsFunction extends JsObject implements Function { | 1233 class JsFunction extends JsObject { |
1189 JsFunction.internal() : super.internal(); | 1234 JsFunction.internal() : super.internal(); |
1190 | 1235 |
1191 /** | 1236 /** |
1192 * Returns a [JsFunction] that captures its 'this' binding and calls [f] | 1237 * Returns a [JsFunction] that captures its 'this' binding and calls [f] |
1193 * with the value of this passed as the first argument. | 1238 * with the value of this passed as the first argument. |
1194 */ | 1239 */ |
1195 factory JsFunction.withThis(Function f) => _withThis(f); | 1240 factory JsFunction.withThis(Function f) => _withThis(f); |
1196 | 1241 |
1197 /** | 1242 /** |
1198 * Invokes the JavaScript function with arguments [args]. If [thisArg] is | 1243 * Invokes the JavaScript function with arguments [args]. If [thisArg] is |
1199 * supplied it is the value of `this` for the invocation. | 1244 * supplied it is the value of `this` for the invocation. |
1200 */ | 1245 */ |
1201 dynamic apply(List args, {thisArg}) => | 1246 dynamic apply(List args, {thisArg}) => |
1202 _maybeWrap(_apply(args, thisArg: thisArg)); | 1247 _maybeWrap(_apply(args, thisArg: thisArg)); |
1203 | 1248 |
1204 dynamic _apply(List args, {thisArg}) native "JsFunction_apply"; | 1249 dynamic _apply(List args, {thisArg}) native "JsFunction_apply"; |
1205 | 1250 |
1206 call([a1 = _UNDEFINED, | |
1207 a2 = _UNDEFINED, | |
1208 a3 = _UNDEFINED, | |
1209 a4 = _UNDEFINED, | |
1210 a5 = _UNDEFINED, | |
1211 a6 = _UNDEFINED, | |
1212 a7 = _UNDEFINED, | |
1213 a8 = _UNDEFINED, | |
1214 a9 = _UNDEFINED, | |
1215 a10 = _UNDEFINED]) { | |
1216 return apply( | |
1217 _stripUndefinedArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10])); | |
1218 } | |
1219 | |
1220 noSuchMethod(Invocation invocation) { | |
1221 if (invocation.isMethod && invocation.memberName == #call) { | |
1222 return apply(_buildArgs(invocation)); | |
1223 } | |
1224 return super.noSuchMethod(invocation); | |
1225 } | |
1226 | |
1227 /** | 1251 /** |
1228 * Internal only version of apply which uses debugger proxies of Dart objects | 1252 * Internal only version of apply which uses debugger proxies of Dart objects |
1229 * rather than opaque handles. This method is private because it cannot be | 1253 * rather than opaque handles. This method is private because it cannot be |
1230 * efficiently implemented in Dart2Js so should only be used by internal | 1254 * efficiently implemented in Dart2Js so should only be used by internal |
1231 * tools. | 1255 * tools. |
1232 */ | 1256 */ |
1233 _applyDebuggerOnly(List args, {thisArg}) | 1257 _applyDebuggerOnly(List args, {thisArg}) |
1234 native "JsFunction_applyDebuggerOnly"; | 1258 native "JsFunction_applyDebuggerOnly"; |
1235 | 1259 |
1236 static JsFunction _withThis(Function f) native "JsFunction_withThis"; | 1260 static JsFunction _withThis(Function f) native "JsFunction_withThis"; |
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1342 * args were passed to a function. | 1366 * args were passed to a function. |
1343 */ | 1367 */ |
1344 const _UNDEFINED = const Object(); | 1368 const _UNDEFINED = const Object(); |
1345 | 1369 |
1346 // TODO(jacobr): this method is a hack to work around the lack of proper dart | 1370 // TODO(jacobr): this method is a hack to work around the lack of proper dart |
1347 // support for varargs methods. | 1371 // support for varargs methods. |
1348 List _stripUndefinedArgs(List args) => | 1372 List _stripUndefinedArgs(List args) => |
1349 args.takeWhile((i) => i != _UNDEFINED).toList(); | 1373 args.takeWhile((i) => i != _UNDEFINED).toList(); |
1350 | 1374 |
1351 /** | 1375 /** |
1376 * Check that that if [arg] is a [Function] it is safe to pass to JavaScript. | |
1377 * To make a function safe, call [allowInterop] or [allowInteropCaptureThis]. | |
1378 */ | |
1379 @Deprecated("Internal Use Only") | |
1380 safeForTypedInterop(arg) { | |
1381 if (CHECK_JS_INVOCATIONS && arg is Function && arg is! JSFunction) { | |
1382 throw new ArgumentError( | |
1383 "Attempt to pass Function '$arg' to JavaScript via without calling allow Interop or allowInteropCaptureThis"); | |
1384 } | |
1385 } | |
1386 | |
1387 /** | |
1388 * Check that that if any elements of [args] are [Function] it is safe to pass | |
1389 * to JavaScript. To make a function safe, call [allowInterop] or | |
1390 * [allowInteropCaptureThis]. | |
1391 */ | |
1392 @Deprecated("Internal Use Only") | |
1393 void argsSafeForTypedInterop(Iterable args) { | |
1394 for (var arg in args) { | |
1395 safeForTypedInterop(arg); | |
1396 } | |
1397 } | |
1398 | |
1399 List _stripAndWrapArgs(Iterable args) { | |
1400 var ret = []; | |
1401 for (var arg in args) { | |
1402 if (arg == _UNDEFINED) break; | |
1403 ret.add(maybeWrapTypedInterop(arg)); | |
1404 } | |
1405 return ret; | |
1406 } | |
1407 | |
1408 /** | |
1352 * Returns a method that can be called with an arbitrary number (for n less | 1409 * Returns a method that can be called with an arbitrary number (for n less |
1353 * than 11) of arguments without violating Dart type checks. | 1410 * than 11) of arguments without violating Dart type checks. |
1354 */ | 1411 */ |
1355 Function _wrapAsDebuggerVarArgsFunction(JsFunction jsFunction) => ( | 1412 Function _wrapAsDebuggerVarArgsFunction(JsFunction jsFunction) => ( |
1356 [a1 = _UNDEFINED, | 1413 [a1 = _UNDEFINED, |
1357 a2 = _UNDEFINED, | 1414 a2 = _UNDEFINED, |
1358 a3 = _UNDEFINED, | 1415 a3 = _UNDEFINED, |
1359 a4 = _UNDEFINED, | 1416 a4 = _UNDEFINED, |
1360 a5 = _UNDEFINED, | 1417 a5 = _UNDEFINED, |
1361 a6 = _UNDEFINED, | 1418 a6 = _UNDEFINED, |
1362 a7 = _UNDEFINED, | 1419 a7 = _UNDEFINED, |
1363 a8 = _UNDEFINED, | 1420 a8 = _UNDEFINED, |
1364 a9 = _UNDEFINED, | 1421 a9 = _UNDEFINED, |
1365 a10 = _UNDEFINED]) => | 1422 a10 = _UNDEFINED]) => |
1366 jsFunction._applyDebuggerOnly( | 1423 jsFunction._applyDebuggerOnly( |
1367 _stripUndefinedArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10])); | 1424 _stripUndefinedArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10])); |
1368 | 1425 |
1369 // The allowInterop method is a no-op in Dartium. | 1426 /// This helper is purely a hack so we can reuse JsFunction.withThis even when |
1370 // TODO(jacobr): tag methods so we can throw if a Dart method is passed to | 1427 /// we don't care about passing JS "this". In an ideal world we would implement |
1371 // JavaScript using the new interop without calling allowInterop. | 1428 /// helpers in C++ that directly implement allowInterop and |
1429 /// allowInteropCaptureThis. | |
1430 class _CreateDartFunctionForInteropIgnoreThis implements Function { | |
1431 Function _fn; | |
1432 | |
1433 _CreateDartFunctionForInteropIgnoreThis(this._fn); | |
1434 | |
1435 call( | |
1436 [ignoredThis = _UNDEFINED, | |
1437 a1 = _UNDEFINED, | |
1438 a2 = _UNDEFINED, | |
1439 a3 = _UNDEFINED, | |
1440 a4 = _UNDEFINED, | |
1441 a5 = _UNDEFINED, | |
1442 a6 = _UNDEFINED, | |
1443 a7 = _UNDEFINED, | |
1444 a8 = _UNDEFINED, | |
1445 a9 = _UNDEFINED, | |
1446 a10 = _UNDEFINED]) { | |
1447 var ret = Function.apply( | |
1448 _fn, _stripAndWrapArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10])); | |
1449 safeForTypedInterop(ret); | |
1450 return ret; | |
1451 } | |
1452 | |
1453 noSuchMethod(Invocation invocation) { | |
1454 if (invocation.isMethod && invocation.memberName == #call) { | |
1455 // Named arguments not yet supported. | |
1456 if (invocation.namedArguments.isNotEmpty) return; | |
1457 var ret = Function.apply( | |
1458 _fn, _stripAndWrapArgs(invocation.positionalArguments.skip(1))); | |
1459 // TODO(jacobr): it would be nice to check that the return value is safe | |
1460 // for interop but we don't want to break existing addEventListener users. | |
1461 // safeForTypedInterop(ret); | |
1462 safeForTypedInterop(ret); | |
1463 return ret; | |
1464 } | |
1465 return super.noSuchMethod(invocation); | |
1466 } | |
1467 } | |
1468 | |
1469 /// See comment for [_CreateDartFunctionForInteropIgnoreThis]. | |
1470 /// This Function exists purely because JsObject doesn't have the DOM type | |
1471 /// conversion semantics we want for JS typed interop. | |
1472 class _CreateDartFunctionForInterop implements Function { | |
1473 Function _fn; | |
1474 | |
1475 _CreateDartFunctionForInterop(this._fn); | |
1476 | |
1477 call( | |
1478 [a1 = _UNDEFINED, | |
1479 a2 = _UNDEFINED, | |
1480 a3 = _UNDEFINED, | |
1481 a4 = _UNDEFINED, | |
1482 a5 = _UNDEFINED, | |
1483 a6 = _UNDEFINED, | |
1484 a7 = _UNDEFINED, | |
1485 a8 = _UNDEFINED, | |
1486 a9 = _UNDEFINED, | |
1487 a10 = _UNDEFINED]) { | |
1488 var ret = Function.apply( | |
1489 _fn, _stripAndWrapArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10])); | |
1490 safeForTypedInterop(ret); | |
1491 return ret; | |
1492 } | |
1493 | |
1494 noSuchMethod(Invocation invocation) { | |
1495 if (invocation.isMethod && invocation.memberName == #call) { | |
1496 // Named arguments not yet supported. | |
1497 if (invocation.namedArguments.isNotEmpty) return; | |
1498 var ret = Function.apply( | |
1499 _fn, _stripAndWrapArgs(invocation.positionalArguments)); | |
1500 safeForTypedInterop(ret); | |
1501 return ret; | |
1502 } | |
1503 return super.noSuchMethod(invocation); | |
1504 } | |
1505 } | |
1506 | |
1507 /// Cached JSFunction associated with the Dart Function. | |
1508 Expando<JSFunction> _interopExpando = new Expando<JSFunction>(); | |
1372 | 1509 |
1373 /// Returns a wrapper around function [f] that can be called from JavaScript | 1510 /// Returns a wrapper around function [f] that can be called from JavaScript |
1374 /// using the package:js Dart-JavaScript interop. | 1511 /// using the package:js Dart-JavaScript interop. |
1375 /// | 1512 /// |
1376 /// For performance reasons in Dart2Js, by default Dart functions cannot be | 1513 /// For performance reasons in Dart2Js, by default Dart functions cannot be |
1377 /// passed directly to JavaScript unless this method is called to create | 1514 /// passed directly to JavaScript unless this method is called to create |
1378 /// a Function compatible with both Dart and JavaScript. | 1515 /// a Function compatible with both Dart and JavaScript. |
1379 /// Calling this method repeatedly on a function will return the same function. | 1516 /// Calling this method repeatedly on a function will return the same function. |
1380 /// The [Function] returned by this method can be used from both Dart and | 1517 /// The [Function] returned by this method can be used from both Dart and |
1381 /// JavaScript. We may remove the need to call this method completely in the | 1518 /// JavaScript. We may remove the need to call this method completely in the |
1382 /// future if Dart2Js is refactored so that its function calling conventions | 1519 /// future if Dart2Js is refactored so that its function calling conventions |
1383 /// are more compatible with JavaScript. | 1520 /// are more compatible with JavaScript. |
1384 Function allowInterop(Function f) => f; | 1521 JSFunction allowInterop(Function f) { |
1522 if (f is JSFunction) { | |
1523 // The function is already a JSFunction... no need to do anything. | |
1524 return f; | |
1525 } else { | |
1526 var ret = _interopExpando[f]; | |
1527 if (ret == null) { | |
1528 // TODO(jacobr): we could optimize this. | |
1529 ret = new JSFunction.create(new JsFunction.withThis( | |
1530 new _CreateDartFunctionForInteropIgnoreThis(f))); | |
1531 _interopExpando[f] = ret; | |
1532 } | |
1533 return ret; | |
1534 } | |
1535 } | |
1385 | 1536 |
1386 Expando<JsFunction> _interopCaptureThisExpando = new Expando<JsFunction>(); | 1537 /// Cached JSFunction associated with the Dart function when "this" is |
1538 /// captured. | |
1539 Expando<JSFunction> _interopCaptureThisExpando = new Expando<JSFunction>(); | |
1387 | 1540 |
1388 /// Returns a [Function] that when called from JavaScript captures its 'this' | 1541 /// Returns a [Function] that when called from JavaScript captures its 'this' |
1389 /// binding and calls [f] with the value of this passed as the first argument. | 1542 /// binding and calls [f] with the value of this passed as the first argument. |
1390 /// When called from Dart, [null] will be passed as the first argument. | 1543 /// When called from Dart, [null] will be passed as the first argument. |
1391 /// | 1544 /// |
1392 /// See the documention for [allowInterop]. This method should only be used with | 1545 /// See the documention for [allowInterop]. This method should only be used with |
1393 /// package:js Dart-JavaScript interop. | 1546 /// package:js Dart-JavaScript interop. |
1394 Function allowInteropCaptureThis(Function f) { | 1547 JSFunction allowInteropCaptureThis(Function f) { |
1395 if (f is JsFunction) { | 1548 if (f is JSFunction) { |
1396 // Behavior when the function is already a JS function is unspecified. | 1549 // Behavior when the function is already a JS function is unspecified. |
1397 throw new ArgumentError( | 1550 throw new ArgumentError( |
1398 "Function is already a JS function so cannot capture this."); | 1551 "Function is already a JS function so cannot capture this."); |
1399 return f; | 1552 return f; |
1400 } else { | 1553 } else { |
1401 var ret = _interopCaptureThisExpando[f]; | 1554 var ret = _interopCaptureThisExpando[f]; |
1402 if (ret == null) { | 1555 if (ret == null) { |
1403 ret = new JsFunction.withThis(f); | 1556 // TODO(jacobr): we could optimize this. |
1557 ret = new JSFunction.create( | |
1558 new JsFunction.withThis(new _CreateDartFunctionForInterop(f))); | |
1404 _interopCaptureThisExpando[f] = ret; | 1559 _interopCaptureThisExpando[f] = ret; |
1405 } | 1560 } |
1406 return ret; | 1561 return ret; |
1407 } | 1562 } |
1408 } | 1563 } |
OLD | NEW |