Chromium Code Reviews| 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 library dart.js; | 5 library dart.js; |
| 6 | 6 |
| 7 import 'dart:html' show Blob, ImageData, Node; | |
| 8 import 'dart:collection' show HashMap; | |
| 9 import 'dart:indexed_db' show KeyRange; | |
| 10 import 'dart:typed_data' show TypedData; | |
| 11 | |
| 7 import 'dart:_foreign_helper' show JS, DART_CLOSURE_TO_JS; | 12 import 'dart:_foreign_helper' show JS, DART_CLOSURE_TO_JS; |
| 13 import 'dart:_interceptors' show JavaScriptObject, UnknownJavaScriptObject; | |
| 8 import 'dart:_js_helper' show Primitives, convertDartClosureToJS; | 14 import 'dart:_js_helper' show Primitives, convertDartClosureToJS; |
| 9 | 15 |
| 10 final JsObject context = new JsObject._fromJs(Primitives.computeGlobalThis()); | 16 final JsObject context = new JsObject._fromJs(Primitives.computeGlobalThis()); |
| 11 | 17 |
| 12 JsObject jsify(dynamic data) => data == null ? null : new JsObject._json(data); | |
| 13 | |
| 14 class Callback implements Serializable<JsFunction> { | |
| 15 final Function _f; // here to allow capture in closure | |
| 16 final bool _withThis; // here to allow capture in closure | |
| 17 dynamic _jsFunction; | |
| 18 | |
| 19 Callback._(this._f, this._withThis) { | |
| 20 _jsFunction = JS('', r''' | |
| 21 (function(){ | |
| 22 var f = #; | |
| 23 return function(){ | |
| 24 return f(this, Array.prototype.slice.apply(arguments)); | |
| 25 }; | |
| 26 }).apply(this)''', convertDartClosureToJS(_call, 2)); | |
| 27 } | |
| 28 | |
| 29 factory Callback(Function f) => new Callback._(f, false); | |
| 30 factory Callback.withThis(Function f) => new Callback._(f, true); | |
| 31 | |
| 32 _call(thisArg, List args) { | |
| 33 final arguments = new List.from(args); | |
| 34 if (_withThis) arguments.insert(0, thisArg); | |
| 35 final dartArgs = arguments.map(_convertToDart).toList(); | |
| 36 return _convertToJS(Function.apply(_f, dartArgs)); | |
| 37 } | |
| 38 | |
| 39 JsFunction toJs() => new JsFunction._fromJs(_jsFunction); | |
| 40 } | |
| 41 | |
| 42 /* | |
| 43 * TODO(justinfagnani): add tests and make public when we remove Callback. | |
| 44 * | |
| 45 * Returns a [JsFunction] that captures its 'this' binding and calls [f] | |
| 46 * with the value of this passed as the first argument. | |
| 47 */ | |
| 48 JsFunction _captureThis(Function f) => | |
| 49 new JsFunction._fromJs(_convertDartFunction(f, captureThis: true)); | |
| 50 | |
| 51 _convertDartFunction(Function f, {bool captureThis: false}) { | 18 _convertDartFunction(Function f, {bool captureThis: false}) { |
| 52 return JS('', | 19 return JS('', |
| 53 'function(_call, f, captureThis) {' | 20 'function(_call, f, captureThis) {' |
| 54 'return function() {' | 21 'return function() {' |
| 55 'return _call(f, captureThis, this, ' | 22 'return _call(f, captureThis, this, ' |
| 56 'Array.prototype.slice.apply(arguments));' | 23 'Array.prototype.slice.apply(arguments));' |
| 57 '}' | 24 '}' |
| 58 '}(#, #, #)', DART_CLOSURE_TO_JS(_callDartFunction), f, captureThis); | 25 '}(#, #, #)', DART_CLOSURE_TO_JS(_callDartFunction), f, captureThis); |
| 59 } | 26 } |
| 60 | 27 |
| 61 _callDartFunction(callback, bool captureThis, self, List arguments) { | 28 _callDartFunction(callback, bool captureThis, self, List arguments) { |
| 62 if (captureThis) { | 29 if (captureThis) { |
| 63 arguments = [self]..addAll(arguments); | 30 arguments = [self]..addAll(arguments); |
| 64 } | 31 } |
| 65 var dartArgs = arguments.map(_convertToDart).toList(); | 32 var dartArgs = arguments.map(_convertToDart).toList(); |
| 66 return _convertToJS(Function.apply(callback, dartArgs)); | 33 return _convertToJS(Function.apply(callback, dartArgs)); |
| 67 } | 34 } |
| 68 | 35 |
| 69 | 36 |
| 70 class JsObject implements Serializable<JsObject> { | 37 class JsObject { |
| 71 // The wrapped JS object. | 38 // The wrapped JS object. |
| 72 final dynamic _jsObject; | 39 final dynamic _jsObject; |
| 73 | 40 |
| 74 JsObject._fromJs(this._jsObject) { | 41 JsObject._fromJs(this._jsObject) { |
| 42 assert(_jsObject != null); | |
| 75 // Remember this proxy for the JS object | 43 // Remember this proxy for the JS object |
| 76 _getDartProxy(_jsObject, _DART_OBJECT_PROPERTY_NAME, (o) => this); | 44 _getDartProxy(_jsObject, _DART_OBJECT_PROPERTY_NAME, (o) => this); |
| 77 } | 45 } |
| 78 | 46 |
| 79 // TODO(vsm): Type constructor as Serializable<JsFunction> when | 47 /** |
| 80 // dartbug.com/11854 is fixed. | 48 * Expert use only: |
| 81 factory JsObject(constructor, [List arguments]) { | 49 * |
| 50 * Use this constructor only if you wish to get access to JS properties | |
| 51 * attached to a browser host object such as a Node or Blob. This constructor | |
| 52 * will return a JsObject proxy on [object], even though the object would | |
| 53 * normally be returned as a native Dart object. | |
| 54 * | |
| 55 * An exception will be thrown if [object] is a primitive type or null. | |
| 56 */ | |
| 57 factory JsObject.fromBrowserObject(Object object) { | |
| 58 if (object is num || object is String || object is bool || object == null) { | |
| 59 throw new ArgumentError( | |
| 60 "object cannot be a num, string, bool, or null"); | |
| 61 } | |
| 62 return new JsObject._fromJs(_convertToJS(object)); | |
| 63 } | |
| 64 | |
| 65 /** | |
| 66 * Converts a json-like [data] to a JavaScript map or array and return a | |
| 67 * [JsObject] to it. | |
| 68 */ | |
| 69 factory JsObject.jsify(Object object) { | |
| 70 if ((object is! Map) && (object is! Iterable)) { | |
| 71 throw new ArgumentError("object must be a Map or Iterable"); | |
| 72 } | |
| 73 return new JsObject._fromJs(_convertDataTree(object)); | |
| 74 } | |
| 75 | |
| 76 factory JsObject(JsFunction constructor, [List arguments]) { | |
| 82 var constr = _convertToJS(constructor); | 77 var constr = _convertToJS(constructor); |
| 83 if (arguments == null) { | 78 if (arguments == null) { |
| 84 return new JsObject._fromJs(JS('', 'new #()', constr)); | 79 return new JsObject._fromJs(JS('', 'new #()', constr)); |
| 85 } | 80 } |
| 86 // The following code solves the problem of invoking a JavaScript | 81 // The following code solves the problem of invoking a JavaScript |
| 87 // constructor with an unknown number arguments. | 82 // constructor with an unknown number arguments. |
| 88 // First bind the constructor to the argument list using bind.apply(). | 83 // First bind the constructor to the argument list using bind.apply(). |
| 89 // The first argument to bind() is the binding of 'this', so add 'null' to | 84 // The first argument to bind() is the binding of 'this', so add 'null' to |
| 90 // the arguments list passed to apply(). | 85 // the arguments list passed to apply(). |
| 91 // After that, use the JavaScript 'new' operator which overrides any binding | 86 // After that, use the JavaScript 'new' operator which overrides any binding |
| 92 // of 'this' with the new instance. | 87 // of 'this' with the new instance. |
| 93 var args = [null]..addAll(arguments.map(_convertToJS)); | 88 var args = [null]..addAll(arguments.map(_convertToJS)); |
| 94 var factoryFunction = JS('', '#.bind.apply(#, #)', constr, constr, args); | 89 var factoryFunction = JS('', '#.bind.apply(#, #)', constr, constr, args); |
| 95 // Without this line, calling factoryFunction as a constructor throws | 90 // Without this line, calling factoryFunction as a constructor throws |
| 96 JS('String', 'String(#)', factoryFunction); | 91 JS('String', 'String(#)', factoryFunction); |
| 97 return new JsObject._fromJs(JS('', 'new #()', factoryFunction)); | 92 // This could return an UnknownJavaScriptObject, or a native |
| 93 // object for which there is an interceptor | |
| 94 var jsObj = JS('JavaScriptObject', 'new #()', factoryFunction); | |
| 95 return new JsObject._fromJs(jsObj); | |
| 98 } | 96 } |
| 99 | 97 |
| 100 factory JsObject._json(data) => new JsObject._fromJs(_convertDataTree(data)); | 98 // TODO: handle cycles |
| 99 static _convertDataTree(data) { | |
| 100 var _convertedObjects = new HashMap.identity(); | |
| 101 | 101 |
| 102 static _convertDataTree(data) { | 102 _convert(o) { |
| 103 if (data is Map) { | 103 if (_convertedObjects.containsKey(o)) { |
| 104 final convertedData = JS('=Object', '{}'); | 104 return _convertedObjects[o]; |
| 105 for (var key in data.keys) { | |
| 106 JS('=Object', '#[#]=#', convertedData, key, | |
| 107 _convertDataTree(data[key])); | |
| 108 } | 105 } |
| 109 return convertedData; | 106 if (o is Map) { |
| 110 } else if (data is Iterable) { | 107 final convertedMap = JS('=Object', '{}'); |
| 111 return data.map(_convertDataTree).toList(); | 108 _convertedObjects[o] = convertedMap; |
| 112 } else { | 109 for (var key in o.keys) { |
| 113 return _convertToJS(data); | 110 JS('=Object', '#[#]=#', convertedMap, key, _convert(o[key])); |
| 111 } | |
| 112 return convertedMap; | |
| 113 } else if (o is Iterable) { | |
| 114 var convertedList = []; | |
| 115 _convertedObjects[o] = convertedList; | |
| 116 convertedList.addAll(o.map(_convert)); | |
| 117 return convertedList; | |
| 118 } else { | |
| 119 return _convertToJS(o); | |
| 120 } | |
| 114 } | 121 } |
| 122 | |
| 123 return _convert(data); | |
| 115 } | 124 } |
| 116 | 125 |
| 117 JsObject toJs() => this; | |
| 118 | |
| 119 /** | 126 /** |
| 120 * Returns the value associated with [key] from the proxied JavaScript | 127 * Returns the value associated with [key] from the proxied JavaScript |
| 121 * object. | 128 * object. |
| 122 * | 129 * |
| 123 * [key] must either be a [String] or [int]. | 130 * [key] must either be a [String] or [num]. |
| 124 */ | 131 */ |
| 125 // TODO(justinfagnani): rename key/name to property | 132 // TODO(justinfagnani): rename key/name to property |
| 126 dynamic operator[](key) { | 133 dynamic operator[](key) { |
| 127 if (key is! String && key is! int) { | 134 if (key is! String && key is! num) { |
| 128 throw new ArgumentError("key is not a String or int"); | 135 throw new ArgumentError("key is not a String or num"); |
| 129 } | 136 } |
| 130 return _convertToDart(JS('', '#[#]', _jsObject, key)); | 137 return _convertToDart(JS('', '#[#]', _jsObject, key)); |
| 131 } | 138 } |
| 132 | 139 |
| 133 /** | 140 /** |
| 134 * Sets the value associated with [key] from the proxied JavaScript | 141 * Sets the value associated with [key] from the proxied JavaScript |
| 135 * object. | 142 * object. |
| 136 * | 143 * |
| 137 * [key] must either be a [String] or [int]. | 144 * [key] must either be a [String] or [num]. |
| 138 */ | 145 */ |
| 139 operator[]=(key, value) { | 146 operator[]=(key, value) { |
| 140 if (key is! String && key is! int) { | 147 if (key is! String && key is! num) { |
| 141 throw new ArgumentError("key is not a String or int"); | 148 throw new ArgumentError("key is not a String or num"); |
| 142 } | 149 } |
| 143 JS('', '#[#]=#', _jsObject, key, _convertToJS(value)); | 150 JS('', '#[#]=#', _jsObject, key, _convertToJS(value)); |
| 144 } | 151 } |
| 145 | 152 |
| 146 int get hashCode => 0; | 153 int get hashCode => 0; |
| 147 | 154 |
| 148 bool operator==(other) => other is JsObject && | 155 bool operator==(other) => other is JsObject && |
| 149 JS('bool', '# === #', _jsObject, other._jsObject); | 156 JS('bool', '# === #', _jsObject, other._jsObject); |
| 150 | 157 |
| 151 bool hasProperty(name) { | 158 bool hasProperty(name) { |
| 152 if (name is! String && name is! int) { | 159 if (name is! String && name is! num) { |
| 153 throw new ArgumentError("name is not a String or int"); | 160 throw new ArgumentError("name is not a String or num"); |
| 154 } | 161 } |
| 155 return JS('bool', '# in #', name, _jsObject); | 162 return JS('bool', '# in #', name, _jsObject); |
| 156 } | 163 } |
| 157 | 164 |
| 158 void deleteProperty(name) { | 165 void deleteProperty(name) { |
| 159 if (name is! String && name is! int) { | 166 if (name is! String && name is! num) { |
| 160 throw new ArgumentError("name is not a String or int"); | 167 throw new ArgumentError("name is not a String or num"); |
| 161 } | 168 } |
| 162 JS('bool', 'delete #[#]', _jsObject, name); | 169 JS('bool', 'delete #[#]', _jsObject, name); |
| 163 } | 170 } |
| 164 | 171 |
| 165 // TODO(vsm): Type type as Serializable<JsFunction> when | |
| 166 // dartbug.com/11854 is fixed. | |
| 167 bool instanceof(type) { | 172 bool instanceof(type) { |
|
alexandre.ardhuin
2013/10/20 07:17:00
`type` should be JsFunction
| |
| 168 return JS('bool', '# instanceof #', _jsObject, _convertToJS(type)); | 173 return JS('bool', '# instanceof #', _jsObject, _convertToJS(type)); |
| 169 } | 174 } |
| 170 | 175 |
| 171 String toString() { | 176 String toString() { |
| 172 try { | 177 try { |
| 173 return JS('String', 'String(#)', _jsObject); | 178 return JS('String', 'String(#)', _jsObject); |
| 174 } catch(e) { | 179 } catch(e) { |
| 175 return super.toString(); | 180 return super.toString(); |
| 176 } | 181 } |
| 177 } | 182 } |
| 178 | 183 |
| 179 dynamic callMethod(name, [List args]) { | 184 dynamic callMethod(name, [List args]) { |
| 180 if (name is! String && name is! int) { | 185 if (name is! String && name is! num) { |
| 181 throw new ArgumentError("name is not a String or int"); | 186 throw new ArgumentError("name is not a String or num"); |
| 182 } | 187 } |
| 183 return _convertToDart(JS('', '#[#].apply(#, #)', _jsObject, name, | 188 return _convertToDart(JS('', '#[#].apply(#, #)', _jsObject, name, |
| 184 _jsObject, | 189 _jsObject, |
| 185 args == null ? null : args.map(_convertToJS).toList())); | 190 args == null ? null : args.map(_convertToJS).toList())); |
| 186 } | 191 } |
| 187 } | 192 } |
| 188 | 193 |
| 189 class JsFunction extends JsObject implements Serializable<JsFunction> { | 194 class JsFunction extends JsObject { |
| 195 | |
| 196 /** | |
| 197 * Returns a [JsFunction] that captures its 'this' binding and calls [f] | |
| 198 * with the value of this passed as the first argument. | |
| 199 */ | |
| 200 factory JsFunction.withThis(Function f) { | |
| 201 var jsFunc = _convertDartFunction(f, captureThis: true); | |
| 202 return new JsFunction._fromJs(jsFunc); | |
| 203 } | |
| 190 | 204 |
| 191 JsFunction._fromJs(jsObject) : super._fromJs(jsObject); | 205 JsFunction._fromJs(jsObject) : super._fromJs(jsObject); |
| 192 | 206 |
| 193 dynamic apply(thisArg, [List args]) => | 207 dynamic apply(List args, { thisArg }) => |
| 194 _convertToDart(JS('', '#.apply(#, #)', _jsObject, | 208 _convertToDart(JS('', '#.apply(#, #)', _jsObject, |
| 195 _convertToJS(thisArg), | 209 _convertToJS(thisArg), |
| 196 args == null ? null : args.map(_convertToJS).toList())); | 210 args == null ? null : args.map(_convertToJS).toList())); |
| 197 } | 211 } |
| 198 | 212 |
| 199 abstract class Serializable<T> { | |
| 200 T toJs(); | |
| 201 } | |
| 202 | |
| 203 // property added to a Dart object referencing its JS-side DartObject proxy | 213 // property added to a Dart object referencing its JS-side DartObject proxy |
| 204 const _DART_OBJECT_PROPERTY_NAME = r'_$dart_dartObject'; | 214 const _DART_OBJECT_PROPERTY_NAME = r'_$dart_dartObject'; |
| 205 const _DART_CLOSURE_PROPERTY_NAME = r'_$dart_dartClosure'; | 215 const _DART_CLOSURE_PROPERTY_NAME = r'_$dart_dartClosure'; |
| 206 | 216 |
| 207 // property added to a JS object referencing its Dart-side JsObject proxy | 217 // property added to a JS object referencing its Dart-side JsObject proxy |
| 208 const _JS_OBJECT_PROPERTY_NAME = r'_$dart_jsObject'; | 218 const _JS_OBJECT_PROPERTY_NAME = r'_$dart_jsObject'; |
| 209 const _JS_FUNCTION_PROPERTY_NAME = r'$dart_jsFunction'; | 219 const _JS_FUNCTION_PROPERTY_NAME = r'$dart_jsFunction'; |
| 210 | 220 |
| 211 bool _defineProperty(o, String name, value) { | 221 bool _defineProperty(o, String name, value) { |
| 212 if (JS('bool', 'Object.isExtensible(#)', o)) { | 222 if (JS('bool', 'Object.isExtensible(#)', o)) { |
| 213 try { | 223 try { |
| 214 JS('void', 'Object.defineProperty(#, #, { value: #})', o, name, value); | 224 JS('void', 'Object.defineProperty(#, #, { value: #})', o, name, value); |
| 215 return true; | 225 return true; |
| 216 } catch(e) { | 226 } catch(e) { |
| 217 // object is native and lies about being extensible | 227 // object is native and lies about being extensible |
| 218 // see https://bugzilla.mozilla.org/show_bug.cgi?id=775185 | 228 // see https://bugzilla.mozilla.org/show_bug.cgi?id=775185 |
| 219 } | 229 } |
| 220 } | 230 } |
| 221 return false; | 231 return false; |
| 222 } | 232 } |
| 223 | 233 |
| 224 dynamic _convertToJS(dynamic o) { | 234 dynamic _convertToJS(dynamic o) { |
| 225 if (o == null) { | 235 if (o == null) { |
| 226 return null; | 236 return null; |
| 227 } else if (o is String || o is num || o is bool) { | 237 } else if (o is String || o is num || o is bool |
| 238 || o is Blob || o is KeyRange || o is ImageData || o is Node | |
| 239 || o is TypedData) { | |
| 228 return o; | 240 return o; |
| 241 } else if (o is DateTime) { | |
| 242 return Primitives.lazyAsJsDate(o); | |
| 229 } else if (o is JsObject) { | 243 } else if (o is JsObject) { |
| 230 return o._jsObject; | 244 return o._jsObject; |
| 231 } else if (o is Serializable) { | |
| 232 return _convertToJS(o.toJs()); | |
| 233 } else if (o is Function) { | 245 } else if (o is Function) { |
| 234 return _getJsProxy(o, _JS_FUNCTION_PROPERTY_NAME, (o) { | 246 return _getJsProxy(o, _JS_FUNCTION_PROPERTY_NAME, (o) { |
| 235 var jsFunction = _convertDartFunction(o); | 247 var jsFunction = _convertDartFunction(o); |
| 236 // set a property on the JS closure referencing the Dart closure | 248 // set a property on the JS closure referencing the Dart closure |
| 237 _defineProperty(jsFunction, _DART_CLOSURE_PROPERTY_NAME, o); | 249 _defineProperty(jsFunction, _DART_CLOSURE_PROPERTY_NAME, o); |
| 238 return jsFunction; | 250 return jsFunction; |
| 239 }); | 251 }); |
| 240 } else { | 252 } else { |
| 241 return _getJsProxy(o, _JS_OBJECT_PROPERTY_NAME, | 253 return _getJsProxy(o, _JS_OBJECT_PROPERTY_NAME, |
| 242 (o) => JS('', 'new DartObject(#)', o)); | 254 (o) => JS('', 'new DartObject(#)', o)); |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 253 } | 265 } |
| 254 | 266 |
| 255 // converts a Dart object to a reference to a native JS object | 267 // converts a Dart object to a reference to a native JS object |
| 256 // which might be a DartObject JS->Dart proxy | 268 // which might be a DartObject JS->Dart proxy |
| 257 Object _convertToDart(o) { | 269 Object _convertToDart(o) { |
| 258 if (JS('bool', '# == null', o) || | 270 if (JS('bool', '# == null', o) || |
| 259 JS('bool', 'typeof # == "string"', o) || | 271 JS('bool', 'typeof # == "string"', o) || |
| 260 JS('bool', 'typeof # == "number"', o) || | 272 JS('bool', 'typeof # == "number"', o) || |
| 261 JS('bool', 'typeof # == "boolean"', o)) { | 273 JS('bool', 'typeof # == "boolean"', o)) { |
| 262 return o; | 274 return o; |
| 275 } else if (o is Blob || o is DateTime || o is KeyRange | |
| 276 || o is ImageData || o is Node || o is TypedData) { | |
| 277 return JS('Blob|DateTime|KeyRange|ImageData|Node|TypedData', '#', o); | |
| 263 } else if (JS('bool', 'typeof # == "function"', o)) { | 278 } else if (JS('bool', 'typeof # == "function"', o)) { |
| 264 return _getDartProxy(o, _DART_CLOSURE_PROPERTY_NAME, | 279 return _getDartProxy(o, _DART_CLOSURE_PROPERTY_NAME, |
| 265 (o) => new JsFunction._fromJs(o)); | 280 (o) => new JsFunction._fromJs(o)); |
| 266 } else if (JS('bool', '#.constructor === DartObject', o)) { | 281 } else if (JS('bool', '#.constructor === DartObject', o)) { |
| 267 return JS('', '#.o', o); | 282 return JS('', '#.o', o); |
| 268 } else { | 283 } else { |
| 269 return _getDartProxy(o, _DART_OBJECT_PROPERTY_NAME, | 284 return _getDartProxy(o, _DART_OBJECT_PROPERTY_NAME, |
| 270 (o) => new JsObject._fromJs(o)); | 285 (o) => new JsObject._fromJs(o)); |
| 271 } | 286 } |
| 272 } | 287 } |
| 273 | 288 |
| 274 Object _getDartProxy(o, String propertyName, createProxy(o)) { | 289 Object _getDartProxy(o, String propertyName, createProxy(o)) { |
| 275 var dartProxy = JS('', '#[#]', o, propertyName); | 290 var dartProxy = JS('', '#[#]', o, propertyName); |
| 276 if (dartProxy == null) { | 291 if (dartProxy == null) { |
| 277 dartProxy = createProxy(o); | 292 dartProxy = createProxy(o); |
| 278 _defineProperty(o, propertyName, dartProxy); | 293 _defineProperty(o, propertyName, dartProxy); |
| 279 } | 294 } |
| 280 return dartProxy; | 295 return dartProxy; |
| 281 } | 296 } |
| OLD | NEW |