Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(717)

Side by Side Diff: lib/js.dart

Issue 12457030: [js-interop] Fix function binding and avoid noSuchMethod when using map notation (Closed) Base URL: https://github.com/dart-lang/js-interop.git@master
Patch Set: Fix for constructors plus cleanup Created 7 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « lib/dart_interop.js ('k') | test/js/browser_tests.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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
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
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
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 }
OLDNEW
« no previous file with comments | « lib/dart_interop.js ('k') | test/js/browser_tests.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698