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 |