Index: third_party/pkg/js/lib/js.dart |
diff --git a/third_party/pkg/js/lib/js.dart b/third_party/pkg/js/lib/js.dart |
deleted file mode 100644 |
index d1eb7acd4982ea26edad1cf20e7d2f771ec6cee0..0000000000000000000000000000000000000000 |
--- a/third_party/pkg/js/lib/js.dart |
+++ /dev/null |
@@ -1,1398 +0,0 @@ |
-// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
- |
-/** |
- * The js.dart library provides simple JavaScript invocation from Dart that |
- * works on both Dartium and on other modern browsers via Dart2JS. |
- * |
- * It provides a model based on scoped [Proxy] objects. Proxies give Dart |
- * code access to JavaScript objects, fields, and functions as well as the |
- * ability to pass Dart objects and functions to JavaScript functions. Scopes |
- * enable developers to use proxies without memory leaks - a common challenge |
- * with cross-runtime interoperation. |
- * |
- * The top-level [context] getter provides a [Proxy] to the global JavaScript |
- * context for the page your Dart code is running on. In the following example: |
- * |
- * import 'package:js/js.dart' as js; |
- * |
- * void main() { |
- * js.context.alert('Hello from Dart via JavaScript'); |
- * } |
- * |
- * js.context.alert creates a proxy to the top-level alert function in |
- * JavaScript. It is invoked from Dart as a regular function that forwards to |
- * the underlying JavaScript one. By default, proxies are released when |
- * the currently executing event completes, e.g., when main is completes |
- * in this example. |
- * |
- * The library also enables JavaScript proxies to Dart objects and functions. |
- * For example, the following Dart code: |
- * |
- * js.context.dartCallback = new Callback.once((x) => print(x*2)); |
- * |
- * defines a top-level JavaScript function 'dartCallback' that is a proxy to |
- * the corresponding Dart function. The [Callback.once] constructor allows the |
- * proxy to the Dart function to be retained across multiple events; |
- * instead it is released after the first invocation. (This is a common |
- * pattern for asychronous callbacks.) |
- * |
- * Note, parameters and return values are intuitively passed by value for |
- * primitives and by reference for non-primitives. In the latter case, the |
- * references are automatically wrapped and unwrapped as proxies by the library. |
- * |
- * This library also allows construction of JavaScripts objects given a [Proxy] |
- * to a corresponding JavaScript constructor. For example, if the following |
- * JavaScript is loaded on the page: |
- * |
- * function Foo(x) { |
- * this.x = x; |
- * } |
- * |
- * Foo.prototype.add = function(other) { |
- * return new Foo(this.x + other.x); |
- * } |
- * |
- * then, the following Dart: |
- * |
- * var foo = new js.Proxy(js.context.Foo, 42); |
- * var foo2 = foo.add(foo); |
- * print(foo2.x); |
- * |
- * will construct a JavaScript Foo object with the parameter 42, invoke its |
- * add method, and return a [Proxy] to a new Foo object whose x field is 84. |
- * |
- * See [samples](http://dart-lang.github.com/js-interop/example) for more |
- * examples of usage. |
- * |
- * See this [article](http://www.dartlang.org/articles/js-dart-interop) for |
- * more detailed discussion. |
- */ |
- |
-library js; |
- |
-import 'dart:async'; |
-import 'dart:html'; |
-import 'dart:isolate'; |
-import 'dart:mirrors'; |
- |
-// JavaScript bootstrapping code. |
-// TODO(vsm): Migrate this to use a builtin resource mechanism once we have |
-// one. |
- |
-// NOTE: Please re-run tools/create_bootstrap.dart on any modification of |
-// this bootstrap string. |
-final _JS_BOOTSTRAP = r""" |
-(function() { |
- // Proxy support for js.dart. |
- |
- var globalContext = window; |
- |
- // Support for binding the receiver (this) in proxied functions. |
- function bindIfFunction(f, _this) { |
- if (typeof(f) != "function") { |
- return f; |
- } else { |
- return new BoundFunction(_this, f); |
- } |
- } |
- |
- function unbind(obj) { |
- if (obj instanceof BoundFunction) { |
- return obj.object; |
- } else { |
- return obj; |
- } |
- } |
- |
- function getBoundThis(obj) { |
- if (obj instanceof BoundFunction) { |
- return obj._this; |
- } else { |
- return globalContext; |
- } |
- } |
- |
- function BoundFunction(_this, object) { |
- this._this = _this; |
- this.object = object; |
- } |
- |
- // Table for local objects and functions that are proxied. |
- function ProxiedObjectTable() { |
- // Name for debugging. |
- this.name = 'js-ref'; |
- |
- // 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. |
- ProxiedObjectTable.prototype.count = function () { |
- return Object.keys(this.map).length; |
- } |
- |
- // Number of total IDs ever allocated. |
- ProxiedObjectTable.prototype.total = function () { |
- return this.count() + this._deletedCount; |
- } |
- |
- // Adds an object to the table and return an ID for serialization. |
- ProxiedObjectTable.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; |
- } |
- |
- ProxiedObjectTable.prototype._initializeOnce = function () { |
- if (!this._initialized) { |
- this._initialize(); |
- this._initialized = true; |
- } |
- } |
- |
- // Enters a new scope for this table. |
- ProxiedObjectTable.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. |
- ProxiedObjectTable.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. |
- ProxiedObjectTable.prototype.globalize = function(id) { |
- this.globalIds[id] = true; |
- } |
- |
- // Invalidates this ID, potentially freeing its corresponding object. |
- ProxiedObjectTable.prototype.invalidate = function(id) { |
- var old = this.get(id); |
- delete this.globalIds[id]; |
- delete this.map[id]; |
- this._deletedCount++; |
- } |
- |
- // Gets the object or function corresponding to this ID. |
- ProxiedObjectTable.prototype.get = function (id) { |
- if (!this.map.hasOwnProperty(id)) { |
- throw 'Proxy ' + id + ' has been invalidated.' |
- } |
- return this.map[id]; |
- } |
- |
- 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. |
- try { |
- var object = table.get(message[0]); |
- var receiver = unbind(object); |
- var member = message[1]; |
- var kind = message[2]; |
- var args = message[3].map(deserialize); |
- if (kind == 'get') { |
- // Getter. |
- var field = member; |
- if (field in receiver && args.length == 0) { |
- var result = bindIfFunction(receiver[field], receiver); |
- return [ 'return', serialize(result) ]; |
- } |
- } else if (kind == 'set') { |
- // Setter. |
- var field = member; |
- if (args.length == 1) { |
- return [ 'return', serialize(receiver[field] = args[0]) ]; |
- } |
- } else if (kind == 'apply') { |
- // Direct function invocation. |
- var _this = getBoundThis(object); |
- return [ 'return', serialize(receiver.apply(_this, args)) ]; |
- } else if (member == '[]' && args.length == 1) { |
- // Index getter. |
- var result = bindIfFunction(receiver[args[0]], receiver); |
- return [ 'return', serialize(result) ]; |
- } else if (member == '[]=' && args.length == 2) { |
- // Index setter. |
- return [ 'return', serialize(receiver[args[0]] = args[1]) ]; |
- } else { |
- // Member function invocation. |
- var f = receiver[member]; |
- if (f) { |
- var result = f.apply(receiver, args); |
- return [ 'return', serialize(result) ]; |
- } |
- } |
- return [ 'none' ]; |
- } catch (e) { |
- return [ 'throws', e.toString() ]; |
- } |
- }); |
- } |
- |
- // 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 && |
- (message.ownerDocument == null || message.ownerDocument == document)) { |
- return [ 'domref', serializeElement(message) ]; |
- } else if (message instanceof BoundFunction && |
- typeof(message.object) == 'function') { |
- // Local function proxy. |
- return [ 'funcref', |
- proxiedObjectTable.add(message), |
- proxiedObjectTable.sendPort ]; |
- } 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', |
- proxiedObjectTable.add(message), |
- proxiedObjectTable.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 unbind(proxiedObjectTable.get(id)); |
- } else { |
- // Remote function. Forward to its port. |
- var f = function () { |
- var depth = enterScope(); |
- try { |
- var args = Array.prototype.slice.apply(arguments); |
- args.splice(0, 0, this); |
- args = args.map(serialize); |
- var result = port.callSync([id, '#call', args]); |
- if (result[0] == 'throws') throw deserialize(result[1]); |
- return deserialize(result[1]); |
- } finally { |
- exitScope(depth); |
- } |
- }; |
- // 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 = unbind(args[0]); |
- args = Array.prototype.slice.call(args, 1); |
- |
- // Until 10 args, the 'new' operator is used. With more arguments we use a |
- // generic way that may not work, particulary when the constructor does not |
- // have an "apply" method. |
- var ret = null; |
- if (args.length === 0) { |
- ret = new constructor(); |
- } else if (args.length === 1) { |
- ret = new constructor(args[0]); |
- } else if (args.length === 2) { |
- ret = new constructor(args[0], args[1]); |
- } else if (args.length === 3) { |
- ret = new constructor(args[0], args[1], args[2]); |
- } else if (args.length === 4) { |
- ret = new constructor(args[0], args[1], args[2], args[3]); |
- } else if (args.length === 5) { |
- ret = new constructor(args[0], args[1], args[2], args[3], args[4]); |
- } else if (args.length === 6) { |
- ret = new constructor(args[0], args[1], args[2], args[3], args[4], |
- args[5]); |
- } else if (args.length === 7) { |
- ret = new constructor(args[0], args[1], args[2], args[3], args[4], |
- args[5], args[6]); |
- } else if (args.length === 8) { |
- ret = new constructor(args[0], args[1], args[2], args[3], args[4], |
- args[5], args[6], args[7]); |
- } else if (args.length === 9) { |
- ret = new constructor(args[0], args[1], args[2], args[3], args[4], |
- args[5], args[6], args[7], args[8]); |
- } else if (args.length === 10) { |
- ret = new constructor(args[0], args[1], args[2], args[3], args[4], |
- args[5], args[6], args[7], args[8], args[9]); |
- } else { |
- // Dummy Type with correct constructor. |
- var Type = function(){}; |
- Type.prototype = constructor.prototype; |
- |
- // Create a new instance |
- var instance = new Type(); |
- |
- // Call the original constructor. |
- ret = constructor.apply(instance, args); |
- ret = Object(ret) === ret ? ret : instance; |
- } |
- return serialize(ret); |
- } |
- |
- // Remote handler to return the top-level JavaScript context. |
- function context(data) { |
- return serialize(globalContext); |
- } |
- |
- // Remote handler to track number of live / allocated proxies. |
- function proxyCount() { |
- var live = proxiedObjectTable.count(); |
- var total = proxiedObjectTable.total(); |
- return [live, total]; |
- } |
- |
- // Return true if two JavaScript proxies are equal (==). |
- function proxyEquals(args) { |
- return deserialize(args[0]) == deserialize(args[1]); |
- } |
- |
- // Return true if a JavaScript proxy is instance of a given type (instanceof). |
- function proxyInstanceof(args) { |
- var obj = unbind(deserialize(args[0])); |
- var type = unbind(deserialize(args[1])); |
- return obj instanceof type; |
- } |
- |
- // Return true if a JavaScript proxy has a given property. |
- function proxyHasProperty(args) { |
- var obj = unbind(deserialize(args[0])); |
- var member = unbind(deserialize(args[1])); |
- return member in obj; |
- } |
- |
- // Delete a given property of object. |
- function proxyDeleteProperty(args) { |
- var obj = unbind(deserialize(args[0])); |
- var member = unbind(deserialize(args[1])); |
- delete obj[member]; |
- } |
- |
- function proxyConvert(args) { |
- return serialize(deserializeDataTree(args)); |
- } |
- |
- function deserializeDataTree(data) { |
- var type = data[0]; |
- var value = data[1]; |
- if (type === 'map') { |
- var obj = {}; |
- for (var i = 0; i < value.length; i++) { |
- obj[value[i][0]] = deserializeDataTree(value[i][1]); |
- } |
- return obj; |
- } else if (type === 'list') { |
- var list = []; |
- for (var i = 0; i < value.length; i++) { |
- list.push(deserializeDataTree(value[i])); |
- } |
- return list; |
- } else /* 'simple' */ { |
- return deserialize(value); |
- } |
- } |
- |
- 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 enterJavaScriptScope() { |
- proxiedObjectTable.enterScope(); |
- } |
- |
- // Enters a new scope in both the JavaScript and Dart context. |
- var _dartEnterScopePort = null; |
- function enterScope() { |
- enterJavaScriptScope(); |
- if (!_dartEnterScopePort) { |
- _dartEnterScopePort = window.lookupPort('js-dart-interop-enter-scope'); |
- } |
- return _dartEnterScopePort.callSync([]); |
- } |
- |
- // Exits the current scope (and invalidate local IDs) in the JavaScript |
- // context. |
- function exitJavaScriptScope() { |
- proxiedObjectTable.exitScope(); |
- } |
- |
- // Exits the current scope in both the JavaScript and Dart context. |
- var _dartExitScopePort = null; |
- function exitScope(depth) { |
- exitJavaScriptScope(); |
- if (!_dartExitScopePort) { |
- _dartExitScopePort = window.lookupPort('js-dart-interop-exit-scope'); |
- } |
- return _dartExitScopePort.callSync([ depth ]); |
- } |
- |
- makeGlobalPort('dart-js-interop-context', context); |
- makeGlobalPort('dart-js-interop-create', construct); |
- makeGlobalPort('dart-js-interop-proxy-count', proxyCount); |
- makeGlobalPort('dart-js-interop-equals', proxyEquals); |
- makeGlobalPort('dart-js-interop-instanceof', proxyInstanceof); |
- makeGlobalPort('dart-js-interop-has-property', proxyHasProperty); |
- makeGlobalPort('dart-js-interop-delete-property', proxyDeleteProperty); |
- makeGlobalPort('dart-js-interop-convert', proxyConvert); |
- makeGlobalPort('dart-js-interop-enter-scope', enterJavaScriptScope); |
- makeGlobalPort('dart-js-interop-exit-scope', exitJavaScriptScope); |
- makeGlobalPort('dart-js-interop-globalize', function(data) { |
- if (data[0] == "objref" || data[0] == "funcref") return proxiedObjectTable.globalize(data[1]); |
- throw 'Illegal type: ' + data[0]; |
- }); |
- makeGlobalPort('dart-js-interop-invalidate', function(data) { |
- if (data[0] == "objref" || data[0] == "funcref") return proxiedObjectTable.invalidate(data[1]); |
- throw 'Illegal type: ' + data[0]; |
- }); |
-})(); |
-"""; |
- |
-// Injects JavaScript source code onto the page. |
-// This is only used to load the bootstrapping code above. |
-void _inject(code) { |
- final script = new ScriptElement(); |
- script.type = 'text/javascript'; |
- script.innerHtml = code; |
- document.body.nodes.add(script); |
-} |
- |
-// Global ports to manage communication from Dart to JS. |
-SendPortSync _jsPortSync = null; |
-SendPortSync _jsPortCreate = null; |
-SendPortSync _jsPortProxyCount = null; |
-SendPortSync _jsPortEquals = null; |
-SendPortSync _jsPortInstanceof = null; |
-SendPortSync _jsPortHasProperty = null; |
-SendPortSync _jsPortDeleteProperty = null; |
-SendPortSync _jsPortConvert = null; |
-SendPortSync _jsEnterJavaScriptScope = null; |
-SendPortSync _jsExitJavaScriptScope = null; |
-SendPortSync _jsGlobalize = null; |
-SendPortSync _jsInvalidate = null; |
- |
-// Global ports to manage communication from JS to Dart. |
-ReceivePortSync _dartEnterDartScope = null; |
-ReceivePortSync _dartExitDartScope = null; |
- |
-// Initializes bootstrap code and ports. |
-void _initialize() { |
- if (_jsPortSync != null) return; |
- |
- // Test if the port is already defined. |
- try { |
- _jsPortSync = window.lookupPort('dart-js-interop-context'); |
- } catch (e) { |
- // TODO(vsm): Suppress the exception until dartbug.com/5854 is fixed. |
- } |
- |
- // If not, try injecting the script. |
- if (_jsPortSync == null) { |
- _inject(_JS_BOOTSTRAP); |
- _jsPortSync = window.lookupPort('dart-js-interop-context'); |
- } |
- |
- _jsPortCreate = window.lookupPort('dart-js-interop-create'); |
- _jsPortProxyCount = window.lookupPort('dart-js-interop-proxy-count'); |
- _jsPortEquals = window.lookupPort('dart-js-interop-equals'); |
- _jsPortInstanceof = window.lookupPort('dart-js-interop-instanceof'); |
- _jsPortHasProperty = window.lookupPort('dart-js-interop-has-property'); |
- _jsPortDeleteProperty = window.lookupPort('dart-js-interop-delete-property'); |
- _jsPortConvert = window.lookupPort('dart-js-interop-convert'); |
- _jsEnterJavaScriptScope = window.lookupPort('dart-js-interop-enter-scope'); |
- _jsExitJavaScriptScope = window.lookupPort('dart-js-interop-exit-scope'); |
- _jsGlobalize = window.lookupPort('dart-js-interop-globalize'); |
- _jsInvalidate = window.lookupPort('dart-js-interop-invalidate'); |
- |
- _dartEnterDartScope = new ReceivePortSync() |
- ..receive((_) => _enterScope()); |
- _dartExitDartScope = new ReceivePortSync() |
- ..receive((args) => _exitScope(args[0])); |
- window.registerPort('js-dart-interop-enter-scope', _dartEnterDartScope.toSendPort()); |
- window.registerPort('js-dart-interop-exit-scope', _dartExitDartScope.toSendPort()); |
-} |
- |
-/** |
- * Returns a proxy to the global JavaScript context for this page. |
- */ |
-Proxy get context { |
- _enterScopeIfNeeded(); |
- return _deserialize(_jsPortSync.callSync([])); |
-} |
- |
-// Depth of current scope. Return 0 if no scope. |
-get _depth => _proxiedObjectTable._scopeIndices.length; |
- |
-// If we are not already in a scope, enter one and register a |
-// corresponding exit once we return to the event loop. |
-void _enterScopeIfNeeded() { |
- if (_depth == 0) { |
- var depth = _enterScope(); |
- scheduleMicrotask(() => _exitScope(depth)); |
- } |
-} |
- |
-/** |
- * Executes the closure [f] within a scope. Any proxies created within this |
- * scope are invalidated afterward unless they are converted to a global proxy. |
- */ |
-scoped(f) { |
- var depth = _enterScope(); |
- try { |
- return f(); |
- } finally { |
- _exitScope(depth); |
- } |
-} |
- |
-int _enterScope() { |
- _initialize(); |
- _proxiedObjectTable.enterScope(); |
- _jsEnterJavaScriptScope.callSync([]); |
- return _proxiedObjectTable._scopeIndices.length; |
-} |
- |
-void _exitScope(int depth) { |
- assert(_proxiedObjectTable._scopeIndices.length == depth); |
- _jsExitJavaScriptScope.callSync([]); |
- _proxiedObjectTable.exitScope(); |
-} |
- |
-/* |
- * Enters a scope and returns the depth of the scope stack. |
- */ |
-/// WARNING: This API is experimental and may be removed. |
-int $experimentalEnterScope() { |
- return _enterScope(); |
-} |
- |
-/* |
- * Exits a scope. The [depth] must match that returned by the corresponding |
- * enter scope call. |
- */ |
-/// WARNING: This API is experimental and may be removed. |
-void $experimentalExitScope(int depth) { |
- _exitScope(depth); |
-} |
- |
-/** |
- * Retains the given [object] beyond the current scope. |
- * Instead, it will need to be explicitly released. |
- * The given [object] is returned for convenience. |
- */ |
-// TODO(aa) : change dynamic to Serializable<Proxy> if http://dartbug.com/9023 |
-// is fixed. |
-// TODO(aa) : change to "<T extends Serializable<Proxy>> T retain(T object)" |
-// once generic methods have landed. |
-dynamic retain(Serializable<Proxy> object) { |
- _jsGlobalize.callSync(_serialize(object.toJs())); |
- return object; |
-} |
- |
-/** |
- * Releases a retained [object]. |
- */ |
-void release(Serializable<Proxy> object) { |
- _jsInvalidate.callSync(_serialize(object.toJs())); |
-} |
- |
-/** |
- * Check if [proxy] is instance of [type]. |
- */ |
-bool instanceof(Serializable<Proxy> proxy, Serializable<FunctionProxy> type) => |
- _jsPortInstanceof.callSync([proxy.toJs(), type.toJs()].map(_serialize) |
- .toList()); |
- |
-/** |
- * Check if [proxy] has a [name] property. |
- */ |
-bool hasProperty(Serializable<Proxy> proxy, String name) => |
- _jsPortHasProperty.callSync([proxy.toJs(), name].map(_serialize).toList()); |
- |
-/** |
- * Delete the [name] property of [proxy]. |
- */ |
-void deleteProperty(Serializable<Proxy> proxy, String name) { |
- _jsPortDeleteProperty.callSync([proxy.toJs(), name].map(_serialize).toList()); |
-} |
- |
-/** |
- * Converts a Dart map [data] to a JavaScript map and return a [Proxy] to it. |
- */ |
-Proxy map(Map data) => new Proxy._json(data); |
- |
-/** |
- * Converts a Dart [Iterable] to a JavaScript array and return a [Proxy] to it. |
- */ |
-Proxy array(Iterable data) => new Proxy._json(data); |
- |
-/** |
- * Converts a local Dart function to a callback that can be passed to |
- * JavaScript. |
- * |
- * A callback can either be: |
- * |
- * - single-fire, in which case it is automatically invalidated after the first |
- * invocation, or |
- * - multi-fire, in which case it must be explicitly disposed. |
- */ |
-class Callback implements Serializable<FunctionProxy> { |
- var _manualDispose; |
- var _id; |
- var _callback; |
- |
- _initialize(manualDispose) { |
- _manualDispose = manualDispose; |
- _id = _proxiedObjectTable.add(_callback); |
- _proxiedObjectTable.globalize(_id); |
- } |
- |
- _dispose() { |
- var c = _proxiedObjectTable.invalidate(_id); |
- } |
- |
- FunctionProxy toJs() => |
- new FunctionProxy._internal(_proxiedObjectTable.sendPort, _id); |
- |
- /** |
- * Disposes this [Callback] so that it may be collected. |
- * Once a [Callback] is disposed, it is an error to invoke it from JavaScript. |
- */ |
- dispose() { |
- assert(_manualDispose); |
- _dispose(); |
- } |
- |
- /** |
- * Creates a single-fire [Callback] that invokes [f]. The callback is |
- * automatically disposed after the first invocation. |
- */ |
- Callback.once(Function f, {bool withThis: false}) { |
- _callback = (List args) { |
- try { |
- return Function.apply(f, withThis ? args : args.skip(1).toList()); |
- } finally { |
- _dispose(); |
- } |
- }; |
- _initialize(false); |
- } |
- |
- /** |
- * Creates a multi-fire [Callback] that invokes [f]. The callback must be |
- * explicitly disposed to avoid memory leaks. |
- */ |
- Callback.many(Function f, {bool withThis: false}) { |
- _callback = (List args) => |
- Function.apply(f, withThis ? args : args.skip(1).toList()); |
- _initialize(true); |
- } |
-} |
- |
-// Detect unspecified arguments. |
-class _Undefined { |
- const _Undefined(); |
-} |
-const _undefined = const _Undefined(); |
-List _pruneUndefined(arg1, arg2, arg3, arg4, arg5, arg6) { |
- // This assumes no argument |
- final args = [arg1, arg2, arg3, arg4, arg5, arg6]; |
- final index = args.indexOf(_undefined); |
- if (index < 0) return args; |
- return args.sublist(0, index); |
-} |
- |
-/** |
- * Proxies to JavaScript objects. |
- */ |
-class Proxy implements Serializable<Proxy> { |
- SendPortSync _port; |
- final _id; |
- |
- /** |
- * Constructs a [Proxy] to a new JavaScript object by invoking a (proxy to a) |
- * JavaScript [constructor]. The arguments should be either |
- * primitive values, DOM elements, or Proxies. |
- */ |
- factory Proxy(Serializable<FunctionProxy> constructor, |
- [arg1 = _undefined, |
- arg2 = _undefined, |
- arg3 = _undefined, |
- arg4 = _undefined, |
- arg5 = _undefined, |
- arg6 = _undefined]) { |
- var arguments = _pruneUndefined(arg1, arg2, arg3, arg4, arg5, arg6); |
- return new Proxy.withArgList(constructor, arguments); |
- } |
- |
- /** |
- * Constructs a [Proxy] to a new JavaScript object by invoking a (proxy to a) |
- * JavaScript [constructor]. The [arguments] list should contain either |
- * primitive values, DOM elements, or Proxies. |
- */ |
- factory Proxy.withArgList(Serializable<FunctionProxy> constructor, |
- List arguments) { |
- _enterScopeIfNeeded(); |
- final serialized = ([constructor]..addAll(arguments)).map(_serialize). |
- toList(); |
- final result = _jsPortCreate.callSync(serialized); |
- return _deserialize(result); |
- } |
- |
- /** |
- * Constructs a [Proxy] to a new JavaScript map or list created defined via |
- * Dart map or list. |
- */ |
- factory Proxy._json(data) { |
- _enterScopeIfNeeded(); |
- return _convert(data); |
- } |
- |
- static _convert(data) { |
- return _deserialize(_jsPortConvert.callSync(_serializeDataTree(data))); |
- } |
- |
- static _serializeDataTree(data) { |
- if (data is Map) { |
- final entries = new List(); |
- for (var key in data.keys) { |
- entries.add([key, _serializeDataTree(data[key])]); |
- } |
- return ['map', entries]; |
- } else if (data is Iterable) { |
- return ['list', data.map(_serializeDataTree).toList()]; |
- } else { |
- return ['simple', _serialize(data)]; |
- } |
- } |
- |
- Proxy._internal(this._port, this._id); |
- |
- Proxy toJs() => this; |
- |
- // Resolve whether this is needed. |
- operator[](arg) => _forward(this, '[]', 'method', [ arg ]); |
- |
- // Resolve whether this is needed. |
- operator[]=(key, value) => _forward(this, '[]=', 'method', [ key, value ]); |
- |
- // Test if this is equivalent to another Proxy. This essentially |
- // maps to JavaScript's == operator. |
- // TODO(vsm): Can we avoid forwarding to JS? |
- operator==(other) => identical(this, other) |
- ? true |
- : (other is Proxy && |
- _jsPortEquals.callSync([_serialize(this), _serialize(other)])); |
- |
- String toString() { |
- try { |
- return _forward(this, 'toString', 'method', []); |
- } catch(e) { |
- return super.toString(); |
- } |
- } |
- |
- // Forward member accesses to the backing JavaScript object. |
- noSuchMethod(Invocation invocation) { |
- String member = MirrorSystem.getName(invocation.memberName); |
- // If trying to access a JavaScript field/variable that starts with |
- // _ (underscore), Dart treats it a library private and member name |
- // it suffixed with '@internalLibraryIdentifier' which we have to |
- // strip before sending over to the JS side. |
- if (member.indexOf('@') != -1) { |
- member = member.substring(0, member.indexOf('@')); |
- } |
- String kind; |
- List args = invocation.positionalArguments; |
- if (args == null) args = []; |
- if (invocation.isGetter) { |
- kind = 'get'; |
- } else if (invocation.isSetter) { |
- kind = 'set'; |
- if (member.endsWith('=')) { |
- member = member.substring(0, member.length - 1); |
- } |
- } else if (member == 'call') { |
- // A 'call' (probably) means that this proxy was invoked directly |
- // as if it was a function. Map this to JS function application. |
- kind = 'apply'; |
- } else { |
- kind = 'method'; |
- } |
- return _forward(this, member, kind, args); |
- } |
- |
- // Forward member accesses to the backing JavaScript object. |
- static _forward(Proxy receiver, String member, String kind, List args) { |
- _enterScopeIfNeeded(); |
- var result = receiver._port.callSync([receiver._id, member, kind, |
- args.map(_serialize).toList()]); |
- switch (result[0]) { |
- case 'return': return _deserialize(result[1]); |
- case 'throws': throw _deserialize(result[1]); |
- case 'none': throw new NoSuchMethodError(receiver, member, args, {}); |
- default: throw 'Invalid return value'; |
- } |
- } |
-} |
- |
-// TODO(aa) make FunctionProxy implements Function once it is allowed |
-/// A [Proxy] subtype to JavaScript functions. |
-class FunctionProxy extends Proxy |
- implements Serializable<FunctionProxy> /*,Function*/ { |
- FunctionProxy._internal(SendPortSync port, id) : super._internal(port, id); |
- |
- // TODO(vsm): This allows calls with a limited number of arguments |
- // in the context of dartbug.com/9283. Eliminate pending the resolution |
- // of this bug. Note, if this Proxy is called with more arguments then |
- // allowed below, it will trigger the 'call' path in Proxy.noSuchMethod |
- // - and still work correctly in unminified mode. |
- call([arg1 = _undefined, arg2 = _undefined, |
- arg3 = _undefined, arg4 = _undefined, |
- arg5 = _undefined, arg6 = _undefined]) { |
- var arguments = _pruneUndefined(arg1, arg2, arg3, arg4, arg5, arg6); |
- return Proxy._forward(this, '', 'apply', arguments); |
- } |
-} |
- |
-/// Marker class used to indicate it is serializable to js. If a class is a |
-/// [Serializable] the "toJs" method will be called and the result will be used |
-/// as value. |
-abstract class Serializable<T> { |
- T toJs(); |
-} |
- |
-// A table to managed local Dart objects that are proxied in JavaScript. |
-class _ProxiedObjectTable { |
- // Debugging name. |
- final String _name; |
- |
- // Generator for unique IDs. |
- int _nextId; |
- |
- // Counter for invalidated IDs for debugging. |
- int _deletedCount; |
- |
- // Table of IDs to Dart objects. |
- final Map<String, Object> _registry; |
- |
- // Port to handle and forward requests to the underlying Dart objects. |
- // A remote proxy is uniquely identified by an ID and SendPortSync. |
- final ReceivePortSync _port; |
- |
- // The set of IDs that are global. These must be explicitly invalidated. |
- final Set<String> _globalIds; |
- |
- // The stack of valid IDs. |
- final List<String> _handleStack; |
- |
- // The stack of scopes, where each scope is represented by an index into the |
- // handleStack. |
- final List<int> _scopeIndices; |
- |
- // Enters a new scope. |
- enterScope() { |
- _scopeIndices.add(_handleStack.length); |
- } |
- |
- // Invalidates non-global IDs created in the current scope and |
- // restore to the previous scope. |
- exitScope() { |
- int start = _scopeIndices.removeLast(); |
- for (int i = start; i < _handleStack.length; ++i) { |
- String key = _handleStack[i]; |
- if (!_globalIds.contains(key)) { |
- _registry.remove(_handleStack[i]); |
- _deletedCount++; |
- } |
- } |
- if (start != _handleStack.length) { |
- _handleStack.removeRange(start, _handleStack.length - start); |
- } |
- } |
- |
- // Converts an ID to a global. |
- globalize(id) => _globalIds.add(id); |
- |
- // Invalidates an ID. |
- invalidate(id) { |
- var old = _registry[id]; |
- _globalIds.remove(id); |
- _registry.remove(id); |
- _deletedCount++; |
- return old; |
- } |
- |
- // Replaces the object referenced by an ID. |
- _replace(id, x) { |
- _registry[id] = x; |
- } |
- |
- _ProxiedObjectTable() : |
- _name = 'dart-ref', |
- _nextId = 0, |
- _deletedCount = 0, |
- _registry = {}, |
- _port = new ReceivePortSync(), |
- _handleStack = new List<String>(), |
- _scopeIndices = new List<int>(), |
- _globalIds = new Set<String>() { |
- _port.receive((msg) { |
- try { |
- final receiver = _registry[msg[0]]; |
- final method = msg[1]; |
- final args = msg[2].map(_deserialize).toList(); |
- if (method == '#call') { |
- final func = receiver as Function; |
- var result = _serialize(func(args)); |
- return ['return', result]; |
- } else { |
- // TODO(vsm): Support a mechanism to register a handler here. |
- throw 'Invocation unsupported on non-function Dart proxies'; |
- } |
- } catch (e) { |
- // TODO(vsm): callSync should just handle exceptions itself. |
- return ['throws', '$e']; |
- } |
- }); |
- } |
- |
- // Adds a new object to the table and return a new ID for it. |
- String add(x) { |
- _enterScopeIfNeeded(); |
- // TODO(vsm): Cache x and reuse id. |
- final id = '$_name-${_nextId++}'; |
- _registry[id] = x; |
- _handleStack.add(id); |
- return id; |
- } |
- |
- // Gets an object by ID. |
- Object get(String id) { |
- return _registry[id]; |
- } |
- |
- // Gets the current number of objects kept alive by this table. |
- get count => _registry.length; |
- |
- // Gets the total number of IDs ever allocated. |
- get total => count + _deletedCount; |
- |
- // Gets a send port for this table. |
- get sendPort => _port.toSendPort(); |
-} |
- |
-// The singleton to manage proxied Dart objects. |
-_ProxiedObjectTable _proxiedObjectTable = new _ProxiedObjectTable(); |
- |
-/// End of proxy implementation. |
- |
-// Dart serialization support. |
- |
-_serialize(var message) { |
- if (message == null) { |
- return null; // Convert undefined to null. |
- } else if (message is String || |
- message is num || |
- message is bool) { |
- // Primitives are passed directly through. |
- return message; |
- } else if (message is SendPortSync) { |
- // Non-proxied objects are serialized. |
- return message; |
- } else if (message is Element && |
- (message.document == null || message.document == document)) { |
- return [ 'domref', _serializeElement(message) ]; |
- } else if (message is FunctionProxy) { |
- // Remote function proxy. |
- return [ 'funcref', message._id, message._port ]; |
- } else if (message is Proxy) { |
- // Remote object proxy. |
- return [ 'objref', message._id, message._port ]; |
- } else if (message is Serializable) { |
- // use of result of toJs() |
- return _serialize(message.toJs()); |
- } else { |
- // Local object proxy. |
- return [ 'objref', |
- _proxiedObjectTable.add(message), |
- _proxiedObjectTable.sendPort ]; |
- } |
-} |
- |
-_deserialize(var message) { |
- deserializeFunction(message) { |
- var id = message[1]; |
- var port = message[2]; |
- if (port == _proxiedObjectTable.sendPort) { |
- // Local function. |
- return _proxiedObjectTable.get(id); |
- } else { |
- // Remote function. Forward to its port. |
- return new FunctionProxy._internal(port, id); |
- } |
- } |
- |
- deserializeObject(message) { |
- var id = message[1]; |
- var port = message[2]; |
- if (port == _proxiedObjectTable.sendPort) { |
- // Local object. |
- return _proxiedObjectTable.get(id); |
- } else { |
- // Remote object. |
- return new Proxy._internal(port, id); |
- } |
- } |
- |
- |
- if (message == null) { |
- return null; // Convert undefined to null. |
- } else if (message is String || |
- message is num || |
- message is bool) { |
- // Primitives are passed directly through. |
- return message; |
- } else if (message is 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'; |
-} |
- |
-// DOM element serialization. |
- |
-int _localNextElementId = 0; |
- |
-const _DART_ID = 'data-dart_id'; |
-const _DART_TEMPORARY_ATTACHED = 'data-dart_temporary_attached'; |
- |
-_serializeElement(Element e) { |
- // TODO(vsm): Use an isolate-specific id. |
- var id; |
- if (e.attributes.containsKey(_DART_ID)) { |
- id = e.attributes[_DART_ID]; |
- } else { |
- id = 'dart-${_localNextElementId++}'; |
- e.attributes[_DART_ID] = id; |
- } |
- if (!identical(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.attributes.containsKey(_DART_TEMPORARY_ATTACHED)) { |
- final oldValue = top.attributes[_DART_TEMPORARY_ATTACHED]; |
- final newValue = oldValue + 'a'; |
- top.attributes[_DART_TEMPORARY_ATTACHED] = newValue; |
- break; |
- } |
- if (top.parent == null) { |
- top.attributes[_DART_TEMPORARY_ATTACHED] = 'a'; |
- document.documentElement.children.add(top); |
- break; |
- } |
- if (identical(top.parent, document.documentElement)) { |
- // e was already attached to dom |
- break; |
- } |
- top = top.parent; |
- } |
- } |
- return id; |
-} |
- |
-Element _deserializeElement(var id) { |
- var list = queryAll('[$_DART_ID="$id"]'); |
- if (list.length > 1) throw 'Non unique ID: $id'; |
- if (list.length == 0) { |
- throw 'Only elements attached to document can be serialized: $id'; |
- } |
- final e = list[0]; |
- if (!identical(e, document.documentElement)) { |
- // detach temporary attached element |
- var top = e; |
- while (true) { |
- if (top.attributes.containsKey(_DART_TEMPORARY_ATTACHED)) { |
- final oldValue = top.attributes[_DART_TEMPORARY_ATTACHED]; |
- final newValue = oldValue.substring(1); |
- top.attributes[_DART_TEMPORARY_ATTACHED] = newValue; |
- // detach top only if no more elements have to be unserialized |
- if (top.attributes[_DART_TEMPORARY_ATTACHED].length == 0) { |
- top.attributes.remove(_DART_TEMPORARY_ATTACHED); |
- top.remove(); |
- } |
- break; |
- } |
- if (identical(top.parent, document.documentElement)) { |
- // e was already attached to dom |
- break; |
- } |
- top = top.parent; |
- } |
- } |
- return e; |
-} |
- |
-// Fetch the number of proxies to JavaScript objects. |
-// This returns a 2 element list. The first is the number of currently |
-// live proxies. The second is the total number of proxies ever |
-// allocated. |
-List _proxyCountJavaScript() => _jsPortProxyCount.callSync([]); |
- |
-/** |
- * Returns the number of allocated proxy objects matching the given |
- * conditions. By default, the total number of live proxy objects are |
- * return. In a well behaved program, this should stay below a small |
- * bound. |
- * |
- * Set [all] to true to return the total number of proxies ever allocated. |
- * Set [dartOnly] to only count proxies to Dart objects (live or all). |
- * Set [jsOnly] to only count proxies to JavaScript objects (live or all). |
- */ |
-int proxyCount({all: false, dartOnly: false, jsOnly: false}) { |
- final js = !dartOnly; |
- final dart = !jsOnly; |
- final jsCounts = js ? _proxyCountJavaScript() : null; |
- var sum = 0; |
- if (!all) { |
- if (js) |
- sum += jsCounts[0]; |
- if (dart) |
- sum += _proxiedObjectTable.count; |
- } else { |
- if (js) |
- sum += jsCounts[1]; |
- if (dart) |
- sum += _proxiedObjectTable.total; |
- } |
- return sum; |
-} |
- |
-// Prints the number of live handles in Dart and JavaScript. This is for |
-// debugging / profiling purposes. |
-void _proxyDebug([String message = '']) { |
- print('Proxy status $message:'); |
- var dartLive = proxyCount(dartOnly: true); |
- var dartTotal = proxyCount(dartOnly: true, all: true); |
- var jsLive = proxyCount(jsOnly: true); |
- var jsTotal = proxyCount(jsOnly: true, all: true); |
- print(' Dart objects Live : $dartLive (out of $dartTotal ever allocated).'); |
- print(' JS objects Live : $jsLive (out of $jsTotal ever allocated).'); |
-} |