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 // --------------------------------------------------------------------------- |
6 // Support for JS interoperability | 6 // Support for JS interoperability |
7 // --------------------------------------------------------------------------- | 7 // --------------------------------------------------------------------------- |
8 function SendPortSync() { | 8 function SendPortSync() { |
9 } | 9 } |
10 | 10 |
11 function ReceivePortSync() { | 11 function ReceivePortSync() { |
12 this.id = ReceivePortSync.id++; | 12 this.id = ReceivePortSync.id++; |
13 ReceivePortSync.map[this.id] = this; | 13 ReceivePortSync.map[this.id] = this; |
14 } | 14 } |
15 | 15 |
| 16 // Type for remote proxies to Dart objects with dart2js. |
| 17 function DartProxy(o) { |
| 18 this.o = o; |
| 19 } |
| 20 |
16 (function() { | 21 (function() { |
17 // Serialize the following types as follows: | 22 // Serialize the following types as follows: |
18 // - primitives / null: unchanged | 23 // - primitives / null: unchanged |
19 // - lists: [ 'list', internal id, list of recursively serialized elements ] | 24 // - lists: [ 'list', internal id, list of recursively serialized elements ] |
20 // - maps: [ 'map', internal id, map of keys and recursively serialized value
s ] | 25 // - maps: [ 'map', internal id, map of keys and recursively serialized value
s ] |
21 // - send ports: [ 'sendport', type, isolate id, port id ] | 26 // - send ports: [ 'sendport', type, isolate id, port id ] |
22 // | 27 // |
23 // Note, internal id's are for cycle detection. | 28 // Note, internal id's are for cycle detection. |
24 function serialize(message) { | 29 function serialize(message) { |
25 var visited = []; | 30 var visited = []; |
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
208 var result = null; | 213 var result = null; |
209 var listener = function (e) { | 214 var listener = function (e) { |
210 result = JSON.parse(getPortSyncEventData(e)); | 215 result = JSON.parse(getPortSyncEventData(e)); |
211 }; | 216 }; |
212 window.addEventListener(source, listener, false); | 217 window.addEventListener(source, listener, false); |
213 dispatchEvent(target, [source, serialized]); | 218 dispatchEvent(target, [source, serialized]); |
214 window.removeEventListener(source, listener, false); | 219 window.removeEventListener(source, listener, false); |
215 return deserialize(result); | 220 return deserialize(result); |
216 } | 221 } |
217 })(); | 222 })(); |
| 223 |
| 224 (function() { |
| 225 // Proxy support for js.dart. |
| 226 |
| 227 var globalContext = window; |
| 228 |
| 229 // Table for local objects and functions that are proxied. |
| 230 function ProxiedObjectTable() { |
| 231 // Name for debugging. |
| 232 this.name = 'js-ref'; |
| 233 |
| 234 // Table from IDs to JS objects. |
| 235 this.map = {}; |
| 236 |
| 237 // Generator for new IDs. |
| 238 this._nextId = 0; |
| 239 |
| 240 // Ports for managing communication to proxies. |
| 241 this.port = new ReceivePortSync(); |
| 242 this.sendPort = this.port.toSendPort(); |
| 243 } |
| 244 |
| 245 // Number of valid IDs. This is the number of objects (global and local) |
| 246 // kept alive by this table. |
| 247 ProxiedObjectTable.prototype.count = function () { |
| 248 return Object.keys(this.map).length; |
| 249 } |
| 250 |
| 251 // Adds an object to the table and return an ID for serialization. |
| 252 ProxiedObjectTable.prototype.add = function (obj) { |
| 253 for (var ref in this.map) { |
| 254 var o = this.map[ref]; |
| 255 if (o === obj) { |
| 256 return ref; |
| 257 } |
| 258 } |
| 259 var ref = this.name + '-' + this._nextId++; |
| 260 this.map[ref] = obj; |
| 261 return ref; |
| 262 } |
| 263 |
| 264 // Gets the object or function corresponding to this ID. |
| 265 ProxiedObjectTable.prototype.get = function (id) { |
| 266 if (!this.map.hasOwnProperty(id)) { |
| 267 throw 'Proxy ' + id + ' has been invalidated.' |
| 268 } |
| 269 return this.map[id]; |
| 270 } |
| 271 |
| 272 ProxiedObjectTable.prototype._initialize = function () { |
| 273 // Configure this table's port to forward methods, getters, and setters |
| 274 // from the remote proxy to the local object. |
| 275 var table = this; |
| 276 |
| 277 this.port.receive(function (message) { |
| 278 // TODO(vsm): Support a mechanism to register a handler here. |
| 279 try { |
| 280 var receiver = table.get(message[0]); |
| 281 var member = message[1]; |
| 282 var kind = message[2]; |
| 283 var args = message[3].map(deserialize); |
| 284 if (kind == 'get') { |
| 285 // Getter. |
| 286 var field = member; |
| 287 if (field in receiver && args.length == 0) { |
| 288 return [ 'return', serialize(receiver[field]) ]; |
| 289 } |
| 290 } else if (kind == 'set') { |
| 291 // Setter. |
| 292 var field = member; |
| 293 if (args.length == 1) { |
| 294 return [ 'return', serialize(receiver[field] = args[0]) ]; |
| 295 } |
| 296 } else if (kind == 'hasProperty') { |
| 297 var field = member; |
| 298 return [ 'return', field in receiver ]; |
| 299 } else if (kind == 'apply') { |
| 300 // Direct function invocation. |
| 301 return [ 'return', |
| 302 serialize(receiver.apply(args[0], args.slice(1))) ]; |
| 303 } else if (member == '[]' && args.length == 1) { |
| 304 // Index getter. |
| 305 return [ 'return', serialize(receiver[args[0]]) ]; |
| 306 } else if (member == '[]=' && args.length == 2) { |
| 307 // Index setter. |
| 308 return [ 'return', serialize(receiver[args[0]] = args[1]) ]; |
| 309 } else { |
| 310 // Member function invocation. |
| 311 var f = receiver[member]; |
| 312 if (f) { |
| 313 var result = f.apply(receiver, args); |
| 314 return [ 'return', serialize(result) ]; |
| 315 } |
| 316 } |
| 317 return [ 'none' ]; |
| 318 } catch (e) { |
| 319 return [ 'throws', e.toString() ]; |
| 320 } |
| 321 }); |
| 322 } |
| 323 |
| 324 // Singleton for local proxied objects. |
| 325 var proxiedObjectTable = new ProxiedObjectTable(); |
| 326 proxiedObjectTable._initialize() |
| 327 |
| 328 // Type for remote proxies to Dart objects. |
| 329 function DartProxy(id, sendPort) { |
| 330 this.id = id; |
| 331 this.port = sendPort; |
| 332 } |
| 333 |
| 334 // Serializes JS types to SendPortSync format: |
| 335 // - primitives -> primitives |
| 336 // - sendport -> sendport |
| 337 // - Function -> [ 'funcref', function-id, sendport ] |
| 338 // - Object -> [ 'objref', object-id, sendport ] |
| 339 function serialize(message) { |
| 340 if (message == null) { |
| 341 return null; // Convert undefined to null. |
| 342 } else if (typeof(message) == 'string' || |
| 343 typeof(message) == 'number' || |
| 344 typeof(message) == 'boolean') { |
| 345 // Primitives are passed directly through. |
| 346 return message; |
| 347 } else if (message instanceof SendPortSync) { |
| 348 // Non-proxied objects are serialized. |
| 349 return message; |
| 350 } else if (typeof(message) == 'function') { |
| 351 if ('_dart_id' in message) { |
| 352 // Remote function proxy. |
| 353 var remoteId = message._dart_id; |
| 354 var remoteSendPort = message._dart_port; |
| 355 return [ 'funcref', remoteId, remoteSendPort ]; |
| 356 } else { |
| 357 // Local function proxy. |
| 358 return [ 'funcref', |
| 359 proxiedObjectTable.add(message), |
| 360 proxiedObjectTable.sendPort ]; |
| 361 } |
| 362 } else if (message instanceof DartProxy) { |
| 363 // Remote object proxy. |
| 364 return [ 'objref', message.id, message.port ]; |
| 365 } else { |
| 366 // Local object proxy. |
| 367 return [ 'objref', |
| 368 proxiedObjectTable.add(message), |
| 369 proxiedObjectTable.sendPort ]; |
| 370 } |
| 371 } |
| 372 |
| 373 function deserialize(message) { |
| 374 if (message == null) { |
| 375 return null; // Convert undefined to null. |
| 376 } else if (typeof(message) == 'string' || |
| 377 typeof(message) == 'number' || |
| 378 typeof(message) == 'boolean') { |
| 379 // Primitives are passed directly through. |
| 380 return message; |
| 381 } else if (message instanceof SendPortSync) { |
| 382 // Serialized type. |
| 383 return message; |
| 384 } |
| 385 var tag = message[0]; |
| 386 switch (tag) { |
| 387 case 'funcref': return deserializeFunction(message); |
| 388 case 'objref': return deserializeObject(message); |
| 389 } |
| 390 throw 'Unsupported serialized data: ' + message; |
| 391 } |
| 392 |
| 393 // Create a local function that forwards to the remote function. |
| 394 function deserializeFunction(message) { |
| 395 var id = message[1]; |
| 396 var port = message[2]; |
| 397 // TODO(vsm): Add a more robust check for a local SendPortSync. |
| 398 if ("receivePort" in port) { |
| 399 // Local function. |
| 400 return proxiedObjectTable.get(id); |
| 401 } else { |
| 402 // Remote function. Forward to its port. |
| 403 var f = function () { |
| 404 var args = Array.prototype.slice.apply(arguments); |
| 405 args.splice(0, 0, this); |
| 406 args = args.map(serialize); |
| 407 var result = port.callSync([id, '#call', args]); |
| 408 if (result[0] == 'throws') throw deserialize(result[1]); |
| 409 return deserialize(result[1]); |
| 410 }; |
| 411 // Cache the remote id and port. |
| 412 f._dart_id = id; |
| 413 f._dart_port = port; |
| 414 return f; |
| 415 } |
| 416 } |
| 417 |
| 418 // Creates a DartProxy to forwards to the remote object. |
| 419 function deserializeObject(message) { |
| 420 var id = message[1]; |
| 421 var port = message[2]; |
| 422 // TODO(vsm): Add a more robust check for a local SendPortSync. |
| 423 if ("receivePort" in port) { |
| 424 // Local object. |
| 425 return proxiedObjectTable.get(id); |
| 426 } else { |
| 427 // Remote object. |
| 428 return new DartProxy(id, port); |
| 429 } |
| 430 } |
| 431 |
| 432 // Remote handler to construct a new JavaScript object given its |
| 433 // serialized constructor and arguments. |
| 434 function construct(args) { |
| 435 args = args.map(deserialize); |
| 436 var constructor = args[0]; |
| 437 args = Array.prototype.slice.call(args, 1); |
| 438 |
| 439 // Until 10 args, the 'new' operator is used. With more arguments we use a |
| 440 // generic way that may not work, particularly when the constructor does not |
| 441 // have an "apply" method. |
| 442 var ret = null; |
| 443 if (args.length === 0) { |
| 444 ret = new constructor(); |
| 445 } else if (args.length === 1) { |
| 446 ret = new constructor(args[0]); |
| 447 } else if (args.length === 2) { |
| 448 ret = new constructor(args[0], args[1]); |
| 449 } else if (args.length === 3) { |
| 450 ret = new constructor(args[0], args[1], args[2]); |
| 451 } else if (args.length === 4) { |
| 452 ret = new constructor(args[0], args[1], args[2], args[3]); |
| 453 } else if (args.length === 5) { |
| 454 ret = new constructor(args[0], args[1], args[2], args[3], args[4]); |
| 455 } else if (args.length === 6) { |
| 456 ret = new constructor(args[0], args[1], args[2], args[3], args[4], |
| 457 args[5]); |
| 458 } else if (args.length === 7) { |
| 459 ret = new constructor(args[0], args[1], args[2], args[3], args[4], |
| 460 args[5], args[6]); |
| 461 } else if (args.length === 8) { |
| 462 ret = new constructor(args[0], args[1], args[2], args[3], args[4], |
| 463 args[5], args[6], args[7]); |
| 464 } else if (args.length === 9) { |
| 465 ret = new constructor(args[0], args[1], args[2], args[3], args[4], |
| 466 args[5], args[6], args[7], args[8]); |
| 467 } else if (args.length === 10) { |
| 468 ret = new constructor(args[0], args[1], args[2], args[3], args[4], |
| 469 args[5], args[6], args[7], args[8], args[9]); |
| 470 } else { |
| 471 // Dummy Type with correct constructor. |
| 472 var Type = function(){}; |
| 473 Type.prototype = constructor.prototype; |
| 474 |
| 475 // Create a new instance |
| 476 var instance = new Type(); |
| 477 |
| 478 // Call the original constructor. |
| 479 ret = constructor.apply(instance, args); |
| 480 ret = Object(ret) === ret ? ret : instance; |
| 481 } |
| 482 return serialize(ret); |
| 483 } |
| 484 |
| 485 // Remote handler to return the top-level JavaScript context. |
| 486 function context(data) { |
| 487 return serialize(globalContext); |
| 488 } |
| 489 |
| 490 // Return true if a JavaScript proxy is instance of a given type (instanceof). |
| 491 function proxyInstanceof(args) { |
| 492 var obj = deserialize(args[0]); |
| 493 var type = deserialize(args[1]); |
| 494 return obj instanceof type; |
| 495 } |
| 496 |
| 497 // Return true if a JavaScript proxy is instance of a given type (instanceof). |
| 498 function proxyDeleteProperty(args) { |
| 499 var obj = deserialize(args[0]); |
| 500 var member = deserialize(args[1]); |
| 501 delete obj[member]; |
| 502 } |
| 503 |
| 504 function proxyConvert(args) { |
| 505 return serialize(deserializeDataTree(args)); |
| 506 } |
| 507 |
| 508 function deserializeDataTree(data) { |
| 509 var type = data[0]; |
| 510 var value = data[1]; |
| 511 if (type === 'map') { |
| 512 var obj = {}; |
| 513 for (var i = 0; i < value.length; i++) { |
| 514 obj[value[i][0]] = deserializeDataTree(value[i][1]); |
| 515 } |
| 516 return obj; |
| 517 } else if (type === 'list') { |
| 518 var list = []; |
| 519 for (var i = 0; i < value.length; i++) { |
| 520 list.push(deserializeDataTree(value[i])); |
| 521 } |
| 522 return list; |
| 523 } else /* 'simple' */ { |
| 524 return deserialize(value); |
| 525 } |
| 526 } |
| 527 |
| 528 function makeGlobalPort(name, f) { |
| 529 var port = new ReceivePortSync(); |
| 530 port.receive(f); |
| 531 window.registerPort(name, port.toSendPort()); |
| 532 } |
| 533 |
| 534 makeGlobalPort('dart-js-context', context); |
| 535 makeGlobalPort('dart-js-create', construct); |
| 536 makeGlobalPort('dart-js-instanceof', proxyInstanceof); |
| 537 makeGlobalPort('dart-js-delete-property', proxyDeleteProperty); |
| 538 makeGlobalPort('dart-js-convert', proxyConvert); |
| 539 })(); |
OLD | NEW |