| OLD | NEW |
| (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 })(); | |
| OLD | NEW |