Chromium Code Reviews| 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 |