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

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: Rebased 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 /**
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; 5 library dart.js;
68 6
69 import 'dart:collection' show HashMap; 7 import 'dart:nativewrappers';
70 import 'dart:html';
71 import 'dart:isolate';
72 8
73 // Global ports to manage communication from Dart to JS. 9 JsObject _cachedContext;
74 10
75 SendPortSync _jsPortSync = window.lookupPort('dart-js-context'); 11 JsObject get _context native "Js_context_Callback";
76 SendPortSync _jsPortCreate = window.lookupPort('dart-js-create');
77 SendPortSync _jsPortInstanceof = window.lookupPort('dart-js-instanceof');
78 SendPortSync _jsPortDeleteProperty = window.lookupPort('dart-js-delete-property' );
79 SendPortSync _jsPortConvert = window.lookupPort('dart-js-convert');
80 12
81 final String _objectIdPrefix = 'dart-obj-ref';
82 final String _functionIdPrefix = 'dart-fun-ref';
83 final _objectTable = new _ObjectTable();
84 final _functionTable = new _ObjectTable.forFunctions();
85
86 // Port to handle and forward requests to the underlying Dart objects.
87 // A remote proxy is uniquely identified by an ID and SendPortSync.
88 ReceivePortSync _port = new ReceivePortSync()
89 ..receive((msg) {
90 try {
91 var id = msg[0];
92 var method = msg[1];
93 if (method == '#call') {
94 var receiver = _getObjectTable(id).get(id);
95 var result;
96 if (receiver is Function) {
97 // remove the first argument, which is 'this', but never
98 // used for a raw function
99 var args = msg[2].sublist(1).map(_deserialize).toList();
100 result = Function.apply(receiver, args);
101 } else if (receiver is Callback) {
102 var args = msg[2].map(_deserialize).toList();
103 result = receiver._call(args);
104 } else {
105 throw new StateError('bad function type: $receiver');
106 }
107 return ['return', _serialize(result)];
108 } else {
109 // TODO(vsm): Support a mechanism to register a handler here.
110 throw 'Invocation unsupported on non-function Dart proxies';
111 }
112 } catch (e) {
113 // TODO(vsm): callSync should just handle exceptions itself.
114 return ['throws', '$e'];
115 }
116 });
117
118 _ObjectTable _getObjectTable(String id) {
119 if (id.startsWith(_functionIdPrefix)) return _functionTable;
120 if (id.startsWith(_objectIdPrefix)) return _objectTable;
121 throw new ArgumentError('internal error: invalid object id: $id');
122 }
123
124 JsObject _context;
125
126 /**
127 * Returns a proxy to the global JavaScript context for this page.
128 */
129 JsObject get context { 13 JsObject get context {
130 if (_context == null) { 14 if (_cachedContext == null) {
131 var port = _jsPortSync; 15 _cachedContext = _context;
132 if (port == null) {
133 return null;
134 }
135 _context = _deserialize(_jsPortSync.callSync([]));
136 } 16 }
137 return _context; 17 return _cachedContext;
138 } 18 }
139 19
140 /** 20 /**
141 * Converts a json-like [data] to a JavaScript map or array and return a 21 * Returns a [JsFunction] that captures its 'this' binding and calls [f]
142 * [JsObject] to it. 22 * with the value of this passed as the first argument.
143 */ 23 */
144 JsObject jsify(dynamic data) => data == null ? null : new JsObject._json(data); 24 JsFunction captureThis(Function f) native "Js_captureThis";
justinfagnani 2013/10/18 01:05:47 This has moved to the named constructor JsFunction
Jacob 2013/10/18 21:56:25 Done.
145 25
146 /** 26 class JsObject extends NativeFieldWrapperClass1 {
147 * Converts a local Dart function to a callback that can be passed to
148 * JavaScript.
149 */
150 class Callback implements Serializable<JsFunction> {
151 final bool _withThis;
152 final Function _function;
153 JsFunction _jsFunction;
154 27
155 Callback._(this._function, this._withThis) { 28 factory JsObject(JsFunction constructor, [List arguments]) => _create(construc tor, arguments);
156 var id = _functionTable.add(this);
157 _jsFunction = new JsFunction._internal(_port.toSendPort(), id);
158 }
159 29
160 factory Callback(Function f) => new Callback._(f, false); 30 static JsObject _create(JsFunction constructor, arguments) native "JsObject_co nstructorCallback";
161 factory Callback.withThis(Function f) => new Callback._(f, true);
162 31
163 dynamic _call(List args) { 32 /**
164 var arguments = (_withThis) ? args : args.sublist(1); 33 * Expert users only:
165 return Function.apply(_function, arguments); 34 * Use this constructor only if you want to gain access to JS expandos
166 } 35 * attached to a browser native object such as a Node.
167 36 * Not all native browser objects can be converted using fromBrowserObject.
168 JsFunction toJs() => _jsFunction; 37 * Currently the following types are supported:
169 } 38 * * Node
170 39 * * ArrayBuffer
171 /** 40 * * Blob
172 * Proxies to JavaScript objects. 41 * * ImageData
173 */ 42 * * IDBKeyRange
174 class JsObject implements Serializable<JsObject> { 43 * TODO(jacobr): support Event and NodeList as well.
justinfagnani 2013/10/18 01:05:47 Window too, if we figure out what to do with other
justinfagnani 2013/10/18 01:05:47 Window too, if we figure out what to do with other
Jacob 2013/10/18 21:56:25 Done.
Jacob 2013/10/18 21:56:25 Done.
175 final SendPortSync _port; 44 */
176 final String _id; 45 factory JsObject.fromBrowserObject(var object) {
177 46 return _fromBrowserObject(object);
178 /**
179 * Constructs a [JsObject] to a new JavaScript object by invoking a (proxy to
180 * a) JavaScript [constructor]. The [arguments] list should contain either
181 * primitive values, DOM elements, or Proxies.
182 */
183 factory JsObject(Serializable<JsFunction> constructor, [List arguments]) {
184 final params = [constructor];
185 if (arguments != null) params.addAll(arguments);
186 final serialized = params.map(_serialize).toList();
187 final result = _jsPortCreate.callSync(serialized);
188 return _deserialize(result);
189 } 47 }
190 48
191 /** 49 /**
192 * Constructs a [JsObject] to a new JavaScript map or list created defined via 50 * Converts a json-like [data] to a JavaScript map or array and return a
193 * Dart map or list. 51 * [JsObject] to it.
194 */ 52 */
195 factory JsObject._json(data) => _convert(data); 53 factory JsObject.jsify(data) {
196 54 if (object is Map || object is Iterable) {
197 static _convert(data) => 55 return _jsify(data);
198 _deserialize(_jsPortConvert.callSync(_serializeDataTree(data)));
199
200 static _serializeDataTree(data) {
201 if (data is Map) {
202 final entries = new List();
203 for (var key in data.keys) {
204 entries.add([key, _serializeDataTree(data[key])]);
205 }
206 return ['map', entries];
207 } else if (data is Iterable) {
208 return ['list', data.map(_serializeDataTree).toList()];
209 } else { 56 } else {
210 return ['simple', _serialize(data)]; 57 throw new IllegalArgumentException("data must be an Iterable or Map");
211 } 58 }
212 } 59 }
213 60
214 JsObject._internal(this._port, this._id); 61 static JSObject _jsify(data) native "Js_jsify";
215 62
216 JsObject toJs() => this; 63 static JsObject _fromBrowserObject(object) native "JsObject_fromBrowserObject" ;
217 64
218 // Resolve whether this is needed. 65 operator[](key) native "JsObject_[]";
219 operator[](arg) => _forward(this, '[]', 'method', [ arg ]); 66 operator[]=(key, value) native "JsObject_[]=";
220 67
221 // Resolve whether this is needed. 68 int get hashCode native "JsObject_hashCode";
222 operator[]=(key, value) => _forward(this, '[]=', 'method', [ key, value ]);
223 69
224 int get hashCode => _id.hashCode; 70 operator==(other) => other is JsObject && _identityEquality(this, other);
225 71
226 // Test if this is equivalent to another Proxy. This essentially 72 static bool _identityEquality(JsObject a, JsObject b) native "JsObject_identit yEquality";
227 // maps to JavaScript's === operator.
228 operator==(other) => other is JsObject && this._id == other._id;
229 73
230 /** 74 bool hasProperty(String property) native "JsObject_hasProperty";
231 * Check if this [JsObject] has a [name] property.
232 */
233 bool hasProperty(String name) => _forward(this, name, 'hasProperty', []);
234 75
235 /** 76 void deleteProperty(JsFunction name) native "JsObject_deleteProperty";
236 * Delete the [name] property.
237 */
238 void deleteProperty(String name) {
239 _jsPortDeleteProperty.callSync([this, name].map(_serialize).toList());
240 }
241 77
242 /** 78 bool instanceof(JsFunction type) native "JsObject_instanceof";
243 * Check if this [JsObject] is instance of [type].
244 */
245 bool instanceof(Serializable<JsFunction> type) =>
246 _jsPortInstanceof.callSync([this, type].map(_serialize).toList());
247 79
248 String toString() { 80 String toString() {
249 try { 81 try {
250 return _forward(this, 'toString', 'method', []); 82 return _toString();
251 } catch(e) { 83 } catch(e) {
252 return super.toString(); 84 return super.toString();
253 } 85 }
254 } 86 }
255 87
256 callMethod(String name, [List args]) { 88 String _toString() native "JsObject_toString";
257 return _forward(this, name, 'method', args != null ? args : []);
258 }
259 89
260 // Forward member accesses to the backing JavaScript object. 90 callMethod(String name, [List args]) native "JsObject_callMethod";
justinfagnani 2013/10/18 01:05:47 I wonder if name should have the same type restric
Jacob 2013/10/18 21:56:25 I think name should be a string. You can't write a
261 static _forward(JsObject receiver, String member, String kind, List args) {
262 var result = receiver._port.callSync([receiver._id, member, kind,
263 args.map(_serialize).toList()]);
264 switch (result[0]) {
265 case 'return': return _deserialize(result[1]);
266 case 'throws': throw _deserialize(result[1]);
267 case 'none':
268 throw new NoSuchMethodError(receiver, new Symbol(member), args, {});
269 default: throw 'Invalid return value';
270 }
271 }
272 } 91 }
273 92
274 /// A [JsObject] subtype to JavaScript functions. 93 class JsFunction extends JsObject {
275 class JsFunction extends JsObject implements Serializable<JsFunction> { 94 apply([thisArg, List args]) native "JsFunction_apply";
justinfagnani 2013/10/18 01:05:47 I think (possibly) thisArg should either come seco
Jacob 2013/10/18 21:56:25 synced offline. Made thisArg named and moved it to
276 JsFunction._internal(SendPortSync port, String id)
277 : super._internal(port, id);
278
279 apply(thisArg, [List args]) {
280 return JsObject._forward(this, '', 'apply',
281 [thisArg]..addAll(args == null ? [] : args));
282 }
283 } 95 }
284
285 /// Marker class used to indicate it is serializable to js. If a class is a
286 /// [Serializable] the "toJs" method will be called and the result will be used
287 /// as value.
288 abstract class Serializable<T> {
289 T toJs();
290 }
291
292 class _ObjectTable {
293 final String name;
294 final Map<String, Object> objects;
295 final Map<Object, String> ids;
296 int nextId = 0;
297
298 // Creates a table that uses an identity Map to store IDs
299 _ObjectTable()
300 : name = _objectIdPrefix,
301 objects = new HashMap<String, Object>(),
302 ids = new HashMap<Object, String>.identity();
303
304 // Creates a table that uses an equality-based Map to store IDs, since
305 // closurized methods may be equal, but not identical
306 _ObjectTable.forFunctions()
307 : name = _functionIdPrefix,
308 objects = new HashMap<String, Object>(),
309 ids = new HashMap<Object, String>();
310
311 // Adds a new object to the table. If [id] is not given, a new unique ID is
312 // generated. Returns the ID.
313 String add(Object o, {String id}) {
314 // TODO(vsm): Cache x and reuse id.
315 if (id == null) id = ids[o];
316 if (id == null) id = '$name-${nextId++}';
317 ids[o] = id;
318 objects[id] = o;
319 return id;
320 }
321
322 // Gets an object by ID.
323 Object get(String id) => objects[id];
324
325 bool contains(String id) => objects.containsKey(id);
326
327 String getId(Object o) => ids[o];
328
329 // Gets the current number of objects kept alive by this table.
330 get count => objects.length;
331 }
332
333 // Dart serialization support.
334
335 _serialize(var message) {
336 if (message == null) {
337 return null; // Convert undefined to null.
338 } else if (message is String ||
339 message is num ||
340 message is bool) {
341 // Primitives are passed directly through.
342 return message;
343 } else if (message is SendPortSync) {
344 // Non-proxied objects are serialized.
345 return message;
346 } else if (message is JsFunction) {
347 // Remote function proxy.
348 return ['funcref', message._id, message._port];
349 } else if (message is JsObject) {
350 // Remote object proxy.
351 return ['objref', message._id, message._port];
352 } else if (message is Serializable) {
353 // use of result of toJs()
354 return _serialize(message.toJs());
355 } else if (message is Function) {
356 var id = _functionTable.getId(message);
357 if (id != null) {
358 return ['funcref', id, _port.toSendPort()];
359 }
360 id = _functionTable.add(message);
361 return ['funcref', id, _port.toSendPort()];
362 } else {
363 // Local object proxy.
364 return ['objref', _objectTable.add(message), _port.toSendPort()];
365 }
366 }
367
368 _deserialize(var message) {
369 deserializeFunction(message) {
370 var id = message[1];
371 var port = message[2];
372 if (port == _port.toSendPort()) {
373 // Local function.
374 return _functionTable.get(id);
375 } else {
376 // Remote function.
377 var jsFunction = _functionTable.get(id);
378 if (jsFunction == null) {
379 jsFunction = new JsFunction._internal(port, id);
380 _functionTable.add(jsFunction, id: id);
381 }
382 return jsFunction;
383 }
384 }
385
386 deserializeObject(message) {
387 var id = message[1];
388 var port = message[2];
389 if (port == _port.toSendPort()) {
390 // Local object.
391 return _objectTable.get(id);
392 } else {
393 // Remote object.
394 var jsObject = _objectTable.get(id);
395 if (jsObject == null) {
396 jsObject = new JsObject._internal(port, id);
397 _objectTable.add(jsObject, id: id);
398 }
399 return jsObject;
400 }
401 }
402
403 if (message == null) {
404 return null; // Convert undefined to null.
405 } else if (message is String ||
406 message is num ||
407 message is bool) {
408 // Primitives are passed directly through.
409 return message;
410 } else if (message is SendPortSync) {
411 // Serialized type.
412 return message;
413 }
414 var tag = message[0];
415 switch (tag) {
416 case 'funcref': return deserializeFunction(message);
417 case 'objref': return deserializeObject(message);
418 }
419 throw 'Unsupported serialized data: $message';
420 }
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