Add JSNative utility class with static methods methods to efficiently manipulate typed JSInterop objects in cases where the member name is not known statically. These methods would be extension methods on JSObject if Dart supported extension methods. Update package js to export these methods. Implement in Dart2JS. Implement JS$ in dart2js.

BUG=
R=alanknight@google.com, sra@google.com

Review URL: https://codereview.chromium.org/2150313003 .
This commit is contained in:
Jacob Richman 2016-07-25 09:59:01 -07:00
parent f5e157f7d0
commit 96ca5db7e5
23 changed files with 1034 additions and 228 deletions

View file

@ -35,6 +35,10 @@ class NativeData {
Map<MemberElement, NativeBehavior> nativeFieldStoreBehavior =
<FieldElement, NativeBehavior>{};
/// Prefix used to escape JS names that are not valid Dart names
/// when using JSInterop.
static const String _jsInteropEscapePrefix = r'JS$';
/// Returns `true` if [element] is explicitly marked as part of JsInterop.
bool _isJsInterop(Element element) {
return jsInteropNames.containsKey(element.declaration);
@ -93,7 +97,7 @@ class NativeData {
if (jsInteropName != null && jsInteropName.isNotEmpty) {
return jsInteropName;
}
return element.isLibrary ? 'self' : element.name;
return element.isLibrary ? 'self' : getUnescapedJSInteropName(element.name);
}
/// Computes the name for [element] to use in the generated JavaScript. This
@ -216,4 +220,12 @@ class NativeData {
FieldElement field, NativeBehavior behavior) {
nativeFieldStoreBehavior[field] = behavior;
}
/// Apply JS$ escaping scheme to convert possible escaped Dart names into
/// JS names.
String getUnescapedJSInteropName(String name) {
return name.startsWith(_jsInteropEscapePrefix)
? name.substring(_jsInteropEscapePrefix.length)
: name;
}
}

View file

@ -345,6 +345,8 @@ class ProgramBuilder {
for (Element e in elements) {
if (e is ClassElement && backend.isJsInterop(e)) {
e.declaration.forEachMember((_, Element member) {
var jsName =
backend.nativeData.getUnescapedJSInteropName(member.name);
if (!member.isInstanceMember) return;
if (member.isGetter || member.isField || member.isFunction) {
var selectors =
@ -354,7 +356,7 @@ class ProgramBuilder {
var stubName = namer.invocationName(selector);
if (stubNames.add(stubName.key)) {
interceptorClass.callStubs.add(_buildStubMethod(stubName,
js.js('function(obj) { return obj.# }', [member.name]),
js.js('function(obj) { return obj.# }', [jsName]),
element: member));
}
}
@ -367,10 +369,8 @@ class ProgramBuilder {
if (selectors != null && !selectors.isEmpty) {
var stubName = namer.setterForElement(member);
if (stubNames.add(stubName.key)) {
interceptorClass.callStubs.add(_buildStubMethod(
stubName,
js.js('function(obj, v) { return obj.# = v }',
[member.name]),
interceptorClass.callStubs.add(_buildStubMethod(stubName,
js.js('function(obj, v) { return obj.# = v }', [jsName]),
element: member));
}
}
@ -447,7 +447,7 @@ class ProgramBuilder {
interceptorClass.callStubs.add(_buildStubMethod(
stubName,
js.js('function(receiver, #) { return receiver.#(#) }',
[parameters, member.name, parameters]),
[parameters, jsName, parameters]),
element: member));
}
}

View file

@ -19,6 +19,7 @@ const Iterable<String> _allowedDartSchemePaths = const <String>[
'html_common',
'indexed_db',
'js',
'js_util',
'svg',
'_native_typed_data',
'web_audio',

View file

@ -5619,14 +5619,15 @@ class SsaBuilder extends ast.Visitor
var filteredArguments = <HInstruction>[];
var parameterNameMap = new Map<String, js.Expression>();
params.orderedForEachParameter((ParameterElement parameter) {
// TODO(jacobr): throw if parameter names do not match names of property
// names in the class.
// TODO(jacobr): consider throwing if parameter names do not match
// names of properties in the class.
assert(parameter.isNamed);
HInstruction argument = arguments[i];
if (argument != null) {
filteredArguments.add(argument);
parameterNameMap[parameter.name] =
new js.InterpolatedExpression(positions++);
var jsName =
backend.nativeData.getUnescapedJSInteropName(parameter.name);
parameterNameMap[jsName] = new js.InterpolatedExpression(positions++);
}
i++;
});

View file

@ -1,3 +1,9 @@
## 0.6.1
* Add js_util library of utility methods to efficiently manipulate typed
JavaScript interop objects in cases where the member name is not known
statically. These methods would be extension methods on JSObject if
Dart supported extension methods.
## 0.6.0
* Version 0.6.0 is a complete rewrite of `package:js`.

8
pkg/js/lib/js_util.dart Normal file
View file

@ -0,0 +1,8 @@
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// Allows interoperability with Javascript APIs.
library js_util;
export 'dart:js_util';

View file

@ -1,5 +1,5 @@
name: js
version: 0.6.0
version: 0.6.1
authors:
- Dart Team <misc@dartlang.org>
description: Access JavaScript from Dart.

View file

@ -11,6 +11,7 @@
'html_cc_file': '<(gen_source_dir)/html_gen.cc',
'html_common_cc_file': '<(gen_source_dir)/html_common_gen.cc',
'js_cc_file': '<(gen_source_dir)/js_gen.cc',
'js_util_cc_file': '<(gen_source_dir)/js_util_gen.cc',
'blink_cc_file': '<(gen_source_dir)/blink_gen.cc',
'indexeddb_cc_file': '<(gen_source_dir)/indexeddb_gen.cc',
'cached_patches_cc_file': '<(gen_source_dir)/cached_patches_gen.cc',
@ -233,6 +234,38 @@
},
]
},
{
'target_name': 'generate_js_util_cc_file',
'type': 'none',
'toolsets':['host'],
'sources': [
'../../sdk/lib/js_util/dartium/js_util_dartium.dart',
],
'actions': [
{
'action_name': 'generate_js_util_cc',
'inputs': [
'../tools/gen_library_src_paths.py',
'<(builtin_in_cc_file)',
'<@(_sources)',
],
'outputs': [
'<(js_util_cc_file)',
],
'action': [
'python',
'tools/gen_library_src_paths.py',
'--output', '<(js_util_cc_file)',
'--input_cc', '<(builtin_in_cc_file)',
'--include', 'bin/builtin.h',
'--var_name', 'dart::bin::Builtin::js_util_source_paths_',
'--library_name', 'dart:js_util',
'<@(_sources)',
],
'message': 'Generating ''<(js_util_cc_file)'' file.'
},
]
},
{
'target_name': 'generate_blink_cc_file',
'type': 'none',
@ -500,6 +533,7 @@
'generate_html_cc_file#host',
'generate_html_common_cc_file#host',
'generate_js_cc_file#host',
'generate_js_util_cc_file#host',
'generate_blink_cc_file#host',
'generate_indexeddb_cc_file#host',
'generate_cached_patches_cc_file#host',
@ -1262,6 +1296,7 @@
'<(html_cc_file)',
'<(html_common_cc_file)',
'<(js_cc_file)',
'<(js_util_cc_file)',
'<(blink_cc_file)',
'<(indexeddb_cc_file)',
'<(cached_patches_cc_file)',

View file

@ -24,6 +24,7 @@ Builtin::builtin_lib_props Builtin::builtin_libraries_[] = {
{ "dart:html", html_source_paths_, NULL, NULL, true },
{ "dart:html_common", html_common_source_paths_, NULL, NULL, true},
{ "dart:js", js_source_paths_, NULL, NULL, true},
{ "dart:js_util", js_util_source_paths_, NULL, NULL, true},
{ "dart:_blink", blink_source_paths_, NULL, NULL, true },
{ "dart:indexed_db", indexeddb_source_paths_, NULL, NULL, true },
{ "cached_patches.dart", cached_patches_source_paths_, NULL, NULL, true },

View file

@ -74,6 +74,7 @@ class Builtin {
static const char* html_source_paths_[];
static const char* html_common_source_paths_[];
static const char* js_source_paths_[];
static const char* js_util_source_paths_[];
static const char* blink_source_paths_[];
static const char* indexeddb_source_paths_[];
static const char* cached_patches_source_paths_[];

View file

@ -116,6 +116,12 @@ const Map<String, LibraryInfo> libraries = const {
maturity: Maturity.STABLE,
dart2jsPath: "js/dart2js/js_dart2js.dart"),
"js_util": const LibraryInfo(
"js_util/dartium/js_util_dartium.dart",
categories: "Client",
maturity: Maturity.STABLE,
dart2jsPath: "js_util/dart2js/js_util_dart2js.dart"),
"math": const LibraryInfo(
"math/math.dart",
categories: "Client,Server,Embedded",

View file

@ -25,6 +25,7 @@ indexed_db: indexed_db/dart2js/indexed_db_dart2js.dart
io: io/io.dart
isolate: isolate/isolate.dart
js: js/dart2js/js_dart2js.dart
js_util: js_util/dart2js/js_util_dart2js.dart
math: math/math.dart
mirrors: mirrors/mirrors.dart
nativewrappers: html/dart2js/nativewrappers.dart

View file

@ -26,6 +26,7 @@ indexed_db: indexed_db/dart2js/indexed_db_dart2js.dart
io: unsupported:
isolate: isolate/isolate.dart
js: js/dart2js/js_dart2js.dart
js_util: js_util/dart2js/js_util_dart2js.dart
math: math/math.dart
mirrors: mirrors/mirrors.dart
nativewrappers: html/dart2js/nativewrappers.dart

View file

@ -24,6 +24,7 @@ indexed_db: indexed_db/dart2js/indexed_db_dart2js.dart
io: io/io.dart
isolate: isolate/isolate.dart
js: js/dart2js/js_dart2js.dart
js_util: js_util/dart2js/js_util_dart2js.dart
math: math/math.dart
mirrors: mirrors/mirrors.dart
nativewrappers: html/dart2js/nativewrappers.dart

View file

@ -707,7 +707,7 @@ _callDartFunctionFastCaptureThis(callback, self, List arguments) {
return Function.apply(callback, [self]..addAll(arguments));
}
Function /*=F*/ allowInterop/*<F extends Function>*/(Function /*=F*/ f) {
Function/*=F*/ allowInterop/*<F extends Function>*/(Function/*=F*/ f) {
if (JS('bool', 'typeof(#) == "function"', f)) {
// Already supports interop, just use the existing function.
return f;

View file

@ -138,6 +138,114 @@ final _jsInterfaceTypes = new Set<mirrors.ClassMirror>();
@Deprecated("Internal Use Only")
Iterable<mirrors.ClassMirror> get jsInterfaceTypes => _jsInterfaceTypes;
class _StringLiteralEscape {
// Character code constants.
static const int BACKSPACE = 0x08;
static const int TAB = 0x09;
static const int NEWLINE = 0x0a;
static const int CARRIAGE_RETURN = 0x0d;
static const int FORM_FEED = 0x0c;
static const int QUOTE = 0x22;
static const int CHAR_$ = 0x24;
static const int CHAR_0 = 0x30;
static const int BACKSLASH = 0x5c;
static const int CHAR_b = 0x62;
static const int CHAR_f = 0x66;
static const int CHAR_n = 0x6e;
static const int CHAR_r = 0x72;
static const int CHAR_t = 0x74;
static const int CHAR_u = 0x75;
final StringSink _sink;
_StringLiteralEscape(this._sink);
void writeString(String string) {
_sink.write(string);
}
void writeStringSlice(String string, int start, int end) {
_sink.write(string.substring(start, end));
}
void writeCharCode(int charCode) {
_sink.writeCharCode(charCode);
}
/// ('0' + x) or ('a' + x - 10)
static int hexDigit(int x) => x < 10 ? 48 + x : 87 + x;
/// Write, and suitably escape, a string's content as a JSON string literal.
void writeStringContent(String s) {
// Identical to JSON string literal escaping except that we also escape $.
int offset = 0;
final int length = s.length;
for (int i = 0; i < length; i++) {
int charCode = s.codeUnitAt(i);
if (charCode > BACKSLASH) continue;
if (charCode < 32) {
if (i > offset) writeStringSlice(s, offset, i);
offset = i + 1;
writeCharCode(BACKSLASH);
switch (charCode) {
case BACKSPACE:
writeCharCode(CHAR_b);
break;
case TAB:
writeCharCode(CHAR_t);
break;
case NEWLINE:
writeCharCode(CHAR_n);
break;
case FORM_FEED:
writeCharCode(CHAR_f);
break;
case CARRIAGE_RETURN:
writeCharCode(CHAR_r);
break;
default:
writeCharCode(CHAR_u);
writeCharCode(CHAR_0);
writeCharCode(CHAR_0);
writeCharCode(hexDigit((charCode >> 4) & 0xf));
writeCharCode(hexDigit(charCode & 0xf));
break;
}
} else if (charCode == QUOTE ||
charCode == BACKSLASH ||
charCode == CHAR_$) {
if (i > offset) writeStringSlice(s, offset, i);
offset = i + 1;
writeCharCode(BACKSLASH);
writeCharCode(charCode);
}
}
if (offset == 0) {
writeString(s);
} else if (offset < length) {
writeStringSlice(s, offset, length);
}
}
/**
* Serialize a [num], [String], [bool], [Null], [List] or [Map] value.
*
* Returns true if the value is one of these types, and false if not.
* If a value is both a [List] and a [Map], it's serialized as a [List].
*/
bool writeStringLiteral(String str) {
writeString('"');
writeStringContent(str);
writeString('"');
}
}
String _escapeString(String str) {
StringBuffer output = new StringBuffer();
new _StringLiteralEscape(output)..writeStringLiteral(str);
return output.toString();
}
/// A collection of methods where all methods have the same name.
/// This class is intended to optimize whether a specific invocation is
/// appropritate for at least some of the methods in the collection.
@ -188,8 +296,8 @@ class _DeclarationSet {
// Not enough positional arguments.
return false;
}
if (!_checkType(
invocation.positionalArguments[i], parameters[i].type)) return false;
if (!_checkType(invocation.positionalArguments[i], parameters[i].type))
return false;
}
if (invocation.namedArguments.isNotEmpty) {
var startNamed;
@ -208,8 +316,9 @@ class _DeclarationSet {
for (var j = startNamed; j < parameters.length; j++) {
var p = parameters[j];
if (p.simpleName == name) {
if (!_checkType(invocation.namedArguments[name],
parameters[j].type)) return false;
if (!_checkType(
invocation.namedArguments[name], parameters[j].type))
return false;
match = true;
break;
}
@ -345,7 +454,9 @@ bool hasDomName(mirrors.DeclarationMirror mirror) {
_getJsMemberName(mirrors.DeclarationMirror mirror) {
var name = _getJsName(mirror);
return name == null || name.isEmpty ? _stripReservedNamePrefix(_getDeclarationName(mirror)) : name;
return name == null || name.isEmpty
? _stripReservedNamePrefix(_getDeclarationName(mirror))
: name;
}
// TODO(jacobr): handle setters correctyl.
@ -369,7 +480,7 @@ String _accessJsPathHelper(Iterable<String> parts) {
..write('${_JS_LIBRARY_PREFIX}.JsNative.getProperty(' * parts.length)
..write("${_JS_LIBRARY_PREFIX}.context");
for (var p in parts) {
sb.write(", '$p')");
sb.write(", ${_escapeString(p)})");
}
return sb.toString();
}
@ -380,13 +491,13 @@ String _accessJsPathHelper(Iterable<String> parts) {
String _accessJsPathSetter(String path) {
var parts = path.split(".");
return "${_JS_LIBRARY_PREFIX}.JsNative.setProperty(${_accessJsPathHelper(parts.getRange(0, parts.length - 1))
}, '${parts.last}', v)";
}, ${_escapeString(parts.last)}, v)";
}
String _accessJsPathCallMethodHelper(String path) {
var parts = path.split(".");
return "${_JS_LIBRARY_PREFIX}.JsNative.callMethod(${_accessJsPathHelper(parts.getRange(0, parts.length - 1))
}, '${parts.last}',";
}, ${_escapeString(parts.last)},";
}
@Deprecated("Internal Use Only")
@ -407,8 +518,7 @@ void addMemberHelper(
}
sb.write(" ");
if (declaration.isGetter) {
sb.write(
"get $name => ${_accessJsPath(path)};");
sb.write("get $name => ${_accessJsPath(path)};");
} else if (declaration.isSetter) {
sb.write("set $name(v) {\n"
" ${_JS_LIBRARY_PREFIX}.safeForTypedInterop(v);\n"
@ -471,7 +581,8 @@ bool _isExternal(mirrors.MethodMirror mirror) {
return false;
}
List<String> _generateExternalMethods(List<String> libraryPaths, bool useCachedPatches) {
List<String> _generateExternalMethods(
List<String> libraryPaths, bool useCachedPatches) {
var staticCodegen = <String>[];
if (libraryPaths.length == 0) {
@ -489,7 +600,7 @@ List<String> _generateExternalMethods(List<String> libraryPaths, bool useCachedP
// the patches for this file.
_generateLibraryCodegen(uri, library, staticCodegen);
}
}); // End of library foreach
}); // End of library foreach
} else {
// Used to generate cached_patches.dart file for all IDL generated dart:
// files to the WebKit DOM.
@ -505,140 +616,140 @@ List<String> _generateExternalMethods(List<String> libraryPaths, bool useCachedP
}
_generateLibraryCodegen(uri, library, staticCodegen) {
// Is it a dart generated library?
var dartLibrary = uri.scheme == 'dart';
// Is it a dart generated library?
var dartLibrary = uri.scheme == 'dart';
var sb = new StringBuffer();
String jsLibraryName = _getJsName(library);
var sb = new StringBuffer();
String jsLibraryName = _getJsName(library);
// Sort by patch file by its declaration name.
var sortedDeclKeys = library.declarations.keys.toList();
sortedDeclKeys.sort((a, b) => mirrors.MirrorSystem.getName(a).compareTo(mirrors.MirrorSystem.getName(b)));
// Sort by patch file by its declaration name.
var sortedDeclKeys = library.declarations.keys.toList();
sortedDeclKeys.sort((a, b) => mirrors.MirrorSystem
.getName(a)
.compareTo(mirrors.MirrorSystem.getName(b)));
sortedDeclKeys.forEach((name) {
var declaration = library.declarations[name];
if (declaration is mirrors.MethodMirror) {
if ((_hasJsName(declaration) || jsLibraryName != null) &&
_isExternal(declaration)) {
addMemberHelper(declaration, jsLibraryName, sb);
}
} else if (declaration is mirrors.ClassMirror) {
mirrors.ClassMirror clazz = declaration;
var isDom = dartLibrary ? hasDomName(clazz) : false;
var isJsInterop = _hasJsName(clazz);
if (isDom || isJsInterop) {
// TODO(jacobr): verify class implements JavaScriptObject.
var className = mirrors.MirrorSystem.getName(clazz.simpleName);
bool isPrivate = className.startsWith('_');
var classNameImpl = '${className}Impl';
var sbPatch = new StringBuffer();
if (isJsInterop) {
String jsClassName = _getJsMemberName(clazz);
sortedDeclKeys.forEach((name) {
var declaration = library.declarations[name];
if (declaration is mirrors.MethodMirror) {
if ((_hasJsName(declaration) || jsLibraryName != null) &&
_isExternal(declaration)) {
addMemberHelper(declaration, jsLibraryName, sb);
}
} else if (declaration is mirrors.ClassMirror) {
mirrors.ClassMirror clazz = declaration;
var isDom = dartLibrary ? hasDomName(clazz) : false;
var isJsInterop = _hasJsName(clazz);
if (isDom || isJsInterop) {
// TODO(jacobr): verify class implements JavaScriptObject.
var className = mirrors.MirrorSystem.getName(clazz.simpleName);
bool isPrivate = className.startsWith('_');
var classNameImpl = '${className}Impl';
var sbPatch = new StringBuffer();
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 = ${_JS_LIBRARY_PREFIX}.JsNative.newObject();\n");
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"
" ${_JS_LIBRARY_PREFIX}.JsNative.setProperty(ret, '$jsName', $name);\n"
" }\n");
i++;
}
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 = ${_JS_LIBRARY_PREFIX}.JsNative.newObject();\n");
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"
" ${_JS_LIBRARY_PREFIX}.JsNative.setProperty(ret, ${_escapeString(jsName)}, $name);\n"
" }\n");
i++;
}
sbPatch.write(
" return ret;"
sbPatch.write(" return ret;"
"}\n");
} else if (declaration.isConstructor ||
declaration.isFactoryConstructor) {
sbPatch.write(" ");
addMemberHelper(
declaration,
(jsLibraryName != null && jsLibraryName.isNotEmpty)
? "${jsLibraryName}.${jsClassName}"
: jsClassName,
sbPatch,
isStatic: true,
memberName: className);
}
}); // End of clazz.declarations.forEach
} else if (declaration.isConstructor ||
declaration.isFactoryConstructor) {
sbPatch.write(" ");
addMemberHelper(
declaration,
(jsLibraryName != null && jsLibraryName.isNotEmpty)
? "${jsLibraryName}.${jsClassName}"
: jsClassName,
sbPatch,
isStatic: true,
memberName: className);
}
}); // End of clazz.declarations.forEach
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(" static Type get instanceRuntimeType => ${classNameImpl};\n");
}
if (isPrivate) {
sb.write("""
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(
" static Type get instanceRuntimeType => ${classNameImpl};\n");
}
if (isPrivate) {
sb.write("""
class ${escapePrivateClassPrefix}${className} implements $className {}
""");
}
}
if (sbPatch.isNotEmpty) {
var typeVariablesClause = '';
if (!clazz.typeVariables.isEmpty) {
typeVariablesClause =
'<${clazz.typeVariables.map((m) => mirrors.MirrorSystem.getName(m.simpleName)).join(',')}>';
}
sb.write("""
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 (isDom) {
sb.write("""
if (isDom) {
sb.write("""
class $classNameImpl$typeVariablesClause extends $className implements ${_JS_LIBRARY_PREFIX}.JSObjectInterfacesDom {
${classNameImpl}.internal_() : super.internal_();
get runtimeType => $className;
toString() => super.toString();
}
""");
}
}
}
}
});
if (sb.isNotEmpty) {
staticCodegen
..add(uri.toString())
..add("${uri}_js_interop_patch.dart")
..add("""
}
});
if (sb.isNotEmpty) {
staticCodegen
..add(uri.toString())
..add("${uri}_js_interop_patch.dart")
..add("""
import 'dart:js' as ${_JS_LIBRARY_PREFIX};
/**
@ -649,7 +760,7 @@ const ${_UNDEFINED_VAR} = const Object();
${sb}
""");
}
}
}
// Remember the @JS type to compare annotation type.
@ -679,11 +790,13 @@ void setupJsTypeCache() {
* signal to generate and emit the patches to stdout to be captured and put into
* the file sdk/lib/js/dartium/cached_patches.dart
*/
List<String> _generateInteropPatchFiles(List<String> libraryPaths, genCachedPatches) {
List<String> _generateInteropPatchFiles(
List<String> libraryPaths, genCachedPatches) {
// Cache the @JS Type.
if (_atJsType == -1) setupJsTypeCache();
var ret = _generateExternalMethods(libraryPaths, genCachedPatches ? false : true);
var ret =
_generateExternalMethods(libraryPaths, genCachedPatches ? false : true);
var libraryPrefixes = new Map<mirrors.LibraryMirror, String>();
var prefixNames = new Set<String>();
var sb = new StringBuffer();
@ -720,8 +833,7 @@ List<String> _generateInteropPatchFiles(List<String> libraryPaths, genCachedPatc
var className = mirrors.MirrorSystem.getName(typeMirror.simpleName);
var isPrivate = className.startsWith('_');
if (isPrivate) className = '${escapePrivateClassPrefix}${className}';
var fullName =
'${prefixName}.${className}';
var fullName = '${prefixName}.${className}';
(isArray ? implementsArray : implements).add(fullName);
if (!isArray && !isFunction && !isJSObject) {
// For DOM classes we need to be a bit more conservative at tagging them
@ -960,25 +1072,27 @@ JsObject get context {
_lookupType(o, bool isCrossFrame, bool isElement) {
try {
var type = html_common.lookupType(o, isElement);
var typeMirror = mirrors.reflectType(type);
var legacyInteropConvertToNative = typeMirror.isSubtypeOf(mirrors.reflectType(html.Blob)) ||
typeMirror.isSubtypeOf(mirrors.reflectType(html.Event)) ||
typeMirror.isSubtypeOf(mirrors.reflectType(indexed_db.KeyRange)) ||
typeMirror.isSubtypeOf(mirrors.reflectType(html.ImageData)) ||
typeMirror.isSubtypeOf(mirrors.reflectType(html.Node)) ||
var type = html_common.lookupType(o, isElement);
var typeMirror = mirrors.reflectType(type);
var legacyInteropConvertToNative =
typeMirror.isSubtypeOf(mirrors.reflectType(html.Blob)) ||
typeMirror.isSubtypeOf(mirrors.reflectType(html.Event)) ||
typeMirror.isSubtypeOf(mirrors.reflectType(indexed_db.KeyRange)) ||
typeMirror.isSubtypeOf(mirrors.reflectType(html.ImageData)) ||
typeMirror.isSubtypeOf(mirrors.reflectType(html.Node)) ||
// TypedData is removed from this list as it is converted directly
// rather than flowing through the interceptor code path.
// typeMirror.isSubtypeOf(mirrors.reflectType(typed_data.TypedData)) ||
typeMirror.isSubtypeOf(mirrors.reflectType(html.Window));
if (isCrossFrame && !typeMirror.isSubtypeOf(mirrors.reflectType(html.Window))) {
typeMirror.isSubtypeOf(mirrors.reflectType(html.Window));
if (isCrossFrame &&
!typeMirror.isSubtypeOf(mirrors.reflectType(html.Window))) {
// TODO(jacobr): evaluate using the true cross frame Window class, etc.
// as well as triggering that legacy JS Interop returns raw JsObject
// instances.
legacyInteropConvertToNative = false;
}
return [type, legacyInteropConvertToNative];
} catch (e) { }
} catch (e) {}
return [JSObject.instanceRuntimeType, false];
}
@ -1060,7 +1174,8 @@ class JsObject extends _JSObjectBase {
static JsObject _jsify(object) native "JsObject_jsify";
static JsObject _fromBrowserObject(object) native "JsObject_fromBrowserObject";
static JsObject _fromBrowserObject(object)
native "JsObject_fromBrowserObject";
/**
* Returns the value associated with [property] from the proxied JavaScript
@ -1103,8 +1218,7 @@ class JsObject extends _JSObjectBase {
return _identityEquality(this, other);
}
static bool _identityEquality(a, b)
native "JsObject_identityEquality";
static bool _identityEquality(a, b) native "JsObject_identityEquality";
/**
* Returns `true` if the JavaScript object contains the specified property
@ -1163,7 +1277,6 @@ class JsObject extends _JSObjectBase {
_callMethodLegacy(String name, List args) native "JsObject_callMethodLegacy";
}
/// Base class for all JS objects used through dart:html and typed JS interop.
@Deprecated("Internal Use Only")
class JSObject extends _JSObjectBase {
@ -1200,8 +1313,8 @@ class JSObject extends _JSObjectBase {
if (matches != null) return ret;
if (ret is Function ||
(ret is JsFunction /* shouldn't be needed in the future*/) &&
_allowedMethods.containsKey(
invocation.memberName)) return ret; // Warning: we have not bound "this"... we could type check on the Function but that is of little value in Dart.
_allowedMethods.containsKey(invocation.memberName))
return ret; // Warning: we have not bound "this"... we could type check on the Function but that is of little value in Dart.
throwError();
} else {
// TODO(jacobr): should we throw if the JavaScript object doesn't have the property?
@ -1210,8 +1323,8 @@ class JSObject extends _JSObjectBase {
} else if (invocation.isSetter) {
if (CHECK_JS_INVOCATIONS) {
var matches = _allowedSetters[invocation.memberName];
if (matches == null ||
!matches.checkInvocation(invocation)) throwError();
if (matches == null || !matches.checkInvocation(invocation))
throwError();
}
assert(name.endsWith("="));
name = name.substring(0, name.length - 1);
@ -1221,8 +1334,8 @@ class JSObject extends _JSObjectBase {
var matches;
if (CHECK_JS_INVOCATIONS) {
matches = _allowedMethods[invocation.memberName];
if (matches == null ||
!matches.checkInvocation(invocation)) throwError();
if (matches == null || !matches.checkInvocation(invocation))
throwError();
}
var ret = _callMethod(name, _buildArgs(invocation));
if (CHECK_JS_INVOCATIONS) {
@ -1302,7 +1415,8 @@ class JSFunction extends JSObject implements Function {
a8 = _UNDEFINED,
a9 = _UNDEFINED,
a10 = _UNDEFINED]) {
return _apply(_stripUndefinedArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10]));
return _apply(
_stripUndefinedArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10]));
}
noSuchMethod(Invocation invocation) {
@ -1314,7 +1428,8 @@ class JSFunction extends JSObject implements Function {
dynamic _apply(List args, {thisArg}) native "JSFunction_apply";
static JSFunction _createWithThis(Function f) native "JSFunction_createWithThis";
static JSFunction _createWithThis(Function f)
native "JSFunction_createWithThis";
static JSFunction _create(Function f) native "JSFunction_create";
}
@ -1329,11 +1444,16 @@ class JsNative {
static hasProperty(_JSObjectBase o, name) => o._hasProperty(name);
static getProperty(_JSObjectBase o, name) => o._operator_getter(name);
static setProperty(_JSObjectBase o, name, value) => o._operator_setter(name, value);
static callMethod(_JSObjectBase o, String method, List args) => o._callMethod(method, args);
static instanceof(_JSObjectBase o, /*JsFunction|JSFunction*/ type) => o._instanceof(type);
static callConstructor0(_JSObjectBase constructor) native "JSNative_callConstructor0";
static callConstructor(_JSObjectBase constructor, List args) native "JSNative_callConstructor";
static setProperty(_JSObjectBase o, name, value) =>
o._operator_setter(name, value);
static callMethod(_JSObjectBase o, String method, List args) =>
o._callMethod(method, args);
static instanceof(_JSObjectBase o, /*JsFunction|JSFunction*/ type) =>
o._instanceof(type);
static callConstructor0(_JSObjectBase constructor)
native "JSNative_callConstructor0";
static callConstructor(_JSObjectBase constructor, List args)
native "JSNative_callConstructor";
static toTypedObject(JsObject o) native "JSNative_toTypedObject";
@ -1344,42 +1464,6 @@ class JsNative {
static JSFunction withThis(Function f) native "JsFunction_withThisNoWrap";
}
/// Utility methods to efficiently manipulate typed JSInterop objects in cases
/// where the name to call is not known at runtime. You should only use these
/// methods when the same effect cannot be achieved with @JS annotations.
/// These methods would be extension methods on JSObject if Dart supported
/// extension methods.
class JSNative {
/**
* WARNING: performance of this method is much worse than other methods
* in JSNative. Only use this method as a last resort.
*
* Recursively converts a JSON-like collection of Dart objects to a
* collection of JavaScript objects and returns a [JsObject] proxy to it.
*
* [object] must be a [Map] or [Iterable], the contents of which are also
* converted. Maps and Iterables are copied to a new JavaScript object.
* Primitives and other transferrable values are directly converted to their
* JavaScript type, and all other objects are proxied.
*/
static jsify(object) {
if ((object is! Map) && (object is! Iterable)) {
throw new ArgumentError("object must be a Map or Iterable");
}
return _jsify(object);
}
static _jsify(object) native "JSObject_jsify";
static JSObject newObject() native "JSObject_newObject";
static hasProperty(JSObject o, name) => o._hasProperty(name);
static getProperty(JSObject o, name) => o._operator_getter(name);
static setProperty(JSObject o, name, value) => o._operator_setter(name, value);
static callMethod(JSObject o, String method, List args) => o._callMethod(method, args);
static instanceof(JSObject o, Function type) => o._instanceof(type);
static callConstructor(JSObject constructor, List args) native "JSNative_callConstructor";
}
/**
* Proxies a JavaScript Function object.
*/
@ -1396,8 +1480,7 @@ class JsFunction extends JsObject {
* Invokes the JavaScript function with arguments [args]. If [thisArg] is
* supplied it is the value of `this` for the invocation.
*/
dynamic apply(List args, {thisArg}) =>
_apply(args, thisArg: thisArg);
dynamic apply(List args, {thisArg}) => _apply(args, thisArg: thisArg);
dynamic _apply(List args, {thisArg}) native "JsFunction_apply";
@ -1578,7 +1661,7 @@ 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 /*=F*/ allowInterop/*<F extends Function>*/(Function /*=F*/ f) {
Function/*=F*/ allowInterop/*<F extends Function>*/(Function/*=F*/ f) {
if (f is JSFunction) {
// The function is already a JSFunction... no need to do anything.
return f;

View file

@ -0,0 +1,126 @@
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// Utility methods to efficiently manipulate typed JSInterop objects in cases
/// where the name to call is not known at runtime. You should only use these
/// methods when the same effect cannot be achieved with @JS annotations.
/// These methods would be extension methods on JSObject if Dart supported
/// extension methods.
library dart.js_util;
import 'dart:_foreign_helper' show JS;
import 'dart:collection' show HashMap;
/// WARNING: performance of this method is much worse than other uitil
/// methods in this library. Only use this method as a last resort.
///
/// Recursively converts a JSON-like collection of Dart objects to a
/// collection of JavaScript objects and returns a [JsObject] proxy to it.
///
/// [object] must be a [Map] or [Iterable], the contents of which are also
/// converted. Maps and Iterables are copied to a new JavaScript object.
/// Primitives and other transferrable values are directly converted to their
/// JavaScript type, and all other objects are proxied.
jsify(object) {
if ((object is! Map) && (object is! Iterable)) {
throw new ArgumentError("object must be a Map or Iterable");
}
return _convertDataTree(object);
}
_convertDataTree(data) {
var _convertedObjects = new HashMap.identity();
_convert(o) {
if (_convertedObjects.containsKey(o)) {
return _convertedObjects[o];
}
if (o is Map) {
final convertedMap = JS('=Object', '{}');
_convertedObjects[o] = convertedMap;
for (var key in o.keys) {
JS('=Object', '#[#]=#', convertedMap, key, _convert(o[key]));
}
return convertedMap;
} else if (o is Iterable) {
var convertedList = [];
_convertedObjects[o] = convertedList;
convertedList.addAll(o.map(_convert));
return convertedList;
} else {
return o;
}
}
return _convert(data);
}
JSObject newObject() => JS('=Object', '{}');
hasProperty(o, name) => JS('bool', '# in #', name, o);
getProperty(o, name) => JS('Object', '#[#]', o, name);
setProperty(o, name, value) => JS('', '#[#]=#', o, name, value);
callMethod(o, String method, List args) =>
JS('Object', '#[#].apply(#, #)', o, method, o, args);
instanceof(o, Function type) => JS('bool', '# instanceof #', o, type);
callConstructor(Function constr, List arguments) {
if (arguments == null) {
return JS('Object', 'new #()', constr);
}
if (JS('bool', '# instanceof Array', arguments)) {
int argumentCount = JS('int', '#.length', arguments);
switch (argumentCount) {
case 0:
return JS('Object', 'new #()', constr);
case 1:
var arg0 = JS('', '#[0]', arguments);
return JS('Object', 'new #(#)', constr, arg0);
case 2:
var arg0 = JS('', '#[0]', arguments);
var arg1 = JS('', '#[1]', arguments);
return JS('Object', 'new #(#, #)', constr, arg0, arg1);
case 3:
var arg0 = JS('', '#[0]', arguments);
var arg1 = JS('', '#[1]', arguments);
var arg2 = JS('', '#[2]', arguments);
return JS('Object', 'new #(#, #, #)', constr, arg0, arg1, arg2);
case 4:
var arg0 = JS('', '#[0]', arguments);
var arg1 = JS('', '#[1]', arguments);
var arg2 = JS('', '#[2]', arguments);
var arg3 = JS('', '#[3]', arguments);
return JS(
'Object', 'new #(#, #, #, #)', constr, arg0, arg1, arg2, arg3);
}
}
// The following code solves the problem of invoking a JavaScript
// constructor with an unknown number arguments.
// First bind the constructor to the argument list using bind.apply().
// The first argument to bind() is the binding of 't', so add 'null' to
// the arguments list passed to apply().
// After that, use the JavaScript 'new' operator which overrides any binding
// of 'this' with the new instance.
var args = [null]..addAll(arguments);
var factoryFunction = JS('', '#.bind.apply(#, #)', constr, constr, args);
// Without this line, calling factoryFunction as a constructor throws
JS('String', 'String(#)', factoryFunction);
// This could return an UnknownJavaScriptObject, or a native
// object for which there is an interceptor
return JS('Object', 'new #()', factoryFunction);
// TODO(sra): Investigate:
//
// var jsObj = JS('', 'Object.create(#.prototype)', constr);
// JS('', '#.apply(#, #)', constr, jsObj,
// []..addAll(arguments.map(_convertToJS)));
// return _wrapToDart(jsObj);
}

View file

@ -0,0 +1,38 @@
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// Utility methods to efficiently manipulate typed JSInterop objects in cases
/// where the name to call is not known at runtime. You should only use these
/// methods when the same effect cannot be achieved with @JS annotations.
/// These methods would be extension methods on JSObject if Dart supported
/// extension methods.
library dart.js_util;
import 'dart:js';
/// WARNING: performance of this method is much worse than other uitil
/// methods in this library. Only use this method as a last resort.
///
/// Recursively converts a JSON-like collection of Dart objects to a
/// collection of JavaScript objects and returns a [JsObject] proxy to it.
///
/// [object] must be a [Map] or [Iterable], the contents of which are also
/// converted. Maps and Iterables are copied to a new JavaScript object.
/// Primitives and other transferrable values are directly converted to their
/// JavaScript type, and all other objects are proxied.
jsify(object) {
if ((object is! Map) && (object is! Iterable)) {
throw new ArgumentError("object must be a Map or Iterable");
}
return JsNative.jsify(object);
}
JSObject newObject() => JsNative.newObject();
hasProperty(JSObject o, name) => JsNative.hasProperty(o, name);
getProperty(JSObject o, name) => JsNative.getProperty(o, name);
setProperty(JSObject o, name, value) => JsNative.setProperty(o, name, value);
callMethod(JSObject o, String method, List args) => JsNative.callMethod(o, method, args);
instanceof(JSObject o, Function type) => JsNative.instanceof(o, type);
callConstructor(JSObject constructor, List args) => JsNative.callConstructor(constructor, args);

View file

@ -1,17 +0,0 @@
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:_js_helper';
@Native('*A')
class A {
}
class B extends A {
call() => 42;
}
main() {
new B()();
}

View file

@ -5,8 +5,6 @@
[ $browser ]
*: Skip
[ $compiler == dart2js ]
call_on_native_class_test: RuntimeError # Issue 14813
native_no_such_method_exception4_frog_test: CompileTimeError # Issue 9631
native_no_such_method_exception5_frog_test: CompileTimeError # Issue 9631

View file

@ -71,6 +71,15 @@ _injectJs() {
getA: function() { return this.a;}
};
function _PrivateClass(a, b) {
this._a = a;
this._b = b;
};
_PrivateClass.prototype = {
_getA: function() { return this._a;}
};
var selection = ["a", "b", "c", foo, bar];
function returnNumArgs() { return arguments.length; };
@ -78,6 +87,23 @@ _injectJs() {
function confuse(obj) { return obj; }
window['class'] = function() { return 42; };
window['delete'] = 100;
window['JS$hasJsInName'] = 'unicorn';
window['JS$hasJsInNameMethod'] = function(x) { return x*5; };
function JS$ClassWithJSInName(x) {
this.x = x;
this.JS$hasJsInName = 73;
this.$JS$doesNotNeedEscape = 103;
};
JS$ClassWithJSInName.prototype = {
JS$getXwithJsInName: function() { return this.x;}
};
JS$ClassWithJSInName.JS$staticMethod = function(x) { return x * 3; };
function StringWrapper(str) {
this.str = str;
}
@ -110,6 +136,49 @@ class ClassWithConstructor {
external get b;
}
@JS('ClassWithConstructor')
class _ClassWithConstructor {
external _ClassWithConstructor(aParam, bParam);
external getA();
external get a;
external get b;
}
@JS()
class JS$_PrivateClass {
external JS$_PrivateClass(aParam, bParam);
external JS$_getA();
external get JS$_a;
external get JS$_b;
// Equivalent to JS$_a but only visible within
// the class.
external get _a;
}
@JS()
external String get JS$JS$hasJsInName;
@JS()
external int JS$JS$hasJsInNameMethod(int x);
// This is the prefered way to handle static or top level members that start
// with JS$. We verify that JS$JS$ works purely to prevent bugs.
@JS(r'JS$hasJsInName')
external String get JS$hasJsInName;
@JS(r'JS$hasJsInNameMethod')
external int JS$hasJsInNameMethod(int x);
@JS()
class JS$JS$ClassWithJSInName {
external JS$JS$ClassWithJSInName(x);
external int get x;
external int get JS$JS$hasJsInName;
external int get $JS$doesNotNeedEscape;
external int JS$JS$getXwithJsInName();
external static int JS$JS$staticMethod(x);
}
typedef num MultiplyWithDefault(num a, [num b]);
@JS()
@ -118,6 +187,7 @@ class Foo {
external set x(int v);
external num multiplyByX(num y);
external num multiplyBy2(num y);
external num JS$multiplyBy2(num y);
external MultiplyWithDefault get multiplyDefault2Function;
external callClosureWithArgAndThis(Function closure, arg);
@ -126,14 +196,17 @@ class Foo {
external Bar getBar();
external static num multiplyDefault2(num a, [num b]);
// Should desugar to multiplyDefault2.
external static num JS$multiplyDefault2(num a, [num b]);
}
@anonymous
@JS()
class ExampleLiteral {
external factory ExampleLiteral({int x, String y, num z});
external factory ExampleLiteral({int x, String y, num z, JS$class});
external int get x;
external int get JS$class;
external String get y;
external num get z;
}
@ -182,6 +255,13 @@ class StringWrapper {
@JS()
external confuse(obj);
/// Desugars to calling the js method named class.
@JS()
external JS$class();
@JS()
external get JS$delete;
@JS()
external CanvasRenderingContext2D getCanvasContext();
@ -191,6 +271,16 @@ external num get propertyOnDocument;
@JS('window.self.window.window.windowProperty')
external num get propertyOnWindow;
@JS()
@anonymous
class Simple
{
external List<int> get numbers;
external set numbers(List<int> numbers);
external factory Simple({ List<int> numbers });
}
main() {
_injectJs();
@ -210,6 +300,17 @@ main() {
expect(stringify(l), equals('{"z":100}'));
});
test('with array', () {
// Repro for https://github.com/dart-lang/sdk/issues/26768
var simple = new Simple(numbers: [ 1, 2, 3 ]);
expect(stringify(simple), equals('{"numbers":[1,2,3]}'));
});
test(r'JS$ escaped name', () {
var l = new ExampleLiteral(JS$class: 3, y: "foo");
expect(l.JS$class, equals(3));
});
test('empty', () {
var l = new EmptyLiteral();
expect(stringify(l), equals('{}'));
@ -225,6 +326,25 @@ main() {
});
});
group('private class', () {
test('simple', () {
var o = new _ClassWithConstructor("foo", "bar");
expect(o.a, equals("foo"));
expect(o.b, equals("bar"));
expect(o.getA(), equals("foo"));
});
});
group('private class', () {
test('simple', () {
var o = new JS$_PrivateClass("foo", "bar");
expect(o.JS$_a, equals("foo"));
expect(o.JS$_b, equals("bar"));
expect(o._a, equals("foo"));
expect(o.JS$_getA(), equals("foo"));
});
});
group('property', () {
test('get', () {
expect(foo.x, equals(3));
@ -276,6 +396,22 @@ main() {
expect(untypedFunction(), isNaN);
});
test(r'JS$ escaped name', () {
foo.x = 10;
expect(foo.JS$multiplyBy2(5), equals(10));
Function multiplyBy2 = foo.JS$multiplyBy2;
expect(multiplyBy2(5), equals(10));
});
test(r'JS$ double escaped name', () {
var obj = new JS$JS$ClassWithJSInName(42);
expect(obj.x, equals(42));
expect(obj.JS$JS$getXwithJsInName(), equals(42));
expect(obj.JS$JS$hasJsInName, equals(73));
expect(obj.$JS$doesNotNeedEscape, equals(103));
});
});
group('static_method_call', () {
@ -283,6 +419,15 @@ main() {
expect(Foo.multiplyDefault2(6, 7), equals(42));
expect(Foo.multiplyDefault2(6), equals(12));
});
test(r'JS$ escaped name', () {
expect(Foo.JS$multiplyDefault2(6, 7), equals(42));
expect(Foo.JS$multiplyDefault2(6), equals(12));
});
test(r'JS$ double escaped name', () {
expect(JS$JS$ClassWithJSInName.JS$JS$staticMethod(4), equals(12));
});
});
// Note: these extra groups are added to be able to mark each test
@ -384,6 +529,17 @@ main() {
});
});
group(r'JS$ escaped', () {
test('top level', () {
expect(JS$class(), equals(42));
expect(JS$delete, equals(100));
});
test('top level double escaped', () {
expect(JS$JS$hasJsInName, equals('unicorn'));
expect(JS$JS$hasJsInNameMethod(4), equals(20));
});
});
group('type check', () {
test('js interfaces', () {
// Is checks return true for all JavaScript interfaces.

View file

@ -0,0 +1,347 @@
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
@JS()
library js_native_test;
import 'dart:async';
import 'dart:html';
import 'dart:typed_data' show ByteBuffer, Int32List;
import 'dart:indexed_db' show IdbFactory, KeyRange;
import 'package:js/js.dart';
import 'package:js/js_util.dart' as js_util;
import 'package:unittest/unittest.dart';
import 'package:unittest/html_individual_config.dart';
_injectJs() {
final script = new ScriptElement();
script.type = 'text/javascript';
script.innerHtml = r"""
var x = 42;
var _x = 123;
var myArray = ["value1"];
function returnThis() {
return this;
}
function getTypeOf(o) {
return typeof(o);
}
function Foo(a) {
this.a = a;
}
Foo.b = 38;
Foo.prototype.bar = function() {
return this.a;
}
Foo.prototype.toString = function() {
return "I'm a Foo a=" + this.a;
}
var container = new Object();
container.Foo = Foo;
function checkMap(m, key, value) {
if (m.hasOwnProperty(key))
return m[key] == value;
else
return false;
}
""";
document.body.append(script);
}
@JS()
external bool checkMap(m, String, value);
@JS('JSON.stringify')
external String stringify(o);
@JS('Node')
external Function get JSNodeType;
@JS('Element')
external Function get JSElementType;
@JS('Text')
external Function get JSTextType;
@JS('HTMLCanvasElement')
external Function get JSHtmlCanvasElementType;
@JS()
class Foo {
external Foo(num a);
external num get a;
external num bar();
}
@JS('Foo')
external Function get JSFooType;
@JS()
@anonymous
class ExampleTypedLiteral {
external factory ExampleTypedLiteral({a, b, JS$_c, JS$class});
external get a;
external get b;
external get JS$_c;
external set JS$_c(v);
// Identical to JS$_c but only accessible within the library.
external get _c;
external get JS$class;
external set JS$class(v);
}
@JS("Object.prototype.hasOwnProperty")
external Function get _hasOwnProperty;
bool hasOwnProperty(o, String name) {
return js_util.callMethod(_hasOwnProperty, 'call', [o, name]);
}
main() {
_injectJs();
useHtmlIndividualConfiguration();
group('js_util.jsify()', () {
test('convert a List', () {
final list = [1, 2, 3, 4, 5, 6, 7, 8];
var array = js_util.jsify(list);
expect(array is List, isTrue);
expect(identical(array, list), isFalse);
expect(array.length, equals(list.length));
for (var i = 0; i < list.length; i++) {
expect(array[i], equals(list[i]));
}
});
test('convert an Iterable', () {
final set = new Set.from([1, 2, 3, 4, 5, 6, 7, 8]);
var array = js_util.jsify(set);
expect(array is List, isTrue);
expect(array.length, equals(set.length));
for (var i = 0; i < array.length; i++) {
expect(set.contains(array[i]), isTrue);
}
});
test('convert a Map', () {
var map = {'a': 1, 'b': 2, 'c': 3};
var jsMap = js_util.jsify(map);
expect(jsMap is! List, isTrue);
for (var key in map.keys) {
expect(checkMap(jsMap, key, map[key]), isTrue);
}
});
test('deep convert a complex object', () {
final object = {
'a': [
1,
[2, 3]
],
'b': {'c': 3, 'd': new Foo(42)},
'e': null
};
var jsObject = js_util.jsify(object);
expect(js_util.getProperty(jsObject, 'a')[0], equals(object['a'][0]));
expect(
js_util.getProperty(jsObject, 'a')[1][0], equals(object['a'][1][0]));
expect(
js_util.getProperty(jsObject, 'a')[1][1], equals(object['a'][1][1]));
expect(js_util.getProperty(js_util.getProperty(jsObject, 'b'), 'c'),
equals(object['b']['c']));
expect(js_util.getProperty(js_util.getProperty(jsObject, 'b'), 'd'),
equals(object['b']['d']));
expect(
js_util.callMethod(
js_util.getProperty(js_util.getProperty(jsObject, 'b'), 'd'),
'bar', []),
equals(42));
expect(js_util.getProperty(jsObject, 'e'), isNull);
});
test('throws if object is not a Map or Iterable', () {
expect(() => js_util.jsify('a'),
throwsA(new isInstanceOf<ArgumentError>()));
});
});
group('js_util.newObject', () {
test('create', () {
expect(identical(js_util.newObject(), js_util.newObject()), isFalse);
});
test('callMethod', () {
var o = js_util.newObject();
expect(js_util.callMethod(o, 'toString', []), equals('[object Object]'));
expect(stringify(o), equals('{}'));
});
test('properties', () {
var o = js_util.newObject();
expect(js_util.hasProperty(o, 'foo bar'), isFalse);
expect(js_util.hasProperty(o, 'toString'), isTrue);
expect(hasOwnProperty(o, 'toString'), isFalse);
expect(hasOwnProperty(o, 'foo bar'), isFalse);
js_util.setProperty(o, 'foo bar', 42);
expect(hasOwnProperty(o, 'foo bar'), isTrue);
expect(js_util.getProperty(o, 'foo bar'), equals(42));
expect(js_util.hasProperty(o, 'foo bar'), isTrue);
expect(stringify(o), equals('{"foo bar":42}'));
});
});
group('hasProperty', () {
test('typed object', () {
var f = new Foo(42);
expect(js_util.hasProperty(f, 'a'), isTrue);
expect(js_util.hasProperty(f, 'toString'), isTrue);
js_util.setProperty(f, '__proto__', null);
expect(js_util.hasProperty(f, 'toString'), isFalse);
});
test('typed literal', () {
var l =
new ExampleTypedLiteral(a: 'x', b: 42, JS$_c: null, JS$class: true);
expect(js_util.hasProperty(l, 'a'), isTrue);
expect(js_util.hasProperty(l, 'b'), isTrue);
expect(js_util.hasProperty(l, '_c'), isTrue);
expect(l.JS$_c, isNull);
expect(js_util.hasProperty(l, 'class'), isTrue);
// JS$_c escapes to _c so the property JS$_c will not exist on the object.
expect(js_util.hasProperty(l, r'JS$_c'), isFalse);
expect(js_util.hasProperty(l, r'JS$class'), isFalse);
expect(l.JS$class, isTrue);
l = new ExampleTypedLiteral(a: null);
expect(js_util.hasProperty(l, 'a'), isTrue);
expect(js_util.hasProperty(l, 'b'), isFalse);
expect(js_util.hasProperty(l, '_c'), isFalse);
expect(js_util.hasProperty(l, 'class'), isFalse);
l = new ExampleTypedLiteral(JS$_c: 74);
expect(js_util.hasProperty(l, '_c'), isTrue);
expect(l.JS$_c, equals(74));
});
});
group('getProperty', () {
test('typed object', () {
var f = new Foo(42);
expect(js_util.getProperty(f, 'a'), equals(42));
expect(js_util.getProperty(f, 'toString') is Function, isTrue);
js_util.setProperty(f, '__proto__', null);
expect(js_util.getProperty(f, 'toString'), isNull);
});
test('typed literal', () {
var l = new ExampleTypedLiteral(a: 'x', b: 42, JS$_c: 7, JS$class: true);
expect(js_util.getProperty(l, 'a'), equals('x'));
expect(js_util.getProperty(l, 'b'), equals(42));
expect(js_util.getProperty(l, '_c'), equals(7));
expect(l.JS$_c, equals(7));
expect(js_util.getProperty(l, 'class'), isTrue);
expect(js_util.getProperty(l, r'JS$_c'), isNull);
expect(js_util.getProperty(l, r'JS$class'), isNull);
});
});
group('setProperty', () {
test('typed object', () {
var f = new Foo(42);
expect(js_util.getProperty(f, 'a'), equals(42));
js_util.setProperty(f, 'a', 100);
expect(f.a, equals(100));
expect(js_util.getProperty(f, 'a'), equals(100));
});
test('typed literal', () {
var l = new ExampleTypedLiteral();
js_util.setProperty(l, 'a', 'foo');
expect(js_util.getProperty(l, 'a'), equals('foo'));
expect(l.a, equals('foo'));
js_util.setProperty(l, 'a', l);
expect(identical(l.a, l), isTrue);
var list = ['arr'];
js_util.setProperty(l, 'a', list);
expect(identical(l.a, list), isTrue);
l.JS$class = 42;
expect(l.JS$class, equals(42));
js_util.setProperty(l, 'class', 100);
expect(l.JS$class, equals(100));
});
});
group('callMethod', () {
test('html object', () {
var canvas = new Element.tag('canvas');
expect(
identical(canvas.getContext('2d'),
js_util.callMethod(canvas, 'getContext', ['2d'])),
isTrue);
});
test('typed object', () {
var f = new Foo(42);
expect(js_util.callMethod(f, 'bar', []), equals(42));
});
});
group('instanceof', () {
test('html object', () {
var canvas = new Element.tag('canvas');
expect(js_util.instanceof(canvas, JSNodeType), isTrue);
expect(js_util.instanceof(canvas, JSTextType), isFalse);
expect(js_util.instanceof(canvas, JSElementType), isTrue);
expect(js_util.instanceof(canvas, JSHtmlCanvasElementType), isTrue);
var div = new Element.tag('div');
expect(js_util.instanceof(div, JSNodeType), isTrue);
expect(js_util.instanceof(div, JSTextType), isFalse);
expect(js_util.instanceof(div, JSElementType), isTrue);
expect(js_util.instanceof(div, JSHtmlCanvasElementType), isFalse);
var text = new Text('foo');
expect(js_util.instanceof(text, JSNodeType), isTrue);
expect(js_util.instanceof(text, JSTextType), isTrue);
expect(js_util.instanceof(text, JSElementType), isFalse);
});
test('typed object', () {
var f = new Foo(42);
expect(js_util.instanceof(f, JSFooType), isTrue);
expect(js_util.instanceof(f, JSNodeType), isFalse);
});
test('typed literal', () {
var l = new ExampleTypedLiteral();
expect(js_util.instanceof(l, JSFooType), isFalse);
});
});
group('callConstructor', () {
test('html object', () {
var textNode = js_util.callConstructor(JSTextType, ['foo']);
expect(js_util.instanceof(textNode, JSTextType), isTrue);
expect(textNode is Text, isTrue);
expect(textNode.text, equals('foo'));
});
test('typed object', () {
Foo f = js_util.callConstructor(JSFooType, [42]);
expect(f.a, equals(42));
});
});
}

View file

@ -59,6 +59,7 @@
# ......io/
# ......isolate/
# ......js/
# ......js_util/
# ......math/
# ......mirrors/
# ......typed_data/
@ -254,7 +255,7 @@ def Main():
join('html', 'dart2js'), join('html', 'dartium'),
join('html', 'html_common'),
join('indexed_db', 'dart2js'), join('indexed_db', 'dartium'),
'js', 'math', 'mirrors', 'profiler', 'typed_data',
'js', 'js_util', 'math', 'mirrors', 'profiler', 'typed_data',
join('svg', 'dart2js'), join('svg', 'dartium'),
join('web_audio', 'dart2js'), join('web_audio', 'dartium'),
join('web_gl', 'dart2js'), join('web_gl', 'dartium'),