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 // method to implement for ListMixin |
| 233 |
| 234 int get length => super['length']; |
| 235 void set length(int length) { super['length'] = length; } |
| 236 operator [](index) { |
| 237 if (index is int && (index < 0 || index >= this.length)) { |
| 238 throw new RangeError.value(index); |
| 239 } |
| 240 return super[index]; |
| 241 } |
| 242 void operator []=(index, value) { |
| 243 if (index is int && (index < 0 || index >= this.length)) { |
| 244 throw new RangeError.value(index); |
| 245 } |
| 246 super[index] = value; |
| 247 } |
| 248 |
| 249 // overriden methods for better performance |
| 250 |
| 251 void add(value) { callMethod('push', [value]); } |
| 252 void addAll(Iterable iterable) { callMethod('push', iterable.toList()); } |
| 253 void sort([int compare(a, b)]) { |
| 254 final sortedList = toList()..sort(compare); |
| 255 setRange(0, sortedList.length, sortedList); |
| 256 } |
| 257 void insert(int index, element) { |
| 258 callMethod('splice', [index, 0, element]); |
| 259 } |
| 260 removeAt(int index) { |
| 261 if (index < 0 || index >= this.length) throw new RangeError.value(index); |
| 262 return callMethod('splice', [index, 1])[0]; |
| 263 } |
| 264 removeLast() => callMethod('pop'); |
| 265 void setRange(int start, int length, List from, |
| 266 [int startFrom = 0]) { |
| 267 final args = [start, length]; |
| 268 for(int i = startFrom; i < startFrom + length; i++) { |
| 269 args.add(from[i]); |
| 270 } |
| 271 callMethod('splice', args); |
| 272 } |
| 273 void removeRange(int start, int end) { |
| 274 callMethod('splice', [start, end - start]); |
| 275 } |
| 276 } |
| 277 |
| 278 class _JsObjectAsMap implements Map<String, dynamic> { |
| 279 JsObject _jsObject; |
| 280 |
| 281 _JsObjectAsMap(this._jsObject); |
| 282 |
| 283 operator [](String key) => _jsObject[key]; |
| 284 void operator []=(String key, value) { |
| 285 _jsObject[key] = value; |
| 286 } |
| 287 remove(String key) { |
| 288 final value = this[key]; |
| 289 _jsObject.deleteProperty(key); |
| 290 return value; |
| 291 } |
| 292 Iterable<String> get keys => |
| 293 context['Object'].callMethod('keys', [_jsObject]); |
| 294 |
| 295 bool containsValue(value) => |
| 296 JsObject._forward(_jsObject, 'containsValue', 'asMap', [value]); |
| 297 bool containsKey(String key) => _jsObject.hasProperty(key); |
| 298 putIfAbsent(String key, ifAbsent()) => Maps.putIfAbsent(this, key, ifAbsent); |
| 299 void addAll(Map<String, dynamic> other) { |
| 300 if (other != null) { |
| 301 JsObject._forward(_jsObject, 'addAll', 'asMap', [jsify(other)]); |
| 302 } |
| 303 } |
| 304 void clear() => Maps.clear(this); |
| 305 void forEach(void f(String key, value)) => Maps.forEach(this, f); |
| 306 Iterable get values => |
| 307 JsObject._forward(_jsObject, 'values', 'asMap', []); |
| 308 int get length => |
| 309 JsObject._forward(_jsObject, 'length', 'asMap', []); |
| 310 bool get isEmpty => Maps.isEmpty(this); |
| 311 bool get isNotEmpty => Maps.isNotEmpty(this); |
| 312 } |
| 313 |
225 /// Marker class used to indicate it is serializable to js. If a class is a | 314 /// 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 | 315 /// [Serializable] the "toJs" method will be called and the result will be used |
227 /// as value. | 316 /// as value. |
228 abstract class Serializable<T> { | 317 abstract class Serializable<T> { |
229 T toJs(); | 318 T toJs(); |
230 } | 319 } |
231 | 320 |
232 // A table to managed local Dart objects that are proxied in JavaScript. | 321 // A table to managed local Dart objects that are proxied in JavaScript. |
233 class _ProxiedObjectTable { | 322 class _ProxiedObjectTable { |
234 // Debugging name. | 323 // Debugging name. |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
311 // Remote function proxy. | 400 // Remote function proxy. |
312 return [ 'funcref', message._id, message._port ]; | 401 return [ 'funcref', message._id, message._port ]; |
313 } else if (message is JsObject) { | 402 } else if (message is JsObject) { |
314 // Remote object proxy. | 403 // Remote object proxy. |
315 return [ 'objref', message._id, message._port ]; | 404 return [ 'objref', message._id, message._port ]; |
316 } else if (message is Serializable) { | 405 } else if (message is Serializable) { |
317 // use of result of toJs() | 406 // use of result of toJs() |
318 return _serialize(message.toJs()); | 407 return _serialize(message.toJs()); |
319 } else if (message is Function) { | 408 } else if (message is Function) { |
320 return _serialize(new Callback(message)); | 409 return _serialize(new Callback(message)); |
| 410 } else if (message is Map) { |
| 411 return _serialize(jsify(message)); |
| 412 } else if (message is Iterable) { |
| 413 return _serialize(jsify(message)); |
321 } else { | 414 } else { |
322 // Local object proxy. | 415 // Local object proxy. |
323 return [ 'objref', | 416 return [ 'objref', |
324 _proxiedObjectTable.add(message), | 417 _proxiedObjectTable.add(message), |
325 _proxiedObjectTable.sendPort ]; | 418 _proxiedObjectTable.sendPort ]; |
326 } | 419 } |
327 } | 420 } |
328 | 421 |
329 _deserialize(var message) { | 422 _deserialize(var message) { |
330 deserializeFunction(message) { | 423 deserializeFunction(message) { |
(...skipping 13 matching lines...) Expand all Loading... |
344 var port = message[2]; | 437 var port = message[2]; |
345 if (port == _proxiedObjectTable.sendPort) { | 438 if (port == _proxiedObjectTable.sendPort) { |
346 // Local object. | 439 // Local object. |
347 return _proxiedObjectTable.get(id); | 440 return _proxiedObjectTable.get(id); |
348 } else { | 441 } else { |
349 // Remote object. | 442 // Remote object. |
350 return new JsObject._internal(port, id); | 443 return new JsObject._internal(port, id); |
351 } | 444 } |
352 } | 445 } |
353 | 446 |
| 447 deserializeArray(message) { |
| 448 var id = message[1]; |
| 449 var port = message[2]; |
| 450 return new JsArray._internal(port, id); |
| 451 } |
| 452 |
354 if (message == null) { | 453 if (message == null) { |
355 return null; // Convert undefined to null. | 454 return null; // Convert undefined to null. |
356 } else if (message is String || | 455 } else if (message is String || |
357 message is num || | 456 message is num || |
358 message is bool) { | 457 message is bool) { |
359 // Primitives are passed directly through. | 458 // Primitives are passed directly through. |
360 return message; | 459 return message; |
361 } else if (message is SendPortSync) { | 460 } else if (message is SendPortSync) { |
362 // Serialized type. | 461 // Serialized type. |
363 return message; | 462 return message; |
364 } | 463 } |
365 var tag = message[0]; | 464 var tag = message[0]; |
366 switch (tag) { | 465 switch (tag) { |
367 case 'funcref': return deserializeFunction(message); | 466 case 'funcref': return deserializeFunction(message); |
368 case 'objref': return deserializeObject(message); | 467 case 'objref': return deserializeObject(message); |
| 468 case 'arrayref': return deserializeArray(message); |
369 } | 469 } |
370 throw 'Unsupported serialized data: $message'; | 470 throw 'Unsupported serialized data: $message'; |
371 } | 471 } |
OLD | NEW |