Index: pkg/browser/lib/interop.js |
diff --git a/pkg/browser/lib/interop.js b/pkg/browser/lib/interop.js |
index 1caeb5a209937eb0967c211bd0bc1e4a8a59926b..f4087dc745e80800184b2e14bab399503a79a16c 100644 |
--- a/pkg/browser/lib/interop.js |
+++ b/pkg/browser/lib/interop.js |
@@ -2,536 +2,8 @@ |
// for details. All rights reserved. Use of this source code is governed by a |
// BSD-style license that can be found in the LICENSE file. |
-// --------------------------------------------------------------------------- |
-// Support for JS interoperability |
-// --------------------------------------------------------------------------- |
-function SendPortSync() { |
-} |
- |
-function ReceivePortSync() { |
- this.id = ReceivePortSync.id++; |
- ReceivePortSync.map[this.id] = this; |
-} |
- |
// Type for remote proxies to Dart objects with dart2js. |
+// WARNING: do not call this constructor |
Jacob
2013/10/28 21:36:59
missing period.
|
function DartObject(o) { |
this.o = o; |
} |
- |
-(function() { |
- // Serialize the following types as follows: |
- // - primitives / null: unchanged |
- // - lists: [ 'list', internal id, list of recursively serialized elements ] |
- // - maps: [ 'map', internal id, map of keys and recursively serialized values ] |
- // - send ports: [ 'sendport', type, isolate id, port id ] |
- // |
- // Note, internal id's are for cycle detection. |
- function serialize(message) { |
- var visited = []; |
- function checkedSerialization(obj, serializer) { |
- // Implementation detail: for now use linear search. |
- // Another option is expando, but it may prohibit |
- // VM optimizations (like putting object into slow mode |
- // on property deletion.) |
- var id = visited.indexOf(obj); |
- if (id != -1) return [ 'ref', id ]; |
- var id = visited.length; |
- visited.push(obj); |
- return serializer(id); |
- } |
- |
- function doSerialize(message) { |
- if (message == null) { |
- return null; // Convert undefined to null. |
- } else if (typeof(message) == 'string' || |
- typeof(message) == 'number' || |
- typeof(message) == 'boolean') { |
- return message; |
- } else if (message instanceof Array) { |
- return checkedSerialization(message, function(id) { |
- var values = new Array(message.length); |
- for (var i = 0; i < message.length; i++) { |
- values[i] = doSerialize(message[i]); |
- } |
- return [ 'list', id, values ]; |
- }); |
- } else if (message instanceof LocalSendPortSync) { |
- return [ 'sendport', 'nativejs', message.receivePort.id ]; |
- } else if (message instanceof DartSendPortSync) { |
- return [ 'sendport', 'dart', message.isolateId, message.portId ]; |
- } else { |
- return checkedSerialization(message, function(id) { |
- var keys = Object.getOwnPropertyNames(message); |
- var values = new Array(keys.length); |
- for (var i = 0; i < keys.length; i++) { |
- values[i] = doSerialize(message[keys[i]]); |
- } |
- return [ 'map', id, keys, values ]; |
- }); |
- } |
- } |
- return doSerialize(message); |
- } |
- |
- function deserialize(message) { |
- return deserializeHelper(message); |
- } |
- |
- function deserializeHelper(message) { |
- if (message == null || |
- typeof(message) == 'string' || |
- typeof(message) == 'number' || |
- typeof(message) == 'boolean') { |
- return message; |
- } |
- switch (message[0]) { |
- case 'map': return deserializeMap(message); |
- case 'sendport': return deserializeSendPort(message); |
- case 'list': return deserializeList(message); |
- default: throw 'unimplemented'; |
- } |
- } |
- |
- function deserializeMap(message) { |
- var result = { }; |
- var id = message[1]; |
- var keys = message[2]; |
- var values = message[3]; |
- for (var i = 0, length = keys.length; i < length; i++) { |
- var key = deserializeHelper(keys[i]); |
- var value = deserializeHelper(values[i]); |
- result[key] = value; |
- } |
- return result; |
- } |
- |
- function deserializeSendPort(message) { |
- var tag = message[1]; |
- switch (tag) { |
- case 'nativejs': |
- var id = message[2]; |
- return new LocalSendPortSync(ReceivePortSync.map[id]); |
- case 'dart': |
- var isolateId = message[2]; |
- var portId = message[3]; |
- return new DartSendPortSync(isolateId, portId); |
- default: |
- throw 'Illegal SendPortSync type: $tag'; |
- } |
- } |
- |
- function deserializeList(message) { |
- var values = message[2]; |
- var length = values.length; |
- var result = new Array(length); |
- for (var i = 0; i < length; i++) { |
- result[i] = deserializeHelper(values[i]); |
- } |
- return result; |
- } |
- |
- window.registerPort = function(name, port) { |
- var stringified = JSON.stringify(serialize(port)); |
- var attrName = 'dart-port:' + name; |
- document.documentElement.setAttribute(attrName, stringified); |
- }; |
- |
- window.lookupPort = function(name) { |
- var attrName = 'dart-port:' + name; |
- var stringified = document.documentElement.getAttribute(attrName); |
- return deserialize(JSON.parse(stringified)); |
- }; |
- |
- ReceivePortSync.id = 0; |
- ReceivePortSync.map = {}; |
- |
- ReceivePortSync.dispatchCall = function(id, message) { |
- // TODO(vsm): Handle and propagate exceptions. |
- var deserialized = deserialize(message); |
- var result = ReceivePortSync.map[id].callback(deserialized); |
- return serialize(result); |
- }; |
- |
- ReceivePortSync.prototype.receive = function(callback) { |
- this.callback = callback; |
- }; |
- |
- ReceivePortSync.prototype.toSendPort = function() { |
- return new LocalSendPortSync(this); |
- }; |
- |
- ReceivePortSync.prototype.close = function() { |
- delete ReceivePortSync.map[this.id]; |
- }; |
- |
- if (navigator.webkitStartDart) { |
- window.addEventListener('js-sync-message', function(event) { |
- var data = JSON.parse(getPortSyncEventData(event)); |
- var deserialized = deserialize(data.message); |
- var result = ReceivePortSync.map[data.id].callback(deserialized); |
- // TODO(vsm): Handle and propagate exceptions. |
- dispatchEvent('js-result', serialize(result)); |
- }, false); |
- } |
- |
- function LocalSendPortSync(receivePort) { |
- this.receivePort = receivePort; |
- } |
- |
- LocalSendPortSync.prototype = new SendPortSync(); |
- |
- LocalSendPortSync.prototype.callSync = function(message) { |
- // TODO(vsm): Do a direct deepcopy. |
- message = deserialize(serialize(message)); |
- return this.receivePort.callback(message); |
- } |
- |
- function DartSendPortSync(isolateId, portId) { |
- this.isolateId = isolateId; |
- this.portId = portId; |
- } |
- |
- DartSendPortSync.prototype = new SendPortSync(); |
- |
- function dispatchEvent(receiver, message) { |
- var string = JSON.stringify(message); |
- var event = document.createEvent('CustomEvent'); |
- event.initCustomEvent(receiver, false, false, string); |
- window.dispatchEvent(event); |
- } |
- |
- function getPortSyncEventData(event) { |
- return event.detail; |
- } |
- |
- DartSendPortSync.prototype.callSync = function(message) { |
- var serialized = serialize(message); |
- var target = 'dart-port-' + this.isolateId + '-' + this.portId; |
- // TODO(vsm): Make this re-entrant. |
- // TODO(vsm): Set this up set once, on the first call. |
- var source = target + '-result'; |
- var result = null; |
- var listener = function (e) { |
- result = JSON.parse(getPortSyncEventData(e)); |
- }; |
- window.addEventListener(source, listener, false); |
- dispatchEvent(target, [source, serialized]); |
- window.removeEventListener(source, listener, false); |
- return deserialize(result); |
- } |
-})(); |
- |
-(function() { |
- // Proxy support for js.dart. |
- |
- // We don't use 'window' because we might be in a web worker, but we don't |
- // use 'self' because not all browsers support it |
- var globalContext = function() { return this; }(); |
- |
- // 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; |
- } |
- |
- var _dartRefPropertyName = "_$dart_ref"; |
- |
- // attempts to add an unenumerable property to o. If that is not allowed |
- // it silently fails. |
- function _defineProperty(o, name, value) { |
- if (Object.isExtensible(o)) { |
- try { |
- Object.defineProperty(o, name, { 'value': value }); |
- } catch (e) { |
- // object is native and lies about being extensible |
- // see https://bugzilla.mozilla.org/show_bug.cgi?id=775185 |
- } |
- } |
- } |
- |
- // Adds an object to the table and return an ID for serialization. |
- ProxiedObjectTable.prototype.add = function (obj, id) { |
- if (id != null) { |
- this.map[id] = obj; |
- return id; |
- } else { |
- var ref = obj[_dartRefPropertyName]; |
- if (ref == null) { |
- ref = this.name + '-' + this._nextId++; |
- this.map[ref] = obj; |
- _defineProperty(obj, _dartRefPropertyName, ref); |
- } |
- return ref; |
- } |
- } |
- |
- // Gets the object or function corresponding to this ID. |
- ProxiedObjectTable.prototype.contains = function (id) { |
- return this.map.hasOwnProperty(id); |
- } |
- |
- // 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 DartObject(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 DartObject) { |
- // 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. |
- if (proxiedObjectTable.contains(id)) { |
- return proxiedObjectTable.get(id); |
- } |
- 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; |
- proxiedObjectTable.add(f, id); |
- return f; |
- } |
- } |
- |
- // Creates a DartObject 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. |
- if (proxiedObjectTable.contains(id)) { |
- return proxiedObjectTable.get(id); |
- } |
- var proxy = new DartObject(id, port); |
- proxiedObjectTable.add(proxy, id); |
- return proxy; |
- } |
- } |
- |
- // 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]; |
- |
- // The following code solves the problem of invoking a JavaScript |
- // constructor with an unknown number arguments. |
- // First bind the constructor to the argument list using bind.apply(). |
- // The first argument to bind() is the binding of 'this', make it 'null' |
- // After that, use the JavaScript 'new' operator which overrides any binding |
- // of 'this' with the new instance. |
- args[0] = null; |
- var factoryFunction = constructor.bind.apply(constructor, args); |
- return serialize(new factoryFunction()); |
- } |
- |
- // 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); |
-})(); |