Chromium Code Reviews| 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 |