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 = Map<MemberElement, NativeBehavior> nativeFieldStoreBehavior =
<FieldElement, NativeBehavior>{}; <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. /// Returns `true` if [element] is explicitly marked as part of JsInterop.
bool _isJsInterop(Element element) { bool _isJsInterop(Element element) {
return jsInteropNames.containsKey(element.declaration); return jsInteropNames.containsKey(element.declaration);
@ -93,7 +97,7 @@ class NativeData {
if (jsInteropName != null && jsInteropName.isNotEmpty) { if (jsInteropName != null && jsInteropName.isNotEmpty) {
return jsInteropName; 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 /// Computes the name for [element] to use in the generated JavaScript. This
@ -216,4 +220,12 @@ class NativeData {
FieldElement field, NativeBehavior behavior) { FieldElement field, NativeBehavior behavior) {
nativeFieldStoreBehavior[field] = 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) { for (Element e in elements) {
if (e is ClassElement && backend.isJsInterop(e)) { if (e is ClassElement && backend.isJsInterop(e)) {
e.declaration.forEachMember((_, Element member) { e.declaration.forEachMember((_, Element member) {
var jsName =
backend.nativeData.getUnescapedJSInteropName(member.name);
if (!member.isInstanceMember) return; if (!member.isInstanceMember) return;
if (member.isGetter || member.isField || member.isFunction) { if (member.isGetter || member.isField || member.isFunction) {
var selectors = var selectors =
@ -354,7 +356,7 @@ class ProgramBuilder {
var stubName = namer.invocationName(selector); var stubName = namer.invocationName(selector);
if (stubNames.add(stubName.key)) { if (stubNames.add(stubName.key)) {
interceptorClass.callStubs.add(_buildStubMethod(stubName, interceptorClass.callStubs.add(_buildStubMethod(stubName,
js.js('function(obj) { return obj.# }', [member.name]), js.js('function(obj) { return obj.# }', [jsName]),
element: member)); element: member));
} }
} }
@ -367,10 +369,8 @@ class ProgramBuilder {
if (selectors != null && !selectors.isEmpty) { if (selectors != null && !selectors.isEmpty) {
var stubName = namer.setterForElement(member); var stubName = namer.setterForElement(member);
if (stubNames.add(stubName.key)) { if (stubNames.add(stubName.key)) {
interceptorClass.callStubs.add(_buildStubMethod( interceptorClass.callStubs.add(_buildStubMethod(stubName,
stubName, js.js('function(obj, v) { return obj.# = v }', [jsName]),
js.js('function(obj, v) { return obj.# = v }',
[member.name]),
element: member)); element: member));
} }
} }
@ -447,7 +447,7 @@ class ProgramBuilder {
interceptorClass.callStubs.add(_buildStubMethod( interceptorClass.callStubs.add(_buildStubMethod(
stubName, stubName,
js.js('function(receiver, #) { return receiver.#(#) }', js.js('function(receiver, #) { return receiver.#(#) }',
[parameters, member.name, parameters]), [parameters, jsName, parameters]),
element: member)); element: member));
} }
} }

View file

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

View file

@ -5619,14 +5619,15 @@ class SsaBuilder extends ast.Visitor
var filteredArguments = <HInstruction>[]; var filteredArguments = <HInstruction>[];
var parameterNameMap = new Map<String, js.Expression>(); var parameterNameMap = new Map<String, js.Expression>();
params.orderedForEachParameter((ParameterElement parameter) { params.orderedForEachParameter((ParameterElement parameter) {
// TODO(jacobr): throw if parameter names do not match names of property // TODO(jacobr): consider throwing if parameter names do not match
// names in the class. // names of properties in the class.
assert(parameter.isNamed); assert(parameter.isNamed);
HInstruction argument = arguments[i]; HInstruction argument = arguments[i];
if (argument != null) { if (argument != null) {
filteredArguments.add(argument); filteredArguments.add(argument);
parameterNameMap[parameter.name] = var jsName =
new js.InterpolatedExpression(positions++); backend.nativeData.getUnescapedJSInteropName(parameter.name);
parameterNameMap[jsName] = new js.InterpolatedExpression(positions++);
} }
i++; 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 ## 0.6.0
* Version 0.6.0 is a complete rewrite of `package:js`. * 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 name: js
version: 0.6.0 version: 0.6.1
authors: authors:
- Dart Team <misc@dartlang.org> - Dart Team <misc@dartlang.org>
description: Access JavaScript from Dart. description: Access JavaScript from Dart.

View file

@ -11,6 +11,7 @@
'html_cc_file': '<(gen_source_dir)/html_gen.cc', 'html_cc_file': '<(gen_source_dir)/html_gen.cc',
'html_common_cc_file': '<(gen_source_dir)/html_common_gen.cc', 'html_common_cc_file': '<(gen_source_dir)/html_common_gen.cc',
'js_cc_file': '<(gen_source_dir)/js_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', 'blink_cc_file': '<(gen_source_dir)/blink_gen.cc',
'indexeddb_cc_file': '<(gen_source_dir)/indexeddb_gen.cc', 'indexeddb_cc_file': '<(gen_source_dir)/indexeddb_gen.cc',
'cached_patches_cc_file': '<(gen_source_dir)/cached_patches_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', 'target_name': 'generate_blink_cc_file',
'type': 'none', 'type': 'none',
@ -500,6 +533,7 @@
'generate_html_cc_file#host', 'generate_html_cc_file#host',
'generate_html_common_cc_file#host', 'generate_html_common_cc_file#host',
'generate_js_cc_file#host', 'generate_js_cc_file#host',
'generate_js_util_cc_file#host',
'generate_blink_cc_file#host', 'generate_blink_cc_file#host',
'generate_indexeddb_cc_file#host', 'generate_indexeddb_cc_file#host',
'generate_cached_patches_cc_file#host', 'generate_cached_patches_cc_file#host',
@ -1262,6 +1296,7 @@
'<(html_cc_file)', '<(html_cc_file)',
'<(html_common_cc_file)', '<(html_common_cc_file)',
'<(js_cc_file)', '<(js_cc_file)',
'<(js_util_cc_file)',
'<(blink_cc_file)', '<(blink_cc_file)',
'<(indexeddb_cc_file)', '<(indexeddb_cc_file)',
'<(cached_patches_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", html_source_paths_, NULL, NULL, true },
{ "dart:html_common", html_common_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", 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:_blink", blink_source_paths_, NULL, NULL, true },
{ "dart:indexed_db", indexeddb_source_paths_, NULL, NULL, true }, { "dart:indexed_db", indexeddb_source_paths_, NULL, NULL, true },
{ "cached_patches.dart", cached_patches_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_source_paths_[];
static const char* html_common_source_paths_[]; static const char* html_common_source_paths_[];
static const char* js_source_paths_[]; static const char* js_source_paths_[];
static const char* js_util_source_paths_[];
static const char* blink_source_paths_[]; static const char* blink_source_paths_[];
static const char* indexeddb_source_paths_[]; static const char* indexeddb_source_paths_[];
static const char* cached_patches_source_paths_[]; static const char* cached_patches_source_paths_[];

View file

@ -116,6 +116,12 @@ const Map<String, LibraryInfo> libraries = const {
maturity: Maturity.STABLE, maturity: Maturity.STABLE,
dart2jsPath: "js/dart2js/js_dart2js.dart"), 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": const LibraryInfo(
"math/math.dart", "math/math.dart",
categories: "Client,Server,Embedded", categories: "Client,Server,Embedded",

View file

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

View file

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

View file

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

View file

@ -138,6 +138,114 @@ final _jsInterfaceTypes = new Set<mirrors.ClassMirror>();
@Deprecated("Internal Use Only") @Deprecated("Internal Use Only")
Iterable<mirrors.ClassMirror> get jsInterfaceTypes => _jsInterfaceTypes; 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. /// A collection of methods where all methods have the same name.
/// This class is intended to optimize whether a specific invocation is /// This class is intended to optimize whether a specific invocation is
/// appropritate for at least some of the methods in the collection. /// appropritate for at least some of the methods in the collection.
@ -188,8 +296,8 @@ class _DeclarationSet {
// Not enough positional arguments. // Not enough positional arguments.
return false; return false;
} }
if (!_checkType( if (!_checkType(invocation.positionalArguments[i], parameters[i].type))
invocation.positionalArguments[i], parameters[i].type)) return false; return false;
} }
if (invocation.namedArguments.isNotEmpty) { if (invocation.namedArguments.isNotEmpty) {
var startNamed; var startNamed;
@ -208,8 +316,9 @@ class _DeclarationSet {
for (var j = startNamed; j < parameters.length; j++) { for (var j = startNamed; j < parameters.length; j++) {
var p = parameters[j]; var p = parameters[j];
if (p.simpleName == name) { if (p.simpleName == name) {
if (!_checkType(invocation.namedArguments[name], if (!_checkType(
parameters[j].type)) return false; invocation.namedArguments[name], parameters[j].type))
return false;
match = true; match = true;
break; break;
} }
@ -345,7 +454,9 @@ bool hasDomName(mirrors.DeclarationMirror mirror) {
_getJsMemberName(mirrors.DeclarationMirror mirror) { _getJsMemberName(mirrors.DeclarationMirror mirror) {
var name = _getJsName(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. // 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}.JsNative.getProperty(' * parts.length)
..write("${_JS_LIBRARY_PREFIX}.context"); ..write("${_JS_LIBRARY_PREFIX}.context");
for (var p in parts) { for (var p in parts) {
sb.write(", '$p')"); sb.write(", ${_escapeString(p)})");
} }
return sb.toString(); return sb.toString();
} }
@ -380,13 +491,13 @@ String _accessJsPathHelper(Iterable<String> parts) {
String _accessJsPathSetter(String path) { String _accessJsPathSetter(String path) {
var parts = path.split("."); var parts = path.split(".");
return "${_JS_LIBRARY_PREFIX}.JsNative.setProperty(${_accessJsPathHelper(parts.getRange(0, parts.length - 1)) return "${_JS_LIBRARY_PREFIX}.JsNative.setProperty(${_accessJsPathHelper(parts.getRange(0, parts.length - 1))
}, '${parts.last}', v)"; }, ${_escapeString(parts.last)}, v)";
} }
String _accessJsPathCallMethodHelper(String path) { String _accessJsPathCallMethodHelper(String path) {
var parts = path.split("."); var parts = path.split(".");
return "${_JS_LIBRARY_PREFIX}.JsNative.callMethod(${_accessJsPathHelper(parts.getRange(0, parts.length - 1)) return "${_JS_LIBRARY_PREFIX}.JsNative.callMethod(${_accessJsPathHelper(parts.getRange(0, parts.length - 1))
}, '${parts.last}',"; }, ${_escapeString(parts.last)},";
} }
@Deprecated("Internal Use Only") @Deprecated("Internal Use Only")
@ -407,8 +518,7 @@ void addMemberHelper(
} }
sb.write(" "); sb.write(" ");
if (declaration.isGetter) { if (declaration.isGetter) {
sb.write( sb.write("get $name => ${_accessJsPath(path)};");
"get $name => ${_accessJsPath(path)};");
} else if (declaration.isSetter) { } else if (declaration.isSetter) {
sb.write("set $name(v) {\n" sb.write("set $name(v) {\n"
" ${_JS_LIBRARY_PREFIX}.safeForTypedInterop(v);\n" " ${_JS_LIBRARY_PREFIX}.safeForTypedInterop(v);\n"
@ -471,7 +581,8 @@ bool _isExternal(mirrors.MethodMirror mirror) {
return false; return false;
} }
List<String> _generateExternalMethods(List<String> libraryPaths, bool useCachedPatches) { List<String> _generateExternalMethods(
List<String> libraryPaths, bool useCachedPatches) {
var staticCodegen = <String>[]; var staticCodegen = <String>[];
if (libraryPaths.length == 0) { if (libraryPaths.length == 0) {
@ -489,7 +600,7 @@ List<String> _generateExternalMethods(List<String> libraryPaths, bool useCachedP
// the patches for this file. // the patches for this file.
_generateLibraryCodegen(uri, library, staticCodegen); _generateLibraryCodegen(uri, library, staticCodegen);
} }
}); // End of library foreach }); // End of library foreach
} else { } else {
// Used to generate cached_patches.dart file for all IDL generated dart: // Used to generate cached_patches.dart file for all IDL generated dart:
// files to the WebKit DOM. // files to the WebKit DOM.
@ -505,140 +616,140 @@ List<String> _generateExternalMethods(List<String> libraryPaths, bool useCachedP
} }
_generateLibraryCodegen(uri, library, staticCodegen) { _generateLibraryCodegen(uri, library, staticCodegen) {
// Is it a dart generated library? // Is it a dart generated library?
var dartLibrary = uri.scheme == 'dart'; var dartLibrary = uri.scheme == 'dart';
var sb = new StringBuffer(); var sb = new StringBuffer();
String jsLibraryName = _getJsName(library); String jsLibraryName = _getJsName(library);
// Sort by patch file by its declaration name. // Sort by patch file by its declaration name.
var sortedDeclKeys = library.declarations.keys.toList(); var sortedDeclKeys = library.declarations.keys.toList();
sortedDeclKeys.sort((a, b) => mirrors.MirrorSystem.getName(a).compareTo(mirrors.MirrorSystem.getName(b))); sortedDeclKeys.sort((a, b) => mirrors.MirrorSystem
.getName(a)
.compareTo(mirrors.MirrorSystem.getName(b)));
sortedDeclKeys.forEach((name) { sortedDeclKeys.forEach((name) {
var declaration = library.declarations[name]; var declaration = library.declarations[name];
if (declaration is mirrors.MethodMirror) { if (declaration is mirrors.MethodMirror) {
if ((_hasJsName(declaration) || jsLibraryName != null) && if ((_hasJsName(declaration) || jsLibraryName != null) &&
_isExternal(declaration)) { _isExternal(declaration)) {
addMemberHelper(declaration, jsLibraryName, sb); addMemberHelper(declaration, jsLibraryName, sb);
} }
} else if (declaration is mirrors.ClassMirror) { } else if (declaration is mirrors.ClassMirror) {
mirrors.ClassMirror clazz = declaration; mirrors.ClassMirror clazz = declaration;
var isDom = dartLibrary ? hasDomName(clazz) : false; var isDom = dartLibrary ? hasDomName(clazz) : false;
var isJsInterop = _hasJsName(clazz); var isJsInterop = _hasJsName(clazz);
if (isDom || isJsInterop) { if (isDom || isJsInterop) {
// TODO(jacobr): verify class implements JavaScriptObject. // TODO(jacobr): verify class implements JavaScriptObject.
var className = mirrors.MirrorSystem.getName(clazz.simpleName); var className = mirrors.MirrorSystem.getName(clazz.simpleName);
bool isPrivate = className.startsWith('_'); bool isPrivate = className.startsWith('_');
var classNameImpl = '${className}Impl'; var classNameImpl = '${className}Impl';
var sbPatch = new StringBuffer(); var sbPatch = new StringBuffer();
if (isJsInterop) { if (isJsInterop) {
String jsClassName = _getJsMemberName(clazz); String jsClassName = _getJsMemberName(clazz);
jsInterfaceTypes.add(clazz); jsInterfaceTypes.add(clazz);
clazz.declarations.forEach((name, declaration) { clazz.declarations.forEach((name, declaration) {
if (declaration is! mirrors.MethodMirror || if (declaration is! mirrors.MethodMirror ||
!_isExternal(declaration)) return; !_isExternal(declaration)) return;
if (declaration.isFactoryConstructor && if (declaration.isFactoryConstructor && _isAnonymousClass(clazz)) {
_isAnonymousClass(clazz)) { sbPatch.write(" factory ${className}(");
sbPatch.write(" factory ${className}("); int i = 0;
int i = 0; var args = <String>[];
var args = <String>[]; for (var p in declaration.parameters) {
for (var p in declaration.parameters) { args.add(mirrors.MirrorSystem.getName(p.simpleName));
args.add(mirrors.MirrorSystem.getName(p.simpleName)); i++;
i++; }
} if (args.isNotEmpty) {
if (args.isNotEmpty) { sbPatch
sbPatch ..write('{')
..write('{') ..write(
..write(args args.map((name) => '$name:${_UNDEFINED_VAR}').join(", "))
.map((name) => '$name:${_UNDEFINED_VAR}') ..write('}');
.join(", ")) }
..write('}'); sbPatch.write(") {\n"
} " var ret = ${_JS_LIBRARY_PREFIX}.JsNative.newObject();\n");
sbPatch.write(") {\n" i = 0;
" var ret = ${_JS_LIBRARY_PREFIX}.JsNative.newObject();\n"); for (var p in declaration.parameters) {
i = 0; assert(p.isNamed); // TODO(jacobr): throw.
for (var p in declaration.parameters) { var name = args[i];
assert(p.isNamed); // TODO(jacobr): throw. var jsName = _stripReservedNamePrefix(
var name = args[i]; mirrors.MirrorSystem.getName(p.simpleName));
var jsName = _stripReservedNamePrefix( sbPatch.write(" if($name != ${_UNDEFINED_VAR}) {\n"
mirrors.MirrorSystem.getName(p.simpleName)); " ${_JS_LIBRARY_PREFIX}.safeForTypedInterop($name);\n"
sbPatch.write(" if($name != ${_UNDEFINED_VAR}) {\n" " ${_JS_LIBRARY_PREFIX}.JsNative.setProperty(ret, ${_escapeString(jsName)}, $name);\n"
" ${_JS_LIBRARY_PREFIX}.safeForTypedInterop($name);\n" " }\n");
" ${_JS_LIBRARY_PREFIX}.JsNative.setProperty(ret, '$jsName', $name);\n" i++;
" }\n"); }
i++;
}
sbPatch.write( sbPatch.write(" return ret;"
" return ret;"
"}\n"); "}\n");
} else if (declaration.isConstructor || } else if (declaration.isConstructor ||
declaration.isFactoryConstructor) { declaration.isFactoryConstructor) {
sbPatch.write(" "); sbPatch.write(" ");
addMemberHelper( addMemberHelper(
declaration, declaration,
(jsLibraryName != null && jsLibraryName.isNotEmpty) (jsLibraryName != null && jsLibraryName.isNotEmpty)
? "${jsLibraryName}.${jsClassName}" ? "${jsLibraryName}.${jsClassName}"
: jsClassName, : jsClassName,
sbPatch, sbPatch,
isStatic: true, isStatic: true,
memberName: className); memberName: className);
} }
}); // End of clazz.declarations.forEach }); // End of clazz.declarations.forEach
clazz.staticMembers.forEach((memberName, member) { clazz.staticMembers.forEach((memberName, member) {
if (_isExternal(member)) { if (_isExternal(member)) {
sbPatch.write(" "); sbPatch.write(" ");
addMemberHelper( addMemberHelper(
member, member,
(jsLibraryName != null && jsLibraryName.isNotEmpty) (jsLibraryName != null && jsLibraryName.isNotEmpty)
? "${jsLibraryName}.${jsClassName}" ? "${jsLibraryName}.${jsClassName}"
: jsClassName, : jsClassName,
sbPatch, sbPatch,
isStatic: true); isStatic: true);
} }
}); });
} }
if (isDom) { if (isDom) {
sbPatch.write(" static Type get instanceRuntimeType => ${classNameImpl};\n"); sbPatch.write(
} " static Type get instanceRuntimeType => ${classNameImpl};\n");
if (isPrivate) { }
sb.write(""" if (isPrivate) {
sb.write("""
class ${escapePrivateClassPrefix}${className} implements $className {} class ${escapePrivateClassPrefix}${className} implements $className {}
"""); """);
} }
if (sbPatch.isNotEmpty) { if (sbPatch.isNotEmpty) {
var typeVariablesClause = ''; var typeVariablesClause = '';
if (!clazz.typeVariables.isEmpty) { if (!clazz.typeVariables.isEmpty) {
typeVariablesClause = typeVariablesClause =
'<${clazz.typeVariables.map((m) => mirrors.MirrorSystem.getName(m.simpleName)).join(',')}>'; '<${clazz.typeVariables.map((m) => mirrors.MirrorSystem.getName(m.simpleName)).join(',')}>';
} }
sb.write(""" sb.write("""
patch class $className$typeVariablesClause { patch class $className$typeVariablesClause {
$sbPatch $sbPatch
} }
"""); """);
if (isDom) { if (isDom) {
sb.write(""" sb.write("""
class $classNameImpl$typeVariablesClause extends $className implements ${_JS_LIBRARY_PREFIX}.JSObjectInterfacesDom { class $classNameImpl$typeVariablesClause extends $className implements ${_JS_LIBRARY_PREFIX}.JSObjectInterfacesDom {
${classNameImpl}.internal_() : super.internal_(); ${classNameImpl}.internal_() : super.internal_();
get runtimeType => $className; get runtimeType => $className;
toString() => super.toString(); toString() => super.toString();
} }
"""); """);
}
} }
} }
} }
}); }
if (sb.isNotEmpty) { });
staticCodegen if (sb.isNotEmpty) {
..add(uri.toString()) staticCodegen
..add("${uri}_js_interop_patch.dart") ..add(uri.toString())
..add(""" ..add("${uri}_js_interop_patch.dart")
..add("""
import 'dart:js' as ${_JS_LIBRARY_PREFIX}; import 'dart:js' as ${_JS_LIBRARY_PREFIX};
/** /**
@ -649,7 +760,7 @@ const ${_UNDEFINED_VAR} = const Object();
${sb} ${sb}
"""); """);
} }
} }
// Remember the @JS type to compare annotation type. // 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 * signal to generate and emit the patches to stdout to be captured and put into
* the file sdk/lib/js/dartium/cached_patches.dart * 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. // Cache the @JS Type.
if (_atJsType == -1) setupJsTypeCache(); 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 libraryPrefixes = new Map<mirrors.LibraryMirror, String>();
var prefixNames = new Set<String>(); var prefixNames = new Set<String>();
var sb = new StringBuffer(); var sb = new StringBuffer();
@ -720,8 +833,7 @@ List<String> _generateInteropPatchFiles(List<String> libraryPaths, genCachedPatc
var className = mirrors.MirrorSystem.getName(typeMirror.simpleName); var className = mirrors.MirrorSystem.getName(typeMirror.simpleName);
var isPrivate = className.startsWith('_'); var isPrivate = className.startsWith('_');
if (isPrivate) className = '${escapePrivateClassPrefix}${className}'; if (isPrivate) className = '${escapePrivateClassPrefix}${className}';
var fullName = var fullName = '${prefixName}.${className}';
'${prefixName}.${className}';
(isArray ? implementsArray : implements).add(fullName); (isArray ? implementsArray : implements).add(fullName);
if (!isArray && !isFunction && !isJSObject) { if (!isArray && !isFunction && !isJSObject) {
// For DOM classes we need to be a bit more conservative at tagging them // 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) { _lookupType(o, bool isCrossFrame, bool isElement) {
try { try {
var type = html_common.lookupType(o, isElement); var type = html_common.lookupType(o, isElement);
var typeMirror = mirrors.reflectType(type); var typeMirror = mirrors.reflectType(type);
var legacyInteropConvertToNative = typeMirror.isSubtypeOf(mirrors.reflectType(html.Blob)) || var legacyInteropConvertToNative =
typeMirror.isSubtypeOf(mirrors.reflectType(html.Event)) || typeMirror.isSubtypeOf(mirrors.reflectType(html.Blob)) ||
typeMirror.isSubtypeOf(mirrors.reflectType(indexed_db.KeyRange)) || typeMirror.isSubtypeOf(mirrors.reflectType(html.Event)) ||
typeMirror.isSubtypeOf(mirrors.reflectType(html.ImageData)) || typeMirror.isSubtypeOf(mirrors.reflectType(indexed_db.KeyRange)) ||
typeMirror.isSubtypeOf(mirrors.reflectType(html.Node)) || typeMirror.isSubtypeOf(mirrors.reflectType(html.ImageData)) ||
typeMirror.isSubtypeOf(mirrors.reflectType(html.Node)) ||
// TypedData is removed from this list as it is converted directly // TypedData is removed from this list as it is converted directly
// rather than flowing through the interceptor code path. // rather than flowing through the interceptor code path.
// typeMirror.isSubtypeOf(mirrors.reflectType(typed_data.TypedData)) || // typeMirror.isSubtypeOf(mirrors.reflectType(typed_data.TypedData)) ||
typeMirror.isSubtypeOf(mirrors.reflectType(html.Window)); typeMirror.isSubtypeOf(mirrors.reflectType(html.Window));
if (isCrossFrame && !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. // TODO(jacobr): evaluate using the true cross frame Window class, etc.
// as well as triggering that legacy JS Interop returns raw JsObject // as well as triggering that legacy JS Interop returns raw JsObject
// instances. // instances.
legacyInteropConvertToNative = false; legacyInteropConvertToNative = false;
} }
return [type, legacyInteropConvertToNative]; return [type, legacyInteropConvertToNative];
} catch (e) { } } catch (e) {}
return [JSObject.instanceRuntimeType, false]; return [JSObject.instanceRuntimeType, false];
} }
@ -1060,7 +1174,8 @@ class JsObject extends _JSObjectBase {
static JsObject _jsify(object) native "JsObject_jsify"; 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 * Returns the value associated with [property] from the proxied JavaScript
@ -1103,8 +1218,7 @@ class JsObject extends _JSObjectBase {
return _identityEquality(this, other); return _identityEquality(this, other);
} }
static bool _identityEquality(a, b) static bool _identityEquality(a, b) native "JsObject_identityEquality";
native "JsObject_identityEquality";
/** /**
* Returns `true` if the JavaScript object contains the specified property * 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"; _callMethodLegacy(String name, List args) native "JsObject_callMethodLegacy";
} }
/// Base class for all JS objects used through dart:html and typed JS interop. /// Base class for all JS objects used through dart:html and typed JS interop.
@Deprecated("Internal Use Only") @Deprecated("Internal Use Only")
class JSObject extends _JSObjectBase { class JSObject extends _JSObjectBase {
@ -1200,8 +1313,8 @@ class JSObject extends _JSObjectBase {
if (matches != null) return ret; if (matches != null) return ret;
if (ret is Function || if (ret is Function ||
(ret is JsFunction /* shouldn't be needed in the future*/) && (ret is JsFunction /* shouldn't be needed in the future*/) &&
_allowedMethods.containsKey( _allowedMethods.containsKey(invocation.memberName))
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. return ret; // Warning: we have not bound "this"... we could type check on the Function but that is of little value in Dart.
throwError(); throwError();
} else { } else {
// TODO(jacobr): should we throw if the JavaScript object doesn't have the property? // 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) { } else if (invocation.isSetter) {
if (CHECK_JS_INVOCATIONS) { if (CHECK_JS_INVOCATIONS) {
var matches = _allowedSetters[invocation.memberName]; var matches = _allowedSetters[invocation.memberName];
if (matches == null || if (matches == null || !matches.checkInvocation(invocation))
!matches.checkInvocation(invocation)) throwError(); throwError();
} }
assert(name.endsWith("=")); assert(name.endsWith("="));
name = name.substring(0, name.length - 1); name = name.substring(0, name.length - 1);
@ -1221,8 +1334,8 @@ class JSObject extends _JSObjectBase {
var matches; var matches;
if (CHECK_JS_INVOCATIONS) { if (CHECK_JS_INVOCATIONS) {
matches = _allowedMethods[invocation.memberName]; matches = _allowedMethods[invocation.memberName];
if (matches == null || if (matches == null || !matches.checkInvocation(invocation))
!matches.checkInvocation(invocation)) throwError(); throwError();
} }
var ret = _callMethod(name, _buildArgs(invocation)); var ret = _callMethod(name, _buildArgs(invocation));
if (CHECK_JS_INVOCATIONS) { if (CHECK_JS_INVOCATIONS) {
@ -1302,7 +1415,8 @@ class JSFunction extends JSObject implements Function {
a8 = _UNDEFINED, a8 = _UNDEFINED,
a9 = _UNDEFINED, a9 = _UNDEFINED,
a10 = _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) { noSuchMethod(Invocation invocation) {
@ -1314,7 +1428,8 @@ class JSFunction extends JSObject implements Function {
dynamic _apply(List args, {thisArg}) native "JSFunction_apply"; 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"; static JSFunction _create(Function f) native "JSFunction_create";
} }
@ -1329,11 +1444,16 @@ class JsNative {
static hasProperty(_JSObjectBase o, name) => o._hasProperty(name); static hasProperty(_JSObjectBase o, name) => o._hasProperty(name);
static getProperty(_JSObjectBase o, name) => o._operator_getter(name); static getProperty(_JSObjectBase o, name) => o._operator_getter(name);
static setProperty(_JSObjectBase o, name, value) => o._operator_setter(name, value); static setProperty(_JSObjectBase o, name, value) =>
static callMethod(_JSObjectBase o, String method, List args) => o._callMethod(method, args); o._operator_setter(name, value);
static instanceof(_JSObjectBase o, /*JsFunction|JSFunction*/ type) => o._instanceof(type); static callMethod(_JSObjectBase o, String method, List args) =>
static callConstructor0(_JSObjectBase constructor) native "JSNative_callConstructor0"; o._callMethod(method, args);
static callConstructor(_JSObjectBase constructor, List args) native "JSNative_callConstructor"; 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"; static toTypedObject(JsObject o) native "JSNative_toTypedObject";
@ -1344,42 +1464,6 @@ class JsNative {
static JSFunction withThis(Function f) native "JsFunction_withThisNoWrap"; 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. * Proxies a JavaScript Function object.
*/ */
@ -1396,8 +1480,7 @@ class JsFunction extends JsObject {
* Invokes the JavaScript function with arguments [args]. If [thisArg] is * Invokes the JavaScript function with arguments [args]. If [thisArg] is
* supplied it is the value of `this` for the invocation. * supplied it is the value of `this` for the invocation.
*/ */
dynamic apply(List args, {thisArg}) => dynamic apply(List args, {thisArg}) => _apply(args, thisArg: thisArg);
_apply(args, thisArg: thisArg);
dynamic _apply(List args, {thisArg}) native "JsFunction_apply"; 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 /// JavaScript. We may remove the need to call this method completely in the
/// future if Dart2Js is refactored so that its function calling conventions /// future if Dart2Js is refactored so that its function calling conventions
/// are more compatible with JavaScript. /// 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) { if (f is JSFunction) {
// The function is already a JSFunction... no need to do anything. // The function is already a JSFunction... no need to do anything.
return f; 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 ] [ $browser ]
*: Skip *: 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_exception4_frog_test: CompileTimeError # Issue 9631
native_no_such_method_exception5_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;} 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]; var selection = ["a", "b", "c", foo, bar];
function returnNumArgs() { return arguments.length; }; function returnNumArgs() { return arguments.length; };
@ -78,6 +87,23 @@ _injectJs() {
function confuse(obj) { return obj; } 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) { function StringWrapper(str) {
this.str = str; this.str = str;
} }
@ -110,6 +136,49 @@ class ClassWithConstructor {
external get b; 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]); typedef num MultiplyWithDefault(num a, [num b]);
@JS() @JS()
@ -118,6 +187,7 @@ class Foo {
external set x(int v); external set x(int v);
external num multiplyByX(num y); external num multiplyByX(num y);
external num multiplyBy2(num y); external num multiplyBy2(num y);
external num JS$multiplyBy2(num y);
external MultiplyWithDefault get multiplyDefault2Function; external MultiplyWithDefault get multiplyDefault2Function;
external callClosureWithArgAndThis(Function closure, arg); external callClosureWithArgAndThis(Function closure, arg);
@ -126,14 +196,17 @@ class Foo {
external Bar getBar(); external Bar getBar();
external static num multiplyDefault2(num a, [num b]); external static num multiplyDefault2(num a, [num b]);
// Should desugar to multiplyDefault2.
external static num JS$multiplyDefault2(num a, [num b]);
} }
@anonymous @anonymous
@JS() @JS()
class ExampleLiteral { 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 x;
external int get JS$class;
external String get y; external String get y;
external num get z; external num get z;
} }
@ -182,6 +255,13 @@ class StringWrapper {
@JS() @JS()
external confuse(obj); external confuse(obj);
/// Desugars to calling the js method named class.
@JS()
external JS$class();
@JS()
external get JS$delete;
@JS() @JS()
external CanvasRenderingContext2D getCanvasContext(); external CanvasRenderingContext2D getCanvasContext();
@ -191,6 +271,16 @@ external num get propertyOnDocument;
@JS('window.self.window.window.windowProperty') @JS('window.self.window.window.windowProperty')
external num get propertyOnWindow; 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() { main() {
_injectJs(); _injectJs();
@ -210,6 +300,17 @@ main() {
expect(stringify(l), equals('{"z":100}')); 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', () { test('empty', () {
var l = new EmptyLiteral(); var l = new EmptyLiteral();
expect(stringify(l), equals('{}')); 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', () { group('property', () {
test('get', () { test('get', () {
expect(foo.x, equals(3)); expect(foo.x, equals(3));
@ -276,6 +396,22 @@ main() {
expect(untypedFunction(), isNaN); 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', () { group('static_method_call', () {
@ -283,6 +419,15 @@ main() {
expect(Foo.multiplyDefault2(6, 7), equals(42)); expect(Foo.multiplyDefault2(6, 7), equals(42));
expect(Foo.multiplyDefault2(6), equals(12)); 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 // 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', () { group('type check', () {
test('js interfaces', () { test('js interfaces', () {
// Is checks return true for all JavaScript 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/ # ......io/
# ......isolate/ # ......isolate/
# ......js/ # ......js/
# ......js_util/
# ......math/ # ......math/
# ......mirrors/ # ......mirrors/
# ......typed_data/ # ......typed_data/
@ -254,7 +255,7 @@ def Main():
join('html', 'dart2js'), join('html', 'dartium'), join('html', 'dart2js'), join('html', 'dartium'),
join('html', 'html_common'), join('html', 'html_common'),
join('indexed_db', 'dart2js'), join('indexed_db', 'dartium'), 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('svg', 'dart2js'), join('svg', 'dartium'),
join('web_audio', 'dart2js'), join('web_audio', 'dartium'), join('web_audio', 'dart2js'), join('web_audio', 'dartium'),
join('web_gl', 'dart2js'), join('web_gl', 'dartium'), join('web_gl', 'dart2js'), join('web_gl', 'dartium'),