Index: pkg/browser/lib/interop.js |
diff --git a/pkg/browser/lib/interop.js b/pkg/browser/lib/interop.js |
index 52cc966cdb200a57402da7387b8e4aa5012f5be8..40e1700ad76d53fe7d69ec4819c10387678d1789 100644 |
--- a/pkg/browser/lib/interop.js |
+++ b/pkg/browser/lib/interop.js |
@@ -13,6 +13,11 @@ function ReceivePortSync() { |
ReceivePortSync.map[this.id] = this; |
} |
+// Type for remote proxies to Dart objects with dart2js. |
+function DartProxy(o) { |
+ this.o = o; |
+} |
+ |
(function() { |
// Serialize the following types as follows: |
// - primitives / null: unchanged |
@@ -215,3 +220,320 @@ function ReceivePortSync() { |
return deserialize(result); |
} |
})(); |
+ |
+(function() { |
+ // Proxy support for js.dart. |
+ |
+ var globalContext = window; |
+ |
+ // Table for local objects and functions that are proxied. |
+ function ProxiedObjectTable() { |
+ // Name for debugging. |
+ this.name = 'js-ref'; |
+ |
+ // Table from IDs to JS objects. |
+ this.map = {}; |
+ |
+ // Generator for new IDs. |
+ this._nextId = 0; |
+ |
+ // Ports for managing communication to proxies. |
+ this.port = new ReceivePortSync(); |
+ this.sendPort = this.port.toSendPort(); |
+ } |
+ |
+ // Number of valid IDs. This is the number of objects (global and local) |
+ // kept alive by this table. |
+ ProxiedObjectTable.prototype.count = function () { |
+ return Object.keys(this.map).length; |
+ } |
+ |
+ // Adds an object to the table and return an ID for serialization. |
+ ProxiedObjectTable.prototype.add = function (obj) { |
+ for (var ref in this.map) { |
+ var o = this.map[ref]; |
+ if (o === obj) { |
+ return ref; |
+ } |
+ } |
+ var ref = this.name + '-' + this._nextId++; |
+ this.map[ref] = obj; |
+ return ref; |
+ } |
+ |
+ // Gets the object or function corresponding to this ID. |
+ ProxiedObjectTable.prototype.get = function (id) { |
+ if (!this.map.hasOwnProperty(id)) { |
+ throw 'Proxy ' + id + ' has been invalidated.' |
+ } |
+ return this.map[id]; |
+ } |
+ |
+ ProxiedObjectTable.prototype._initialize = function () { |
+ // Configure this table's port to forward methods, getters, and setters |
+ // from the remote proxy to the local object. |
+ var table = this; |
+ |
+ this.port.receive(function (message) { |
+ // TODO(vsm): Support a mechanism to register a handler here. |
+ try { |
+ var receiver = table.get(message[0]); |
+ var member = message[1]; |
+ var kind = message[2]; |
+ var args = message[3].map(deserialize); |
+ if (kind == 'get') { |
+ // Getter. |
+ var field = member; |
+ if (field in receiver && args.length == 0) { |
+ return [ 'return', serialize(receiver[field]) ]; |
+ } |
+ } else if (kind == 'set') { |
+ // Setter. |
+ var field = member; |
+ if (args.length == 1) { |
+ return [ 'return', serialize(receiver[field] = args[0]) ]; |
+ } |
+ } else if (kind == 'hasProperty') { |
+ var field = member; |
+ return [ 'return', field in receiver ]; |
+ } else if (kind == 'apply') { |
+ // Direct function invocation. |
+ return [ 'return', |
+ serialize(receiver.apply(args[0], args.slice(1))) ]; |
+ } else if (member == '[]' && args.length == 1) { |
+ // Index getter. |
+ return [ 'return', serialize(receiver[args[0]]) ]; |
+ } 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); |
+ return [ 'return', serialize(result) ]; |
+ } |
+ } |
+ return [ 'none' ]; |
+ } catch (e) { |
+ return [ 'throws', e.toString() ]; |
+ } |
+ }); |
+ } |
+ |
+ // Singleton for local proxied objects. |
+ var proxiedObjectTable = new ProxiedObjectTable(); |
+ proxiedObjectTable._initialize() |
+ |
+ // Type for remote proxies to Dart objects. |
+ function DartProxy(id, sendPort) { |
+ this.id = id; |
+ this.port = sendPort; |
+ } |
+ |
+ // Serializes JS types to SendPortSync format: |
+ // - primitives -> primitives |
+ // - sendport -> sendport |
+ // - Function -> [ 'funcref', function-id, sendport ] |
+ // - Object -> [ 'objref', object-id, sendport ] |
+ function serialize(message) { |
+ if (message == null) { |
+ return null; // Convert undefined to null. |
+ } else if (typeof(message) == 'string' || |
+ typeof(message) == 'number' || |
+ typeof(message) == 'boolean') { |
+ // Primitives are passed directly through. |
+ return message; |
+ } else if (message instanceof SendPortSync) { |
+ // Non-proxied objects are serialized. |
+ return message; |
+ } else if (typeof(message) == 'function') { |
+ if ('_dart_id' in message) { |
+ // Remote function proxy. |
+ var remoteId = message._dart_id; |
+ var remoteSendPort = message._dart_port; |
+ return [ 'funcref', remoteId, remoteSendPort ]; |
+ } else { |
+ // Local function proxy. |
+ return [ 'funcref', |
+ proxiedObjectTable.add(message), |
+ proxiedObjectTable.sendPort ]; |
+ } |
+ } else if (message instanceof DartProxy) { |
+ // Remote object proxy. |
+ return [ 'objref', message.id, message.port ]; |
+ } else { |
+ // Local object proxy. |
+ return [ 'objref', |
+ proxiedObjectTable.add(message), |
+ proxiedObjectTable.sendPort ]; |
+ } |
+ } |
+ |
+ function deserialize(message) { |
+ if (message == null) { |
+ return null; // Convert undefined to null. |
+ } else if (typeof(message) == 'string' || |
+ typeof(message) == 'number' || |
+ typeof(message) == 'boolean') { |
+ // Primitives are passed directly through. |
+ return message; |
+ } else if (message instanceof SendPortSync) { |
+ // Serialized type. |
+ return message; |
+ } |
+ var tag = message[0]; |
+ switch (tag) { |
+ case 'funcref': return deserializeFunction(message); |
+ case 'objref': return deserializeObject(message); |
+ } |
+ throw 'Unsupported serialized data: ' + message; |
+ } |
+ |
+ // Create a local function that forwards to the remote function. |
+ function deserializeFunction(message) { |
+ var id = message[1]; |
+ var port = message[2]; |
+ // TODO(vsm): Add a more robust check for a local SendPortSync. |
+ if ("receivePort" in port) { |
+ // Local function. |
+ return proxiedObjectTable.get(id); |
+ } else { |
+ // Remote function. Forward to its port. |
+ var f = function () { |
+ var args = Array.prototype.slice.apply(arguments); |
+ args.splice(0, 0, this); |
+ args = args.map(serialize); |
+ var result = port.callSync([id, '#call', args]); |
+ if (result[0] == 'throws') throw deserialize(result[1]); |
+ return deserialize(result[1]); |
+ }; |
+ // Cache the remote id and port. |
+ f._dart_id = id; |
+ f._dart_port = port; |
+ return f; |
+ } |
+ } |
+ |
+ // Creates a DartProxy to forwards to the remote object. |
+ function deserializeObject(message) { |
+ var id = message[1]; |
+ var port = message[2]; |
+ // TODO(vsm): Add a more robust check for a local SendPortSync. |
+ if ("receivePort" in port) { |
+ // Local object. |
+ return proxiedObjectTable.get(id); |
+ } else { |
+ // Remote object. |
+ return new DartProxy(id, port); |
+ } |
+ } |
+ |
+ // Remote handler to construct a new JavaScript object given its |
+ // serialized constructor and arguments. |
+ function construct(args) { |
+ args = args.map(deserialize); |
+ var constructor = args[0]; |
+ args = Array.prototype.slice.call(args, 1); |
+ |
+ // Until 10 args, the 'new' operator is used. With more arguments we use a |
+ // generic way that may not work, particularly when the constructor does not |
+ // have an "apply" method. |
+ var ret = null; |
+ if (args.length === 0) { |
+ ret = new constructor(); |
+ } else if (args.length === 1) { |
+ ret = new constructor(args[0]); |
+ } else if (args.length === 2) { |
+ ret = new constructor(args[0], args[1]); |
+ } else if (args.length === 3) { |
+ ret = new constructor(args[0], args[1], args[2]); |
+ } else if (args.length === 4) { |
+ ret = new constructor(args[0], args[1], args[2], args[3]); |
+ } else if (args.length === 5) { |
+ ret = new constructor(args[0], args[1], args[2], args[3], args[4]); |
+ } else if (args.length === 6) { |
+ ret = new constructor(args[0], args[1], args[2], args[3], args[4], |
+ args[5]); |
+ } else if (args.length === 7) { |
+ ret = new constructor(args[0], args[1], args[2], args[3], args[4], |
+ args[5], args[6]); |
+ } else if (args.length === 8) { |
+ ret = new constructor(args[0], args[1], args[2], args[3], args[4], |
+ args[5], args[6], args[7]); |
+ } else if (args.length === 9) { |
+ ret = new constructor(args[0], args[1], args[2], args[3], args[4], |
+ args[5], args[6], args[7], args[8]); |
+ } else if (args.length === 10) { |
+ ret = new constructor(args[0], args[1], args[2], args[3], args[4], |
+ args[5], args[6], args[7], args[8], args[9]); |
+ } else { |
+ // Dummy Type with correct constructor. |
+ var Type = function(){}; |
+ Type.prototype = constructor.prototype; |
+ |
+ // Create a new instance |
+ var instance = new Type(); |
+ |
+ // Call the original constructor. |
+ ret = constructor.apply(instance, args); |
+ ret = Object(ret) === ret ? ret : instance; |
+ } |
+ return serialize(ret); |
+ } |
+ |
+ // Remote handler to return the top-level JavaScript context. |
+ function context(data) { |
+ return serialize(globalContext); |
+ } |
+ |
+ // Return true if a JavaScript proxy is instance of a given type (instanceof). |
+ function proxyInstanceof(args) { |
+ var obj = deserialize(args[0]); |
+ var type = deserialize(args[1]); |
+ return obj instanceof type; |
+ } |
+ |
+ // Return true if a JavaScript proxy is instance of a given type (instanceof). |
+ function proxyDeleteProperty(args) { |
+ var obj = deserialize(args[0]); |
+ var member = deserialize(args[1]); |
+ delete obj[member]; |
+ } |
+ |
+ function proxyConvert(args) { |
+ return serialize(deserializeDataTree(args)); |
+ } |
+ |
+ function deserializeDataTree(data) { |
+ var type = data[0]; |
+ var value = data[1]; |
+ if (type === 'map') { |
+ var obj = {}; |
+ for (var i = 0; i < value.length; i++) { |
+ obj[value[i][0]] = deserializeDataTree(value[i][1]); |
+ } |
+ return obj; |
+ } else if (type === 'list') { |
+ var list = []; |
+ for (var i = 0; i < value.length; i++) { |
+ list.push(deserializeDataTree(value[i])); |
+ } |
+ return list; |
+ } else /* 'simple' */ { |
+ return deserialize(value); |
+ } |
+ } |
+ |
+ function makeGlobalPort(name, f) { |
+ var port = new ReceivePortSync(); |
+ port.receive(f); |
+ window.registerPort(name, port.toSendPort()); |
+ } |
+ |
+ makeGlobalPort('dart-js-context', context); |
+ makeGlobalPort('dart-js-create', construct); |
+ makeGlobalPort('dart-js-instanceof', proxyInstanceof); |
+ makeGlobalPort('dart-js-delete-property', proxyDeleteProperty); |
+ makeGlobalPort('dart-js-convert', proxyConvert); |
+})(); |