| 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..908d440e0ca4a5fc80b0a9311a52da105113dded 100644
|
| --- a/sdk/lib/js/dartium/js_dartium.dart
|
| +++ b/sdk/lib/js/dartium/js_dartium.dart
|
| @@ -2,419 +2,96 @@
|
| // 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.
|
| -
|
| -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');
|
| -
|
| -final String _objectIdPrefix = 'dart-obj-ref';
|
| -final String _functionIdPrefix = 'dart-fun-ref';
|
| -final _objectTable = new _ObjectTable();
|
| -final _functionTable = new _ObjectTable.forFunctions();
|
| +import 'dart:nativewrappers';
|
|
|
| -// 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'];
|
| - }
|
| - });
|
| +JsObject _cachedContext;
|
|
|
| -_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;
|
| +JsObject get _context native "Js_context_Callback";
|
|
|
| -/**
|
| - * 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;
|
| + return _cachedContext;
|
| }
|
|
|
| -/**
|
| - * 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);
|
| +class JsObject extends NativeFieldWrapperClass2 {
|
|
|
| -/**
|
| - * 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;
|
| + factory JsObject(JsFunction constructor, [List arguments]) => _create(constructor, arguments);
|
|
|
| - Callback._(this._function, this._withThis) {
|
| - var id = _functionTable.add(this);
|
| - _jsFunction = new JsFunction._internal(_port.toSendPort(), id);
|
| - }
|
| + static JsObject _create(JsFunction constructor, arguments) native "JsObject_constructorCallback";
|
|
|
| - 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;
|
| -}
|
| -
|
| -/**
|
| - * 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);
|
| + /**
|
| + * 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) {
|
| + return _fromBrowserObject(object);
|
| }
|
|
|
| /**
|
| - * Constructs a [JsObject] to a new JavaScript map or list created defined via
|
| - * Dart map or list.
|
| + * Converts a json-like [data] 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()];
|
| + factory JsObject.jsify(data) {
|
| + if (object is Map || object is Iterable) {
|
| + return _jsify(data);
|
| } else {
|
| - return ['simple', _serialize(data)];
|
| + throw new IllegalArgumentException("data must be an Iterable or Map");
|
| }
|
| }
|
|
|
| - JsObject._internal(this._port, this._id);
|
| + static JSObject _jsify(data) native "Js_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";
|
|
|
| - /**
|
| - * Delete the [name] property.
|
| - */
|
| - void deleteProperty(String name) {
|
| - _jsPortDeleteProperty.callSync([this, name].map(_serialize).toList());
|
| - }
|
| + void deleteProperty(JsFunction name) native "JsObject_deleteProperty";
|
|
|
| - /**
|
| - * 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]) native "JsObject_callMethod";
|
| }
|
|
|
| -/// 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();
|
| -}
|
| -
|
| -class _ObjectTable {
|
| - final String name;
|
| - final Map<String, Object> objects;
|
| - final Map<Object, String> ids;
|
| - int nextId = 0;
|
| -
|
| - // 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;
|
| - }
|
| - }
|
| +class JsFunction extends JsObject {
|
| + /**
|
| + * 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";
|
| }
|
|
|