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

Side by Side Diff: extension/dart_proxy.js

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

Powered by Google App Engine
This is Rietveld 408576698