Index: sdk/lib/js/dartium/js_dartium.dart |
diff --git a/sdk/lib/js/dartium/js_dartium.dart b/sdk/lib/js/dartium/js_dartium.dart |
index e7f3004c686d8efbc0b5bdc038bbb28178f2f5d2..0bb53c361dcd37bde51019ab9d5a4d782b004292 100644 |
--- a/sdk/lib/js/dartium/js_dartium.dart |
+++ b/sdk/lib/js/dartium/js_dartium.dart |
@@ -89,11 +89,388 @@ library dart.js; |
import 'dart:collection' show ListMixin; |
import 'dart:nativewrappers'; |
+import 'dart:math' as math; |
+import 'dart:mirrors' as mirrors; |
+ |
+// Pretend we are always in checked mode as we aren't interested in users |
+// running Dartium code outside of checked mode. |
+final bool CHECK_JS_INVOCATIONS = true; |
+ |
+final _allowedMethods = new Map<Symbol, _DeclarationSet>(); |
+final _allowedGetters = new Map<Symbol, _DeclarationSet>(); |
+final _allowedSetters = new Map<Symbol, _DeclarationSet>(); |
+ |
+final _jsInterfaceTypes = new Set<Type>(); |
+Iterable<Type> get jsInterfaceTypes => _jsInterfaceTypes; |
+ |
+/// A collection of methods where all methods have the same name. |
+/// This class is intended to optimize whether a specific invocation is |
+/// appropritate for at least some of the methods in the collection. |
+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 unneccessarily using an O(n^2) algorithm here. |
+ // If we have JS APIs with a lange 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. |
+ */ |
+void registerJsInterfaces(List<Type> classes) { |
+ if (_finalized == true) { |
+ throw 'JSInterop class registration already finalized'; |
+ } |
+ for (Type type in classes) { |
+ if (!_jsInterfaceTypes.add(type)) continue; // Already registered. |
+ 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"; |
+ |
+/** |
+ * Generates a part file defining source code for JsObjectImpl and related |
+ * classes. This calass is needed so that type checks for all registered JavaScript |
+ * interop classes pass. |
+ */ |
+String _generateJsObjectImplPart() { |
+ Iterable<Type> types = jsInterfaceTypes; |
+ var libraryPrefixes = new Map<mirrors.LibraryMirror, String>(); |
+ var prefixNames = new Set<String>(); |
+ var sb = new StringBuffer(); |
+ |
+ var implements = <String>[]; |
+ for (var type in types) { |
+ mirrors.ClassMirror typeMirror = mirrors.reflectType(type); |
+ mirrors.LibraryMirror libraryMirror = typeMirror.owner; |
+ 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; |
+ } |
+ implements.add( |
+ '${prefixName}.${mirrors.MirrorSystem.getName(typeMirror.simpleName)}'); |
+ } |
+ libraryPrefixes.forEach((libraryMirror, prefix) { |
+ sb.writeln('import "${libraryMirror.uri}" as $prefix;'); |
+ }); |
+ var implementsClause = |
+ implements.isEmpty ? "" : "implements ${implements.join(', ')}"; |
+ // TODO(jacobr): only certain classes need to be implemented by |
+ // Function and Array. |
+ sb.write(''' |
+class JsObjectImpl extends JsObject $implementsClause { |
+ JsObjectImpl.internal() : super.internal(); |
+} |
+ |
+class JsFunctionImpl extends JsFunction $implementsClause { |
+ JsFunctionImpl.internal() : super.internal(); |
+} |
+ |
+class JsArrayImpl<E> extends JsArray<E> $implementsClause { |
+ JsArrayImpl.internal() : super.internal(); |
+} |
+'''); |
+ return sb.toString(); |
+} |
+ |
+// 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. |
+ */ |
+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; |
@@ -114,9 +491,24 @@ class JsObject extends NativeFieldWrapperClass2 { |
* Constructs a new JavaScript object from [constructor] and returns a proxy |
* to it. |
*/ |
- factory JsObject(JsFunction constructor, [List arguments]) => _create(constructor, arguments); |
- |
- static JsObject _create(JsFunction constructor, arguments) native "JsObject_constructorCallback"; |
+ factory JsObject(JsFunction constructor, [List arguments]) => |
+ _create(constructor, arguments); |
+ |
+ static JsObject _create( |
+ JsFunction constructor, arguments) native "JsObject_constructorCallback"; |
+ |
+ _buildArgs(Invocation invocation) { |
+ if (invocation.namedArguments.isEmpty) { |
+ return invocation.positionalArguments; |
+ } else { |
+ var varArgs = new Map<String, Object>(); |
+ invocation.namedArguments.forEach((symbol, val) { |
+ varArgs[mirrors.MirrorSystem.getName(symbol)] = val; |
+ }); |
+ return invocation.positionalArguments.toList() |
+ ..add(new JsObject.jsify(varArgs)); |
+ } |
+ } |
/** |
* Constructs a [JsObject] that proxies a native Dart object; _for expert use |
@@ -131,8 +523,7 @@ class JsObject extends NativeFieldWrapperClass2 { |
*/ |
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"); |
+ throw new ArgumentError("object cannot be a num, string, bool, or null"); |
} |
return _fromBrowserObject(object); |
} |
@@ -155,7 +546,8 @@ class JsObject extends NativeFieldWrapperClass2 { |
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 |
@@ -163,7 +555,7 @@ class JsObject extends NativeFieldWrapperClass2 { |
* |
* The type of [property] must be either [String] or [num]. |
*/ |
- operator[](property) native "JsObject_[]"; |
+ operator [](property) native "JsObject_[]"; |
/** |
* Sets the value associated with [property] on the proxied JavaScript |
@@ -171,13 +563,14 @@ class JsObject extends NativeFieldWrapperClass2 { |
* |
* The type of [property] must be either [String] or [num]. |
*/ |
- operator[]=(property, value) native "JsObject_[]="; |
+ operator []=(property, value) native "JsObject_[]="; |
int get hashCode native "JsObject_hashCode"; |
- operator==(other) => other is JsObject && _identityEquality(this, other); |
+ operator ==(other) => other is JsObject && _identityEquality(this, other); |
- static bool _identityEquality(JsObject a, JsObject b) native "JsObject_identityEquality"; |
+ static bool _identityEquality( |
+ JsObject a, JsObject b) native "JsObject_identityEquality"; |
/** |
* Returns `true` if the JavaScript object contains the specified property |
@@ -207,7 +600,7 @@ class JsObject extends NativeFieldWrapperClass2 { |
String toString() { |
try { |
return _toString(); |
- } catch(e) { |
+ } catch (e) { |
return super.toString(); |
} |
} |
@@ -223,7 +616,7 @@ class JsObject extends NativeFieldWrapperClass2 { |
callMethod(String method, [List args]) { |
try { |
return _callMethod(method, args); |
- } catch(e) { |
+ } catch (e) { |
if (hasProperty(method)) { |
rethrow; |
} else { |
@@ -232,13 +625,63 @@ class JsObject extends NativeFieldWrapperClass2 { |
} |
} |
+ noSuchMethod(Invocation invocation) { |
+ throwError() { |
+ throw new NoSuchMethodError(this, invocation.memberName, |
+ invocation.positionalArguments, invocation.namedArguments); |
+ } |
+ |
+ String name = mirrors.MirrorSystem.getName(invocation.memberName); |
+ if (invocation.isGetter) { |
+ if (CHECK_JS_INVOCATIONS) { |
+ var matches = _allowedGetters[invocation.memberName]; |
+ if (matches == null && |
+ !_allowedMethods.containsKey(invocation.memberName)) { |
+ throwError(); |
+ } |
+ var ret = this[name]; |
+ if (matches != null && matches._checkReturnType(ret)) 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 this[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 this[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 = this.callMethod(name, _buildArgs(invocation)); |
+ if (CHECK_JS_INVOCATIONS) { |
+ if (!matches._checkReturnType(ret)) throwError(); |
+ } |
+ return ret; |
+ } |
+ } |
+ |
_callMethod(String name, List args) native "JsObject_callMethod"; |
} |
/** |
* Proxies a JavaScript Function object. |
*/ |
-class JsFunction extends JsObject { |
+class JsFunction extends JsObject implements Function { |
JsFunction.internal() : super.internal(); |
/** |
@@ -253,13 +696,21 @@ class JsFunction extends JsObject { |
*/ |
dynamic apply(List args, {thisArg}) native "JsFunction_apply"; |
+ noSuchMethod(Invocation invocation) { |
+ if (invocation.isMethod && invocation.memberName == #call) { |
+ return apply(_buildArgs(invocation)); |
+ } |
+ return super.noSuchMethod(invocation); |
+ } |
+ |
/** |
* Internal only version of apply which uses debugger proxies of Dart objects |
* rather than opaque handles. This method is private because it cannot be |
* efficiently implemented in Dart2Js so should only be used by internal |
* tools. |
*/ |
- _applyDebuggerOnly(List args, {thisArg}) native "JsFunction_applyDebuggerOnly"; |
+ _applyDebuggerOnly(List args, |
+ {thisArg}) native "JsFunction_applyDebuggerOnly"; |
static JsFunction _withThis(Function f) native "JsFunction_withThis"; |
} |
@@ -268,14 +719,17 @@ class JsFunction extends JsObject { |
* 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)); |
+ factory JsArray.from(Iterable<E> other) => |
+ _newJsArrayFromSafeList(new List.from(other)); |
- static JsArray _newJsArrayFromSafeList(List list) native "JsArray_newJsArrayFromSafeList"; |
+ static JsArray _newJsArrayFromSafeList( |
+ List list) native "JsArray_newJsArrayFromSafeList"; |
_checkIndex(int index, {bool insert: false}) { |
int length = insert ? this.length + 1 : this.length; |
@@ -304,7 +758,7 @@ class JsArray<E> extends JsObject with ListMixin<E> { |
} |
void operator []=(index, E value) { |
- if(index is int) { |
+ if (index is int) { |
_checkIndex(index); |
} |
super[index] = value; |
@@ -312,7 +766,9 @@ class JsArray<E> extends JsObject with ListMixin<E> { |
int get length native "JsArray_length"; |
- void set length(int length) { super['length'] = length; } |
+ void set length(int length) { |
+ super['length'] = length; |
+ } |
// Methods overriden for better performance |
@@ -326,7 +782,7 @@ class JsArray<E> extends JsObject with ListMixin<E> { |
} |
void insert(int index, E element) { |
- _checkIndex(index, insert:true); |
+ _checkIndex(index, insert: true); |
callMethod('splice', [index, 0, element]); |
} |
@@ -365,7 +821,7 @@ class JsArray<E> extends JsObject with ListMixin<E> { |
*/ |
const _UNDEFINED = const Object(); |
-// FIXME(jacobr): this method is a hack to work around the lack of proper dart |
+// 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(); |
@@ -375,8 +831,7 @@ List _stripUndefinedArgs(List args) => |
* 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])); |
+ ([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])); |