Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2)

Unified Diff: tool/input_sdk/lib/debugger/debugger.dart

Issue 1625563002: Use the great new Devtools API for custom formatters. Now Dart objects are now generally as easy to… (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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);
+}

Powered by Google App Engine
This is Rietveld 408576698