| 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
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..393320f91ffd8f70fe532342e4164d54a8e452d1
|
| --- /dev/null
|
| +++ b/sdk/lib/js/dartium/js_dartium.dart
|
| @@ -0,0 +1,371 @@
|
| +// 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 [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: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');
|
| +
|
| +/**
|
| + * Returns a proxy to the global JavaScript context for this page.
|
| + */
|
| +JsObject get context => _deserialize(_jsPortSync.callSync([]));
|
| +
|
| +/**
|
| + * 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> {
|
| + JsFunction _f;
|
| +
|
| + Callback._(Function f, bool withThis) {
|
| + final id = _proxiedObjectTable.add((List args) {
|
| + final arguments = new List.from(args);
|
| + if (!withThis) arguments.removeAt(0);
|
| + return Function.apply(f, arguments);
|
| + });
|
| + _f = new JsFunction._internal(_proxiedObjectTable.sendPort, id);
|
| + }
|
| +
|
| + factory Callback(Function f) => new Callback._(f, false);
|
| + factory Callback.withThis(Function f) => new Callback._(f, true);
|
| +
|
| + JsFunction toJs() => _f;
|
| +}
|
| +
|
| +/**
|
| + * 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);
|
| + }
|
| +
|
| + /**
|
| + * Constructs a [JsObject] to a new JavaScript map or list created defined via
|
| + * Dart map or list.
|
| + */
|
| + 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)];
|
| + }
|
| + }
|
| +
|
| + JsObject._internal(this._port, this._id);
|
| +
|
| + JsObject 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 ]);
|
| +
|
| + int get hashCode => _id.hashCode;
|
| +
|
| + // Test if this is equivalent to another Proxy. This essentially
|
| + // maps to JavaScript's === operator.
|
| + operator==(other) => other is JsObject && this._id == other._id;
|
| +
|
| + /**
|
| + * Check if this [JsObject] has a [name] property.
|
| + */
|
| + bool hasProperty(String name) => _forward(this, name, 'hasProperty', []);
|
| +
|
| + /**
|
| + * Delete the [name] property.
|
| + */
|
| + void deleteProperty(String name) {
|
| + _jsPortDeleteProperty.callSync([this, name].map(_serialize).toList());
|
| + }
|
| +
|
| + /**
|
| + * Check if this [JsObject] is instance of [type].
|
| + */
|
| + bool instanceof(Serializable<JsFunction> type) =>
|
| + _jsPortInstanceof.callSync([this, type].map(_serialize).toList());
|
| +
|
| + String toString() {
|
| + try {
|
| + return _forward(this, 'toString', 'method', []);
|
| + } catch(e) {
|
| + return super.toString();
|
| + }
|
| + }
|
| +
|
| + callMethod(String name, [List args]) {
|
| + return _forward(this, name, 'method', args != null ? args : []);
|
| + }
|
| +
|
| + // 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, member, args, {});
|
| + default: throw 'Invalid return value';
|
| + }
|
| + }
|
| +}
|
| +
|
| +/// 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();
|
| +}
|
| +
|
| +// 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;
|
| +
|
| + // 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;
|
| +
|
| + _ProxiedObjectTable() :
|
| + _name = 'dart-ref',
|
| + _nextId = 0,
|
| + _registry = {},
|
| + _port = new ReceivePortSync() {
|
| + _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) {
|
| + // TODO(vsm): Cache x and reuse id.
|
| + final id = '$_name-${_nextId++}';
|
| + _registry[id] = x;
|
| + 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 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 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) {
|
| + return _serialize(new Callback(message));
|
| + } 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 JsFunction._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 JsObject._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);
|
| + }
|
| + throw 'Unsupported serialized data: $message';
|
| +}
|
|
|