Chromium Code Reviews| Index: sdk/lib/js/dart2js/js.dart |
| diff --git a/sdk/lib/js/dart2js/js.dart b/sdk/lib/js/dart2js/js.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..0af96797f0e7986710fee0d796235148c9c0ffc9 |
| --- /dev/null |
| +++ b/sdk/lib/js/dart2js/js.dart |
| @@ -0,0 +1,328 @@ |
| +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| +// for details. All rights reserved. Use of this source code is governed by a |
| +// BSD-style license that can be found in the LICENSE file. |
| + |
| +library dart.js; |
| + |
| +import 'dart:async' show runAsync; |
| +import 'dart:html' show Element, document; |
|
vsm
2013/06/06 17:06:41
Can you try killing the two imports above and test
|
| +import 'dart:_foreign_helper' show JS; |
| +import 'dart:_js_helper' show convertDartClosureToJS; |
| + |
| +JsObject get context { |
| + _enterScopeIfNeeded(); |
|
vsm
2013/05/29 04:55:39
Shall we make scopes no-ops here and get rid of th
alexandre.ardhuin
2013/05/31 19:11:13
I just made some tests. On js_tests.dart we loose
vsm
2013/06/06 17:06:41
Did you eliminate dart:async above as well?
On 20
|
| + return new JsObject._fromJs(JS('Object', 'window')); |
| +} |
| + |
| +int _scopeDepth = 0; |
| + |
| +int _dartObjectsTotal = 0; |
| +final Map<int, List> _dartObjects = new Map<int, List>(); |
| + |
| +int _jsObjectsTotal = 0; |
| +final List<JsObject> _jsObjects = new List<JsObject>(); |
| + |
| +void _enterScopeIfNeeded() { |
| + if (_scopeDepth == 0) { |
| + final depth = _enterScope(); |
| + runAsync(() => _exitScope(depth)); |
| + } |
| +} |
| + |
| +scoped(f) { |
| + final depth = _enterScope(); |
| + try { |
| + return f(); |
| + } finally { |
| + _exitScope(depth); |
| + } |
| +} |
| + |
| +int _enterScope() => ++_scopeDepth; |
| + |
| +void _exitScope(int depth) { |
| + assert(_scopeDepth == depth); |
| + _dartObjects.remove(_scopeDepth); |
| + _jsObjects |
| + ..where((JsObject e) => e._scope == depth).forEach((JsObject e) { |
| + e._scope = null; |
| + }) |
| + ..removeWhere((JsObject e) => e._scope == depth); |
| + _scopeDepth--; |
| +} |
| + |
| +int $experimentalEnterScope() { |
|
vsm
2013/06/06 17:06:41
These can go.
|
| + return _enterScope(); |
| +} |
| + |
| +void $experimentalExitScope(int depth) { |
| + _exitScope(depth); |
| +} |
| + |
| +final _globalDartObjects = []; |
| +final _globalJsObjects = new List<JsObject>(); |
| + |
| +dynamic retain(Serializable<JsObject> object) { |
| + final JsObject jsObject = object.toJs(); |
| + jsObject._scope = 0; |
| + _globalJsObjects.add(jsObject); |
| + _jsObjects.removeWhere((e) => identical(e, jsObject)); |
| + return object; |
| +} |
| +void release(Serializable<JsObject> object) { |
| + final JsObject jsObject = object.toJs(); |
| + jsObject._scope = null; |
| + _removeLast(_globalJsObjects, jsObject); |
| +} |
| + |
| +void _removeLast(List l, object) { |
| + for (int i = l.length - 1; i >= 0; i--) { |
| + var element = l[i]; |
| + if (identical(element, object)) { |
| + l.removeAt(i); |
| + return; |
| + } |
| + } |
| +} |
| + |
| +JsObject jsify(dynamic data) => data == null ? null : new JsObject._json(data); |
| + |
| +class Callback implements Serializable<JsFunction> { |
| + final bool _manualDispose; |
|
vsm
2013/06/06 17:06:41
We can try getting rid of _manualDispose and relat
|
| + final Function _f; // here to allow capture in closure |
| + final bool _withThis; // here to allow capture in closure |
| + Object _jsFunction; |
|
vsm
2013/06/06 17:06:41
This should probably be typed dynamic. That is wh
|
| + |
| + Callback._internal(this._manualDispose, this._f, this._withThis) { |
| + _globalDartObjects.add(_f); |
| + _jsFunction = JS('Object', r''' |
| +(function(){ |
| + var f = #; |
| + return function(){ |
| + return f(this, Array.prototype.slice.apply(arguments)); |
| + }; |
| +}).apply(this)''', convertDartClosureToJS(_call, 2)); |
| + } |
| + |
| + _call(thisArg, List args) { |
| + if (!_globalDartObjects.any((e) => identical(e, _f)) && |
| + !_dartObjects.values.expand((e) => e).any((e) => identical(e, _f))) { |
| + throw 'function has already been disposed'; |
| + } |
| + |
| + final arguments = new List.from(args); |
| + if (_withThis) arguments.insert(0, thisArg); |
| + final deserializedArgs = arguments.map(_deserialize).toList(); |
| + if (_manualDispose) { |
| + return _serialize(Function.apply(_f, deserializedArgs)); |
| + } else { |
| + try { |
| + return _serialize(Function.apply(_f, deserializedArgs)); |
| + } finally { |
| + _dispose(); |
| + } |
| + } |
| + } |
| + |
| + _dispose() { |
| + _removeLast(_globalDartObjects, _f); |
| + } |
| + |
| + JsFunction toJs() => new JsFunction._fromJs(_jsFunction); |
| + |
| + dispose() { |
| + assert(_manualDispose); |
| + _dispose(); |
| + } |
| + |
| + factory Callback.once(Function f, {bool withThis: false}) => |
| + new Callback._internal(false, f, withThis); |
| + |
| + factory Callback.many(Function f, {bool withThis: false}) => |
| + new Callback._internal(true, f, withThis); |
| +} |
| + |
| +class JsObject implements Serializable<JsObject> { |
| + final Object _jsObject; |
| + int _scope = _scopeDepth; |
| + |
| + JsObject._fromJs(this._jsObject) { |
| + if (_scopeDepth == 0) throw 'Cannot allocate a proxy outside of a scope.'; |
| + _jsObjectsTotal++; |
| + _jsObjects.add(this); |
| + } |
| + |
| + factory JsObject(Serializable<JsFunction> constructor, [List arguments]) { |
| + _enterScopeIfNeeded(); |
| + final constr = _serialize(constructor); |
| + if (arguments == null) { |
| + return new JsObject._fromJs(JS('Object', 'new #()', constr)); |
| + } |
| + final args = arguments.map(_serialize).toList(); |
| + switch (args.length) { |
| + case 0: |
| + return new JsObject._fromJs(JS('Object', 'new #()', constr)); |
| + case 1: |
| + return new JsObject._fromJs(JS('Object', 'new #(#)', constr, args[0])); |
| + case 2: |
| + return new JsObject._fromJs(JS('Object', 'new #(#,#)', constr, args[0], |
| + args[1])); |
| + case 3: |
| + return new JsObject._fromJs(JS('Object', 'new #(#,#,#)', constr, |
| + args[0], args[1], args[2])); |
| + case 4: |
| + return new JsObject._fromJs(JS('Object', 'new #(#,#,#,#)', constr, |
| + args[0], args[1], args[2], args[3])); |
| + case 5: |
| + return new JsObject._fromJs(JS('Object', 'new #(#,#,#,#,#)', constr, |
| + args[0], args[1], args[2], args[3], args[4])); |
| + case 6: |
| + return new JsObject._fromJs(JS('Object', 'new #(#,#,#,#,#,#)', constr, |
| + args[0], args[1], args[2], args[3], args[4], args[5])); |
| + case 7: |
| + return new JsObject._fromJs(JS('Object', 'new #(#,#,#,#,#,#,#)', constr, |
| + args[0], args[1], args[2], args[3], args[4], args[5], args[6])); |
| + case 8: |
| + return new JsObject._fromJs(JS('Object', 'new #(#,#,#,#,#,#,#,#)', |
| + constr, args[0], args[1], args[2], args[3], args[4], args[5], |
| + args[6], args[7])); |
| + case 9: |
| + return new JsObject._fromJs(JS('Object', 'new #(#,#,#,#,#,#,#,#,#)', |
| + constr, args[0], args[1], args[2], args[3], args[4], args[5], |
| + args[6], args[7], args[8])); |
| + case 10: |
| + return new JsObject._fromJs(JS('Object', 'new #(#,#,#,#,#,#,#,#,#,#)', |
| + constr, args[0], args[1], args[2], args[3], args[4], args[5], |
| + args[6], args[7], args[8], args[9])); |
| + } |
| + return new JsObject._fromJs(JS('Object', r'''(function(){ |
| +var Type = function(){}; |
| +Type.prototype = #.prototype; |
| +var instance = new Type(); |
| +ret = constructor.apply(instance, #); |
| +ret = Object(ret) === ret ? ret : instance; |
| +})()''', constr, args)); |
| + } |
| + |
| + factory JsObject._json(data) { |
| + _enterScopeIfNeeded(); |
| + return new JsObject._fromJs(_serializeDataTree(data)); |
| + } |
| + |
| + static _serializeDataTree(data) { |
| + if (data is Map) { |
| + final serializedData = JS('Object', '{}'); |
| + for (var key in data.keys) { |
| + JS('Object', '#[#]=#', serializedData, key, |
| + _serializeDataTree(data[key])); |
| + } |
| + return serializedData; |
| + } else if (data is Iterable) { |
| + return data.map(_serializeDataTree).toList(); |
| + } else { |
| + return _serialize(data); |
| + } |
| + } |
| + |
| + JsObject toJs() => this; |
| + |
| + operator[](key) => _deserialize(JS('var', '#[#]', _serialize(this), key)); |
| + operator[]=(key, value) => JS('void', '#[#]=#', _serialize(this), key, |
| + _serialize(value)); |
| + |
| + operator==(other) => identical(this, other) || |
| + (other is JsObject && JS('bool', '# == #', _serialize(this), |
| + _serialize(other))); |
| + |
| + bool hasProperty(String property) => JS('bool', '# in #', property, |
| + _serialize(this)); |
| + |
| + void deleteProperty(String name) { |
| + JS('void', 'delete #[#]', _serialize(this), name); |
| + } |
| + |
| + bool instanceof(Serializable<JsFunction> type) => |
| + JS('bool', '# instanceof #', _serialize(this), _serialize(type)); |
| + |
| + String toString() { |
| + try { |
| + return JS('String', '#.toString()', _serialize(this)); |
| + } catch(e) { |
| + return super.toString(); |
| + } |
| + } |
| + |
| + callMethod(String name, [List args]) => |
| + _deserialize(JS('var', '#[#].apply(#, #)', _serialize(this), name, |
| + _serialize(this), |
| + args == null ? null : args.map(_serialize).toList())); |
| +} |
| + |
| +class JsFunction extends JsObject implements Serializable<JsFunction> { |
| + JsFunction._fromJs(jsObject) : super._fromJs(jsObject); |
| + apply(thisArg, [List args]) => |
| + _deserialize(JS('var', '#.apply(#, #)', _serialize(this), |
| + _serialize(thisArg), |
| + args == null ? null : args.map(_serialize).toList())); |
| +} |
| + |
| +abstract class Serializable<T> { |
| + T toJs(); |
| +} |
| + |
| +dynamic _serialize(dynamic o) { |
|
vsm
2013/06/06 17:06:41
Is this really serializing? Perhaps call _convert
|
| + if (o == null) { |
| + return null; |
| + } else if (o is String || o is num || o is bool) { |
| + return o; |
| + } else if (o is Element && (o.document == null || o.document == document)) { |
|
vsm
2013/06/06 17:06:41
Let's try removing the dependence on dart:html.
C
|
| + return o; |
| + } else if (o is JsObject) { |
| + if (o._scope == null){ |
| + throw 'Proxy $o has been invalidated'; |
| + } |
| + return o._jsObject; |
| + } else if (o is Serializable) { |
| + return _serialize(o.toJs()); |
| + } else { |
| + _enterScopeIfNeeded(); |
| + _dartObjectsTotal++; |
| + _dartObjects.putIfAbsent(_scopeDepth, () => []).add(o); |
| + return o; |
|
vsm
2013/06/06 17:06:41
Does this mean we're passing a raw Dart object to
|
| + } |
| +} |
| +dynamic _deserialize(dynamic o) { |
|
vsm
2013/06/06 17:06:42
_convertToDart?
|
| + if (o == null) { |
| + return null; |
| + } else if (o is num || o is String || o is bool) { |
| + return o; |
| + } else if (JS('bool', '# instanceof Element && ' |
| + '(#.ownerDocument == null || #.ownerDocument === document)', o, o, o)) { |
| + return JS('Element', '#', o); |
| + } else if (JS('bool', '# instanceof Function', o)) { |
| + return new JsFunction._fromJs(JS('Function', '#', o)); |
| + } else if (_globalDartObjects.any((e) => identical(e, o)) || |
| + _dartObjects.values.expand((e) => e).any((e) => identical(e, o))) { |
| + return o; |
| + } else { |
| + return new JsObject._fromJs(JS('Object', '#', o)); |
| + } |
| +} |
| + |
| +int proxyCount({all: false, dartOnly: false, jsOnly: false}) { |
| + final js = !dartOnly; |
|
vsm
2013/06/06 17:06:42
Note, if you're getting rid of scopes, you can try
|
| + final dart = !jsOnly; |
| + var sum = 0; |
| + if (!all) { |
| + if (js) |
| + sum += _globalJsObjects.length; |
| + if (dart) |
| + sum += _globalDartObjects.length; |
| + } else { |
| + if (js) |
| + sum += _jsObjectsTotal; |
| + if (dart) |
| + sum += _dartObjectsTotal; |
| + } |
| + return sum; |
| +} |