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

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

Issue 1583773003: Support JS$ prefix for dart and fix bug where _operator_getter and the [] operator were used incons… (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: remove tests to put with matching dart2js tests Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « sdk/lib/indexed_db/dartium/indexed_db_dartium.dart ('k') | sdk/lib/svg/dartium/svg_dartium.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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;
« no previous file with comments | « sdk/lib/indexed_db/dartium/indexed_db_dartium.dart ('k') | sdk/lib/svg/dartium/svg_dartium.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698