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

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 and replied to comments. PTAL 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/systemnative.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'; 13 JsObject get context {
82 final String _functionIdPrefix = 'dart-fun-ref'; 14 if (_cachedContext == null) {
83 final _objectTable = new _ObjectTable(); 15 _cachedContext = _context;
84 final _functionTable = new _ObjectTable.forFunctions(); 16 }
85 17 return _cachedContext;
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 } 18 }
123 19
124 JsObject _context; 20 class JsObject extends NativeFieldWrapperClass2 {
125 21
126 /** 22 factory JsObject(JsFunction constructor, [List arguments]) => _create(construc tor, arguments);
127 * Returns a proxy to the global JavaScript context for this page.
128 */
129 JsObject get context {
130 if (_context == null) {
131 var port = _jsPortSync;
132 if (port == null) {
133 return null;
134 }
135 _context = _deserialize(_jsPortSync.callSync([]));
136 }
137 return _context;
138 }
139 23
140 /** 24 static JsObject _create(JsFunction constructor, arguments) native "JsObject_co nstructorCallback";
141 * Converts a json-like [data] to a JavaScript map or array and return a
142 * [JsObject] to it.
143 */
144 JsObject jsify(dynamic data) => data == null ? null : new JsObject._json(data);
145 25
146 /** 26 /**
147 * Converts a local Dart function to a callback that can be passed to 27 * Expert users only:
148 * JavaScript. 28 * Use this constructor only if you want to gain access to JS expandos
149 */ 29 * attached to a browser native object such as a Node.
150 class Callback implements Serializable<JsFunction> { 30 * Not all native browser objects can be converted using fromBrowserObject.
151 final bool _withThis; 31 * Currently the following types are supported:
152 final Function _function; 32 * * Node
153 JsFunction _jsFunction; 33 * * ArrayBuffer
154 34 * * Blob
155 Callback._(this._function, this._withThis) { 35 * * ImageData
156 var id = _functionTable.add(this); 36 * * IDBKeyRange
157 _jsFunction = new JsFunction._internal(_port.toSendPort(), id); 37 * TODO(jacobr): support Event, Window and NodeList as well.
158 } 38 */
159 39 factory JsObject.fromBrowserObject(var object) {
160 factory Callback(Function f) => new Callback._(f, false); 40 return _fromBrowserObject(object);
161 factory Callback.withThis(Function f) => new Callback._(f, true);
162
163 dynamic _call(List args) {
164 var arguments = (_withThis) ? args : args.sublist(1);
165 return Function.apply(_function, arguments);
166 }
167
168 JsFunction toJs() => _jsFunction;
169 }
170
171 /**
172 * Proxies to JavaScript objects.
173 */
174 class JsObject implements Serializable<JsObject> {
175 final SendPortSync _port;
176 final String _id;
177
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 } 41 }
190 42
191 /** 43 /**
192 * Constructs a [JsObject] to a new JavaScript map or list created defined via 44 * Converts a json-like [data] to a JavaScript map or array and return a
193 * Dart map or list. 45 * [JsObject] to it.
194 */ 46 */
195 factory JsObject._json(data) => _convert(data); 47 factory JsObject.jsify(data) {
196 48 if (object is Map || object is Iterable) {
197 static _convert(data) => 49 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 { 50 } else {
210 return ['simple', _serialize(data)]; 51 throw new IllegalArgumentException("data must be an Iterable or Map");
211 } 52 }
212 } 53 }
213 54
214 JsObject._internal(this._port, this._id); 55 static JSObject _jsify(data) native "Js_jsify";
215 56
216 JsObject toJs() => this; 57 static JsObject _fromBrowserObject(object) native "JsObject_fromBrowserObject" ;
217 58
218 // Resolve whether this is needed. 59 operator[](key) native "JsObject_[]";
219 operator[](arg) => _forward(this, '[]', 'method', [ arg ]); 60 operator[]=(key, value) native "JsObject_[]=";
220 61
221 // Resolve whether this is needed. 62 int get hashCode native "JsObject_hashCode";
222 operator[]=(key, value) => _forward(this, '[]=', 'method', [ key, value ]);
223 63
224 int get hashCode => _id.hashCode; 64 operator==(other) => other is JsObject && _identityEquality(this, other);
225 65
226 // Test if this is equivalent to another Proxy. This essentially 66 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 67
230 /** 68 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 69
235 /** 70 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 71
242 /** 72 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 73
248 String toString() { 74 String toString() {
249 try { 75 try {
250 return _forward(this, 'toString', 'method', []); 76 return _toString();
251 } catch(e) { 77 } catch(e) {
252 return super.toString(); 78 return super.toString();
253 } 79 }
254 } 80 }
255 81
256 callMethod(String name, [List args]) { 82 String _toString() native "JsObject_toString";
257 return _forward(this, name, 'method', args != null ? args : []);
258 }
259 83
260 // Forward member accesses to the backing JavaScript object. 84 callMethod(String name, [List args]) native "JsObject_callMethod";
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 } 85 }
273 86
274 /// A [JsObject] subtype to JavaScript functions. 87 class JsFunction extends JsObject {
275 class JsFunction extends JsObject implements Serializable<JsFunction> { 88 /**
276 JsFunction._internal(SendPortSync port, String id) 89 * Returns a [JsFunction] that captures its 'this' binding and calls [f]
277 : super._internal(port, id); 90 * with the value of this passed as the first argument.
91 */
92 factory JsFunction.withThis(Function f) => _withThis(f);
278 93
279 apply(thisArg, [List args]) { 94 apply(List args, {thisArg}) native "JsFunction_apply";
280 return JsObject._forward(this, '', 'apply', 95
281 [thisArg]..addAll(args == null ? [] : args)); 96 static JsFunction _withThis(Function f) native "JsFunction_withThis";
282 }
283 } 97 }
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/systemnative.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698