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 |