| 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);
|
| +})();
|
|
|