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

Side by Side Diff: sdk/lib/js/dartium/js_dartium.dart

Issue 26729002: dartium_js_interop changes Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: ready for review Created 7 years, 2 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 | « no previous file | tools/dom/scripts/dartdomgenerator.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 /** 5 library dart.Js;
Jennifer Messerly 2013/10/11 19:21:09 did you mean to uppercase the "J"?
Jacob 2013/10/15 22:39:44 Done. lowercased.
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 6
67 library dart.js; 7 import 'dart:nativewrappers';
68 8
69 import 'dart:html'; 9 JsObject get context native "Js_context_Callback";
70 import 'dart:isolate'; 10
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 JsObject _context;
81
82 /**
83 * Returns a proxy to the global JavaScript context for this page.
84 */
85 JsObject get context {
86 if (_context == null) {
87 var port = _jsPortSync;
88 if (port == null) {
89 return null;
90 }
91 _context = _deserialize(_jsPortSync.callSync([]));
92 }
93 return _context;
94 }
95
96 /** 11 /**
97 * Converts a json-like [data] to a JavaScript map or array and return a 12 * Converts a json-like [data] to a JavaScript map or array and return a
98 * [JsObject] to it. 13 * [JsObject] to it.
99 */ 14 */
100 JsObject jsify(dynamic data) => data == null ? null : new JsObject._json(data); 15 JsObject jsify(dynamic data) native "Js_jsify";
justinfagnani 2013/10/11 18:30:59 I think we should move this to a constructor on Js
Jacob 2013/10/15 22:39:44 Done. Although we still need a clearer name.
101 16
102 /** 17 /**
103 * Converts a local Dart function to a callback that can be passed to 18 * Returns a [JsFunction] that captures its 'this' binding and calls [f]
104 * JavaScript. 19 * with the value of this passed as the first argument.
105 */ 20 */
106 class Callback implements Serializable<JsFunction> { 21 JsFunction captureThis(Function f) native "Js_captureThis";
107 JsFunction _f;
108 22
109 Callback._(Function f, bool withThis) { 23 class JsObject extends NativeFieldWrapperClass1 {
justinfagnani 2013/10/11 18:30:59 will NativeFieldWrapperClass1 be visible in the ob
Jacob 2013/10/15 22:39:44 Discussed offline. I wouldn't worry too much about
110 final id = _proxiedObjectTable.add((List args) { 24
111 final arguments = new List.from(args); 25 factory JsObject(JsFunction constructor, [List arguments]) => _create(construc tor, arguments);
112 if (!withThis) arguments.removeAt(0); 26
113 return Function.apply(f, arguments); 27 static JsObject _create(JsFunction constructor, arguments) native "JsObject_co nstructorCallback";
114 }); 28
115 _f = new JsFunction._internal(_proxiedObjectTable.sendPort, id); 29 /**
30 * Expert users only:
31 * Use this constructor only if you wish to get access to JS expandos
32 * attached to a WebKit native object such as a Node.
33 * An exception will be thrown if a primitive type is passed in passing one
34 * of these types to this method indicates an error.
35 */
36 factory JsObject.fromDartObject(var object) {
vsm 2013/10/15 21:05:34 Perhaps fromBrowserObject? Will this throw if obj
Jacob 2013/10/15 22:39:44 one step ahead of me. :) just wrote a comment that
37 if (object is num || object is String || object == null) {
justinfagnani 2013/10/11 18:30:59 add bool
Jacob 2013/10/15 22:39:44 synced offline. Instead restricting further and t
Jacob 2013/10/15 22:39:44 Synced offline. We'll restrict which types can be
38 throw new IllegalArgumentException("object cannot be a num, string, or nu ll");
justinfagnani 2013/10/11 18:30:59 what about returning a proxy to the boxed primitiv
Jacob 2013/10/15 22:39:44 same note as previous comment.
39 }
40 return _fromDartObject(object);
116 } 41 }
117 42
118 factory Callback(Function f) => new Callback._(f, false); 43 static JsObject _fromDartObject(object) native "JsObject_fromDartObject";
119 factory Callback.withThis(Function f) => new Callback._(f, true);
120 44
121 JsFunction toJs() => _f; 45 operator[](key) native "JsObject_[]";
122 } 46 operator[]=(key, value) native "JsObject_[]=";
123 47
124 /** 48 int get hashCode => 0;
justinfagnani 2013/10/11 18:30:59 we talked about an identity-based hashCode
Jacob 2013/10/15 22:39:44 added that to this cl.
125 * Proxies to JavaScript objects.
126 */
127 class JsObject implements Serializable<JsObject> {
128 final SendPortSync _port;
129 final String _id;
130 49
131 /** 50 operator==(other) => other is JsObject && _identityEquality(this, other);
132 * Constructs a [JsObject] to a new JavaScript object by invoking a (proxy to
133 * a) JavaScript [constructor]. The [arguments] list should contain either
134 * primitive values, DOM elements, or Proxies.
135 */
136 factory JsObject(Serializable<JsFunction> constructor, [List arguments]) {
137 final params = [constructor];
138 if (arguments != null) params.addAll(arguments);
139 final serialized = params.map(_serialize).toList();
140 final result = _jsPortCreate.callSync(serialized);
141 return _deserialize(result);
142 }
143 51
144 /** 52 static bool _identityEquality(JsObject a, JsObject b) native "JsObject_identit yEquality";
145 * Constructs a [JsObject] to a new JavaScript map or list created defined via
146 * Dart map or list.
147 */
148 factory JsObject._json(data) => _convert(data);
149 53
150 static _convert(data) => 54 bool hasProperty(String property) native "JsObject_hasProperty";
151 _deserialize(_jsPortConvert.callSync(_serializeDataTree(data)));
152 55
153 static _serializeDataTree(data) { 56 void deleteProperty(JsFunction name) native "JsObject_deleteProperty";
154 if (data is Map) {
155 final entries = new List();
156 for (var key in data.keys) {
157 entries.add([key, _serializeDataTree(data[key])]);
158 }
159 return ['map', entries];
160 } else if (data is Iterable) {
161 return ['list', data.map(_serializeDataTree).toList()];
162 } else {
163 return ['simple', _serialize(data)];
164 }
165 }
166 57
167 JsObject._internal(this._port, this._id); 58 bool instanceof(JsFunction type) native "JsObject_instanceof";
168
169 JsObject toJs() => this;
170
171 // Resolve whether this is needed.
172 operator[](arg) => _forward(this, '[]', 'method', [ arg ]);
173
174 // Resolve whether this is needed.
175 operator[]=(key, value) => _forward(this, '[]=', 'method', [ key, value ]);
176
177 int get hashCode => _id.hashCode;
178
179 // Test if this is equivalent to another Proxy. This essentially
180 // maps to JavaScript's === operator.
181 operator==(other) => other is JsObject && this._id == other._id;
182
183 /**
184 * Check if this [JsObject] has a [name] property.
185 */
186 bool hasProperty(String name) => _forward(this, name, 'hasProperty', []);
187
188 /**
189 * Delete the [name] property.
190 */
191 void deleteProperty(String name) {
192 _jsPortDeleteProperty.callSync([this, name].map(_serialize).toList());
193 }
194
195 /**
196 * Check if this [JsObject] is instance of [type].
197 */
198 bool instanceof(Serializable<JsFunction> type) =>
199 _jsPortInstanceof.callSync([this, type].map(_serialize).toList());
200 59
201 String toString() { 60 String toString() {
202 try { 61 try {
203 return _forward(this, 'toString', 'method', []); 62 return _toString();
204 } catch(e) { 63 } catch(e) {
205 return super.toString(); 64 return super.toString();
206 } 65 }
207 } 66 }
208 67
209 callMethod(String name, [List args]) { 68 // FIXME: should we remove this? It seems like a a JS mis-feature we
210 return _forward(this, name, 'method', args != null ? args : []); 69 // shouldn't be exposing. If someone really wants it they can expose
211 } 70 // it themselves. If you have a JsObject then the value of jsType
71 // is unlikely to be interesting as it should just be "object" or
72 // "function" unless you used the pro-user only forceToJs method.
73 // String get jsType native "JsObject_jsType";
justinfagnani 2013/10/11 18:30:59 yes, remove it, it's not in current dart:js anyway
Jacob 2013/10/15 22:39:44 yay :) done.
212 74
213 // Forward member accesses to the backing JavaScript object. 75 String _toString() native "JsObject_toString";
214 static _forward(JsObject receiver, String member, String kind, List args) { 76
215 var result = receiver._port.callSync([receiver._id, member, kind, 77 callMethod(String name, [List args]) native "JsObject_callMethod";
216 args.map(_serialize).toList()]);
217 switch (result[0]) {
218 case 'return': return _deserialize(result[1]);
219 case 'throws': throw _deserialize(result[1]);
220 case 'none':
221 throw new NoSuchMethodError(receiver, new Symbol(member), args, {});
222 default: throw 'Invalid return value';
223 }
224 }
225 } 78 }
226 79
227 /// A [JsObject] subtype to JavaScript functions. 80 class JsFunction extends JsObject {
228 class JsFunction extends JsObject implements Serializable<JsFunction> { 81 apply(thisArg, [List args]) native "JsFunction_apply";
justinfagnani 2013/10/11 18:30:59 I think we should make thisArg optional
229 JsFunction._internal(SendPortSync port, String id)
230 : super._internal(port, id);
231
232 apply(thisArg, [List args]) {
233 return JsObject._forward(this, '', 'apply',
234 [thisArg]..addAll(args == null ? [] : args));
235 }
236 } 82 }
237
238 /// Marker class used to indicate it is serializable to js. If a class is a
239 /// [Serializable] the "toJs" method will be called and the result will be used
240 /// as value.
241 abstract class Serializable<T> {
242 T toJs();
243 }
244
245 // A table to managed local Dart objects that are proxied in JavaScript.
246 class _ProxiedObjectTable {
247 // Debugging name.
248 final String _name;
249
250 // Generator for unique IDs.
251 int _nextId;
252
253 // Table of IDs to Dart objects.
254 final Map<String, Object> _registry;
255
256 // Port to handle and forward requests to the underlying Dart objects.
257 // A remote proxy is uniquely identified by an ID and SendPortSync.
258 final ReceivePortSync _port;
259
260 _ProxiedObjectTable() :
261 _name = 'dart-ref',
262 _nextId = 0,
263 _registry = {},
264 _port = new ReceivePortSync() {
265 _port.receive((msg) {
266 try {
267 final receiver = _registry[msg[0]];
268 final method = msg[1];
269 final args = msg[2].map(_deserialize).toList();
270 if (method == '#call') {
271 final func = receiver as Function;
272 var result = _serialize(func(args));
273 return ['return', result];
274 } else {
275 // TODO(vsm): Support a mechanism to register a handler here.
276 throw 'Invocation unsupported on non-function Dart proxies';
277 }
278 } catch (e) {
279 // TODO(vsm): callSync should just handle exceptions itself.
280 return ['throws', '$e'];
281 }
282 });
283 }
284
285 // Adds a new object to the table and return a new ID for it.
286 String add(x) {
287 // TODO(vsm): Cache x and reuse id.
288 final id = '$_name-${_nextId++}';
289 _registry[id] = x;
290 return id;
291 }
292
293 // Gets an object by ID.
294 Object get(String id) {
295 return _registry[id];
296 }
297
298 // Gets the current number of objects kept alive by this table.
299 get count => _registry.length;
300
301 // Gets a send port for this table.
302 get sendPort => _port.toSendPort();
303 }
304
305 // The singleton to manage proxied Dart objects.
306 _ProxiedObjectTable _proxiedObjectTable = new _ProxiedObjectTable();
307
308 /// End of proxy implementation.
309
310 // Dart serialization support.
311
312 _serialize(var message) {
313 if (message == null) {
314 return null; // Convert undefined to null.
315 } else if (message is String ||
316 message is num ||
317 message is bool) {
318 // Primitives are passed directly through.
319 return message;
320 } else if (message is SendPortSync) {
321 // Non-proxied objects are serialized.
322 return message;
323 } else if (message is JsFunction) {
324 // Remote function proxy.
325 return [ 'funcref', message._id, message._port ];
326 } else if (message is JsObject) {
327 // Remote object proxy.
328 return [ 'objref', message._id, message._port ];
329 } else if (message is Serializable) {
330 // use of result of toJs()
331 return _serialize(message.toJs());
332 } else if (message is Function) {
333 return _serialize(new Callback(message));
334 } else {
335 // Local object proxy.
336 return [ 'objref',
337 _proxiedObjectTable.add(message),
338 _proxiedObjectTable.sendPort ];
339 }
340 }
341
342 _deserialize(var message) {
343 deserializeFunction(message) {
344 var id = message[1];
345 var port = message[2];
346 if (port == _proxiedObjectTable.sendPort) {
347 // Local function.
348 return _proxiedObjectTable.get(id);
349 } else {
350 // Remote function. Forward to its port.
351 return new JsFunction._internal(port, id);
352 }
353 }
354
355 deserializeObject(message) {
356 var id = message[1];
357 var port = message[2];
358 if (port == _proxiedObjectTable.sendPort) {
359 // Local object.
360 return _proxiedObjectTable.get(id);
361 } else {
362 // Remote object.
363 return new JsObject._internal(port, id);
364 }
365 }
366
367 if (message == null) {
368 return null; // Convert undefined to null.
369 } else if (message is String ||
370 message is num ||
371 message is bool) {
372 // Primitives are passed directly through.
373 return message;
374 } else if (message is SendPortSync) {
375 // Serialized type.
376 return message;
377 }
378 var tag = message[0];
379 switch (tag) {
380 case 'funcref': return deserializeFunction(message);
381 case 'objref': return deserializeObject(message);
382 }
383 throw 'Unsupported serialized data: $message';
384 }
OLDNEW
« no previous file with comments | « no previous file | tools/dom/scripts/dartdomgenerator.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698