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 && |
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; |