| 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();
|
|
|
|
|