OLD | NEW |
---|---|
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, 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 * Support for interoperating with JavaScript. | 6 * Support for interoperating with JavaScript. |
7 * | 7 * |
8 * This library provides access to JavaScript objects from Dart, allowing | 8 * This library provides access to JavaScript objects from Dart, allowing |
9 * Dart code to get and set properties, and call methods of JavaScript objects | 9 * Dart code to get and set properties, and call methods of JavaScript objects |
10 * and invoke JavaScript functions. The library takes care of converting | 10 * and invoke JavaScript functions. The library takes care of converting |
11 * between Dart and JavaScript objects where possible, or providing proxies if | 11 * between Dart and JavaScript objects where possible, or providing proxies if |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
80 * `a` and `b` defined: | 80 * `a` and `b` defined: |
81 * | 81 * |
82 * var jsMap = new JsObject.jsify({'a': 1, 'b': 2}); | 82 * var jsMap = new JsObject.jsify({'a': 1, 'b': 2}); |
83 * | 83 * |
84 * This expression creates a JavaScript array: | 84 * This expression creates a JavaScript array: |
85 * | 85 * |
86 * var jsArray = new JsObject.jsify([1, 2, 3]); | 86 * var jsArray = new JsObject.jsify([1, 2, 3]); |
87 */ | 87 */ |
88 library dart.js; | 88 library dart.js; |
89 | 89 |
90 import 'dart:html' show Blob, Event, ImageData, Node, Window; | |
91 import 'dart:collection' show HashMap, ListMixin; | 90 import 'dart:collection' show HashMap, ListMixin; |
92 import 'dart:indexed_db' show KeyRange; | |
93 import 'dart:typed_data' show TypedData; | |
94 | 91 |
95 import 'dart:_foreign_helper' show JS, DART_CLOSURE_TO_JS; | 92 import 'dart:_interceptors' as _interceptors show JSArray; |
96 import 'dart:_interceptors' show JavaScriptObject, UnknownJavaScriptObject; | 93 import 'dart:_js_helper' show JsName, Primitives; |
97 import 'dart:_js_helper' show Primitives, convertDartClosureToJS, | 94 import 'dart:_foreign_helper' show JS; |
98 getIsolateAffinityTag; | |
99 | 95 |
100 final JsObject context = _wrapToDart(JS('', 'self')); | 96 final JsObject context = _wrapToDart(JS('', 'dart.global')); |
101 | |
102 _convertDartFunction(Function f, {bool captureThis: false}) { | |
103 return JS('', | |
104 'function(_call, f, captureThis) {' | |
105 'return function() {' | |
106 'return _call(f, captureThis, this, ' | |
107 'Array.prototype.slice.apply(arguments));' | |
108 '}' | |
109 '}(#, #, #)', DART_CLOSURE_TO_JS(_callDartFunction), f, captureThis); | |
110 } | |
111 | |
112 _callDartFunction(callback, bool captureThis, self, List arguments) { | |
113 if (captureThis) { | |
114 arguments = [self]..addAll(arguments); | |
115 } | |
116 var dartArgs = new List.from(arguments.map(_convertToDart)); | |
117 return _convertToJS(Function.apply(callback, dartArgs)); | |
118 } | |
119 | 97 |
120 /** | 98 /** |
121 * Proxies a JavaScript object to Dart. | 99 * Proxies a JavaScript object to Dart. |
122 * | 100 * |
123 * The properties of the JavaScript object are accessible via the `[]` and | 101 * The properties of the JavaScript object are accessible via the `[]` and |
124 * `[]=` operators. Methods are callable via [callMethod]. | 102 * `[]=` operators. Methods are callable via [callMethod]. |
125 */ | 103 */ |
126 class JsObject { | 104 class JsObject { |
127 // The wrapped JS object. | 105 // The wrapped JS object. |
128 final dynamic _jsObject; | 106 final dynamic _jsObject; |
129 | 107 |
130 // This shoud only be called from _wrapToDart | 108 // This should only be called from _wrapToDart |
131 JsObject._fromJs(this._jsObject) { | 109 JsObject._fromJs(this._jsObject) { |
132 assert(_jsObject != null); | 110 assert(_jsObject != null); |
133 } | 111 } |
134 | 112 |
135 /** | 113 /** |
136 * Constructs a new JavaScript object from [constructor] and returns a proxy | 114 * Constructs a new JavaScript object from [constructor] and returns a proxy |
137 * to it. | 115 * to it. |
138 */ | 116 */ |
139 factory JsObject(JsFunction constructor, [List arguments]) { | 117 factory JsObject(JsFunction constructor, [List arguments]) { |
140 var constr = _convertToJS(constructor); | 118 var ctor = constructor._jsObject; |
141 if (arguments == null) { | 119 if (arguments == null) { |
142 return _wrapToDart(JS('', 'new #()', constr)); | 120 return _wrapToDart(JS('', 'new #()', ctor)); |
143 } | 121 } |
144 // The following code solves the problem of invoking a JavaScript | 122 return _wrapToDart(JS('', 'new #(...#)', ctor, arguments)); |
vsm
2015/06/24 23:16:15
Well, that's a lot simpler... :-)
| |
145 // constructor with an unknown number arguments. | |
146 // First bind the constructor to the argument list using bind.apply(). | |
147 // The first argument to bind() is the binding of 'this', so add 'null' to | |
148 // the arguments list passed to apply(). | |
149 // After that, use the JavaScript 'new' operator which overrides any binding | |
150 // of 'this' with the new instance. | |
151 var args = [null]..addAll(arguments.map(_convertToJS)); | |
152 var factoryFunction = JS('', '#.bind.apply(#, #)', constr, constr, args); | |
153 // Without this line, calling factoryFunction as a constructor throws | |
154 JS('String', 'String(#)', factoryFunction); | |
155 // This could return an UnknownJavaScriptObject, or a native | |
156 // object for which there is an interceptor | |
157 var jsObj = JS('JavaScriptObject', 'new #()', factoryFunction); | |
158 | |
159 return _wrapToDart(jsObj); | |
160 } | 123 } |
161 | 124 |
162 /** | 125 /** |
163 * Constructs a [JsObject] that proxies a native Dart object; _for expert use | 126 * Constructs a [JsObject] that proxies a native Dart object; _for expert use |
164 * only_. | 127 * only_. |
165 * | 128 * |
166 * Use this constructor only if you wish to get access to JavaScript | 129 * Use this constructor only if you wish to get access to JavaScript |
167 * properties attached to a browser host object, such as a Node or Blob, that | 130 * properties attached to a browser host object, such as a Node or Blob, that |
168 * is normally automatically converted into a native Dart object. | 131 * is normally automatically converted into a native Dart object. |
169 * | 132 * |
170 * An exception will be thrown if [object] either is `null` or has the type | 133 * An exception will be thrown if [object] either is `null` or has the type |
171 * `bool`, `num`, or `String`. | 134 * `bool`, `num`, or `String`. |
172 */ | 135 */ |
173 factory JsObject.fromBrowserObject(object) { | 136 factory JsObject.fromBrowserObject(object) { |
174 if (object is num || object is String || object is bool || object == null) { | 137 if (object is num || object is String || object is bool || object == null) { |
175 throw new ArgumentError( | 138 throw new ArgumentError("object cannot be a num, string, bool, or null"); |
176 "object cannot be a num, string, bool, or null"); | |
177 } | 139 } |
178 return _wrapToDart(_convertToJS(object)); | 140 return _wrapToDart(_convertToJS(object)); |
179 } | 141 } |
180 | 142 |
181 /** | 143 /** |
182 * Recursively converts a JSON-like collection of Dart objects to a | 144 * Recursively converts a JSON-like collection of Dart objects to a |
183 * collection of JavaScript objects and returns a [JsObject] proxy to it. | 145 * collection of JavaScript objects and returns a [JsObject] proxy to it. |
184 * | 146 * |
185 * [object] must be a [Map] or [Iterable], the contents of which are also | 147 * [object] must be a [Map] or [Iterable], the contents of which are also |
186 * converted. Maps and Iterables are copied to a new JavaScript object. | 148 * converted. Maps and Iterables are copied to a new JavaScript object. |
187 * Primitives and other transferrable values are directly converted to their | 149 * Primitives and other transferable values are directly converted to their |
188 * JavaScript type, and all other objects are proxied. | 150 * JavaScript type, and all other objects are proxied. |
189 */ | 151 */ |
190 factory JsObject.jsify(object) { | 152 factory JsObject.jsify(object) { |
191 if ((object is! Map) && (object is! Iterable)) { | 153 if ((object is! Map) && (object is! Iterable)) { |
192 throw new ArgumentError("object must be a Map or Iterable"); | 154 throw new ArgumentError("object must be a Map or Iterable"); |
193 } | 155 } |
194 return _wrapToDart(_convertDataTree(object)); | 156 return _wrapToDart(_convertDataTree(object)); |
195 } | 157 } |
196 | 158 |
197 static _convertDataTree(data) { | 159 static _convertDataTree(data) { |
198 var _convertedObjects = new HashMap.identity(); | 160 var _convertedObjects = new HashMap.identity(); |
199 | 161 |
200 _convert(o) { | 162 _convert(o) { |
201 if (_convertedObjects.containsKey(o)) { | 163 if (_convertedObjects.containsKey(o)) { |
202 return _convertedObjects[o]; | 164 return _convertedObjects[o]; |
203 } | 165 } |
204 if (o is Map) { | 166 if (o is Map) { |
205 final convertedMap = JS('=Object', '{}'); | 167 final convertedMap = JS('', '{}'); |
206 _convertedObjects[o] = convertedMap; | 168 _convertedObjects[o] = convertedMap; |
207 for (var key in o.keys) { | 169 for (var key in o.keys) { |
208 JS('=Object', '#[#]=#', convertedMap, key, _convert(o[key])); | 170 JS('', '#[#] = #', convertedMap, key, _convert(o[key])); |
vsm
2015/06/24 23:16:15
Should we preserve the '=Object'? We won't use it
Jennifer Messerly
2015/06/25 20:11:27
hmm, I hope not :)
Not sure it ever makes sense t
| |
209 } | 171 } |
210 return convertedMap; | 172 return convertedMap; |
211 } else if (o is Iterable) { | 173 } else if (o is Iterable) { |
212 var convertedList = []; | 174 var convertedList = []; |
213 _convertedObjects[o] = convertedList; | 175 _convertedObjects[o] = convertedList; |
214 convertedList.addAll(o.map(_convert)); | 176 convertedList.addAll(o.map(_convert)); |
215 return convertedList; | 177 return convertedList; |
216 } else { | 178 } else { |
217 return _convertToJS(o); | 179 return _convertToJS(o); |
218 } | 180 } |
219 } | 181 } |
220 | 182 |
221 return _convert(data); | 183 return _convert(data); |
222 } | 184 } |
223 | 185 |
224 /** | 186 /** |
225 * Returns the value associated with [property] from the proxied JavaScript | 187 * Returns the value associated with [property] from the proxied JavaScript |
226 * object. | 188 * object. |
227 * | 189 * |
228 * The type of [property] must be either [String] or [num]. | 190 * The type of [property] must be either [String] or [num]. |
229 */ | 191 */ |
230 dynamic operator[](property) { | 192 dynamic operator [](property) { |
231 if (property is! String && property is! num) { | 193 if (property is! String && property is! num) { |
232 throw new ArgumentError("property is not a String or num"); | 194 throw new ArgumentError("property is not a String or num"); |
233 } | 195 } |
234 return _convertToDart(JS('', '#[#]', _jsObject, property)); | 196 return _convertToDart(JS('', '#[#]', _jsObject, property)); |
235 } | 197 } |
236 | 198 |
237 /** | 199 /** |
238 * Sets the value associated with [property] on the proxied JavaScript | 200 * Sets the value associated with [property] on the proxied JavaScript |
239 * object. | 201 * object. |
240 * | 202 * |
241 * The type of [property] must be either [String] or [num]. | 203 * The type of [property] must be either [String] or [num]. |
242 */ | 204 */ |
243 operator[]=(property, value) { | 205 operator []=(property, value) { |
244 if (property is! String && property is! num) { | 206 if (property is! String && property is! num) { |
245 throw new ArgumentError("property is not a String or num"); | 207 throw new ArgumentError("property is not a String or num"); |
246 } | 208 } |
247 JS('', '#[#]=#', _jsObject, property, _convertToJS(value)); | 209 JS('', '#[#] = #', _jsObject, property, _convertToJS(value)); |
248 } | 210 } |
249 | 211 |
250 int get hashCode => 0; | 212 int get hashCode => 0; |
251 | 213 |
252 bool operator==(other) => other is JsObject && | 214 bool operator ==(other) => |
253 JS('bool', '# === #', _jsObject, other._jsObject); | 215 other is JsObject && JS('bool', '# === #', _jsObject, other._jsObject); |
254 | 216 |
255 /** | 217 /** |
256 * Returns `true` if the JavaScript object contains the specified property | 218 * Returns `true` if the JavaScript object contains the specified property |
257 * either directly or though its prototype chain. | 219 * either directly or though its prototype chain. |
258 * | 220 * |
259 * This is the equivalent of the `in` operator in JavaScript. | 221 * This is the equivalent of the `in` operator in JavaScript. |
260 */ | 222 */ |
261 bool hasProperty(property) { | 223 bool hasProperty(property) { |
262 if (property is! String && property is! num) { | 224 if (property is! String && property is! num) { |
263 throw new ArgumentError("property is not a String or num"); | 225 throw new ArgumentError("property is not a String or num"); |
(...skipping 21 matching lines...) Expand all Loading... | |
285 bool instanceof(JsFunction type) { | 247 bool instanceof(JsFunction type) { |
286 return JS('bool', '# instanceof #', _jsObject, _convertToJS(type)); | 248 return JS('bool', '# instanceof #', _jsObject, _convertToJS(type)); |
287 } | 249 } |
288 | 250 |
289 /** | 251 /** |
290 * Returns the result of the JavaScript objects `toString` method. | 252 * Returns the result of the JavaScript objects `toString` method. |
291 */ | 253 */ |
292 String toString() { | 254 String toString() { |
293 try { | 255 try { |
294 return JS('String', 'String(#)', _jsObject); | 256 return JS('String', 'String(#)', _jsObject); |
295 } catch(e) { | 257 } catch (e) { |
296 return super.toString(); | 258 return super.toString(); |
297 } | 259 } |
298 } | 260 } |
299 | 261 |
300 /** | 262 /** |
301 * Calls [method] on the JavaScript object with the arguments [args] and | 263 * Calls [method] on the JavaScript object with the arguments [args] and |
302 * returns the result. | 264 * returns the result. |
303 * | 265 * |
304 * The type of [method] must be either [String] or [num]. | 266 * The type of [method] must be either [String] or [num]. |
305 */ | 267 */ |
306 dynamic callMethod(method, [List args]) { | 268 dynamic callMethod(method, [List args]) { |
307 if (method is! String && method is! num) { | 269 if (method is! String && method is! num) { |
308 throw new ArgumentError("method is not a String or num"); | 270 throw new ArgumentError("method is not a String or num"); |
309 } | 271 } |
310 return _convertToDart(JS('', '#[#].apply(#, #)', _jsObject, method, | 272 if (args != null) args = new List.from(args.map(_convertToJS)); |
311 _jsObject, | 273 var fn = JS('', '#[#]', _jsObject, method); |
312 args == null ? null : new List.from(args.map(_convertToJS)))); | 274 if (!JS('bool', '# instanceof Function', fn)) { |
275 throw new NoSuchMethodError(_jsObject, new Symbol(method), args, {}); | |
276 } | |
277 return _convertToDart(JS('', '#.apply(#, #)', fn, _jsObject, args)); | |
313 } | 278 } |
314 } | 279 } |
315 | 280 |
316 /** | 281 /** |
317 * Proxies a JavaScript Function object. | 282 * Proxies a JavaScript Function object. |
318 */ | 283 */ |
319 class JsFunction extends JsObject { | 284 class JsFunction extends JsObject { |
320 | 285 |
321 /** | 286 /** |
322 * Returns a [JsFunction] that captures its 'this' binding and calls [f] | 287 * Returns a [JsFunction] that captures its 'this' binding and calls [f] |
323 * with the value of this passed as the first argument. | 288 * with the value of this passed as the first argument. |
324 */ | 289 */ |
325 factory JsFunction.withThis(Function f) { | 290 factory JsFunction.withThis(Function f) { |
326 var jsFunc = _convertDartFunction(f, captureThis: true); | 291 return new JsFunction._fromJs(JS('', 'function(/*...arguments*/) {' |
327 return new JsFunction._fromJs(jsFunc); | 292 ' let args = [#(this)];' |
293 ' for (let arg of arguments) {' | |
294 ' args.push(#(arg));' | |
295 ' }' | |
296 ' return #(#(...args));' | |
297 '}', _convertToDart, _convertToDart, _convertToJS, f)); | |
328 } | 298 } |
329 | 299 |
330 JsFunction._fromJs(jsObject) : super._fromJs(jsObject); | 300 JsFunction._fromJs(jsObject) : super._fromJs(jsObject); |
331 | 301 |
332 /** | 302 /** |
333 * Invokes the JavaScript function with arguments [args]. If [thisArg] is | 303 * Invokes the JavaScript function with arguments [args]. If [thisArg] is |
334 * supplied it is the value of `this` for the invocation. | 304 * supplied it is the value of `this` for the invocation. |
335 */ | 305 */ |
336 dynamic apply(List args, { thisArg }) => | 306 dynamic apply(List args, {thisArg}) => _convertToDart(JS('', '#.apply(#, #)', |
337 _convertToDart(JS('', '#.apply(#, #)', _jsObject, | 307 _jsObject, _convertToJS(thisArg), |
338 _convertToJS(thisArg), | 308 args == null ? null : new List.from(args.map(_convertToJS)))); |
339 args == null ? null : new List.from(args.map(_convertToJS)))); | |
340 } | 309 } |
341 | 310 |
342 /** | 311 // TODO(jmesserly): this is totally unnecessary in dev_compiler. |
343 * A [List] that proxies a JavaScript array. | 312 /** A [List] that proxies a JavaScript array. */ |
344 */ | |
345 class JsArray<E> extends JsObject with ListMixin<E> { | 313 class JsArray<E> extends JsObject with ListMixin<E> { |
346 | 314 |
347 /** | 315 /** |
348 * Creates a new JavaScript array. | 316 * Creates a new JavaScript array. |
349 */ | 317 */ |
350 JsArray() : super._fromJs([]); | 318 JsArray() : super._fromJs([]); |
351 | 319 |
352 /** | 320 /** |
353 * Creates a new JavaScript array and initializes it to the contents of | 321 * Creates a new JavaScript array and initializes it to the contents of |
354 * [other]. | 322 * [other]. |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
402 int get length { | 370 int get length { |
403 // Check the length honours the List contract. | 371 // Check the length honours the List contract. |
404 var len = JS('', '#.length', _jsObject); | 372 var len = JS('', '#.length', _jsObject); |
405 // JavaScript arrays have lengths which are unsigned 32-bit integers. | 373 // JavaScript arrays have lengths which are unsigned 32-bit integers. |
406 if (JS('bool', 'typeof # === "number" && (# >>> 0) === #', len, len, len)) { | 374 if (JS('bool', 'typeof # === "number" && (# >>> 0) === #', len, len, len)) { |
407 return JS('int', '#', len); | 375 return JS('int', '#', len); |
408 } | 376 } |
409 throw new StateError('Bad JsArray length'); | 377 throw new StateError('Bad JsArray length'); |
410 } | 378 } |
411 | 379 |
412 void set length(int length) { super['length'] = length; } | 380 void set length(int length) { |
413 | 381 super['length'] = length; |
382 } | |
414 | 383 |
415 // Methods overriden for better performance | 384 // Methods overriden for better performance |
416 | 385 |
417 void add(E value) { | 386 void add(E value) { |
418 callMethod('push', [value]); | 387 callMethod('push', [value]); |
419 } | 388 } |
420 | 389 |
421 void addAll(Iterable<E> iterable) { | 390 void addAll(Iterable<E> iterable) { |
422 var list = (JS('bool', '# instanceof Array', iterable)) | 391 var list = (JS('bool', '# instanceof Array', iterable)) |
423 ? iterable | 392 ? iterable |
(...skipping 15 matching lines...) Expand all Loading... | |
439 if (length == 0) throw new RangeError(-1); | 408 if (length == 0) throw new RangeError(-1); |
440 return callMethod('pop'); | 409 return callMethod('pop'); |
441 } | 410 } |
442 | 411 |
443 void removeRange(int start, int end) { | 412 void removeRange(int start, int end) { |
444 _checkRange(start, end, length); | 413 _checkRange(start, end, length); |
445 callMethod('splice', [start, end - start]); | 414 callMethod('splice', [start, end - start]); |
446 } | 415 } |
447 | 416 |
448 void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) { | 417 void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) { |
449 _checkRange(start, end, length); | 418 _checkRange(start, end, this.length); |
450 int length = end - start; | 419 int length = end - start; |
451 if (length == 0) return; | 420 if (length == 0) return; |
452 if (skipCount < 0) throw new ArgumentError(skipCount); | 421 if (skipCount < 0) throw new ArgumentError(skipCount); |
453 var args = [start, length]..addAll(iterable.skip(skipCount).take(length)); | 422 var args = [start, length]..addAll(iterable.skip(skipCount).take(length)); |
454 callMethod('splice', args); | 423 callMethod('splice', args); |
455 } | 424 } |
456 | 425 |
457 void sort([int compare(E a, E b)]) { | 426 void sort([int compare(E a, E b)]) { |
458 // Note: arr.sort(null) is a type error in FF | 427 // Note: arr.sort(null) is a type error in FF |
459 callMethod('sort', compare == null ? [] : [compare]); | 428 callMethod('sort', compare == null ? [] : [compare]); |
460 } | 429 } |
461 } | 430 } |
462 | 431 |
463 // property added to a Dart object referencing its JS-side DartObject proxy | 432 bool _isBrowserType(o) => JS('bool', |
464 final String _DART_OBJECT_PROPERTY_NAME = | 433 '# instanceof Blob || ' |
465 getIsolateAffinityTag(r'_$dart_dartObject'); | 434 '# instanceof Event || ' |
466 final String _DART_CLOSURE_PROPERTY_NAME = | 435 '(window.KeyRange && # instanceof KeyRange) || ' |
467 getIsolateAffinityTag(r'_$dart_dartClosure'); | 436 '# instanceof ImageData || ' |
437 '# instanceof Node || ' | |
438 '(window.TypedData && # instanceof TypedData) || ' | |
439 '# instanceof Window', o, o, o, o, o, o, o); | |
468 | 440 |
469 // property added to a JS object referencing its Dart-side JsObject proxy | 441 class _DartObject { |
470 const _JS_OBJECT_PROPERTY_NAME = r'_$dart_jsObject'; | 442 final _dartObj; |
471 const _JS_FUNCTION_PROPERTY_NAME = r'$dart_jsFunction'; | 443 _DartObject(this._dartObj); |
472 | |
473 bool _defineProperty(o, String name, value) { | |
474 if (_isExtensible(o) && | |
475 // TODO(ahe): Calling _hasOwnProperty to work around | |
476 // https://code.google.com/p/dart/issues/detail?id=21331. | |
477 !_hasOwnProperty(o, name)) { | |
478 try { | |
479 JS('void', 'Object.defineProperty(#, #, { value: #})', o, name, value); | |
480 return true; | |
481 } catch (e) { | |
482 // object is native and lies about being extensible | |
483 // see https://bugzilla.mozilla.org/show_bug.cgi?id=775185 | |
484 } | |
485 } | |
486 return false; | |
487 } | 444 } |
488 | 445 |
489 bool _hasOwnProperty(o, String name) { | |
490 return JS('bool', 'Object.prototype.hasOwnProperty.call(#, #)', o, name); | |
491 } | |
492 | |
493 bool _isExtensible(o) => JS('bool', 'Object.isExtensible(#)', o); | |
494 | |
495 Object _getOwnProperty(o, String name) { | |
496 if (_hasOwnProperty(o, name)) { | |
497 return JS('', '#[#]', o, name); | |
498 } | |
499 return null; | |
500 } | |
501 | |
502 bool _isLocalObject(o) => JS('bool', '# instanceof Object', o); | |
503 | |
504 // The shared constructor function for proxies to Dart objects in JavaScript. | |
505 final _dartProxyCtor = JS('', 'function DartObject(o) { this.o = o; }'); | |
506 | |
507 dynamic _convertToJS(dynamic o) { | 446 dynamic _convertToJS(dynamic o) { |
508 // Note: we don't write `if (o == null) return null;` to make sure dart2js | 447 if (o == null || |
509 // doesn't convert `return null;` into `return;` (which would make `null` be | 448 o is String || |
510 // `undefined` in Javascprit). See dartbug.com/20305 for details. | 449 o is num || |
511 if (o == null || o is String || o is num || o is bool) { | 450 o is bool || |
512 return o; | 451 _isBrowserType(o)) { |
513 } else if (o is Blob || o is Event || o is KeyRange || o is ImageData | |
514 || o is Node || o is TypedData || o is Window) { | |
515 return o; | 452 return o; |
516 } else if (o is DateTime) { | 453 } else if (o is DateTime) { |
517 return Primitives.lazyAsJsDate(o); | 454 return Primitives.lazyAsJsDate(o); |
518 } else if (o is JsObject) { | 455 } else if (o is JsObject) { |
519 return o._jsObject; | 456 return o._jsObject; |
520 } else if (o is Function) { | 457 } else if (o is Function) { |
521 return _getJsProxy(o, _JS_FUNCTION_PROPERTY_NAME, (o) { | 458 return _putIfAbsent(_jsProxies, o, _wrapDartFunction); |
522 var jsFunction = _convertDartFunction(o); | |
523 // set a property on the JS closure referencing the Dart closure | |
524 _defineProperty(jsFunction, _DART_CLOSURE_PROPERTY_NAME, o); | |
525 return jsFunction; | |
526 }); | |
527 } else { | 459 } else { |
528 var ctor = _dartProxyCtor; | 460 // TODO(jmesserly): for now, we wrap other objects, to keep compatibility |
529 return _getJsProxy(o, _JS_OBJECT_PROPERTY_NAME, | 461 // with the original dart:js behavior. |
530 (o) => JS('', 'new #(#)', ctor, o)); | 462 return _putIfAbsent(_jsProxies, o, (o) => new _DartObject(o)); |
531 } | 463 } |
532 } | 464 } |
533 | 465 |
534 Object _getJsProxy(o, String propertyName, createProxy(o)) { | 466 dynamic _wrapDartFunction(f) { |
535 var jsProxy = _getOwnProperty(o, propertyName); | 467 var wrapper = JS('', 'function(/*...arguments*/) {' |
536 if (jsProxy == null) { | 468 ' let args = Array.prototype.map.call(arguments, #);' |
537 jsProxy = createProxy(o); | 469 ' return #(#(...args));' |
538 _defineProperty(o, propertyName, jsProxy); | 470 '}', _convertToDart, _convertToJS, f); |
539 } | 471 _dartProxies[wrapper] = f; |
540 return jsProxy; | 472 return wrapper; |
541 } | 473 } |
542 | 474 |
543 // converts a Dart object to a reference to a native JS object | 475 // converts a Dart object to a reference to a native JS object |
544 // which might be a DartObject JS->Dart proxy | 476 // which might be a DartObject JS->Dart proxy |
545 Object _convertToDart(o) { | 477 Object _convertToDart(o) { |
546 if (JS('bool', '# == null', o) || | 478 if (JS('bool', '# == null', o) || |
547 JS('bool', 'typeof # == "string"', o) || | 479 JS('bool', 'typeof # == "string"', o) || |
548 JS('bool', 'typeof # == "number"', o) || | 480 JS('bool', 'typeof # == "number"', o) || |
549 JS('bool', 'typeof # == "boolean"', o)) { | 481 JS('bool', 'typeof # == "boolean"', o) || |
482 _isBrowserType(o)) { | |
550 return o; | 483 return o; |
551 } else if (_isLocalObject(o) | |
552 && (o is Blob || o is Event || o is KeyRange || o is ImageData | |
553 || o is Node || o is TypedData || o is Window)) { | |
554 // long line: dart2js doesn't allow string concatenation in the JS() form | |
555 return JS('Blob|Event|KeyRange|ImageData|Node|TypedData|Window', '#', o); | |
556 } else if (JS('bool', '# instanceof Date', o)) { | 484 } else if (JS('bool', '# instanceof Date', o)) { |
557 var ms = JS('num', '#.getTime()', o); | 485 var ms = JS('num', '#.getTime()', o); |
558 return new DateTime.fromMillisecondsSinceEpoch(ms); | 486 return new DateTime.fromMillisecondsSinceEpoch(ms); |
559 } else if (JS('bool', '#.constructor === #', o, _dartProxyCtor)) { | 487 } else if (o is _DartObject) { |
560 return JS('', '#.o', o); | 488 return o._dartObj; |
561 } else { | 489 } else { |
562 return _wrapToDart(o); | 490 return _putIfAbsent(_dartProxies, o, _wrapToDart); |
563 } | 491 } |
564 } | 492 } |
565 | 493 |
566 JsObject _wrapToDart(o) { | 494 _wrapToDart(o) { |
567 if (JS('bool', 'typeof # == "function"', o)) { | 495 if (JS('bool', 'typeof # == "function"', o)) { |
568 return _getDartProxy(o, _DART_CLOSURE_PROPERTY_NAME, | 496 return new JsFunction._fromJs(o); |
569 (o) => new JsFunction._fromJs(o)); | |
570 } else if (JS('bool', '# instanceof Array', o)) { | |
571 return _getDartProxy(o, _DART_OBJECT_PROPERTY_NAME, | |
572 (o) => new JsArray._fromJs(o)); | |
573 } else { | |
574 return _getDartProxy(o, _DART_OBJECT_PROPERTY_NAME, | |
575 (o) => new JsObject._fromJs(o)); | |
576 } | 497 } |
498 if (JS('bool', '# instanceof Array', o)) { | |
499 return new JsArray._fromJs(o); | |
500 } | |
501 return new JsObject._fromJs(o); | |
577 } | 502 } |
578 | 503 |
579 Object _getDartProxy(o, String propertyName, createProxy(o)) { | 504 final _dartProxies = JS('', 'new WeakMap()'); |
580 var dartProxy = _getOwnProperty(o, propertyName); | 505 final _jsProxies = JS('', 'new WeakMap()'); |
581 // Temporary fix for dartbug.com/15193 | 506 |
582 // In some cases it's possible to see a JavaScript object that | 507 Object _putIfAbsent(weakMap, o, getValue(o)) { |
583 // came from a different context and was previously proxied to | 508 var value = JS('', '#.get(#)', weakMap, o); |
584 // Dart in that context. The JS object will have a cached proxy | 509 if (value == null) { |
585 // but it won't be a valid Dart object in this context. | 510 value = getValue(o); |
586 // For now we throw away the cached proxy, but we should be able | 511 JS('', '#.set(#, #)', weakMap, o, value); |
587 // to cache proxies from multiple JS contexts and Dart isolates. | |
588 if (dartProxy == null || !_isLocalObject(o)) { | |
589 dartProxy = createProxy(o); | |
590 _defineProperty(o, propertyName, dartProxy); | |
591 } | 512 } |
592 return dartProxy; | 513 return value; |
593 } | 514 } |
OLD | NEW |