Index: tools/dom/src/native_DOMImplementation.dart |
diff --git a/tools/dom/src/native_DOMImplementation.dart b/tools/dom/src/native_DOMImplementation.dart |
index 490dd1448514f6b0181edff75d978cb923444bf4..e1166d5318cb88ff8c99b409309b8d8b72489ca3 100644 |
--- a/tools/dom/src/native_DOMImplementation.dart |
+++ b/tools/dom/src/native_DOMImplementation.dart |
@@ -4,6 +4,32 @@ |
part of html; |
+class _Property { |
+ _Property(this.name) : |
+ _hasValue = false, |
+ writable = false, |
+ isMethod = false, |
+ isOwn = true, |
+ wasThrown = false; |
+ |
+ bool get hasValue => _hasValue; |
+ get value => _value; |
+ set value(v) { |
+ _value = v; |
+ _hasValue = true; |
+ } |
+ |
+ final String name; |
+ Function setter; |
+ Function getter; |
+ var _value; |
+ bool _hasValue; |
+ bool writable; |
+ bool isMethod; |
+ bool isOwn; |
+ bool wasThrown; |
+} |
+ |
class _ConsoleVariables { |
Map<String, Object> _data = new Map<String, Object>(); |
@@ -28,7 +54,53 @@ class _ConsoleVariables { |
/** |
* List all variables currently defined. |
*/ |
- List variables() => _data.keys.toList(growable: false); |
+ List variables() => _data.keys.toList(); |
+} |
+ |
+/** |
+ * Base class for invocation trampolines used to closurize methods, getters |
+ * and setters. |
+ */ |
+abstract class _Trampoline implements Function { |
+ final ObjectMirror _receiver; |
+ final MethodMirror _methodMirror; |
+ final Symbol _selector; |
+ |
+ _Trampoline(this._receiver, this._methodMirror, this._selector); |
+} |
+ |
+class _MethodTrampoline extends _Trampoline { |
+ _MethodTrampoline(ObjectMirror receiver, MethodMirror methodMirror, Symbol selector) : |
+ super(receiver, methodMirror, selector); |
+ |
+ noSuchMethod(Invocation msg) { |
+ if (msg.memberName != #call) return super.noSuchMethod(msg); |
+ return _receiver.invoke(_selector, |
+ msg.positionalArguments, |
+ msg.namedArguments).reflectee; |
+ } |
+} |
+ |
+/** |
+ * Invocation trampoline class used to closurize getters. |
+ */ |
+class _GetterTrampoline extends _Trampoline { |
+ _GetterTrampoline(ObjectMirror receiver, MethodMirror methodMirror, Symbol selector) : |
+ super(receiver, methodMirror, selector); |
+ |
+ call() => _receiver.getField(_selector).reflectee; |
+} |
+ |
+/** |
+ * Invocation trampoline class used to closurize setters. |
+ */ |
+class _SetterTrampoline extends _Trampoline { |
+ _SetterTrampoline(ObjectMirror receiver, MethodMirror methodMirror, Symbol selector) : |
+ super(receiver, methodMirror, selector); |
+ |
+ call(value) { |
+ _receiver.setField(_selector, value); |
+ } |
} |
class _Utils { |
@@ -144,17 +216,6 @@ class _Utils { |
static _ConsoleVariables _consoleTempVariables = new _ConsoleVariables(); |
/** |
- * Header passed in from the Dartium Developer Tools when an expression is |
- * evaluated in the console as opposed to the watch window or another context |
- * that does not expect REPL support. |
- */ |
- static const _CONSOLE_API_SUPPORT_HEADER = |
- 'with ((console && console._commandLineAPI) || { __proto__: null }) {\n'; |
- static bool expectsConsoleApi(String expression) { |
- return expression.indexOf(_CONSOLE_API_SUPPORT_HEADER) == 0;; |
- } |
- |
- /** |
* Takes an [expression] and a list of [local] variable and returns an |
* expression for a closure with a body matching the original expression |
* where locals are passed in as arguments. Returns a list containing the |
@@ -166,8 +227,7 @@ class _Utils { |
* For example: |
* <code> |
* _consoleTempVariables = {'a' : someValue, 'b': someOtherValue} |
- * wrapExpressionAsClosure("${_CONSOLE_API_SUPPORT_HEADER}foo + bar + a", |
- * ["bar", 40, "foo", 2]) |
+ * wrapExpressionAsClosure("foo + bar + a", ["bar", 40, "foo", 2], true) |
* </code> |
* will return: |
* <code> |
@@ -177,9 +237,8 @@ class _Utils { |
* [_consoleTempVariables, 40, 2, someValue, someOtherValue]] |
* </code> |
*/ |
- static List wrapExpressionAsClosure(String expression, List locals) { |
- // FIXME: dartbug.com/10434 find a less fragile way to determine whether |
- // we need to strip off console API support added by InjectedScript. |
+ static List wrapExpressionAsClosure(String expression, List locals, |
+ bool includeCommandLineAPI) { |
var args = {}; |
var sb = new StringBuffer("("); |
addArg(arg, value) { |
@@ -198,10 +257,7 @@ class _Utils { |
args[arg] = value; |
} |
- if (expectsConsoleApi(expression)) { |
- expression = expression.substring(expression.indexOf('\n') + 1); |
- expression = expression.substring(0, expression.lastIndexOf('\n')); |
- |
+ if (includeCommandLineAPI) { |
addArg("\$consoleVariables", _consoleTempVariables); |
// FIXME: use a real Dart tokenizer. The following regular expressions |
@@ -258,53 +314,427 @@ class _Utils { |
return [sb.toString(), args.values.toList(growable: false)]; |
} |
+ static String _getShortSymbolName(Symbol symbol, |
+ DeclarationMirror declaration) { |
+ var name = MirrorSystem.getName(symbol); |
+ if (declaration is MethodMirror) { |
+ if (declaration.isSetter && name[name.length-1] == "=") { |
+ return name.substring(0, name.length-1); |
+ } |
+ if (declaration.isConstructor) { |
+ return name.substring(name.indexOf('.') + 1); |
+ } |
+ } |
+ return name; |
+ } |
+ |
+ /** |
+ * Adds all candidate String completitions from [declarations] to [output] |
+ * filtering based on [staticContext] and [includePrivate]. |
+ */ |
+ static void _getCompletionsHelper(ClassMirror classMirror, |
+ bool staticContext, LibraryMirror libraryMirror, Set<String> output) { |
+ bool includePrivate = libraryMirror == classMirror.owner; |
+ classMirror.declarations.forEach((symbol, declaration) { |
+ if (!includePrivate && declaration.isPrivate) return; |
+ if (declaration is VariableMirror) { |
+ if (staticContext != declaration.isStatic) return; |
+ } else if (declaration is MethodMirror) { |
+ if (declaration.isOperator) return; |
+ if (declaration.isConstructor) { |
+ if (!staticContext) return; |
+ var name = MirrorSystem.getName(declaration.constructorName); |
+ if (name.isNotEmpty) output.add(name); |
+ return; |
+ } |
+ if (staticContext != declaration.isStatic) return; |
+ } else if (declaration is TypeMirror) { |
+ return; |
+ } |
+ output.add(_getShortSymbolName(symbol, declaration)); |
+ }); |
+ |
+ if (!staticContext) { |
+ for (var interface in classMirror.superinterfaces) { |
+ _getCompletionsHelper(interface, staticContext, |
+ libraryMirror, output); |
+ } |
+ if (classMirror.superclass != null) { |
+ _getCompletionsHelper(classMirror.superclass, staticContext, |
+ libraryMirror, output); |
+ } |
+ } |
+ } |
+ |
+ static void _getLibraryCompletionsHelper( |
+ LibraryMirror library, bool includePrivate, Set<String> output) { |
+ library.declarations.forEach((symbol, declaration) { |
+ if (!includePrivate && declaration.isPrivate) return; |
+ output.add(_getShortSymbolName(symbol, declaration)); |
+ }); |
+ } |
+ |
+ static LibraryMirror getLibraryMirror(String url) => |
+ currentMirrorSystem().libraries[Uri.parse(url)]; |
+ |
+ /** |
+ * Get code completions for [o] only showing privates from [libraryUrl]. |
+ */ |
+ static List<String> getObjectCompletions(o, String libraryUrl) { |
+ var classMirror; |
+ bool staticContext; |
+ if (o is Type) { |
+ classMirror = reflectClass(o); |
+ staticContext = true; |
+ } else { |
+ classMirror = reflect(o).type; |
+ staticContext = false; |
+ } |
+ var names = new Set<String>(); |
+ getClassCompletions(classMirror, names, staticContext, libraryUrl); |
+ return names.toList()..sort(); |
+ } |
+ |
+ static void getClassCompletions(ClassMirror classMirror, Set<String> names, |
+ bool staticContext, String libraryUrl) { |
+ LibraryMirror libraryMirror = getLibraryMirror(libraryUrl); |
+ _getCompletionsHelper(classMirror, staticContext, libraryMirror, names); |
+ } |
+ |
+ static List<String> getLibraryCompletions(String url) { |
+ var names = new Set<String>(); |
+ _getLibraryCompletionsHelper(getLibraryMirror(url), true, names); |
+ return names.toList(); |
+ } |
+ |
/** |
- * TODO(jacobr): this is a big hack to get around the fact that we are still |
- * passing some JS expression to the evaluate method even when in a Dart |
- * context. |
+ * Get valid code completitions from within a library and all libraries |
+ * imported by that library. |
*/ |
- static bool isJsExpression(String expression) => |
- expression.startsWith("(function getCompletions"); |
+ static List<String> getLibraryCompletionsIncludingImports(String url) { |
+ var names = new Set<String>(); |
+ var libraryMirror = getLibraryMirror(url); |
+ _getLibraryCompletionsHelper(libraryMirror, true, names); |
+ for (var dependency in libraryMirror.libraryDependencies) { |
+ if (dependency.isImport) { |
+ if (dependency.prefix == null) { |
+ _getLibraryCompletionsHelper(dependency.targetLibrary, false, names); |
+ } else { |
+ names.add(MirrorSystem.getName(dependency.prefix)); |
+ } |
+ } |
+ } |
+ return names.toList(); |
+ } |
+ static final SIDE_EFFECT_FREE_LIBRARIES = new Set<String>() |
+ ..add('dart:html') |
+ ..add('dart:indexed_db') |
+ ..add('dart:svg') |
+ ..add('dart:typed_data') |
+ ..add('dart:web_audio') |
+ ..add('dart:web_gl') |
+ ..add('dart:web_sql'); |
+ /** |
+ * For parity with the JavaScript debugger, we treat some getters as if |
+ * they are fields so that users can see their values immediately. |
+ * This matches JavaScript's behavior for getters on DOM objects. |
+ * In the future we should consider adding an annotation to tag getters |
+ * in user libraries as side effect free. |
+ */ |
+ static bool _isSideEffectFreeGetter(MethodMirror methodMirror) { |
+ var owner = methodMirror.owner; |
+ var libraryMirror; |
+ if (owner is ClassMirror) { |
+ libraryMirror = owner.owner; |
+ } else if (owner is LibraryMirror) { |
+ libraryMirror = owner; |
+ } |
+ if (libraryMirror != null) { |
+ // This matches JavaScript behavior. We should consider displaying |
+ // getters for all dart platform libraries rather than just the DOM |
+ // libraries. |
+ if (libraryMirror.uri.scheme == 'dart' && |
+ SIDE_EFFECT_FREE_LIBRARIES.contains(libraryMirror.uri.toString())) { |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
/** |
- * Returns a list of completions to use if the receiver is o. |
+ * Whether we should treat a property as a field for the purposes of the |
+ * debugger. |
*/ |
- static List<String> getCompletions(o) { |
- MirrorSystem system = currentMirrorSystem(); |
- var completions = new Set<String>(); |
- addAll(Map<Symbol, dynamic> map, bool isStatic) { |
- map.forEach((symbol, mirror) { |
- if (mirror.isStatic == isStatic && !mirror.isPrivate) { |
+ static bool treatPropertyAsField(MethodMirror methodMirror) => |
+ (methodMirror.isGetter || methodMirror.isSetter) && |
+ (methodMirror.isSynthetic || _isSideEffectFreeGetter(methodMirror)); |
+ |
+ // TODO(jacobr): generate more concise function descriptions instead of |
+ // dumping the entire function source. |
+ static String describeFunction(function) { |
+ if (function is _Trampoline) return function._methodMirror.source; |
+ try { |
+ return reflect(function).function.source; |
+ } catch (e) { |
+ return function.toString(); |
+ } |
+ } |
+ |
+ static List getInvocationTrampolineDetails(_Trampoline method) { |
+ var loc = method._methodMirror.location; |
+ return [loc.line, loc.column, loc.sourceUri.toString(), |
+ MirrorSystem.getName(method._selector)]; |
+ } |
+ |
+ static List getLibraryProperties(String libraryUrl, bool ownProperties, |
+ bool accessorPropertiesOnly) { |
+ var properties = new Map<String, _Property>(); |
+ var libraryMirror = getLibraryMirror(libraryUrl); |
+ _addInstanceMirrors(libraryMirror, libraryMirror, |
+ libraryMirror.declarations, |
+ ownProperties, accessorPropertiesOnly, false, false, |
+ properties); |
+ if (!accessorPropertiesOnly) { |
+ // We need to add class properties for all classes in the library. |
+ libraryMirror.declarations.forEach((symbol, declarationMirror) { |
+ if (declarationMirror is ClassMirror) { |
var name = MirrorSystem.getName(symbol); |
- if (mirror is MethodMirror && mirror.isSetter) |
- name = name.substring(0, name.length - 1); |
- completions.add(name); |
+ if (declarationMirror.hasReflectedType |
+ && !properties.containsKey(name)) { |
+ properties[name] = new _Property(name) |
+ ..value = declarationMirror.reflectedType; |
+ } |
} |
}); |
} |
+ return packageProperties(properties); |
+ } |
+ |
+ static List getObjectProperties(o, bool ownProperties, |
+ bool accessorPropertiesOnly) { |
+ var properties = new Map<String, _Property>(); |
+ var names = new Set<String>(); |
+ var objectMirror = reflect(o); |
+ var classMirror = objectMirror.type; |
+ _addInstanceMirrors(objectMirror, classMirror.owner, |
+ classMirror.instanceMembers, |
+ ownProperties, accessorPropertiesOnly, false, true, |
+ properties); |
+ return packageProperties(properties); |
+ } |
+ |
+ static List getObjectClassProperties(o, bool ownProperties, |
+ bool accessorPropertiesOnly) { |
+ var properties = new Map<String, _Property>(); |
+ var objectMirror = reflect(o); |
+ var classMirror = objectMirror.type; |
+ _addInstanceMirrors(objectMirror, classMirror.owner, |
+ classMirror.instanceMembers, |
+ ownProperties, accessorPropertiesOnly, true, false, |
+ properties); |
+ _addStatics(classMirror, properties, accessorPropertiesOnly); |
+ return packageProperties(properties); |
+ } |
+ |
+ static List getClassProperties(Type t, bool ownProperties, |
+ bool accessorPropertiesOnly) { |
+ var properties = new Map<String, _Property>(); |
+ var classMirror = reflectClass(t); |
+ _addStatics(classMirror, properties, accessorPropertiesOnly); |
+ return packageProperties(properties); |
+ } |
- addForClass(ClassMirror mirror, bool isStatic) { |
- if (mirror == null) |
+ static void _addStatics(ClassMirror classMirror, |
+ Map<String, _Property> properties, |
+ bool accessorPropertiesOnly) { |
+ classMirror.declarations.forEach((symbol, declaration) { |
+ var name = _getShortSymbolName(symbol, declaration); |
+ if (declaration is VariableMirror) { |
+ if (accessorPropertiesOnly) return; |
+ if (!declaration.isStatic) return; |
+ properties.putIfAbsent(name, () => new _Property(name)) |
+ ..value = classMirror.getField(symbol).reflectee |
+ ..writable = declaration.isFinal && !declaration.isConst; |
+ } else if (declaration is MethodMirror) { |
+ MethodMirror methodMirror = declaration; |
+ // FIXMEDART: should we display constructors? |
+ if (methodMirror.isConstructor) return; |
+ if (!methodMirror.isStatic) return; |
+ if (accessorPropertiesOnly) { |
+ if (methodMirror.isRegularMethod || |
+ treatPropertyAsField(methodMirror)) { |
+ return; |
+ } |
+ } else if (!methodMirror.isRegularMethod && |
+ !treatPropertyAsField(methodMirror)) { |
+ return; |
+ } |
+ var property = properties.putIfAbsent(name, () => new _Property(name)); |
+ if (methodMirror.isRegularMethod) { |
+ property |
+ ..value = new _MethodTrampoline(classMirror, methodMirror, symbol) |
+ ..isMethod = true; |
+ } else if (methodMirror.isGetter) { |
+ if (treatPropertyAsField(methodMirror)) { |
+ try { |
+ property.value = classMirror.getField(symbol).reflectee; |
+ } catch (e) { |
+ property |
+ ..wasThrown = true |
+ ..value = e; |
+ } |
+ } else if (accessorPropertiesOnly) { |
+ property.getter = new _GetterTrampoline(classMirror, |
+ methodMirror, symbol); |
+ } |
+ } else if (methodMirror.isSetter) { |
+ if (accessorPropertiesOnly && !treatPropertyAsField(methodMirror)) { |
+ property.setter = new _SetterTrampoline(classMirror, |
+ methodMirror, classMirror.owner); |
+ } |
+ property.writable = true; |
+ } |
+ } |
+ }); |
+ } |
+ |
+ /** |
+ * Helper method that handles collecting up properties from classes |
+ * or libraries using the filters [ownProperties], [accessorPropertiesOnly], |
+ * [hideFields], and [hideMethods] to determine which properties are |
+ * collected. [accessorPropertiesOnly] specifies whether all properties |
+ * should be returned or just accessors. [hideFields] specifies whether |
+ * fields should be hidden. hideMethods specifies whether methods should be |
+ * shown or hidden. [ownProperties] is not currently used but is part of the |
+ * Blink devtools API for enumerating properties. |
+ */ |
+ static void _addInstanceMirrors( |
+ ObjectMirror objectMirror, |
+ LibraryMirror libraryMirror, |
+ Map<Symbol, Mirror> declarations, |
+ bool ownProperties, bool accessorPropertiesOnly, |
+ bool hideFields, bool hideMethods, |
+ Map<String, _Property> properties) { |
+ declarations.forEach((Symbol symbol, Mirror declaration) { |
+ if (declaration is TypedefMirror || declaration is ClassMirror) return; |
+ var name = _getShortSymbolName(symbol, declaration); |
+ bool isField = declaration is VariableMirror || |
+ (declaration is MethodMirror && treatPropertyAsField(declaration)); |
+ if ((isField && hideFields) || (hideMethods && !isField)) return; |
+ if (accessorPropertiesOnly) { |
+ if (declaration is ClassMirror || declaration is VariableMirror || |
+ declaration.isRegularMethod || isField) { |
+ return; |
+ } |
+ } else if (declaration is MethodMirror && |
+ (declaration.isGetter || declaration.isSetter) && |
+ !treatPropertyAsField(declaration)) { |
return; |
- addAll(mirror.declarations, isStatic); |
- if (mirror.superclass != null) |
- addForClass(mirror.superclass, isStatic); |
- for (var interface in mirror.superinterfaces) { |
- addForClass(interface, isStatic); |
} |
- } |
+ var property = properties.putIfAbsent(name, () => new _Property(name)); |
+ if (declaration is VariableMirror) { |
+ property.value = objectMirror.getField(symbol).reflectee; |
+ property.writable = !declaration.isFinal && !declaration.isConst; |
+ } else if (declaration is ClassMirror) { |
+ property.value = declaration.runtimeType; |
+ } else if (declaration.isRegularMethod) { |
+ property.value = new _MethodTrampoline(objectMirror, |
+ declaration, symbol); |
+ property.isMethod = true; |
+ } else if (declaration.isGetter) { |
+ if (treatPropertyAsField(declaration)) { |
+ try { |
+ property.value = objectMirror.getField(symbol).reflectee; |
+ } catch (e) { |
+ property |
+ ..wasThrown = true |
+ ..value = e; |
+ } |
+ } else if (accessorPropertiesOnly) { |
+ property.getter = new _GetterTrampoline(objectMirror, |
+ declaration, symbol); |
+ } |
+ } else if (declaration.isSetter) { |
+ property.writable = true; |
+ if (accessorPropertiesOnly && !treatPropertyAsField(declaration)) { |
+ property.setter = new _SetterTrampoline(objectMirror, |
+ declaration, MirrorSystem.getSymbol(name, libraryMirror)); |
+ } |
+ } |
+ }); |
+ } |
- if (o is Type) { |
- addForClass(reflectClass(o), true); |
- } else { |
- addForClass(reflect(o).type, false); |
+ /** |
+ * Flatten down the properties data structure into a List that is easy to |
+ * access from native code. |
+ */ |
+ static List packageProperties(Map<String, _Property> properties) { |
+ var ret = []; |
+ for (var property in properties.values) { |
+ ret.addAll([property.name, |
+ property.setter, |
+ property.getter, |
+ property.value, |
+ property.hasValue, |
+ property.writable, |
+ property.isMethod, |
+ property.isOwn, |
+ property.wasThrown]); |
+ } |
+ return ret; |
+ } |
+ |
+ /** |
+ * Get a property, returning null if the property does not exist. |
+ * For private property names, we attempt to resolve the property in the |
+ * context of each library that the property name could be associated with. |
+ */ |
+ static getObjectPropertySafe(o, String propertyName) { |
+ var objectMirror = reflect(o); |
+ var classMirror = objectMirror.type; |
+ if (propertyName.startsWith("_")) { |
+ var attemptedLibraries = new Set<LibraryMirror>(); |
+ while (classMirror != null) { |
+ LibraryMirror library = classMirror.owner; |
+ if (!attemptedLibraries.contains(library)) { |
+ try { |
+ return objectMirror.getField( |
+ MirrorSystem.getSymbol(propertyName, library)).reflectee; |
+ } catch (e) { } |
+ attemptedLibraries.add(library); |
+ } |
+ classMirror = classMirror.superclass; |
+ } |
+ return null; |
+ } |
+ try { |
+ return objectMirror.getField( |
+ MirrorSystem.getSymbol(propertyName)).reflectee; |
+ } catch (e) { |
+ return null; |
} |
- return completions.toList(growable: false); |
} |
/** |
- * Convenience helper to get the keys of a [Map] as a [List]. |
+ * Helper to wrap the inspect method on InjectedScriptHost to provide the |
+ * inspect method required for the |
*/ |
+ static List consoleApi(host) { |
+ return [ |
+ "inspect", |
+ (o) { |
+ host.inspect(o, null); |
+ return o; |
+ }, |
+ "dir", |
+ window().console.dir, |
+ "dirxml", |
+ window().console.dirxml |
+ // FIXME: add copy method. |
+ ]; |
+ } |
+ |
static List getMapKeyList(Map map) => map.keys.toList(); |
/** |