Chromium Code Reviews| 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); |
| +} |