Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(98)

Side by Side Diff: extension/dart_proxy.js

Issue 11092092: Support compiling templates in the browser. Base URL: git@github.com:dart-lang/dart-web-components.git@master
Patch Set: Created 8 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « extension/dart.js ('k') | extension/manifest.json » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // We have to add this manually due to Chrome extension restrictions injecting arbitrary JS.
2 // TODO(jacobr): modify the jsinterop library so this is not required.
3 (function() {
4 // Proxy support
5
6 // Table for local objects and functions that are proxied.
7 // TODO(vsm): Merge into one.
8 function ProxiedReferenceTable(name) {
9 // Name for debugging.
10 this.name = name;
11
12 // Table from IDs to JS objects.
13 this.map = {};
14
15 // Generator for new IDs.
16 this._nextId = 0;
17
18 // Counter for deleted proxies.
19 this._deletedCount = 0;
20
21 // Flag for one-time initialization.
22 this._initialized = false;
23
24 // Ports for managing communication to proxies.
25 this.port = new ReceivePortSync();
26 this.sendPort = this.port.toSendPort();
27
28 // Set of IDs that are global.
29 // These will not be freed on an exitScope().
30 this.globalIds = {};
31
32 // Stack of scoped handles.
33 this.handleStack = [];
34
35 // Stack of active scopes where each value is represented by the size of
36 // the handleStack at the beginning of the scope. When an active scope
37 // is popped, the handleStack is restored to where it was when the
38 // scope was entered.
39 this.scopeIndices = [];
40 }
41
42 // Number of valid IDs. This is the number of objects (global and local)
43 // kept alive by this table.
44 ProxiedReferenceTable.prototype.count = function () {
45 return Object.keys(this.map).length;
46 }
47
48 // Number of total IDs ever allocated.
49 ProxiedReferenceTable.prototype.total = function () {
50 return this.count() + this._deletedCount;
51 }
52
53 // Adds an object to the table and return an ID for serialization.
54 ProxiedReferenceTable.prototype.add = function (obj) {
55 if (this.scopeIndices.length == 0) {
56 throw "Cannot allocate a proxy outside of a scope.";
57 }
58 // TODO(vsm): Cache refs for each obj?
59 var ref = this.name + '-' + this._nextId++;
60 this.handleStack.push(ref);
61 this.map[ref] = obj;
62 return ref;
63 }
64
65 ProxiedReferenceTable.prototype._initializeOnce = function () {
66 if (!this._initialized) {
67 this._initialize();
68 }
69 this._initialized = true;
70 }
71
72 // Overridable initialization on first use hook.
73 ProxiedReferenceTable.prototype._initialize = function () {}
74
75 // Enters a new scope for this table.
76 ProxiedReferenceTable.prototype.enterScope = function() {
77 this._initializeOnce();
78 this.scopeIndices.push(this.handleStack.length);
79 }
80
81 // Invalidates all non-global IDs in the current scope and
82 // exit the current scope.
83 ProxiedReferenceTable.prototype.exitScope = function() {
84 var start = this.scopeIndices.pop();
85 for (var i = start; i < this.handleStack.length; ++i) {
86 var key = this.handleStack[i];
87 if (!this.globalIds.hasOwnProperty(key)) {
88 delete this.map[this.handleStack[i]];
89 this._deletedCount++;
90 }
91 }
92 this.handleStack = this.handleStack.splice(0, start);
93 }
94
95 // Makes this ID globally scope. It must be explicitly invalidated.
96 ProxiedReferenceTable.prototype.globalize = function(id) {
97 this.globalIds[id] = true;
98 }
99
100 // Invalidates this ID, potentially freeing its corresponding object.
101 ProxiedReferenceTable.prototype.invalidate = function(id) {
102 var old = this.get(id);
103 delete this.globalIds[id];
104 delete this.map[id];
105 this._deletedCount++;
106 return old;
107 }
108
109 // Gets the object or function corresponding to this ID.
110 ProxiedReferenceTable.prototype.get = function (id) {
111 if (!this.map.hasOwnProperty(id)) {
112 throw 'Proxy ' + id + ' has been invalidated.'
113 }
114 return this.map[id];
115 }
116
117 // Subtype for managing function proxies.
118 function ProxiedFunctionTable() {}
119
120 ProxiedFunctionTable.prototype = new ProxiedReferenceTable('func-ref');
121
122 ProxiedFunctionTable.prototype._initialize = function () {
123 // Configure this table's port to invoke the corresponding function given
124 // its ID.
125 // TODO(vsm): Should we enter / exit a scope?
126 var table = this;
127
128 this.port.receive(function (message) {
129 var id = message[0];
130 var args = message[1].map(deserialize);
131 var f = table.get(id);
132 // TODO(vsm): Should we capture _this_ automatically?
133 return serialize(f.apply(null, args));
134 });
135 }
136
137 // The singleton table for proxied local functions.
138 var proxiedFunctionTable = new ProxiedFunctionTable();
139
140 // Subtype for proxied local objects.
141 function ProxiedObjectTable() {}
142
143 ProxiedObjectTable.prototype = new ProxiedReferenceTable('js-ref');
144
145 ProxiedObjectTable.prototype._initialize = function () {
146 // Configure this table's port to forward methods, getters, and setters
147 // from the remote proxy to the local object.
148 var table = this;
149
150 this.port.receive(function (message) {
151 // TODO(vsm): Support a mechanism to register a handler here.
152 var receiver = table.get(message[0]);
153 var method = message[1];
154 var args = message[2].map(deserialize);
155 if (method.indexOf("get:") == 0) {
156 // Getter.
157 var field = method.substring(4);
158 if (field in receiver && args.length == 0) {
159 return [ 'return', serialize(receiver[field]) ];
160 }
161 } else if (method.indexOf("set:") == 0) {
162 // Setter.
163 var field = method.substring(4);
164 if (args.length == 1) {
165 return [ 'return', serialize(receiver[field] = args[0]) ];
166 }
167 } else if (method == '[]' && args.length == 1) {
168 // Index getter.
169 return [ 'return', serialize(receiver[args[0]]) ];
170 } else {
171 var f = receiver[method];
172 if (f) {
173 try {
174 var result = f.apply(receiver, args);
175 return [ 'return', serialize(result) ];
176 } catch (e) {
177 return [ 'exception', serialize(e) ];
178 }
179 }
180 }
181 return [ 'none' ];
182 });
183 }
184
185 // Singleton for local proxied objects.
186 var proxiedObjectTable = new ProxiedObjectTable();
187
188 // DOM element serialization code.
189 var _localNextElementId = 0;
190 var _DART_ID = 'data-dart_id';
191 var _DART_TEMPORARY_ATTACHED = 'data-dart_temporary_attached';
192
193 function serializeElement(e) {
194 // TODO(vsm): Use an isolate-specific id.
195 var id;
196 if (e.hasAttribute(_DART_ID)) {
197 id = e.getAttribute(_DART_ID);
198 } else {
199 id = (_localNextElementId++).toString();
200 e.setAttribute(_DART_ID, id);
201 }
202 if (e !== document.documentElement) {
203 // Element must be attached to DOM to be retrieve in js part.
204 // Attach top unattached parent to avoid detaching parent of "e" when
205 // appending "e" directly to document. We keep count of elements
206 // temporarily attached to prevent detaching top unattached parent to
207 // early. This count is equals to the length of _DART_TEMPORARY_ATTACHED
208 // attribute. There could be other elements to serialize having the same
209 // top unattached parent.
210 var top = e;
211 while (true) {
212 if (top.hasAttribute(_DART_TEMPORARY_ATTACHED)) {
213 var oldValue = top.getAttribute(_DART_TEMPORARY_ATTACHED);
214 var newValue = oldValue + "a";
215 top.setAttribute(_DART_TEMPORARY_ATTACHED, newValue);
216 break;
217 }
218 if (top.parentNode == null) {
219 top.setAttribute(_DART_TEMPORARY_ATTACHED, "a");
220 document.documentElement.appendChild(top);
221 break;
222 }
223 if (top.parentNode === document.documentElement) {
224 // e was already attached to dom
225 break;
226 }
227 top = top.parentNode;
228 }
229 }
230 return id;
231 }
232
233 function deserializeElement(id) {
234 // TODO(vsm): Clear the attribute.
235 var list = document.querySelectorAll('[' + _DART_ID + '="' + id + '"]');
236
237 if (list.length > 1) throw 'Non unique ID: ' + id;
238 if (list.length == 0) {
239 throw 'Element must be attached to the document: ' + id;
240 }
241 var e = list[0];
242 if (e !== document.documentElement) {
243 // detach temporary attached element
244 var top = e;
245 while (true) {
246 if (top.hasAttribute(_DART_TEMPORARY_ATTACHED)) {
247 var oldValue = top.getAttribute(_DART_TEMPORARY_ATTACHED);
248 var newValue = oldValue.substring(1);
249 top.setAttribute(_DART_TEMPORARY_ATTACHED, newValue);
250 // detach top only if no more elements have to be unserialized
251 if (top.getAttribute(_DART_TEMPORARY_ATTACHED).length === 0) {
252 top.removeAttribute(_DART_TEMPORARY_ATTACHED);
253 document.documentElement.removeChild(top);
254 }
255 break;
256 }
257 if (top.parentNode === document.documentElement) {
258 // e was already attached to dom
259 break;
260 }
261 top = top.parentNode;
262 }
263 }
264 return e;
265 }
266
267
268 // Type for remote proxies to Dart objects.
269 function DartProxy(id, sendPort) {
270 this.id = id;
271 this.port = sendPort;
272 }
273
274 // Serializes JS types to SendPortSync format:
275 // - primitives -> primitives
276 // - sendport -> sendport
277 // - DOM element -> [ 'domref', element-id ]
278 // - Function -> [ 'funcref', function-id, sendport ]
279 // - Object -> [ 'objref', object-id, sendport ]
280 function serialize(message) {
281 if (message == null) {
282 return null; // Convert undefined to null.
283 } else if (typeof(message) == 'string' ||
284 typeof(message) == 'number' ||
285 typeof(message) == 'boolean') {
286 // Primitives are passed directly through.
287 return message;
288 } else if (message instanceof SendPortSync) {
289 // Non-proxied objects are serialized.
290 return message;
291 } else if (message instanceof Element) {
292 return [ 'domref', serializeElement(message) ];
293 } else if (typeof(message) == 'function') {
294 if ('_dart_id' in message) {
295 // Remote function proxy.
296 var remoteId = message._dart_id;
297 var remoteSendPort = message._dart_port;
298 return [ 'funcref', remoteId, remoteSendPort ];
299 } else {
300 // Local function proxy.
301 return [ 'funcref',
302 proxiedFunctionTable.add(message),
303 proxiedFunctionTable.sendPort ];
304 }
305 } else if (message instanceof DartProxy) {
306 // Remote object proxy.
307 return [ 'objref', message.id, message.port ];
308 } else {
309 // Local object proxy.
310 return [ 'objref',
311 proxiedObjectTable.add(message),
312 proxiedObjectTable.sendPort ];
313 }
314 }
315
316 function deserialize(message) {
317 if (message == null) {
318 return null; // Convert undefined to null.
319 } else if (typeof(message) == 'string' ||
320 typeof(message) == 'number' ||
321 typeof(message) == 'boolean') {
322 // Primitives are passed directly through.
323 return message;
324 } else if (message instanceof SendPortSync) {
325 // Serialized type.
326 return message;
327 }
328 var tag = message[0];
329 switch (tag) {
330 case 'funcref': return deserializeFunction(message);
331 case 'objref': return deserializeObject(message);
332 case 'domref': return deserializeElement(message[1]);
333 }
334 throw 'Unsupported serialized data: ' + message;
335 }
336
337 // Create a local function that forwards to the remote function.
338 function deserializeFunction(message) {
339 var id = message[1];
340 var port = message[2];
341 // TODO(vsm): Add a more robust check for a local SendPortSync.
342 if ("receivePort" in port) {
343 // Local function.
344 return proxiedFunctionTable.get(id);
345 } else {
346 // Remote function. Forward to its port.
347 var f = function () {
348 enterScope();
349 try {
350 var args = Array.prototype.slice.apply(arguments).map(serialize);
351 var result = port.callSync([id, args]);
352 return deserialize(result);
353 } finally {
354 exitScope();
355 }
356 };
357 // Cache the remote id and port.
358 f._dart_id = id;
359 f._dart_port = port;
360 return f;
361 }
362 }
363
364 // Creates a DartProxy to forwards to the remote object.
365 function deserializeObject(message) {
366 var id = message[1];
367 var port = message[2];
368 // TODO(vsm): Add a more robust check for a local SendPortSync.
369 if ("receivePort" in port) {
370 // Local object.
371 return proxiedObjectTable.get(id);
372 } else {
373 // Remote object.
374 return new DartProxy(id, port);
375 }
376 }
377
378 // Remote handler to construct a new JavaScript object given its
379 // serialized constructor and arguments.
380 function construct(args) {
381 args = args.map(deserialize);
382 var constructor = args[0];
383 args = Array.prototype.slice.call(args, 1);
384
385 // Dummy Type with correct constructor.
386 var Type = function(){};
387 Type.prototype = constructor.prototype;
388
389 // Create a new instance
390 var instance = new Type();
391
392 // Call the original constructor.
393 var ret = constructor.apply(instance, args);
394
395 return serialize(Object(ret) === ret ? ret : instance);
396 }
397
398 // Remote handler to evaluate a string in JavaScript and return a serialized
399 // result.
400 function evaluate(data) {
401 return serialize(eval(deserialize(data)));
402 }
403
404 // Remote handler for debugging.
405 function debug() {
406 var live = proxiedObjectTable.count() + proxiedFunctionTable.count();
407 var total = proxiedObjectTable.total() + proxiedFunctionTable.total();
408 return 'JS objects Live : ' + live +
409 ' (out of ' + total + ' ever allocated).';
410 }
411
412 function makeGlobalPort(name, f) {
413 var port = new ReceivePortSync();
414 port.receive(f);
415 window.registerPort(name, port.toSendPort());
416 }
417
418 // Enters a new scope in the JavaScript context.
419 function enterScope() {
420 proxiedObjectTable.enterScope();
421 proxiedFunctionTable.enterScope();
422 }
423
424 // Exits the current scope (and invalidate local IDs) in the JavaScript
425 // context.
426 function exitScope() {
427 proxiedFunctionTable.exitScope();
428 proxiedObjectTable.exitScope();
429 }
430
431 makeGlobalPort('dart-js-evaluate', evaluate);
432 makeGlobalPort('dart-js-create', construct);
433 makeGlobalPort('dart-js-debug', debug);
434 makeGlobalPort('dart-js-enter-scope', enterScope);
435 makeGlobalPort('dart-js-exit-scope', exitScope);
436 makeGlobalPort('dart-js-globalize', function(data) {
437 if (data[0] == "objref") return proxiedObjectTable.globalize(data[1]);
438 // TODO(vsm): Do we ever need to globalize functions?
439 throw 'Illegal type: ' + data[0];
440 });
441 makeGlobalPort('dart-js-invalidate', function(data) {
442 if (data[0] == "objref") return proxiedObjectTable.invalidate(data[1]);
443 // TODO(vsm): Do we ever need to globalize functions?
444 throw 'Illegal type: ' + data[0];
445 });
446 })();
OLDNEW
« no previous file with comments | « extension/dart.js ('k') | extension/manifest.json » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698