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

Side by Side 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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « sdk/lib/js/dart2js/js_dart2js.dart ('k') | tests/html/js_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 /**
6 * The js.dart library provides simple JavaScript invocation from Dart that
7 * works on both Dartium and on other modern browsers via Dart2JS.
8 *
9 * It provides a model based on scoped [JsObject] objects. Proxies give Dart
10 * code access to JavaScript objects, fields, and functions as well as the
11 * ability to pass Dart objects and functions to JavaScript functions. Scopes
12 * enable developers to use proxies without memory leaks - a common challenge
13 * with cross-runtime interoperation.
14 *
15 * The top-level [context] getter provides a [JsObject] to the global JavaScript
16 * context for the page your Dart code is running on. In the following example:
17 *
18 * import 'dart:js';
19 *
20 * void main() {
21 * context.callMethod('alert', ['Hello from Dart via JavaScript']);
22 * }
23 *
24 * context['alert'] creates a proxy to the top-level alert function in
25 * JavaScript. It is invoked from Dart as a regular function that forwards to
26 * the underlying JavaScript one. By default, proxies are released when
27 * the currently executing event completes, e.g., when main is completes
28 * in this example.
29 *
30 * The library also enables JavaScript proxies to Dart objects and functions.
31 * For example, the following Dart code:
32 *
33 * context['dartCallback'] = new Callback.once((x) => print(x*2));
34 *
35 * defines a top-level JavaScript function 'dartCallback' that is a proxy to
36 * the corresponding Dart function. The [Callback.once] constructor allows the
37 * proxy to the Dart function to be retained across multiple events;
38 * instead it is released after the first invocation. (This is a common
39 * pattern for asychronous callbacks.)
40 *
41 * Note, parameters and return values are intuitively passed by value for
42 * primitives and by reference for non-primitives. In the latter case, the
43 * references are automatically wrapped and unwrapped as proxies by the library.
44 *
45 * This library also allows construction of JavaScripts objects given a
46 * [JsObject] to a corresponding JavaScript constructor. For example, if the
47 * following JavaScript is loaded on the page:
48 *
49 * function Foo(x) {
50 * this.x = x;
51 * }
52 *
53 * Foo.prototype.add = function(other) {
54 * return new Foo(this.x + other.x);
55 * }
56 *
57 * then, the following Dart:
58 *
59 * var foo = new JsObject(context['Foo'], [42]);
60 * var foo2 = foo.callMethod('add', [foo]);
61 * print(foo2['x']);
62 *
63 * will construct a JavaScript Foo object with the parameter 42, invoke its
64 * add method, and return a [JsObject] to a new Foo object whose x field is 84.
65 */
66
67 library dart.js;
68
69 import 'dart:html';
70 import 'dart:isolate';
71
72 // Global ports to manage communication from Dart to JS.
73 SendPortSync _jsPortSync = window.lookupPort('dart-js-context');
74 SendPortSync _jsPortCreate = window.lookupPort('dart-js-create');
75 SendPortSync _jsPortInstanceof = window.lookupPort('dart-js-instanceof');
76 SendPortSync _jsPortDeleteProperty = window.lookupPort('dart-js-delete-property' );
77 SendPortSync _jsPortConvert = window.lookupPort('dart-js-convert');
78
79 /**
80 * Returns a proxy to the global JavaScript context for this page.
81 */
82 JsObject get context => _deserialize(_jsPortSync.callSync([]));
83
84 /**
85 * Converts a json-like [data] to a JavaScript map or array and return a
86 * [JsObject] to it.
87 */
88 JsObject jsify(dynamic data) => data == null ? null : new JsObject._json(data);
89
90 /**
91 * Converts a local Dart function to a callback that can be passed to
92 * JavaScript.
93 */
94 class Callback implements Serializable<JsFunction> {
95 JsFunction _f;
96
97 Callback._(Function f, bool withThis) {
98 final id = _proxiedObjectTable.add((List args) {
99 final arguments = new List.from(args);
100 if (!withThis) arguments.removeAt(0);
101 return Function.apply(f, arguments);
102 });
103 _f = new JsFunction._internal(_proxiedObjectTable.sendPort, id);
104 }
105
106 factory Callback(Function f) => new Callback._(f, false);
107 factory Callback.withThis(Function f) => new Callback._(f, true);
108
109 JsFunction toJs() => _f;
110 }
111
112 /**
113 * Proxies to JavaScript objects.
114 */
115 class JsObject implements Serializable<JsObject> {
116 final SendPortSync _port;
117 final String _id;
118
119 /**
120 * Constructs a [JsObject] to a new JavaScript object by invoking a (proxy to
121 * a) JavaScript [constructor]. The [arguments] list should contain either
122 * primitive values, DOM elements, or Proxies.
123 */
124 factory JsObject(Serializable<JsFunction> constructor, [List arguments]) {
125 final params = [constructor];
126 if (arguments != null) params.addAll(arguments);
127 final serialized = params.map(_serialize).toList();
128 final result = _jsPortCreate.callSync(serialized);
129 return _deserialize(result);
130 }
131
132 /**
133 * Constructs a [JsObject] to a new JavaScript map or list created defined via
134 * Dart map or list.
135 */
136 factory JsObject._json(data) => _convert(data);
137
138 static _convert(data) =>
139 _deserialize(_jsPortConvert.callSync(_serializeDataTree(data)));
140
141 static _serializeDataTree(data) {
142 if (data is Map) {
143 final entries = new List();
144 for (var key in data.keys) {
145 entries.add([key, _serializeDataTree(data[key])]);
146 }
147 return ['map', entries];
148 } else if (data is Iterable) {
149 return ['list', data.map(_serializeDataTree).toList()];
150 } else {
151 return ['simple', _serialize(data)];
152 }
153 }
154
155 JsObject._internal(this._port, this._id);
156
157 JsObject toJs() => this;
158
159 // Resolve whether this is needed.
160 operator[](arg) => _forward(this, '[]', 'method', [ arg ]);
161
162 // Resolve whether this is needed.
163 operator[]=(key, value) => _forward(this, '[]=', 'method', [ key, value ]);
164
165 int get hashCode => _id.hashCode;
166
167 // Test if this is equivalent to another Proxy. This essentially
168 // maps to JavaScript's === operator.
169 operator==(other) => other is JsObject && this._id == other._id;
170
171 /**
172 * Check if this [JsObject] has a [name] property.
173 */
174 bool hasProperty(String name) => _forward(this, name, 'hasProperty', []);
175
176 /**
177 * Delete the [name] property.
178 */
179 void deleteProperty(String name) {
180 _jsPortDeleteProperty.callSync([this, name].map(_serialize).toList());
181 }
182
183 /**
184 * Check if this [JsObject] is instance of [type].
185 */
186 bool instanceof(Serializable<JsFunction> type) =>
187 _jsPortInstanceof.callSync([this, type].map(_serialize).toList());
188
189 String toString() {
190 try {
191 return _forward(this, 'toString', 'method', []);
192 } catch(e) {
193 return super.toString();
194 }
195 }
196
197 callMethod(String name, [List args]) {
198 return _forward(this, name, 'method', args != null ? args : []);
199 }
200
201 // Forward member accesses to the backing JavaScript object.
202 static _forward(JsObject receiver, String member, String kind, List args) {
203 var result = receiver._port.callSync([receiver._id, member, kind,
204 args.map(_serialize).toList()]);
205 switch (result[0]) {
206 case 'return': return _deserialize(result[1]);
207 case 'throws': throw _deserialize(result[1]);
208 case 'none': throw new NoSuchMethodError(receiver, member, args, {});
209 default: throw 'Invalid return value';
210 }
211 }
212 }
213
214 /// A [JsObject] subtype to JavaScript functions.
215 class JsFunction extends JsObject implements Serializable<JsFunction> {
216 JsFunction._internal(SendPortSync port, String id)
217 : super._internal(port, id);
218
219 apply(thisArg, [List args]) {
220 return JsObject._forward(this, '', 'apply',
221 [thisArg]..addAll(args == null ? [] : args));
222 }
223 }
224
225 /// Marker class used to indicate it is serializable to js. If a class is a
226 /// [Serializable] the "toJs" method will be called and the result will be used
227 /// as value.
228 abstract class Serializable<T> {
229 T toJs();
230 }
231
232 // A table to managed local Dart objects that are proxied in JavaScript.
233 class _ProxiedObjectTable {
234 // Debugging name.
235 final String _name;
236
237 // Generator for unique IDs.
238 int _nextId;
239
240 // Table of IDs to Dart objects.
241 final Map<String, Object> _registry;
242
243 // Port to handle and forward requests to the underlying Dart objects.
244 // A remote proxy is uniquely identified by an ID and SendPortSync.
245 final ReceivePortSync _port;
246
247 _ProxiedObjectTable() :
248 _name = 'dart-ref',
249 _nextId = 0,
250 _registry = {},
251 _port = new ReceivePortSync() {
252 _port.receive((msg) {
253 try {
254 final receiver = _registry[msg[0]];
255 final method = msg[1];
256 final args = msg[2].map(_deserialize).toList();
257 if (method == '#call') {
258 final func = receiver as Function;
259 var result = _serialize(func(args));
260 return ['return', result];
261 } else {
262 // TODO(vsm): Support a mechanism to register a handler here.
263 throw 'Invocation unsupported on non-function Dart proxies';
264 }
265 } catch (e) {
266 // TODO(vsm): callSync should just handle exceptions itself.
267 return ['throws', '$e'];
268 }
269 });
270 }
271
272 // Adds a new object to the table and return a new ID for it.
273 String add(x) {
274 // TODO(vsm): Cache x and reuse id.
275 final id = '$_name-${_nextId++}';
276 _registry[id] = x;
277 return id;
278 }
279
280 // Gets an object by ID.
281 Object get(String id) {
282 return _registry[id];
283 }
284
285 // Gets the current number of objects kept alive by this table.
286 get count => _registry.length;
287
288 // Gets a send port for this table.
289 get sendPort => _port.toSendPort();
290 }
291
292 // The singleton to manage proxied Dart objects.
293 _ProxiedObjectTable _proxiedObjectTable = new _ProxiedObjectTable();
294
295 /// End of proxy implementation.
296
297 // Dart serialization support.
298
299 _serialize(var message) {
300 if (message == null) {
301 return null; // Convert undefined to null.
302 } else if (message is String ||
303 message is num ||
304 message is bool) {
305 // Primitives are passed directly through.
306 return message;
307 } else if (message is SendPortSync) {
308 // Non-proxied objects are serialized.
309 return message;
310 } else if (message is JsFunction) {
311 // Remote function proxy.
312 return [ 'funcref', message._id, message._port ];
313 } else if (message is JsObject) {
314 // Remote object proxy.
315 return [ 'objref', message._id, message._port ];
316 } else if (message is Serializable) {
317 // use of result of toJs()
318 return _serialize(message.toJs());
319 } else if (message is Function) {
320 return _serialize(new Callback(message));
321 } else {
322 // Local object proxy.
323 return [ 'objref',
324 _proxiedObjectTable.add(message),
325 _proxiedObjectTable.sendPort ];
326 }
327 }
328
329 _deserialize(var message) {
330 deserializeFunction(message) {
331 var id = message[1];
332 var port = message[2];
333 if (port == _proxiedObjectTable.sendPort) {
334 // Local function.
335 return _proxiedObjectTable.get(id);
336 } else {
337 // Remote function. Forward to its port.
338 return new JsFunction._internal(port, id);
339 }
340 }
341
342 deserializeObject(message) {
343 var id = message[1];
344 var port = message[2];
345 if (port == _proxiedObjectTable.sendPort) {
346 // Local object.
347 return _proxiedObjectTable.get(id);
348 } else {
349 // Remote object.
350 return new JsObject._internal(port, id);
351 }
352 }
353
354 if (message == null) {
355 return null; // Convert undefined to null.
356 } else if (message is String ||
357 message is num ||
358 message is bool) {
359 // Primitives are passed directly through.
360 return message;
361 } else if (message is SendPortSync) {
362 // Serialized type.
363 return message;
364 }
365 var tag = message[0];
366 switch (tag) {
367 case 'funcref': return deserializeFunction(message);
368 case 'objref': return deserializeObject(message);
369 }
370 throw 'Unsupported serialized data: $message';
371 }
OLDNEW
« 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