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

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

Issue 27514003: A few API changes: (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: 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
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 {
21 JsObject.internal();
125 22
126 /** 23 factory JsObject(JsFunction constructor, [List arguments]) => _create(construc tor, arguments);
127 * Returns a proxy to the global JavaScript context for this page. 24
128 */ 25 static JsObject _create(JsFunction constructor, arguments) native "JsObject_co nstructorCallback";
129 JsObject get context { 26
130 if (_context == null) { 27 /**
131 var port = _jsPortSync; 28 * Expert users only:
132 if (port == null) { 29 * Use this constructor only if you want to gain access to JS expandos
133 return null; 30 * attached to a browser native object such as a Node.
31 * Not all native browser objects can be converted using fromBrowserObject.
32 * Currently the following types are supported:
33 * * Node
34 * * ArrayBuffer
35 * * Blob
36 * * ImageData
37 * * IDBKeyRange
38 * TODO(jacobr): support Event, Window and NodeList as well.
39 */
40 factory JsObject.fromBrowserObject(var object) {
41 if (object is num || object is String || object is bool || object == null) {
42 throw new ArgumentError(
43 "object cannot be a num, string, bool, or null");
134 } 44 }
135 _context = _deserialize(_jsPortSync.callSync([])); 45 return _fromBrowserObject(object);
136 }
137 return _context;
138 }
139
140 /**
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
146 /**
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
155 Callback._(this._function, this._withThis) {
156 var id = _functionTable.add(this);
157 _jsFunction = new JsFunction._internal(_port.toSendPort(), id);
158 }
159
160 factory Callback(Function f) => new Callback._(f, false);
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 } 46 }
190 47
191 /** 48 /**
192 * Constructs a [JsObject] to a new JavaScript map or list created defined via 49 * Converts a json-like [object] to a JavaScript map or array and return a
193 * Dart map or list. 50 * [JsObject] to it.
194 */ 51 */
195 factory JsObject._json(data) => _convert(data); 52 factory JsObject.jsify(object) {
196 53 if ((object is! Map) && (object is! Iterable)) {
197 static _convert(data) => 54 throw new ArgumentError("object must be a Map or Iterable");
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 {
210 return ['simple', _serialize(data)];
211 } 55 }
56 return _jsify(object);
212 } 57 }
213 58
214 JsObject._internal(this._port, this._id); 59 static JSObject _jsify(object) native "JsObject_jsify";
215 60
216 JsObject toJs() => this; 61 static JsObject _fromBrowserObject(object) native "JsObject_fromBrowserObject" ;
217 62
218 // Resolve whether this is needed. 63 operator[](key) native "JsObject_[]";
219 operator[](arg) => _forward(this, '[]', 'method', [ arg ]); 64 operator[]=(key, value) native "JsObject_[]=";
220 65
221 // Resolve whether this is needed. 66 int get hashCode native "JsObject_hashCode";
222 operator[]=(key, value) => _forward(this, '[]=', 'method', [ key, value ]);
223 67
224 int get hashCode => _id.hashCode; 68 operator==(other) => other is JsObject && _identityEquality(this, other);
225 69
226 // Test if this is equivalent to another Proxy. This essentially 70 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 71
230 /** 72 bool hasProperty(String property) native "JsObject_hasProperty";
alexandre.ardhuin 2013/10/20 07:17:00 `property` should be dynamic like in js_dart2js.da
231 * Check if this [JsObject] has a [name] property.
232 */
233 bool hasProperty(String name) => _forward(this, name, 'hasProperty', []);
234 73
235 /** 74 void deleteProperty(JsFunction name) native "JsObject_deleteProperty";
alexandre.ardhuin 2013/10/20 07:17:00 `name` should be dynamic like in js_dart2js.dart
236 * Delete the [name] property.
237 */
238 void deleteProperty(String name) {
239 _jsPortDeleteProperty.callSync([this, name].map(_serialize).toList());
240 }
241 75
242 /** 76 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 77
248 String toString() { 78 String toString() {
249 try { 79 try {
250 return _forward(this, 'toString', 'method', []); 80 return _toString();
251 } catch(e) { 81 } catch(e) {
252 return super.toString(); 82 return super.toString();
253 } 83 }
254 } 84 }
255 85
86 String _toString() native "JsObject_toString";
87
256 callMethod(String name, [List args]) { 88 callMethod(String name, [List args]) {
257 return _forward(this, name, 'method', args != null ? args : []); 89 try {
258 } 90 return _callMethod(name, args);
259 91 } catch(e) {
260 // Forward member accesses to the backing JavaScript object. 92 if (hasProperty(name)) {
261 static _forward(JsObject receiver, String member, String kind, List args) { 93 rethrow;
262 var result = receiver._port.callSync([receiver._id, member, kind, 94 } else {
263 args.map(_serialize).toList()]); 95 throw new NoSuchMethodError(this, new Symbol(name), args, null);
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 }
273
274 /// A [JsObject] subtype to JavaScript functions.
275 class JsFunction extends JsObject implements Serializable<JsFunction> {
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 }
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 } 96 }
382 return jsFunction;
383 } 97 }
384 } 98 }
385 99
386 deserializeObject(message) { 100 _callMethod(String name, List args) native "JsObject_callMethod";
387 var id = message[1]; 101 }
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 102
403 if (message == null) { 103 class JsFunction extends JsObject {
404 return null; // Convert undefined to null. 104 JsFunction.internal();
405 } else if (message is String || 105
406 message is num || 106 /**
407 message is bool) { 107 * Returns a [JsFunction] that captures its 'this' binding and calls [f]
408 // Primitives are passed directly through. 108 * with the value of this passed as the first argument.
409 return message; 109 */
410 } else if (message is SendPortSync) { 110 factory JsFunction.withThis(Function f) => _withThis(f);
411 // Serialized type. 111
412 return message; 112 apply(List args, {thisArg}) native "JsFunction_apply";
413 } 113
414 var tag = message[0]; 114 static JsFunction _withThis(Function f) native "JsFunction_withThis";
415 switch (tag) {
416 case 'funcref': return deserializeFunction(message);
417 case 'objref': return deserializeObject(message);
418 }
419 throw 'Unsupported serialized data: $message';
420 } 115 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698