Index: sdk/lib/js/dartium/js_dartium.dart |
diff --git a/sdk/lib/js/dartium/js_dartium.dart b/sdk/lib/js/dartium/js_dartium.dart |
index 234a4bf3bff3f352481c7667dd5b22612853a160..8cc3928b9e6f41eefc48d78b8c9bdd5b5c07d36c 100644 |
--- a/sdk/lib/js/dartium/js_dartium.dart |
+++ b/sdk/lib/js/dartium/js_dartium.dart |
@@ -2,419 +2,114 @@ |
// 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 [JsObject] 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 [JsObject] to the global JavaScript |
- * context for the page your Dart code is running on. In the following example: |
- * |
- * import 'dart:js'; |
- * |
- * void main() { |
- * context.callMethod('alert', ['Hello from Dart via JavaScript']); |
- * } |
- * |
- * 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: |
- * |
- * 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 |
- * [JsObject] 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 JsObject(context['Foo'], [42]); |
- * var foo2 = foo.callMethod('add', [foo]); |
- * print(foo2['x']); |
- * |
- * will construct a JavaScript Foo object with the parameter 42, invoke its |
- * add method, and return a [JsObject] to a new Foo object whose x field is 84. |
- */ |
- |
library dart.js; |
-import 'dart:collection' show HashMap; |
-import 'dart:html'; |
-import 'dart:isolate'; |
- |
-// Global ports to manage communication from Dart to JS. |
+import 'dart:nativewrappers'; |
-SendPortSync _jsPortSync = window.lookupPort('dart-js-context'); |
-SendPortSync _jsPortCreate = window.lookupPort('dart-js-create'); |
-SendPortSync _jsPortInstanceof = window.lookupPort('dart-js-instanceof'); |
-SendPortSync _jsPortDeleteProperty = window.lookupPort('dart-js-delete-property'); |
-SendPortSync _jsPortConvert = window.lookupPort('dart-js-convert'); |
+JsObject _cachedContext; |
-final String _objectIdPrefix = 'dart-obj-ref'; |
-final String _functionIdPrefix = 'dart-fun-ref'; |
-final _objectTable = new _ObjectTable(); |
-final _functionTable = new _ObjectTable.forFunctions(); |
+JsObject get _context native "Js_context_Callback"; |
-// Port to handle and forward requests to the underlying Dart objects. |
-// A remote proxy is uniquely identified by an ID and SendPortSync. |
-ReceivePortSync _port = new ReceivePortSync() |
- ..receive((msg) { |
- try { |
- var id = msg[0]; |
- var method = msg[1]; |
- if (method == '#call') { |
- var receiver = _getObjectTable(id).get(id); |
- var result; |
- if (receiver is Function) { |
- // remove the first argument, which is 'this', but never |
- // used for a raw function |
- var args = msg[2].sublist(1).map(_deserialize).toList(); |
- result = Function.apply(receiver, args); |
- } else if (receiver is Callback) { |
- var args = msg[2].map(_deserialize).toList(); |
- result = receiver._call(args); |
- } else { |
- throw new StateError('bad function type: $receiver'); |
- } |
- return ['return', _serialize(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']; |
- } |
- }); |
- |
-_ObjectTable _getObjectTable(String id) { |
- if (id.startsWith(_functionIdPrefix)) return _functionTable; |
- if (id.startsWith(_objectIdPrefix)) return _objectTable; |
- throw new ArgumentError('internal error: invalid object id: $id'); |
-} |
- |
-JsObject _context; |
- |
-/** |
- * Returns a proxy to the global JavaScript context for this page. |
- */ |
JsObject get context { |
- if (_context == null) { |
- var port = _jsPortSync; |
- if (port == null) { |
- return null; |
- } |
- _context = _deserialize(_jsPortSync.callSync([])); |
+ if (_cachedContext == null) { |
+ _cachedContext = _context; |
} |
- return _context; |
-} |
- |
-/** |
- * Converts a json-like [data] to a JavaScript map or array and return a |
- * [JsObject] to it. |
- */ |
-JsObject jsify(dynamic data) => data == null ? null : new JsObject._json(data); |
- |
-/** |
- * Converts a local Dart function to a callback that can be passed to |
- * JavaScript. |
- */ |
-class Callback implements Serializable<JsFunction> { |
- final bool _withThis; |
- final Function _function; |
- JsFunction _jsFunction; |
- |
- Callback._(this._function, this._withThis) { |
- var id = _functionTable.add(this); |
- _jsFunction = new JsFunction._internal(_port.toSendPort(), id); |
- } |
- |
- factory Callback(Function f) => new Callback._(f, false); |
- factory Callback.withThis(Function f) => new Callback._(f, true); |
- |
- dynamic _call(List args) { |
- var arguments = (_withThis) ? args : args.sublist(1); |
- return Function.apply(_function, arguments); |
- } |
- |
- JsFunction toJs() => _jsFunction; |
+ return _cachedContext; |
} |
-/** |
- * Proxies to JavaScript objects. |
- */ |
-class JsObject implements Serializable<JsObject> { |
- final SendPortSync _port; |
- final String _id; |
- |
- /** |
- * Constructs a [JsObject] 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 JsObject(Serializable<JsFunction> constructor, [List arguments]) { |
- final params = [constructor]; |
- if (arguments != null) params.addAll(arguments); |
- final serialized = params.map(_serialize).toList(); |
- final result = _jsPortCreate.callSync(serialized); |
- return _deserialize(result); |
+class JsObject extends NativeFieldWrapperClass2 { |
+ JsObject.internal(); |
+ |
+ factory JsObject(JsFunction constructor, [List arguments]) => _create(constructor, arguments); |
+ |
+ static JsObject _create(JsFunction constructor, arguments) native "JsObject_constructorCallback"; |
+ |
+ /** |
+ * Expert users only: |
+ * Use this constructor only if you want to gain access to JS expandos |
+ * attached to a browser native object such as a Node. |
+ * Not all native browser objects can be converted using fromBrowserObject. |
+ * Currently the following types are supported: |
+ * * Node |
+ * * ArrayBuffer |
+ * * Blob |
+ * * ImageData |
+ * * IDBKeyRange |
+ * TODO(jacobr): support Event, Window and NodeList as well. |
+ */ |
+ factory JsObject.fromBrowserObject(var object) { |
+ if (object is num || object is String || object is bool || object == null) { |
+ throw new ArgumentError( |
+ "object cannot be a num, string, bool, or null"); |
+ } |
+ return _fromBrowserObject(object); |
} |
/** |
- * Constructs a [JsObject] to a new JavaScript map or list created defined via |
- * Dart map or list. |
+ * Converts a json-like [object] to a JavaScript map or array and return a |
+ * [JsObject] to it. |
*/ |
- factory JsObject._json(data) => _convert(data); |
- |
- static _convert(data) => |
- _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)]; |
+ factory JsObject.jsify(object) { |
+ if ((object is! Map) && (object is! Iterable)) { |
+ throw new ArgumentError("object must be a Map or Iterable"); |
} |
+ return _jsify(object); |
} |
- JsObject._internal(this._port, this._id); |
+ static JSObject _jsify(object) native "JsObject_jsify"; |
- JsObject toJs() => this; |
+ static JsObject _fromBrowserObject(object) native "JsObject_fromBrowserObject"; |
- // Resolve whether this is needed. |
- operator[](arg) => _forward(this, '[]', 'method', [ arg ]); |
+ operator[](key) native "JsObject_[]"; |
+ operator[]=(key, value) native "JsObject_[]="; |
- // Resolve whether this is needed. |
- operator[]=(key, value) => _forward(this, '[]=', 'method', [ key, value ]); |
+ int get hashCode native "JsObject_hashCode"; |
- int get hashCode => _id.hashCode; |
+ operator==(other) => other is JsObject && _identityEquality(this, other); |
- // Test if this is equivalent to another Proxy. This essentially |
- // maps to JavaScript's === operator. |
- operator==(other) => other is JsObject && this._id == other._id; |
+ static bool _identityEquality(JsObject a, JsObject b) native "JsObject_identityEquality"; |
- /** |
- * Check if this [JsObject] has a [name] property. |
- */ |
- bool hasProperty(String name) => _forward(this, name, 'hasProperty', []); |
+ bool hasProperty(String property) native "JsObject_hasProperty"; |
alexandre.ardhuin
2013/10/20 07:17:00
`property` should be dynamic like in js_dart2js.da
|
- /** |
- * Delete the [name] property. |
- */ |
- void deleteProperty(String name) { |
- _jsPortDeleteProperty.callSync([this, name].map(_serialize).toList()); |
- } |
+ void deleteProperty(JsFunction name) native "JsObject_deleteProperty"; |
alexandre.ardhuin
2013/10/20 07:17:00
`name` should be dynamic like in js_dart2js.dart
|
- /** |
- * Check if this [JsObject] is instance of [type]. |
- */ |
- bool instanceof(Serializable<JsFunction> type) => |
- _jsPortInstanceof.callSync([this, type].map(_serialize).toList()); |
+ bool instanceof(JsFunction type) native "JsObject_instanceof"; |
String toString() { |
try { |
- return _forward(this, 'toString', 'method', []); |
+ return _toString(); |
} catch(e) { |
return super.toString(); |
} |
} |
- callMethod(String name, [List args]) { |
- return _forward(this, name, 'method', args != null ? args : []); |
- } |
+ String _toString() native "JsObject_toString"; |
- // Forward member accesses to the backing JavaScript object. |
- static _forward(JsObject receiver, String member, String kind, List args) { |
- 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, new Symbol(member), args, {}); |
- default: throw 'Invalid return value'; |
+ callMethod(String name, [List args]) { |
+ try { |
+ return _callMethod(name, args); |
+ } catch(e) { |
+ if (hasProperty(name)) { |
+ rethrow; |
+ } else { |
+ throw new NoSuchMethodError(this, new Symbol(name), args, null); |
+ } |
} |
} |
-} |
- |
-/// A [JsObject] subtype to JavaScript functions. |
-class JsFunction extends JsObject implements Serializable<JsFunction> { |
- JsFunction._internal(SendPortSync port, String id) |
- : super._internal(port, id); |
- |
- apply(thisArg, [List args]) { |
- return JsObject._forward(this, '', 'apply', |
- [thisArg]..addAll(args == null ? [] : args)); |
- } |
-} |
-/// 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(); |
+ _callMethod(String name, List args) native "JsObject_callMethod"; |
} |
-class _ObjectTable { |
- final String name; |
- final Map<String, Object> objects; |
- final Map<Object, String> ids; |
- int nextId = 0; |
+class JsFunction extends JsObject { |
+ JsFunction.internal(); |
- // Creates a table that uses an identity Map to store IDs |
- _ObjectTable() |
- : name = _objectIdPrefix, |
- objects = new HashMap<String, Object>(), |
- ids = new HashMap<Object, String>.identity(); |
- |
- // Creates a table that uses an equality-based Map to store IDs, since |
- // closurized methods may be equal, but not identical |
- _ObjectTable.forFunctions() |
- : name = _functionIdPrefix, |
- objects = new HashMap<String, Object>(), |
- ids = new HashMap<Object, String>(); |
- |
- // Adds a new object to the table. If [id] is not given, a new unique ID is |
- // generated. Returns the ID. |
- String add(Object o, {String id}) { |
- // TODO(vsm): Cache x and reuse id. |
- if (id == null) id = ids[o]; |
- if (id == null) id = '$name-${nextId++}'; |
- ids[o] = id; |
- objects[id] = o; |
- return id; |
- } |
- |
- // Gets an object by ID. |
- Object get(String id) => objects[id]; |
- |
- bool contains(String id) => objects.containsKey(id); |
- |
- String getId(Object o) => ids[o]; |
- |
- // Gets the current number of objects kept alive by this table. |
- get count => objects.length; |
-} |
- |
-// 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 JsFunction) { |
- // Remote function proxy. |
- return ['funcref', message._id, message._port]; |
- } else if (message is JsObject) { |
- // Remote object proxy. |
- return ['objref', message._id, message._port]; |
- } else if (message is Serializable) { |
- // use of result of toJs() |
- return _serialize(message.toJs()); |
- } else if (message is Function) { |
- var id = _functionTable.getId(message); |
- if (id != null) { |
- return ['funcref', id, _port.toSendPort()]; |
- } |
- id = _functionTable.add(message); |
- return ['funcref', id, _port.toSendPort()]; |
- } else { |
- // Local object proxy. |
- return ['objref', _objectTable.add(message), _port.toSendPort()]; |
- } |
-} |
- |
-_deserialize(var message) { |
- deserializeFunction(message) { |
- var id = message[1]; |
- var port = message[2]; |
- if (port == _port.toSendPort()) { |
- // Local function. |
- return _functionTable.get(id); |
- } else { |
- // Remote function. |
- var jsFunction = _functionTable.get(id); |
- if (jsFunction == null) { |
- jsFunction = new JsFunction._internal(port, id); |
- _functionTable.add(jsFunction, id: id); |
- } |
- return jsFunction; |
- } |
- } |
+ /** |
+ * Returns a [JsFunction] that captures its 'this' binding and calls [f] |
+ * with the value of this passed as the first argument. |
+ */ |
+ factory JsFunction.withThis(Function f) => _withThis(f); |
- deserializeObject(message) { |
- var id = message[1]; |
- var port = message[2]; |
- if (port == _port.toSendPort()) { |
- // Local object. |
- return _objectTable.get(id); |
- } else { |
- // Remote object. |
- var jsObject = _objectTable.get(id); |
- if (jsObject == null) { |
- jsObject = new JsObject._internal(port, id); |
- _objectTable.add(jsObject, id: id); |
- } |
- return jsObject; |
- } |
- } |
+ apply(List args, {thisArg}) native "JsFunction_apply"; |
- 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); |
- } |
- throw 'Unsupported serialized data: $message'; |
+ static JsFunction _withThis(Function f) native "JsFunction_withThis"; |
} |