Index: tool/input_sdk/lib/debugger/debugger.dart |
diff --git a/tool/input_sdk/lib/debugger/debugger.dart b/tool/input_sdk/lib/debugger/debugger.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2ebf63416c4e9fe72bbe7ca630e500e2e1daac54 |
--- /dev/null |
+++ b/tool/input_sdk/lib/debugger/debugger.dart |
@@ -0,0 +1,540 @@ |
+// Copyright (c) 2015, 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. |
+ |
+library dart._debugger; |
+ |
+import 'dart:_foreign_helper' show JS; |
+import 'dart:_runtime' as dart; |
+import 'dart:core'; |
+ |
+/// Config object to pass to devtools to signal that an object should not be |
+/// formatted by the Dart formatter. This is used to specify that an Object |
skybrian
2016/01/22 21:45:21
style nit: there should be blank line after "forma
|
+/// should just be displayed using the regular JavaScript view instead of a |
+/// custom Dart view. For example, this is used to display the JavaScript view |
+/// of a Dart Function as a child of the regular Function object. |
+const SKIP_DART_CONFIG = const Object(); |
Jennifer Messerly
2016/01/22 19:52:16
nit, these should be lowerCamelCase in new code
ht
Jacob
2016/01/22 21:28:53
Done.
|
+final int MAX_ITERABLE_CHILDREN_TO_DISPLAY = 50; |
+ |
+var _devtoolsFormatter = new JsonMLFormatter(new DartFormatter()); |
+ |
+String typeof(object) => JS('String', 'typeof #', object); |
+bool instanceof(object, clazz) => JS('bool', '# instanceof #', object, clazz); |
+ |
+List<String> getOwnPropertyNames(object) => |
+ dart.list(JS('List', 'Object.getOwnPropertyNames(#)', object), String); |
+ |
+List getOwnPropertySymbols(object) => |
+ JS('List', 'Object.getOwnPropertySymbols(#)', object); |
+ |
+// TODO(jacobr): move this to dart:js and fully implement. |
+class JSNative { |
+ // Name may be a String or a Symbol. |
+ static getProperty(object, name) => JS('', '#[#]', object, name); |
+ // Name may be a String or a Symbol. |
+ static setProperty(object, name, value) => |
+ JS('', '#[#]=#', object, name, value); |
+} |
+ |
+bool isRegularDartObject(object) { |
+ if (typeof(object) == 'function') return false; |
+ return instanceof(object, Object); |
+} |
+ |
+String getObjectTypeName(object) { |
+ var realRuntimeType = dart.realRuntimeType(object); |
+ if (realRuntimeType == null) { |
+ if (typeof(object) == 'function') { |
+ return '[[Raw JavaScript Function]]'; |
+ } |
+ return '<Error getting type name>'; |
+ } |
+ return getTypeName(realRuntimeType); |
+} |
+ |
+String getTypeName(Type 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 |
+ // between a List<dynamic> created from Dart and an Array passed in from |
+ // JavaScript. |
+ if (name == 'JSArray<dynamic>' || |
+ name == 'JSObject<Array>') return 'List<dynamic>'; |
+ return name; |
+} |
+ |
+String safePreview(object) { |
+ try { |
+ var preview = _devtoolsFormatter._simpleFormatter.preview(object); |
+ if (preview != null) return preview; |
+ return object.toString(); |
+ } catch (e) { |
+ return '<Exception thrown>'; |
+ } |
+} |
+ |
+String symbolName(symbol) { |
+ var name = symbol.toString(); |
+ assert(name.startsWith('Symbol(')); |
+ return name.substring('Symbol('.length, name.length - 1); |
+} |
+ |
+bool hasMethod(object, String name) { |
+ try { |
+ return dart.hasMethod(object, name); |
+ } catch (e) { |
+ return false; |
Jennifer Messerly
2016/01/22 19:52:17
Just curious, when does this throw? Do we have a b
Jacob
2016/01/22 21:28:53
as I recall it throws if you ask whether 'construc
|
+ } |
+} |
+ |
+/// [JsonMLFormatter] consumes [NameValuePair] objects and |
skybrian
2016/01/22 21:45:21
looks like you're missing something after "and"
|
+class NameValuePair { |
+ NameValuePair({this.name, this.value, skipDart}) { |
+ this.skipDart = skipDart == true; |
Jennifer Messerly
2016/01/22 19:52:17
btw, this can be an initializer:
: skipDart = ski
Jacob
2016/01/22 21:28:53
Done.
|
+ } |
+ |
+ String name; |
Jennifer Messerly
2016/01/22 19:52:16
can these be final?
Jacob
2016/01/22 21:28:53
yep. heads up this code was ported from JS with a
|
+ var value; |
Jennifer Messerly
2016/01/22 19:52:17
Object value?
Jacob
2016/01/22 21:28:53
Done.
|
+ bool skipDart; |
+} |
+ |
+class MapEntry { |
+ MapEntry({this.key, this.value}); |
+ |
+ String key; |
+ var value; |
+} |
+ |
+class ClassMetadata { |
+ ClassMetadata(this.object); |
+ |
+ var object; |
+} |
+ |
+class HeritageClause { |
+ HeritageClause(this.name, this.types); |
+ |
+ String name; |
+ List types; |
+} |
+ |
+/// Class to simplify building the JsonML objects expected by the |
+/// Devtools Formatter API. |
+class JsonMLElement { |
+ dynamic _attributes; |
+ List _jsonML; |
+ |
+ JsonMLElement(tagName) { |
+ _attributes = JS('', '{}'); |
+ _jsonML = [tagName, _attributes]; |
+ } |
+ |
+ appendChild(element) { |
+ _jsonML.add(element.toJsonML()); |
+ } |
+ |
+ JsonMLElement createChild(String tagName) { |
+ var c = new JsonMLElement(tagName); |
+ _jsonML.add(c.toJsonML()); |
+ return c; |
+ } |
+ |
+ JsonMLElement createObjectTag(object) => |
+ createChild('object')..addAttribute('object', object); |
+ |
+ void setStyle(String style) { |
+ _attributes.style = style; |
+ } |
+ |
+ addStyle(String style) { |
+ if (_attributes.style == null) { |
+ _attributes.style = style; |
+ } else { |
+ _attributes.style += style; |
+ } |
+ } |
+ |
+ addAttribute(key, value) { |
+ JSNative.setProperty(_attributes, key, value); |
+ } |
+ |
+ createTextChild(String text) { |
+ _jsonML.add(text); |
+ } |
+ |
+ toJsonML() => _jsonML; |
+} |
+ |
+/// Class implementing the Devtools Formatter API described by: |
+/// https://docs.google.com/document/d/1FTascZXT9cxfetuPRT2eXPQKXui4nWFivUnS_335T3U |
Jennifer Messerly
2016/01/22 19:52:17
I couldn't access this document. Wrong link?
Jacob
2016/01/22 21:28:53
I assume you accidentally had spaces due to 80 cha
Jennifer Messerly
2016/01/22 22:11:27
Oh, yes :). Thanks, Rietveld.
|
+/// Specifically, a formatter implements a header, hasBody, and body method. |
+/// This class renders the simple structured format objects [_simpleFormatter] |
+/// provides as JsonML. |
+class JsonMLFormatter { |
+ // TODO(jacobr): define a SimpleFormatter base class that DartFormatter |
+ // implements if we decide to use this class elsewhere. We specify that the |
+ // type is DartFormatter here purely to get type checking benefits not because |
+ // this class is really intended to only support instances of type |
+ // DartFormatter. |
+ DartFormatter _simpleFormatter; |
+ |
+ JsonMLFormatter(this._simpleFormatter); |
+ |
+ header(object, config) { |
+ if (identical(config, SKIP_DART_CONFIG)) return null; |
+ |
+ var c = _simpleFormatter.preview(object); |
+ if (c == null) return null; |
+ |
+ // Indicate this is a Dart Object by using a Dart background color. |
+ // This is stylistically a bit ugly but it eases distinguishing Dart and |
+ // JS objects. |
+ var element = new JsonMLElement('span') |
+ ..setStyle('background-color: #d9edf7') |
+ ..createTextChild(c); |
+ return element.toJsonML(); |
+ } |
+ |
+ bool hasBody(object) => _simpleFormatter.hasChildren(object); |
+ |
+ body(object) { |
+ var body = new JsonMLElement('ol') |
+ ..setStyle('list-style-type:none;' |
Jennifer Messerly
2016/01/22 19:52:16
trivial, noticed there is no space after ":" here
Jacob
2016/01/22 21:28:53
why doesn't dart formatter fix my CSS style???? :)
|
+ 'padding-left: 0px;' |
+ 'margin-top: 0px;' |
+ 'margin-bottom: 0px;' |
+ 'margin-left: 12px'); |
+ var children = _simpleFormatter.children(object); |
+ for (NameValuePair child in children) { |
+ var li = body.createChild('li'); |
+ var nameSpan = new JsonMLElement('span') |
+ ..createTextChild(child.name != null ? child.name + ': ' : '') |
+ ..setStyle('color: rgb(136, 19, 145);'); |
+ if (typeof(child.value) == 'object' || |
+ typeof(child.value) == 'function') { |
+ nameSpan.addStyle("padding-left: 13px;"); |
+ |
+ li.appendChild(nameSpan); |
+ var objectTag = li.createObjectTag(child.value); |
+ if (child.skipDart) { |
+ objectTag.addAttribute('config', SKIP_DART_CONFIG); |
+ } |
+ if (!_simpleFormatter.hasChildren(child.value)) { |
+ li.setStyle("padding-left: 13px;"); |
+ } |
+ } else { |
+ li.setStyle("padding-left: 13px;"); |
+ li.createChild('span') |
+ ..appendChild(nameSpan) |
+ ..createTextChild(safePreview(child.value)); |
+ } |
+ } |
+ return body.toJsonML(); |
+ } |
+} |
+ |
+abstract class Formatter { |
+ bool accept(object); |
+ String preview(object); |
+ bool hasChildren(object); |
+ List<NameValuePair> children(object); |
+} |
+ |
+class DartFormatter { |
+ List<Formatter> _formatters; |
+ |
+ DartFormatter() { |
+ // The order of formatters matters as formatters later in the list take |
+ // precidence. |
+ _formatters = [ |
+ new FunctionFormatter(), |
+ new MapFormatter(), |
+ new IterableFormatter(), |
+ new MapEntryFormatter(), |
+ new ClassMetadataFormatter(), |
+ new HeritageClauseFormatter(), |
+ new ObjectFormatter() |
+ ]; |
+ } |
+ |
+ String preview(object) { |
+ if (object == null) return 'null'; |
+ if (object is num) return object.toString(); |
+ if (object is String) return '"$object"'; |
+ |
+ for (var formatter in _formatters) { |
+ if (formatter.accept(object)) return formatter.preview(object); |
+ } |
+ |
+ return null; |
+ } |
+ |
+ bool hasChildren(object) { |
+ if (object == null) return false; |
+ |
+ for (var formatter in _formatters) { |
+ if (formatter.accept(object)) return formatter.hasChildren(object); |
+ } |
+ |
+ return false; |
+ } |
+ |
+ List<NameValuePair> children(object) { |
+ if (object != null) { |
+ for (var formatter in _formatters) { |
+ if (formatter.accept(object)) return formatter.children(object); |
+ } |
+ } |
+ return <NameValuePair>[]; |
+ } |
+} |
+ |
+/// Default formatter for Dart Objects. |
+class ObjectFormatter extends Formatter { |
+ bool accept(object) => isRegularDartObject(object); |
+ |
+ String preview(object) => getObjectTypeName(object); |
+ |
+ bool hasChildren(object) => true; |
+ |
+ /// Helper to add members walking up the prototype chain being careful |
+ /// to avoid properties that are Dart methods. |
+ _addMembers(current, object, List<NameValuePair> properties) { |
+ // TODO(jacobr): optionally distinguish properties and fields so that |
+ // it is safe to expand untrusted objects without side effects. |
Jennifer Messerly
2016/01/22 19:52:16
Ah, the eternal question in debuggers!
Usually, j
Jacob
2016/01/22 21:28:53
I agree with you. Added "button to turn off eager
|
+ var className = dart.realRuntimeType(current).name; |
+ for (var name in getOwnPropertyNames(current)) { |
+ if (name == 'constructor' || |
+ name == '__proto__' || |
+ name == className) continue; |
+ if (hasMethod(object, name)) { |
+ continue; |
+ } |
+ var value; |
+ try { |
+ value = JSNative.getProperty(object, name); |
+ } catch (e) { |
+ value = '<Exception thrown>'; |
+ } |
+ properties.add(new NameValuePair(name: name, value: value)); |
+ } |
+ for (var symbol in getOwnPropertySymbols(current)) { |
+ var dartName = symbolName(symbol); |
+ if (hasMethod(object, dartName)) { |
+ continue; |
+ } |
+ var value; |
+ try { |
+ value = JSNative.getProperty(object, symbol); |
+ } catch (e) { |
+ value = '<Exception thrown>'; |
+ } |
+ properties.add(new NameValuePair(name: dartName, value: value)); |
+ } |
+ var base = JSNative.getProperty(current, '__proto__'); |
+ if (base == null) return; |
+ if (isRegularDartObject(base)) { |
+ _addMembers(base, object, properties); |
+ } |
+ } |
+ |
+ List<NameValuePair> children(object) { |
+ var properties = <NameValuePair>[]; |
+ addMetadataChildren(object, properties); |
+ _addMembers(object, object, properties); |
+ return properties; |
+ } |
+ |
+ addMetadataChildren(object, List<NameValuePair> ret) { |
+ ret.add( |
+ new NameValuePair(name: '[[class]]', value: new ClassMetadata(object))); |
+ } |
+} |
+ |
+/// Formatter for Dart Function objects. |
+/// Dart functions happen to be regular JavaScript Function objects but |
+/// we can distinguish them based on whether they have been tagged with |
+/// runtime type information. |
+class FunctionFormatter extends Formatter { |
+ accept(object) { |
+ if (typeof(object) != 'function') return false; |
+ return dart.realRuntimeType(object) != null; |
+ } |
+ |
+ bool hasChildren(object) => true; |
+ |
+ String preview(object) { |
+ return dart.typeName(dart.realRuntimeType(object)); |
+ } |
+ |
+ List<NameValuePair> children(object) => <NameValuePair>[ |
+ new NameValuePair(name: 'signature', value: preview(object)), |
+ new NameValuePair( |
+ name: 'JavaScript Function', value: object, skipDart: true) |
+ ]; |
+} |
+ |
+/// Formatter for Dart Map objects. |
+class MapFormatter extends ObjectFormatter { |
+ accept(object) => object is Map; |
+ |
+ bool hasChildren(object) => true; |
+ |
+ String preview(object) { |
+ Map map = object; |
+ return '${getObjectTypeName(map)} length ${map.length}'; |
+ } |
+ |
+ List<NameValuePair> children(object) { |
+ // TODO(jacobr): be lazier about enumerating contents of Maps that are not |
+ // the build in LinkedHashMap class. |
+ // TODO(jacobr): handle large Maps better. |
+ Map map = object; |
+ var keys = map.keys.toList(); |
+ var entries = <NameValuePair>[]; |
+ map.forEach((key, value) { |
+ var entryWrapper = new MapEntry(key: key, value: value); |
+ entries.add(new NameValuePair( |
+ name: entries.length.toString(), value: entryWrapper)); |
+ }); |
+ addMetadataChildren(object, entries); |
+ return entries; |
+ } |
+} |
+ |
+/// Formatter for Dart Iterable objects including List and Set. |
+class IterableFormatter extends ObjectFormatter { |
+ bool accept(object) => object is Iterable; |
+ |
+ String preview(object) { |
+ Iterable iterable = object; |
+ try { |
+ var length = iterable.length; |
+ return '${getObjectTypeName(iterable)} length $length'; |
+ } catch (_) { |
+ return '${getObjectTypeName(iterable)}'; |
+ } |
+ } |
+ |
+ bool hasChildren(object) => true; |
+ |
+ List<NameValuePair> children(object) { |
+ // TODO(jacobr): be lazier about enumerating contents of Iterables that |
+ // are not the built in Set or List types. |
+ // TODO(jacobr): handle large Iterables better. |
+ // TODO(jacobr): consider only using numeric indices |
+ Iterable iterable = object; |
+ var ret = <NameValuePair>[]; |
+ var i = 0; |
+ for (var entry in iterable) { |
+ if (i > MAX_ITERABLE_CHILDREN_TO_DISPLAY) { |
+ ret.add(new NameValuePair( |
+ name: 'Warning', value: 'Truncated Iterable display')); |
+ // TODO(jacobr): provide an expandable entry to show more entries. |
+ break; |
+ } |
+ ret.add(new NameValuePair(name: i.toString(), value: entry)); |
+ i++; |
+ } |
+ // TODO(jacobr): provide a link to show regular class properties here. |
+ // required for subclasses of iterable, etc. |
+ addMetadataChildren(object, ret); |
+ return ret; |
+ } |
+} |
+ |
+// This class does double duting displaying metadata for |
skybrian
2016/01/22 21:45:21
s/duting/duty/
|
+class ClassMetadataFormatter implements Formatter { |
+ accept(object) => object is ClassMetadata; |
+ |
+ _getType(object) { |
+ if (object is Type) return object; |
+ return dart.realRuntimeType(object); |
+ } |
+ |
+ String preview(object) { |
+ ClassMetadata entry = object; |
+ return getTypeName(_getType(entry.object)); |
+ } |
+ |
+ bool hasChildren(object) => true; |
+ |
+ List<NameValuePair> children(object) { |
+ ClassMetadata entry = object; |
+ // TODO(jacobr): add other entries describing the class such as |
+ // links to the superclass, mixins, implemented interfaces, and methods. |
+ var type = _getType(entry.object); |
+ var ret = <NameValuePair>[]; |
+ var implements = dart.getImplements(type); |
+ if (implements != null) { |
+ ret.add(new NameValuePair( |
+ name: '[[Implements]]', |
+ value: new HeritageClause('implements', implements()))); |
+ } |
+ var mixins = dart.getMixins(type); |
+ if (mixins != null) { |
+ ret.add(new NameValuePair( |
+ name: '[[Mixins]]', value: new HeritageClause('mixins', mixins()))); |
+ } |
+ ret.add(new NameValuePair( |
+ name: '[[JavaScript View]]', value: entry.object, skipDart: true)); |
+ |
+ // TODO(jacobr): provide a link to the base class or perhaps the entire |
+ // base class hierarchy as a flat list. |
+ |
+ if (entry.object is! Type) { |
+ ret.add(new NameValuePair( |
+ name: '[[JavaScript Constructor]]', |
+ value: JSNative.getProperty(entry.object, 'constructor'), |
+ skipDart: true)); |
+ // TODO(jacobr): add constructors, methods, extended class, and static |
+ } |
+ return ret; |
+ } |
+} |
+ |
+/// Formatter for synthetic MapEntry objects used to display contents of a Map |
+/// cleanly. |
+class MapEntryFormatter implements Formatter { |
+ accept(object) => object is MapEntry; |
+ |
+ String preview(object) { |
+ MapEntry entry = object; |
+ return '${safePreview(entry.key)} => ${safePreview(entry.value)}'; |
+ } |
+ |
+ bool hasChildren(object) => true; |
+ |
+ List<NameValuePair> children(object) => <NameValuePair>[ |
+ new NameValuePair(name: 'key', value: object.key), |
+ new NameValuePair(name: 'value', value: object.value) |
+ ]; |
+} |
+ |
+/// Formatter for Dart Iterable objects including List and Set. |
+class HeritageClauseFormatter implements Formatter { |
+ bool accept(object) => object is HeritageClause; |
+ |
+ String preview(object) { |
+ HeritageClause clause = object; |
+ return '${clause.name} ${clause.types.map((type) => getTypeName(type)).join(", ")}'; |
Jennifer Messerly
2016/01/22 19:52:17
long line
Jacob
2016/01/22 21:28:53
wish ... dartfmt doesn't handle long lines due to
|
+ } |
+ |
+ bool hasChildren(object) => true; |
+ |
+ List<NameValuePair> children(object) { |
+ HeritageClause clause = object; |
+ var ret = <NameValuePair>[]; |
+ for (var type in clause.types) { |
+ ret.add(new NameValuePair(value: new ClassMetadata(type))); |
+ } |
+ return ret; |
+ } |
+} |
+ |
+/// This entry point is automatically invoked by the code generated by |
+/// Dart Dev Compiler |
+registerDevtoolsFormatter() { |
+ var formatters = [_devtoolsFormatter]; |
+ JS('', 'window.devtoolsFormatters = #', formatters); |
+} |