Support general serializing of functions between JS and Dart

Anton: This expands on your earlier CL.  Do you mind taking a look?

Review URL: https://chromiumcodereview.appspot.com//10834426

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@11066 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
vsm@google.com 2012-08-21 15:59:54 +00:00
parent ce718e65fb
commit 9259b263fd
6 changed files with 209 additions and 25 deletions

View file

@ -46,6 +46,33 @@ function ReceivePortSync() {
}
(function() {
// Track proxied functions.
// TODO: Fix leaks, particularly in dart2js case.
var functionRefMap = {};
var nextFunctionRefId = 0;
function functionRefDispatch(message) {
var id = message[0];
var args = message[1];
var f = functionRefMap[id];
// TODO: Should we capture this automatically?
return f.apply(null, args);
}
var functionRefPort = null;
function makeFunctionRef(f) {
if (functionRefPort == null) {
var port = new ReceivePortSync();
port.receive(functionRefDispatch);
functionRefPort = port.toSendPort();
}
var ref = 'func-ref-' + (nextFunctionRefId++);
functionRefMap[ref] = f;
return ref;
}
function serialize(message) {
var visited = [];
function checkedSerialization(obj, serializer) {
@ -79,6 +106,9 @@ function ReceivePortSync() {
return [ 'sendport', 'nativejs', message.receivePort.id ];
} else if (message instanceof DartSendPortSync) {
return [ 'sendport', 'dart', message.isolateId, message.portId ];
} else if (message instanceof Function) {
return [ 'funcref', makeFunctionRef(message),
doSerialize(functionRefPort) ];
} else {
return checkedSerialization(message, function(id) {
var keys = Object.getOwnPropertyNames(message);

View file

@ -37121,6 +37121,12 @@ class _JsSerializer extends _Serializer {
x._receivePort._isolateId, x._receivePort._portId ];
}
visitObject(Object x) {
if (x is Function) return visitFunction(x);
// TODO: Handle DOM elements and proxy other objects.
throw "Unserializable object $x";
}
visitFunction(Function func) {
return [ 'funcref',
_makeFunctionRef(func), visitSendPortSync(_sendPort()), null ];
@ -37179,6 +37185,8 @@ _deserialize(var message) {
class _JsDeserializer extends _Deserializer {
static final _UNSPECIFIED = const Object();
deserializeSendPort(List x) {
String tag = x[1];
switch (tag) {
@ -37194,6 +37202,27 @@ class _JsDeserializer extends _Deserializer {
}
}
deserializeObject(List x) {
String tag = x[0];
switch (tag) {
case 'funcref': return deserializeFunction(x);
default: throw 'Illegal object type: $x';
}
}
deserializeFunction(List x) {
var id = x[1];
SendPortSync port = deserializeSendPort(x[2]);
// TODO: Support varargs when there is support in the language.
return ([arg0 = _UNSPECIFIED, arg1 = _UNSPECIFIED,
arg2 = _UNSPECIFIED, arg3 = _UNSPECIFIED]) {
var args = [arg0, arg1, arg2, arg3];
var last = args.indexOf(_UNSPECIFIED);
if (last >= 0) args = args.getRange(0, last);
var message = [id, args];
return port.callSync(message);
};
}
}
// The receiver is JS.
@ -38067,10 +38096,9 @@ class _MessageTraverser {
if (x is Map) return visitMap(x);
if (x is SendPort) return visitSendPort(x);
if (x is SendPortSync) return visitSendPortSync(x);
if (x is Function) return visitFunction(x);
// TODO(floitsch): make this a real exception. (which one)?
throw "Message serialization: Illegal value $x passed";
// Overridable fallback.
return visitObject(x);
}
abstract visitPrimitive(x);
@ -38079,8 +38107,9 @@ class _MessageTraverser {
abstract visitSendPort(SendPort x);
abstract visitSendPortSync(SendPortSync x);
visitFunction(Function func) {
throw "Serialization of functions is not allowed.";
visitObject(Object x) {
// TODO(floitsch): make this a real exception. (which one)?
throw "Message serialization: Illegal value $x passed";
}
static bool isPrimitive(x) {
@ -38188,8 +38217,7 @@ class _Deserializer {
case 'list': return _deserializeList(x);
case 'map': return _deserializeMap(x);
case 'sendport': return deserializeSendPort(x);
// TODO(floitsch): Use real exception (which one?).
default: throw "Unexpected serialized object";
default: return deserializeObject(x);
}
}
@ -38230,4 +38258,8 @@ class _Deserializer {
abstract deserializeSendPort(List x);
deserializeObject(List x) {
// TODO(floitsch): Use real exception (which one?).
throw "Unexpected serialized object";
}
}

View file

@ -40763,6 +40763,12 @@ class _JsSerializer extends _Serializer {
x._receivePort._isolateId, x._receivePort._portId ];
}
visitObject(Object x) {
if (x is Function) return visitFunction(x);
// TODO: Handle DOM elements and proxy other objects.
throw "Unserializable object $x";
}
visitFunction(Function func) {
return [ 'funcref',
_makeFunctionRef(func), visitSendPortSync(_sendPort()), null ];
@ -40821,6 +40827,8 @@ _deserialize(var message) {
class _JsDeserializer extends _Deserializer {
static final _UNSPECIFIED = const Object();
deserializeSendPort(List x) {
String tag = x[1];
switch (tag) {
@ -40836,6 +40844,27 @@ class _JsDeserializer extends _Deserializer {
}
}
deserializeObject(List x) {
String tag = x[0];
switch (tag) {
case 'funcref': return deserializeFunction(x);
default: throw 'Illegal object type: $x';
}
}
deserializeFunction(List x) {
var id = x[1];
SendPortSync port = deserializeSendPort(x[2]);
// TODO: Support varargs when there is support in the language.
return ([arg0 = _UNSPECIFIED, arg1 = _UNSPECIFIED,
arg2 = _UNSPECIFIED, arg3 = _UNSPECIFIED]) {
var args = [arg0, arg1, arg2, arg3];
var last = args.indexOf(_UNSPECIFIED);
if (last >= 0) args = args.getRange(0, last);
var message = [id, args];
return port.callSync(message);
};
}
}
// The receiver is JS.
@ -41276,10 +41305,9 @@ class _MessageTraverser {
if (x is Map) return visitMap(x);
if (x is SendPort) return visitSendPort(x);
if (x is SendPortSync) return visitSendPortSync(x);
if (x is Function) return visitFunction(x);
// TODO(floitsch): make this a real exception. (which one)?
throw "Message serialization: Illegal value $x passed";
// Overridable fallback.
return visitObject(x);
}
abstract visitPrimitive(x);
@ -41288,8 +41316,9 @@ class _MessageTraverser {
abstract visitSendPort(SendPort x);
abstract visitSendPortSync(SendPortSync x);
visitFunction(Function func) {
throw "Serialization of functions is not allowed.";
visitObject(Object x) {
// TODO(floitsch): make this a real exception. (which one)?
throw "Message serialization: Illegal value $x passed";
}
static bool isPrimitive(x) {
@ -41397,8 +41426,7 @@ class _Deserializer {
case 'list': return _deserializeList(x);
case 'map': return _deserializeMap(x);
case 'sendport': return deserializeSendPort(x);
// TODO(floitsch): Use real exception (which one?).
default: throw "Unexpected serialized object";
default: return deserializeObject(x);
}
}
@ -41439,6 +41467,10 @@ class _Deserializer {
abstract deserializeSendPort(List x);
deserializeObject(List x) {
// TODO(floitsch): Use real exception (which one?).
throw "Unexpected serialized object";
}
}
// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a

View file

@ -29,6 +29,12 @@ class _JsSerializer extends _Serializer {
x._receivePort._isolateId, x._receivePort._portId ];
}
visitObject(Object x) {
if (x is Function) return visitFunction(x);
// TODO: Handle DOM elements and proxy other objects.
throw "Unserializable object $x";
}
visitFunction(Function func) {
return [ 'funcref',
_makeFunctionRef(func), visitSendPortSync(_sendPort()), null ];
@ -87,6 +93,8 @@ _deserialize(var message) {
class _JsDeserializer extends _Deserializer {
static final _UNSPECIFIED = const Object();
deserializeSendPort(List x) {
String tag = x[1];
switch (tag) {
@ -102,6 +110,27 @@ class _JsDeserializer extends _Deserializer {
}
}
deserializeObject(List x) {
String tag = x[0];
switch (tag) {
case 'funcref': return deserializeFunction(x);
default: throw 'Illegal object type: $x';
}
}
deserializeFunction(List x) {
var id = x[1];
SendPortSync port = deserializeSendPort(x[2]);
// TODO: Support varargs when there is support in the language.
return ([arg0 = _UNSPECIFIED, arg1 = _UNSPECIFIED,
arg2 = _UNSPECIFIED, arg3 = _UNSPECIFIED]) {
var args = [arg0, arg1, arg2, arg3];
var last = args.indexOf(_UNSPECIFIED);
if (last >= 0) args = args.getRange(0, last);
var message = [id, args];
return port.callSync(message);
};
}
}
// The receiver is JS.

View file

@ -37,10 +37,9 @@ class _MessageTraverser {
if (x is Map) return visitMap(x);
if (x is SendPort) return visitSendPort(x);
if (x is SendPortSync) return visitSendPortSync(x);
if (x is Function) return visitFunction(x);
// TODO(floitsch): make this a real exception. (which one)?
throw "Message serialization: Illegal value $x passed";
// Overridable fallback.
return visitObject(x);
}
abstract visitPrimitive(x);
@ -49,8 +48,9 @@ class _MessageTraverser {
abstract visitSendPort(SendPort x);
abstract visitSendPortSync(SendPortSync x);
visitFunction(Function func) {
throw "Serialization of functions is not allowed.";
visitObject(Object x) {
// TODO(floitsch): make this a real exception. (which one)?
throw "Message serialization: Illegal value $x passed";
}
static bool isPrimitive(x) {
@ -158,8 +158,7 @@ class _Deserializer {
case 'list': return _deserializeList(x);
case 'map': return _deserializeMap(x);
case 'sendport': return deserializeSendPort(x);
// TODO(floitsch): Use real exception (which one?).
default: throw "Unexpected serialized object";
default: return deserializeObject(x);
}
}
@ -200,4 +199,8 @@ class _Deserializer {
abstract deserializeSendPort(List x);
deserializeObject(List x) {
// TODO(floitsch): Use real exception (which one?).
throw "Unexpected serialized object";
}
}

View file

@ -15,25 +15,83 @@ injectSource(code) {
document.body.nodes.add(script);
}
var isolateTest = """
var dartToJsTest = """
var port = new ReceivePortSync();
port.receive(function (f) {
return f('fromJS');
});
window.registerPort('test', port.toSendPort());
window.registerPort('test1', port.toSendPort());
""";
var jsToDartTest = """
var port1 = window.lookupPort('test2a');
var result = port1.callSync(function (x) {
return x*2;
});
var port2 = window.lookupPort('test2b');
port2.callSync(result);
""";
var dartToJsToDartTest = """
var port = new ReceivePortSync();
port.receive(function (f) {
return f;
});
window.registerPort('test3', port.toSendPort());
""";
main() {
useHtmlConfiguration();
test('dart-to-js-function', () {
injectSource(isolateTest);
injectSource(dartToJsTest);
SendPortSync port = window.lookupPort('test');
SendPortSync port = window.lookupPort('test1');
var result = port.callSync((msg) {
Expect.equals('fromJS', msg);
return 'received';
});
Expect.equals('received', result);
});
test('js-to-dart-function', () {
var port1 = new ReceivePortSync();
int invoked1 = 0;
port1.receive((f) {
++invoked1;
var data = f(21);
expect(data, equals(42));
return 'fromDart';
});
window.registerPort('test2a', port1.toSendPort());
var port2 = new ReceivePortSync();
int invoked2 = 0;
port2.receive((x) {
++invoked2;
expect(x, equals('fromDart'));
});
window.registerPort('test2b', port2.toSendPort());
injectSource(jsToDartTest);
expect(1, equals(invoked1));
expect(1, equals(invoked2));
});
test('dart-to-js-to-dart-function', () {
injectSource(dartToJsToDartTest);
validate(x) {
expect(x, equals('fromCaller'));
return 'fromCallee';
}
SendPortSync port = window.lookupPort('test3');
var f = port.callSync(validate);
var result = f('fromCaller');
Expect.equals('fromCallee', result);
});
}