Index: sdk/lib/js/dartium/js_dartium.dart |
diff --git a/sdk/lib/js/dartium/js_dartium.dart b/sdk/lib/js/dartium/js_dartium.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..6c3ed0d68059b401aa6e57a6cb685f100dbf5cb5 |
--- /dev/null |
+++ b/sdk/lib/js/dartium/js_dartium.dart |
@@ -0,0 +1,1706 @@ |
+// 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. |
+ |
+/** |
+ * Support for interoperating with JavaScript. |
+ * |
+ * This library provides access to JavaScript objects from Dart, allowing |
+ * Dart code to get and set properties, and call methods of JavaScript objects |
+ * and invoke JavaScript functions. The library takes care of converting |
+ * between Dart and JavaScript objects where possible, or providing proxies if |
+ * conversion isn't possible. |
+ * |
+ * This library does not yet make Dart objects usable from JavaScript, their |
+ * methods and proeprties are not accessible, though it does allow Dart |
+ * functions to be passed into and called from JavaScript. |
+ * |
+ * [JsObject] is the core type and represents a proxy of a JavaScript object. |
+ * JsObject gives access to the underlying JavaScript objects properties and |
+ * methods. `JsObject`s can be acquired by calls to JavaScript, or they can be |
+ * created from proxies to JavaScript constructors. |
+ * |
+ * The top-level getter [context] provides a [JsObject] that represents the |
+ * global object in JavaScript, usually `window`. |
+ * |
+ * The following example shows an alert dialog via a JavaScript call to the |
+ * global function `alert()`: |
+ * |
+ * import 'dart:js'; |
+ * |
+ * main() => context.callMethod('alert', ['Hello from Dart!']); |
+ * |
+ * This example shows how to create a [JsObject] from a JavaScript constructor |
+ * and access its properties: |
+ * |
+ * import 'dart:js'; |
+ * |
+ * main() { |
+ * var object = new JsObject(context['Object']); |
+ * object['greeting'] = 'Hello'; |
+ * object['greet'] = (name) => "${object['greeting']} $name"; |
+ * var message = object.callMethod('greet', ['JavaScript']); |
+ * context['console'].callMethod('log', [message]); |
+ * } |
+ * |
+ * ## Proxying and automatic conversion |
+ * |
+ * When setting properties on a JsObject or passing arguments to a Javascript |
+ * method or function, Dart objects are automatically converted or proxied to |
+ * JavaScript objects. When accessing JavaScript properties, or when a Dart |
+ * closure is invoked from JavaScript, the JavaScript objects are also |
+ * converted to Dart. |
+ * |
+ * Functions and closures are proxied in such a way that they are callable. A |
+ * Dart closure assigned to a JavaScript property is proxied by a function in |
+ * JavaScript. A JavaScript function accessed from Dart is proxied by a |
+ * [JsFunction], which has a [apply] method to invoke it. |
+ * |
+ * The following types are transferred directly and not proxied: |
+ * |
+ * * "Basic" types: `null`, `bool`, `num`, `String`, `DateTime` |
+ * * `Blob` |
+ * * `Event` |
+ * * `HtmlCollection` |
+ * * `ImageData` |
+ * * `KeyRange` |
+ * * `Node` |
+ * * `NodeList` |
+ * * `TypedData`, including its subclasses like `Int32List`, but _not_ |
+ * `ByteBuffer` |
+ * * `Window` |
+ * |
+ * ## Converting collections with JsObject.jsify() |
+ * |
+ * To create a JavaScript collection from a Dart collection use the |
+ * [JsObject.jsify] constructor, which converts Dart [Map]s and [Iterable]s |
+ * into JavaScript Objects and Arrays. |
+ * |
+ * The following expression creates a new JavaScript object with the properties |
+ * `a` and `b` defined: |
+ * |
+ * var jsMap = new JsObject.jsify({'a': 1, 'b': 2}); |
+ * |
+ * This expression creates a JavaScript array: |
+ * |
+ * var jsArray = new JsObject.jsify([1, 2, 3]); |
+ */ |
+library dart.js; |
+ |
+import 'dart:collection' show ListMixin; |
+import 'dart:nativewrappers'; |
+import 'dart:math' as math; |
+import 'dart:mirrors' as mirrors; |
+import 'dart:html' as html; |
+import 'dart:_blink' as _blink; |
+import 'dart:html_common' as html_common; |
+import 'dart:indexed_db' as indexed_db; |
+import 'dart:typed_data'; |
+import 'dart:core'; |
+ |
+import 'cached_patches.dart'; |
+ |
+// Pretend we are always in checked mode as we aren't interested in users |
+// running Dartium code outside of checked mode. |
+@Deprecated("Internal Use Only") |
+final bool CHECK_JS_INVOCATIONS = true; |
+ |
+final String _DART_RESERVED_NAME_PREFIX = r'JS$'; |
+// If a private class is defined to use @JS we need to inject a non-private |
+// class with a name that will not cause collisions in the library so we can |
+// make JSObject implement that interface even though it is in a different |
+// library. |
+final String escapePrivateClassPrefix = r'$JSImplClass23402893498'; |
+ |
+// Exposed to return ArrayBufferView from a TypedArray passed to readPixels. |
+toArrayBufferView(TypedData data) native "Dart_TypedArray_ArrayBufferView"; |
+ |
+String _stripReservedNamePrefix(String name) => |
+ name.startsWith(_DART_RESERVED_NAME_PREFIX) |
+ ? name.substring(_DART_RESERVED_NAME_PREFIX.length) |
+ : name; |
+ |
+_buildArgs(Invocation invocation) { |
+ if (invocation.namedArguments.isEmpty) { |
+ return invocation.positionalArguments; |
+ } else { |
+ var varArgs = new Map<String, Object>(); |
+ invocation.namedArguments.forEach((symbol, val) { |
+ varArgs[mirrors.MirrorSystem.getName(symbol)] = val; |
+ }); |
+ return invocation.positionalArguments.toList() |
+ ..add(JsNative.jsify(varArgs)); |
+ } |
+} |
+ |
+final _allowedMethods = new Map<Symbol, _DeclarationSet>(); |
+final _allowedGetters = new Map<Symbol, _DeclarationSet>(); |
+final _allowedSetters = new Map<Symbol, _DeclarationSet>(); |
+ |
+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 |
+/// appropriate for at least some of the methods in the collection. |
+class _DeclarationSet { |
+ _DeclarationSet() : _members = <mirrors.DeclarationMirror>[]; |
+ |
+ static bool _checkType(obj, mirrors.TypeMirror type) { |
+ if (obj == null) return true; |
+ return mirrors.reflectType(obj.runtimeType).isSubtypeOf(type); |
+ } |
+ |
+ /// Returns whether the return [value] has a type is consistent with the |
+ /// return type from at least one of the members matching the DeclarationSet. |
+ bool _checkReturnType(value) { |
+ if (value == null) return true; |
+ var valueMirror = mirrors.reflectType(value.runtimeType); |
+ for (var member in _members) { |
+ if (member is mirrors.VariableMirror || member.isGetter) { |
+ // TODO(jacobr): actually check return types for getters that return |
+ // function types. |
+ return true; |
+ } else { |
+ if (valueMirror.isSubtypeOf(member.returnType)) return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ /** |
+ * Check whether the [invocation] is consistent with the [member] mirror. |
+ */ |
+ bool _checkDeclaration( |
+ Invocation invocation, mirrors.DeclarationMirror member) { |
+ if (member is mirrors.VariableMirror || (member as dynamic).isGetter) { |
+ // TODO(jacobr): actually check method types against the function type |
+ // returned by the getter or field. |
+ return true; |
+ } |
+ var parameters = (member as dynamic).parameters; |
+ var positionalArguments = invocation.positionalArguments; |
+ // Too many arguments |
+ if (parameters.length < positionalArguments.length) return false; |
+ // Too few required arguments. |
+ if (parameters.length > positionalArguments.length && |
+ !parameters[positionalArguments.length].isOptional) return false; |
+ for (var i = 0; i < positionalArguments.length; i++) { |
+ if (parameters[i].isNamed) { |
+ // Not enough positional arguments. |
+ return false; |
+ } |
+ if (!_checkType(invocation.positionalArguments[i], parameters[i].type)) |
+ return false; |
+ } |
+ if (invocation.namedArguments.isNotEmpty) { |
+ var startNamed; |
+ for (startNamed = parameters.length - 1; startNamed >= 0; startNamed--) { |
+ if (!parameters[startNamed].isNamed) break; |
+ } |
+ startNamed++; |
+ |
+ // TODO(jacobr): we are unnecessarily using an O(n^2) algorithm here. |
+ // If we have JS APIs with a large number of named parameters we should |
+ // optimize this. Either use a HashSet or invert this, walking over |
+ // parameters, querying invocation, and making sure we match |
+ //invocation.namedArguments.size keys. |
+ for (var name in invocation.namedArguments.keys) { |
+ bool match = false; |
+ 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; |
+ match = true; |
+ break; |
+ } |
+ } |
+ if (match == false) return false; |
+ } |
+ } |
+ return true; |
+ } |
+ |
+ bool checkInvocation(Invocation invocation) { |
+ for (var member in _members) { |
+ if (_checkDeclaration(invocation, member)) return true; |
+ } |
+ return false; |
+ } |
+ |
+ void add(mirrors.DeclarationMirror mirror) { |
+ _members.add(mirror); |
+ } |
+ |
+ final List<mirrors.DeclarationMirror> _members; |
+} |
+ |
+/** |
+ * Temporary method that we hope to remove at some point. This method should |
+ * generally only be called by machine generated code. |
+ */ |
+@Deprecated("Internal Use Only") |
+void registerJsInterfaces([List<Type> classes]) { |
+ // This method is now obsolete in Dartium. |
+} |
+ |
+void _registerJsInterfaces(List<Type> classes) { |
+ for (Type type in classes) { |
+ mirrors.ClassMirror typeMirror = mirrors.reflectType(type); |
+ typeMirror.declarations.forEach((symbol, declaration) { |
+ if (declaration is mirrors.MethodMirror || |
+ declaration is mirrors.VariableMirror && !declaration.isStatic) { |
+ bool treatAsGetter = false; |
+ bool treatAsSetter = false; |
+ if (declaration is mirrors.VariableMirror) { |
+ treatAsGetter = true; |
+ if (!declaration.isConst && !declaration.isFinal) { |
+ treatAsSetter = true; |
+ } |
+ } else { |
+ if (declaration.isGetter) { |
+ treatAsGetter = true; |
+ } else if (declaration.isSetter) { |
+ treatAsSetter = true; |
+ } else if (!declaration.isConstructor) { |
+ _allowedMethods |
+ .putIfAbsent(symbol, () => new _DeclarationSet()) |
+ .add(declaration); |
+ } |
+ } |
+ if (treatAsGetter) { |
+ _allowedGetters |
+ .putIfAbsent(symbol, () => new _DeclarationSet()) |
+ .add(declaration); |
+ _allowedMethods |
+ .putIfAbsent(symbol, () => new _DeclarationSet()) |
+ .add(declaration); |
+ } |
+ if (treatAsSetter) { |
+ _allowedSetters |
+ .putIfAbsent(symbol, () => new _DeclarationSet()) |
+ .add(declaration); |
+ } |
+ } |
+ }); |
+ } |
+} |
+ |
+_finalizeJsInterfaces() native "Js_finalizeJsInterfaces"; |
+ |
+String _getJsName(mirrors.DeclarationMirror mirror) { |
+ if (_atJsType != null) { |
+ for (var annotation in mirror.metadata) { |
+ if (annotation.type.reflectedType == _atJsType) { |
+ try { |
+ var name = annotation.reflectee.name; |
+ return name != null ? name : ""; |
+ } catch (e) {} |
+ } |
+ } |
+ } |
+ return null; |
+} |
+ |
+bool _isAnonymousClass(mirrors.ClassMirror mirror) { |
+ for (var annotation in mirror.metadata) { |
+ if (mirrors.MirrorSystem.getName(annotation.type.simpleName) == |
+ "_Anonymous") { |
+ mirrors.LibraryMirror library = annotation.type.owner; |
+ var uri = library.uri; |
+ // make sure the annotation is from package://js |
+ if (uri.scheme == 'package' && uri.path == 'js/js.dart') { |
+ return true; |
+ } |
+ } |
+ } |
+ return false; |
+} |
+ |
+bool _hasJsName(mirrors.DeclarationMirror mirror) { |
+ if (_atJsType != null) { |
+ for (var annotation in mirror.metadata) { |
+ if (annotation.type.reflectedType == _atJsType) { |
+ return true; |
+ } |
+ } |
+ } |
+ return false; |
+} |
+ |
+var _domNameType; |
+ |
+bool hasDomName(mirrors.DeclarationMirror mirror) { |
+ var location = mirror.location; |
+ if (location == null || location.sourceUri.scheme != 'dart') return false; |
+ for (var annotation in mirror.metadata) { |
+ if (mirrors.MirrorSystem.getName(annotation.type.simpleName) == "DomName") { |
+ // We can't make sure the annotation is in dart: as Dartium believes it |
+ // is file://dart/sdk/lib/html/html_common/metadata.dart |
+ // instead of a proper dart: location. |
+ return true; |
+ } |
+ } |
+ return false; |
+} |
+ |
+_getJsMemberName(mirrors.DeclarationMirror mirror) { |
+ var name = _getJsName(mirror); |
+ return name == null || name.isEmpty |
+ ? _stripReservedNamePrefix(_getDeclarationName(mirror)) |
+ : name; |
+} |
+ |
+// TODO(jacobr): handle setters correctyl. |
+String _getDeclarationName(mirrors.DeclarationMirror declaration) { |
+ var name = mirrors.MirrorSystem.getName(declaration.simpleName); |
+ if (declaration is mirrors.MethodMirror && declaration.isSetter) { |
+ assert(name.endsWith("=")); |
+ name = name.substring(0, name.length - 1); |
+ } |
+ return name; |
+} |
+ |
+final _JS_LIBRARY_PREFIX = "js_library"; |
+final _UNDEFINED_VAR = "_UNDEFINED_JS_CONST"; |
+ |
+String _accessJsPath(String path) => _accessJsPathHelper(path.split(".")); |
+ |
+String _accessJsPathHelper(Iterable<String> parts) { |
+ var sb = new StringBuffer(); |
+ sb |
+ ..write('${_JS_LIBRARY_PREFIX}.JsNative.getProperty(' * parts.length) |
+ ..write("${_JS_LIBRARY_PREFIX}.context"); |
+ for (var p in parts) { |
+ sb.write(", ${_escapeString(p)})"); |
+ } |
+ return sb.toString(); |
+} |
+ |
+// TODO(jacobr): remove these helpers and add JsNative.setPropertyDotted, |
+// getPropertyDotted, and callMethodDotted helpers that would be simpler |
+// and more efficient. |
+String _accessJsPathSetter(String path) { |
+ var parts = path.split("."); |
+ return "${_JS_LIBRARY_PREFIX}.JsNative.setProperty(${_accessJsPathHelper(parts.getRange(0, parts.length - 1)) |
+ }, ${_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)) |
+ }, ${_escapeString(parts.last)},"; |
+} |
+ |
+@Deprecated("Internal Use Only") |
+void addMemberHelper( |
+ mirrors.MethodMirror declaration, String path, StringBuffer sb, |
+ {bool isStatic: false, String memberName}) { |
+ if (!declaration.isConstructor) { |
+ var jsName = _getJsMemberName(declaration); |
+ path = (path != null && path.isNotEmpty) ? "${path}.${jsName}" : jsName; |
+ } |
+ var name = memberName != null ? memberName : _getDeclarationName(declaration); |
+ if (declaration.isConstructor) { |
+ sb.write("factory"); |
+ } else if (isStatic) { |
+ sb.write("static"); |
+ } else { |
+ sb.write("@patch"); |
+ } |
+ sb.write(" "); |
+ if (declaration.isGetter) { |
+ sb.write("get $name => ${_accessJsPath(path)};"); |
+ } else if (declaration.isSetter) { |
+ sb.write("set $name(v) {\n" |
+ " ${_JS_LIBRARY_PREFIX}.safeForTypedInterop(v);\n" |
+ " return ${_accessJsPathSetter(path)};\n" |
+ "}\n"); |
+ } else { |
+ sb.write("$name("); |
+ bool hasOptional = false; |
+ int i = 0; |
+ var args = <String>[]; |
+ for (var p in declaration.parameters) { |
+ assert(!p.isNamed); // TODO(jacobr): throw. |
+ assert(!p.hasDefaultValue); |
+ if (i > 0) { |
+ sb.write(", "); |
+ } |
+ if (p.isOptional && !hasOptional) { |
+ sb.write("["); |
+ hasOptional = true; |
+ } |
+ var arg = "p$i"; |
+ args.add(arg); |
+ sb.write(arg); |
+ if (p.isOptional) { |
+ sb.write("=${_UNDEFINED_VAR}"); |
+ } |
+ i++; |
+ } |
+ if (hasOptional) { |
+ sb.write("]"); |
+ } |
+ // TODO(jacobr): |
+ sb.write(") {\n"); |
+ for (var arg in args) { |
+ sb.write(" ${_JS_LIBRARY_PREFIX}.safeForTypedInterop($arg);\n"); |
+ } |
+ sb.write(" return "); |
+ if (declaration.isConstructor) { |
+ sb.write("${_JS_LIBRARY_PREFIX}.JsNative.callConstructor("); |
+ sb..write(_accessJsPath(path))..write(","); |
+ } else { |
+ sb.write(_accessJsPathCallMethodHelper(path)); |
+ } |
+ sb.write("[${args.join(",")}]"); |
+ |
+ if (hasOptional) { |
+ sb.write(".takeWhile((i) => i != ${_UNDEFINED_VAR}).toList()"); |
+ } |
+ sb.write(");"); |
+ sb.write("}\n"); |
+ } |
+ sb.write("\n"); |
+} |
+ |
+bool _isExternal(mirrors.MethodMirror mirror) { |
+ // This try-catch block is a workaround for BUG:24834. |
+ try { |
+ return mirror.isExternal; |
+ } catch (e) {} |
+ return false; |
+} |
+ |
+List<String> _generateExternalMethods( |
+ List<String> libraryPaths, bool useCachedPatches) { |
+ var staticCodegen = <String>[]; |
+ |
+ if (libraryPaths.length == 0) { |
+ mirrors.currentMirrorSystem().libraries.forEach((uri, library) { |
+ var library_name = "${uri.scheme}:${uri.path}"; |
+ if (useCachedPatches && cached_patches.containsKey(library_name)) { |
+ // Use the pre-generated patch files for DOM dart:nnnn libraries. |
+ var patch = cached_patches[library_name]; |
+ staticCodegen.addAll(patch); |
+ } else if (_hasJsName(library)) { |
+ // Library marked with @JS |
+ _generateLibraryCodegen(uri, library, staticCodegen); |
+ } else if (!useCachedPatches) { |
+ // Can't use the cached patches file, instead this is a signal to generate |
+ // the patches for this file. |
+ _generateLibraryCodegen(uri, library, staticCodegen); |
+ } |
+ }); // End of library foreach |
+ } else { |
+ // Used to generate cached_patches.dart file for all IDL generated dart: |
+ // files to the WebKit DOM. |
+ for (var library_name in libraryPaths) { |
+ var parts = library_name.split(':'); |
+ var uri = new Uri(scheme: parts[0], path: parts[1]); |
+ var library = mirrors.currentMirrorSystem().libraries[uri]; |
+ _generateLibraryCodegen(uri, library, staticCodegen); |
+ } |
+ } |
+ |
+ return staticCodegen; |
+} |
+ |
+_generateLibraryCodegen(uri, library, staticCodegen) { |
+ // Is it a dart generated library? |
+ var dartLibrary = uri.scheme == 'dart'; |
+ |
+ 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))); |
+ |
+ 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 isPrivateUserDefinedClass = |
+ className.startsWith('_') && !dartLibrary; |
+ 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, ${_escapeString(jsName)}, $name);\n" |
+ " }\n"); |
+ i++; |
+ } |
+ |
+ 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 |
+ |
+ 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 (isPrivateUserDefinedClass) { |
+ 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(""" |
+@patch class $className$typeVariablesClause { |
+$sbPatch |
+} |
+"""); |
+ if (isDom) { |
+ sb.write(""" |
+class $classNameImpl$typeVariablesClause extends $className implements ${_JS_LIBRARY_PREFIX}.JSObjectInterfacesDom { |
+ ${classNameImpl}.internal_() : super.internal_(); |
+ get runtimeType => $className; |
+ toString() => super.toString(); |
+} |
+"""); |
+ } |
+ } |
+ } |
+ } |
+ }); |
+ if (sb.isNotEmpty) { |
+ staticCodegen |
+ ..add(uri.toString()) |
+ ..add("${uri}_js_interop_patch.dart") |
+ ..add(""" |
+import 'dart:js' as ${_JS_LIBRARY_PREFIX}; |
+ |
+/** |
+ * Placeholder object for cases where we need to determine exactly how many |
+ * args were passed to a function. |
+ */ |
+const ${_UNDEFINED_VAR} = const Object(); |
+ |
+${sb} |
+"""); |
+ } |
+} |
+ |
+// Remember the @JS type to compare annotation type. |
+var _atJsType = -1; |
+ |
+void setupJsTypeCache() { |
+ // Cache the @JS Type. |
+ if (_atJsType == -1) { |
+ var uri = new Uri(scheme: "package", path: "js/js.dart"); |
+ var jsLibrary = mirrors.currentMirrorSystem().libraries[uri]; |
+ if (jsLibrary != null) { |
+ // @ JS used somewhere. |
+ var jsDeclaration = jsLibrary.declarations[new Symbol("JS")]; |
+ _atJsType = jsDeclaration.reflectedType; |
+ } else { |
+ // @ JS not used in any library. |
+ _atJsType = null; |
+ } |
+ } |
+} |
+ |
+/** |
+ * Generates part files defining source code for JSObjectImpl, all DOM classes |
+ * classes. This codegen is needed so that type checks for all registered |
+ * JavaScript interop classes pass. |
+ * If genCachedPatches is true then the patch files don't exist this is a special |
+ * 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) { |
+ // Cache the @JS Type. |
+ if (_atJsType == -1) setupJsTypeCache(); |
+ |
+ var ret = |
+ _generateExternalMethods(libraryPaths, genCachedPatches ? false : true); |
+ var libraryPrefixes = new Map<mirrors.LibraryMirror, String>(); |
+ var prefixNames = new Set<String>(); |
+ var sb = new StringBuffer(); |
+ |
+ var implements = <String>[]; |
+ var implementsArray = <String>[]; |
+ var implementsDom = <String>[]; |
+ var listMirror = mirrors.reflectType(List); |
+ var functionMirror = mirrors.reflectType(Function); |
+ var jsObjectMirror = mirrors.reflectType(JSObject); |
+ |
+ for (var typeMirror in jsInterfaceTypes) { |
+ mirrors.LibraryMirror libraryMirror = typeMirror.owner; |
+ var location = libraryMirror.location; |
+ var dartLibrary = location != null && location.sourceUri.scheme == 'dart'; |
+ |
+ var prefixName; |
+ if (libraryPrefixes.containsKey(libraryMirror)) { |
+ prefixName = libraryPrefixes[libraryMirror]; |
+ } else { |
+ var basePrefixName = |
+ mirrors.MirrorSystem.getName(libraryMirror.simpleName); |
+ basePrefixName = basePrefixName.replaceAll('.', '_'); |
+ if (basePrefixName.isEmpty) basePrefixName = "lib"; |
+ prefixName = basePrefixName; |
+ var i = 1; |
+ while (prefixNames.contains(prefixName)) { |
+ prefixName = '$basePrefixName$i'; |
+ i++; |
+ } |
+ prefixNames.add(prefixName); |
+ libraryPrefixes[libraryMirror] = prefixName; |
+ } |
+ var isArray = typeMirror.isSubtypeOf(listMirror); |
+ var isFunction = typeMirror.isSubtypeOf(functionMirror); |
+ var isJSObject = typeMirror.isSubtypeOf(jsObjectMirror); |
+ var className = mirrors.MirrorSystem.getName(typeMirror.simpleName); |
+ var isPrivateUserDefinedClass = className.startsWith('_') && !dartLibrary; |
+ if (isPrivateUserDefinedClass) |
+ className = '${escapePrivateClassPrefix}${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 |
+ // as implementing JS interop classes risks strange unintended |
+ // consequences as unrleated code may have instanceof checks. Checking |
+ // for isJSObject ensures we do not accidentally pull in existing |
+ // dart:html classes as they all have JSObject as a base class. |
+ // Note that methods from these classes can still be called on a |
+ // dart:html instance but checked mode type checks will fail. This is |
+ // not ideal but is better than causing strange breaks in existing |
+ // code that uses dart:html. |
+ // TODO(jacobr): consider throwing compile time errors if @JS classes |
+ // extend JSObject as that case cannot be safely handled in Dartium. |
+ implementsDom.add(fullName); |
+ } |
+ } |
+ libraryPrefixes.forEach((libraryMirror, prefix) { |
+ sb.writeln('import "${libraryMirror.uri}" as $prefix;'); |
+ }); |
+ buildImplementsClause(classes) => |
+ classes.isEmpty ? "" : "implements ${classes.join(', ')}"; |
+ var implementsClause = buildImplementsClause(implements); |
+ var implementsClauseDom = buildImplementsClause(implementsDom); |
+ // TODO(jacobr): only certain classes need to be implemented by |
+ // JsFunctionImpl. |
+ var allTypes = []..addAll(implements)..addAll(implementsArray); |
+ sb.write(''' |
+class JSObjectImpl extends JSObject $implementsClause { |
+ JSObjectImpl.internal() : super.internal(); |
+} |
+ |
+class JSFunctionImpl extends JSFunction $implementsClause { |
+ JSFunctionImpl.internal() : super.internal(); |
+} |
+ |
+class JSArrayImpl extends JSArray ${buildImplementsClause(implementsArray)} { |
+ JSArrayImpl.internal() : super.internal(); |
+} |
+ |
+// Interfaces that are safe to slam on all DOM classes. |
+// Adding implementsClause would be risky as it could contain Function which |
+// is likely to break a lot of instanceof checks. |
+abstract class JSObjectInterfacesDom $implementsClauseDom { |
+} |
+ |
+@patch class JSObject { |
+ static Type get instanceRuntimeType => JSObjectImpl; |
+} |
+ |
+@patch class JSFunction { |
+ static Type get instanceRuntimeType => JSFunctionImpl; |
+} |
+ |
+@patch class JSArray { |
+ static Type get instanceRuntimeType => JSArrayImpl; |
+} |
+ |
+_registerAllJsInterfaces() { |
+ _registerJsInterfaces([${allTypes.join(", ")}]); |
+} |
+ |
+'''); |
+ ret..addAll(["dart:js", "JSInteropImpl.dart", sb.toString()]); |
+ return ret; |
+} |
+ |
+// Start of block of helper methods facilitating emulating JavaScript Array |
+// methods on Dart List objects passed to JavaScript via JS interop. |
+// TODO(jacobr): match JS more closely. |
+String _toStringJs(obj) => '$obj'; |
+ |
+// TODO(jacobr): this might not exactly match JS semantics but should be |
+// adequate for now. |
+int _toIntJs(obj) { |
+ if (obj is int) return obj; |
+ if (obj is num) return obj.toInt(); |
+ return num.parse('$obj'.trim(), (_) => 0).toInt(); |
+} |
+ |
+// TODO(jacobr): this might not exactly match JS semantics but should be |
+// adequate for now. |
+num _toNumJs(obj) { |
+ return obj is num ? obj : num.parse('$obj'.trim(), (_) => 0); |
+} |
+ |
+/// Match the behavior of setting List length in JavaScript with the exception |
+/// that Dart does not distinguish undefined and null. |
+_setListLength(List list, rawlen) { |
+ num len = _toNumJs(rawlen); |
+ if (len is! int || len < 0) { |
+ throw new RangeError("Invalid array length"); |
+ } |
+ if (len > list.length) { |
+ _arrayExtend(list, len); |
+ } else if (len < list.length) { |
+ list.removeRange(len, list.length); |
+ } |
+ return rawlen; |
+} |
+ |
+// TODO(jacobr): should we really bother with this method instead of just |
+// shallow copying to a JS array and calling the JavaScript join method? |
+String _arrayJoin(List list, sep) { |
+ if (sep == null) { |
+ sep = ","; |
+ } |
+ return list.map((e) => e == null ? "" : e.toString()).join(sep.toString()); |
+} |
+ |
+// TODO(jacobr): should we really bother with this method instead of just |
+// shallow copying to a JS array and using the toString method? |
+String _arrayToString(List list) => _arrayJoin(list, ","); |
+ |
+int _arrayPush(List list, List args) { |
+ for (var e in args) { |
+ list.add(e); |
+ } |
+ return list.length; |
+} |
+ |
+_arrayPop(List list) { |
+ if (list.length > 0) return list.removeLast(); |
+} |
+ |
+// TODO(jacobr): would it be better to just copy input to a JS List |
+// and call Array.concat? |
+List _arrayConcat(List input, List args) { |
+ var ret = new List.from(input); |
+ for (var e in args) { |
+ // TODO(jacobr): technically in ES6 we should use |
+ // Symbol.isConcatSpreadable to determine whether call addAll. Once v8 |
+ // supports it, we can make all Dart classes implementing Iterable |
+ // specify isConcatSpreadable and tweak this behavior to allow Iterable. |
+ if (e is List) { |
+ ret.addAll(e); |
+ } else { |
+ ret.add(e); |
+ } |
+ } |
+ return ret; |
+} |
+ |
+List _arraySplice(List input, List args) { |
+ int start = 0; |
+ if (args.length > 0) { |
+ var rawStart = _toIntJs(args[0]); |
+ if (rawStart < 0) { |
+ start = math.max(0, input.length - rawStart); |
+ } else { |
+ start = math.min(input.length, rawStart); |
+ } |
+ } |
+ var end = start; |
+ if (args.length > 1) { |
+ var rawDeleteCount = _toIntJs(args[1]); |
+ if (rawDeleteCount < 0) rawDeleteCount = 0; |
+ end = math.min(input.length, start + rawDeleteCount); |
+ } |
+ var replacement = []; |
+ var removedElements = input.getRange(start, end).toList(); |
+ if (args.length > 2) { |
+ replacement = args.getRange(2, args.length); |
+ } |
+ input.replaceRange(start, end, replacement); |
+ return removedElements; |
+} |
+ |
+List _arrayReverse(List l) { |
+ for (var i = 0, j = l.length - 1; i < j; i++, j--) { |
+ var tmp = l[i]; |
+ l[i] = l[j]; |
+ l[j] = tmp; |
+ } |
+ return l; |
+} |
+ |
+_arrayShift(List l) { |
+ if (l.isEmpty) return null; // Technically we should return undefined. |
+ return l.removeAt(0); |
+} |
+ |
+int _arrayUnshift(List l, List args) { |
+ l.insertAll(0, args); |
+ return l.length; |
+} |
+ |
+_arrayExtend(List l, int newLength) { |
+ for (var i = l.length; i < newLength; i++) { |
+ // TODO(jacobr): we'd really like to add undefined to better match |
+ // JavaScript semantics. |
+ l.add(null); |
+ } |
+} |
+ |
+List _arraySort(List l, rawCompare) { |
+ // TODO(jacobr): alternately we could just copy the Array to JavaScript, |
+ // invoke the JS sort method and then copy the result back to Dart. |
+ Comparator compare; |
+ if (rawCompare == null) { |
+ compare = (a, b) => _toStringJs(a).compareTo(_toStringJs(b)); |
+ } else if (rawCompare is JsFunction) { |
+ compare = (a, b) => rawCompare.apply([a, b]); |
+ } else { |
+ compare = rawCompare; |
+ } |
+ l.sort(compare); |
+ return l; |
+} |
+// End of block of helper methods to emulate JavaScript Array methods on Dart List. |
+ |
+/** |
+ * Can be called to provide a predictable point where no more JS interfaces can |
+ * be added. Creating an instance of JsObject will also automatically trigger |
+ * all JsObjects to be finalized. |
+ */ |
+@Deprecated("Internal Use Only") |
+void finalizeJsInterfaces() { |
+ if (_finalized == true) { |
+ throw 'JSInterop class registration already finalized'; |
+ } |
+ _finalizeJsInterfaces(); |
+} |
+ |
+JsObject _cachedContext; |
+ |
+JsObject get _context native "Js_context_Callback"; |
+ |
+bool get _finalized native "Js_interfacesFinalized_Callback"; |
+ |
+JsObject get context { |
+ if (_cachedContext == null) { |
+ _cachedContext = _context; |
+ } |
+ return _cachedContext; |
+} |
+ |
+_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)) || |
+// 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))) { |
+ // 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) {} |
+ return [JSObject.instanceRuntimeType, false]; |
+} |
+ |
+/** |
+ * Base class for both the legacy JsObject class and the modern JSObject class. |
+ * This allows the JsNative utility class tobehave identically whether it is |
+ * called on a JsObject or a JSObject. |
+ */ |
+class _JSObjectBase extends NativeFieldWrapperClass2 { |
+ String _toString() native "JSObject_toString"; |
+ _callMethod(String name, List args) native "JSObject_callMethod"; |
+ _operator_getter(String property) native "JSObject_[]"; |
+ _operator_setter(String property, value) native "JSObject_[]="; |
+ bool _hasProperty(String property) native "JsObject_hasProperty"; |
+ bool _instanceof(/*JsFunction|JSFunction*/ type) native "JsObject_instanceof"; |
+ |
+ int get hashCode native "JSObject_hashCode"; |
+} |
+ |
+/** |
+ * Proxies a JavaScript object to Dart. |
+ * |
+ * The properties of the JavaScript object are accessible via the `[]` and |
+ * `[]=` operators. Methods are callable via [callMethod]. |
+ */ |
+class JsObject extends _JSObjectBase { |
+ JsObject.internal(); |
+ |
+ /** |
+ * Constructs a new JavaScript object from [constructor] and returns a proxy |
+ * to it. |
+ */ |
+ factory JsObject(JsFunction constructor, [List arguments]) { |
+ try { |
+ return _create(constructor, arguments); |
+ } catch (e) { |
+ // Re-throw any errors (returned as a string) as a DomException. |
+ throw new html.DomException.jsInterop(e); |
+ } |
+ } |
+ |
+ static JsObject _create(JsFunction constructor, arguments) |
+ native "JsObject_constructorCallback"; |
+ |
+ /** |
+ * Constructs a [JsObject] that proxies a native Dart object; _for expert use |
+ * only_. |
+ * |
+ * Use this constructor only if you wish to get access to JavaScript |
+ * properties attached to a browser host object, such as a Node or Blob, that |
+ * is normally automatically converted into a native Dart object. |
+ * |
+ * An exception will be thrown if [object] either is `null` or has the type |
+ * `bool`, `num`, or `String`. |
+ */ |
+ factory JsObject.fromBrowserObject(object) { |
+ if (object is num || object is String || object is bool || object == null) { |
+ throw new ArgumentError("object cannot be a num, string, bool, or null"); |
+ } |
+ if (object is JsObject) return object; |
+ return _fromBrowserObject(object); |
+ } |
+ |
+ /** |
+ * 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. |
+ */ |
+ factory JsObject.jsify(object) { |
+ if ((object is! Map) && (object is! Iterable)) { |
+ throw new ArgumentError("object must be a Map or Iterable"); |
+ } |
+ return _jsify(object); |
+ } |
+ |
+ static JsObject _jsify(object) native "JsObject_jsify"; |
+ |
+ static JsObject _fromBrowserObject(object) |
+ native "JsObject_fromBrowserObject"; |
+ |
+ /** |
+ * Returns the value associated with [property] from the proxied JavaScript |
+ * object. |
+ * |
+ * The type of [property] must be either [String] or [num]. |
+ */ |
+ operator [](property) { |
+ try { |
+ return _operator_getterLegacy(property); |
+ } catch (e) { |
+ // Re-throw any errors (returned as a string) as a DomException. |
+ throw new html.DomException.jsInterop(e); |
+ } |
+ } |
+ |
+ _operator_getterLegacy(property) native "JsObject_[]Legacy"; |
+ |
+ /** |
+ * Sets the value associated with [property] on the proxied JavaScript |
+ * object. |
+ * |
+ * The type of [property] must be either [String] or [num]. |
+ */ |
+ operator []=(property, value) { |
+ try { |
+ _operator_setterLegacy(property, value); |
+ } catch (e) { |
+ // Re-throw any errors (returned as a string) as a DomException. |
+ throw new html.DomException.jsInterop(e); |
+ } |
+ } |
+ |
+ _operator_setterLegacy(property, value) native "JsObject_[]=Legacy"; |
+ |
+ int get hashCode native "JsObject_hashCode"; |
+ |
+ operator ==(other) { |
+ if (other is! JsObject && other is! JSObject) return false; |
+ return _identityEquality(this, other); |
+ } |
+ |
+ static bool _identityEquality(a, b) native "JsObject_identityEquality"; |
+ |
+ /** |
+ * Returns `true` if the JavaScript object contains the specified property |
+ * either directly or though its prototype chain. |
+ * |
+ * This is the equivalent of the `in` operator in JavaScript. |
+ */ |
+ bool hasProperty(String property) => _hasProperty(property); |
+ |
+ /** |
+ * Removes [property] from the JavaScript object. |
+ * |
+ * This is the equivalent of the `delete` operator in JavaScript. |
+ */ |
+ void deleteProperty(String property) native "JsObject_deleteProperty"; |
+ |
+ /** |
+ * Returns `true` if the JavaScript object has [type] in its prototype chain. |
+ * |
+ * This is the equivalent of the `instanceof` operator in JavaScript. |
+ */ |
+ bool instanceof(JsFunction type) => _instanceof(type); |
+ |
+ /** |
+ * Returns the result of the JavaScript objects `toString` method. |
+ */ |
+ String toString() { |
+ try { |
+ return _toString(); |
+ } catch (e) { |
+ return super.toString(); |
+ } |
+ } |
+ |
+ String _toString() native "JsObject_toString"; |
+ |
+ /** |
+ * Calls [method] on the JavaScript object with the arguments [args] and |
+ * returns the result. |
+ * |
+ * The type of [method] must be either [String] or [num]. |
+ */ |
+ callMethod(String method, [List args]) { |
+ try { |
+ return _callMethodLegacy(method, args); |
+ } catch (e) { |
+ if (hasProperty(method)) { |
+ // Return a DomException if DOM call returned an error. |
+ throw new html.DomException.jsInterop(e); |
+ } else { |
+ throw new NoSuchMethodError(this, new Symbol(method), args, null); |
+ } |
+ } |
+ } |
+ |
+ _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 { |
+ JSObject.internal() {} |
+ external static Type get instanceRuntimeType; |
+ |
+ /** |
+ * Returns the result of the JavaScript objects `toString` method. |
+ */ |
+ String toString() { |
+ try { |
+ return _toString(); |
+ } catch (e) { |
+ return super.toString(); |
+ } |
+ } |
+ |
+ noSuchMethod(Invocation invocation) { |
+ throwError() { |
+ super.noSuchMethod(invocation); |
+ } |
+ |
+ String name = _stripReservedNamePrefix( |
+ mirrors.MirrorSystem.getName(invocation.memberName)); |
+ argsSafeForTypedInterop(invocation.positionalArguments); |
+ if (invocation.isGetter) { |
+ if (CHECK_JS_INVOCATIONS) { |
+ var matches = _allowedGetters[invocation.memberName]; |
+ if (matches == null && |
+ !_allowedMethods.containsKey(invocation.memberName)) { |
+ throwError(); |
+ } |
+ var ret = _operator_getter(name); |
+ 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. |
+ throwError(); |
+ } else { |
+ // TODO(jacobr): should we throw if the JavaScript object doesn't have the property? |
+ return _operator_getter(name); |
+ } |
+ } else if (invocation.isSetter) { |
+ if (CHECK_JS_INVOCATIONS) { |
+ var matches = _allowedSetters[invocation.memberName]; |
+ if (matches == null || !matches.checkInvocation(invocation)) |
+ throwError(); |
+ } |
+ assert(name.endsWith("=")); |
+ name = name.substring(0, name.length - 1); |
+ return _operator_setter(name, invocation.positionalArguments.first); |
+ } else { |
+ // TODO(jacobr): also allow calling getters that look like functions. |
+ var matches; |
+ if (CHECK_JS_INVOCATIONS) { |
+ matches = _allowedMethods[invocation.memberName]; |
+ if (matches == null || !matches.checkInvocation(invocation)) |
+ throwError(); |
+ } |
+ var ret = _callMethod(name, _buildArgs(invocation)); |
+ if (CHECK_JS_INVOCATIONS) { |
+ if (!matches._checkReturnType(ret)) { |
+ html.window.console.error("Return value for method: ${name} is " |
+ "${ret.runtimeType} which is inconsistent with all typed " |
+ "JS interop definitions for method ${name}."); |
+ } |
+ } |
+ return ret; |
+ } |
+ } |
+} |
+ |
+@Deprecated("Internal Use Only") |
+class JSArray extends JSObject with ListMixin { |
+ JSArray.internal() : super.internal(); |
+ external static Type get instanceRuntimeType; |
+ |
+ // Reuse JsArray_length as length behavior is unchanged. |
+ int get length native "JsArray_length"; |
+ |
+ set length(int length) { |
+ _operator_setter('length', length); |
+ } |
+ |
+ _checkIndex(int index, {bool insert: false}) { |
+ int length = insert ? this.length + 1 : this.length; |
+ if (index is int && (index < 0 || index >= length)) { |
+ throw new RangeError.range(index, 0, length); |
+ } |
+ } |
+ |
+ _checkRange(int start, int end) { |
+ int cachedLength = this.length; |
+ if (start < 0 || start > cachedLength) { |
+ throw new RangeError.range(start, 0, cachedLength); |
+ } |
+ if (end < start || end > cachedLength) { |
+ throw new RangeError.range(end, start, cachedLength); |
+ } |
+ } |
+ |
+ _indexed_getter(int index) native "JSArray_indexed_getter"; |
+ _indexed_setter(int index, o) native "JSArray_indexed_setter"; |
+ |
+ // Methods required by ListMixin |
+ |
+ operator [](index) { |
+ if (index is int) { |
+ _checkIndex(index); |
+ } |
+ |
+ return _indexed_getter(index); |
+ } |
+ |
+ void operator []=(int index, value) { |
+ _checkIndex(index); |
+ _indexed_setter(index, value); |
+ } |
+} |
+ |
+@Deprecated("Internal Use Only") |
+class JSFunction extends JSObject implements Function { |
+ JSFunction.internal() : super.internal(); |
+ |
+ external static Type get instanceRuntimeType; |
+ |
+ call( |
+ [a1 = _UNDEFINED, |
+ a2 = _UNDEFINED, |
+ a3 = _UNDEFINED, |
+ a4 = _UNDEFINED, |
+ a5 = _UNDEFINED, |
+ a6 = _UNDEFINED, |
+ a7 = _UNDEFINED, |
+ a8 = _UNDEFINED, |
+ a9 = _UNDEFINED, |
+ a10 = _UNDEFINED]) { |
+ return _apply( |
+ _stripUndefinedArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10])); |
+ } |
+ |
+ noSuchMethod(Invocation invocation) { |
+ if (invocation.isMethod && invocation.memberName == #call) { |
+ return _apply(_buildArgs(invocation)); |
+ } |
+ return super.noSuchMethod(invocation); |
+ } |
+ |
+ dynamic _apply(List args, {thisArg}) native "JSFunction_apply"; |
+ |
+ static JSFunction _createWithThis(Function f) |
+ native "JSFunction_createWithThis"; |
+ static JSFunction _create(Function f) native "JSFunction_create"; |
+} |
+ |
+// JavaScript interop methods that do not automatically wrap to dart:html types. |
+// Warning: this API is not exposed to dart:js. |
+// TODO(jacobr): rename to JSNative and make at least part of this API public. |
+@Deprecated("Internal Use Only") |
+class JsNative { |
+ static JSObject jsify(object) native "JSObject_jsify"; |
+ static JSObject newObject() native "JSObject_newObject"; |
+ static JSArray newArray() native "JSObject_newArray"; |
+ |
+ 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 toTypedObject(JsObject o) native "JSNative_toTypedObject"; |
+ |
+ /** |
+ * Same behavior as new JsFunction.withThis except that JavaScript "this" is not |
+ * wrapped. |
+ */ |
+ static JSFunction withThis(Function f) native "JsFunction_withThisNoWrap"; |
+} |
+ |
+/** |
+ * Proxies a JavaScript Function object. |
+ */ |
+class JsFunction extends JsObject { |
+ JsFunction.internal() : super.internal(); |
+ |
+ /** |
+ * Returns a [JsFunction] that captures its 'this' binding and calls [f] |
+ * with the value of this passed as the first argument. |
+ */ |
+ factory JsFunction.withThis(Function f) => _withThis(f); |
+ |
+ /** |
+ * 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}) native "JsFunction_apply"; |
+ |
+ /** |
+ * Internal only version of apply which uses debugger proxies of Dart objects |
+ * rather than opaque handles. This method is private because it cannot be |
+ * efficiently implemented in Dart2Js so should only be used by internal |
+ * tools. |
+ */ |
+ _applyDebuggerOnly(List args, {thisArg}) |
+ native "JsFunction_applyDebuggerOnly"; |
+ |
+ static JsFunction _withThis(Function f) native "JsFunction_withThis"; |
+} |
+ |
+/** |
+ * A [List] proxying a JavaScript Array. |
+ */ |
+class JsArray<E> extends JsObject with ListMixin<E> { |
+ JsArray.internal() : super.internal(); |
+ |
+ factory JsArray() => _newJsArray(); |
+ |
+ static JsArray _newJsArray() native "JsArray_newJsArray"; |
+ |
+ factory JsArray.from(Iterable<E> other) => |
+ _newJsArrayFromSafeList(new List.from(other)); |
+ |
+ static JsArray _newJsArrayFromSafeList(List list) |
+ native "JsArray_newJsArrayFromSafeList"; |
+ |
+ _checkIndex(int index, {bool insert: false}) { |
+ int length = insert ? this.length + 1 : this.length; |
+ if (index is int && (index < 0 || index >= length)) { |
+ throw new RangeError.range(index, 0, length); |
+ } |
+ } |
+ |
+ _checkRange(int start, int end) { |
+ int cachedLength = this.length; |
+ if (start < 0 || start > cachedLength) { |
+ throw new RangeError.range(start, 0, cachedLength); |
+ } |
+ if (end < start || end > cachedLength) { |
+ throw new RangeError.range(end, start, cachedLength); |
+ } |
+ } |
+ |
+ // Methods required by ListMixin |
+ |
+ E operator [](index) { |
+ if (index is int) { |
+ _checkIndex(index); |
+ } |
+ |
+ return super[index]; |
+ } |
+ |
+ void operator []=(index, E value) { |
+ if (index is int) { |
+ _checkIndex(index); |
+ } |
+ super[index] = value; |
+ } |
+ |
+ int get length native "JsArray_length"; |
+ |
+ set length(int length) { |
+ super['length'] = length; |
+ } |
+ |
+ // Methods overridden for better performance |
+ |
+ void add(E value) { |
+ callMethod('push', [value]); |
+ } |
+ |
+ void addAll(Iterable<E> iterable) { |
+ // TODO(jacobr): this can be optimized slightly. |
+ callMethod('push', new List.from(iterable)); |
+ } |
+ |
+ void insert(int index, E element) { |
+ _checkIndex(index, insert: true); |
+ callMethod('splice', [index, 0, element]); |
+ } |
+ |
+ E removeAt(int index) { |
+ _checkIndex(index); |
+ return callMethod('splice', [index, 1])[0]; |
+ } |
+ |
+ E removeLast() { |
+ if (length == 0) throw new RangeError(-1); |
+ return callMethod('pop'); |
+ } |
+ |
+ void removeRange(int start, int end) { |
+ _checkRange(start, end); |
+ callMethod('splice', [start, end - start]); |
+ } |
+ |
+ void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) { |
+ _checkRange(start, end); |
+ int length = end - start; |
+ if (length == 0) return; |
+ if (skipCount < 0) throw new ArgumentError(skipCount); |
+ var args = [start, length]..addAll(iterable.skip(skipCount).take(length)); |
+ callMethod('splice', args); |
+ } |
+ |
+ void sort([int compare(E a, E b)]) { |
+ callMethod('sort', [compare]); |
+ } |
+} |
+ |
+/** |
+ * Placeholder object for cases where we need to determine exactly how many |
+ * args were passed to a function. |
+ */ |
+const _UNDEFINED = const Object(); |
+ |
+// TODO(jacobr): this method is a hack to work around the lack of proper dart |
+// support for varargs methods. |
+List _stripUndefinedArgs(List args) => |
+ args.takeWhile((i) => i != _UNDEFINED).toList(); |
+ |
+/** |
+ * Check that that if [arg] is a [Function] it is safe to pass to JavaScript. |
+ * To make a function safe, call [allowInterop] or [allowInteropCaptureThis]. |
+ */ |
+@Deprecated("Internal Use Only") |
+safeForTypedInterop(arg) { |
+ if (CHECK_JS_INVOCATIONS && arg is Function && arg is! JSFunction) { |
+ throw new ArgumentError( |
+ "Attempt to pass Function '$arg' to JavaScript via without calling allowInterop or allowInteropCaptureThis"); |
+ } |
+} |
+ |
+/** |
+ * Check that that if any elements of [args] are [Function] it is safe to pass |
+ * to JavaScript. To make a function safe, call [allowInterop] or |
+ * [allowInteropCaptureThis]. |
+ */ |
+@Deprecated("Internal Use Only") |
+void argsSafeForTypedInterop(Iterable args) { |
+ for (var arg in args) { |
+ safeForTypedInterop(arg); |
+ } |
+} |
+ |
+/** |
+ * Returns a method that can be called with an arbitrary number (for n less |
+ * than 11) of arguments without violating Dart type checks. |
+ */ |
+Function _wrapAsDebuggerVarArgsFunction(JsFunction jsFunction) => ( |
+ [a1 = _UNDEFINED, |
+ a2 = _UNDEFINED, |
+ a3 = _UNDEFINED, |
+ a4 = _UNDEFINED, |
+ a5 = _UNDEFINED, |
+ a6 = _UNDEFINED, |
+ a7 = _UNDEFINED, |
+ a8 = _UNDEFINED, |
+ a9 = _UNDEFINED, |
+ a10 = _UNDEFINED]) => |
+ jsFunction._applyDebuggerOnly( |
+ _stripUndefinedArgs([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10])); |
+ |
+/// Returns a wrapper around function [f] that can be called from JavaScript |
+/// using the package:js Dart-JavaScript interop. |
+/// |
+/// For performance reasons in Dart2Js, by default Dart functions cannot be |
+/// passed directly to JavaScript unless this method is called to create |
+/// a Function compatible with both Dart and JavaScript. |
+/// Calling this method repeatedly on a function will return the same function. |
+/// The [Function] returned by this method can be used from both Dart and |
+/// 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) { |
+ if (f is JSFunction) { |
+ // The function is already a JSFunction... no need to do anything. |
+ return f; |
+ } else { |
+ return JSFunction._create(f); |
+ } |
+} |
+ |
+/// Cached JSFunction associated with the Dart function when "this" is |
+/// captured. |
+Expando<JSFunction> _interopCaptureThisExpando = new Expando<JSFunction>(); |
+ |
+/// Returns a [Function] that when called from JavaScript captures its 'this' |
+/// binding and calls [f] with the value of this passed as the first argument. |
+/// When called from Dart, [null] will be passed as the first argument. |
+/// |
+/// See the documentation for [allowInterop]. This method should only be used |
+/// with package:js Dart-JavaScript interop. |
+JSFunction allowInteropCaptureThis(Function f) { |
+ if (f is JSFunction) { |
+ // Behavior when the function is already a JS function is unspecified. |
+ throw new ArgumentError( |
+ "Function is already a JS function so cannot capture this."); |
+ return f; |
+ } else { |
+ var ret = _interopCaptureThisExpando[f]; |
+ if (ret == null) { |
+ // TODO(jacobr): we could optimize this. |
+ ret = JSFunction._createWithThis(f); |
+ _interopCaptureThisExpando[f] = ret; |
+ } |
+ return ret; |
+ } |
+} |