OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
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. | |
4 | |
5 // THIS FILE IS AUTO GENERATED. PLEASE DO NOT EDIT. | |
6 | |
7 // TODO(vsm): Move this file once we determine where assets should go. See | |
8 // http://dartbug.com/6101. | |
9 | |
10 (function() { | |
11 // Proxy support for js.dart. | |
12 | |
13 var globalContext = window; | |
14 | |
15 // Support for binding the receiver (this) in proxied functions. | |
16 function bindIfFunction(f, _this) { | |
17 if (typeof(f) != "function") { | |
18 return f; | |
19 } else { | |
20 return new BoundFunction(_this, f); | |
21 } | |
22 } | |
23 | |
24 function unbind(obj) { | |
25 if (obj instanceof BoundFunction) { | |
26 return obj.object; | |
27 } else { | |
28 return obj; | |
29 } | |
30 } | |
31 | |
32 function getBoundThis(obj) { | |
33 if (obj instanceof BoundFunction) { | |
34 return obj._this; | |
35 } else { | |
36 return globalContext; | |
37 } | |
38 } | |
39 | |
40 function BoundFunction(_this, object) { | |
41 this._this = _this; | |
42 this.object = object; | |
43 } | |
44 | |
45 // Table for local objects and functions that are proxied. | |
46 function ProxiedObjectTable() { | |
47 // Name for debugging. | |
48 this.name = 'js-ref'; | |
49 | |
50 // Table from IDs to JS objects. | |
51 this.map = {}; | |
52 | |
53 // Generator for new IDs. | |
54 this._nextId = 0; | |
55 | |
56 // Counter for deleted proxies. | |
57 this._deletedCount = 0; | |
58 | |
59 // Flag for one-time initialization. | |
60 this._initialized = false; | |
61 | |
62 // Ports for managing communication to proxies. | |
63 this.port = new ReceivePortSync(); | |
64 this.sendPort = this.port.toSendPort(); | |
65 | |
66 // Set of IDs that are global. | |
67 // These will not be freed on an exitScope(). | |
68 this.globalIds = {}; | |
69 | |
70 // Stack of scoped handles. | |
71 this.handleStack = []; | |
72 | |
73 // Stack of active scopes where each value is represented by the size of | |
74 // the handleStack at the beginning of the scope. When an active scope | |
75 // is popped, the handleStack is restored to where it was when the | |
76 // scope was entered. | |
77 this.scopeIndices = []; | |
78 } | |
79 | |
80 // Number of valid IDs. This is the number of objects (global and local) | |
81 // kept alive by this table. | |
82 ProxiedObjectTable.prototype.count = function () { | |
83 return Object.keys(this.map).length; | |
84 } | |
85 | |
86 // Number of total IDs ever allocated. | |
87 ProxiedObjectTable.prototype.total = function () { | |
88 return this.count() + this._deletedCount; | |
89 } | |
90 | |
91 // Adds an object to the table and return an ID for serialization. | |
92 ProxiedObjectTable.prototype.add = function (obj) { | |
93 if (this.scopeIndices.length == 0) { | |
94 throw "Cannot allocate a proxy outside of a scope."; | |
95 } | |
96 // TODO(vsm): Cache refs for each obj? | |
97 var ref = this.name + '-' + this._nextId++; | |
98 this.handleStack.push(ref); | |
99 this.map[ref] = obj; | |
100 return ref; | |
101 } | |
102 | |
103 ProxiedObjectTable.prototype._initializeOnce = function () { | |
104 if (!this._initialized) { | |
105 this._initialize(); | |
106 this._initialized = true; | |
107 } | |
108 } | |
109 | |
110 // Enters a new scope for this table. | |
111 ProxiedObjectTable.prototype.enterScope = function() { | |
112 this._initializeOnce(); | |
113 this.scopeIndices.push(this.handleStack.length); | |
114 } | |
115 | |
116 // Invalidates all non-global IDs in the current scope and | |
117 // exit the current scope. | |
118 ProxiedObjectTable.prototype.exitScope = function() { | |
119 var start = this.scopeIndices.pop(); | |
120 for (var i = start; i < this.handleStack.length; ++i) { | |
121 var key = this.handleStack[i]; | |
122 if (!this.globalIds.hasOwnProperty(key)) { | |
123 delete this.map[this.handleStack[i]]; | |
124 this._deletedCount++; | |
125 } | |
126 } | |
127 this.handleStack = this.handleStack.splice(0, start); | |
128 } | |
129 | |
130 // Makes this ID globally scope. It must be explicitly invalidated. | |
131 ProxiedObjectTable.prototype.globalize = function(id) { | |
132 this.globalIds[id] = true; | |
133 } | |
134 | |
135 // Invalidates this ID, potentially freeing its corresponding object. | |
136 ProxiedObjectTable.prototype.invalidate = function(id) { | |
137 var old = this.get(id); | |
138 delete this.globalIds[id]; | |
139 delete this.map[id]; | |
140 this._deletedCount++; | |
141 } | |
142 | |
143 // Gets the object or function corresponding to this ID. | |
144 ProxiedObjectTable.prototype.get = function (id) { | |
145 if (!this.map.hasOwnProperty(id)) { | |
146 throw 'Proxy ' + id + ' has been invalidated.' | |
147 } | |
148 return this.map[id]; | |
149 } | |
150 | |
151 ProxiedObjectTable.prototype._initialize = function () { | |
152 // Configure this table's port to forward methods, getters, and setters | |
153 // from the remote proxy to the local object. | |
154 var table = this; | |
155 | |
156 this.port.receive(function (message) { | |
157 // TODO(vsm): Support a mechanism to register a handler here. | |
158 try { | |
159 var object = table.get(message[0]); | |
160 var receiver = unbind(object); | |
161 var member = message[1]; | |
162 var kind = message[2]; | |
163 var args = message[3].map(deserialize); | |
164 if (kind == 'get') { | |
165 // Getter. | |
166 var field = member; | |
167 if (field in receiver && args.length == 0) { | |
168 var result = bindIfFunction(receiver[field], receiver); | |
169 return [ 'return', serialize(result) ]; | |
170 } | |
171 } else if (kind == 'set') { | |
172 // Setter. | |
173 var field = member; | |
174 if (args.length == 1) { | |
175 return [ 'return', serialize(receiver[field] = args[0]) ]; | |
176 } | |
177 } else if (kind == 'apply') { | |
178 // Direct function invocation. | |
179 var _this = getBoundThis(object); | |
180 return [ 'return', serialize(receiver.apply(_this, args)) ]; | |
181 } else if (member == '[]' && args.length == 1) { | |
182 // Index getter. | |
183 var result = bindIfFunction(receiver[args[0]], receiver); | |
184 return [ 'return', serialize(result) ]; | |
185 } else if (member == '[]=' && args.length == 2) { | |
186 // Index setter. | |
187 return [ 'return', serialize(receiver[args[0]] = args[1]) ]; | |
188 } else { | |
189 // Member function invocation. | |
190 var f = receiver[member]; | |
191 if (f) { | |
192 var result = f.apply(receiver, args); | |
193 return [ 'return', serialize(result) ]; | |
194 } | |
195 } | |
196 return [ 'none' ]; | |
197 } catch (e) { | |
198 return [ 'throws', e.toString() ]; | |
199 } | |
200 }); | |
201 } | |
202 | |
203 // Singleton for local proxied objects. | |
204 var proxiedObjectTable = new ProxiedObjectTable(); | |
205 | |
206 // DOM element serialization code. | |
207 var _localNextElementId = 0; | |
208 var _DART_ID = 'data-dart_id'; | |
209 var _DART_TEMPORARY_ATTACHED = 'data-dart_temporary_attached'; | |
210 | |
211 function serializeElement(e) { | |
212 // TODO(vsm): Use an isolate-specific id. | |
213 var id; | |
214 if (e.hasAttribute(_DART_ID)) { | |
215 id = e.getAttribute(_DART_ID); | |
216 } else { | |
217 id = (_localNextElementId++).toString(); | |
218 e.setAttribute(_DART_ID, id); | |
219 } | |
220 if (e !== document.documentElement) { | |
221 // Element must be attached to DOM to be retrieve in js part. | |
222 // Attach top unattached parent to avoid detaching parent of "e" when | |
223 // appending "e" directly to document. We keep count of elements | |
224 // temporarily attached to prevent detaching top unattached parent to | |
225 // early. This count is equals to the length of _DART_TEMPORARY_ATTACHED | |
226 // attribute. There could be other elements to serialize having the same | |
227 // top unattached parent. | |
228 var top = e; | |
229 while (true) { | |
230 if (top.hasAttribute(_DART_TEMPORARY_ATTACHED)) { | |
231 var oldValue = top.getAttribute(_DART_TEMPORARY_ATTACHED); | |
232 var newValue = oldValue + "a"; | |
233 top.setAttribute(_DART_TEMPORARY_ATTACHED, newValue); | |
234 break; | |
235 } | |
236 if (top.parentNode == null) { | |
237 top.setAttribute(_DART_TEMPORARY_ATTACHED, "a"); | |
238 document.documentElement.appendChild(top); | |
239 break; | |
240 } | |
241 if (top.parentNode === document.documentElement) { | |
242 // e was already attached to dom | |
243 break; | |
244 } | |
245 top = top.parentNode; | |
246 } | |
247 } | |
248 return id; | |
249 } | |
250 | |
251 function deserializeElement(id) { | |
252 // TODO(vsm): Clear the attribute. | |
253 var list = document.querySelectorAll('[' + _DART_ID + '="' + id + '"]'); | |
254 | |
255 if (list.length > 1) throw 'Non unique ID: ' + id; | |
256 if (list.length == 0) { | |
257 throw 'Element must be attached to the document: ' + id; | |
258 } | |
259 var e = list[0]; | |
260 if (e !== document.documentElement) { | |
261 // detach temporary attached element | |
262 var top = e; | |
263 while (true) { | |
264 if (top.hasAttribute(_DART_TEMPORARY_ATTACHED)) { | |
265 var oldValue = top.getAttribute(_DART_TEMPORARY_ATTACHED); | |
266 var newValue = oldValue.substring(1); | |
267 top.setAttribute(_DART_TEMPORARY_ATTACHED, newValue); | |
268 // detach top only if no more elements have to be unserialized | |
269 if (top.getAttribute(_DART_TEMPORARY_ATTACHED).length === 0) { | |
270 top.removeAttribute(_DART_TEMPORARY_ATTACHED); | |
271 document.documentElement.removeChild(top); | |
272 } | |
273 break; | |
274 } | |
275 if (top.parentNode === document.documentElement) { | |
276 // e was already attached to dom | |
277 break; | |
278 } | |
279 top = top.parentNode; | |
280 } | |
281 } | |
282 return e; | |
283 } | |
284 | |
285 | |
286 // Type for remote proxies to Dart objects. | |
287 function DartProxy(id, sendPort) { | |
288 this.id = id; | |
289 this.port = sendPort; | |
290 } | |
291 | |
292 // Serializes JS types to SendPortSync format: | |
293 // - primitives -> primitives | |
294 // - sendport -> sendport | |
295 // - DOM element -> [ 'domref', element-id ] | |
296 // - Function -> [ 'funcref', function-id, sendport ] | |
297 // - Object -> [ 'objref', object-id, sendport ] | |
298 function serialize(message) { | |
299 if (message == null) { | |
300 return null; // Convert undefined to null. | |
301 } else if (typeof(message) == 'string' || | |
302 typeof(message) == 'number' || | |
303 typeof(message) == 'boolean') { | |
304 // Primitives are passed directly through. | |
305 return message; | |
306 } else if (message instanceof SendPortSync) { | |
307 // Non-proxied objects are serialized. | |
308 return message; | |
309 } else if (message instanceof Element && | |
310 (message.ownerDocument == null || message.ownerDocument == document)) { | |
311 return [ 'domref', serializeElement(message) ]; | |
312 } else if (message instanceof BoundFunction && | |
313 typeof(message.object) == 'function') { | |
314 // Local function proxy. | |
315 return [ 'funcref', | |
316 proxiedObjectTable.add(message), | |
317 proxiedObjectTable.sendPort ]; | |
318 } else if (typeof(message) == 'function') { | |
319 if ('_dart_id' in message) { | |
320 // Remote function proxy. | |
321 var remoteId = message._dart_id; | |
322 var remoteSendPort = message._dart_port; | |
323 return [ 'funcref', remoteId, remoteSendPort ]; | |
324 } else { | |
325 // Local function proxy. | |
326 return [ 'funcref', | |
327 proxiedObjectTable.add(message), | |
328 proxiedObjectTable.sendPort ]; | |
329 } | |
330 } else if (message instanceof DartProxy) { | |
331 // Remote object proxy. | |
332 return [ 'objref', message.id, message.port ]; | |
333 } else { | |
334 // Local object proxy. | |
335 return [ 'objref', | |
336 proxiedObjectTable.add(message), | |
337 proxiedObjectTable.sendPort ]; | |
338 } | |
339 } | |
340 | |
341 function deserialize(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 // Serialized type. | |
351 return message; | |
352 } | |
353 var tag = message[0]; | |
354 switch (tag) { | |
355 case 'funcref': return deserializeFunction(message); | |
356 case 'objref': return deserializeObject(message); | |
357 case 'domref': return deserializeElement(message[1]); | |
358 } | |
359 throw 'Unsupported serialized data: ' + message; | |
360 } | |
361 | |
362 // Create a local function that forwards to the remote function. | |
363 function deserializeFunction(message) { | |
364 var id = message[1]; | |
365 var port = message[2]; | |
366 // TODO(vsm): Add a more robust check for a local SendPortSync. | |
367 if ("receivePort" in port) { | |
368 // Local function. | |
369 return unbind(proxiedObjectTable.get(id)); | |
370 } else { | |
371 // Remote function. Forward to its port. | |
372 var f = function () { | |
373 var depth = enterScope(); | |
374 try { | |
375 var args = Array.prototype.slice.apply(arguments); | |
376 args.splice(0, 0, this); | |
377 args = args.map(serialize); | |
378 var result = port.callSync([id, '#call', args]); | |
379 if (result[0] == 'throws') throw deserialize(result[1]); | |
380 return deserialize(result[1]); | |
381 } finally { | |
382 exitScope(depth); | |
383 } | |
384 }; | |
385 // Cache the remote id and port. | |
386 f._dart_id = id; | |
387 f._dart_port = port; | |
388 return f; | |
389 } | |
390 } | |
391 | |
392 // Creates a DartProxy to forwards to the remote object. | |
393 function deserializeObject(message) { | |
394 var id = message[1]; | |
395 var port = message[2]; | |
396 // TODO(vsm): Add a more robust check for a local SendPortSync. | |
397 if ("receivePort" in port) { | |
398 // Local object. | |
399 return proxiedObjectTable.get(id); | |
400 } else { | |
401 // Remote object. | |
402 return new DartProxy(id, port); | |
403 } | |
404 } | |
405 | |
406 // Remote handler to construct a new JavaScript object given its | |
407 // serialized constructor and arguments. | |
408 function construct(args) { | |
409 args = args.map(deserialize); | |
410 var constructor = unbind(args[0]); | |
411 args = Array.prototype.slice.call(args, 1); | |
412 | |
413 // Until 10 args, the 'new' operator is used. With more arguments we use a | |
414 // generic way that may not work, particulary when the constructor does not | |
415 // have an "apply" method. | |
416 var ret = null; | |
417 if (args.length === 0) { | |
418 ret = new constructor(); | |
419 } else if (args.length === 1) { | |
420 ret = new constructor(args[0]); | |
421 } else if (args.length === 2) { | |
422 ret = new constructor(args[0], args[1]); | |
423 } else if (args.length === 3) { | |
424 ret = new constructor(args[0], args[1], args[2]); | |
425 } else if (args.length === 4) { | |
426 ret = new constructor(args[0], args[1], args[2], args[3]); | |
427 } else if (args.length === 5) { | |
428 ret = new constructor(args[0], args[1], args[2], args[3], args[4]); | |
429 } else if (args.length === 6) { | |
430 ret = new constructor(args[0], args[1], args[2], args[3], args[4], | |
431 args[5]); | |
432 } else if (args.length === 7) { | |
433 ret = new constructor(args[0], args[1], args[2], args[3], args[4], | |
434 args[5], args[6]); | |
435 } else if (args.length === 8) { | |
436 ret = new constructor(args[0], args[1], args[2], args[3], args[4], | |
437 args[5], args[6], args[7]); | |
438 } else if (args.length === 9) { | |
439 ret = new constructor(args[0], args[1], args[2], args[3], args[4], | |
440 args[5], args[6], args[7], args[8]); | |
441 } else if (args.length === 10) { | |
442 ret = new constructor(args[0], args[1], args[2], args[3], args[4], | |
443 args[5], args[6], args[7], args[8], args[9]); | |
444 } else { | |
445 // Dummy Type with correct constructor. | |
446 var Type = function(){}; | |
447 Type.prototype = constructor.prototype; | |
448 | |
449 // Create a new instance | |
450 var instance = new Type(); | |
451 | |
452 // Call the original constructor. | |
453 ret = constructor.apply(instance, args); | |
454 ret = Object(ret) === ret ? ret : instance; | |
455 } | |
456 return serialize(ret); | |
457 } | |
458 | |
459 // Remote handler to return the top-level JavaScript context. | |
460 function context(data) { | |
461 return serialize(globalContext); | |
462 } | |
463 | |
464 // Remote handler to track number of live / allocated proxies. | |
465 function proxyCount() { | |
466 var live = proxiedObjectTable.count(); | |
467 var total = proxiedObjectTable.total(); | |
468 return [live, total]; | |
469 } | |
470 | |
471 // Return true if two JavaScript proxies are equal (==). | |
472 function proxyEquals(args) { | |
473 return deserialize(args[0]) == deserialize(args[1]); | |
474 } | |
475 | |
476 // Return true if a JavaScript proxy is instance of a given type (instanceof). | |
477 function proxyInstanceof(args) { | |
478 var obj = unbind(deserialize(args[0])); | |
479 var type = unbind(deserialize(args[1])); | |
480 return obj instanceof type; | |
481 } | |
482 | |
483 // Return true if a JavaScript proxy has a given property. | |
484 function proxyHasProperty(args) { | |
485 var obj = unbind(deserialize(args[0])); | |
486 var member = unbind(deserialize(args[1])); | |
487 return member in obj; | |
488 } | |
489 | |
490 // Delete a given property of object. | |
491 function proxyDeleteProperty(args) { | |
492 var obj = unbind(deserialize(args[0])); | |
493 var member = unbind(deserialize(args[1])); | |
494 delete obj[member]; | |
495 } | |
496 | |
497 function proxyConvert(args) { | |
498 return serialize(deserializeDataTree(args)); | |
499 } | |
500 | |
501 function deserializeDataTree(data) { | |
502 var type = data[0]; | |
503 var value = data[1]; | |
504 if (type === 'map') { | |
505 var obj = {}; | |
506 for (var i = 0; i < value.length; i++) { | |
507 obj[value[i][0]] = deserializeDataTree(value[i][1]); | |
508 } | |
509 return obj; | |
510 } else if (type === 'list') { | |
511 var list = []; | |
512 for (var i = 0; i < value.length; i++) { | |
513 list.push(deserializeDataTree(value[i])); | |
514 } | |
515 return list; | |
516 } else /* 'simple' */ { | |
517 return deserialize(value); | |
518 } | |
519 } | |
520 | |
521 function makeGlobalPort(name, f) { | |
522 var port = new ReceivePortSync(); | |
523 port.receive(f); | |
524 window.registerPort(name, port.toSendPort()); | |
525 } | |
526 | |
527 // Enters a new scope in the JavaScript context. | |
528 function enterJavaScriptScope() { | |
529 proxiedObjectTable.enterScope(); | |
530 } | |
531 | |
532 // Enters a new scope in both the JavaScript and Dart context. | |
533 var _dartEnterScopePort = null; | |
534 function enterScope() { | |
535 enterJavaScriptScope(); | |
536 if (!_dartEnterScopePort) { | |
537 _dartEnterScopePort = window.lookupPort('js-dart-interop-enter-scope'); | |
538 } | |
539 return _dartEnterScopePort.callSync([]); | |
540 } | |
541 | |
542 // Exits the current scope (and invalidate local IDs) in the JavaScript | |
543 // context. | |
544 function exitJavaScriptScope() { | |
545 proxiedObjectTable.exitScope(); | |
546 } | |
547 | |
548 // Exits the current scope in both the JavaScript and Dart context. | |
549 var _dartExitScopePort = null; | |
550 function exitScope(depth) { | |
551 exitJavaScriptScope(); | |
552 if (!_dartExitScopePort) { | |
553 _dartExitScopePort = window.lookupPort('js-dart-interop-exit-scope'); | |
554 } | |
555 return _dartExitScopePort.callSync([ depth ]); | |
556 } | |
557 | |
558 makeGlobalPort('dart-js-interop-context', context); | |
559 makeGlobalPort('dart-js-interop-create', construct); | |
560 makeGlobalPort('dart-js-interop-proxy-count', proxyCount); | |
561 makeGlobalPort('dart-js-interop-equals', proxyEquals); | |
562 makeGlobalPort('dart-js-interop-instanceof', proxyInstanceof); | |
563 makeGlobalPort('dart-js-interop-has-property', proxyHasProperty); | |
564 makeGlobalPort('dart-js-interop-delete-property', proxyDeleteProperty); | |
565 makeGlobalPort('dart-js-interop-convert', proxyConvert); | |
566 makeGlobalPort('dart-js-interop-enter-scope', enterJavaScriptScope); | |
567 makeGlobalPort('dart-js-interop-exit-scope', exitJavaScriptScope); | |
568 makeGlobalPort('dart-js-interop-globalize', function(data) { | |
569 if (data[0] == "objref" || data[0] == "funcref") return proxiedObjectTable.g
lobalize(data[1]); | |
570 throw 'Illegal type: ' + data[0]; | |
571 }); | |
572 makeGlobalPort('dart-js-interop-invalidate', function(data) { | |
573 if (data[0] == "objref" || data[0] == "funcref") return proxiedObjectTable.i
nvalidate(data[1]); | |
574 throw 'Illegal type: ' + data[0]; | |
575 }); | |
576 })(); | |
OLD | NEW |