Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1067)

Unified Diff: sdk/lib/js/dartium/js_dartium.dart

Issue 15782009: RFC: introduce dart:js (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: fix comments on patch 9 Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « sdk/lib/js/dart2js/js_dart2js.dart ('k') | tests/html/js_test.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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';
+}
« no previous file with comments | « sdk/lib/js/dart2js/js_dart2js.dart ('k') | tests/html/js_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698