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