| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 // --------------------------------------------------------------------------- | 5 // TODO(jmesserly): remove this script after a deprecation period. |
| 6 // Support for JS interoperability | 6 if (typeof console == "object" && typeof console.warn == "function") { |
| 7 // --------------------------------------------------------------------------- | 7 console.warn('<script src="packages/browser/interop.js"> is no longer ' + |
| 8 function SendPortSync() { | 8 'needed for dart:js. See http://pub.dartlang.org/packages/browser.'); |
| 9 } | 9 } |
| 10 | |
| 11 function ReceivePortSync() { | |
| 12 this.id = ReceivePortSync.id++; | |
| 13 ReceivePortSync.map[this.id] = this; | |
| 14 } | |
| 15 | |
| 16 // Type for remote proxies to Dart objects with dart2js. | |
| 17 function DartProxy(o) { | |
| 18 this.o = o; | |
| 19 } | |
| 20 | |
| 21 (function() { | |
| 22 // Serialize the following types as follows: | |
| 23 // - primitives / null: unchanged | |
| 24 // - lists: [ 'list', internal id, list of recursively serialized elements ] | |
| 25 // - maps: [ 'map', internal id, map of keys and recursively serialized value
s ] | |
| 26 // - send ports: [ 'sendport', type, isolate id, port id ] | |
| 27 // | |
| 28 // Note, internal id's are for cycle detection. | |
| 29 function serialize(message) { | |
| 30 var visited = []; | |
| 31 function checkedSerialization(obj, serializer) { | |
| 32 // Implementation detail: for now use linear search. | |
| 33 // Another option is expando, but it may prohibit | |
| 34 // VM optimizations (like putting object into slow mode | |
| 35 // on property deletion.) | |
| 36 var id = visited.indexOf(obj); | |
| 37 if (id != -1) return [ 'ref', id ]; | |
| 38 var id = visited.length; | |
| 39 visited.push(obj); | |
| 40 return serializer(id); | |
| 41 } | |
| 42 | |
| 43 function doSerialize(message) { | |
| 44 if (message == null) { | |
| 45 return null; // Convert undefined to null. | |
| 46 } else if (typeof(message) == 'string' || | |
| 47 typeof(message) == 'number' || | |
| 48 typeof(message) == 'boolean') { | |
| 49 return message; | |
| 50 } else if (message instanceof Array) { | |
| 51 return checkedSerialization(message, function(id) { | |
| 52 var values = new Array(message.length); | |
| 53 for (var i = 0; i < message.length; i++) { | |
| 54 values[i] = doSerialize(message[i]); | |
| 55 } | |
| 56 return [ 'list', id, values ]; | |
| 57 }); | |
| 58 } else if (message instanceof LocalSendPortSync) { | |
| 59 return [ 'sendport', 'nativejs', message.receivePort.id ]; | |
| 60 } else if (message instanceof DartSendPortSync) { | |
| 61 return [ 'sendport', 'dart', message.isolateId, message.portId ]; | |
| 62 } else { | |
| 63 return checkedSerialization(message, function(id) { | |
| 64 var keys = Object.getOwnPropertyNames(message); | |
| 65 var values = new Array(keys.length); | |
| 66 for (var i = 0; i < keys.length; i++) { | |
| 67 values[i] = doSerialize(message[keys[i]]); | |
| 68 } | |
| 69 return [ 'map', id, keys, values ]; | |
| 70 }); | |
| 71 } | |
| 72 } | |
| 73 return doSerialize(message); | |
| 74 } | |
| 75 | |
| 76 function deserialize(message) { | |
| 77 return deserializeHelper(message); | |
| 78 } | |
| 79 | |
| 80 function deserializeHelper(message) { | |
| 81 if (message == null || | |
| 82 typeof(message) == 'string' || | |
| 83 typeof(message) == 'number' || | |
| 84 typeof(message) == 'boolean') { | |
| 85 return message; | |
| 86 } | |
| 87 switch (message[0]) { | |
| 88 case 'map': return deserializeMap(message); | |
| 89 case 'sendport': return deserializeSendPort(message); | |
| 90 case 'list': return deserializeList(message); | |
| 91 default: throw 'unimplemented'; | |
| 92 } | |
| 93 } | |
| 94 | |
| 95 function deserializeMap(message) { | |
| 96 var result = { }; | |
| 97 var id = message[1]; | |
| 98 var keys = message[2]; | |
| 99 var values = message[3]; | |
| 100 for (var i = 0, length = keys.length; i < length; i++) { | |
| 101 var key = deserializeHelper(keys[i]); | |
| 102 var value = deserializeHelper(values[i]); | |
| 103 result[key] = value; | |
| 104 } | |
| 105 return result; | |
| 106 } | |
| 107 | |
| 108 function deserializeSendPort(message) { | |
| 109 var tag = message[1]; | |
| 110 switch (tag) { | |
| 111 case 'nativejs': | |
| 112 var id = message[2]; | |
| 113 return new LocalSendPortSync(ReceivePortSync.map[id]); | |
| 114 case 'dart': | |
| 115 var isolateId = message[2]; | |
| 116 var portId = message[3]; | |
| 117 return new DartSendPortSync(isolateId, portId); | |
| 118 default: | |
| 119 throw 'Illegal SendPortSync type: $tag'; | |
| 120 } | |
| 121 } | |
| 122 | |
| 123 function deserializeList(message) { | |
| 124 var values = message[2]; | |
| 125 var length = values.length; | |
| 126 var result = new Array(length); | |
| 127 for (var i = 0; i < length; i++) { | |
| 128 result[i] = deserializeHelper(values[i]); | |
| 129 } | |
| 130 return result; | |
| 131 } | |
| 132 | |
| 133 window.registerPort = function(name, port) { | |
| 134 var stringified = JSON.stringify(serialize(port)); | |
| 135 var attrName = 'dart-port:' + name; | |
| 136 document.documentElement.setAttribute(attrName, stringified); | |
| 137 }; | |
| 138 | |
| 139 window.lookupPort = function(name) { | |
| 140 var attrName = 'dart-port:' + name; | |
| 141 var stringified = document.documentElement.getAttribute(attrName); | |
| 142 return deserialize(JSON.parse(stringified)); | |
| 143 }; | |
| 144 | |
| 145 ReceivePortSync.id = 0; | |
| 146 ReceivePortSync.map = {}; | |
| 147 | |
| 148 ReceivePortSync.dispatchCall = function(id, message) { | |
| 149 // TODO(vsm): Handle and propagate exceptions. | |
| 150 var deserialized = deserialize(message); | |
| 151 var result = ReceivePortSync.map[id].callback(deserialized); | |
| 152 return serialize(result); | |
| 153 }; | |
| 154 | |
| 155 ReceivePortSync.prototype.receive = function(callback) { | |
| 156 this.callback = callback; | |
| 157 }; | |
| 158 | |
| 159 ReceivePortSync.prototype.toSendPort = function() { | |
| 160 return new LocalSendPortSync(this); | |
| 161 }; | |
| 162 | |
| 163 ReceivePortSync.prototype.close = function() { | |
| 164 delete ReceivePortSync.map[this.id]; | |
| 165 }; | |
| 166 | |
| 167 if (navigator.webkitStartDart) { | |
| 168 window.addEventListener('js-sync-message', function(event) { | |
| 169 var data = JSON.parse(getPortSyncEventData(event)); | |
| 170 var deserialized = deserialize(data.message); | |
| 171 var result = ReceivePortSync.map[data.id].callback(deserialized); | |
| 172 // TODO(vsm): Handle and propagate exceptions. | |
| 173 dispatchEvent('js-result', serialize(result)); | |
| 174 }, false); | |
| 175 } | |
| 176 | |
| 177 function LocalSendPortSync(receivePort) { | |
| 178 this.receivePort = receivePort; | |
| 179 } | |
| 180 | |
| 181 LocalSendPortSync.prototype = new SendPortSync(); | |
| 182 | |
| 183 LocalSendPortSync.prototype.callSync = function(message) { | |
| 184 // TODO(vsm): Do a direct deepcopy. | |
| 185 message = deserialize(serialize(message)); | |
| 186 return this.receivePort.callback(message); | |
| 187 } | |
| 188 | |
| 189 function DartSendPortSync(isolateId, portId) { | |
| 190 this.isolateId = isolateId; | |
| 191 this.portId = portId; | |
| 192 } | |
| 193 | |
| 194 DartSendPortSync.prototype = new SendPortSync(); | |
| 195 | |
| 196 function dispatchEvent(receiver, message) { | |
| 197 var string = JSON.stringify(message); | |
| 198 var event = document.createEvent('CustomEvent'); | |
| 199 event.initCustomEvent(receiver, false, false, string); | |
| 200 window.dispatchEvent(event); | |
| 201 } | |
| 202 | |
| 203 function getPortSyncEventData(event) { | |
| 204 return event.detail; | |
| 205 } | |
| 206 | |
| 207 DartSendPortSync.prototype.callSync = function(message) { | |
| 208 var serialized = serialize(message); | |
| 209 var target = 'dart-port-' + this.isolateId + '-' + this.portId; | |
| 210 // TODO(vsm): Make this re-entrant. | |
| 211 // TODO(vsm): Set this up set once, on the first call. | |
| 212 var source = target + '-result'; | |
| 213 var result = null; | |
| 214 var listener = function (e) { | |
| 215 result = JSON.parse(getPortSyncEventData(e)); | |
| 216 }; | |
| 217 window.addEventListener(source, listener, false); | |
| 218 dispatchEvent(target, [source, serialized]); | |
| 219 window.removeEventListener(source, listener, false); | |
| 220 return deserialize(result); | |
| 221 } | |
| 222 })(); | |
| 223 | |
| 224 (function() { | |
| 225 // Proxy support for js.dart. | |
| 226 | |
| 227 // We don't use 'window' because we might be in a web worker, but we don't | |
| 228 // use 'self' because not all browsers support it | |
| 229 var globalContext = function() { return this; }(); | |
| 230 | |
| 231 // Table for local objects and functions that are proxied. | |
| 232 function ProxiedObjectTable() { | |
| 233 // Name for debugging. | |
| 234 this.name = 'js-ref'; | |
| 235 | |
| 236 // Table from IDs to JS objects. | |
| 237 this.map = {}; | |
| 238 | |
| 239 // Generator for new IDs. | |
| 240 this._nextId = 0; | |
| 241 | |
| 242 // Ports for managing communication to proxies. | |
| 243 this.port = new ReceivePortSync(); | |
| 244 this.sendPort = this.port.toSendPort(); | |
| 245 } | |
| 246 | |
| 247 // Number of valid IDs. This is the number of objects (global and local) | |
| 248 // kept alive by this table. | |
| 249 ProxiedObjectTable.prototype.count = function () { | |
| 250 return Object.keys(this.map).length; | |
| 251 } | |
| 252 | |
| 253 // Adds an object to the table and return an ID for serialization. | |
| 254 ProxiedObjectTable.prototype.add = function (obj) { | |
| 255 for (var ref in this.map) { | |
| 256 var o = this.map[ref]; | |
| 257 if (o === obj) { | |
| 258 return ref; | |
| 259 } | |
| 260 } | |
| 261 var ref = this.name + '-' + this._nextId++; | |
| 262 this.map[ref] = obj; | |
| 263 return ref; | |
| 264 } | |
| 265 | |
| 266 // Gets the object or function corresponding to this ID. | |
| 267 ProxiedObjectTable.prototype.get = function (id) { | |
| 268 if (!this.map.hasOwnProperty(id)) { | |
| 269 throw 'Proxy ' + id + ' has been invalidated.' | |
| 270 } | |
| 271 return this.map[id]; | |
| 272 } | |
| 273 | |
| 274 ProxiedObjectTable.prototype._initialize = function () { | |
| 275 // Configure this table's port to forward methods, getters, and setters | |
| 276 // from the remote proxy to the local object. | |
| 277 var table = this; | |
| 278 | |
| 279 this.port.receive(function (message) { | |
| 280 // TODO(vsm): Support a mechanism to register a handler here. | |
| 281 try { | |
| 282 var receiver = table.get(message[0]); | |
| 283 var member = message[1]; | |
| 284 var kind = message[2]; | |
| 285 var args = message[3].map(deserialize); | |
| 286 if (kind == 'get') { | |
| 287 // Getter. | |
| 288 var field = member; | |
| 289 if (field in receiver && args.length == 0) { | |
| 290 return [ 'return', serialize(receiver[field]) ]; | |
| 291 } | |
| 292 } else if (kind == 'set') { | |
| 293 // Setter. | |
| 294 var field = member; | |
| 295 if (args.length == 1) { | |
| 296 return [ 'return', serialize(receiver[field] = args[0]) ]; | |
| 297 } | |
| 298 } else if (kind == 'hasProperty') { | |
| 299 var field = member; | |
| 300 return [ 'return', field in receiver ]; | |
| 301 } else if (kind == 'apply') { | |
| 302 // Direct function invocation. | |
| 303 return [ 'return', | |
| 304 serialize(receiver.apply(args[0], args.slice(1))) ]; | |
| 305 } else if (member == '[]' && args.length == 1) { | |
| 306 // Index getter. | |
| 307 return [ 'return', serialize(receiver[args[0]]) ]; | |
| 308 } else if (member == '[]=' && args.length == 2) { | |
| 309 // Index setter. | |
| 310 return [ 'return', serialize(receiver[args[0]] = args[1]) ]; | |
| 311 } else { | |
| 312 // Member function invocation. | |
| 313 var f = receiver[member]; | |
| 314 if (f) { | |
| 315 var result = f.apply(receiver, args); | |
| 316 return [ 'return', serialize(result) ]; | |
| 317 } | |
| 318 } | |
| 319 return [ 'none' ]; | |
| 320 } catch (e) { | |
| 321 return [ 'throws', e.toString() ]; | |
| 322 } | |
| 323 }); | |
| 324 } | |
| 325 | |
| 326 // Singleton for local proxied objects. | |
| 327 var proxiedObjectTable = new ProxiedObjectTable(); | |
| 328 proxiedObjectTable._initialize() | |
| 329 | |
| 330 // Type for remote proxies to Dart objects. | |
| 331 function DartProxy(id, sendPort) { | |
| 332 this.id = id; | |
| 333 this.port = sendPort; | |
| 334 } | |
| 335 | |
| 336 // Serializes JS types to SendPortSync format: | |
| 337 // - primitives -> primitives | |
| 338 // - sendport -> sendport | |
| 339 // - Function -> [ 'funcref', function-id, sendport ] | |
| 340 // - Object -> [ 'objref', object-id, sendport ] | |
| 341 function serialize(message) { | |
| 342 if (message == null) { | |
| 343 return null; // Convert undefined to null. | |
| 344 } else if (typeof(message) == 'string' || | |
| 345 typeof(message) == 'number' || | |
| 346 typeof(message) == 'boolean') { | |
| 347 // Primitives are passed directly through. | |
| 348 return message; | |
| 349 } else if (message instanceof SendPortSync) { | |
| 350 // Non-proxied objects are serialized. | |
| 351 return message; | |
| 352 } else if (typeof(message) == 'function') { | |
| 353 if ('_dart_id' in message) { | |
| 354 // Remote function proxy. | |
| 355 var remoteId = message._dart_id; | |
| 356 var remoteSendPort = message._dart_port; | |
| 357 return [ 'funcref', remoteId, remoteSendPort ]; | |
| 358 } else { | |
| 359 // Local function proxy. | |
| 360 return [ 'funcref', | |
| 361 proxiedObjectTable.add(message), | |
| 362 proxiedObjectTable.sendPort ]; | |
| 363 } | |
| 364 } else if (message instanceof DartProxy) { | |
| 365 // Remote object proxy. | |
| 366 return [ 'objref', message.id, message.port ]; | |
| 367 } else { | |
| 368 // Local object proxy. | |
| 369 return [ 'objref', | |
| 370 proxiedObjectTable.add(message), | |
| 371 proxiedObjectTable.sendPort ]; | |
| 372 } | |
| 373 } | |
| 374 | |
| 375 function deserialize(message) { | |
| 376 if (message == null) { | |
| 377 return null; // Convert undefined to null. | |
| 378 } else if (typeof(message) == 'string' || | |
| 379 typeof(message) == 'number' || | |
| 380 typeof(message) == 'boolean') { | |
| 381 // Primitives are passed directly through. | |
| 382 return message; | |
| 383 } else if (message instanceof SendPortSync) { | |
| 384 // Serialized type. | |
| 385 return message; | |
| 386 } | |
| 387 var tag = message[0]; | |
| 388 switch (tag) { | |
| 389 case 'funcref': return deserializeFunction(message); | |
| 390 case 'objref': return deserializeObject(message); | |
| 391 } | |
| 392 throw 'Unsupported serialized data: ' + message; | |
| 393 } | |
| 394 | |
| 395 // Create a local function that forwards to the remote function. | |
| 396 function deserializeFunction(message) { | |
| 397 var id = message[1]; | |
| 398 var port = message[2]; | |
| 399 // TODO(vsm): Add a more robust check for a local SendPortSync. | |
| 400 if ("receivePort" in port) { | |
| 401 // Local function. | |
| 402 return proxiedObjectTable.get(id); | |
| 403 } else { | |
| 404 // Remote function. Forward to its port. | |
| 405 var f = function () { | |
| 406 var args = Array.prototype.slice.apply(arguments); | |
| 407 args.splice(0, 0, this); | |
| 408 args = args.map(serialize); | |
| 409 var result = port.callSync([id, '#call', args]); | |
| 410 if (result[0] == 'throws') throw deserialize(result[1]); | |
| 411 return deserialize(result[1]); | |
| 412 }; | |
| 413 // Cache the remote id and port. | |
| 414 f._dart_id = id; | |
| 415 f._dart_port = port; | |
| 416 return f; | |
| 417 } | |
| 418 } | |
| 419 | |
| 420 // Creates a DartProxy to forwards to the remote object. | |
| 421 function deserializeObject(message) { | |
| 422 var id = message[1]; | |
| 423 var port = message[2]; | |
| 424 // TODO(vsm): Add a more robust check for a local SendPortSync. | |
| 425 if ("receivePort" in port) { | |
| 426 // Local object. | |
| 427 return proxiedObjectTable.get(id); | |
| 428 } else { | |
| 429 // Remote object. | |
| 430 return new DartProxy(id, port); | |
| 431 } | |
| 432 } | |
| 433 | |
| 434 // Remote handler to construct a new JavaScript object given its | |
| 435 // serialized constructor and arguments. | |
| 436 function construct(args) { | |
| 437 args = args.map(deserialize); | |
| 438 var constructor = args[0]; | |
| 439 args = Array.prototype.slice.call(args, 1); | |
| 440 | |
| 441 // Until 10 args, the 'new' operator is used. With more arguments we use a | |
| 442 // generic way that may not work, particularly when the constructor does not | |
| 443 // have an "apply" method. | |
| 444 var ret = null; | |
| 445 if (args.length === 0) { | |
| 446 ret = new constructor(); | |
| 447 } else if (args.length === 1) { | |
| 448 ret = new constructor(args[0]); | |
| 449 } else if (args.length === 2) { | |
| 450 ret = new constructor(args[0], args[1]); | |
| 451 } else if (args.length === 3) { | |
| 452 ret = new constructor(args[0], args[1], args[2]); | |
| 453 } else if (args.length === 4) { | |
| 454 ret = new constructor(args[0], args[1], args[2], args[3]); | |
| 455 } else if (args.length === 5) { | |
| 456 ret = new constructor(args[0], args[1], args[2], args[3], args[4]); | |
| 457 } else if (args.length === 6) { | |
| 458 ret = new constructor(args[0], args[1], args[2], args[3], args[4], | |
| 459 args[5]); | |
| 460 } else if (args.length === 7) { | |
| 461 ret = new constructor(args[0], args[1], args[2], args[3], args[4], | |
| 462 args[5], args[6]); | |
| 463 } else if (args.length === 8) { | |
| 464 ret = new constructor(args[0], args[1], args[2], args[3], args[4], | |
| 465 args[5], args[6], args[7]); | |
| 466 } else if (args.length === 9) { | |
| 467 ret = new constructor(args[0], args[1], args[2], args[3], args[4], | |
| 468 args[5], args[6], args[7], args[8]); | |
| 469 } else if (args.length === 10) { | |
| 470 ret = new constructor(args[0], args[1], args[2], args[3], args[4], | |
| 471 args[5], args[6], args[7], args[8], args[9]); | |
| 472 } else { | |
| 473 // Dummy Type with correct constructor. | |
| 474 var Type = function(){}; | |
| 475 Type.prototype = constructor.prototype; | |
| 476 | |
| 477 // Create a new instance | |
| 478 var instance = new Type(); | |
| 479 | |
| 480 // Call the original constructor. | |
| 481 ret = constructor.apply(instance, args); | |
| 482 ret = Object(ret) === ret ? ret : instance; | |
| 483 } | |
| 484 return serialize(ret); | |
| 485 } | |
| 486 | |
| 487 // Remote handler to return the top-level JavaScript context. | |
| 488 function context(data) { | |
| 489 return serialize(globalContext); | |
| 490 } | |
| 491 | |
| 492 // Return true if a JavaScript proxy is instance of a given type (instanceof). | |
| 493 function proxyInstanceof(args) { | |
| 494 var obj = deserialize(args[0]); | |
| 495 var type = deserialize(args[1]); | |
| 496 return obj instanceof type; | |
| 497 } | |
| 498 | |
| 499 // Return true if a JavaScript proxy is instance of a given type (instanceof). | |
| 500 function proxyDeleteProperty(args) { | |
| 501 var obj = deserialize(args[0]); | |
| 502 var member = deserialize(args[1]); | |
| 503 delete obj[member]; | |
| 504 } | |
| 505 | |
| 506 function proxyConvert(args) { | |
| 507 return serialize(deserializeDataTree(args)); | |
| 508 } | |
| 509 | |
| 510 function deserializeDataTree(data) { | |
| 511 var type = data[0]; | |
| 512 var value = data[1]; | |
| 513 if (type === 'map') { | |
| 514 var obj = {}; | |
| 515 for (var i = 0; i < value.length; i++) { | |
| 516 obj[value[i][0]] = deserializeDataTree(value[i][1]); | |
| 517 } | |
| 518 return obj; | |
| 519 } else if (type === 'list') { | |
| 520 var list = []; | |
| 521 for (var i = 0; i < value.length; i++) { | |
| 522 list.push(deserializeDataTree(value[i])); | |
| 523 } | |
| 524 return list; | |
| 525 } else /* 'simple' */ { | |
| 526 return deserialize(value); | |
| 527 } | |
| 528 } | |
| 529 | |
| 530 function makeGlobalPort(name, f) { | |
| 531 var port = new ReceivePortSync(); | |
| 532 port.receive(f); | |
| 533 window.registerPort(name, port.toSendPort()); | |
| 534 } | |
| 535 | |
| 536 makeGlobalPort('dart-js-context', context); | |
| 537 makeGlobalPort('dart-js-create', construct); | |
| 538 makeGlobalPort('dart-js-instanceof', proxyInstanceof); | |
| 539 makeGlobalPort('dart-js-delete-property', proxyDeleteProperty); | |
| 540 makeGlobalPort('dart-js-convert', proxyConvert); | |
| 541 })(); | |
| OLD | NEW |