Index: lib/js.dart |
diff --git a/lib/js.dart b/lib/js.dart |
index 215b849f9d2fd660f9e21b76f355bbe7af10faf9..afe9e672f958c76a6e1094eaf66ed32661e9e02b 100644 |
--- a/lib/js.dart |
+++ b/lib/js.dart |
@@ -89,6 +89,36 @@ final _JS_BOOTSTRAP = r""" |
var globalContext = window; |
+ // Support for binding the receiver (this) in proxied functions. |
+ function bindIfFunction(f, _this) { |
+ if (typeof(f) != "function") { |
+ return f; |
+ } else { |
+ return new BoundFunction(_this, f); |
+ } |
+ } |
+ |
+ function unbind(obj) { |
+ if (obj instanceof BoundFunction) { |
+ return obj.object; |
+ } else { |
+ return obj; |
+ } |
+ } |
+ |
+ function getBoundThis(obj) { |
+ if (obj instanceof BoundFunction) { |
+ return obj._this; |
+ } else { |
+ return globalContext; |
+ } |
+ } |
+ |
+ function BoundFunction(_this, object) { |
+ this._this = _this; |
+ this.object = object; |
+ } |
+ |
// Table for local objects and functions that are proxied. |
function ProxiedObjectTable() { |
// Name for debugging. |
@@ -204,7 +234,8 @@ final _JS_BOOTSTRAP = r""" |
this.port.receive(function (message) { |
// TODO(vsm): Support a mechanism to register a handler here. |
try { |
- var receiver = table.get(message[0]); |
+ var object = table.get(message[0]); |
+ var receiver = unbind(object); |
var member = message[1]; |
var kind = message[2]; |
var args = message[3].map(deserialize); |
@@ -212,7 +243,8 @@ final _JS_BOOTSTRAP = r""" |
// Getter. |
var field = member; |
if (field in receiver && args.length == 0) { |
- return [ 'return', serialize(receiver[field]) ]; |
+ var result = bindIfFunction(receiver[field], receiver); |
+ return [ 'return', serialize(result) ]; |
} |
} else if (kind == 'set') { |
// Setter. |
@@ -222,15 +254,17 @@ final _JS_BOOTSTRAP = r""" |
} |
} else if (kind == 'apply') { |
// Direct function invocation. |
- // TODO(vsm): Should we capture _this_ automatically? |
- return [ 'return', serialize(receiver.apply(null, args)) ]; |
+ var _this = getBoundThis(object); |
+ return [ 'return', serialize(receiver.apply(_this, args)) ]; |
} else if (member == '[]' && args.length == 1) { |
// Index getter. |
- return [ 'return', serialize(receiver[args[0]]) ]; |
+ var result = bindIfFunction(receiver[args[0]], receiver); |
+ return [ 'return', serialize(result) ]; |
} else if (member == '[]=' && args.length == 2) { |
// Index setter. |
return [ 'return', serialize(receiver[args[0]] = args[1]) ]; |
} else { |
+ // Member function invocation. |
var f = receiver[member]; |
if (f) { |
var result = f.apply(receiver, args); |
@@ -353,6 +387,12 @@ final _JS_BOOTSTRAP = r""" |
} else if (message instanceof Element && |
(message.ownerDocument == null || message.ownerDocument == document)) { |
return [ 'domref', serializeElement(message) ]; |
+ } else if (message instanceof BoundFunction && |
+ 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
|
+ // Local function proxy. |
+ return [ 'funcref', |
+ proxiedObjectTable.add(message), |
+ proxiedObjectTable.sendPort ]; |
} else if (typeof(message) == 'function') { |
if ('_dart_id' in message) { |
// Remote function proxy. |
@@ -443,7 +483,7 @@ final _JS_BOOTSTRAP = r""" |
// serialized constructor and arguments. |
function construct(args) { |
args = args.map(deserialize); |
- var constructor = args[0]; |
+ var constructor = unbind(args[0]); |
args = Array.prototype.slice.call(args, 1); |
// Until 10 args, the 'new' operator is used. With more arguments we use a |
@@ -512,12 +552,16 @@ final _JS_BOOTSTRAP = r""" |
// Return true if a JavaScript proxy is instance of a given type (instanceof). |
function proxyInstanceof(args) { |
- return deserialize(args[0]) instanceof deserialize(args[1]); |
+ var obj = unbind(deserialize(args[0])); |
+ var type = unbind(deserialize(args[1])); |
+ return obj instanceof type; |
} |
// Return true if a JavaScript proxy is instance of a given type (instanceof). |
function proxyDeleteProperty(args) { |
- delete deserialize(args[0])[deserialize(args[1])]; |
+ var obj = unbind(deserialize(args[0])); |
+ var member = unbind(deserialize(args[1])); |
+ delete obj[member]; |
} |
function proxyConvert(args) { |
@@ -825,6 +869,19 @@ class Callback { |
} |
} |
+// Detect unspecified arguments. |
+class _Undefined { |
+ const _Undefined(); |
+} |
+const _undefined = const _Undefined(); |
+List _pruneUndefined(arg1, arg2, arg3, arg4, arg5, arg6) { |
+ // This assumes no argument |
+ final args = [arg1, arg2, arg3, arg4, arg5, arg6]; |
+ final index = args.indexOf(_undefined); |
+ if (index < 0) return args; |
+ return args.sublist(0, index); |
+} |
+ |
/** |
* Proxies to JavaScript objects. |
*/ |
@@ -837,19 +894,14 @@ class Proxy implements Serializable<Proxy> { |
* JavaScript [constructor]. The arguments should be either |
* primitive values, DOM elements, or Proxies. |
*/ |
- factory Proxy(FunctionProxy constructor, [arg1, arg2, arg3, arg4]) { |
- var arguments; |
- if (?arg4) { |
- arguments = [arg1, arg2, arg3, arg4]; |
- } else if (?arg3) { |
- arguments = [arg1, arg2, arg3]; |
- } else if (?arg2) { |
- arguments = [arg1, arg2]; |
- } else if (?arg1) { |
- arguments = [arg1]; |
- } else { |
- arguments = []; |
- } |
+ factory Proxy(FunctionProxy constructor, |
+ [arg1 = _undefined, |
+ arg2 = _undefined, |
+ arg3 = _undefined, |
+ arg4 = _undefined, |
+ arg5 = _undefined, |
+ arg6 = _undefined]) { |
+ var arguments = _pruneUndefined(arg1, arg2, arg3, arg4, arg5, arg6); |
return new Proxy.withArgList(constructor, arguments); |
} |
@@ -897,20 +949,16 @@ class Proxy implements Serializable<Proxy> { |
Proxy toJs() => this; |
- // TODO(vsm): This is not required in Dartium, but |
- // it is in Dart2JS. |
// Resolve whether this is needed. |
operator[](arg) => _forward(this, '[]', 'method', [ arg ]); |
- // TODO(vsm): This is not required in Dartium, but |
- // it is in Dart2JS. |
// Resolve whether this is needed. |
operator[]=(key, value) => _forward(this, '[]=', 'method', [ key, value ]); |
// Test if this is equivalent to another Proxy. This essentially |
// maps to JavaScript's == operator. |
// TODO(vsm): Can we avoid forwarding to JS? |
- operator==(Proxy other) => identical(this, other) |
+ operator==(other) => identical(this, other) |
? true |
: (other is Proxy && |
_jsPortEquals.callSync([_serialize(this), _serialize(other)])); |
@@ -950,6 +998,10 @@ class Proxy implements Serializable<Proxy> { |
} else if (member.startsWith('set:')) { |
kind = 'set'; |
member = member.substring(4); |
+ } 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
|
+ // A 'call' (probably) means that this proxy was invoked directly |
+ // as if it was a function. Map this to JS function application. |
+ kind = 'apply'; |
} else { |
kind = 'method'; |
} |
@@ -975,16 +1027,16 @@ class Proxy implements Serializable<Proxy> { |
class FunctionProxy extends Proxy /*implements Function*/ { |
FunctionProxy._internal(port, id) : super._internal(port, id); |
- noSuchMethod(InvocationMirror invocation) { |
- if (invocation.isMethod && invocation.memberName == 'call') { |
- var message = [_id, '', 'apply', |
- invocation.positionalArguments.map(_serialize).toList()]; |
- var result = _port.callSync(message); |
- if (result[0] == 'throws') throw result[1]; |
- return _deserialize(result[1]); |
- } else { |
- return super.noSuchMethod(invocation); |
- } |
+ // TODO(vsm): This allows calls with a limited number of arguments |
+ // in the context of dartbug.com/9283. Eliminate pending the resolution |
+ // of this bug. Note, if this Proxy is called with more arguments then |
+ // allowed below, it will trigger the 'call' path in Proxy.noSuchMethod |
+ // - and still work correctly in unminified mode. |
+ call([arg1 = _undefined, arg2 = _undefined, |
+ arg3 = _undefined, arg4 = _undefined, |
+ arg5 = _undefined, arg6 = _undefined]) { |
+ var arguments = _pruneUndefined(arg1, arg2, arg3, arg4, arg5, arg6); |
+ return Proxy._forward(this, '', 'apply', arguments); |
} |
} |