Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Commit e561ab6

Browse files
committed
[js-interop] Fix function binding and avoid noSuchMethod when using map notation
This change does two things: (1) It avoids noSuchMethod and dartbug.com/9283 when map notation is used. I don't like losing the varargs-like behavior of NSM, so I'm not entirely sure we should land this part. (2) This also binds receivers on returned functions to make a['foo']() and a.foo() equivalent. JS has odd semantics around this. In JS: a['foo']() a is the receiver (i.e., the "this") in the function invocation. But, in the following: var tmp = a['foo']; tmp(); it is not. We don't distinguish between the two in js-interop. This change essentially forces the former semantics over the latter. Note: kevmoo's pop-pop-win app in dartbug.com/9283 works with in dart2js minified with these changes along with the source change in comment #15. Review URL: https://chromiumcodereview.appspot.com//12457030
1 parent b17e33a commit e561ab6

File tree

4 files changed

+199
-44
lines changed

4 files changed

+199
-44
lines changed

lib/dart_interop.js

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,36 @@
1212

1313
var globalContext = window;
1414

15+
// Support for binding the receiver (this) in proxied functions.
16+
function bindIfFunction(f, _this) {
17+
if (typeof(f) != "function") {
18+
return f;
19+
} else {
20+
return new BoundFunction(_this, f);
21+
}
22+
}
23+
24+
function unbind(obj) {
25+
if (obj instanceof BoundFunction) {
26+
return obj.object;
27+
} else {
28+
return obj;
29+
}
30+
}
31+
32+
function getBoundThis(obj) {
33+
if (obj instanceof BoundFunction) {
34+
return obj._this;
35+
} else {
36+
return globalContext;
37+
}
38+
}
39+
40+
function BoundFunction(_this, object) {
41+
this._this = _this;
42+
this.object = object;
43+
}
44+
1545
// Table for local objects and functions that are proxied.
1646
function ProxiedObjectTable() {
1747
// Name for debugging.
@@ -127,15 +157,17 @@
127157
this.port.receive(function (message) {
128158
// TODO(vsm): Support a mechanism to register a handler here.
129159
try {
130-
var receiver = table.get(message[0]);
160+
var object = table.get(message[0]);
161+
var receiver = unbind(object);
131162
var member = message[1];
132163
var kind = message[2];
133164
var args = message[3].map(deserialize);
134165
if (kind == 'get') {
135166
// Getter.
136167
var field = member;
137168
if (field in receiver && args.length == 0) {
138-
return [ 'return', serialize(receiver[field]) ];
169+
var result = bindIfFunction(receiver[field], receiver);
170+
return [ 'return', serialize(result) ];
139171
}
140172
} else if (kind == 'set') {
141173
// Setter.
@@ -145,15 +177,17 @@
145177
}
146178
} else if (kind == 'apply') {
147179
// Direct function invocation.
148-
// TODO(vsm): Should we capture _this_ automatically?
149-
return [ 'return', serialize(receiver.apply(null, args)) ];
180+
var _this = getBoundThis(object);
181+
return [ 'return', serialize(receiver.apply(_this, args)) ];
150182
} else if (member == '[]' && args.length == 1) {
151183
// Index getter.
152-
return [ 'return', serialize(receiver[args[0]]) ];
184+
var result = bindIfFunction(receiver[args[0]], receiver);
185+
return [ 'return', serialize(result) ];
153186
} else if (member == '[]=' && args.length == 2) {
154187
// Index setter.
155188
return [ 'return', serialize(receiver[args[0]] = args[1]) ];
156189
} else {
190+
// Member function invocation.
157191
var f = receiver[member];
158192
if (f) {
159193
var result = f.apply(receiver, args);
@@ -276,6 +310,12 @@
276310
} else if (message instanceof Element &&
277311
(message.ownerDocument == null || message.ownerDocument == document)) {
278312
return [ 'domref', serializeElement(message) ];
313+
} else if (message instanceof BoundFunction &&
314+
typeof(message.object) == 'function') {
315+
// Local function proxy.
316+
return [ 'funcref',
317+
proxiedObjectTable.add(message),
318+
proxiedObjectTable.sendPort ];
279319
} else if (typeof(message) == 'function') {
280320
if ('_dart_id' in message) {
281321
// Remote function proxy.
@@ -366,7 +406,7 @@
366406
// serialized constructor and arguments.
367407
function construct(args) {
368408
args = args.map(deserialize);
369-
var constructor = args[0];
409+
var constructor = unbind(args[0]);
370410
args = Array.prototype.slice.call(args, 1);
371411

372412
// Until 10 args, the 'new' operator is used. With more arguments we use a
@@ -435,12 +475,16 @@
435475

436476
// Return true if a JavaScript proxy is instance of a given type (instanceof).
437477
function proxyInstanceof(args) {
438-
return deserialize(args[0]) instanceof deserialize(args[1]);
478+
var obj = unbind(deserialize(args[0]));
479+
var type = unbind(deserialize(args[1]));
480+
return obj instanceof type;
439481
}
440482

441483
// Return true if a JavaScript proxy is instance of a given type (instanceof).
442484
function proxyDeleteProperty(args) {
443-
delete deserialize(args[0])[deserialize(args[1])];
485+
var obj = unbind(deserialize(args[0]));
486+
var member = unbind(deserialize(args[1]));
487+
delete obj[member];
444488
}
445489

446490
function proxyConvert(args) {

lib/js.dart

Lines changed: 88 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,36 @@ final _JS_BOOTSTRAP = r"""
8989
9090
var globalContext = window;
9191
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+
92122
// Table for local objects and functions that are proxied.
93123
function ProxiedObjectTable() {
94124
// Name for debugging.
@@ -204,15 +234,17 @@ final _JS_BOOTSTRAP = r"""
204234
this.port.receive(function (message) {
205235
// TODO(vsm): Support a mechanism to register a handler here.
206236
try {
207-
var receiver = table.get(message[0]);
237+
var object = table.get(message[0]);
238+
var receiver = unbind(object);
208239
var member = message[1];
209240
var kind = message[2];
210241
var args = message[3].map(deserialize);
211242
if (kind == 'get') {
212243
// Getter.
213244
var field = member;
214245
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) ];
216248
}
217249
} else if (kind == 'set') {
218250
// Setter.
@@ -222,15 +254,17 @@ final _JS_BOOTSTRAP = r"""
222254
}
223255
} else if (kind == 'apply') {
224256
// Direct function invocation.
225-
// TODO(vsm): Should we capture _this_ automatically?
226-
return [ 'return', serialize(receiver.apply(null, args)) ];
257+
var _this = getBoundThis(object);
258+
return [ 'return', serialize(receiver.apply(_this, args)) ];
227259
} else if (member == '[]' && args.length == 1) {
228260
// Index getter.
229-
return [ 'return', serialize(receiver[args[0]]) ];
261+
var result = bindIfFunction(receiver[args[0]], receiver);
262+
return [ 'return', serialize(result) ];
230263
} else if (member == '[]=' && args.length == 2) {
231264
// Index setter.
232265
return [ 'return', serialize(receiver[args[0]] = args[1]) ];
233266
} else {
267+
// Member function invocation.
234268
var f = receiver[member];
235269
if (f) {
236270
var result = f.apply(receiver, args);
@@ -353,6 +387,12 @@ final _JS_BOOTSTRAP = r"""
353387
} else if (message instanceof Element &&
354388
(message.ownerDocument == null || message.ownerDocument == document)) {
355389
return [ 'domref', serializeElement(message) ];
390+
} else if (message instanceof BoundFunction &&
391+
typeof(message.object) == 'function') {
392+
// Local function proxy.
393+
return [ 'funcref',
394+
proxiedObjectTable.add(message),
395+
proxiedObjectTable.sendPort ];
356396
} else if (typeof(message) == 'function') {
357397
if ('_dart_id' in message) {
358398
// Remote function proxy.
@@ -443,7 +483,7 @@ final _JS_BOOTSTRAP = r"""
443483
// serialized constructor and arguments.
444484
function construct(args) {
445485
args = args.map(deserialize);
446-
var constructor = args[0];
486+
var constructor = unbind(args[0]);
447487
args = Array.prototype.slice.call(args, 1);
448488
449489
// Until 10 args, the 'new' operator is used. With more arguments we use a
@@ -512,12 +552,16 @@ final _JS_BOOTSTRAP = r"""
512552
513553
// Return true if a JavaScript proxy is instance of a given type (instanceof).
514554
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;
516558
}
517559
518560
// Return true if a JavaScript proxy is instance of a given type (instanceof).
519561
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];
521565
}
522566
523567
function proxyConvert(args) {
@@ -825,6 +869,19 @@ class Callback {
825869
}
826870
}
827871

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+
828885
/**
829886
* Proxies to JavaScript objects.
830887
*/
@@ -837,19 +894,14 @@ class Proxy implements Serializable<Proxy> {
837894
* JavaScript [constructor]. The arguments should be either
838895
* primitive values, DOM elements, or Proxies.
839896
*/
840-
factory Proxy(FunctionProxy constructor, [arg1, arg2, arg3, arg4]) {
841-
var arguments;
842-
if (?arg4) {
843-
arguments = [arg1, arg2, arg3, arg4];
844-
} else if (?arg3) {
845-
arguments = [arg1, arg2, arg3];
846-
} else if (?arg2) {
847-
arguments = [arg1, arg2];
848-
} else if (?arg1) {
849-
arguments = [arg1];
850-
} else {
851-
arguments = [];
852-
}
897+
factory Proxy(FunctionProxy constructor,
898+
[arg1 = _undefined,
899+
arg2 = _undefined,
900+
arg3 = _undefined,
901+
arg4 = _undefined,
902+
arg5 = _undefined,
903+
arg6 = _undefined]) {
904+
var arguments = _pruneUndefined(arg1, arg2, arg3, arg4, arg5, arg6);
853905
return new Proxy.withArgList(constructor, arguments);
854906
}
855907

@@ -897,20 +949,16 @@ class Proxy implements Serializable<Proxy> {
897949

898950
Proxy toJs() => this;
899951

900-
// TODO(vsm): This is not required in Dartium, but
901-
// it is in Dart2JS.
902952
// Resolve whether this is needed.
903953
operator[](arg) => _forward(this, '[]', 'method', [ arg ]);
904954

905-
// TODO(vsm): This is not required in Dartium, but
906-
// it is in Dart2JS.
907955
// Resolve whether this is needed.
908956
operator[]=(key, value) => _forward(this, '[]=', 'method', [ key, value ]);
909957

910958
// Test if this is equivalent to another Proxy. This essentially
911959
// maps to JavaScript's == operator.
912960
// TODO(vsm): Can we avoid forwarding to JS?
913-
operator==(Proxy other) => identical(this, other)
961+
operator==(other) => identical(this, other)
914962
? true
915963
: (other is Proxy &&
916964
_jsPortEquals.callSync([_serialize(this), _serialize(other)]));
@@ -950,6 +998,10 @@ class Proxy implements Serializable<Proxy> {
950998
} else if (member.startsWith('set:')) {
951999
kind = 'set';
9521000
member = member.substring(4);
1001+
} else if (member == 'call') {
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';
9531005
} else {
9541006
kind = 'method';
9551007
}
@@ -975,16 +1027,16 @@ class Proxy implements Serializable<Proxy> {
9751027
class FunctionProxy extends Proxy /*implements Function*/ {
9761028
FunctionProxy._internal(port, id) : super._internal(port, id);
9771029

978-
noSuchMethod(InvocationMirror invocation) {
979-
if (invocation.isMethod && invocation.memberName == 'call') {
980-
var message = [_id, '', 'apply',
981-
invocation.positionalArguments.map(_serialize).toList()];
982-
var result = _port.callSync(message);
983-
if (result[0] == 'throws') throw result[1];
984-
return _deserialize(result[1]);
985-
} else {
986-
return super.noSuchMethod(invocation);
987-
}
1030+
// TODO(vsm): This allows calls with a limited number of arguments
1031+
// in the context of dartbug.com/9283. Eliminate pending the resolution
1032+
// of this bug. Note, if this Proxy is called with more arguments then
1033+
// allowed below, it will trigger the 'call' path in Proxy.noSuchMethod
1034+
// - and still work correctly in unminified mode.
1035+
call([arg1 = _undefined, arg2 = _undefined,
1036+
arg3 = _undefined, arg4 = _undefined,
1037+
arg5 = _undefined, arg6 = _undefined]) {
1038+
var arguments = _pruneUndefined(arg1, arg2, arg3, arg4, arg5, arg6);
1039+
return Proxy._forward(this, '', 'apply', arguments);
9881040
}
9891041
}
9901042

0 commit comments

Comments
 (0)