Chromium Code Reviews| 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 ce2c5a2a301356c4fd9d6aaed882123ef83ee98d..5456924fc749cc23888b8705e5892ca636e79eac 100644 |
| --- a/sdk/lib/js/dartium/js_dartium.dart |
| +++ b/sdk/lib/js/dartium/js_dartium.dart |
| @@ -101,6 +101,26 @@ import 'dart:typed_data'; |
| @Deprecated("Internal Use Only") |
| final bool CHECK_JS_INVOCATIONS = true; |
| +final String _DART_RESERVED_NAME_PREFIX = r'JS$'; |
| + |
| +String _stripReservedNamePrefix(String name) => |
| + name.startsWith(_DART_RESERVED_NAME_PREFIX) |
| + ? name.substring(_DART_RESERVED_NAME_PREFIX.length) |
| + : name; |
| + |
| +_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(maybeWrapTypedInterop(new JsObject.jsify(varArgs))); |
| + } |
| +} |
| + |
| final _allowedMethods = new Map<Symbol, _DeclarationSet>(); |
| final _allowedGetters = new Map<Symbol, _DeclarationSet>(); |
| final _allowedSetters = new Map<Symbol, _DeclarationSet>(); |
| @@ -292,6 +312,20 @@ bool _isAnonymousClass(mirrors.ClassMirror mirror) { |
| bool _hasJsName(mirrors.DeclarationMirror mirror) => _getJsName(mirror) != null; |
| +bool hasDomName(mirrors.DeclarationMirror mirror) { |
| + var location = mirror.location; |
| + if (location == null || location.sourceUri.scheme != 'dart') return false; |
| + for (var annotation in mirror.metadata) { |
| + if (mirrors.MirrorSystem.getName(annotation.type.simpleName) == "DomName") { |
| + // We can't make sure the annotation is in dart: as Dartium believes it |
| + // is file://dart/sdk/lib/html/html_common/metadata.dart |
| + // instead of a proper dart: location. |
| + return true; |
| + } |
| + } |
| + return false; |
| +} |
| + |
| _getJsMemberName(mirrors.DeclarationMirror mirror) { |
| var name = _getJsName(mirror); |
| return name == null || name.isEmpty ? _getDeclarationName(mirror) : name; |
| @@ -304,7 +338,7 @@ String _getDeclarationName(mirrors.DeclarationMirror declaration) { |
| assert(name.endsWith("=")); |
| name = name.substring(0, name.length - 1); |
| } |
| - return name; |
| + return _stripReservedNamePrefix(name); |
| } |
| final _JS_LIBRARY_PREFIX = "js_library"; |
| @@ -347,16 +381,20 @@ void addMemberHelper( |
| } |
| sb.write(" "); |
| if (declaration.isGetter) { |
| - sb.write("get $name => ${_JS_LIBRARY_PREFIX}.maybeWrapTypedInterop(${_accessJsPath(path)});"); |
| + sb.write( |
| + "get $name => ${_JS_LIBRARY_PREFIX}.maybeWrapTypedInterop(${_accessJsPath(path)});"); |
| } else if (declaration.isSetter) { |
| - sb.write("set $name(v) => ${_JS_LIBRARY_PREFIX}.maybeWrapTypedInterop(${_accessJsPathSetter(path)});"); |
| + sb.write("set $name(v) {\n" |
| + " ${_JS_LIBRARY_PREFIX}.safeForTypedInterop(v);\n" |
| + " return ${_JS_LIBRARY_PREFIX}.maybeWrapTypedInterop(${_accessJsPathSetter(path)});\n" |
| + "}\n"); |
| } 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.isNamed); // TODO(jacobr): throw. |
| assert(!p.hasDefaultValue); |
| if (i > 0) { |
| sb.write(", "); |
| @@ -377,8 +415,11 @@ void addMemberHelper( |
| sb.write("]"); |
| } |
| // TODO(jacobr): |
| - sb.write(") => "); |
| - sb.write('${_JS_LIBRARY_PREFIX}.maybeWrapTypedInterop('); |
| + sb.write(") {\n"); |
| + for (var arg in args) { |
| + sb.write(" ${_JS_LIBRARY_PREFIX}.safeForTypedInterop($arg);\n"); |
| + } |
| + sb.write(" return ${_JS_LIBRARY_PREFIX}.maybeWrapTypedInterop("); |
| if (declaration.isConstructor) { |
| sb.write("new ${_JS_LIBRARY_PREFIX}.JsObject("); |
| } |
| @@ -391,6 +432,7 @@ void addMemberHelper( |
| sb.write(".takeWhile((i) => i != ${_UNDEFINED_VAR}).toList()"); |
| } |
| sb.write("));"); |
| + sb.write("}\n"); |
| } |
| sb.write("\n"); |
| } |
| @@ -399,7 +441,7 @@ bool _isExternal(mirrors.MethodMirror mirror) { |
| // This try-catch block is a workaround for BUG:24834. |
| try { |
| return mirror.isExternal; |
| - } catch (e) { } |
| + } catch (e) {} |
| return false; |
| } |
| @@ -416,195 +458,106 @@ List<String> _generateExternalMethods() { |
| } |
| } else if (declaration is mirrors.ClassMirror) { |
| mirrors.ClassMirror clazz = declaration; |
| - if (_hasJsName(clazz)) { |
| + var isDom = hasDomName(clazz); |
| + var isJsInterop = _hasJsName(clazz); |
| + if (isDom || isJsInterop) { |
| // TODO(jacobr): verify class implements JavaScriptObject. |
| - String jsClassName = _getJsMemberName(clazz); |
| var className = mirrors.MirrorSystem.getName(clazz.simpleName); |
| + var classNameImpl = '${className}Impl'; |
| var sbPatch = new StringBuffer(); |
| - jsInterfaceTypes.add(clazz); |
| - clazz.declarations.forEach((name, declaration) { |
| - if (declaration is! mirrors.MethodMirror || |
| - !_isExternal(declaration)) return; |
| - if (declaration.isFactoryConstructor && _isAnonymousClass(clazz)) { |
| - sbPatch.write(" factory ${className}("); |
| - int i = 0; |
| - var args = <String>[]; |
| - for (var p in declaration.parameters) { |
| - args.add(mirrors.MirrorSystem.getName(p.simpleName)); |
| - i++; |
| - } |
| - if (args.isNotEmpty) { |
| - sbPatch |
| - ..write('{') |
| - ..write( |
| - args.map((name) => '$name:${_UNDEFINED_VAR}').join(", ")) |
| - ..write('}'); |
| - } |
| - sbPatch.write(") {\n" |
| + if (isJsInterop) { |
| + String jsClassName = _getJsMemberName(clazz); |
| + |
| + jsInterfaceTypes.add(clazz); |
| + clazz.declarations.forEach((name, declaration) { |
| + if (declaration is! mirrors.MethodMirror || |
| + !_isExternal(declaration)) return; |
| + if (declaration.isFactoryConstructor && |
| + _isAnonymousClass(clazz)) { |
| + sbPatch.write(" factory ${className}("); |
| + int i = 0; |
| + var args = <String>[]; |
| + for (var p in declaration.parameters) { |
| + args.add(mirrors.MirrorSystem.getName(p.simpleName)); |
| + i++; |
| + } |
| + if (args.isNotEmpty) { |
| + sbPatch |
| + ..write('{') |
| + ..write(args |
| + .map((name) => '$name:${_UNDEFINED_VAR}') |
| + .join(", ")) |
| + ..write('}'); |
| + } |
| + sbPatch.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. |
| + i = 0; |
| + for (var p in declaration.parameters) { |
| + assert(p.isNamed); // TODO(jacobr): throw. |
| + var name = args[i]; |
| + var jsName = _stripReservedNamePrefix( |
| + mirrors.MirrorSystem.getName(p.simpleName)); |
| + sbPatch.write(" if($name != ${_UNDEFINED_VAR}) {\n" |
| + " ${_JS_LIBRARY_PREFIX}.safeForTypedInterop($name);\n" |
| + " ret['$jsName'] = $name;\n" |
| + " }\n"); |
| + i++; |
| + } |
| + |
| sbPatch.write( |
| - " if($name != ${_UNDEFINED_VAR}) ret['$jsName'] = $name;\n"); |
| - i++; |
| + " return new ${_JS_LIBRARY_PREFIX}.JSObject.create(ret);\n" |
| + " }\n"); |
| + } else if (declaration.isConstructor || |
| + declaration.isFactoryConstructor) { |
| + sbPatch.write(" "); |
| + addMemberHelper( |
| + declaration, |
| + (jsLibraryName != null && jsLibraryName.isNotEmpty) |
| + ? "${jsLibraryName}.${jsClassName}" |
| + : jsClassName, |
| + sbPatch, |
| + isStatic: true, |
| + memberName: className); |
| } |
| - |
| - sbPatch.write(" return ret;\n" |
| - " }\n"); |
| - } else if (declaration.isConstructor || |
| - declaration.isFactoryConstructor) { |
| - sbPatch.write(" "); |
| - addMemberHelper( |
| - declaration, |
| - (jsLibraryName != null && jsLibraryName.isNotEmpty) |
| - ? "${jsLibraryName}.${jsClassName}" |
| - : jsClassName, |
| - 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); |
| - } |
| - }); |
| - var typeVariablesClause = ''; |
| - if (!clazz.typeVariables.isEmpty) { |
| - typeVariablesClause = |
| - '<${clazz.typeVariables.map((m) => mirrors.MirrorSystem.getName(m.simpleName)).join(',')}>'; |
| + }); |
| + |
| + clazz.staticMembers.forEach((memberName, member) { |
| + if (_isExternal(member)) { |
| + sbPatch.write(" "); |
| + addMemberHelper( |
| + member, |
| + (jsLibraryName != null && jsLibraryName.isNotEmpty) |
| + ? "${jsLibraryName}.${jsClassName}" |
| + : jsClassName, |
| + sbPatch, |
| + isStatic: true); |
| + } |
| + }); |
| + } |
| + if (isDom) { |
| + sbPatch.write(" factory ${className}._internalWrap() => " |
| + "new ${classNameImpl}.internal_();\n"); |
| } |
| if (sbPatch.isNotEmpty) { |
| + var typeVariablesClause = ''; |
| + if (!clazz.typeVariables.isEmpty) { |
| + typeVariablesClause = |
| + '<${clazz.typeVariables.map((m) => mirrors.MirrorSystem.getName(m.simpleName)).join(',')}>'; |
| + } |
| sb.write(""" |
| patch class $className$typeVariablesClause { |
| $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; |
| -} |
| - |
| -List<String> _generateExternalMethods2() { |
| - var staticCodegen = <String>[]; |
| - mirrors.currentMirrorSystem().libraries.forEach((uri, library) { |
| - var sb = new StringBuffer(); |
| - String jsLibraryName = _getJsName(library); |
| - library.declarations.forEach((name, declaration) { |
| - var isExternal = _isExternal(declaration); |
| - if (declaration is mirrors.MethodMirror) { |
| - if (isExternal && (_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. |
| - String jsClassName = _getJsMemberName(clazz); |
| - var className = mirrors.MirrorSystem.getName(clazz.simpleName); |
| - var sbPatch = new StringBuffer(); |
| - jsInterfaceTypes.add(clazz); |
| - clazz.declarations.forEach((name, declaration) { |
| - if (declaration is! mirrors.MethodMirror || |
| - !declaration.isAbstract || |
| - !isExternal) return; |
| - if (_hasLiteralAnnotation(declaration) && |
| - 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 || |
| - declaration.isFactoryConstructor) { |
| - sbPatch.write(" "); |
| - addMemberHelper( |
| - declaration, |
| - (jsLibraryName != null && jsLibraryName.isNotEmpty) |
| - ? "${jsLibraryName}.${jsClassName}" |
| - : jsClassName, |
| - 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); |
| - } |
| - }); |
| - var typeVariablesClause = ''; |
| - if (!clazz.typeVariables.isEmpty) { |
| - typeVariablesClause = |
| - '<${clazz.typeVariables.map((m) => mirrors.MirrorSystem.getName(m.simpleName)).join(',')}>'; |
| - } |
| - if (sbPatch.isNotEmpty) { |
| - sb.write(""" |
| -patch class $className$typeVariablesClause { |
| -$sbPatch |
| + if (isDom) { |
| + sb.write(""" |
| +class $classNameImpl$typeVariablesClause extends $className implements ${_JS_LIBRARY_PREFIX}.JSObjectInterfacesDom { |
| + ${classNameImpl}.internal_() : super.internal_(); |
| + get runtimeType => $className; |
| + toString() => super.toString(); |
| } |
| """); |
| + } |
| } |
| } |
| } |
| @@ -631,9 +584,9 @@ ${sb} |
| } |
| /** |
| - * 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. |
| + * Generates part files defining source code for JSObjectImpl, all DOM classes |
| + * classes. This codegen is needed so that type checks for all registered |
| + * JavaScript interop classes pass. |
| */ |
| List<String> _generateInteropPatchFiles() { |
| var ret = _generateExternalMethods(); |
| @@ -643,7 +596,10 @@ List<String> _generateInteropPatchFiles() { |
| var implements = <String>[]; |
| var implementsArray = <String>[]; |
| + var implementsDom = <String>[]; |
| var listMirror = mirrors.reflectType(List); |
| + var functionMirror = mirrors.reflectType(Function); |
| + var jsObjectMirror = mirrors.reflectType(JSObject); |
| for (var typeMirror in jsInterfaceTypes) { |
| mirrors.LibraryMirror libraryMirror = typeMirror.owner; |
| @@ -665,8 +621,25 @@ List<String> _generateInteropPatchFiles() { |
| libraryPrefixes[libraryMirror] = prefixName; |
| } |
| var isArray = typeMirror.isSubtypeOf(listMirror); |
| - (isArray ? implementsArray : implements).add( |
| - '${prefixName}.${mirrors.MirrorSystem.getName(typeMirror.simpleName)}'); |
| + var isFunction = typeMirror.isSubtypeOf(functionMirror); |
| + var isJSObject = typeMirror.isSubtypeOf(jsObjectMirror); |
| + var fullName = |
| + '${prefixName}.${mirrors.MirrorSystem.getName(typeMirror.simpleName)}'; |
| + (isArray ? implementsArray : implements).add(fullName); |
| + if (!isArray && !isFunction && !isJSObject) { |
| + // For DOM classes we need to be a bit more conservative at tagging them |
| + // as implementing JS inteorp classes risks strange unintended |
| + // consequences as unrleated code may have instanceof checks. Checking |
| + // for isJSObject ensures we do not accidentally pull in existing |
| + // dart:html classes as they all have JSObject as a base class. |
| + // Note that methods from these classes can still be called on a |
| + // dart:html instance but checked mode type checks will fail. This is |
| + // not ideal but is better than causing strange breaks in existing |
| + // code that uses dart:html. |
| + // TODO(jacobr): consider throwing compile time errors if @JS classes |
| + // extend JSObject as that case cannot be safely handled in Dartium. |
| + implementsDom.add(fullName); |
| + } |
| } |
| libraryPrefixes.forEach((libraryMirror, prefix) { |
| sb.writeln('import "${libraryMirror.uri}" as $prefix;'); |
| @@ -674,20 +647,45 @@ List<String> _generateInteropPatchFiles() { |
| buildImplementsClause(classes) => |
| classes.isEmpty ? "" : "implements ${classes.join(', ')}"; |
| var implementsClause = buildImplementsClause(implements); |
| + var implementsClauseDom = buildImplementsClause(implementsDom); |
| // TODO(jacobr): only certain classes need to be implemented by |
| // JsFunctionImpl. |
| var allTypes = []..addAll(implements)..addAll(implementsArray); |
| sb.write(''' |
| -class JsObjectImpl extends JsObject $implementsClause { |
| - JsObjectImpl.internal() : super.internal(); |
| +class JSObjectImpl extends JSObject $implementsClause { |
| + JSObjectImpl.internal() : super.internal(); |
| +} |
| + |
| +class JSFunctionImpl extends JSFunction $implementsClause { |
| + JSFunctionImpl.internal() : super.internal(); |
| } |
| -class JsFunctionImpl extends JsFunction $implementsClause { |
| - JsFunctionImpl.internal() : super.internal(); |
| +class JSArrayImpl extends JSArray ${buildImplementsClause(implementsArray)} { |
| + JSArrayImpl.internal() : super.internal(); |
| } |
| -class JsArrayImpl<E> extends JsArray<E> ${buildImplementsClause(implementsArray)} { |
| - JsArrayImpl.internal() : super.internal(); |
| +// Interfaces that are safe to slam on all DOM classes. |
| +// Adding implementsClause would be risky as it could contain Function which |
| +// is likely to break a lot of instanceof checks. |
| +abstract class JSObjectInterfacesDom $implementsClauseDom { |
| +} |
| + |
| +patch class JSObject { |
| + factory JSObject.create(JsObject jsObject) { |
| + return new JSObjectImpl.internal()..blink_jsObject = jsObject; |
| + } |
| +} |
| + |
| +patch class JSFunction { |
| + factory JSFunction.create(JsObject jsObject) { |
| + return new JSFunctionImpl.internal()..blink_jsObject = jsObject; |
| + } |
| +} |
| + |
| +patch class JSArray { |
| + factory JSArray.create(JsObject jsObject) { |
| + return new JSArrayImpl.internal()..blink_jsObject = jsObject; |
| + } |
| } |
| _registerAllJsInterfaces() { |
| @@ -695,7 +693,7 @@ _registerAllJsInterfaces() { |
| } |
| '''); |
| - ret..addAll(["dart:js", "JsInteropImpl.dart", sb.toString()]); |
| + ret..addAll(["dart:js", "JSInteropImpl.dart", sb.toString()]); |
| return ret; |
| } |
| @@ -870,8 +868,7 @@ JsObject get context { |
| } |
| @Deprecated("Internal Use Only") |
| -maybeWrapTypedInterop(o) => |
| - html_common.wrap_jso_no_SerializedScriptvalue(o); |
| +maybeWrapTypedInterop(o) => html_common.wrap_jso_no_SerializedScriptvalue(o); |
| _maybeWrap(o) { |
| var wrapped = html_common.wrap_jso_no_SerializedScriptvalue(o); |
| @@ -910,7 +907,7 @@ void setDartHtmlWrapperFor(JsObject object, wrapper) { |
| */ |
| @Deprecated("Internal Use Only") |
| unwrap_jso(dartClass_instance) { |
| - if (dartClass_instance is html.DartHtmlDomObject && |
| + if (dartClass_instance is JSObject && |
|
Alan Knight
2016/01/14 00:22:09
I suppose the ship has sailed that we have them as
|
| dartClass_instance is! JsObject) return dartClass_instance.blink_jsObject; |
| else return dartClass_instance; |
| } |
| @@ -946,19 +943,6 @@ 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_. |
| @@ -1099,13 +1083,28 @@ class JsObject extends NativeFieldWrapperClass2 { |
| } |
| } |
| + _callMethod(String name, List args) native "JsObject_callMethod"; |
| +} |
| + |
| +/// Base class for all JS objects used through dart:html and typed JS interop. |
| +@Deprecated("Internal Use Only") |
| +class JSObject { |
| + JSObject.internal() {} |
| + external factory JSObject.create(JsObject jsObject); |
| + |
| + @Deprecated("Internal Use Only") |
| + JsObject blink_jsObject; |
| + |
| + String toString() => blink_jsObject.toString(); |
| + |
| noSuchMethod(Invocation invocation) { |
| throwError() { |
| - throw new NoSuchMethodError(this, invocation.memberName, |
| - invocation.positionalArguments, invocation.namedArguments); |
| + super.noSuchMethod(invocation); |
| } |
| - String name = mirrors.MirrorSystem.getName(invocation.memberName); |
| + String name = _stripReservedNamePrefix( |
| + mirrors.MirrorSystem.getName(invocation.memberName)); |
| + argsSafeForTypedInterop(invocation.positionalArguments); |
| if (invocation.isGetter) { |
| if (CHECK_JS_INVOCATIONS) { |
| var matches = _allowedGetters[invocation.memberName]; |
| @@ -1113,8 +1112,8 @@ class JsObject extends NativeFieldWrapperClass2 { |
| !_allowedMethods.containsKey(invocation.memberName)) { |
| throwError(); |
| } |
| - var ret = this[name]; |
| - if (matches != null && matches._checkReturnType(ret)) return ret; |
| + var ret = maybeWrapTypedInterop(blink_jsObject._operator_getter(name)); |
| + if (matches != null) return ret; |
| if (ret is Function || |
| (ret is JsFunction /* shouldn't be needed in the future*/) && |
| _allowedMethods.containsKey( |
| @@ -1122,7 +1121,7 @@ class JsObject extends NativeFieldWrapperClass2 { |
| throwError(); |
| } else { |
| // TODO(jacobr): should we throw if the JavaScript object doesn't have the property? |
| - return maybeWrapTypedInterop(this._operator_getter(name)); |
| + return maybeWrapTypedInterop(blink_jsObject._operator_getter(name)); |
| } |
| } else if (invocation.isSetter) { |
| if (CHECK_JS_INVOCATIONS) { |
| @@ -1132,7 +1131,7 @@ class JsObject extends NativeFieldWrapperClass2 { |
| } |
| assert(name.endsWith("=")); |
| name = name.substring(0, name.length - 1); |
| - return maybeWrapTypedInterop(_operator_setter( |
| + return maybeWrapTypedInterop(blink_jsObject._operator_setter( |
| name, invocation.positionalArguments.first)); |
| } else { |
| // TODO(jacobr): also allow calling getters that look like functions. |
| @@ -1142,15 +1141,61 @@ class JsObject extends NativeFieldWrapperClass2 { |
| if (matches == null || |
| !matches.checkInvocation(invocation)) throwError(); |
| } |
| - var ret = maybeWrapTypedInterop(this._callMethod(name, _buildArgs(invocation))); |
| + var ret = maybeWrapTypedInterop( |
| + blink_jsObject._callMethod(name, _buildArgs(invocation))); |
| if (CHECK_JS_INVOCATIONS) { |
| - if (!matches._checkReturnType(ret)) throwError(); |
| + if (!matches._checkReturnType(ret)) { |
| + html.window.console.error("Return value for method: ${name} is " |
| + "${ret.runtimeType} which is inconsistent with all typed " |
| + "JS interop definitions for method ${name}."); |
| + } |
| } |
| return ret; |
| } |
| } |
| +} |
| + |
| +@Deprecated("Internal Use Only") |
| +class JSArray extends JSObject with ListMixin { |
| + JSArray.internal() : super.internal(); |
| + external factory JSArray.create(JsObject jsObject); |
| + operator [](int index) => |
| + maybeWrapTypedInterop(JsNative.getArrayIndex(blink_jsObject, index)); |
| + |
| + operator []=(int index, value) => blink_jsObject[index] = value; |
| + |
| + int get length => blink_jsObject.length; |
| + int set length(int newLength) => blink_jsObject.length = newLength; |
| +} |
| + |
| +@Deprecated("Internal Use Only") |
| +class JSFunction extends JSObject implements Function { |
| + JSFunction.internal() : super.internal(); |
| + |
| + external factory JSFunction.create(JsObject jsObject); |
| + |
| + call( |
| + [a1 = _UNDEFINED, |
| + a2 = _UNDEFINED, |
| + a3 = _UNDEFINED, |
| + a4 = _UNDEFINED, |
| + a5 = _UNDEFINED, |
| + a6 = _UNDEFINED, |
| + a7 = _UNDEFINED, |
| + a8 = _UNDEFINED, |
| + a9 = _UNDEFINED, |
| + a10 = _UNDEFINED]) { |
| + return maybeWrapTypedInterop(blink_jsObject |
| + .apply(_stripUndefinedArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10]))); |
| + } |
| - _callMethod(String name, List args) native "JsObject_callMethod"; |
| + noSuchMethod(Invocation invocation) { |
| + if (invocation.isMethod && invocation.memberName == #call) { |
| + return maybeWrapTypedInterop( |
| + blink_jsObject.apply(_buildArgs(invocation))); |
| + } |
| + return super.noSuchMethod(invocation); |
| + } |
| } |
| // JavaScript interop methods that do not automatically wrap to dart:html types. |
| @@ -1185,7 +1230,7 @@ class JsNative { |
| /** |
| * Proxies a JavaScript Function object. |
| */ |
| -class JsFunction extends JsObject implements Function { |
| +class JsFunction extends JsObject { |
| JsFunction.internal() : super.internal(); |
| /** |
| @@ -1203,27 +1248,6 @@ class JsFunction extends JsObject implements Function { |
| dynamic _apply(List args, {thisArg}) native "JsFunction_apply"; |
| - call([a1 = _UNDEFINED, |
| - a2 = _UNDEFINED, |
| - a3 = _UNDEFINED, |
| - a4 = _UNDEFINED, |
| - a5 = _UNDEFINED, |
| - a6 = _UNDEFINED, |
| - a7 = _UNDEFINED, |
| - a8 = _UNDEFINED, |
| - a9 = _UNDEFINED, |
| - a10 = _UNDEFINED]) { |
| - return apply( |
| - _stripUndefinedArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10])); |
| - } |
| - |
| - 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 |
| @@ -1349,6 +1373,39 @@ List _stripUndefinedArgs(List args) => |
| args.takeWhile((i) => i != _UNDEFINED).toList(); |
| /** |
| + * Check that that if [arg] is a [Function] it is safe to pass to JavaScript. |
| + * To make a function safe, call [allowInterop] or [allowInteropCaptureThis]. |
| + */ |
| +@Deprecated("Internal Use Only") |
| +safeForTypedInterop(arg) { |
| + if (CHECK_JS_INVOCATIONS && arg is Function && arg is! JSFunction) { |
| + throw new ArgumentError( |
| + "Attempt to pass Function '$arg' to JavaScript via without calling allowInterop or allowInteropCaptureThis"); |
| + } |
| +} |
| + |
| +/** |
| + * Check that that if any elements of [args] are [Function] it is safe to pass |
| + * to JavaScript. To make a function safe, call [allowInterop] or |
| + * [allowInteropCaptureThis]. |
| + */ |
| +@Deprecated("Internal Use Only") |
| +void argsSafeForTypedInterop(Iterable args) { |
| + for (var arg in args) { |
| + safeForTypedInterop(arg); |
| + } |
| +} |
| + |
| +List _stripAndWrapArgs(Iterable args) { |
| + var ret = []; |
| + for (var arg in args) { |
| + if (arg == _UNDEFINED) break; |
| + ret.add(maybeWrapTypedInterop(arg)); |
| + } |
| + return ret; |
| +} |
| + |
| +/** |
| * Returns a method that can be called with an arbitrary number (for n less |
| * than 11) of arguments without violating Dart type checks. |
| */ |
| @@ -1366,9 +1423,89 @@ Function _wrapAsDebuggerVarArgsFunction(JsFunction jsFunction) => ( |
| jsFunction._applyDebuggerOnly( |
| _stripUndefinedArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10])); |
| -// The allowInterop 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. |
| +/// This helper is purely a hack so we can reuse JsFunction.withThis even when |
| +/// we don't care about passing JS "this". In an ideal world we would implement |
| +/// helpers in C++ that directly implement allowInterop and |
| +/// allowInteropCaptureThis. |
| +class _CreateDartFunctionForInteropIgnoreThis implements Function { |
| + Function _fn; |
| + |
| + _CreateDartFunctionForInteropIgnoreThis(this._fn); |
| + |
| + call( |
| + [ignoredThis = _UNDEFINED, |
| + a1 = _UNDEFINED, |
| + a2 = _UNDEFINED, |
| + a3 = _UNDEFINED, |
| + a4 = _UNDEFINED, |
| + a5 = _UNDEFINED, |
| + a6 = _UNDEFINED, |
| + a7 = _UNDEFINED, |
| + a8 = _UNDEFINED, |
| + a9 = _UNDEFINED, |
| + a10 = _UNDEFINED]) { |
| + var ret = Function.apply( |
| + _fn, _stripAndWrapArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10])); |
| + safeForTypedInterop(ret); |
| + return ret; |
| + } |
| + |
| + noSuchMethod(Invocation invocation) { |
| + if (invocation.isMethod && invocation.memberName == #call) { |
| + // Named arguments not yet supported. |
| + if (invocation.namedArguments.isNotEmpty) return; |
| + var ret = Function.apply( |
| + _fn, _stripAndWrapArgs(invocation.positionalArguments.skip(1))); |
| + // TODO(jacobr): it would be nice to check that the return value is safe |
| + // for interop but we don't want to break existing addEventListener users. |
| + // safeForTypedInterop(ret); |
| + safeForTypedInterop(ret); |
| + return ret; |
| + } |
| + return super.noSuchMethod(invocation); |
| + } |
| +} |
| + |
| +/// See comment for [_CreateDartFunctionForInteropIgnoreThis]. |
| +/// This Function exists purely because JsObject doesn't have the DOM type |
| +/// conversion semantics we want for JS typed interop. |
| +class _CreateDartFunctionForInterop implements Function { |
| + Function _fn; |
| + |
| + _CreateDartFunctionForInterop(this._fn); |
| + |
| + call( |
| + [a1 = _UNDEFINED, |
| + a2 = _UNDEFINED, |
| + a3 = _UNDEFINED, |
| + a4 = _UNDEFINED, |
| + a5 = _UNDEFINED, |
| + a6 = _UNDEFINED, |
| + a7 = _UNDEFINED, |
| + a8 = _UNDEFINED, |
| + a9 = _UNDEFINED, |
| + a10 = _UNDEFINED]) { |
| + var ret = Function.apply( |
| + _fn, _stripAndWrapArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10])); |
| + safeForTypedInterop(ret); |
| + return ret; |
| + } |
| + |
| + noSuchMethod(Invocation invocation) { |
| + if (invocation.isMethod && invocation.memberName == #call) { |
| + // Named arguments not yet supported. |
| + if (invocation.namedArguments.isNotEmpty) return; |
| + var ret = Function.apply( |
| + _fn, _stripAndWrapArgs(invocation.positionalArguments)); |
| + safeForTypedInterop(ret); |
| + return ret; |
| + } |
| + return super.noSuchMethod(invocation); |
| + } |
| +} |
| + |
| +/// Cached JSFunction associated with the Dart Function. |
| +Expando<JSFunction> _interopExpando = new Expando<JSFunction>(); |
| /// Returns a wrapper around function [f] that can be called from JavaScript |
| /// using the package:js Dart-JavaScript interop. |
| @@ -1381,9 +1518,25 @@ Function _wrapAsDebuggerVarArgsFunction(JsFunction jsFunction) => ( |
| /// JavaScript. We may remove the need to call this method completely in the |
| /// future if Dart2Js is refactored so that its function calling conventions |
| /// are more compatible with JavaScript. |
| -Function allowInterop(Function f) => f; |
| +JSFunction allowInterop(Function f) { |
| + if (f is JSFunction) { |
| + // The function is already a JSFunction... no need to do anything. |
| + return f; |
| + } else { |
| + var ret = _interopExpando[f]; |
| + if (ret == null) { |
| + // TODO(jacobr): we could optimize this. |
| + ret = new JSFunction.create(new JsFunction.withThis( |
| + new _CreateDartFunctionForInteropIgnoreThis(f))); |
| + _interopExpando[f] = ret; |
| + } |
| + return ret; |
| + } |
| +} |
| -Expando<JsFunction> _interopCaptureThisExpando = new Expando<JsFunction>(); |
| +/// Cached JSFunction associated with the Dart function when "this" is |
| +/// captured. |
| +Expando<JSFunction> _interopCaptureThisExpando = new Expando<JSFunction>(); |
| /// Returns a [Function] that when called from JavaScript captures its 'this' |
| /// binding and calls [f] with the value of this passed as the first argument. |
| @@ -1391,8 +1544,8 @@ Expando<JsFunction> _interopCaptureThisExpando = new Expando<JsFunction>(); |
| /// |
| /// See the documention for [allowInterop]. This method should only be used with |
| /// package:js Dart-JavaScript interop. |
| -Function allowInteropCaptureThis(Function f) { |
| - if (f is JsFunction) { |
| +JSFunction 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."); |
| @@ -1400,7 +1553,9 @@ Function allowInteropCaptureThis(Function f) { |
| } else { |
| var ret = _interopCaptureThisExpando[f]; |
| if (ret == null) { |
| - ret = new JsFunction.withThis(f); |
| + // TODO(jacobr): we could optimize this. |
| + ret = new JSFunction.create( |
| + new JsFunction.withThis(new _CreateDartFunctionForInterop(f))); |
| _interopCaptureThisExpando[f] = ret; |
| } |
| return ret; |