OLD | NEW |
---|---|
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 /** |
6 * The js.dart library provides simple JavaScript invocation from Dart that | 6 * The js.dart library provides simple JavaScript invocation from Dart that |
7 * works on both Dartium and on other modern browsers via Dart2JS. | 7 * works on both Dartium and on other modern browsers via Dart2JS. |
8 * | 8 * |
9 * It provides a model based on scoped [JsObject] objects. Proxies give Dart | 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 | 10 * code access to JavaScript objects, fields, and functions as well as the |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
59 * var foo = new JsObject(context['Foo'], [42]); | 59 * var foo = new JsObject(context['Foo'], [42]); |
60 * var foo2 = foo.callMethod('add', [foo]); | 60 * var foo2 = foo.callMethod('add', [foo]); |
61 * print(foo2['x']); | 61 * print(foo2['x']); |
62 * | 62 * |
63 * will construct a JavaScript Foo object with the parameter 42, invoke its | 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. | 64 * add method, and return a [JsObject] to a new Foo object whose x field is 84. |
65 */ | 65 */ |
66 | 66 |
67 library dart.js; | 67 library dart.js; |
68 | 68 |
69 import 'dart:collection' show ListMixin, Maps; | |
69 import 'dart:html'; | 70 import 'dart:html'; |
70 import 'dart:isolate'; | 71 import 'dart:isolate'; |
71 | 72 |
72 // Global ports to manage communication from Dart to JS. | 73 // Global ports to manage communication from Dart to JS. |
73 SendPortSync _jsPortSync = window.lookupPort('dart-js-context'); | 74 SendPortSync _jsPortSync = window.lookupPort('dart-js-context'); |
74 SendPortSync _jsPortCreate = window.lookupPort('dart-js-create'); | 75 SendPortSync _jsPortCreate = window.lookupPort('dart-js-create'); |
75 SendPortSync _jsPortInstanceof = window.lookupPort('dart-js-instanceof'); | 76 SendPortSync _jsPortInstanceof = window.lookupPort('dart-js-instanceof'); |
76 SendPortSync _jsPortDeleteProperty = window.lookupPort('dart-js-delete-property' ); | 77 SendPortSync _jsPortDeleteProperty = window.lookupPort('dart-js-delete-property' ); |
77 SendPortSync _jsPortConvert = window.lookupPort('dart-js-convert'); | 78 SendPortSync _jsPortConvert = window.lookupPort('dart-js-convert'); |
78 | 79 |
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
191 return _forward(this, 'toString', 'method', []); | 192 return _forward(this, 'toString', 'method', []); |
192 } catch(e) { | 193 } catch(e) { |
193 return super.toString(); | 194 return super.toString(); |
194 } | 195 } |
195 } | 196 } |
196 | 197 |
197 callMethod(String name, [List args]) { | 198 callMethod(String name, [List args]) { |
198 return _forward(this, name, 'method', args != null ? args : []); | 199 return _forward(this, name, 'method', args != null ? args : []); |
199 } | 200 } |
200 | 201 |
202 Map<String, dynamic> asDartMap() => new _JsObjectAsMap(this); | |
203 | |
201 // Forward member accesses to the backing JavaScript object. | 204 // Forward member accesses to the backing JavaScript object. |
202 static _forward(JsObject receiver, String member, String kind, List args) { | 205 static _forward(JsObject receiver, String member, String kind, List args) { |
203 var result = receiver._port.callSync([receiver._id, member, kind, | 206 var result = receiver._port.callSync([receiver._id, member, kind, |
204 args.map(_serialize).toList()]); | 207 args.map(_serialize).toList()]); |
205 switch (result[0]) { | 208 switch (result[0]) { |
206 case 'return': return _deserialize(result[1]); | 209 case 'return': return _deserialize(result[1]); |
207 case 'throws': throw _deserialize(result[1]); | 210 case 'throws': throw _deserialize(result[1]); |
208 case 'none': throw new NoSuchMethodError(receiver, member, args, {}); | 211 case 'none': throw new NoSuchMethodError(receiver, member, args, {}); |
209 default: throw 'Invalid return value'; | 212 default: throw 'Invalid return value'; |
210 } | 213 } |
211 } | 214 } |
212 } | 215 } |
213 | 216 |
214 /// A [JsObject] subtype to JavaScript functions. | 217 /// A [JsObject] subtype to JavaScript functions. |
215 class JsFunction extends JsObject implements Serializable<JsFunction> { | 218 class JsFunction extends JsObject implements Serializable<JsFunction> { |
216 JsFunction._internal(SendPortSync port, String id) | 219 JsFunction._internal(SendPortSync port, String id) |
217 : super._internal(port, id); | 220 : super._internal(port, id); |
218 | 221 |
219 apply(thisArg, [List args]) { | 222 apply(thisArg, [List args]) { |
220 return JsObject._forward(this, '', 'apply', | 223 return JsObject._forward(this, '', 'apply', |
221 [thisArg]..addAll(args == null ? [] : args)); | 224 [thisArg]..addAll(args == null ? [] : args)); |
222 } | 225 } |
223 } | 226 } |
224 | 227 |
228 /// A [JsObject] subtype for JavaScript arrays. | |
229 class JsArray extends JsObject with ListMixin { | |
230 JsArray._internal(SendPortSync port, String id) : super._internal(port, id); | |
231 | |
232 // Iterable | |
233 /*@override*/ int get length => super['length']; | |
234 | |
235 // Collection | |
236 /*@override*/ void add(value) { callMethod('push', [value]); } | |
justinfagnani
2013/08/22 18:29:36
I think you should override addAll() also. The def
alexandre.ardhuin
2013/08/23 21:08:33
Done.
| |
237 | |
238 // List | |
239 /*@override*/ operator [](index) { | |
240 if (index is int && (index < 0 || index >= this.length)) { | |
241 throw new RangeError.value(index); | |
242 } | |
243 return super[index]; | |
244 } | |
245 /*@override*/ void operator []=(index, value) { | |
246 if (index is int && (index < 0 || index >= this.length)) { | |
247 throw new RangeError.value(index); | |
248 } | |
249 super[index] = value; | |
250 } | |
251 /*@override*/ void set length(int length) { super['length'] = length; } | |
252 /*@override*/ void sort([int compare(a, b)]) { | |
253 final sortedList = toList()..sort(compare); | |
254 setRange(0, sortedList.length, sortedList); | |
255 } | |
256 /*@override*/ void insert(int index, element) { | |
257 callMethod('splice', [index, 0, element]); | |
258 } | |
259 /*@override*/ removeAt(int index) { | |
260 if (index < 0 || index >= this.length) throw new RangeError.value(index); | |
261 return callMethod('splice', [index, 1])[0]; | |
262 } | |
263 /*@override*/ removeLast() => callMethod('pop'); | |
264 /*@override*/ void setRange(int start, int length, List from, | |
265 [int startFrom = 0]) { | |
266 final args = [start, length]; | |
267 for(int i = startFrom; i < startFrom + length; i++) { | |
268 args.add(from[i]); | |
269 } | |
270 callMethod('splice', args); | |
271 } | |
272 /*@override*/ void removeRange(int start, int end) { | |
273 callMethod('splice', [start, end - start]); | |
274 } | |
275 } | |
276 | |
277 class _JsObjectAsMap implements Map<String, dynamic> { | |
278 JsObject _jsObject; | |
279 | |
280 _JsObjectAsMap(this._jsObject); | |
281 | |
282 /*@override*/ operator [](String key) => _jsObject[key]; | |
283 /*@override*/ void operator []=(String key, value) { | |
284 _jsObject[key] = value; | |
285 } | |
286 /*@override*/ remove(String key) { | |
287 final value = this[key]; | |
288 _jsObject.deleteProperty(key); | |
289 return value; | |
290 } | |
291 /*@override*/ Iterable<String> get keys => | |
292 context['Object'].callMethod('keys', [_jsObject]); | |
293 | |
294 // use Maps to implement functions | |
295 /*@override*/ bool containsValue(value) => Maps.containsValue(this, value); | |
justinfagnani
2013/08/22 18:29:36
I think containsValue should be implemented in JS
| |
296 /*@override*/ bool containsKey(String key) => Maps.containsKey(this, key); | |
justinfagnani
2013/08/22 18:29:36
same here, but much less important. rather than tr
| |
297 /*@override*/ putIfAbsent(String key, ifAbsent()) => | |
298 Maps.putIfAbsent(this, key, ifAbsent); | |
299 /*@override*/ void addAll(Map<String, dynamic> other) { | |
justinfagnani
2013/08/22 18:29:36
same here, but more important so that conversion /
| |
300 if (other != null) { | |
301 other.forEach((k,v) => this[k] = v); | |
302 } | |
303 } | |
304 /*@override*/ void clear() => Maps.clear(this); | |
305 /*@override*/ void forEach(void f(String key, value)) => Maps.forEach(this, f) ; | |
justinfagnani
2013/08/22 18:29:36
long line
alexandre.ardhuin
2013/08/23 21:08:33
Done.
| |
306 /*@override*/ Iterable get values => Maps.getValues(this); | |
justinfagnani
2013/08/22 18:29:36
also implement in JS
| |
307 /*@override*/ int get length => Maps.length(this); | |
justinfagnani
2013/08/22 18:29:36
these are especially important to implement in JS
alexandre.ardhuin
2013/08/23 21:08:33
The result of "get keys" is not the entire key lis
| |
308 /*@override*/ bool get isEmpty => Maps.isEmpty(this); | |
309 /*@override*/ bool get isNotEmpty => Maps.isNotEmpty(this); | |
310 } | |
311 | |
225 /// Marker class used to indicate it is serializable to js. If a class is a | 312 /// 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 | 313 /// [Serializable] the "toJs" method will be called and the result will be used |
227 /// as value. | 314 /// as value. |
228 abstract class Serializable<T> { | 315 abstract class Serializable<T> { |
229 T toJs(); | 316 T toJs(); |
230 } | 317 } |
231 | 318 |
232 // A table to managed local Dart objects that are proxied in JavaScript. | 319 // A table to managed local Dart objects that are proxied in JavaScript. |
233 class _ProxiedObjectTable { | 320 class _ProxiedObjectTable { |
234 // Debugging name. | 321 // Debugging name. |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
311 // Remote function proxy. | 398 // Remote function proxy. |
312 return [ 'funcref', message._id, message._port ]; | 399 return [ 'funcref', message._id, message._port ]; |
313 } else if (message is JsObject) { | 400 } else if (message is JsObject) { |
314 // Remote object proxy. | 401 // Remote object proxy. |
315 return [ 'objref', message._id, message._port ]; | 402 return [ 'objref', message._id, message._port ]; |
316 } else if (message is Serializable) { | 403 } else if (message is Serializable) { |
317 // use of result of toJs() | 404 // use of result of toJs() |
318 return _serialize(message.toJs()); | 405 return _serialize(message.toJs()); |
319 } else if (message is Function) { | 406 } else if (message is Function) { |
320 return _serialize(new Callback(message)); | 407 return _serialize(new Callback(message)); |
408 } else if (message is Map) { | |
409 return _serialize(jsify(message)); | |
410 } else if (message is Iterable) { | |
411 return _serialize(jsify(message)); | |
321 } else { | 412 } else { |
322 // Local object proxy. | 413 // Local object proxy. |
323 return [ 'objref', | 414 return [ 'objref', |
324 _proxiedObjectTable.add(message), | 415 _proxiedObjectTable.add(message), |
325 _proxiedObjectTable.sendPort ]; | 416 _proxiedObjectTable.sendPort ]; |
326 } | 417 } |
327 } | 418 } |
328 | 419 |
329 _deserialize(var message) { | 420 _deserialize(var message) { |
330 deserializeFunction(message) { | 421 deserializeFunction(message) { |
(...skipping 13 matching lines...) Expand all Loading... | |
344 var port = message[2]; | 435 var port = message[2]; |
345 if (port == _proxiedObjectTable.sendPort) { | 436 if (port == _proxiedObjectTable.sendPort) { |
346 // Local object. | 437 // Local object. |
347 return _proxiedObjectTable.get(id); | 438 return _proxiedObjectTable.get(id); |
348 } else { | 439 } else { |
349 // Remote object. | 440 // Remote object. |
350 return new JsObject._internal(port, id); | 441 return new JsObject._internal(port, id); |
351 } | 442 } |
352 } | 443 } |
353 | 444 |
445 deserializeArray(message) { | |
446 var id = message[1]; | |
447 var port = message[2]; | |
448 return new JsArray._internal(port, id); | |
449 } | |
450 | |
354 if (message == null) { | 451 if (message == null) { |
355 return null; // Convert undefined to null. | 452 return null; // Convert undefined to null. |
356 } else if (message is String || | 453 } else if (message is String || |
357 message is num || | 454 message is num || |
358 message is bool) { | 455 message is bool) { |
359 // Primitives are passed directly through. | 456 // Primitives are passed directly through. |
360 return message; | 457 return message; |
361 } else if (message is SendPortSync) { | 458 } else if (message is SendPortSync) { |
362 // Serialized type. | 459 // Serialized type. |
363 return message; | 460 return message; |
364 } | 461 } |
365 var tag = message[0]; | 462 var tag = message[0]; |
366 switch (tag) { | 463 switch (tag) { |
367 case 'funcref': return deserializeFunction(message); | 464 case 'funcref': return deserializeFunction(message); |
368 case 'objref': return deserializeObject(message); | 465 case 'objref': return deserializeObject(message); |
466 case 'arrayref': return deserializeArray(message); | |
369 } | 467 } |
370 throw 'Unsupported serialized data: $message'; | 468 throw 'Unsupported serialized data: $message'; |
371 } | 469 } |
OLD | NEW |