| OLD | NEW |
| (Empty) | |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 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 [Proxy] 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 [Proxy] to the global JavaScript |
| 16 * context for the page your Dart code is running on. In the following example: |
| 17 * |
| 18 * import 'package:js/js.dart' as js; |
| 19 * |
| 20 * void main() { |
| 21 * js.context.alert('Hello from Dart via JavaScript'); |
| 22 * } |
| 23 * |
| 24 * js.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 * js.context.dartCallback = (x) => print(x*2); |
| 34 * |
| 35 * defines a top-level JavaScript function 'dartCallback' that is a proxy to |
| 36 * the corresponding Dart function. |
| 37 * |
| 38 * Note, parameters and return values are intuitively passed by value for |
| 39 * primitives and by reference for non-primitives. In the latter case, the |
| 40 * references are automatically wrapped and unwrapped as proxies by the library. |
| 41 * |
| 42 * This library also allows construction of JavaScripts objects given a [Proxy] |
| 43 * to a corresponding JavaScript constructor. For example, if the following |
| 44 * JavaScript is loaded on the page: |
| 45 * |
| 46 * function Foo(x) { |
| 47 * this.x = x; |
| 48 * } |
| 49 * |
| 50 * Foo.prototype.add = function(other) { |
| 51 * return new Foo(this.x + other.x); |
| 52 * } |
| 53 * |
| 54 * then, the following Dart: |
| 55 * |
| 56 * var foo = new js.Proxy(js.context.Foo, 42); |
| 57 * var foo2 = foo.add(foo); |
| 58 * print(foo2.x); |
| 59 * |
| 60 * will construct a JavaScript Foo object with the parameter 42, invoke its |
| 61 * add method, and return a [Proxy] to a new Foo object whose x field is 84. |
| 62 * |
| 63 * See [samples](http://dart-lang.github.com/js-interop/example) for more |
| 64 * examples of usage. |
| 65 * |
| 66 * See this [article](http://www.dartlang.org/articles/js-dart-interop) for |
| 67 * more detailed discussion. |
| 68 */ |
| 69 |
| 70 library js; |
| 71 |
| 72 import 'dart:js' as js; |
| 73 @MirrorsUsed(symbols: '*') |
| 74 import 'dart:mirrors'; |
| 75 |
| 76 /** |
| 77 * A proxy on the global JavaScript context for this page. |
| 78 */ |
| 79 final Proxy context = new Proxy._(js.context); |
| 80 |
| 81 /** |
| 82 * Check if [proxy] is instance of [type]. |
| 83 */ |
| 84 bool instanceof(Serializable<Proxy> proxy, Serializable<FunctionProxy> type) => |
| 85 proxy.toJs()._jsObject.instanceof(type.toJs()._jsObject); |
| 86 |
| 87 /** |
| 88 * Check if [proxy] has a [name] property. |
| 89 */ |
| 90 bool hasProperty(Serializable<Proxy> proxy, String name) => |
| 91 proxy.toJs()._jsObject.hasProperty(name); |
| 92 |
| 93 /** |
| 94 * Delete the [name] property of [proxy]. |
| 95 */ |
| 96 void deleteProperty(Serializable<Proxy> proxy, String name) { |
| 97 proxy.toJs()._jsObject.deleteProperty(name); |
| 98 } |
| 99 |
| 100 /** |
| 101 * Converts a Dart map [data] to a JavaScript map and return a [Proxy] to it. |
| 102 */ |
| 103 Proxy map(Map data) => new Proxy._json(data); |
| 104 |
| 105 /** |
| 106 * Converts a Dart [Iterable] to a JavaScript array and return a [Proxy] to it. |
| 107 */ |
| 108 Proxy array(Iterable data) => new Proxy._json(data); |
| 109 |
| 110 // Detect unspecified arguments. |
| 111 class _Undefined { |
| 112 const _Undefined(); |
| 113 } |
| 114 |
| 115 const _undefined = const _Undefined(); |
| 116 |
| 117 List _pruneUndefined(arg1, arg2, arg3, arg4, arg5, arg6) { |
| 118 // This assumes no argument |
| 119 final args = [arg1, arg2, arg3, arg4, arg5, arg6]; |
| 120 final index = args.indexOf(_undefined); |
| 121 if (index < 0) return args; |
| 122 return args.sublist(0, index); |
| 123 } |
| 124 |
| 125 /** |
| 126 * Proxies to JavaScript objects. |
| 127 */ |
| 128 @proxy |
| 129 class Proxy<T extends Proxy> implements Serializable<T> { |
| 130 final js.JsObject _jsObject; |
| 131 |
| 132 Proxy._(this._jsObject); |
| 133 |
| 134 /** |
| 135 * Constructs a [Proxy] that proxies a native Dart object; _for expert use |
| 136 * only_. |
| 137 * |
| 138 * Use this constructor only if you wish to get access to JavaScript |
| 139 * properties attached to a browser host object, such as a Node or Blob, that |
| 140 * is normally automatically converted into a native Dart object. |
| 141 * |
| 142 * An exception will be thrown if [object] either is `null` or has the type |
| 143 * `bool`, `num`, or `String`. |
| 144 */ |
| 145 Proxy.fromBrowserObject(o) : this._(new js.JsObject.fromBrowserObject(o)); |
| 146 |
| 147 /** |
| 148 * Constructs a [Proxy] to a new JavaScript object by invoking a (proxy to a) |
| 149 * JavaScript [constructor]. The arguments should be either |
| 150 * primitive values, DOM elements, or Proxies. |
| 151 */ |
| 152 factory Proxy(Serializable<FunctionProxy> constructor, |
| 153 [arg1 = _undefined, |
| 154 arg2 = _undefined, |
| 155 arg3 = _undefined, |
| 156 arg4 = _undefined, |
| 157 arg5 = _undefined, |
| 158 arg6 = _undefined]) { |
| 159 var arguments = _pruneUndefined(arg1, arg2, arg3, arg4, arg5, arg6); |
| 160 return new Proxy.withArgList(constructor, arguments); |
| 161 } |
| 162 |
| 163 /** |
| 164 * Constructs a [Proxy] to a new JavaScript object by invoking a (proxy to a) |
| 165 * JavaScript [constructor]. The [arguments] list should contain either |
| 166 * primitive values, DOM elements, or Proxies. |
| 167 */ |
| 168 factory Proxy.withArgList(Serializable<FunctionProxy> constructor, |
| 169 List arguments) => new Proxy._(new js.JsObject( |
| 170 constructor.toJs()._jsObject, arguments.map(_serialize).toList())); |
| 171 |
| 172 /** |
| 173 * Constructs a [Proxy] to a new JavaScript map or list created defined via |
| 174 * Dart map or list. |
| 175 */ |
| 176 factory Proxy._json(data) => |
| 177 new Proxy._(new js.JsObject.jsify(_serializeDataTree(data))); |
| 178 |
| 179 static _serializeDataTree(data) { |
| 180 if (data is Map) { |
| 181 final map = new Map(); |
| 182 for (var key in data.keys) { |
| 183 map[key] = _serializeDataTree(data[key]); |
| 184 } |
| 185 return map; |
| 186 } else if (data is Iterable) { |
| 187 return data.map(_serializeDataTree).toList(); |
| 188 } else { |
| 189 return _serialize(data); |
| 190 } |
| 191 } |
| 192 |
| 193 Proxy toJs() => this; |
| 194 |
| 195 // Resolve whether this is needed. |
| 196 operator[](arg) => _deserialize(_jsObject[arg], thisArg: this); |
| 197 |
| 198 // Resolve whether this is needed. |
| 199 operator[]=(key, value) => _jsObject[key] = _serialize(value); |
| 200 |
| 201 int get hashCode => _jsObject.hashCode; |
| 202 |
| 203 // Test if this is equivalent to another Proxy. This essentially |
| 204 // maps to JavaScript's == operator. |
| 205 operator==(other) => _jsObject == _serialize(other); |
| 206 |
| 207 String toString() => _jsObject.toString(); |
| 208 |
| 209 // Forward member accesses to the backing JavaScript object. |
| 210 noSuchMethod(Invocation invocation) { |
| 211 String member = MirrorSystem.getName(invocation.memberName); |
| 212 // If trying to access a JavaScript field/variable that starts with |
| 213 // _ (underscore), Dart treats it a library private and member name |
| 214 // it suffixed with '@internalLibraryIdentifier' which we have to |
| 215 // strip before sending over to the JS side. |
| 216 if (member.indexOf('@') != -1) { |
| 217 member = member.substring(0, member.indexOf('@')); |
| 218 } |
| 219 if (invocation.isGetter) { |
| 220 if (_jsObject.hasProperty(member)) { |
| 221 return _deserialize(_jsObject[member], thisArg: this); |
| 222 } else { |
| 223 super.noSuchMethod(invocation); |
| 224 } |
| 225 } else if (invocation.isSetter) { |
| 226 if (member.endsWith('=')) { |
| 227 member = member.substring(0, member.length - 1); |
| 228 } |
| 229 _jsObject[member] = _serialize(invocation.positionalArguments[0]); |
| 230 return null; |
| 231 } else { |
| 232 return _deserialize(_jsObject.callMethod(member, |
| 233 invocation.positionalArguments.map(_serialize).toList()), |
| 234 thisArg: this); |
| 235 } |
| 236 } |
| 237 } |
| 238 |
| 239 class _CallbackFunction implements Function { |
| 240 final Function f; |
| 241 final bool withThis; |
| 242 |
| 243 _CallbackFunction(this.f, {this.withThis}); |
| 244 |
| 245 call() => throw new StateError('There should always been at least 1 parameter' |
| 246 '(js this).'); |
| 247 |
| 248 noSuchMethod(Invocation invocation) { |
| 249 final args = invocation.positionalArguments.skip( |
| 250 withThis != null && withThis ? 0 : 1); |
| 251 return _serialize(Function.apply(f, |
| 252 args.map((e) => _deserialize(e)).toList())); |
| 253 } |
| 254 } |
| 255 |
| 256 /// A [Proxy] subtype to JavaScript functions. |
| 257 class FunctionProxy extends Proxy<FunctionProxy> implements Function { |
| 258 final js.JsFunction _jsFunction; |
| 259 final _thisArg; |
| 260 |
| 261 FunctionProxy._(js.JsFunction jsFunction, {thisArg}) : |
| 262 this._jsFunction = jsFunction, |
| 263 this._thisArg = thisArg, |
| 264 super._(jsFunction); |
| 265 |
| 266 factory FunctionProxy(Function f) => new FunctionProxy._( |
| 267 new js.JsFunction.withThis(new _CallbackFunction(f))); |
| 268 |
| 269 factory FunctionProxy.withThis(Function f) => new FunctionProxy._( |
| 270 new js.JsFunction.withThis(new _CallbackFunction(f, withThis: true))); |
| 271 |
| 272 // We need to implement call() to satisfy the Function "interface" |
| 273 // This handles the no-arg case, noSuchMethod handles the rest. |
| 274 call() => _deserialize(_jsFunction.apply([], thisArg: _serialize(_thisArg)), |
| 275 thisArg: this); |
| 276 |
| 277 noSuchMethod(Invocation invocation) { |
| 278 String member = MirrorSystem.getName(invocation.memberName); |
| 279 // If trying to access a JavaScript field/variable that starts with |
| 280 // _ (underscore), Dart treats it a library private and member name |
| 281 // it suffixed with '@internalLibraryIdentifier' which we have to |
| 282 // strip before sending over to the JS side. |
| 283 if (member.indexOf('@') != -1) { |
| 284 member = member.substring(0, member.indexOf('@')); |
| 285 } |
| 286 if (member == 'call') { |
| 287 // A 'call' (probably) means that this proxy was invoked directly |
| 288 // as if it was a function. Map this to JS function application. |
| 289 return _deserialize(_jsFunction.apply( |
| 290 invocation.positionalArguments.map(_serialize).toList(), |
| 291 thisArg: _serialize(_thisArg)), thisArg: this); |
| 292 } |
| 293 return super.noSuchMethod(invocation); |
| 294 } |
| 295 } |
| 296 |
| 297 /// Marker class used to indicate it is serializable to js. If a class is a |
| 298 /// [Serializable] the "toJs" method will be called and the result will be used |
| 299 /// as value. |
| 300 abstract class Serializable<T> { |
| 301 T toJs(); |
| 302 } |
| 303 |
| 304 _serialize(var o) { |
| 305 if (o == null) { |
| 306 return null; |
| 307 } else if (o is Proxy) { |
| 308 return o._jsObject; |
| 309 } else if (o is Serializable) { |
| 310 return _serialize(o.toJs()); |
| 311 } else if (o is Function) { |
| 312 return _serialize(new FunctionProxy(o)); |
| 313 } else { |
| 314 return o; |
| 315 } |
| 316 } |
| 317 |
| 318 _deserialize(var o, {thisArg}) { |
| 319 if (o == null) { |
| 320 return null; |
| 321 } else if (o is js.JsFunction) { |
| 322 return new FunctionProxy._(o, thisArg: thisArg); |
| 323 } else if (o is js.JsObject) { |
| 324 return new Proxy._(o); |
| 325 } else { |
| 326 return o; |
| 327 } |
| 328 } |
| OLD | NEW |