OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, 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 266 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
277 throw new NoSuchMethodError(_jsObject, new Symbol(method), args, {}); | 277 throw new NoSuchMethodError(_jsObject, new Symbol(method), args, {}); |
278 } | 278 } |
279 return _convertToDart(JS('', '#.apply(#, #)', fn, _jsObject, args)); | 279 return _convertToDart(JS('', '#.apply(#, #)', fn, _jsObject, args)); |
280 } | 280 } |
281 } | 281 } |
282 | 282 |
283 /** | 283 /** |
284 * Proxies a JavaScript Function object. | 284 * Proxies a JavaScript Function object. |
285 */ | 285 */ |
286 class JsFunction extends JsObject { | 286 class JsFunction extends JsObject { |
287 | |
288 /** | 287 /** |
289 * Returns a [JsFunction] that captures its 'this' binding and calls [f] | 288 * Returns a [JsFunction] that captures its 'this' binding and calls [f] |
290 * with the value of this passed as the first argument. | 289 * with the value of this passed as the first argument. |
291 */ | 290 */ |
292 factory JsFunction.withThis(Function f) { | 291 factory JsFunction.withThis(Function f) { |
293 return new JsFunction._fromJs(JS('', 'function(/*...arguments*/) {' | 292 return new JsFunction._fromJs(JS( |
| 293 '', |
| 294 'function(/*...arguments*/) {' |
294 ' let args = [#(this)];' | 295 ' let args = [#(this)];' |
295 ' for (let arg of arguments) {' | 296 ' for (let arg of arguments) {' |
296 ' args.push(#(arg));' | 297 ' args.push(#(arg));' |
297 ' }' | 298 ' }' |
298 ' return #(#(...args));' | 299 ' return #(#(...args));' |
299 '}', _convertToDart, _convertToDart, _convertToJS, f)); | 300 '}', |
| 301 _convertToDart, |
| 302 _convertToDart, |
| 303 _convertToJS, |
| 304 f)); |
300 } | 305 } |
301 | 306 |
302 JsFunction._fromJs(jsObject) : super._fromJs(jsObject); | 307 JsFunction._fromJs(jsObject) : super._fromJs(jsObject); |
303 | 308 |
304 /** | 309 /** |
305 * Invokes the JavaScript function with arguments [args]. If [thisArg] is | 310 * Invokes the JavaScript function with arguments [args]. If [thisArg] is |
306 * supplied it is the value of `this` for the invocation. | 311 * supplied it is the value of `this` for the invocation. |
307 */ | 312 */ |
308 dynamic apply(List args, {thisArg}) => _convertToDart(JS('', '#.apply(#, #)', | 313 dynamic apply(List args, {thisArg}) => _convertToDart(JS( |
309 _jsObject, _convertToJS(thisArg), | 314 '', |
| 315 '#.apply(#, #)', |
| 316 _jsObject, |
| 317 _convertToJS(thisArg), |
310 args == null ? null : new List.from(args.map(_convertToJS)))); | 318 args == null ? null : new List.from(args.map(_convertToJS)))); |
311 } | 319 } |
312 | 320 |
313 // TODO(jmesserly): this is totally unnecessary in dev_compiler. | 321 // TODO(jmesserly): this is totally unnecessary in dev_compiler. |
314 /** A [List] that proxies a JavaScript array. */ | 322 /** A [List] that proxies a JavaScript array. */ |
315 class JsArray<E> extends JsObject with ListMixin<E> { | 323 class JsArray<E> extends JsObject with ListMixin<E> { |
316 | |
317 /** | 324 /** |
318 * Creates a new JavaScript array. | 325 * Creates a new JavaScript array. |
319 */ | 326 */ |
320 JsArray() : super._fromJs([]); | 327 JsArray() : super._fromJs([]); |
321 | 328 |
322 /** | 329 /** |
323 * Creates a new JavaScript array and initializes it to the contents of | 330 * Creates a new JavaScript array and initializes it to the contents of |
324 * [other]. | 331 * [other]. |
325 */ | 332 */ |
326 JsArray.from(Iterable<E> other) | 333 JsArray.from(Iterable<E> other) |
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
414 void removeRange(int start, int end) { | 421 void removeRange(int start, int end) { |
415 _checkRange(start, end, length); | 422 _checkRange(start, end, length); |
416 callMethod('splice', [start, end - start]); | 423 callMethod('splice', [start, end - start]); |
417 } | 424 } |
418 | 425 |
419 void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) { | 426 void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) { |
420 _checkRange(start, end, this.length); | 427 _checkRange(start, end, this.length); |
421 int length = end - start; | 428 int length = end - start; |
422 if (length == 0) return; | 429 if (length == 0) return; |
423 if (skipCount < 0) throw new ArgumentError(skipCount); | 430 if (skipCount < 0) throw new ArgumentError(skipCount); |
424 var args = <Object>[start, length]..addAll(iterable.skip(skipCount).take(len
gth)); | 431 var args = <Object>[start, length] |
| 432 ..addAll(iterable.skip(skipCount).take(length)); |
425 callMethod('splice', args); | 433 callMethod('splice', args); |
426 } | 434 } |
427 | 435 |
428 void sort([int compare(E a, E b)]) { | 436 void sort([int compare(E a, E b)]) { |
429 // Note: arr.sort(null) is a type error in FF | 437 // Note: arr.sort(null) is a type error in FF |
430 callMethod('sort', compare == null ? [] : [compare]); | 438 callMethod('sort', compare == null ? [] : [compare]); |
431 } | 439 } |
432 } | 440 } |
433 | 441 |
434 // Cross frame objects should not be considered browser types. | 442 // Cross frame objects should not be considered browser types. |
435 // We include the the instanceof Object test to filter out cross frame objects | 443 // We include the the instanceof Object test to filter out cross frame objects |
436 // on FireFox. Surprisingly on FireFox the instanceof Window test succeeds for | 444 // on FireFox. Surprisingly on FireFox the instanceof Window test succeeds for |
437 // cross frame windows while the instanceof Object test fails. | 445 // cross frame windows while the instanceof Object test fails. |
438 bool _isBrowserType(o) => JS('bool', | 446 bool _isBrowserType(o) => JS( |
| 447 'bool', |
439 '# instanceof Object && (' | 448 '# instanceof Object && (' |
440 '# instanceof Blob || ' | 449 '# instanceof Blob || ' |
441 '# instanceof Event || ' | 450 '# instanceof Event || ' |
442 '(window.KeyRange && # instanceof KeyRange) || ' | 451 '(window.KeyRange && # instanceof KeyRange) || ' |
443 '(window.IDBKeyRange && # instanceof IDBKeyRange) || ' | 452 '(window.IDBKeyRange && # instanceof IDBKeyRange) || ' |
444 '# instanceof ImageData || ' | 453 '# instanceof ImageData || ' |
445 '# instanceof Node || ' | 454 '# instanceof Node || ' |
446 // Int8Array.__proto__ is TypedArray. | 455 // Int8Array.__proto__ is TypedArray. |
447 '(window.Int8Array && # instanceof Int8Array.__proto__) || ' | 456 '(window.Int8Array && # instanceof Int8Array.__proto__) || ' |
448 '# instanceof Window)', o, o, o, o, o, o, o, o, o); | 457 '# instanceof Window)', |
| 458 o, |
| 459 o, |
| 460 o, |
| 461 o, |
| 462 o, |
| 463 o, |
| 464 o, |
| 465 o, |
| 466 o); |
449 | 467 |
450 class _DartObject { | 468 class _DartObject { |
451 final _dartObj; | 469 final _dartObj; |
452 _DartObject(this._dartObj); | 470 _DartObject(this._dartObj); |
453 } | 471 } |
454 | 472 |
455 dynamic _convertToJS(dynamic o) { | 473 dynamic _convertToJS(dynamic o) { |
456 if (o == null || | 474 if (o == null || o is String || o is num || o is bool || _isBrowserType(o)) { |
457 o is String || | |
458 o is num || | |
459 o is bool || | |
460 _isBrowserType(o)) { | |
461 return o; | 475 return o; |
462 } else if (o is DateTime) { | 476 } else if (o is DateTime) { |
463 return Primitives.lazyAsJsDate(o); | 477 return Primitives.lazyAsJsDate(o); |
464 } else if (o is JsObject) { | 478 } else if (o is JsObject) { |
465 return o._jsObject; | 479 return o._jsObject; |
466 } else if (o is Function) { | 480 } else if (o is Function) { |
467 return _putIfAbsent(_jsProxies, o, _wrapDartFunction); | 481 return _putIfAbsent(_jsProxies, o, _wrapDartFunction); |
468 } else { | 482 } else { |
469 // TODO(jmesserly): for now, we wrap other objects, to keep compatibility | 483 // TODO(jmesserly): for now, we wrap other objects, to keep compatibility |
470 // with the original dart:js behavior. | 484 // with the original dart:js behavior. |
471 return _putIfAbsent(_jsProxies, o, (o) => new _DartObject(o)); | 485 return _putIfAbsent(_jsProxies, o, (o) => new _DartObject(o)); |
472 } | 486 } |
473 } | 487 } |
474 | 488 |
475 dynamic _wrapDartFunction(f) { | 489 dynamic _wrapDartFunction(f) { |
476 var wrapper = JS('', 'function(/*...arguments*/) {' | 490 var wrapper = JS( |
| 491 '', |
| 492 'function(/*...arguments*/) {' |
477 ' let args = Array.prototype.map.call(arguments, #);' | 493 ' let args = Array.prototype.map.call(arguments, #);' |
478 ' return #(#(...args));' | 494 ' return #(#(...args));' |
479 '}', _convertToDart, _convertToJS, f); | 495 '}', |
| 496 _convertToDart, |
| 497 _convertToJS, |
| 498 f); |
480 JS('', '#.set(#, #)', _dartProxies, wrapper, f); | 499 JS('', '#.set(#, #)', _dartProxies, wrapper, f); |
481 | 500 |
482 return wrapper; | 501 return wrapper; |
483 } | 502 } |
484 | 503 |
485 // converts a Dart object to a reference to a native JS object | 504 // converts a Dart object to a reference to a native JS object |
486 // which might be a DartObject JS->Dart proxy | 505 // which might be a DartObject JS->Dart proxy |
487 Object _convertToDart(o) { | 506 Object _convertToDart(o) { |
488 if (JS('bool', '# == null', o) || | 507 if (JS('bool', '# == null', o) || |
489 JS('bool', 'typeof # == "string"', o) || | 508 JS('bool', 'typeof # == "string"', o) || |
490 JS('bool', 'typeof # == "number"', o) || | 509 JS('bool', 'typeof # == "number"', o) || |
491 JS('bool', 'typeof # == "boolean"', o) || | 510 JS('bool', 'typeof # == "boolean"', o) || |
492 _isBrowserType(o)) { | 511 _isBrowserType(o)) { |
493 return o; | 512 return o; |
494 } else if (JS('bool', '# instanceof Date', o)) { | 513 } else if (JS('bool', '# instanceof Date', o)) { |
495 var ms = JS('num', '#.getTime()', o); | 514 var ms = JS('num', '#.getTime()', o); |
496 return new DateTime.fromMillisecondsSinceEpoch(ms); | 515 return new DateTime.fromMillisecondsSinceEpoch(ms); |
497 } else if (o is _DartObject && | 516 } else if (o is _DartObject && |
498 JS('bool', 'dart.jsobject != dart.getReifiedType(#)', o)) { | 517 JS('bool', 'dart.jsobject != dart.getReifiedType(#)', o)) { |
499 return o._dartObj; | 518 return o._dartObj; |
500 } else { | 519 } else { |
501 return _wrapToDart(o); | 520 return _wrapToDart(o); |
502 } | 521 } |
503 } | 522 } |
504 | 523 |
505 Object _wrapToDart(o) => _putIfAbsent(_dartProxies, o, _wrapToDartHelper); | 524 Object _wrapToDart(o) => _putIfAbsent(_dartProxies, o, _wrapToDartHelper); |
506 | 525 |
507 Object _wrapToDartHelper(o) { | 526 Object _wrapToDartHelper(o) { |
508 if (JS('bool', 'typeof # == "function"', o)) { | 527 if (JS('bool', 'typeof # == "function"', o)) { |
(...skipping 25 matching lines...) Expand all Loading... |
534 /// using the package:js Dart-JavaScript interop. | 553 /// using the package:js Dart-JavaScript interop. |
535 /// | 554 /// |
536 /// For performance reasons in Dart2Js, by default Dart functions cannot be | 555 /// For performance reasons in Dart2Js, by default Dart functions cannot be |
537 /// passed directly to JavaScript unless this method is called to create | 556 /// passed directly to JavaScript unless this method is called to create |
538 /// a Function compatible with both Dart and JavaScript. | 557 /// a Function compatible with both Dart and JavaScript. |
539 /// Calling this method repeatedly on a function will return the same function. | 558 /// Calling this method repeatedly on a function will return the same function. |
540 /// The [Function] returned by this method can be used from both Dart and | 559 /// The [Function] returned by this method can be used from both Dart and |
541 /// JavaScript. We may remove the need to call this method completely in the | 560 /// JavaScript. We may remove the need to call this method completely in the |
542 /// future if Dart2Js is refactored so that its function calling conventions | 561 /// future if Dart2Js is refactored so that its function calling conventions |
543 /// are more compatible with JavaScript. | 562 /// are more compatible with JavaScript. |
544 Function /*=F*/ allowInterop/*<F extends Function>*/(Function /*=F*/ f) => f; | 563 Function/*=F*/ allowInterop/*<F extends Function>*/(Function/*=F*/ f) => f; |
545 | 564 |
546 Expando<Function> _interopCaptureThisExpando = new Expando<Function>(); | 565 Expando<Function> _interopCaptureThisExpando = new Expando<Function>(); |
547 | 566 |
548 /// Returns a [Function] that when called from JavaScript captures its 'this' | 567 /// Returns a [Function] that when called from JavaScript captures its 'this' |
549 /// binding and calls [f] with the value of this passed as the first argument. | 568 /// binding and calls [f] with the value of this passed as the first argument. |
550 /// When called from Dart, [null] will be passed as the first argument. | 569 /// When called from Dart, [null] will be passed as the first argument. |
551 /// | 570 /// |
552 /// See the documention for [allowInterop]. This method should only be used with | 571 /// See the documention for [allowInterop]. This method should only be used with |
553 /// package:js Dart-JavaScript interop. | 572 /// package:js Dart-JavaScript interop. |
554 Function allowInteropCaptureThis(Function f) { | 573 Function allowInteropCaptureThis(Function f) { |
555 var ret = _interopCaptureThisExpando[f]; | 574 var ret = _interopCaptureThisExpando[f]; |
556 if (ret == null) { | 575 if (ret == null) { |
557 ret = JS('', | 576 ret = JS( |
| 577 '', |
558 'function(/*...arguments*/) {' | 578 'function(/*...arguments*/) {' |
559 ' let args = [this];' | 579 ' let args = [this];' |
560 ' for (let arg of arguments) {' | 580 ' for (let arg of arguments) {' |
561 ' args.push(arg);' | 581 ' args.push(arg);' |
562 ' }' | 582 ' }' |
563 ' return #(...args);' | 583 ' return #(...args);' |
564 '}', | 584 '}', |
565 f); | 585 f); |
566 _interopCaptureThisExpando[f] = ret; | 586 _interopCaptureThisExpando[f] = ret; |
567 } | 587 } |
568 return ret; | 588 return ret; |
569 } | 589 } |
OLD | NEW |