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; |
+} |