Index: pkg/dev_compiler/tool/input_sdk/private/debugger.dart |
diff --git a/pkg/dev_compiler/tool/input_sdk/private/debugger.dart b/pkg/dev_compiler/tool/input_sdk/private/debugger.dart |
index a6bc9639f2b7b9205f002c83db3fce2bd4828552..7fbc2deb39f65d19eacbff0c57e7d69bfb93cf87 100644 |
--- a/pkg/dev_compiler/tool/input_sdk/private/debugger.dart |
+++ b/pkg/dev_compiler/tool/input_sdk/private/debugger.dart |
@@ -53,11 +53,68 @@ class JSNative { |
void addMetadataChildren(object, Set<NameValuePair> ret) { |
ret.add(new NameValuePair( |
- name: getTypeName(_getType(object)), |
- value: object, |
+ name: "[[class]]", |
+ value: dart.getReifiedType(object), |
config: JsonMLConfig.asClass)); |
} |
+/// Add properties from a signature definition [sig] for [object]. |
+/// Walk the prototype chain if [walkProtypeChain] is set. |
+/// Tag types on function typed properties of [object] if [tagTypes] is set. |
+/// |
+void addPropertiesFromSignature( |
+ sig, Set<NameValuePair> properties, object, bool walkPrototypeChain, |
+ {tagTypes: false}) { |
+ // Including these property names doesn't add any value and just clutters |
+ // the debugger output. |
+ // TODO(jacobr): consider adding runtimeType to this list. |
+ var skippedNames = new Set()..add('hashCode'); |
+ |
+ while (sig != null) { |
+ for (var symbol in getOwnPropertySymbols(sig)) { |
+ var dartName = symbolName(symbol); |
+ String dartXPrefix = 'dartx.'; |
+ if (dartName.startsWith(dartXPrefix)) { |
+ dartName = dartName.substring(dartXPrefix.length); |
+ } |
+ if (skippedNames.contains(dartName)) continue; |
+ var value = safeGetProperty(object, symbol); |
+ // Tag the function with its runtime type. |
+ if (tagTypes && _typeof(value) == 'function') { |
+ dart.tag(value, JS('', '#[#]', sig, symbol)); |
+ } |
+ properties.add(new NameValuePair(name: dartName, value: value)); |
+ } |
+ |
+ for (var name in getOwnPropertyNames(sig)) { |
+ var value = safeGetProperty(object, name); |
+ if (skippedNames.contains(name)) continue; |
+ // Tag the function with its runtime type. |
+ if (tagTypes && _typeof(value) == 'function') { |
+ dart.tag(value, JS('', '#[#]', sig, name)); |
+ } |
+ properties.add(new NameValuePair(name: name, value: value)); |
+ } |
+ |
+ if (!walkPrototypeChain) break; |
+ |
+ sig = safeGetProperty(sig, '__proto__'); |
+ } |
+} |
+ |
+/// Sort properties sorting public names before private names. |
+List<NameValuePair> sortProperties(Iterable<NameValuePair> properties) { |
+ var sortedProperties = properties.toList(); |
+ |
+ sortedProperties.sort((a, b) { |
+ var aPrivate = a.name.startsWith('_'); |
+ var bPrivate = b.name.startsWith('_'); |
+ if (aPrivate != bPrivate) return aPrivate ? 1 : -1; |
+ return a.name.compareTo(b.name); |
+ }); |
+ return sortedProperties; |
+} |
+ |
String getObjectTypeName(object) { |
var reifiedType = dart.getReifiedType(object); |
if (reifiedType == null) { |
@@ -69,7 +126,7 @@ String getObjectTypeName(object) { |
return getTypeName(reifiedType); |
} |
-String getTypeName(Type type) { |
+String getTypeName(type) { |
var name = dart.typeName(type); |
// Hack to cleanup names for List<dynamic> |
// TODO(jacobr): it would be nice if there was a way we could distinguish |
@@ -80,9 +137,6 @@ String getTypeName(Type type) { |
return name; |
} |
-Object _getType(object) => |
- object is Type ? object : dart.getReifiedType(object); |
- |
String safePreview(object, config) { |
try { |
var preview = _devtoolsFormatter._simpleFormatter.preview(object, config); |
@@ -117,7 +171,12 @@ class NameValuePair { |
// Define equality and hashCode so that NameValuePair can be used |
// in a Set to dedupe entries with duplicate names. |
- operator ==(other) => other is NameValuePair && other.name == name; |
+ bool operator ==(other) { |
+ if (other is! NameValuePair) return false; |
+ if (this.hideName || other.hideName) return identical(this, other); |
+ return other.name == name; |
+ } |
+ |
int get hashCode => name.hashCode; |
final String name; |
@@ -269,7 +328,7 @@ bool isNativeJavaScriptObject(object) { |
// Treat Node objects as a native JavaScript type as the regular DOM render |
// in devtools is superior to the dart specific view. |
return (type != 'object' && type != 'function') || |
- object is dart.JSObject || |
+ dart.isJsInterop(object) || |
object is html.Node; |
} |
@@ -328,27 +387,41 @@ class JsonMLFormatter { |
body.addStyle('color: rgb(196, 26, 22);'); |
} |
var children = _simpleFormatter.children(object, config); |
+ if (children == null) return body.toJsonML(); |
for (NameValuePair child in children) { |
var li = body.createChild('li'); |
- var nameSpan = new JsonMLElement('span') |
- ..createTextChild( |
- child.displayName.isNotEmpty ? '${child.displayName}: ' : '') |
- ..setStyle('color: rgb(136, 19, 145);'); |
+ li.setStyle("padding-left: 13px;"); |
+ |
+ // The value is indented when it is on a different line from the name |
+ // by setting right padding of the name to -13px and the padding of the |
+ // value to 13px. |
+ JsonMLElement nameSpan; |
+ var valueStyle = ''; |
+ if (!child.hideName) { |
+ nameSpan = new JsonMLElement('span') |
+ ..createTextChild( |
+ child.displayName.isNotEmpty ? '${child.displayName}: ' : '') |
+ ..setStyle('color: rgb(136, 19, 145); margin-right: -13px'); |
+ valueStyle = 'margin-left: 13px'; |
+ } |
+ |
if (_typeof(child.value) == 'object' || |
_typeof(child.value) == 'function') { |
- nameSpan.addStyle("padding-left: 13px;"); |
- |
- li.appendChild(nameSpan); |
- var objectTag = li.createObjectTag(child.value); |
- objectTag.addAttribute('config', child.config); |
- if (!_simpleFormatter.hasChildren(child.value, child.config)) { |
- li.setStyle("padding-left: 13px;"); |
+ var valueSpan = new JsonMLElement('span')..setStyle(valueStyle); |
+ valueSpan.createObjectTag(child.value) |
+ ..addAttribute('config', child.config); |
+ if (nameSpan != null) { |
+ li.appendChild(nameSpan); |
} |
+ li.appendChild(valueSpan); |
} else { |
- li.setStyle("padding-left: 13px;"); |
- li.createChild('span') |
- ..appendChild(nameSpan) |
- ..createTextChild(safePreview(child.value, child.config)); |
+ var line = li.createChild('span'); |
+ if (nameSpan != null) { |
+ line.appendChild(nameSpan); |
+ } |
+ line.appendChild(new JsonMLElement('span') |
+ ..createTextChild(safePreview(child.value, child.config)) |
+ ..setStyle(valueStyle)); |
} |
} |
return body.toJsonML(); |
@@ -370,6 +443,7 @@ class DartFormatter { |
// precedence. |
_formatters = [ |
new ClassFormatter(), |
+ new TypeFormatter(), |
new NamedConstructorFormatter(), |
new MapFormatter(), |
new IterableFormatter(), |
@@ -437,10 +511,6 @@ class DartFormatter { |
/// Default formatter for Dart Objects. |
class ObjectFormatter extends Formatter { |
- static Set<String> _customNames = new Set() |
- ..add('constructor') |
- ..add('prototype') |
- ..add('__proto__'); |
bool accept(object, config) => !isNativeJavaScriptObject(object); |
String preview(object) => getObjectTypeName(object); |
@@ -448,76 +518,34 @@ class ObjectFormatter extends Formatter { |
bool hasChildren(object) => true; |
List<NameValuePair> children(object) { |
- var properties = new LinkedHashSet<NameValuePair>(); |
- // Set of property names used to avoid duplicates. |
- addMetadataChildren(object, properties); |
- |
- var current = object; |
- |
- var protoChain = <Object>[]; |
- while (current != null && |
- !isNativeJavaScriptObject(current) && |
- JS("bool", "# !== Object.prototype", current)) { |
- protoChain.add(current); |
- current = safeGetProperty(current, '__proto__'); |
- } |
- |
- // We walk the prototype chain for symbol properties because they take |
- // priority and are accessed instead of Dart properties according to Dart |
- // calling conventions. |
- // TODO(jacobr): where possible use the data stored by dart.setSignature |
- // instead of walking the JavaScript object directly. |
- for (current in protoChain) { |
- for (var symbol in getOwnPropertySymbols(current)) { |
- var dartName = symbolName(symbol); |
- if (hasMethod(object, dartName)) { |
- continue; |
- } |
- // TODO(jacobr): find a cleaner solution than checking for dartx |
- String dartXPrefix = 'dartx.'; |
- if (dartName.startsWith(dartXPrefix)) { |
- dartName = dartName.substring(dartXPrefix.length); |
- } else if (!dartName.startsWith('_')) { |
- // Dart method extension names should either be from dartx or should |
- // start with an _ |
- continue; |
- } |
- var value = safeGetProperty(object, symbol); |
- properties.add(new NameValuePair(name: dartName, value: value)); |
- } |
- } |
- |
- for (current in protoChain) { |
- // TODO(jacobr): optionally distinguish properties and fields so that |
- // it is safe to expand untrusted objects without side effects. |
- var className = dart.getReifiedType(current).name; |
- for (var name in getOwnPropertyNames(current)) { |
- if (_customNames.contains(name) || name == className) continue; |
- if (hasMethod(object, name)) { |
- continue; |
- } |
- var value = safeGetProperty(object, name); |
- properties.add(new NameValuePair(name: name, value: value)); |
- } |
- } |
- |
- return properties.toList(); |
+ var type = dart.getType(object); |
+ var ret = new LinkedHashSet<NameValuePair>(); |
+ // We use a Set rather than a List to avoid duplicates. |
+ var properties = new Set<NameValuePair>(); |
+ addPropertiesFromSignature( |
+ dart.getFieldSig(type), properties, object, true); |
+ addPropertiesFromSignature( |
+ dart.getGetterSig(type), properties, object, true); |
+ ret.addAll(sortProperties(properties)); |
+ addMetadataChildren(object, ret); |
+ return ret.toList(); |
} |
} |
/// Formatter for module Dart Library objects. |
class LibraryModuleFormatter implements Formatter { |
- accept(object, config) => dart.getDartLibraryName(object) != null; |
+ accept(object, config) => dart.getModuleName(object) != null; |
bool hasChildren(object) => true; |
String preview(object) { |
- var libraryNames = dart.getDartLibraryName(object).split('/'); |
+ var libraryNames = dart.getModuleName(object).split('/'); |
// Library names are received with a repeat directory name, so strip the |
// last directory entry here to make the path cleaner. For example, the |
// library "third_party/dart/utf/utf" shoud display as |
// "third_party/dart/utf/". |
- if (libraryNames.length > 1) { |
+ if (libraryNames.length > 1 && |
+ libraryNames.last == libraryNames[libraryNames.length - 2]) { |
libraryNames[libraryNames.length - 1] = ''; |
} |
return 'Library Module: ${libraryNames.join('/')}'; |
@@ -527,9 +555,6 @@ class LibraryModuleFormatter implements Formatter { |
var children = new LinkedHashSet<NameValuePair>(); |
for (var name in getOwnPropertyNames(object)) { |
var value = safeGetProperty(object, name); |
- // Replace __ with / to make file paths more readable. Then |
- // 'src__result__error' becomes 'src/result/error'. |
- name = '${name.replaceAll("__", "/")}.dart'; |
children.add(new NameValuePair( |
name: name, value: new Library(name, value), hideName: true)); |
} |
@@ -547,51 +572,26 @@ class LibraryFormatter implements Formatter { |
String preview(object) => object.name; |
List<NameValuePair> children(object) { |
+ // Maintain library member order rather than sorting members as is the |
+ // case for class members. |
var children = new LinkedHashSet<NameValuePair>(); |
- var nonGenericProperties = new LinkedHashMap<String, Object>(); |
var objectProperties = safeProperties(object.object); |
objectProperties.forEach((name, value) { |
- var genericTypeConstructor = dart.getGenericTypeCtor(value); |
- if (genericTypeConstructor != null) { |
- recordGenericParameters(name, genericTypeConstructor); |
- } else { |
- nonGenericProperties[name] = value; |
- } |
- }); |
- nonGenericProperties.forEach((name, value) { |
- if (value is Type) { |
- children.add(classChild(name, value)); |
- } else { |
- children.add(new NameValuePair(name: name, value: value)); |
- } |
+ // Skip the generic constructors for each class as users are only |
+ // interested in seeing the actual classes. |
+ if (dart.getGenericTypeCtor(value) != null) return; |
+ |
+ children.add(dart.isType(value) |
+ ? classChild(name, value) |
+ : new NameValuePair(name: name, value: value)); |
}); |
return children.toList(); |
} |
- recordGenericParameters(String name, Object genericTypeConstructor) { |
- // Using JS toString() eliminates the leading metadata that is generated |
- // with the toString function provided in operations.dart. |
- // Splitting by => and taking the first element gives the list of |
- // arguments in the constructor. |
- genericParameters[name] = |
- JS('String', '#.toString()', genericTypeConstructor) |
- .split(' =>') |
- .first |
- .replaceAll(new RegExp(r'[(|)]'), ''); |
- } |
- |
classChild(String name, Object child) { |
var typeName = getTypeName(child); |
- // Generic class names are generated with a $ at the end, so the |
- // corresponding non-generic class can be identified by adding $. |
- var parameterName = '$name\$'; |
- if (genericParameters.keys.contains(parameterName)) { |
- typeName = '$typeName<${genericParameters[parameterName]}>'; |
- // TODO(bmilligan): Add a symbol to classes with generic types at their |
- // creation so they can be recognized independently by the debugger. |
- JSNative.setProperty(child, 'genericTypeName', typeName); |
- } |
- return new NameValuePair(name: typeName, value: child); |
+ return new NameValuePair( |
+ name: typeName, value: child, config: JsonMLConfig.asClass); |
} |
} |
@@ -770,14 +770,11 @@ class StackTraceFormatter implements Formatter { |
} |
class ClassFormatter implements Formatter { |
- accept(object, config) => object is Type || config == JsonMLConfig.asClass; |
+ accept(object, config) => config == JsonMLConfig.asClass; |
- String preview(object) { |
- var typeName = safeGetProperty(object, 'genericTypeName'); |
- if (typeName != null) return typeName; |
- var type = _getType(object); |
+ String preview(type) { |
var implements = dart.getImplements(type); |
- typeName = getTypeName(type); |
+ var typeName = getTypeName(type); |
if (implements != null) { |
var typeNames = implements().map(getTypeName); |
return '${typeName} implements ${typeNames.join(", ")}'; |
@@ -788,60 +785,86 @@ class ClassFormatter implements Formatter { |
bool hasChildren(object) => true; |
- List<NameValuePair> children(object) { |
+ List<NameValuePair> children(type) { |
// TODO(jacobr): add other entries describing the class such as |
- // links to the superclass, mixins, implemented interfaces, and methods. |
- var type = _getType(object); |
- var children = <NameValuePair>[]; |
- var typeName = getTypeName(_getType(object)); |
+ // implemented interfaces, and methods. |
+ var ret = new LinkedHashSet<NameValuePair>(); |
+ |
+ var staticProperties = new Set<NameValuePair>(); |
+ var staticMethods = new Set<NameValuePair>(); |
+ // Static fields and properties. |
+ addPropertiesFromSignature( |
+ dart.getStaticFieldSig(type), staticProperties, type, false); |
+ addPropertiesFromSignature( |
+ dart.getStaticGetterSig(type), staticProperties, type, false); |
+ // static methods. |
+ addPropertiesFromSignature( |
+ dart.getStaticSig(type), staticMethods, type, false); |
+ |
+ if (staticProperties.isNotEmpty || staticMethods.isNotEmpty) { |
+ ret |
+ ..add(new NameValuePair(value: '[[Static members]]', hideName: true)) |
+ ..addAll(sortProperties(staticProperties)) |
+ ..addAll(sortProperties(staticMethods)); |
+ } |
+ |
+ // instance methods. |
+ var instanceMethods = new Set<NameValuePair>(); |
+ // Instance methods are defined on the prototype not the constructor object. |
+ addPropertiesFromSignature(dart.getMethodSig(type), instanceMethods, |
+ JS('', '#.prototype', type), false, |
+ tagTypes: true); |
+ if (instanceMethods.isNotEmpty) { |
+ ret |
+ ..add(new NameValuePair(value: '[[Instance Methods]]', hideName: true)) |
+ ..addAll(sortProperties(instanceMethods)); |
+ } |
+ |
+ var typeName = getTypeName(type); |
var mixins = dart.getMixins(type); |
if (mixins != null && mixins.isNotEmpty) { |
- children.add(new NameValuePair( |
+ ret.add(new NameValuePair( |
name: '[[Mixins]]', value: new HeritageClause('mixins', mixins))); |
} |
- var hiddenProperties = ['length', 'name', 'prototype', 'genericTypeName']; |
- // Addition of NameValuePairs for static variables and named constructors. |
- for (var name in getOwnPropertyNames(object)) { |
- // TODO(bmilligan): Perform more principled checks to filter out spurious |
- // members. |
- if (hiddenProperties.contains(name)) continue; |
- var value = safeGetProperty(object, name); |
- if (value != null && dart.getIsNamedConstructor(value) != null) { |
- value = new NamedConstructor(value); |
- name = '${typeName}.$name'; |
- } |
- children.add(new NameValuePair(name: name, value: value)); |
+ var baseProto = JS('', '#.__proto__', type); |
+ if (baseProto != null && !dart.isJsInterop(baseProto)) { |
+ ret.add(new NameValuePair( |
+ name: "[[base class]]", |
+ value: baseProto, |
+ config: JsonMLConfig.asClass)); |
} |
- // TODO(bmilligan): Replace the hard coding of $identityHash. |
- var hiddenPrototypeProperties = ['constructor', 'new', r'$identityHash']; |
- // Addition of class methods. |
- var prototype = JS('var', '#["prototype"]', object); |
- if (prototype != null) { |
- for (var name in getOwnPropertyNames(prototype)) { |
- if (hiddenPrototypeProperties.contains(name)) continue; |
- // Simulate dart.bind by using dart.tag and tear off the function |
- // so it will be recognized by the FunctionFormatter. |
- var function = safeGetProperty(prototype, name); |
- var constructor = safeGetProperty(prototype, 'constructor'); |
- var sigObj = dart.getMethodSig(constructor); |
- if (sigObj != null) { |
- var value = safeGetProperty(sigObj, name); |
- if (getTypeName(dart.getReifiedType(value)) != 'Null') { |
- dart.tag(function, value); |
- children.add(new NameValuePair(name: name, value: function)); |
- } |
- } |
- } |
- } |
- return children; |
+ // TODO(jacobr): add back fields for named constructors. |
+ return ret.toList(); |
} |
} |
+class TypeFormatter implements Formatter { |
+ accept(object, config) => object is Type; |
+ |
+ String preview(object) => object.toString(); |
+ |
+ bool hasChildren(object) => false; |
+ |
+ List<NameValuePair> children(object) => []; |
+} |
+ |
/// This entry point is automatically invoked by the code generated by |
/// Dart Dev Compiler |
registerDevtoolsFormatter() { |
var formatters = [_devtoolsFormatter]; |
JS('', 'dart.global.devtoolsFormatters = #', formatters); |
} |
+ |
+// Expose these methods here to facilitate writing debugger tests. |
+// If export worked for private SDK libraries we could just export |
+// these methods from dart:_runtime. |
+ |
+getModuleNames() { |
+ return dart.getModuleNames(); |
+} |
+ |
+getModuleLibraries(String name) { |
+ return dart.getModuleLibraries(name); |
+} |