OLD | NEW |
---|---|
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, 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 * The js.dart library provides simple JavaScript invocation from Dart that | 6 * The js.dart library provides simple JavaScript invocation from Dart that |
7 * works on both Dartium and on other modern browsers via Dart2JS. | 7 * works on both Dartium and on other modern browsers via Dart2JS. |
8 * | 8 * |
9 * It provides a model based on scoped [Proxy] objects. Proxies give Dart | 9 * It provides a model based on scoped [Proxy] objects. Proxies give Dart |
10 * code access to JavaScript objects, fields, and functions as well as the | 10 * code access to JavaScript objects, fields, and functions as well as the |
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
82 // one. | 82 // one. |
83 | 83 |
84 // NOTE: Please re-run tools/create_bootstrap.dart on any modification of | 84 // NOTE: Please re-run tools/create_bootstrap.dart on any modification of |
85 // this bootstrap string. | 85 // this bootstrap string. |
86 final _JS_BOOTSTRAP = r""" | 86 final _JS_BOOTSTRAP = r""" |
87 (function() { | 87 (function() { |
88 // Proxy support for js.dart. | 88 // Proxy support for js.dart. |
89 | 89 |
90 var globalContext = window; | 90 var globalContext = window; |
91 | 91 |
92 // Support for binding the receiver (this) in proxied functions. | |
93 function bindIfFunction(f, _this) { | |
94 if (typeof(f) != "function") { | |
95 return f; | |
96 } else { | |
97 return new BoundFunction(_this, f); | |
98 } | |
99 } | |
100 | |
101 function unbind(obj) { | |
102 if (obj instanceof BoundFunction) { | |
103 return obj.object; | |
104 } else { | |
105 return obj; | |
106 } | |
107 } | |
108 | |
109 function getBoundThis(obj) { | |
110 if (obj instanceof BoundFunction) { | |
111 return obj._this; | |
112 } else { | |
113 return globalContext; | |
114 } | |
115 } | |
116 | |
117 function BoundFunction(_this, object) { | |
118 this._this = _this; | |
119 this.object = object; | |
120 } | |
121 | |
92 // Table for local objects and functions that are proxied. | 122 // Table for local objects and functions that are proxied. |
93 function ProxiedObjectTable() { | 123 function ProxiedObjectTable() { |
94 // Name for debugging. | 124 // Name for debugging. |
95 this.name = 'js-ref'; | 125 this.name = 'js-ref'; |
96 | 126 |
97 // Table from IDs to JS objects. | 127 // Table from IDs to JS objects. |
98 this.map = {}; | 128 this.map = {}; |
99 | 129 |
100 // Generator for new IDs. | 130 // Generator for new IDs. |
101 this._nextId = 0; | 131 this._nextId = 0; |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
197 } | 227 } |
198 | 228 |
199 ProxiedObjectTable.prototype._initialize = function () { | 229 ProxiedObjectTable.prototype._initialize = function () { |
200 // Configure this table's port to forward methods, getters, and setters | 230 // Configure this table's port to forward methods, getters, and setters |
201 // from the remote proxy to the local object. | 231 // from the remote proxy to the local object. |
202 var table = this; | 232 var table = this; |
203 | 233 |
204 this.port.receive(function (message) { | 234 this.port.receive(function (message) { |
205 // TODO(vsm): Support a mechanism to register a handler here. | 235 // TODO(vsm): Support a mechanism to register a handler here. |
206 try { | 236 try { |
207 var receiver = table.get(message[0]); | 237 var object = table.get(message[0]); |
238 var receiver = unbind(object); | |
208 var member = message[1]; | 239 var member = message[1]; |
209 var kind = message[2]; | 240 var kind = message[2]; |
210 var args = message[3].map(deserialize); | 241 var args = message[3].map(deserialize); |
211 if (kind == 'get') { | 242 if (kind == 'get') { |
212 // Getter. | 243 // Getter. |
213 var field = member; | 244 var field = member; |
214 if (field in receiver && args.length == 0) { | 245 if (field in receiver && args.length == 0) { |
215 return [ 'return', serialize(receiver[field]) ]; | 246 var result = bindIfFunction(receiver[field], receiver); |
247 return [ 'return', serialize(result) ]; | |
216 } | 248 } |
217 } else if (kind == 'set') { | 249 } else if (kind == 'set') { |
218 // Setter. | 250 // Setter. |
219 var field = member; | 251 var field = member; |
220 if (args.length == 1) { | 252 if (args.length == 1) { |
221 return [ 'return', serialize(receiver[field] = args[0]) ]; | 253 return [ 'return', serialize(receiver[field] = args[0]) ]; |
222 } | 254 } |
223 } else if (kind == 'apply') { | 255 } else if (kind == 'apply') { |
224 // Direct function invocation. | 256 // Direct function invocation. |
225 // TODO(vsm): Should we capture _this_ automatically? | 257 var _this = getBoundThis(object); |
226 return [ 'return', serialize(receiver.apply(null, args)) ]; | 258 return [ 'return', serialize(receiver.apply(_this, args)) ]; |
227 } else if (member == '[]' && args.length == 1) { | 259 } else if (member == '[]' && args.length == 1) { |
228 // Index getter. | 260 // Index getter. |
229 return [ 'return', serialize(receiver[args[0]]) ]; | 261 var result = bindIfFunction(receiver[args[0]], receiver); |
262 return [ 'return', serialize(result) ]; | |
230 } else if (member == '[]=' && args.length == 2) { | 263 } else if (member == '[]=' && args.length == 2) { |
231 // Index setter. | 264 // Index setter. |
232 return [ 'return', serialize(receiver[args[0]] = args[1]) ]; | 265 return [ 'return', serialize(receiver[args[0]] = args[1]) ]; |
233 } else { | 266 } else { |
267 // Member function invocation. | |
234 var f = receiver[member]; | 268 var f = receiver[member]; |
235 if (f) { | 269 if (f) { |
236 var result = f.apply(receiver, args); | 270 var result = f.apply(receiver, args); |
237 return [ 'return', serialize(result) ]; | 271 return [ 'return', serialize(result) ]; |
238 } | 272 } |
239 } | 273 } |
240 return [ 'none' ]; | 274 return [ 'none' ]; |
241 } catch (e) { | 275 } catch (e) { |
242 return [ 'throws', e.toString() ]; | 276 return [ 'throws', e.toString() ]; |
243 } | 277 } |
(...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
346 typeof(message) == 'number' || | 380 typeof(message) == 'number' || |
347 typeof(message) == 'boolean') { | 381 typeof(message) == 'boolean') { |
348 // Primitives are passed directly through. | 382 // Primitives are passed directly through. |
349 return message; | 383 return message; |
350 } else if (message instanceof SendPortSync) { | 384 } else if (message instanceof SendPortSync) { |
351 // Non-proxied objects are serialized. | 385 // Non-proxied objects are serialized. |
352 return message; | 386 return message; |
353 } else if (message instanceof Element && | 387 } else if (message instanceof Element && |
354 (message.ownerDocument == null || message.ownerDocument == document)) { | 388 (message.ownerDocument == null || message.ownerDocument == document)) { |
355 return [ 'domref', serializeElement(message) ]; | 389 return [ 'domref', serializeElement(message) ]; |
390 } else if (message instanceof BoundFunction && | |
391 typeof(message.object) == 'function') { | |
alexandre.ardhuin
2013/04/07 21:20:20
Could typeof(message.object) be something else tha
vsm
2013/04/07 21:37:36
It shouldn't. I'll change this to assert that.
O
| |
392 // Local function proxy. | |
393 return [ 'funcref', | |
394 proxiedObjectTable.add(message), | |
395 proxiedObjectTable.sendPort ]; | |
356 } else if (typeof(message) == 'function') { | 396 } else if (typeof(message) == 'function') { |
357 if ('_dart_id' in message) { | 397 if ('_dart_id' in message) { |
358 // Remote function proxy. | 398 // Remote function proxy. |
359 var remoteId = message._dart_id; | 399 var remoteId = message._dart_id; |
360 var remoteSendPort = message._dart_port; | 400 var remoteSendPort = message._dart_port; |
361 return [ 'funcref', remoteId, remoteSendPort ]; | 401 return [ 'funcref', remoteId, remoteSendPort ]; |
362 } else { | 402 } else { |
363 // Local function proxy. | 403 // Local function proxy. |
364 return [ 'funcref', | 404 return [ 'funcref', |
365 proxiedObjectTable.add(message), | 405 proxiedObjectTable.add(message), |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
436 } else { | 476 } else { |
437 // Remote object. | 477 // Remote object. |
438 return new DartProxy(id, port); | 478 return new DartProxy(id, port); |
439 } | 479 } |
440 } | 480 } |
441 | 481 |
442 // Remote handler to construct a new JavaScript object given its | 482 // Remote handler to construct a new JavaScript object given its |
443 // serialized constructor and arguments. | 483 // serialized constructor and arguments. |
444 function construct(args) { | 484 function construct(args) { |
445 args = args.map(deserialize); | 485 args = args.map(deserialize); |
446 var constructor = args[0]; | 486 var constructor = unbind(args[0]); |
447 args = Array.prototype.slice.call(args, 1); | 487 args = Array.prototype.slice.call(args, 1); |
448 | 488 |
449 // Until 10 args, the 'new' operator is used. With more arguments we use a | 489 // Until 10 args, the 'new' operator is used. With more arguments we use a |
450 // generic way that may not work, particulary when the constructor does not | 490 // generic way that may not work, particulary when the constructor does not |
451 // have an "apply" method. | 491 // have an "apply" method. |
452 var ret = null; | 492 var ret = null; |
453 if (args.length === 0) { | 493 if (args.length === 0) { |
454 ret = new constructor(); | 494 ret = new constructor(); |
455 } else if (args.length === 1) { | 495 } else if (args.length === 1) { |
456 ret = new constructor(args[0]); | 496 ret = new constructor(args[0]); |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
505 ' (out of ' + total + ' ever allocated).'; | 545 ' (out of ' + total + ' ever allocated).'; |
506 } | 546 } |
507 | 547 |
508 // Return true if two JavaScript proxies are equal (==). | 548 // Return true if two JavaScript proxies are equal (==). |
509 function proxyEquals(args) { | 549 function proxyEquals(args) { |
510 return deserialize(args[0]) == deserialize(args[1]); | 550 return deserialize(args[0]) == deserialize(args[1]); |
511 } | 551 } |
512 | 552 |
513 // Return true if a JavaScript proxy is instance of a given type (instanceof). | 553 // Return true if a JavaScript proxy is instance of a given type (instanceof). |
514 function proxyInstanceof(args) { | 554 function proxyInstanceof(args) { |
515 return deserialize(args[0]) instanceof deserialize(args[1]); | 555 var obj = unbind(deserialize(args[0])); |
556 var type = unbind(deserialize(args[1])); | |
557 return obj instanceof type; | |
516 } | 558 } |
517 | 559 |
518 // Return true if a JavaScript proxy is instance of a given type (instanceof). | 560 // Return true if a JavaScript proxy is instance of a given type (instanceof). |
519 function proxyDeleteProperty(args) { | 561 function proxyDeleteProperty(args) { |
520 delete deserialize(args[0])[deserialize(args[1])]; | 562 var obj = unbind(deserialize(args[0])); |
563 var member = unbind(deserialize(args[1])); | |
564 delete obj[member]; | |
521 } | 565 } |
522 | 566 |
523 function proxyConvert(args) { | 567 function proxyConvert(args) { |
524 return serialize(deserializeDataTree(args)); | 568 return serialize(deserializeDataTree(args)); |
525 } | 569 } |
526 | 570 |
527 function deserializeDataTree(data) { | 571 function deserializeDataTree(data) { |
528 var type = data[0]; | 572 var type = data[0]; |
529 var value = data[1]; | 573 var value = data[1]; |
530 if (type === 'map') { | 574 if (type === 'map') { |
(...skipping 287 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
818 /** | 862 /** |
819 * Creates a multi-fire [Callback] that invokes [f]. The callback must be | 863 * Creates a multi-fire [Callback] that invokes [f]. The callback must be |
820 * explicitly disposed to avoid memory leaks. | 864 * explicitly disposed to avoid memory leaks. |
821 */ | 865 */ |
822 Callback.many(Function f) { | 866 Callback.many(Function f) { |
823 _callback = (args) => Function.apply(f, args); | 867 _callback = (args) => Function.apply(f, args); |
824 _initialize(true); | 868 _initialize(true); |
825 } | 869 } |
826 } | 870 } |
827 | 871 |
872 // Detect unspecified arguments. | |
873 class _Undefined { | |
874 const _Undefined(); | |
875 } | |
876 const _undefined = const _Undefined(); | |
877 List _pruneUndefined(arg1, arg2, arg3, arg4, arg5, arg6) { | |
878 // This assumes no argument | |
879 final args = [arg1, arg2, arg3, arg4, arg5, arg6]; | |
880 final index = args.indexOf(_undefined); | |
881 if (index < 0) return args; | |
882 return args.sublist(0, index); | |
883 } | |
884 | |
828 /** | 885 /** |
829 * Proxies to JavaScript objects. | 886 * Proxies to JavaScript objects. |
830 */ | 887 */ |
831 class Proxy implements Serializable<Proxy> { | 888 class Proxy implements Serializable<Proxy> { |
832 SendPortSync _port; | 889 SendPortSync _port; |
833 final _id; | 890 final _id; |
834 | 891 |
835 /** | 892 /** |
836 * Constructs a [Proxy] to a new JavaScript object by invoking a (proxy to a) | 893 * Constructs a [Proxy] to a new JavaScript object by invoking a (proxy to a) |
837 * JavaScript [constructor]. The arguments should be either | 894 * JavaScript [constructor]. The arguments should be either |
838 * primitive values, DOM elements, or Proxies. | 895 * primitive values, DOM elements, or Proxies. |
839 */ | 896 */ |
840 factory Proxy(FunctionProxy constructor, [arg1, arg2, arg3, arg4]) { | 897 factory Proxy(FunctionProxy constructor, |
841 var arguments; | 898 [arg1 = _undefined, |
842 if (?arg4) { | 899 arg2 = _undefined, |
843 arguments = [arg1, arg2, arg3, arg4]; | 900 arg3 = _undefined, |
844 } else if (?arg3) { | 901 arg4 = _undefined, |
845 arguments = [arg1, arg2, arg3]; | 902 arg5 = _undefined, |
846 } else if (?arg2) { | 903 arg6 = _undefined]) { |
847 arguments = [arg1, arg2]; | 904 var arguments = _pruneUndefined(arg1, arg2, arg3, arg4, arg5, arg6); |
848 } else if (?arg1) { | |
849 arguments = [arg1]; | |
850 } else { | |
851 arguments = []; | |
852 } | |
853 return new Proxy.withArgList(constructor, arguments); | 905 return new Proxy.withArgList(constructor, arguments); |
854 } | 906 } |
855 | 907 |
856 /** | 908 /** |
857 * Constructs a [Proxy] to a new JavaScript object by invoking a (proxy to a) | 909 * Constructs a [Proxy] to a new JavaScript object by invoking a (proxy to a) |
858 * JavaScript [constructor]. The [arguments] list should contain either | 910 * JavaScript [constructor]. The [arguments] list should contain either |
859 * primitive values, DOM elements, or Proxies. | 911 * primitive values, DOM elements, or Proxies. |
860 */ | 912 */ |
861 factory Proxy.withArgList(FunctionProxy constructor, List arguments) { | 913 factory Proxy.withArgList(FunctionProxy constructor, List arguments) { |
862 if (_depth == 0) throw 'Cannot create Proxy out of scope.'; | 914 if (_depth == 0) throw 'Cannot create Proxy out of scope.'; |
(...skipping 27 matching lines...) Expand all Loading... | |
890 return ['list', data.map((e) => _serializeDataTree(e)).toList()]; | 942 return ['list', data.map((e) => _serializeDataTree(e)).toList()]; |
891 } else { | 943 } else { |
892 return ['simple', _serialize(data)]; | 944 return ['simple', _serialize(data)]; |
893 } | 945 } |
894 } | 946 } |
895 | 947 |
896 Proxy._internal(this._port, this._id); | 948 Proxy._internal(this._port, this._id); |
897 | 949 |
898 Proxy toJs() => this; | 950 Proxy toJs() => this; |
899 | 951 |
900 // TODO(vsm): This is not required in Dartium, but | |
901 // it is in Dart2JS. | |
902 // Resolve whether this is needed. | 952 // Resolve whether this is needed. |
903 operator[](arg) => _forward(this, '[]', 'method', [ arg ]); | 953 operator[](arg) => _forward(this, '[]', 'method', [ arg ]); |
904 | 954 |
905 // TODO(vsm): This is not required in Dartium, but | |
906 // it is in Dart2JS. | |
907 // Resolve whether this is needed. | 955 // Resolve whether this is needed. |
908 operator[]=(key, value) => _forward(this, '[]=', 'method', [ key, value ]); | 956 operator[]=(key, value) => _forward(this, '[]=', 'method', [ key, value ]); |
909 | 957 |
910 // Test if this is equivalent to another Proxy. This essentially | 958 // Test if this is equivalent to another Proxy. This essentially |
911 // maps to JavaScript's == operator. | 959 // maps to JavaScript's == operator. |
912 // TODO(vsm): Can we avoid forwarding to JS? | 960 // TODO(vsm): Can we avoid forwarding to JS? |
913 operator==(Proxy other) => identical(this, other) | 961 operator==(other) => identical(this, other) |
914 ? true | 962 ? true |
915 : (other is Proxy && | 963 : (other is Proxy && |
916 _jsPortEquals.callSync([_serialize(this), _serialize(other)])); | 964 _jsPortEquals.callSync([_serialize(this), _serialize(other)])); |
917 | 965 |
918 // Forward member accesses to the backing JavaScript object. | 966 // Forward member accesses to the backing JavaScript object. |
919 noSuchMethod(InvocationMirror invocation) { | 967 noSuchMethod(InvocationMirror invocation) { |
920 String member = invocation.memberName; | 968 String member = invocation.memberName; |
921 // If trying to access a JavaScript field/variable that starts with | 969 // If trying to access a JavaScript field/variable that starts with |
922 // _ (underscore), Dart treats it a library private and member name | 970 // _ (underscore), Dart treats it a library private and member name |
923 // it suffixed with '@internalLibraryIdentifier' which we have to | 971 // it suffixed with '@internalLibraryIdentifier' which we have to |
(...skipping 19 matching lines...) Expand all Loading... | |
943 } | 991 } |
944 if (member.startsWith('set:')) { | 992 if (member.startsWith('set:')) { |
945 member = member.substring(4); | 993 member = member.substring(4); |
946 } | 994 } |
947 } else if (member.startsWith('get:')) { | 995 } else if (member.startsWith('get:')) { |
948 kind = 'get'; | 996 kind = 'get'; |
949 member = member.substring(4); | 997 member = member.substring(4); |
950 } else if (member.startsWith('set:')) { | 998 } else if (member.startsWith('set:')) { |
951 kind = 'set'; | 999 kind = 'set'; |
952 member = member.substring(4); | 1000 member = member.substring(4); |
1001 } else if (member == 'call') { | |
alexandre.ardhuin
2013/04/07 21:20:20
If what I have observed on minification of call is
vsm
2013/04/07 21:37:36
I'm uncomfortable making an assumptions on minifie
| |
1002 // A 'call' (probably) means that this proxy was invoked directly | |
1003 // as if it was a function. Map this to JS function application. | |
1004 kind = 'apply'; | |
953 } else { | 1005 } else { |
954 kind = 'method'; | 1006 kind = 'method'; |
955 } | 1007 } |
956 return _forward(this, member, kind, args); | 1008 return _forward(this, member, kind, args); |
957 } | 1009 } |
958 | 1010 |
959 // Forward member accesses to the backing JavaScript object. | 1011 // Forward member accesses to the backing JavaScript object. |
960 static _forward(Proxy receiver, String member, String kind, List args) { | 1012 static _forward(Proxy receiver, String member, String kind, List args) { |
961 if (_depth == 0) throw 'Cannot access a JavaScript proxy out of scope.'; | 1013 if (_depth == 0) throw 'Cannot access a JavaScript proxy out of scope.'; |
962 var result = receiver._port.callSync([receiver._id, member, kind, | 1014 var result = receiver._port.callSync([receiver._id, member, kind, |
963 args.map(_serialize).toList()]); | 1015 args.map(_serialize).toList()]); |
964 switch (result[0]) { | 1016 switch (result[0]) { |
965 case 'return': return _deserialize(result[1]); | 1017 case 'return': return _deserialize(result[1]); |
966 case 'throws': throw _deserialize(result[1]); | 1018 case 'throws': throw _deserialize(result[1]); |
967 case 'none': throw new NoSuchMethodError(receiver, member, args, {}); | 1019 case 'none': throw new NoSuchMethodError(receiver, member, args, {}); |
968 default: throw 'Invalid return value'; | 1020 default: throw 'Invalid return value'; |
969 } | 1021 } |
970 } | 1022 } |
971 } | 1023 } |
972 | 1024 |
973 // TODO(aa) make FunctionProxy implements Function once it is allowed | 1025 // TODO(aa) make FunctionProxy implements Function once it is allowed |
974 /// A [Proxy] subtype to JavaScript functions. | 1026 /// A [Proxy] subtype to JavaScript functions. |
975 class FunctionProxy extends Proxy /*implements Function*/ { | 1027 class FunctionProxy extends Proxy /*implements Function*/ { |
976 FunctionProxy._internal(port, id) : super._internal(port, id); | 1028 FunctionProxy._internal(port, id) : super._internal(port, id); |
977 | 1029 |
978 noSuchMethod(InvocationMirror invocation) { | 1030 // TODO(vsm): This allows calls with a limited number of arguments |
979 if (invocation.isMethod && invocation.memberName == 'call') { | 1031 // in the context of dartbug.com/9283. Eliminate pending the resolution |
980 var message = [_id, '', 'apply', | 1032 // of this bug. Note, if this Proxy is called with more arguments then |
981 invocation.positionalArguments.map(_serialize).toList()]; | 1033 // allowed below, it will trigger the 'call' path in Proxy.noSuchMethod |
982 var result = _port.callSync(message); | 1034 // - and still work correctly in unminified mode. |
983 if (result[0] == 'throws') throw result[1]; | 1035 call([arg1 = _undefined, arg2 = _undefined, |
984 return _deserialize(result[1]); | 1036 arg3 = _undefined, arg4 = _undefined, |
985 } else { | 1037 arg5 = _undefined, arg6 = _undefined]) { |
986 return super.noSuchMethod(invocation); | 1038 var arguments = _pruneUndefined(arg1, arg2, arg3, arg4, arg5, arg6); |
987 } | 1039 return Proxy._forward(this, '', 'apply', arguments); |
988 } | 1040 } |
989 } | 1041 } |
990 | 1042 |
991 /// Marker class used to indicate it is serializable to js. If a class is a | 1043 /// Marker class used to indicate it is serializable to js. If a class is a |
992 /// [Serializable] the "toJs" method will be called and the result will be used | 1044 /// [Serializable] the "toJs" method will be called and the result will be used |
993 /// as value. | 1045 /// as value. |
994 abstract class Serializable<T> { | 1046 abstract class Serializable<T> { |
995 T toJs(); | 1047 T toJs(); |
996 } | 1048 } |
997 | 1049 |
(...skipping 282 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1280 * Prints the number of live handles in Dart and JavaScript. This is for | 1332 * Prints the number of live handles in Dart and JavaScript. This is for |
1281 * debugging / profiling purposes. | 1333 * debugging / profiling purposes. |
1282 */ | 1334 */ |
1283 void proxyDebug([String message = '']) { | 1335 void proxyDebug([String message = '']) { |
1284 print('Proxy status $message:'); | 1336 print('Proxy status $message:'); |
1285 var live = _proxiedObjectTable.count; | 1337 var live = _proxiedObjectTable.count; |
1286 var total = _proxiedObjectTable.total; | 1338 var total = _proxiedObjectTable.total; |
1287 print(' Dart objects Live : $live (out of $total ever allocated).'); | 1339 print(' Dart objects Live : $live (out of $total ever allocated).'); |
1288 print(' ${_jsPortDebug.callSync([])}'); | 1340 print(' ${_jsPortDebug.callSync([])}'); |
1289 } | 1341 } |
OLD | NEW |