| 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 186bde7d613cac0f9b3f7b2d60e5ace528533329..eaf78716687ca532518dfecc137a45c1612508ed 100644
|
| --- a/sdk/lib/js/dartium/js_dartium.dart
|
| +++ b/sdk/lib/js/dartium/js_dartium.dart
|
| @@ -92,6 +92,8 @@ import 'dart:nativewrappers';
|
| import 'dart:math' as math;
|
| import 'dart:mirrors' as mirrors;
|
| import 'dart:html' as html;
|
| +import 'dart:indexed_db' as indexed_db;
|
| +import 'dart:typed_data';
|
|
|
| // Pretend we are always in checked mode as we aren't interested in users
|
| // running Dartium code outside of checked mode.
|
| @@ -204,10 +206,12 @@ class _DeclarationSet {
|
| * Temporary method that we hope to remove at some point. This method should
|
| * generally only be called by machine generated code.
|
| */
|
| -void registerJsInterfaces(List<Type> classes) {
|
| - if (_finalized == true) {
|
| - throw 'JSInterop class registration already finalized';
|
| - }
|
| +
|
| +void registerJsInterfaces([List<Type> classes]) {
|
| + // This method is now obsolete in Dartium.
|
| +}
|
| +
|
| +void _registerJsInterfaces(List<Type> classes) {
|
| for (Type type in classes) {
|
| if (!_jsInterfaceTypes.add(type)) continue; // Already registered.
|
| mirrors.ClassMirror typeMirror = mirrors.reflectType(type);
|
| @@ -252,19 +256,226 @@ void registerJsInterfaces(List<Type> classes) {
|
|
|
| _finalizeJsInterfaces() native "Js_finalizeJsInterfaces";
|
|
|
| +String _getJsName(mirrors.DeclarationMirror mirror) {
|
| + for (var annotation in mirror.metadata) {
|
| + if (mirrors.MirrorSystem.getName(annotation.type.simpleName) == "Js") {
|
| + mirrors.LibraryMirror library = annotation.type.owner;
|
| + var uri = library.uri;
|
| + // make sure the annotation is from package://js
|
| + if (uri.scheme == 'package' && uri.path == 'js/js.dart') {
|
| + try {
|
| + var name = annotation.reflectee.name;
|
| + return name != null ? name : "";
|
| + } catch (e) {
|
| + }
|
| + }
|
| + }
|
| + }
|
| + return null;
|
| +}
|
| +
|
| +bool _hasJsName(mirrors.DeclarationMirror mirror) =>
|
| + _getJsName(mirror) != null;
|
| +
|
| +
|
| +_getJsMemberName(mirrors.DeclarationMirror mirror) {
|
| + var name = _getJsName(mirror);
|
| + return name == null || name.isEmpty ? _getDeclarationName(mirror) :
|
| + name;
|
| +}
|
| +
|
| +// TODO(jacobr): handle setters correctyl.
|
| +String _getDeclarationName(mirrors.DeclarationMirror declaration) {
|
| + var name = mirrors.MirrorSystem.getName(declaration.simpleName);
|
| + if (declaration is mirrors.MethodMirror && declaration.isSetter) {
|
| + assert(name.endsWith("="));
|
| + name = name.substring(0, name.length - 1);
|
| + }
|
| + return name;
|
| +}
|
| +
|
| +final _JS_LIBRARY_PREFIX = "js_library";
|
| +final _UNDEFINED_VAR = "_UNDEFINED_JS_CONST";
|
| +
|
| +String _accessJsPath(String path) =>
|
| + "${_JS_LIBRARY_PREFIX}.context${path.split(".").map((p) => "['$p']").join('')}";
|
| +
|
| +void addMemberHelper(mirrors.MethodMirror declaration, String path, StringBuffer sb, {bool isStatic: false, String memberName}) {
|
| + var jsName = _getJsMemberName(declaration);
|
| + path = (path != null && path.isNotEmpty) ? "${path}.${jsName}" : jsName;
|
| + var name = memberName != null ? memberName : _getDeclarationName(declaration);
|
| + if (declaration.isConstructor) {
|
| + sb.write("factory");
|
| + } else if (isStatic) {
|
| + sb.write("static");
|
| + } else {
|
| + sb.write("patch");
|
| + }
|
| + sb.write(" ");
|
| + if (declaration.isGetter) {
|
| + sb.write("get $name => ${_accessJsPath(path)};");
|
| + } else if (declaration.isSetter) {
|
| + sb.write("set $name(v) => ${_accessJsPath(path)} = v;");
|
| + } else {
|
| + sb.write("$name(");
|
| + bool hasOptional = false;
|
| + int i = 0;
|
| + var args = <String>[];
|
| + for (var p in declaration.parameters) {
|
| + assert(!p.isNamed); // XXX throw
|
| + assert(!p.hasDefaultValue);
|
| + if (i > 0) {
|
| + sb.write(", ");
|
| + }
|
| + if (p.isOptional && !hasOptional) {
|
| + sb.write("[");
|
| + hasOptional = true;
|
| + }
|
| + var arg = "p$i";
|
| + args.add(arg);
|
| + sb.write(arg);
|
| + if (p.isOptional) {
|
| + sb.write("=${_UNDEFINED_VAR}");
|
| + }
|
| + i++;
|
| + }
|
| + if (hasOptional) {
|
| + sb.write("]");
|
| + }
|
| + // TODO(jacobr):
|
| + sb.write(") => ");
|
| + if (declaration.isConstructor) {
|
| + sb.write("new ${_JS_LIBRARY_PREFIX}.JsObject(");
|
| + }
|
| + sb
|
| + ..write(_accessJsPath(path))
|
| + ..write(declaration.isConstructor ? "," : ".apply(")
|
| + ..write("[${args.join(",")}]");
|
| +
|
| + if (hasOptional) {
|
| + sb.write(".takeWhile((i) => i != ${_UNDEFINED_VAR}).toList()");
|
| + }
|
| + sb.write(");");
|
| + }
|
| + sb.write("\n");
|
| +}
|
| +
|
| +// TODO(jacobr): make this check more robust.
|
| +bool _isExternal(mirrors.MethodMirror mirror) =>
|
| + mirror.source != null && mirror.source.startsWith("external ");
|
| +
|
| +List<String> _generateExternalMethods() {
|
| + var staticCodegen = <String>[];
|
| + mirrors.currentMirrorSystem().libraries.forEach((uri, library) {
|
| + var sb = new StringBuffer();
|
| + String jsLibraryName = _getJsName(library);
|
| + library.declarations.forEach((name, declaration) {
|
| + if (declaration is mirrors.MethodMirror) {
|
| + if (_isExternal(declaration) && (_hasJsName(declaration) || jsLibraryName != null)) {
|
| + addMemberHelper(declaration, jsLibraryName, sb);
|
| + }
|
| + } else if (declaration is mirrors.ClassMirror) {
|
| + mirrors.ClassMirror clazz = declaration;
|
| + if (_hasJsName(clazz)) {
|
| + // TODO(jacobr): verify class implements JavaScriptObject.
|
| + assert(clazz.hasReflectedType);
|
| + jsInterfaceTypes.add(clazz.reflectedType);
|
| + String jsClassName = _getJsMemberName(clazz);
|
| + var className = mirrors.MirrorSystem.getName(clazz.simpleName);
|
| + var sbPatch = new StringBuffer();
|
| + clazz.declarations.forEach((name, declaration) {
|
| + if (declaration is! mirrors.MethodMirror || !_isExternal(declaration)) return;
|
| + if (declaration.isFactoryConstructor) {
|
| + sbPatch.write(" factory ${className}({");
|
| + int i = 0;
|
| + var args = <String>[];
|
| + for (var p in declaration.parameters) {
|
| + assert(p.isNamed); // XXX throw
|
| + args.add(mirrors.MirrorSystem.getName(p.simpleName));
|
| + i++;
|
| + }
|
| + sbPatch
|
| + ..write(args.map((name) => '$name:${_UNDEFINED_VAR}').join(", "))
|
| + ..write("}) {\n"
|
| + " var ret = new ${_JS_LIBRARY_PREFIX}.JsObject.jsify({});\n");
|
| + i = 0;
|
| + for (var p in declaration.parameters) {
|
| + assert(p.isNamed); // XXX throw
|
| + var name = args[i];
|
| + var jsName = mirrors.MirrorSystem.getName(p.simpleName);
|
| + // XXX apply name conversion rules.
|
| + sbPatch.write(" if($name != ${_UNDEFINED_VAR}) ret['$jsName'] = $name;\n");
|
| + i++;
|
| + }
|
| +
|
| + sbPatch.write(" return ret;\n"
|
| + " }\n");
|
| + } else if (declaration.isConstructor) {
|
| + sbPatch.write(" ");
|
| + addMemberHelper(declaration,
|
| + (jsLibraryName != null && jsLibraryName.isNotEmpty) ? "${jsLibraryName}" : "",
|
| + sbPatch,
|
| + isStatic: true,
|
| + memberName: className);
|
| + }
|
| + });
|
| +
|
| + clazz.staticMembers.forEach((memberName, member) {
|
| + if (_isExternal(member)) {
|
| + sbPatch.write(" ");
|
| + addMemberHelper(member,
|
| + (jsLibraryName != null && jsLibraryName.isNotEmpty) ? "${jsLibraryName}.${jsClassName}" : jsClassName,
|
| + sbPatch,
|
| + isStatic: true);
|
| + }
|
| + });
|
| + if (sbPatch.isNotEmpty) {
|
| + sb.write("""
|
| +patch class $className {
|
| +$sbPatch
|
| +}
|
| +""");
|
| + }
|
| + }
|
| + }
|
| + });
|
| + if (sb.isNotEmpty) {
|
| + staticCodegen
|
| + ..add(uri.toString())
|
| + ..add("${uri}_js_interop_patch.dart")
|
| + ..add("""
|
| +import 'dart:js' as ${_JS_LIBRARY_PREFIX};
|
| +
|
| +/**
|
| + * Placeholder object for cases where we need to determine exactly how many
|
| + * args were passed to a function.
|
| + */
|
| +const ${_UNDEFINED_VAR} = const Object();
|
| +
|
| +${sb}
|
| +""");
|
| + }
|
| + });
|
| +
|
| + return staticCodegen;
|
| +}
|
| +
|
| /**
|
| * Generates a part file defining source code for JsObjectImpl and related
|
| * classes. This calass is needed so that type checks for all registered JavaScript
|
| * interop classes pass.
|
| */
|
| -String _generateJsObjectImplPart() {
|
| - Iterable<Type> types = jsInterfaceTypes;
|
| +List<String> _generateInteropPatchFiles() {
|
| + var ret = _generateExternalMethods();
|
| var libraryPrefixes = new Map<mirrors.LibraryMirror, String>();
|
| var prefixNames = new Set<String>();
|
| var sb = new StringBuffer();
|
|
|
| var implements = <String>[];
|
| - for (var type in types) {
|
| + var implementsArray = <String>[];
|
| + var listMirror = mirrors.reflectType(List);
|
| +
|
| + for (var type in jsInterfaceTypes) {
|
| mirrors.ClassMirror typeMirror = mirrors.reflectType(type);
|
| mirrors.LibraryMirror libraryMirror = typeMirror.owner;
|
| var prefixName;
|
| @@ -284,16 +495,19 @@ String _generateJsObjectImplPart() {
|
| prefixNames.add(prefixName);
|
| libraryPrefixes[libraryMirror] = prefixName;
|
| }
|
| - implements.add(
|
| + var isArray = typeMirror.isSubtypeOf(listMirror);
|
| + (isArray ? implementsArray : 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(', ')}";
|
| + buildImplementsClause(classes) =>
|
| + classes.isEmpty ? "" : "implements ${classes.join(', ')}"
|
| + var implementsClause = buildImplementsClause(implements);
|
| // TODO(jacobr): only certain classes need to be implemented by
|
| - // Function and Array.
|
| + // JsFunctionImpl.
|
| + var allTypes = []..addAll(implements)..addAll(implementsArray);
|
| sb.write('''
|
| class JsObjectImpl extends JsObject $implementsClause {
|
| JsObjectImpl.internal() : super.internal();
|
| @@ -303,11 +517,17 @@ class JsFunctionImpl extends JsFunction $implementsClause {
|
| JsFunctionImpl.internal() : super.internal();
|
| }
|
|
|
| -class JsArrayImpl<E> extends JsArray<E> $implementsClause {
|
| +class JsArrayImpl<E> extends JsArray<E> ${buildImplementsClause(implementsArray)} {
|
| JsArrayImpl.internal() : super.internal();
|
| }
|
| +
|
| +_registerAllJsInterfaces() {
|
| + _registerJsInterfaces([${allTypes.join(", ")}]);
|
| +}
|
| +
|
| ''');
|
| - return sb.toString();
|
| + ret..addAll(["dart:js", "JsInteropImpl.dart", sb.toString()]);
|
| + return ret;
|
| }
|
|
|
| // Start of block of helper methods facilitating emulating JavaScript Array
|
| @@ -479,6 +699,18 @@ JsObject get context {
|
| return _cachedContext;
|
| }
|
|
|
| +_maybeWrap(o) {
|
| + var wrapped = html.wrap_jso_no_SerializedScriptvalue(o);
|
| + if (identical(wrapped, o)) return o;
|
| + return (wrapped is html.Blob
|
| + || wrapped is html.Event
|
| + || wrapped is indexed_db.KeyRange
|
| + || wrapped is html.ImageData
|
| + || wrapped is html.Node
|
| + || wrapped is TypedData
|
| + || wrapped is html.Window) ? wrapped : o;
|
| +}
|
| +
|
| /**
|
| * Get the dart wrapper object for object. Top-level so we
|
| * we can access it from other libraries without it being
|
| @@ -501,17 +733,10 @@ void setDartHtmlWrapperFor(JsObject object, wrapper) {
|
| * wrap_jso defined in dart:html.
|
| */
|
| unwrap_jso(dartClass_instance) {
|
| - try {
|
| - if (dartClass_instance != null)
|
| - return dartClass_instance is NativeFieldWrapperClass2 ?
|
| - dartClass_instance.blink_jsObject : dartClass_instance;
|
| - else
|
| - return null;
|
| - } catch(NoSuchMethodException) {
|
| - // No blink_jsObject then return the dartClass_instance is probably an
|
| - // array that was already converted to a Dart class e.g., Uint8ClampedList.
|
| + if (dartClass_instance is NativeFieldWrapperClass2 && dartClass_instance is! JsObject)
|
| + return dartClass_instance.blink_jsObject;
|
| + else
|
| return dartClass_instance;
|
| - }
|
| }
|
|
|
| /**
|
| @@ -604,7 +829,7 @@ class JsObject extends NativeFieldWrapperClass2 {
|
| */
|
| operator [](property) {
|
| try {
|
| - return _operator_getter(property);
|
| + return _maybeWrap(_operator_getter(property));
|
| } catch (e) {
|
| // Re-throw any errors (returned as a string) as a DomException.
|
| throw new html.DomException.jsInterop(e);
|
| @@ -685,11 +910,7 @@ class JsObject extends NativeFieldWrapperClass2 {
|
| */
|
| callMethod(String method, [List args]) {
|
| try {
|
| - if (args != null) {
|
| - for (var i = 0; i < args.length; i++)
|
| - args[i] = unwrap_jso(args[i]);
|
| - }
|
| - return _callMethod(method, args);
|
| + return _maybeWrap(_callMethod(method, args));
|
| } catch (e) {
|
| if (hasProperty(method)) {
|
| // Return a DomException if DOM call returned an error.
|
| @@ -753,6 +974,29 @@ class JsObject extends NativeFieldWrapperClass2 {
|
| _callMethod(String name, List args) native "JsObject_callMethod";
|
| }
|
|
|
| +// JavaScript interop methods that do not automatically wrap to dart:html types.
|
| +// Warning: this API is not exposed to dart:js.
|
| +class JsNative {
|
| + static getProperty(JsObject o, name) {
|
| + return o._operator_getter(name);
|
| + }
|
| +
|
| + static callMethod(JsObject o, String method, List args) {
|
| + return o._callMethod(method, args);
|
| + }
|
| +
|
| + static getArrayIndex(JsArray array, int index) {
|
| + array._checkIndex(index);
|
| + return getProperty(array, index);
|
| + }
|
| +
|
| + /**
|
| + * Same behavior as new JsFunction.withThis except that JavaScript "this" is not
|
| + * wrapped.
|
| + */
|
| + static JsFunction withThis(Function f) native "JsFunction_withThisNoWrap";
|
| +}
|
| +
|
| /**
|
| * Proxies a JavaScript Function object.
|
| */
|
| @@ -769,7 +1013,10 @@ class JsFunction extends JsObject implements Function {
|
| * Invokes the JavaScript function with arguments [args]. If [thisArg] is
|
| * supplied it is the value of `this` for the invocation.
|
| */
|
| - dynamic apply(List args, {thisArg}) native "JsFunction_apply";
|
| + dynamic apply(List args, {thisArg}) =>
|
| + _maybeWrap(_apply(args, thisArg: thisArg));
|
| +
|
| + dynamic _apply(List args, {thisArg}) native "JsFunction_apply";
|
|
|
| noSuchMethod(Invocation invocation) {
|
| if (invocation.isMethod && invocation.memberName == #call) {
|
| @@ -911,3 +1158,26 @@ Function _wrapAsDebuggerVarArgsFunction(JsFunction jsFunction) =>
|
| a5 = _UNDEFINED, a6 = _UNDEFINED, a7 = _UNDEFINED, a8 = _UNDEFINED,
|
| a9 = _UNDEFINED, a10 = _UNDEFINED]) => jsFunction._applyDebuggerOnly(
|
| _stripUndefinedArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10]));
|
| +
|
| +// This method is a no-op in Dartium.
|
| +// TODO(jacobr): tag methods so we can throw if a Dart method is passed to
|
| +// JavaScript using the new interop without calling allowInterop.
|
| +Function allowInterop(Function f) => f;
|
| +
|
| +Expando<JsFunction> _interopCaptureThisExpando = new Expando<JsFunction>();
|
| +
|
| +Function allowInteropCaptureThis(Function f) {
|
| + if (f is JsFunction) {
|
| + // Behavior when the function is already a JS function is unspecified.
|
| + throw new ArgumentError(
|
| + "Function is already a JS function so cannot capture this.");
|
| + return f;
|
| + } else {
|
| + var ret = _interopCaptureThisExpando[f];
|
| + if (ret == null) {
|
| + ret = new JsFunction.withThis(f);
|
| + _interopCaptureThisExpando[f] = ret;
|
| + }
|
| + return ret;
|
| + }
|
| +}
|
|
|