Chromium Code Reviews| Index: pkg/browser/lib/interop.js |
| diff --git a/pkg/browser/lib/interop.js b/pkg/browser/lib/interop.js |
| index 52cc966cdb200a57402da7387b8e4aa5012f5be8..720d91d456e5879ee72864346ecf5a9e4cee661b 100644 |
| --- a/pkg/browser/lib/interop.js |
| +++ b/pkg/browser/lib/interop.js |
| @@ -215,3 +215,351 @@ function ReceivePortSync() { |
| return deserialize(result); |
| } |
| })(); |
| + |
| +(function() { |
| + // Proxy support for js.dart. |
| + |
| + 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 BoundFunction(_this, object) { |
| + this._this = _this; |
| + this.object = object; |
| + } |
| + |
| + // 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) { |
| + // TODO(vsm): Cache refs for each obj? |
| + 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 object = table.get(message[0]); |
| + var receiver = unbind(object); |
| + 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) { |
| + var result = bindIfFunction(receiver[field], receiver); |
| + return [ 'return', serialize(result) ]; |
| + } |
| + } 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))) ]; |
|
vsm
2013/06/20 18:43:58
nit: line length
alexandre.ardhuin
2013/06/27 17:05:36
Done.
|
| + } else if (member == '[]' && args.length == 1) { |
| + // Index getter. |
| + 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); |
| + 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 (message instanceof BoundFunction && |
| + typeof(message.object) == 'function') { |
| + // Local function proxy. |
| + return [ 'funcref', |
| + proxiedObjectTable.add(message), |
| + proxiedObjectTable.sendPort ]; |
| + } 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 unbind(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 = unbind(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 two JavaScript proxies are equal (==). |
| + function proxyEquals(args) { |
| + return deserialize(args[0]) == deserialize(args[1]); |
| + } |
| + |
| + // Return true if a JavaScript proxy is instance of a given type (instanceof). |
| + function proxyInstanceof(args) { |
| + 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) { |
| + var obj = unbind(deserialize(args[0])); |
| + var member = unbind(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-equals', proxyEquals); |
| + makeGlobalPort('dart-js-instanceof', proxyInstanceof); |
| + makeGlobalPort('dart-js-delete-property', proxyDeleteProperty); |
| + makeGlobalPort('dart-js-convert', proxyConvert); |
| +})(); |