OLD | NEW |
(Empty) | |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 /** |
| 6 * Support for interoperating with JavaScript. |
| 7 * |
| 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 |
| 10 * and invoke JavaScript functions. The library takes care of converting |
| 11 * between Dart and JavaScript objects where possible, or providing proxies if |
| 12 * conversion isn't possible. |
| 13 * |
| 14 * This library does not yet make Dart objects usable from JavaScript, their |
| 15 * methods and proeprties are not accessible, though it does allow Dart |
| 16 * functions to be passed into and called from JavaScript. |
| 17 * |
| 18 * [JsObject] is the core type and represents a proxy of a JavaScript object. |
| 19 * JsObject gives access to the underlying JavaScript objects properties and |
| 20 * methods. `JsObject`s can be acquired by calls to JavaScript, or they can be |
| 21 * created from proxies to JavaScript constructors. |
| 22 * |
| 23 * The top-level getter [context] provides a [JsObject] that represents the |
| 24 * global object in JavaScript, usually `window`. |
| 25 * |
| 26 * The following example shows an alert dialog via a JavaScript call to the |
| 27 * global function `alert()`: |
| 28 * |
| 29 * import 'dart:js'; |
| 30 * |
| 31 * main() => context.callMethod('alert', ['Hello from Dart!']); |
| 32 * |
| 33 * This example shows how to create a [JsObject] from a JavaScript constructor |
| 34 * and access its properties: |
| 35 * |
| 36 * import 'dart:js'; |
| 37 * |
| 38 * main() { |
| 39 * var object = new JsObject(context['Object']); |
| 40 * object['greeting'] = 'Hello'; |
| 41 * object['greet'] = (name) => "${object['greeting']} $name"; |
| 42 * var message = object.callMethod('greet', ['JavaScript']); |
| 43 * context['console'].callMethod('log', [message]); |
| 44 * } |
| 45 * |
| 46 * ## Proxying and automatic conversion |
| 47 * |
| 48 * When setting properties on a JsObject or passing arguments to a Javascript |
| 49 * method or function, Dart objects are automatically converted or proxied to |
| 50 * JavaScript objects. When accessing JavaScript properties, or when a Dart |
| 51 * closure is invoked from JavaScript, the JavaScript objects are also |
| 52 * converted to Dart. |
| 53 * |
| 54 * Functions and closures are proxied in such a way that they are callable. A |
| 55 * Dart closure assigned to a JavaScript property is proxied by a function in |
| 56 * JavaScript. A JavaScript function accessed from Dart is proxied by a |
| 57 * [JsFunction], which has a [apply] method to invoke it. |
| 58 * |
| 59 * The following types are transferred directly and not proxied: |
| 60 * |
| 61 * * "Basic" types: `null`, `bool`, `num`, `String`, `DateTime` |
| 62 * * `Blob` |
| 63 * * `Event` |
| 64 * * `HtmlCollection` |
| 65 * * `ImageData` |
| 66 * * `KeyRange` |
| 67 * * `Node` |
| 68 * * `NodeList` |
| 69 * * `TypedData`, including its subclasses like `Int32List`, but _not_ |
| 70 * `ByteBuffer` |
| 71 * * `Window` |
| 72 * |
| 73 * ## Converting collections with JsObject.jsify() |
| 74 * |
| 75 * To create a JavaScript collection from a Dart collection use the |
| 76 * [JsObject.jsify] constructor, which converts Dart [Map]s and [Iterable]s |
| 77 * into JavaScript Objects and Arrays. |
| 78 * |
| 79 * The following expression creates a new JavaScript object with the properties |
| 80 * `a` and `b` defined: |
| 81 * |
| 82 * var jsMap = new JsObject.jsify({'a': 1, 'b': 2}); |
| 83 * |
| 84 * This expression creates a JavaScript array: |
| 85 * |
| 86 * var jsArray = new JsObject.jsify([1, 2, 3]); |
| 87 */ |
| 88 library dart.js; |
| 89 |
| 90 import 'dart:collection' show ListMixin; |
| 91 import 'dart:nativewrappers'; |
| 92 import 'dart:math' as math; |
| 93 import 'dart:mirrors' as mirrors; |
| 94 import 'dart:html' as html; |
| 95 import 'dart:_blink' as _blink; |
| 96 import 'dart:html_common' as html_common; |
| 97 import 'dart:indexed_db' as indexed_db; |
| 98 import 'dart:typed_data'; |
| 99 import 'dart:core'; |
| 100 |
| 101 import 'cached_patches.dart'; |
| 102 |
| 103 // Pretend we are always in checked mode as we aren't interested in users |
| 104 // running Dartium code outside of checked mode. |
| 105 @Deprecated("Internal Use Only") |
| 106 final bool CHECK_JS_INVOCATIONS = true; |
| 107 |
| 108 final String _DART_RESERVED_NAME_PREFIX = r'JS$'; |
| 109 // If a private class is defined to use @JS we need to inject a non-private |
| 110 // class with a name that will not cause collisions in the library so we can |
| 111 // make JSObject implement that interface even though it is in a different |
| 112 // library. |
| 113 final String escapePrivateClassPrefix = r'$JSImplClass23402893498'; |
| 114 |
| 115 // Exposed to return ArrayBufferView from a TypedArray passed to readPixels. |
| 116 toArrayBufferView(TypedData data) native "Dart_TypedArray_ArrayBufferView"; |
| 117 |
| 118 String _stripReservedNamePrefix(String name) => |
| 119 name.startsWith(_DART_RESERVED_NAME_PREFIX) |
| 120 ? name.substring(_DART_RESERVED_NAME_PREFIX.length) |
| 121 : name; |
| 122 |
| 123 _buildArgs(Invocation invocation) { |
| 124 if (invocation.namedArguments.isEmpty) { |
| 125 return invocation.positionalArguments; |
| 126 } else { |
| 127 var varArgs = new Map<String, Object>(); |
| 128 invocation.namedArguments.forEach((symbol, val) { |
| 129 varArgs[mirrors.MirrorSystem.getName(symbol)] = val; |
| 130 }); |
| 131 return invocation.positionalArguments.toList() |
| 132 ..add(JsNative.jsify(varArgs)); |
| 133 } |
| 134 } |
| 135 |
| 136 final _allowedMethods = new Map<Symbol, _DeclarationSet>(); |
| 137 final _allowedGetters = new Map<Symbol, _DeclarationSet>(); |
| 138 final _allowedSetters = new Map<Symbol, _DeclarationSet>(); |
| 139 |
| 140 final _jsInterfaceTypes = new Set<mirrors.ClassMirror>(); |
| 141 @Deprecated("Internal Use Only") |
| 142 Iterable<mirrors.ClassMirror> get jsInterfaceTypes => _jsInterfaceTypes; |
| 143 |
| 144 class _StringLiteralEscape { |
| 145 // Character code constants. |
| 146 static const int BACKSPACE = 0x08; |
| 147 static const int TAB = 0x09; |
| 148 static const int NEWLINE = 0x0a; |
| 149 static const int CARRIAGE_RETURN = 0x0d; |
| 150 static const int FORM_FEED = 0x0c; |
| 151 static const int QUOTE = 0x22; |
| 152 static const int CHAR_$ = 0x24; |
| 153 static const int CHAR_0 = 0x30; |
| 154 static const int BACKSLASH = 0x5c; |
| 155 static const int CHAR_b = 0x62; |
| 156 static const int CHAR_f = 0x66; |
| 157 static const int CHAR_n = 0x6e; |
| 158 static const int CHAR_r = 0x72; |
| 159 static const int CHAR_t = 0x74; |
| 160 static const int CHAR_u = 0x75; |
| 161 |
| 162 final StringSink _sink; |
| 163 |
| 164 _StringLiteralEscape(this._sink); |
| 165 |
| 166 void writeString(String string) { |
| 167 _sink.write(string); |
| 168 } |
| 169 |
| 170 void writeStringSlice(String string, int start, int end) { |
| 171 _sink.write(string.substring(start, end)); |
| 172 } |
| 173 |
| 174 void writeCharCode(int charCode) { |
| 175 _sink.writeCharCode(charCode); |
| 176 } |
| 177 |
| 178 /// ('0' + x) or ('a' + x - 10) |
| 179 static int hexDigit(int x) => x < 10 ? 48 + x : 87 + x; |
| 180 |
| 181 /// Write, and suitably escape, a string's content as a JSON string literal. |
| 182 void writeStringContent(String s) { |
| 183 // Identical to JSON string literal escaping except that we also escape $. |
| 184 int offset = 0; |
| 185 final int length = s.length; |
| 186 for (int i = 0; i < length; i++) { |
| 187 int charCode = s.codeUnitAt(i); |
| 188 if (charCode > BACKSLASH) continue; |
| 189 if (charCode < 32) { |
| 190 if (i > offset) writeStringSlice(s, offset, i); |
| 191 offset = i + 1; |
| 192 writeCharCode(BACKSLASH); |
| 193 switch (charCode) { |
| 194 case BACKSPACE: |
| 195 writeCharCode(CHAR_b); |
| 196 break; |
| 197 case TAB: |
| 198 writeCharCode(CHAR_t); |
| 199 break; |
| 200 case NEWLINE: |
| 201 writeCharCode(CHAR_n); |
| 202 break; |
| 203 case FORM_FEED: |
| 204 writeCharCode(CHAR_f); |
| 205 break; |
| 206 case CARRIAGE_RETURN: |
| 207 writeCharCode(CHAR_r); |
| 208 break; |
| 209 default: |
| 210 writeCharCode(CHAR_u); |
| 211 writeCharCode(CHAR_0); |
| 212 writeCharCode(CHAR_0); |
| 213 writeCharCode(hexDigit((charCode >> 4) & 0xf)); |
| 214 writeCharCode(hexDigit(charCode & 0xf)); |
| 215 break; |
| 216 } |
| 217 } else if (charCode == QUOTE || |
| 218 charCode == BACKSLASH || |
| 219 charCode == CHAR_$) { |
| 220 if (i > offset) writeStringSlice(s, offset, i); |
| 221 offset = i + 1; |
| 222 writeCharCode(BACKSLASH); |
| 223 writeCharCode(charCode); |
| 224 } |
| 225 } |
| 226 if (offset == 0) { |
| 227 writeString(s); |
| 228 } else if (offset < length) { |
| 229 writeStringSlice(s, offset, length); |
| 230 } |
| 231 } |
| 232 |
| 233 /** |
| 234 * Serialize a [num], [String], [bool], [Null], [List] or [Map] value. |
| 235 * |
| 236 * Returns true if the value is one of these types, and false if not. |
| 237 * If a value is both a [List] and a [Map], it's serialized as a [List]. |
| 238 */ |
| 239 bool writeStringLiteral(String str) { |
| 240 writeString('"'); |
| 241 writeStringContent(str); |
| 242 writeString('"'); |
| 243 } |
| 244 } |
| 245 |
| 246 String _escapeString(String str) { |
| 247 StringBuffer output = new StringBuffer(); |
| 248 new _StringLiteralEscape(output)..writeStringLiteral(str); |
| 249 return output.toString(); |
| 250 } |
| 251 |
| 252 /// A collection of methods where all methods have the same name. |
| 253 /// This class is intended to optimize whether a specific invocation is |
| 254 /// appropriate for at least some of the methods in the collection. |
| 255 class _DeclarationSet { |
| 256 _DeclarationSet() : _members = <mirrors.DeclarationMirror>[]; |
| 257 |
| 258 static bool _checkType(obj, mirrors.TypeMirror type) { |
| 259 if (obj == null) return true; |
| 260 return mirrors.reflectType(obj.runtimeType).isSubtypeOf(type); |
| 261 } |
| 262 |
| 263 /// Returns whether the return [value] has a type is consistent with the |
| 264 /// return type from at least one of the members matching the DeclarationSet. |
| 265 bool _checkReturnType(value) { |
| 266 if (value == null) return true; |
| 267 var valueMirror = mirrors.reflectType(value.runtimeType); |
| 268 for (var member in _members) { |
| 269 if (member is mirrors.VariableMirror || member.isGetter) { |
| 270 // TODO(jacobr): actually check return types for getters that return |
| 271 // function types. |
| 272 return true; |
| 273 } else { |
| 274 if (valueMirror.isSubtypeOf(member.returnType)) return true; |
| 275 } |
| 276 } |
| 277 return false; |
| 278 } |
| 279 |
| 280 /** |
| 281 * Check whether the [invocation] is consistent with the [member] mirror. |
| 282 */ |
| 283 bool _checkDeclaration( |
| 284 Invocation invocation, mirrors.DeclarationMirror member) { |
| 285 if (member is mirrors.VariableMirror || (member as dynamic).isGetter) { |
| 286 // TODO(jacobr): actually check method types against the function type |
| 287 // returned by the getter or field. |
| 288 return true; |
| 289 } |
| 290 var parameters = (member as dynamic).parameters; |
| 291 var positionalArguments = invocation.positionalArguments; |
| 292 // Too many arguments |
| 293 if (parameters.length < positionalArguments.length) return false; |
| 294 // Too few required arguments. |
| 295 if (parameters.length > positionalArguments.length && |
| 296 !parameters[positionalArguments.length].isOptional) return false; |
| 297 for (var i = 0; i < positionalArguments.length; i++) { |
| 298 if (parameters[i].isNamed) { |
| 299 // Not enough positional arguments. |
| 300 return false; |
| 301 } |
| 302 if (!_checkType(invocation.positionalArguments[i], parameters[i].type)) |
| 303 return false; |
| 304 } |
| 305 if (invocation.namedArguments.isNotEmpty) { |
| 306 var startNamed; |
| 307 for (startNamed = parameters.length - 1; startNamed >= 0; startNamed--) { |
| 308 if (!parameters[startNamed].isNamed) break; |
| 309 } |
| 310 startNamed++; |
| 311 |
| 312 // TODO(jacobr): we are unnecessarily using an O(n^2) algorithm here. |
| 313 // If we have JS APIs with a large number of named parameters we should |
| 314 // optimize this. Either use a HashSet or invert this, walking over |
| 315 // parameters, querying invocation, and making sure we match |
| 316 //invocation.namedArguments.size keys. |
| 317 for (var name in invocation.namedArguments.keys) { |
| 318 bool match = false; |
| 319 for (var j = startNamed; j < parameters.length; j++) { |
| 320 var p = parameters[j]; |
| 321 if (p.simpleName == name) { |
| 322 if (!_checkType( |
| 323 invocation.namedArguments[name], parameters[j].type)) |
| 324 return false; |
| 325 match = true; |
| 326 break; |
| 327 } |
| 328 } |
| 329 if (match == false) return false; |
| 330 } |
| 331 } |
| 332 return true; |
| 333 } |
| 334 |
| 335 bool checkInvocation(Invocation invocation) { |
| 336 for (var member in _members) { |
| 337 if (_checkDeclaration(invocation, member)) return true; |
| 338 } |
| 339 return false; |
| 340 } |
| 341 |
| 342 void add(mirrors.DeclarationMirror mirror) { |
| 343 _members.add(mirror); |
| 344 } |
| 345 |
| 346 final List<mirrors.DeclarationMirror> _members; |
| 347 } |
| 348 |
| 349 /** |
| 350 * Temporary method that we hope to remove at some point. This method should |
| 351 * generally only be called by machine generated code. |
| 352 */ |
| 353 @Deprecated("Internal Use Only") |
| 354 void registerJsInterfaces([List<Type> classes]) { |
| 355 // This method is now obsolete in Dartium. |
| 356 } |
| 357 |
| 358 void _registerJsInterfaces(List<Type> classes) { |
| 359 for (Type type in classes) { |
| 360 mirrors.ClassMirror typeMirror = mirrors.reflectType(type); |
| 361 typeMirror.declarations.forEach((symbol, declaration) { |
| 362 if (declaration is mirrors.MethodMirror || |
| 363 declaration is mirrors.VariableMirror && !declaration.isStatic) { |
| 364 bool treatAsGetter = false; |
| 365 bool treatAsSetter = false; |
| 366 if (declaration is mirrors.VariableMirror) { |
| 367 treatAsGetter = true; |
| 368 if (!declaration.isConst && !declaration.isFinal) { |
| 369 treatAsSetter = true; |
| 370 } |
| 371 } else { |
| 372 if (declaration.isGetter) { |
| 373 treatAsGetter = true; |
| 374 } else if (declaration.isSetter) { |
| 375 treatAsSetter = true; |
| 376 } else if (!declaration.isConstructor) { |
| 377 _allowedMethods |
| 378 .putIfAbsent(symbol, () => new _DeclarationSet()) |
| 379 .add(declaration); |
| 380 } |
| 381 } |
| 382 if (treatAsGetter) { |
| 383 _allowedGetters |
| 384 .putIfAbsent(symbol, () => new _DeclarationSet()) |
| 385 .add(declaration); |
| 386 _allowedMethods |
| 387 .putIfAbsent(symbol, () => new _DeclarationSet()) |
| 388 .add(declaration); |
| 389 } |
| 390 if (treatAsSetter) { |
| 391 _allowedSetters |
| 392 .putIfAbsent(symbol, () => new _DeclarationSet()) |
| 393 .add(declaration); |
| 394 } |
| 395 } |
| 396 }); |
| 397 } |
| 398 } |
| 399 |
| 400 _finalizeJsInterfaces() native "Js_finalizeJsInterfaces"; |
| 401 |
| 402 String _getJsName(mirrors.DeclarationMirror mirror) { |
| 403 if (_atJsType != null) { |
| 404 for (var annotation in mirror.metadata) { |
| 405 if (annotation.type.reflectedType == _atJsType) { |
| 406 try { |
| 407 var name = annotation.reflectee.name; |
| 408 return name != null ? name : ""; |
| 409 } catch (e) {} |
| 410 } |
| 411 } |
| 412 } |
| 413 return null; |
| 414 } |
| 415 |
| 416 bool _isAnonymousClass(mirrors.ClassMirror mirror) { |
| 417 for (var annotation in mirror.metadata) { |
| 418 if (mirrors.MirrorSystem.getName(annotation.type.simpleName) == |
| 419 "_Anonymous") { |
| 420 mirrors.LibraryMirror library = annotation.type.owner; |
| 421 var uri = library.uri; |
| 422 // make sure the annotation is from package://js |
| 423 if (uri.scheme == 'package' && uri.path == 'js/js.dart') { |
| 424 return true; |
| 425 } |
| 426 } |
| 427 } |
| 428 return false; |
| 429 } |
| 430 |
| 431 bool _hasJsName(mirrors.DeclarationMirror mirror) { |
| 432 if (_atJsType != null) { |
| 433 for (var annotation in mirror.metadata) { |
| 434 if (annotation.type.reflectedType == _atJsType) { |
| 435 return true; |
| 436 } |
| 437 } |
| 438 } |
| 439 return false; |
| 440 } |
| 441 |
| 442 var _domNameType; |
| 443 |
| 444 bool hasDomName(mirrors.DeclarationMirror mirror) { |
| 445 var location = mirror.location; |
| 446 if (location == null || location.sourceUri.scheme != 'dart') return false; |
| 447 for (var annotation in mirror.metadata) { |
| 448 if (mirrors.MirrorSystem.getName(annotation.type.simpleName) == "DomName") { |
| 449 // We can't make sure the annotation is in dart: as Dartium believes it |
| 450 // is file://dart/sdk/lib/html/html_common/metadata.dart |
| 451 // instead of a proper dart: location. |
| 452 return true; |
| 453 } |
| 454 } |
| 455 return false; |
| 456 } |
| 457 |
| 458 _getJsMemberName(mirrors.DeclarationMirror mirror) { |
| 459 var name = _getJsName(mirror); |
| 460 return name == null || name.isEmpty |
| 461 ? _stripReservedNamePrefix(_getDeclarationName(mirror)) |
| 462 : name; |
| 463 } |
| 464 |
| 465 // TODO(jacobr): handle setters correctyl. |
| 466 String _getDeclarationName(mirrors.DeclarationMirror declaration) { |
| 467 var name = mirrors.MirrorSystem.getName(declaration.simpleName); |
| 468 if (declaration is mirrors.MethodMirror && declaration.isSetter) { |
| 469 assert(name.endsWith("=")); |
| 470 name = name.substring(0, name.length - 1); |
| 471 } |
| 472 return name; |
| 473 } |
| 474 |
| 475 final _JS_LIBRARY_PREFIX = "js_library"; |
| 476 final _UNDEFINED_VAR = "_UNDEFINED_JS_CONST"; |
| 477 |
| 478 String _accessJsPath(String path) => _accessJsPathHelper(path.split(".")); |
| 479 |
| 480 String _accessJsPathHelper(Iterable<String> parts) { |
| 481 var sb = new StringBuffer(); |
| 482 sb |
| 483 ..write('${_JS_LIBRARY_PREFIX}.JsNative.getProperty(' * parts.length) |
| 484 ..write("${_JS_LIBRARY_PREFIX}.context"); |
| 485 for (var p in parts) { |
| 486 sb.write(", ${_escapeString(p)})"); |
| 487 } |
| 488 return sb.toString(); |
| 489 } |
| 490 |
| 491 // TODO(jacobr): remove these helpers and add JsNative.setPropertyDotted, |
| 492 // getPropertyDotted, and callMethodDotted helpers that would be simpler |
| 493 // and more efficient. |
| 494 String _accessJsPathSetter(String path) { |
| 495 var parts = path.split("."); |
| 496 return "${_JS_LIBRARY_PREFIX}.JsNative.setProperty(${_accessJsPathHelper(parts
.getRange(0, parts.length - 1)) |
| 497 }, ${_escapeString(parts.last)}, v)"; |
| 498 } |
| 499 |
| 500 String _accessJsPathCallMethodHelper(String path) { |
| 501 var parts = path.split("."); |
| 502 return "${_JS_LIBRARY_PREFIX}.JsNative.callMethod(${_accessJsPathHelper(parts.
getRange(0, parts.length - 1)) |
| 503 }, ${_escapeString(parts.last)},"; |
| 504 } |
| 505 |
| 506 @Deprecated("Internal Use Only") |
| 507 void addMemberHelper( |
| 508 mirrors.MethodMirror declaration, String path, StringBuffer sb, |
| 509 {bool isStatic: false, String memberName}) { |
| 510 if (!declaration.isConstructor) { |
| 511 var jsName = _getJsMemberName(declaration); |
| 512 path = (path != null && path.isNotEmpty) ? "${path}.${jsName}" : jsName; |
| 513 } |
| 514 var name = memberName != null ? memberName : _getDeclarationName(declaration); |
| 515 if (declaration.isConstructor) { |
| 516 sb.write("factory"); |
| 517 } else if (isStatic) { |
| 518 sb.write("static"); |
| 519 } else { |
| 520 sb.write("@patch"); |
| 521 } |
| 522 sb.write(" "); |
| 523 if (declaration.isGetter) { |
| 524 sb.write("get $name => ${_accessJsPath(path)};"); |
| 525 } else if (declaration.isSetter) { |
| 526 sb.write("set $name(v) {\n" |
| 527 " ${_JS_LIBRARY_PREFIX}.safeForTypedInterop(v);\n" |
| 528 " return ${_accessJsPathSetter(path)};\n" |
| 529 "}\n"); |
| 530 } else { |
| 531 sb.write("$name("); |
| 532 bool hasOptional = false; |
| 533 int i = 0; |
| 534 var args = <String>[]; |
| 535 for (var p in declaration.parameters) { |
| 536 assert(!p.isNamed); // TODO(jacobr): throw. |
| 537 assert(!p.hasDefaultValue); |
| 538 if (i > 0) { |
| 539 sb.write(", "); |
| 540 } |
| 541 if (p.isOptional && !hasOptional) { |
| 542 sb.write("["); |
| 543 hasOptional = true; |
| 544 } |
| 545 var arg = "p$i"; |
| 546 args.add(arg); |
| 547 sb.write(arg); |
| 548 if (p.isOptional) { |
| 549 sb.write("=${_UNDEFINED_VAR}"); |
| 550 } |
| 551 i++; |
| 552 } |
| 553 if (hasOptional) { |
| 554 sb.write("]"); |
| 555 } |
| 556 // TODO(jacobr): |
| 557 sb.write(") {\n"); |
| 558 for (var arg in args) { |
| 559 sb.write(" ${_JS_LIBRARY_PREFIX}.safeForTypedInterop($arg);\n"); |
| 560 } |
| 561 sb.write(" return "); |
| 562 if (declaration.isConstructor) { |
| 563 sb.write("${_JS_LIBRARY_PREFIX}.JsNative.callConstructor("); |
| 564 sb..write(_accessJsPath(path))..write(","); |
| 565 } else { |
| 566 sb.write(_accessJsPathCallMethodHelper(path)); |
| 567 } |
| 568 sb.write("[${args.join(",")}]"); |
| 569 |
| 570 if (hasOptional) { |
| 571 sb.write(".takeWhile((i) => i != ${_UNDEFINED_VAR}).toList()"); |
| 572 } |
| 573 sb.write(");"); |
| 574 sb.write("}\n"); |
| 575 } |
| 576 sb.write("\n"); |
| 577 } |
| 578 |
| 579 bool _isExternal(mirrors.MethodMirror mirror) { |
| 580 // This try-catch block is a workaround for BUG:24834. |
| 581 try { |
| 582 return mirror.isExternal; |
| 583 } catch (e) {} |
| 584 return false; |
| 585 } |
| 586 |
| 587 List<String> _generateExternalMethods( |
| 588 List<String> libraryPaths, bool useCachedPatches) { |
| 589 var staticCodegen = <String>[]; |
| 590 |
| 591 if (libraryPaths.length == 0) { |
| 592 mirrors.currentMirrorSystem().libraries.forEach((uri, library) { |
| 593 var library_name = "${uri.scheme}:${uri.path}"; |
| 594 if (useCachedPatches && cached_patches.containsKey(library_name)) { |
| 595 // Use the pre-generated patch files for DOM dart:nnnn libraries. |
| 596 var patch = cached_patches[library_name]; |
| 597 staticCodegen.addAll(patch); |
| 598 } else if (_hasJsName(library)) { |
| 599 // Library marked with @JS |
| 600 _generateLibraryCodegen(uri, library, staticCodegen); |
| 601 } else if (!useCachedPatches) { |
| 602 // Can't use the cached patches file, instead this is a signal to genera
te |
| 603 // the patches for this file. |
| 604 _generateLibraryCodegen(uri, library, staticCodegen); |
| 605 } |
| 606 }); // End of library foreach |
| 607 } else { |
| 608 // Used to generate cached_patches.dart file for all IDL generated dart: |
| 609 // files to the WebKit DOM. |
| 610 for (var library_name in libraryPaths) { |
| 611 var parts = library_name.split(':'); |
| 612 var uri = new Uri(scheme: parts[0], path: parts[1]); |
| 613 var library = mirrors.currentMirrorSystem().libraries[uri]; |
| 614 _generateLibraryCodegen(uri, library, staticCodegen); |
| 615 } |
| 616 } |
| 617 |
| 618 return staticCodegen; |
| 619 } |
| 620 |
| 621 _generateLibraryCodegen(uri, library, staticCodegen) { |
| 622 // Is it a dart generated library? |
| 623 var dartLibrary = uri.scheme == 'dart'; |
| 624 |
| 625 var sb = new StringBuffer(); |
| 626 String jsLibraryName = _getJsName(library); |
| 627 |
| 628 // Sort by patch file by its declaration name. |
| 629 var sortedDeclKeys = library.declarations.keys.toList(); |
| 630 sortedDeclKeys.sort((a, b) => mirrors.MirrorSystem |
| 631 .getName(a) |
| 632 .compareTo(mirrors.MirrorSystem.getName(b))); |
| 633 |
| 634 sortedDeclKeys.forEach((name) { |
| 635 var declaration = library.declarations[name]; |
| 636 if (declaration is mirrors.MethodMirror) { |
| 637 if ((_hasJsName(declaration) || jsLibraryName != null) && |
| 638 _isExternal(declaration)) { |
| 639 addMemberHelper(declaration, jsLibraryName, sb); |
| 640 } |
| 641 } else if (declaration is mirrors.ClassMirror) { |
| 642 mirrors.ClassMirror clazz = declaration; |
| 643 var isDom = dartLibrary ? hasDomName(clazz) : false; |
| 644 var isJsInterop = _hasJsName(clazz); |
| 645 if (isDom || isJsInterop) { |
| 646 // TODO(jacobr): verify class implements JavaScriptObject. |
| 647 var className = mirrors.MirrorSystem.getName(clazz.simpleName); |
| 648 bool isPrivateUserDefinedClass = |
| 649 className.startsWith('_') && !dartLibrary; |
| 650 var classNameImpl = '${className}Impl'; |
| 651 var sbPatch = new StringBuffer(); |
| 652 if (isJsInterop) { |
| 653 String jsClassName = _getJsMemberName(clazz); |
| 654 |
| 655 jsInterfaceTypes.add(clazz); |
| 656 clazz.declarations.forEach((name, declaration) { |
| 657 if (declaration is! mirrors.MethodMirror || |
| 658 !_isExternal(declaration)) return; |
| 659 if (declaration.isFactoryConstructor && _isAnonymousClass(clazz)) { |
| 660 sbPatch.write(" factory ${className}("); |
| 661 int i = 0; |
| 662 var args = <String>[]; |
| 663 for (var p in declaration.parameters) { |
| 664 args.add(mirrors.MirrorSystem.getName(p.simpleName)); |
| 665 i++; |
| 666 } |
| 667 if (args.isNotEmpty) { |
| 668 sbPatch |
| 669 ..write('{') |
| 670 ..write( |
| 671 args.map((name) => '$name:${_UNDEFINED_VAR}').join(", ")) |
| 672 ..write('}'); |
| 673 } |
| 674 sbPatch.write(") {\n" |
| 675 " var ret = ${_JS_LIBRARY_PREFIX}.JsNative.newObject();\n")
; |
| 676 i = 0; |
| 677 for (var p in declaration.parameters) { |
| 678 assert(p.isNamed); // TODO(jacobr): throw. |
| 679 var name = args[i]; |
| 680 var jsName = _stripReservedNamePrefix( |
| 681 mirrors.MirrorSystem.getName(p.simpleName)); |
| 682 sbPatch.write(" if($name != ${_UNDEFINED_VAR}) {\n" |
| 683 " ${_JS_LIBRARY_PREFIX}.safeForTypedInterop($name);\n" |
| 684 " ${_JS_LIBRARY_PREFIX}.JsNative.setProperty(ret, ${_es
capeString(jsName)}, $name);\n" |
| 685 " }\n"); |
| 686 i++; |
| 687 } |
| 688 |
| 689 sbPatch.write(" return ret;" |
| 690 "}\n"); |
| 691 } else if (declaration.isConstructor || |
| 692 declaration.isFactoryConstructor) { |
| 693 sbPatch.write(" "); |
| 694 addMemberHelper( |
| 695 declaration, |
| 696 (jsLibraryName != null && jsLibraryName.isNotEmpty) |
| 697 ? "${jsLibraryName}.${jsClassName}" |
| 698 : jsClassName, |
| 699 sbPatch, |
| 700 isStatic: true, |
| 701 memberName: className); |
| 702 } |
| 703 }); // End of clazz.declarations.forEach |
| 704 |
| 705 clazz.staticMembers.forEach((memberName, member) { |
| 706 if (_isExternal(member)) { |
| 707 sbPatch.write(" "); |
| 708 addMemberHelper( |
| 709 member, |
| 710 (jsLibraryName != null && jsLibraryName.isNotEmpty) |
| 711 ? "${jsLibraryName}.${jsClassName}" |
| 712 : jsClassName, |
| 713 sbPatch, |
| 714 isStatic: true); |
| 715 } |
| 716 }); |
| 717 } |
| 718 if (isDom) { |
| 719 sbPatch.write( |
| 720 " static Type get instanceRuntimeType => ${classNameImpl};\n"); |
| 721 } |
| 722 if (isPrivateUserDefinedClass) { |
| 723 sb.write(""" |
| 724 class ${escapePrivateClassPrefix}${className} implements $className {} |
| 725 """); |
| 726 } |
| 727 |
| 728 if (sbPatch.isNotEmpty) { |
| 729 var typeVariablesClause = ''; |
| 730 if (!clazz.typeVariables.isEmpty) { |
| 731 typeVariablesClause = |
| 732 '<${clazz.typeVariables.map((m) => mirrors.MirrorSystem.getName(
m.simpleName)).join(',')}>'; |
| 733 } |
| 734 sb.write(""" |
| 735 @patch class $className$typeVariablesClause { |
| 736 $sbPatch |
| 737 } |
| 738 """); |
| 739 if (isDom) { |
| 740 sb.write(""" |
| 741 class $classNameImpl$typeVariablesClause extends $className implements ${_JS_LIB
RARY_PREFIX}.JSObjectInterfacesDom { |
| 742 ${classNameImpl}.internal_() : super.internal_(); |
| 743 get runtimeType => $className; |
| 744 toString() => super.toString(); |
| 745 } |
| 746 """); |
| 747 } |
| 748 } |
| 749 } |
| 750 } |
| 751 }); |
| 752 if (sb.isNotEmpty) { |
| 753 staticCodegen |
| 754 ..add(uri.toString()) |
| 755 ..add("${uri}_js_interop_patch.dart") |
| 756 ..add(""" |
| 757 import 'dart:js' as ${_JS_LIBRARY_PREFIX}; |
| 758 |
| 759 /** |
| 760 * Placeholder object for cases where we need to determine exactly how many |
| 761 * args were passed to a function. |
| 762 */ |
| 763 const ${_UNDEFINED_VAR} = const Object(); |
| 764 |
| 765 ${sb} |
| 766 """); |
| 767 } |
| 768 } |
| 769 |
| 770 // Remember the @JS type to compare annotation type. |
| 771 var _atJsType = -1; |
| 772 |
| 773 void setupJsTypeCache() { |
| 774 // Cache the @JS Type. |
| 775 if (_atJsType == -1) { |
| 776 var uri = new Uri(scheme: "package", path: "js/js.dart"); |
| 777 var jsLibrary = mirrors.currentMirrorSystem().libraries[uri]; |
| 778 if (jsLibrary != null) { |
| 779 // @ JS used somewhere. |
| 780 var jsDeclaration = jsLibrary.declarations[new Symbol("JS")]; |
| 781 _atJsType = jsDeclaration.reflectedType; |
| 782 } else { |
| 783 // @ JS not used in any library. |
| 784 _atJsType = null; |
| 785 } |
| 786 } |
| 787 } |
| 788 |
| 789 /** |
| 790 * Generates part files defining source code for JSObjectImpl, all DOM classes |
| 791 * classes. This codegen is needed so that type checks for all registered |
| 792 * JavaScript interop classes pass. |
| 793 * If genCachedPatches is true then the patch files don't exist this is a specia
l |
| 794 * signal to generate and emit the patches to stdout to be captured and put into |
| 795 * the file sdk/lib/js/dartium/cached_patches.dart |
| 796 */ |
| 797 List<String> _generateInteropPatchFiles( |
| 798 List<String> libraryPaths, genCachedPatches) { |
| 799 // Cache the @JS Type. |
| 800 if (_atJsType == -1) setupJsTypeCache(); |
| 801 |
| 802 var ret = |
| 803 _generateExternalMethods(libraryPaths, genCachedPatches ? false : true); |
| 804 var libraryPrefixes = new Map<mirrors.LibraryMirror, String>(); |
| 805 var prefixNames = new Set<String>(); |
| 806 var sb = new StringBuffer(); |
| 807 |
| 808 var implements = <String>[]; |
| 809 var implementsArray = <String>[]; |
| 810 var implementsDom = <String>[]; |
| 811 var listMirror = mirrors.reflectType(List); |
| 812 var functionMirror = mirrors.reflectType(Function); |
| 813 var jsObjectMirror = mirrors.reflectType(JSObject); |
| 814 |
| 815 for (var typeMirror in jsInterfaceTypes) { |
| 816 mirrors.LibraryMirror libraryMirror = typeMirror.owner; |
| 817 var location = libraryMirror.location; |
| 818 var dartLibrary = location != null && location.sourceUri.scheme == 'dart'; |
| 819 |
| 820 var prefixName; |
| 821 if (libraryPrefixes.containsKey(libraryMirror)) { |
| 822 prefixName = libraryPrefixes[libraryMirror]; |
| 823 } else { |
| 824 var basePrefixName = |
| 825 mirrors.MirrorSystem.getName(libraryMirror.simpleName); |
| 826 basePrefixName = basePrefixName.replaceAll('.', '_'); |
| 827 if (basePrefixName.isEmpty) basePrefixName = "lib"; |
| 828 prefixName = basePrefixName; |
| 829 var i = 1; |
| 830 while (prefixNames.contains(prefixName)) { |
| 831 prefixName = '$basePrefixName$i'; |
| 832 i++; |
| 833 } |
| 834 prefixNames.add(prefixName); |
| 835 libraryPrefixes[libraryMirror] = prefixName; |
| 836 } |
| 837 var isArray = typeMirror.isSubtypeOf(listMirror); |
| 838 var isFunction = typeMirror.isSubtypeOf(functionMirror); |
| 839 var isJSObject = typeMirror.isSubtypeOf(jsObjectMirror); |
| 840 var className = mirrors.MirrorSystem.getName(typeMirror.simpleName); |
| 841 var isPrivateUserDefinedClass = className.startsWith('_') && !dartLibrary; |
| 842 if (isPrivateUserDefinedClass) |
| 843 className = '${escapePrivateClassPrefix}${className}'; |
| 844 var fullName = '${prefixName}.${className}'; |
| 845 (isArray ? implementsArray : implements).add(fullName); |
| 846 if (!isArray && !isFunction && !isJSObject) { |
| 847 // For DOM classes we need to be a bit more conservative at tagging them |
| 848 // as implementing JS interop classes risks strange unintended |
| 849 // consequences as unrleated code may have instanceof checks. Checking |
| 850 // for isJSObject ensures we do not accidentally pull in existing |
| 851 // dart:html classes as they all have JSObject as a base class. |
| 852 // Note that methods from these classes can still be called on a |
| 853 // dart:html instance but checked mode type checks will fail. This is |
| 854 // not ideal but is better than causing strange breaks in existing |
| 855 // code that uses dart:html. |
| 856 // TODO(jacobr): consider throwing compile time errors if @JS classes |
| 857 // extend JSObject as that case cannot be safely handled in Dartium. |
| 858 implementsDom.add(fullName); |
| 859 } |
| 860 } |
| 861 libraryPrefixes.forEach((libraryMirror, prefix) { |
| 862 sb.writeln('import "${libraryMirror.uri}" as $prefix;'); |
| 863 }); |
| 864 buildImplementsClause(classes) => |
| 865 classes.isEmpty ? "" : "implements ${classes.join(', ')}"; |
| 866 var implementsClause = buildImplementsClause(implements); |
| 867 var implementsClauseDom = buildImplementsClause(implementsDom); |
| 868 // TODO(jacobr): only certain classes need to be implemented by |
| 869 // JsFunctionImpl. |
| 870 var allTypes = []..addAll(implements)..addAll(implementsArray); |
| 871 sb.write(''' |
| 872 class JSObjectImpl extends JSObject $implementsClause { |
| 873 JSObjectImpl.internal() : super.internal(); |
| 874 } |
| 875 |
| 876 class JSFunctionImpl extends JSFunction $implementsClause { |
| 877 JSFunctionImpl.internal() : super.internal(); |
| 878 } |
| 879 |
| 880 class JSArrayImpl extends JSArray ${buildImplementsClause(implementsArray)} { |
| 881 JSArrayImpl.internal() : super.internal(); |
| 882 } |
| 883 |
| 884 // Interfaces that are safe to slam on all DOM classes. |
| 885 // Adding implementsClause would be risky as it could contain Function which |
| 886 // is likely to break a lot of instanceof checks. |
| 887 abstract class JSObjectInterfacesDom $implementsClauseDom { |
| 888 } |
| 889 |
| 890 @patch class JSObject { |
| 891 static Type get instanceRuntimeType => JSObjectImpl; |
| 892 } |
| 893 |
| 894 @patch class JSFunction { |
| 895 static Type get instanceRuntimeType => JSFunctionImpl; |
| 896 } |
| 897 |
| 898 @patch class JSArray { |
| 899 static Type get instanceRuntimeType => JSArrayImpl; |
| 900 } |
| 901 |
| 902 _registerAllJsInterfaces() { |
| 903 _registerJsInterfaces([${allTypes.join(", ")}]); |
| 904 } |
| 905 |
| 906 '''); |
| 907 ret..addAll(["dart:js", "JSInteropImpl.dart", sb.toString()]); |
| 908 return ret; |
| 909 } |
| 910 |
| 911 // Start of block of helper methods facilitating emulating JavaScript Array |
| 912 // methods on Dart List objects passed to JavaScript via JS interop. |
| 913 // TODO(jacobr): match JS more closely. |
| 914 String _toStringJs(obj) => '$obj'; |
| 915 |
| 916 // TODO(jacobr): this might not exactly match JS semantics but should be |
| 917 // adequate for now. |
| 918 int _toIntJs(obj) { |
| 919 if (obj is int) return obj; |
| 920 if (obj is num) return obj.toInt(); |
| 921 return num.parse('$obj'.trim(), (_) => 0).toInt(); |
| 922 } |
| 923 |
| 924 // TODO(jacobr): this might not exactly match JS semantics but should be |
| 925 // adequate for now. |
| 926 num _toNumJs(obj) { |
| 927 return obj is num ? obj : num.parse('$obj'.trim(), (_) => 0); |
| 928 } |
| 929 |
| 930 /// Match the behavior of setting List length in JavaScript with the exception |
| 931 /// that Dart does not distinguish undefined and null. |
| 932 _setListLength(List list, rawlen) { |
| 933 num len = _toNumJs(rawlen); |
| 934 if (len is! int || len < 0) { |
| 935 throw new RangeError("Invalid array length"); |
| 936 } |
| 937 if (len > list.length) { |
| 938 _arrayExtend(list, len); |
| 939 } else if (len < list.length) { |
| 940 list.removeRange(len, list.length); |
| 941 } |
| 942 return rawlen; |
| 943 } |
| 944 |
| 945 // TODO(jacobr): should we really bother with this method instead of just |
| 946 // shallow copying to a JS array and calling the JavaScript join method? |
| 947 String _arrayJoin(List list, sep) { |
| 948 if (sep == null) { |
| 949 sep = ","; |
| 950 } |
| 951 return list.map((e) => e == null ? "" : e.toString()).join(sep.toString()); |
| 952 } |
| 953 |
| 954 // TODO(jacobr): should we really bother with this method instead of just |
| 955 // shallow copying to a JS array and using the toString method? |
| 956 String _arrayToString(List list) => _arrayJoin(list, ","); |
| 957 |
| 958 int _arrayPush(List list, List args) { |
| 959 for (var e in args) { |
| 960 list.add(e); |
| 961 } |
| 962 return list.length; |
| 963 } |
| 964 |
| 965 _arrayPop(List list) { |
| 966 if (list.length > 0) return list.removeLast(); |
| 967 } |
| 968 |
| 969 // TODO(jacobr): would it be better to just copy input to a JS List |
| 970 // and call Array.concat? |
| 971 List _arrayConcat(List input, List args) { |
| 972 var ret = new List.from(input); |
| 973 for (var e in args) { |
| 974 // TODO(jacobr): technically in ES6 we should use |
| 975 // Symbol.isConcatSpreadable to determine whether call addAll. Once v8 |
| 976 // supports it, we can make all Dart classes implementing Iterable |
| 977 // specify isConcatSpreadable and tweak this behavior to allow Iterable. |
| 978 if (e is List) { |
| 979 ret.addAll(e); |
| 980 } else { |
| 981 ret.add(e); |
| 982 } |
| 983 } |
| 984 return ret; |
| 985 } |
| 986 |
| 987 List _arraySplice(List input, List args) { |
| 988 int start = 0; |
| 989 if (args.length > 0) { |
| 990 var rawStart = _toIntJs(args[0]); |
| 991 if (rawStart < 0) { |
| 992 start = math.max(0, input.length - rawStart); |
| 993 } else { |
| 994 start = math.min(input.length, rawStart); |
| 995 } |
| 996 } |
| 997 var end = start; |
| 998 if (args.length > 1) { |
| 999 var rawDeleteCount = _toIntJs(args[1]); |
| 1000 if (rawDeleteCount < 0) rawDeleteCount = 0; |
| 1001 end = math.min(input.length, start + rawDeleteCount); |
| 1002 } |
| 1003 var replacement = []; |
| 1004 var removedElements = input.getRange(start, end).toList(); |
| 1005 if (args.length > 2) { |
| 1006 replacement = args.getRange(2, args.length); |
| 1007 } |
| 1008 input.replaceRange(start, end, replacement); |
| 1009 return removedElements; |
| 1010 } |
| 1011 |
| 1012 List _arrayReverse(List l) { |
| 1013 for (var i = 0, j = l.length - 1; i < j; i++, j--) { |
| 1014 var tmp = l[i]; |
| 1015 l[i] = l[j]; |
| 1016 l[j] = tmp; |
| 1017 } |
| 1018 return l; |
| 1019 } |
| 1020 |
| 1021 _arrayShift(List l) { |
| 1022 if (l.isEmpty) return null; // Technically we should return undefined. |
| 1023 return l.removeAt(0); |
| 1024 } |
| 1025 |
| 1026 int _arrayUnshift(List l, List args) { |
| 1027 l.insertAll(0, args); |
| 1028 return l.length; |
| 1029 } |
| 1030 |
| 1031 _arrayExtend(List l, int newLength) { |
| 1032 for (var i = l.length; i < newLength; i++) { |
| 1033 // TODO(jacobr): we'd really like to add undefined to better match |
| 1034 // JavaScript semantics. |
| 1035 l.add(null); |
| 1036 } |
| 1037 } |
| 1038 |
| 1039 List _arraySort(List l, rawCompare) { |
| 1040 // TODO(jacobr): alternately we could just copy the Array to JavaScript, |
| 1041 // invoke the JS sort method and then copy the result back to Dart. |
| 1042 Comparator compare; |
| 1043 if (rawCompare == null) { |
| 1044 compare = (a, b) => _toStringJs(a).compareTo(_toStringJs(b)); |
| 1045 } else if (rawCompare is JsFunction) { |
| 1046 compare = (a, b) => rawCompare.apply([a, b]); |
| 1047 } else { |
| 1048 compare = rawCompare; |
| 1049 } |
| 1050 l.sort(compare); |
| 1051 return l; |
| 1052 } |
| 1053 // End of block of helper methods to emulate JavaScript Array methods on Dart Li
st. |
| 1054 |
| 1055 /** |
| 1056 * Can be called to provide a predictable point where no more JS interfaces can |
| 1057 * be added. Creating an instance of JsObject will also automatically trigger |
| 1058 * all JsObjects to be finalized. |
| 1059 */ |
| 1060 @Deprecated("Internal Use Only") |
| 1061 void finalizeJsInterfaces() { |
| 1062 if (_finalized == true) { |
| 1063 throw 'JSInterop class registration already finalized'; |
| 1064 } |
| 1065 _finalizeJsInterfaces(); |
| 1066 } |
| 1067 |
| 1068 JsObject _cachedContext; |
| 1069 |
| 1070 JsObject get _context native "Js_context_Callback"; |
| 1071 |
| 1072 bool get _finalized native "Js_interfacesFinalized_Callback"; |
| 1073 |
| 1074 JsObject get context { |
| 1075 if (_cachedContext == null) { |
| 1076 _cachedContext = _context; |
| 1077 } |
| 1078 return _cachedContext; |
| 1079 } |
| 1080 |
| 1081 _lookupType(o, bool isCrossFrame, bool isElement) { |
| 1082 try { |
| 1083 var type = html_common.lookupType(o, isElement); |
| 1084 var typeMirror = mirrors.reflectType(type); |
| 1085 var legacyInteropConvertToNative = |
| 1086 typeMirror.isSubtypeOf(mirrors.reflectType(html.Blob)) || |
| 1087 typeMirror.isSubtypeOf(mirrors.reflectType(html.Event)) || |
| 1088 typeMirror.isSubtypeOf(mirrors.reflectType(indexed_db.KeyRange)) || |
| 1089 typeMirror.isSubtypeOf(mirrors.reflectType(html.ImageData)) || |
| 1090 typeMirror.isSubtypeOf(mirrors.reflectType(html.Node)) || |
| 1091 // TypedData is removed from this list as it is converted directly |
| 1092 // rather than flowing through the interceptor code path. |
| 1093 // typeMirror.isSubtypeOf(mirrors.reflectType(typed_data.TypedData)) || |
| 1094 typeMirror.isSubtypeOf(mirrors.reflectType(html.Window)); |
| 1095 if (isCrossFrame && |
| 1096 !typeMirror.isSubtypeOf(mirrors.reflectType(html.Window))) { |
| 1097 // TODO(jacobr): evaluate using the true cross frame Window class, etc. |
| 1098 // as well as triggering that legacy JS Interop returns raw JsObject |
| 1099 // instances. |
| 1100 legacyInteropConvertToNative = false; |
| 1101 } |
| 1102 return [type, legacyInteropConvertToNative]; |
| 1103 } catch (e) {} |
| 1104 return [JSObject.instanceRuntimeType, false]; |
| 1105 } |
| 1106 |
| 1107 /** |
| 1108 * Base class for both the legacy JsObject class and the modern JSObject class. |
| 1109 * This allows the JsNative utility class tobehave identically whether it is |
| 1110 * called on a JsObject or a JSObject. |
| 1111 */ |
| 1112 class _JSObjectBase extends NativeFieldWrapperClass2 { |
| 1113 String _toString() native "JSObject_toString"; |
| 1114 _callMethod(String name, List args) native "JSObject_callMethod"; |
| 1115 _operator_getter(String property) native "JSObject_[]"; |
| 1116 _operator_setter(String property, value) native "JSObject_[]="; |
| 1117 bool _hasProperty(String property) native "JsObject_hasProperty"; |
| 1118 bool _instanceof(/*JsFunction|JSFunction*/ type) native "JsObject_instanceof"; |
| 1119 |
| 1120 int get hashCode native "JSObject_hashCode"; |
| 1121 } |
| 1122 |
| 1123 /** |
| 1124 * Proxies a JavaScript object to Dart. |
| 1125 * |
| 1126 * The properties of the JavaScript object are accessible via the `[]` and |
| 1127 * `[]=` operators. Methods are callable via [callMethod]. |
| 1128 */ |
| 1129 class JsObject extends _JSObjectBase { |
| 1130 JsObject.internal(); |
| 1131 |
| 1132 /** |
| 1133 * Constructs a new JavaScript object from [constructor] and returns a proxy |
| 1134 * to it. |
| 1135 */ |
| 1136 factory JsObject(JsFunction constructor, [List arguments]) { |
| 1137 try { |
| 1138 return _create(constructor, arguments); |
| 1139 } catch (e) { |
| 1140 // Re-throw any errors (returned as a string) as a DomException. |
| 1141 throw new html.DomException.jsInterop(e); |
| 1142 } |
| 1143 } |
| 1144 |
| 1145 static JsObject _create(JsFunction constructor, arguments) |
| 1146 native "JsObject_constructorCallback"; |
| 1147 |
| 1148 /** |
| 1149 * Constructs a [JsObject] that proxies a native Dart object; _for expert use |
| 1150 * only_. |
| 1151 * |
| 1152 * Use this constructor only if you wish to get access to JavaScript |
| 1153 * properties attached to a browser host object, such as a Node or Blob, that |
| 1154 * is normally automatically converted into a native Dart object. |
| 1155 * |
| 1156 * An exception will be thrown if [object] either is `null` or has the type |
| 1157 * `bool`, `num`, or `String`. |
| 1158 */ |
| 1159 factory JsObject.fromBrowserObject(object) { |
| 1160 if (object is num || object is String || object is bool || object == null) { |
| 1161 throw new ArgumentError("object cannot be a num, string, bool, or null"); |
| 1162 } |
| 1163 if (object is JsObject) return object; |
| 1164 return _fromBrowserObject(object); |
| 1165 } |
| 1166 |
| 1167 /** |
| 1168 * Recursively converts a JSON-like collection of Dart objects to a |
| 1169 * collection of JavaScript objects and returns a [JsObject] proxy to it. |
| 1170 * |
| 1171 * [object] must be a [Map] or [Iterable], the contents of which are also |
| 1172 * converted. Maps and Iterables are copied to a new JavaScript object. |
| 1173 * Primitives and other transferrable values are directly converted to their |
| 1174 * JavaScript type, and all other objects are proxied. |
| 1175 */ |
| 1176 factory JsObject.jsify(object) { |
| 1177 if ((object is! Map) && (object is! Iterable)) { |
| 1178 throw new ArgumentError("object must be a Map or Iterable"); |
| 1179 } |
| 1180 return _jsify(object); |
| 1181 } |
| 1182 |
| 1183 static JsObject _jsify(object) native "JsObject_jsify"; |
| 1184 |
| 1185 static JsObject _fromBrowserObject(object) |
| 1186 native "JsObject_fromBrowserObject"; |
| 1187 |
| 1188 /** |
| 1189 * Returns the value associated with [property] from the proxied JavaScript |
| 1190 * object. |
| 1191 * |
| 1192 * The type of [property] must be either [String] or [num]. |
| 1193 */ |
| 1194 operator [](property) { |
| 1195 try { |
| 1196 return _operator_getterLegacy(property); |
| 1197 } catch (e) { |
| 1198 // Re-throw any errors (returned as a string) as a DomException. |
| 1199 throw new html.DomException.jsInterop(e); |
| 1200 } |
| 1201 } |
| 1202 |
| 1203 _operator_getterLegacy(property) native "JsObject_[]Legacy"; |
| 1204 |
| 1205 /** |
| 1206 * Sets the value associated with [property] on the proxied JavaScript |
| 1207 * object. |
| 1208 * |
| 1209 * The type of [property] must be either [String] or [num]. |
| 1210 */ |
| 1211 operator []=(property, value) { |
| 1212 try { |
| 1213 _operator_setterLegacy(property, value); |
| 1214 } catch (e) { |
| 1215 // Re-throw any errors (returned as a string) as a DomException. |
| 1216 throw new html.DomException.jsInterop(e); |
| 1217 } |
| 1218 } |
| 1219 |
| 1220 _operator_setterLegacy(property, value) native "JsObject_[]=Legacy"; |
| 1221 |
| 1222 int get hashCode native "JsObject_hashCode"; |
| 1223 |
| 1224 operator ==(other) { |
| 1225 if (other is! JsObject && other is! JSObject) return false; |
| 1226 return _identityEquality(this, other); |
| 1227 } |
| 1228 |
| 1229 static bool _identityEquality(a, b) native "JsObject_identityEquality"; |
| 1230 |
| 1231 /** |
| 1232 * Returns `true` if the JavaScript object contains the specified property |
| 1233 * either directly or though its prototype chain. |
| 1234 * |
| 1235 * This is the equivalent of the `in` operator in JavaScript. |
| 1236 */ |
| 1237 bool hasProperty(String property) => _hasProperty(property); |
| 1238 |
| 1239 /** |
| 1240 * Removes [property] from the JavaScript object. |
| 1241 * |
| 1242 * This is the equivalent of the `delete` operator in JavaScript. |
| 1243 */ |
| 1244 void deleteProperty(String property) native "JsObject_deleteProperty"; |
| 1245 |
| 1246 /** |
| 1247 * Returns `true` if the JavaScript object has [type] in its prototype chain. |
| 1248 * |
| 1249 * This is the equivalent of the `instanceof` operator in JavaScript. |
| 1250 */ |
| 1251 bool instanceof(JsFunction type) => _instanceof(type); |
| 1252 |
| 1253 /** |
| 1254 * Returns the result of the JavaScript objects `toString` method. |
| 1255 */ |
| 1256 String toString() { |
| 1257 try { |
| 1258 return _toString(); |
| 1259 } catch (e) { |
| 1260 return super.toString(); |
| 1261 } |
| 1262 } |
| 1263 |
| 1264 String _toString() native "JsObject_toString"; |
| 1265 |
| 1266 /** |
| 1267 * Calls [method] on the JavaScript object with the arguments [args] and |
| 1268 * returns the result. |
| 1269 * |
| 1270 * The type of [method] must be either [String] or [num]. |
| 1271 */ |
| 1272 callMethod(String method, [List args]) { |
| 1273 try { |
| 1274 return _callMethodLegacy(method, args); |
| 1275 } catch (e) { |
| 1276 if (hasProperty(method)) { |
| 1277 // Return a DomException if DOM call returned an error. |
| 1278 throw new html.DomException.jsInterop(e); |
| 1279 } else { |
| 1280 throw new NoSuchMethodError(this, new Symbol(method), args, null); |
| 1281 } |
| 1282 } |
| 1283 } |
| 1284 |
| 1285 _callMethodLegacy(String name, List args) native "JsObject_callMethodLegacy"; |
| 1286 } |
| 1287 |
| 1288 /// Base class for all JS objects used through dart:html and typed JS interop. |
| 1289 @Deprecated("Internal Use Only") |
| 1290 class JSObject extends _JSObjectBase { |
| 1291 JSObject.internal() {} |
| 1292 external static Type get instanceRuntimeType; |
| 1293 |
| 1294 /** |
| 1295 * Returns the result of the JavaScript objects `toString` method. |
| 1296 */ |
| 1297 String toString() { |
| 1298 try { |
| 1299 return _toString(); |
| 1300 } catch (e) { |
| 1301 return super.toString(); |
| 1302 } |
| 1303 } |
| 1304 |
| 1305 noSuchMethod(Invocation invocation) { |
| 1306 throwError() { |
| 1307 super.noSuchMethod(invocation); |
| 1308 } |
| 1309 |
| 1310 String name = _stripReservedNamePrefix( |
| 1311 mirrors.MirrorSystem.getName(invocation.memberName)); |
| 1312 argsSafeForTypedInterop(invocation.positionalArguments); |
| 1313 if (invocation.isGetter) { |
| 1314 if (CHECK_JS_INVOCATIONS) { |
| 1315 var matches = _allowedGetters[invocation.memberName]; |
| 1316 if (matches == null && |
| 1317 !_allowedMethods.containsKey(invocation.memberName)) { |
| 1318 throwError(); |
| 1319 } |
| 1320 var ret = _operator_getter(name); |
| 1321 if (matches != null) return ret; |
| 1322 if (ret is Function || |
| 1323 (ret is JsFunction /* shouldn't be needed in the future*/) && |
| 1324 _allowedMethods.containsKey(invocation.memberName)) |
| 1325 return ret; // Warning: we have not bound "this"... we could type chec
k on the Function but that is of little value in Dart. |
| 1326 throwError(); |
| 1327 } else { |
| 1328 // TODO(jacobr): should we throw if the JavaScript object doesn't have t
he property? |
| 1329 return _operator_getter(name); |
| 1330 } |
| 1331 } else if (invocation.isSetter) { |
| 1332 if (CHECK_JS_INVOCATIONS) { |
| 1333 var matches = _allowedSetters[invocation.memberName]; |
| 1334 if (matches == null || !matches.checkInvocation(invocation)) |
| 1335 throwError(); |
| 1336 } |
| 1337 assert(name.endsWith("=")); |
| 1338 name = name.substring(0, name.length - 1); |
| 1339 return _operator_setter(name, invocation.positionalArguments.first); |
| 1340 } else { |
| 1341 // TODO(jacobr): also allow calling getters that look like functions. |
| 1342 var matches; |
| 1343 if (CHECK_JS_INVOCATIONS) { |
| 1344 matches = _allowedMethods[invocation.memberName]; |
| 1345 if (matches == null || !matches.checkInvocation(invocation)) |
| 1346 throwError(); |
| 1347 } |
| 1348 var ret = _callMethod(name, _buildArgs(invocation)); |
| 1349 if (CHECK_JS_INVOCATIONS) { |
| 1350 if (!matches._checkReturnType(ret)) { |
| 1351 html.window.console.error("Return value for method: ${name} is " |
| 1352 "${ret.runtimeType} which is inconsistent with all typed " |
| 1353 "JS interop definitions for method ${name}."); |
| 1354 } |
| 1355 } |
| 1356 return ret; |
| 1357 } |
| 1358 } |
| 1359 } |
| 1360 |
| 1361 @Deprecated("Internal Use Only") |
| 1362 class JSArray extends JSObject with ListMixin { |
| 1363 JSArray.internal() : super.internal(); |
| 1364 external static Type get instanceRuntimeType; |
| 1365 |
| 1366 // Reuse JsArray_length as length behavior is unchanged. |
| 1367 int get length native "JsArray_length"; |
| 1368 |
| 1369 set length(int length) { |
| 1370 _operator_setter('length', length); |
| 1371 } |
| 1372 |
| 1373 _checkIndex(int index, {bool insert: false}) { |
| 1374 int length = insert ? this.length + 1 : this.length; |
| 1375 if (index is int && (index < 0 || index >= length)) { |
| 1376 throw new RangeError.range(index, 0, length); |
| 1377 } |
| 1378 } |
| 1379 |
| 1380 _checkRange(int start, int end) { |
| 1381 int cachedLength = this.length; |
| 1382 if (start < 0 || start > cachedLength) { |
| 1383 throw new RangeError.range(start, 0, cachedLength); |
| 1384 } |
| 1385 if (end < start || end > cachedLength) { |
| 1386 throw new RangeError.range(end, start, cachedLength); |
| 1387 } |
| 1388 } |
| 1389 |
| 1390 _indexed_getter(int index) native "JSArray_indexed_getter"; |
| 1391 _indexed_setter(int index, o) native "JSArray_indexed_setter"; |
| 1392 |
| 1393 // Methods required by ListMixin |
| 1394 |
| 1395 operator [](index) { |
| 1396 if (index is int) { |
| 1397 _checkIndex(index); |
| 1398 } |
| 1399 |
| 1400 return _indexed_getter(index); |
| 1401 } |
| 1402 |
| 1403 void operator []=(int index, value) { |
| 1404 _checkIndex(index); |
| 1405 _indexed_setter(index, value); |
| 1406 } |
| 1407 } |
| 1408 |
| 1409 @Deprecated("Internal Use Only") |
| 1410 class JSFunction extends JSObject implements Function { |
| 1411 JSFunction.internal() : super.internal(); |
| 1412 |
| 1413 external static Type get instanceRuntimeType; |
| 1414 |
| 1415 call( |
| 1416 [a1 = _UNDEFINED, |
| 1417 a2 = _UNDEFINED, |
| 1418 a3 = _UNDEFINED, |
| 1419 a4 = _UNDEFINED, |
| 1420 a5 = _UNDEFINED, |
| 1421 a6 = _UNDEFINED, |
| 1422 a7 = _UNDEFINED, |
| 1423 a8 = _UNDEFINED, |
| 1424 a9 = _UNDEFINED, |
| 1425 a10 = _UNDEFINED]) { |
| 1426 return _apply( |
| 1427 _stripUndefinedArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10])); |
| 1428 } |
| 1429 |
| 1430 noSuchMethod(Invocation invocation) { |
| 1431 if (invocation.isMethod && invocation.memberName == #call) { |
| 1432 return _apply(_buildArgs(invocation)); |
| 1433 } |
| 1434 return super.noSuchMethod(invocation); |
| 1435 } |
| 1436 |
| 1437 dynamic _apply(List args, {thisArg}) native "JSFunction_apply"; |
| 1438 |
| 1439 static JSFunction _createWithThis(Function f) |
| 1440 native "JSFunction_createWithThis"; |
| 1441 static JSFunction _create(Function f) native "JSFunction_create"; |
| 1442 } |
| 1443 |
| 1444 // JavaScript interop methods that do not automatically wrap to dart:html types. |
| 1445 // Warning: this API is not exposed to dart:js. |
| 1446 // TODO(jacobr): rename to JSNative and make at least part of this API public. |
| 1447 @Deprecated("Internal Use Only") |
| 1448 class JsNative { |
| 1449 static JSObject jsify(object) native "JSObject_jsify"; |
| 1450 static JSObject newObject() native "JSObject_newObject"; |
| 1451 static JSArray newArray() native "JSObject_newArray"; |
| 1452 |
| 1453 static hasProperty(_JSObjectBase o, name) => o._hasProperty(name); |
| 1454 static getProperty(_JSObjectBase o, name) => o._operator_getter(name); |
| 1455 static setProperty(_JSObjectBase o, name, value) => |
| 1456 o._operator_setter(name, value); |
| 1457 static callMethod(_JSObjectBase o, String method, List args) => |
| 1458 o._callMethod(method, args); |
| 1459 static instanceof(_JSObjectBase o, /*JsFunction|JSFunction*/ type) => |
| 1460 o._instanceof(type); |
| 1461 static callConstructor0(_JSObjectBase constructor) |
| 1462 native "JSNative_callConstructor0"; |
| 1463 static callConstructor(_JSObjectBase constructor, List args) |
| 1464 native "JSNative_callConstructor"; |
| 1465 |
| 1466 static toTypedObject(JsObject o) native "JSNative_toTypedObject"; |
| 1467 |
| 1468 /** |
| 1469 * Same behavior as new JsFunction.withThis except that JavaScript "this" is n
ot |
| 1470 * wrapped. |
| 1471 */ |
| 1472 static JSFunction withThis(Function f) native "JsFunction_withThisNoWrap"; |
| 1473 } |
| 1474 |
| 1475 /** |
| 1476 * Proxies a JavaScript Function object. |
| 1477 */ |
| 1478 class JsFunction extends JsObject { |
| 1479 JsFunction.internal() : super.internal(); |
| 1480 |
| 1481 /** |
| 1482 * Returns a [JsFunction] that captures its 'this' binding and calls [f] |
| 1483 * with the value of this passed as the first argument. |
| 1484 */ |
| 1485 factory JsFunction.withThis(Function f) => _withThis(f); |
| 1486 |
| 1487 /** |
| 1488 * Invokes the JavaScript function with arguments [args]. If [thisArg] is |
| 1489 * supplied it is the value of `this` for the invocation. |
| 1490 */ |
| 1491 dynamic apply(List args, {thisArg}) => _apply(args, thisArg: thisArg); |
| 1492 |
| 1493 dynamic _apply(List args, {thisArg}) native "JsFunction_apply"; |
| 1494 |
| 1495 /** |
| 1496 * Internal only version of apply which uses debugger proxies of Dart objects |
| 1497 * rather than opaque handles. This method is private because it cannot be |
| 1498 * efficiently implemented in Dart2Js so should only be used by internal |
| 1499 * tools. |
| 1500 */ |
| 1501 _applyDebuggerOnly(List args, {thisArg}) |
| 1502 native "JsFunction_applyDebuggerOnly"; |
| 1503 |
| 1504 static JsFunction _withThis(Function f) native "JsFunction_withThis"; |
| 1505 } |
| 1506 |
| 1507 /** |
| 1508 * A [List] proxying a JavaScript Array. |
| 1509 */ |
| 1510 class JsArray<E> extends JsObject with ListMixin<E> { |
| 1511 JsArray.internal() : super.internal(); |
| 1512 |
| 1513 factory JsArray() => _newJsArray(); |
| 1514 |
| 1515 static JsArray _newJsArray() native "JsArray_newJsArray"; |
| 1516 |
| 1517 factory JsArray.from(Iterable<E> other) => |
| 1518 _newJsArrayFromSafeList(new List.from(other)); |
| 1519 |
| 1520 static JsArray _newJsArrayFromSafeList(List list) |
| 1521 native "JsArray_newJsArrayFromSafeList"; |
| 1522 |
| 1523 _checkIndex(int index, {bool insert: false}) { |
| 1524 int length = insert ? this.length + 1 : this.length; |
| 1525 if (index is int && (index < 0 || index >= length)) { |
| 1526 throw new RangeError.range(index, 0, length); |
| 1527 } |
| 1528 } |
| 1529 |
| 1530 _checkRange(int start, int end) { |
| 1531 int cachedLength = this.length; |
| 1532 if (start < 0 || start > cachedLength) { |
| 1533 throw new RangeError.range(start, 0, cachedLength); |
| 1534 } |
| 1535 if (end < start || end > cachedLength) { |
| 1536 throw new RangeError.range(end, start, cachedLength); |
| 1537 } |
| 1538 } |
| 1539 |
| 1540 // Methods required by ListMixin |
| 1541 |
| 1542 E operator [](index) { |
| 1543 if (index is int) { |
| 1544 _checkIndex(index); |
| 1545 } |
| 1546 |
| 1547 return super[index]; |
| 1548 } |
| 1549 |
| 1550 void operator []=(index, E value) { |
| 1551 if (index is int) { |
| 1552 _checkIndex(index); |
| 1553 } |
| 1554 super[index] = value; |
| 1555 } |
| 1556 |
| 1557 int get length native "JsArray_length"; |
| 1558 |
| 1559 set length(int length) { |
| 1560 super['length'] = length; |
| 1561 } |
| 1562 |
| 1563 // Methods overridden for better performance |
| 1564 |
| 1565 void add(E value) { |
| 1566 callMethod('push', [value]); |
| 1567 } |
| 1568 |
| 1569 void addAll(Iterable<E> iterable) { |
| 1570 // TODO(jacobr): this can be optimized slightly. |
| 1571 callMethod('push', new List.from(iterable)); |
| 1572 } |
| 1573 |
| 1574 void insert(int index, E element) { |
| 1575 _checkIndex(index, insert: true); |
| 1576 callMethod('splice', [index, 0, element]); |
| 1577 } |
| 1578 |
| 1579 E removeAt(int index) { |
| 1580 _checkIndex(index); |
| 1581 return callMethod('splice', [index, 1])[0]; |
| 1582 } |
| 1583 |
| 1584 E removeLast() { |
| 1585 if (length == 0) throw new RangeError(-1); |
| 1586 return callMethod('pop'); |
| 1587 } |
| 1588 |
| 1589 void removeRange(int start, int end) { |
| 1590 _checkRange(start, end); |
| 1591 callMethod('splice', [start, end - start]); |
| 1592 } |
| 1593 |
| 1594 void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) { |
| 1595 _checkRange(start, end); |
| 1596 int length = end - start; |
| 1597 if (length == 0) return; |
| 1598 if (skipCount < 0) throw new ArgumentError(skipCount); |
| 1599 var args = [start, length]..addAll(iterable.skip(skipCount).take(length)); |
| 1600 callMethod('splice', args); |
| 1601 } |
| 1602 |
| 1603 void sort([int compare(E a, E b)]) { |
| 1604 callMethod('sort', [compare]); |
| 1605 } |
| 1606 } |
| 1607 |
| 1608 /** |
| 1609 * Placeholder object for cases where we need to determine exactly how many |
| 1610 * args were passed to a function. |
| 1611 */ |
| 1612 const _UNDEFINED = const Object(); |
| 1613 |
| 1614 // TODO(jacobr): this method is a hack to work around the lack of proper dart |
| 1615 // support for varargs methods. |
| 1616 List _stripUndefinedArgs(List args) => |
| 1617 args.takeWhile((i) => i != _UNDEFINED).toList(); |
| 1618 |
| 1619 /** |
| 1620 * Check that that if [arg] is a [Function] it is safe to pass to JavaScript. |
| 1621 * To make a function safe, call [allowInterop] or [allowInteropCaptureThis]. |
| 1622 */ |
| 1623 @Deprecated("Internal Use Only") |
| 1624 safeForTypedInterop(arg) { |
| 1625 if (CHECK_JS_INVOCATIONS && arg is Function && arg is! JSFunction) { |
| 1626 throw new ArgumentError( |
| 1627 "Attempt to pass Function '$arg' to JavaScript via without calling allow
Interop or allowInteropCaptureThis"); |
| 1628 } |
| 1629 } |
| 1630 |
| 1631 /** |
| 1632 * Check that that if any elements of [args] are [Function] it is safe to pass |
| 1633 * to JavaScript. To make a function safe, call [allowInterop] or |
| 1634 * [allowInteropCaptureThis]. |
| 1635 */ |
| 1636 @Deprecated("Internal Use Only") |
| 1637 void argsSafeForTypedInterop(Iterable args) { |
| 1638 for (var arg in args) { |
| 1639 safeForTypedInterop(arg); |
| 1640 } |
| 1641 } |
| 1642 |
| 1643 /** |
| 1644 * Returns a method that can be called with an arbitrary number (for n less |
| 1645 * than 11) of arguments without violating Dart type checks. |
| 1646 */ |
| 1647 Function _wrapAsDebuggerVarArgsFunction(JsFunction jsFunction) => ( |
| 1648 [a1 = _UNDEFINED, |
| 1649 a2 = _UNDEFINED, |
| 1650 a3 = _UNDEFINED, |
| 1651 a4 = _UNDEFINED, |
| 1652 a5 = _UNDEFINED, |
| 1653 a6 = _UNDEFINED, |
| 1654 a7 = _UNDEFINED, |
| 1655 a8 = _UNDEFINED, |
| 1656 a9 = _UNDEFINED, |
| 1657 a10 = _UNDEFINED]) => |
| 1658 jsFunction._applyDebuggerOnly( |
| 1659 _stripUndefinedArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10])); |
| 1660 |
| 1661 /// Returns a wrapper around function [f] that can be called from JavaScript |
| 1662 /// using the package:js Dart-JavaScript interop. |
| 1663 /// |
| 1664 /// For performance reasons in Dart2Js, by default Dart functions cannot be |
| 1665 /// passed directly to JavaScript unless this method is called to create |
| 1666 /// a Function compatible with both Dart and JavaScript. |
| 1667 /// Calling this method repeatedly on a function will return the same function. |
| 1668 /// The [Function] returned by this method can be used from both Dart and |
| 1669 /// JavaScript. We may remove the need to call this method completely in the |
| 1670 /// future if Dart2Js is refactored so that its function calling conventions |
| 1671 /// are more compatible with JavaScript. |
| 1672 Function/*=F*/ allowInterop/*<F extends Function>*/(Function/*=F*/ f) { |
| 1673 if (f is JSFunction) { |
| 1674 // The function is already a JSFunction... no need to do anything. |
| 1675 return f; |
| 1676 } else { |
| 1677 return JSFunction._create(f); |
| 1678 } |
| 1679 } |
| 1680 |
| 1681 /// Cached JSFunction associated with the Dart function when "this" is |
| 1682 /// captured. |
| 1683 Expando<JSFunction> _interopCaptureThisExpando = new Expando<JSFunction>(); |
| 1684 |
| 1685 /// Returns a [Function] that when called from JavaScript captures its 'this' |
| 1686 /// binding and calls [f] with the value of this passed as the first argument. |
| 1687 /// When called from Dart, [null] will be passed as the first argument. |
| 1688 /// |
| 1689 /// See the documentation for [allowInterop]. This method should only be used |
| 1690 /// with package:js Dart-JavaScript interop. |
| 1691 JSFunction allowInteropCaptureThis(Function f) { |
| 1692 if (f is JSFunction) { |
| 1693 // Behavior when the function is already a JS function is unspecified. |
| 1694 throw new ArgumentError( |
| 1695 "Function is already a JS function so cannot capture this."); |
| 1696 return f; |
| 1697 } else { |
| 1698 var ret = _interopCaptureThisExpando[f]; |
| 1699 if (ret == null) { |
| 1700 // TODO(jacobr): we could optimize this. |
| 1701 ret = JSFunction._createWithThis(f); |
| 1702 _interopCaptureThisExpando[f] = ret; |
| 1703 } |
| 1704 return ret; |
| 1705 } |
| 1706 } |
OLD | NEW |