Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(300)

Unified Diff: sdk/lib/js/dartium/js_dartium.dart

Issue 1194643002: Enhance dart:js interop in a backwards compatible manner. List objects can now be passed back and f… (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: sdk/lib/js/dartium/js_dartium.dart
diff --git a/sdk/lib/js/dartium/js_dartium.dart b/sdk/lib/js/dartium/js_dartium.dart
index e7f3004c686d8efbc0b5bdc038bbb28178f2f5d2..9a6f54d211aa70233cf9fa42b3e913d2e14550ce 100644
--- a/sdk/lib/js/dartium/js_dartium.dart
+++ b/sdk/lib/js/dartium/js_dartium.dart
@@ -89,6 +89,322 @@ library dart.js;
import 'dart:collection' show ListMixin;
import 'dart:nativewrappers';
+import 'dart:math' as math;
+import 'dart:mirrors' as mirrors;
+
+// TODO(jacobr): if we care about unchecked mode in Dartium we need to set this
+// to false in unchecked mode.
+final bool _CHECK_JS_INVOCATIONS = true;
+
+final _allowedMethods = new Map<Symbol, _DeclarationSet>();
+final _allowedGetters = new Map<Symbol, _DeclarationSet>();
+final _allowedSetters = new Map<Symbol, _DeclarationSet>();
+
+final Set<Type> _jsInterfaceTypes = new Set<Type>();
+Iterable<Type> get jsInterfaceTypes => _jsInterfaceTypes.toList();
+
+bool _finalized = false;
+
+class _DeclarationSet {
+ _DeclarationSet() : _methods = <mirrors.MethodMirror>[];
+
+ bool checkType(obj, type) {
+ if (obj == null) return true;
+ return mirrors.reflectType(obj.runtimeType).isSubtypeOf(type);
+ }
+
+ bool checkReturnType(value) {
+ if (value == null) return true;
+ var valueMirror = mirrors.reflectType(value.runtimeType);
+ for (var method in _methods) {
+ if (method.isGetter) {
+ // FIXME: actually check return types for getters that return function
+ // types.
+ return true;
+ } else {
+ if (valueMirror.isSubtypeOf(method.returnType)) return true;
+ }
+ }
+ return false;
+ }
+
+ bool _checkDeclaration(Invocation invocation, mirrors.MethodMirror method) {
+ if (method.isGetter) {
+ // FIXME: actually check method types against the function type returned
+ // by the getter.
+ return true;
+ }
+ var parameters = method.parameters;
+ var positionalArguments = invocation.positionalArguments;
+ // Too many arguments
+ if (parameters.length < positionalArguments.length) return false;
+ // Too few required arguments.
+ if (parameters.length > positionalArguments.length &&
+ !parameters[positionalArguments.length].isOptional) return false;
+ var endPositional = math.min(parameters.length, positionalArguments.length);
+ for (var i = 0; i < endPositional; i++) {
+ if (parameters[i].isNamed) {
+ // Not enough positional arguments.
+ return false;
+ }
+ if (!checkType(invocation.positionalArguments[i], parameters[i].type))
+ return false;
+ }
+ if (invocation.namedArguments.isNotEmpty) {
+ var startPositional;
+ for (startPositional = parameters.length -1 ; startPositional >= 0; startPositional--) {
+ if (!parameters[startPositional].isNamed) break;
+ }
+ startPositional++;
+
+ for (var name in invocation.namedArguments.keys) {
+ bool match = false;
+ for (var j = startPositional; j < parameters.length; j++) {
+ var p = parameters[j];
+ if (p.simpleName == name) {
+ if (!checkType(invocation.namedArguments[name], parameters[j].type))
+ return false;
+ match = true;
+ break;
+ }
+ }
+ if (match == false)
+ return false;
+ }
+
+ }
+ return true;
+ }
+
+ bool checkInvocation(Invocation invocation) {
+ for (var method in _methods) {
+ if (_checkDeclaration(invocation, method)) return true;
+ }
+ return false;
+ }
+
+ void add(mirrors.MethodMirror mirror) {
+ _methods.add(mirror);
+ }
+
+ final List<mirrors.MethodMirror> _methods;
+}
+
+/**
+ * Temporary method that we hope to remove at some point. Should only be called by codegen.
+ */
+void registerJsInterfaces(List<Type> classes) {
+ if (_finalized == true) {
+ throw 'JSInterop class registration already finalized';
+ }
+ Map<Symbol, List<mirrors.DeclarationMirror>> allMembers;
+ for (Type type in classes) {
+ if (!_jsInterfaceTypes.add(type)) continue; // Already registered.
+ mirrors.ClassMirror typeMirror = mirrors.reflectType(type);
+ typeMirror.declarations.forEach((symbol, declaration) {
+ if (declaration is mirrors.MethodMirror && !declaration.isStatic) {
+ if (declaration.isGetter) {
+ _allowedGetters.putIfAbsent(symbol, () => new _DeclarationSet()).add(declaration);
+ _allowedMethods.putIfAbsent(symbol, () => new _DeclarationSet()).add(declaration);
+ } else if (declaration.isSetter) {
+ _allowedSetters.putIfAbsent(symbol, () => new _DeclarationSet()).add(declaration);
+ } else if (!declaration.isConstructor) {
+ _allowedMethods.putIfAbsent(symbol, () => new _DeclarationSet()).add(declaration);
+ }
+ }
+ });
+ }
+}
+
+_finalizeJsInterfaces() native "Js_finalizeJsInterfaces";
+
+/**
+ * Generates a part file defining source code for JsObjectImpl and related
+ * classes. This is needed so that type checks for all registered JavaScript
+ * interop classes pass.
+ */
+String _generateJsObjectImplPart() {
+ Iterable<Type> types = jsInterfaceTypes;
+ var libraryPrefixes = new Map<mirrors.LibraryMirror, String>();
+ var prefixNames = new Set<String>();
+ var sb = new StringBuffer();
+
+ var implements = <String>[];
+ for (var type in types) {
+ mirrors.ClassMirror typeMirror = mirrors.reflectType(type);
+ mirrors.LibraryMirror libraryMirror = typeMirror.owner;
+ var prefixName;
+ if (libraryPrefixes.containsKey(libraryMirror)) {
+ prefixName = libraryPrefixes[libraryMirror];
+ } else {
+ var basePrefixName = mirrors.MirrorSystem.getName(libraryMirror.simpleName);
+ basePrefixName = basePrefixName.replaceAll('.', '_');
+ if (basePrefixName.isEmpty) basePrefixName = "lib";
+ prefixName = basePrefixName;
+ var i = 1;
+ while (prefixNames.contains(prefixName)) {
+ prefixName = '$basePrefixName$i';
+ i++;
+ }
+ prefixNames.add(prefixName);
+ libraryPrefixes[libraryMirror] = prefixName;
+ }
+ implements.add('${prefixName}.${mirrors.MirrorSystem.getName(typeMirror.simpleName)}');
+ }
+ libraryPrefixes.forEach((libraryMirror, prefix) {
+ sb.writeln('import "${libraryMirror.uri}" as $prefix;');
+ });
+ var implementsClause = implements.isEmpty ? "" : "implements ${implements.join(', ')}";
+ // TODO(jacobr): only certain classes need to be implemented by
+ // Function and Array.
+ sb.write('''
+class JsObjectImpl extends JsObject $implementsClause {
+ JsObjectImpl.internal() : super.internal();
+}
+
+class JsFunctionImpl extends JsFunction $implementsClause {
+ JsFunctionImpl.internal() : super.internal();
+}
+
+class JsArrayImpl<E> extends JsArray<E> $implementsClause {
+ JsArrayImpl.internal() : super.internal();
+}
+''');
+ return sb.toString();
+}
+
+// Start of block of helper methods to emulate JavaScript Array methods on Dart List.
+// TODO(jacobr): match JS more closely.
+String _toStringJs(obj) => '$obj';
+
+// TODO(jacobr): this might not exactly match JS semantics.
+int _toIntJs(obj) {
+ if (obj is int) return obj;
+ if (obj is num) return obj.toInt();
+ return num.parse('$obj'.trim(), (_) => 0).toInt();
+}
+
+// Helper to match the behavior of setting List length in JavaScript.
+int _setListLength(List list, int len) {
+ for (var i = list.length; i < len; i++) {
+ list.add(null);
+ }
+ return len;
+}
+
+// TODO(jacobr): should we really bother with this method instead of just
+// shallow copying to a JS array and using the join method?
+String _arrayJoin(List list, sep) {
+ if (sep == null) {
+ sep = ",";
+ }
+ return list.map((e) => e == null ? "" : e.toString()).join(sep.toString());
+}
+
+// TODO(jacobr): should we really bother with this method instead of just
+// shallow copying to a JS array and using the toString method?
+String _arrayToString(List list) => _arrayJoin(list, ",");
+
+int _arrayPush(List list, e) {
+ list.add(e);
+ return list.length;
+}
+
+_arrayPop(List list) => list.removeLast();
+
+// TODO(jacobr): would it be better to just copy input to a JS List
+// and call Array.concat?
+List _arrayConcat(List input, List args) {
+ var ret = new List.from(input);
+ for (var e in args) {
+ // TODO(jacobr): should we use Iterable ?
+ if (e is List) { // FIXME use Symbol.isConcatSpreadable
+ ret.addAll(e);
+ } else {
+ ret.add(e);
+ }
+ }
+ return ret;
+}
+
+List _arraySplice(List input, List args) {
+ int start = 0;
+ if (args.length > 0) {
+ var rawStart = _toIntJs(args[0]);
+ if (rawStart < 0) {
+ start = math.max(0, input.length - rawStart);
+ } else {
+ start = math.min(input.length, rawStart);
+ }
+ }
+ var end = start;
+ if (args.length > 1) {
+ var rawDeleteCount = _toIntJs(args[1]);
+ if (rawDeleteCount < 0) rawDeleteCount = 0;
+ end = math.min(input.length, start + rawDeleteCount);
+ }
+ var replacement = [];
+ var removedElements = input.getRange(start, end).toList();
+ if (args.length > 2) {
+ replacement = args.getRange(2, args.length);
+ }
+ input.replaceRange(start, end, replacement);
+ return removedElements;
+}
+
+List _arrayReverse(List l) {
+ for (var i = 0, j = l.length - 1; i < j; i++, j--) {
+ var tmp = l[i];
+ l[i] = l[j];
+ l[j] = tmp;
+ }
+ return l;
+}
+
+_arrayShift(List l) {
+ if (l.isEmpty) return null; // Really want to return JS undefined.
+ return l.removeAt(0);
+}
+
+int _arrayUnshift(List l, List args) {
+ l.insertAll(0, args);
+ return l.length;
+}
+
+_arrayExtend(List l, int newLength) {
+ for (int i = l.length; i < newLength; i++) {
+ // TODO(jacobr): we'd really like to add undefined to better match
+ // JavaScript semantics.
+ l.add(null);
+ }
+}
+
+List _arraySort(List l, rawCompare) {
+ // TODO(jacobr): alternately we could just copy the Array to JavaScript,
+ // invoke the JS sort method and then copy the result back to Dart.
+ Comparator compare;
+ if (rawCompare == null) {
+ compare = (a, b) => _toStringJs(a).compareTo(_toStringJs(b));
+ } else if (rawCompare is JsFunction) {
+ compare = (a, b) => rawCompare.apply([a, b]);
+ } else {
+ compare = rawCompare;
+ }
+ l.sort(compare);
+ return l;
+}
+// End of block of helper methods to emulate JavaScript Array methods on Dart List.
+
+/**
+ * Must be called before JS Interfaces can be used safely used and cross cast.
+ */
+void finalizeJsInterfaces() {
+ if (_finalized == true) {
+ throw 'JSInterop class registration already finalized';
+ }
+ _finalized = true;
+ _finalizeJsInterfaces();
+}
JsObject _cachedContext;
@@ -118,6 +434,18 @@ class JsObject extends NativeFieldWrapperClass2 {
static JsObject _create(JsFunction constructor, arguments) native "JsObject_constructorCallback";
+ _buildArgs(Invocation invocation) {
+ if (invocation.namedArguments.isEmpty) {
+ return invocation.positionalArguments;
+ } else {
+ var varArgs = new Map<String,Object>();
+ invocation.namedArguments.forEach((symbol, val) {
+ varArgs[mirrors.MirrorSystem.getName(symbol)] = val;
+ });
+ return invocation.positionalArguments.toList()..add(new JsObject.jsify(varArgs));
+ }
+ }
+
/**
* Constructs a [JsObject] that proxies a native Dart object; _for expert use
* only_.
@@ -232,13 +560,55 @@ class JsObject extends NativeFieldWrapperClass2 {
}
}
+ noSuchMethod(Invocation invocation) {
+ throwError() {
+ throw new NoSuchMethodError(this, invocation.memberName, invocation.positionalArguments, invocation.namedArguments);
+ }
+
+ String name = mirrors.MirrorSystem.getName(invocation.memberName);
+ if (invocation.isGetter) {
+ if (_CHECK_JS_INVOCATIONS ) {
+ var matches = _allowedGetters[invocation.memberName];
+ if (matches == null && !_allowedMethods.containsKey(invocation.memberName)) {
+ throwError();
+ }
+ var ret = this[name];
+ if (matches != null && matches.checkReturnType(ret)) return ret;
+ if (ret is Function || (ret is JsFunction /* shouldn't be needed in the future*/) && _allowedMethods.containsKey(invocation.memberName))
+ return ret; // Warning: we have not bound "this"... we could type check on the Function but that is of little value in Dart.
+ throwError();
+ } else {
+ // TODO(jacobr): should we throw if the JavaScript object doesn't have the property?
+ return this[name];
+ }
+ } else if (invocation.isSetter) {
+ if (_CHECK_JS_INVOCATIONS ) {
+ var matches = _allowedSetters[invocation.memberName];
+ if (!matches.checkInvocation(invocation)) throwError();
+ }
+ return this[name] = invocation.positionalArguments.first;
+ } else {
+ // TODO(jacobr): also allow calling getters that look like functions.
+ var matches;
+ if (_CHECK_JS_INVOCATIONS ) {
+ matches = _allowedMethods[invocation.memberName];
+ if (matches == null || !matches.checkInvocation(invocation)) throwError();
+ }
+ var ret = this.callMethod(name, _buildArgs(invocation));
+ if (_CHECK_JS_INVOCATIONS ) {
+ if (!matches.checkReturnType(ret)) throwError();
+ }
+ return ret;
+ }
+ }
+
_callMethod(String name, List args) native "JsObject_callMethod";
}
/**
* Proxies a JavaScript Function object.
*/
-class JsFunction extends JsObject {
+class JsFunction extends JsObject implements Function {
JsFunction.internal() : super.internal();
/**
@@ -253,6 +623,13 @@ class JsFunction extends JsObject {
*/
dynamic apply(List args, {thisArg}) native "JsFunction_apply";
+ noSuchMethod(Invocation invocation) {
+ if (invocation.isMethod && invocation.memberName == #call) {
+ return apply(_buildArgs(invocation));
+ }
+ return super.noSuchMethod(invocation);
+ }
+
/**
* Internal only version of apply which uses debugger proxies of Dart objects
* rather than opaque handles. This method is private because it cannot be
@@ -268,6 +645,7 @@ class JsFunction extends JsObject {
* A [List] proxying a JavaScript Array.
*/
class JsArray<E> extends JsObject with ListMixin<E> {
+ JsArray.internal() : super.internal();
factory JsArray() => _newJsArray();

Powered by Google App Engine
This is Rietveld 408576698