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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « extension/dart.js ('k') | extension/manifest.json » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: extension/dart_proxy.js
diff --git a/extension/dart_proxy.js b/extension/dart_proxy.js
new file mode 100644
index 0000000000000000000000000000000000000000..0cbd115d0592aa22416cd89aa772be5413218380
--- /dev/null
+++ b/extension/dart_proxy.js
@@ -0,0 +1,446 @@
+ // We have to add this manually due to Chrome extension restrictions injecting arbitrary JS.
+ // TODO(jacobr): modify the jsinterop library so this is not required.
+ (function() {
+ // Proxy support
+
+ // Table for local objects and functions that are proxied.
+ // TODO(vsm): Merge into one.
+ function ProxiedReferenceTable(name) {
+ // Name for debugging.
+ this.name = name;
+
+ // Table from IDs to JS objects.
+ this.map = {};
+
+ // Generator for new IDs.
+ this._nextId = 0;
+
+ // Counter for deleted proxies.
+ this._deletedCount = 0;
+
+ // Flag for one-time initialization.
+ this._initialized = false;
+
+ // Ports for managing communication to proxies.
+ this.port = new ReceivePortSync();
+ this.sendPort = this.port.toSendPort();
+
+ // Set of IDs that are global.
+ // These will not be freed on an exitScope().
+ this.globalIds = {};
+
+ // Stack of scoped handles.
+ this.handleStack = [];
+
+ // Stack of active scopes where each value is represented by the size of
+ // the handleStack at the beginning of the scope. When an active scope
+ // is popped, the handleStack is restored to where it was when the
+ // scope was entered.
+ this.scopeIndices = [];
+ }
+
+ // Number of valid IDs. This is the number of objects (global and local)
+ // kept alive by this table.
+ ProxiedReferenceTable.prototype.count = function () {
+ return Object.keys(this.map).length;
+ }
+
+ // Number of total IDs ever allocated.
+ ProxiedReferenceTable.prototype.total = function () {
+ return this.count() + this._deletedCount;
+ }
+
+ // Adds an object to the table and return an ID for serialization.
+ ProxiedReferenceTable.prototype.add = function (obj) {
+ if (this.scopeIndices.length == 0) {
+ throw "Cannot allocate a proxy outside of a scope.";
+ }
+ // TODO(vsm): Cache refs for each obj?
+ var ref = this.name + '-' + this._nextId++;
+ this.handleStack.push(ref);
+ this.map[ref] = obj;
+ return ref;
+ }
+
+ ProxiedReferenceTable.prototype._initializeOnce = function () {
+ if (!this._initialized) {
+ this._initialize();
+ }
+ this._initialized = true;
+ }
+
+ // Overridable initialization on first use hook.
+ ProxiedReferenceTable.prototype._initialize = function () {}
+
+ // Enters a new scope for this table.
+ ProxiedReferenceTable.prototype.enterScope = function() {
+ this._initializeOnce();
+ this.scopeIndices.push(this.handleStack.length);
+ }
+
+ // Invalidates all non-global IDs in the current scope and
+ // exit the current scope.
+ ProxiedReferenceTable.prototype.exitScope = function() {
+ var start = this.scopeIndices.pop();
+ for (var i = start; i < this.handleStack.length; ++i) {
+ var key = this.handleStack[i];
+ if (!this.globalIds.hasOwnProperty(key)) {
+ delete this.map[this.handleStack[i]];
+ this._deletedCount++;
+ }
+ }
+ this.handleStack = this.handleStack.splice(0, start);
+ }
+
+ // Makes this ID globally scope. It must be explicitly invalidated.
+ ProxiedReferenceTable.prototype.globalize = function(id) {
+ this.globalIds[id] = true;
+ }
+
+ // Invalidates this ID, potentially freeing its corresponding object.
+ ProxiedReferenceTable.prototype.invalidate = function(id) {
+ var old = this.get(id);
+ delete this.globalIds[id];
+ delete this.map[id];
+ this._deletedCount++;
+ return old;
+ }
+
+ // Gets the object or function corresponding to this ID.
+ ProxiedReferenceTable.prototype.get = function (id) {
+ if (!this.map.hasOwnProperty(id)) {
+ throw 'Proxy ' + id + ' has been invalidated.'
+ }
+ return this.map[id];
+ }
+
+ // Subtype for managing function proxies.
+ function ProxiedFunctionTable() {}
+
+ ProxiedFunctionTable.prototype = new ProxiedReferenceTable('func-ref');
+
+ ProxiedFunctionTable.prototype._initialize = function () {
+ // Configure this table's port to invoke the corresponding function given
+ // its ID.
+ // TODO(vsm): Should we enter / exit a scope?
+ var table = this;
+
+ this.port.receive(function (message) {
+ var id = message[0];
+ var args = message[1].map(deserialize);
+ var f = table.get(id);
+ // TODO(vsm): Should we capture _this_ automatically?
+ return serialize(f.apply(null, args));
+ });
+ }
+
+ // The singleton table for proxied local functions.
+ var proxiedFunctionTable = new ProxiedFunctionTable();
+
+ // Subtype for proxied local objects.
+ function ProxiedObjectTable() {}
+
+ ProxiedObjectTable.prototype = new ProxiedReferenceTable('js-ref');
+
+ ProxiedObjectTable.prototype._initialize = function () {
+ // Configure this table's port to forward methods, getters, and setters
+ // from the remote proxy to the local object.
+ var table = this;
+
+ this.port.receive(function (message) {
+ // TODO(vsm): Support a mechanism to register a handler here.
+ var receiver = table.get(message[0]);
+ var method = message[1];
+ var args = message[2].map(deserialize);
+ if (method.indexOf("get:") == 0) {
+ // Getter.
+ var field = method.substring(4);
+ if (field in receiver && args.length == 0) {
+ return [ 'return', serialize(receiver[field]) ];
+ }
+ } else if (method.indexOf("set:") == 0) {
+ // Setter.
+ var field = method.substring(4);
+ if (args.length == 1) {
+ return [ 'return', serialize(receiver[field] = args[0]) ];
+ }
+ } else if (method == '[]' && args.length == 1) {
+ // Index getter.
+ return [ 'return', serialize(receiver[args[0]]) ];
+ } else {
+ var f = receiver[method];
+ if (f) {
+ try {
+ var result = f.apply(receiver, args);
+ return [ 'return', serialize(result) ];
+ } catch (e) {
+ return [ 'exception', serialize(e) ];
+ }
+ }
+ }
+ return [ 'none' ];
+ });
+ }
+
+ // Singleton for local proxied objects.
+ var proxiedObjectTable = new ProxiedObjectTable();
+
+ // DOM element serialization code.
+ var _localNextElementId = 0;
+ var _DART_ID = 'data-dart_id';
+ var _DART_TEMPORARY_ATTACHED = 'data-dart_temporary_attached';
+
+ function serializeElement(e) {
+ // TODO(vsm): Use an isolate-specific id.
+ var id;
+ if (e.hasAttribute(_DART_ID)) {
+ id = e.getAttribute(_DART_ID);
+ } else {
+ id = (_localNextElementId++).toString();
+ e.setAttribute(_DART_ID, id);
+ }
+ if (e !== document.documentElement) {
+ // Element must be attached to DOM to be retrieve in js part.
+ // Attach top unattached parent to avoid detaching parent of "e" when
+ // appending "e" directly to document. We keep count of elements
+ // temporarily attached to prevent detaching top unattached parent to
+ // early. This count is equals to the length of _DART_TEMPORARY_ATTACHED
+ // attribute. There could be other elements to serialize having the same
+ // top unattached parent.
+ var top = e;
+ while (true) {
+ if (top.hasAttribute(_DART_TEMPORARY_ATTACHED)) {
+ var oldValue = top.getAttribute(_DART_TEMPORARY_ATTACHED);
+ var newValue = oldValue + "a";
+ top.setAttribute(_DART_TEMPORARY_ATTACHED, newValue);
+ break;
+ }
+ if (top.parentNode == null) {
+ top.setAttribute(_DART_TEMPORARY_ATTACHED, "a");
+ document.documentElement.appendChild(top);
+ break;
+ }
+ if (top.parentNode === document.documentElement) {
+ // e was already attached to dom
+ break;
+ }
+ top = top.parentNode;
+ }
+ }
+ return id;
+ }
+
+ function deserializeElement(id) {
+ // TODO(vsm): Clear the attribute.
+ var list = document.querySelectorAll('[' + _DART_ID + '="' + id + '"]');
+
+ if (list.length > 1) throw 'Non unique ID: ' + id;
+ if (list.length == 0) {
+ throw 'Element must be attached to the document: ' + id;
+ }
+ var e = list[0];
+ if (e !== document.documentElement) {
+ // detach temporary attached element
+ var top = e;
+ while (true) {
+ if (top.hasAttribute(_DART_TEMPORARY_ATTACHED)) {
+ var oldValue = top.getAttribute(_DART_TEMPORARY_ATTACHED);
+ var newValue = oldValue.substring(1);
+ top.setAttribute(_DART_TEMPORARY_ATTACHED, newValue);
+ // detach top only if no more elements have to be unserialized
+ if (top.getAttribute(_DART_TEMPORARY_ATTACHED).length === 0) {
+ top.removeAttribute(_DART_TEMPORARY_ATTACHED);
+ document.documentElement.removeChild(top);
+ }
+ break;
+ }
+ if (top.parentNode === document.documentElement) {
+ // e was already attached to dom
+ break;
+ }
+ top = top.parentNode;
+ }
+ }
+ return e;
+ }
+
+
+ // Type for remote proxies to Dart objects.
+ function DartProxy(id, sendPort) {
+ this.id = id;
+ this.port = sendPort;
+ }
+
+ // Serializes JS types to SendPortSync format:
+ // - primitives -> primitives
+ // - sendport -> sendport
+ // - DOM element -> [ 'domref', element-id ]
+ // - Function -> [ 'funcref', function-id, sendport ]
+ // - Object -> [ 'objref', object-id, sendport ]
+ function serialize(message) {
+ if (message == null) {
+ return null; // Convert undefined to null.
+ } else if (typeof(message) == 'string' ||
+ typeof(message) == 'number' ||
+ typeof(message) == 'boolean') {
+ // Primitives are passed directly through.
+ return message;
+ } else if (message instanceof SendPortSync) {
+ // Non-proxied objects are serialized.
+ return message;
+ } else if (message instanceof Element) {
+ return [ 'domref', serializeElement(message) ];
+ } else if (typeof(message) == 'function') {
+ if ('_dart_id' in message) {
+ // Remote function proxy.
+ var remoteId = message._dart_id;
+ var remoteSendPort = message._dart_port;
+ return [ 'funcref', remoteId, remoteSendPort ];
+ } else {
+ // Local function proxy.
+ return [ 'funcref',
+ proxiedFunctionTable.add(message),
+ proxiedFunctionTable.sendPort ];
+ }
+ } else if (message instanceof DartProxy) {
+ // Remote object proxy.
+ return [ 'objref', message.id, message.port ];
+ } else {
+ // Local object proxy.
+ return [ 'objref',
+ proxiedObjectTable.add(message),
+ proxiedObjectTable.sendPort ];
+ }
+ }
+
+ function deserialize(message) {
+ if (message == null) {
+ return null; // Convert undefined to null.
+ } else if (typeof(message) == 'string' ||
+ typeof(message) == 'number' ||
+ typeof(message) == 'boolean') {
+ // Primitives are passed directly through.
+ return message;
+ } else if (message instanceof SendPortSync) {
+ // Serialized type.
+ return message;
+ }
+ var tag = message[0];
+ switch (tag) {
+ case 'funcref': return deserializeFunction(message);
+ case 'objref': return deserializeObject(message);
+ case 'domref': return deserializeElement(message[1]);
+ }
+ throw 'Unsupported serialized data: ' + message;
+ }
+
+ // Create a local function that forwards to the remote function.
+ function deserializeFunction(message) {
+ var id = message[1];
+ var port = message[2];
+ // TODO(vsm): Add a more robust check for a local SendPortSync.
+ if ("receivePort" in port) {
+ // Local function.
+ return proxiedFunctionTable.get(id);
+ } else {
+ // Remote function. Forward to its port.
+ var f = function () {
+ enterScope();
+ try {
+ var args = Array.prototype.slice.apply(arguments).map(serialize);
+ var result = port.callSync([id, args]);
+ return deserialize(result);
+ } finally {
+ exitScope();
+ }
+ };
+ // Cache the remote id and port.
+ f._dart_id = id;
+ f._dart_port = port;
+ return f;
+ }
+ }
+
+ // Creates a DartProxy to forwards to the remote object.
+ function deserializeObject(message) {
+ var id = message[1];
+ var port = message[2];
+ // TODO(vsm): Add a more robust check for a local SendPortSync.
+ if ("receivePort" in port) {
+ // Local object.
+ return proxiedObjectTable.get(id);
+ } else {
+ // Remote object.
+ return new DartProxy(id, port);
+ }
+ }
+
+ // Remote handler to construct a new JavaScript object given its
+ // serialized constructor and arguments.
+ function construct(args) {
+ args = args.map(deserialize);
+ var constructor = args[0];
+ args = Array.prototype.slice.call(args, 1);
+
+ // Dummy Type with correct constructor.
+ var Type = function(){};
+ Type.prototype = constructor.prototype;
+
+ // Create a new instance
+ var instance = new Type();
+
+ // Call the original constructor.
+ var ret = constructor.apply(instance, args);
+
+ return serialize(Object(ret) === ret ? ret : instance);
+ }
+
+ // Remote handler to evaluate a string in JavaScript and return a serialized
+ // result.
+ function evaluate(data) {
+ return serialize(eval(deserialize(data)));
+ }
+
+ // Remote handler for debugging.
+ function debug() {
+ var live = proxiedObjectTable.count() + proxiedFunctionTable.count();
+ var total = proxiedObjectTable.total() + proxiedFunctionTable.total();
+ return 'JS objects Live : ' + live +
+ ' (out of ' + total + ' ever allocated).';
+ }
+
+ function makeGlobalPort(name, f) {
+ var port = new ReceivePortSync();
+ port.receive(f);
+ window.registerPort(name, port.toSendPort());
+ }
+
+ // Enters a new scope in the JavaScript context.
+ function enterScope() {
+ proxiedObjectTable.enterScope();
+ proxiedFunctionTable.enterScope();
+ }
+
+ // Exits the current scope (and invalidate local IDs) in the JavaScript
+ // context.
+ function exitScope() {
+ proxiedFunctionTable.exitScope();
+ proxiedObjectTable.exitScope();
+ }
+
+ makeGlobalPort('dart-js-evaluate', evaluate);
+ makeGlobalPort('dart-js-create', construct);
+ makeGlobalPort('dart-js-debug', debug);
+ makeGlobalPort('dart-js-enter-scope', enterScope);
+ makeGlobalPort('dart-js-exit-scope', exitScope);
+ makeGlobalPort('dart-js-globalize', function(data) {
+ if (data[0] == "objref") return proxiedObjectTable.globalize(data[1]);
+ // TODO(vsm): Do we ever need to globalize functions?
+ throw 'Illegal type: ' + data[0];
+ });
+ makeGlobalPort('dart-js-invalidate', function(data) {
+ if (data[0] == "objref") return proxiedObjectTable.invalidate(data[1]);
+ // TODO(vsm): Do we ever need to globalize functions?
+ throw 'Illegal type: ' + data[0];
+ });
+})();
« 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