b280a7803d
Optimize dynamic call tracking using random sampling. Apply source maps to improve dynamic call tracking accuracy. Dynamic calls within the sdk are now attributed correctly. Fix bug formatting modules where we were incorrectly flagging them as JS. BUG= R=jmesserly@google.com, vsm@google.com Review-Url: https://codereview.chromium.org/2811343002 .
889 lines
27 KiB
Dart
889 lines
27 KiB
Dart
// 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';
|
|
import 'dart:collection';
|
|
import 'dart:html' as html;
|
|
import 'dart:math';
|
|
|
|
/// JsonMLConfig object to pass to devtools to specify how an Object should
|
|
/// be displayed. skipDart signals that an object should not be formatted
|
|
/// by the Dart formatter. This is used to specify that an Object
|
|
/// 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. keyToString
|
|
/// signals that a map key object should have its toString() displayed by
|
|
/// the Dart formatter.
|
|
///
|
|
/// We'd like this to be an enum, but we can't because it's a dev_compiler bug.
|
|
class JsonMLConfig {
|
|
const JsonMLConfig(this.name);
|
|
|
|
final String name;
|
|
static const none = const JsonMLConfig("none");
|
|
static const skipDart = const JsonMLConfig("skipDart");
|
|
static const keyToString = const JsonMLConfig("keyToString");
|
|
static const asClass = const JsonMLConfig("asClass");
|
|
}
|
|
|
|
int _maxSpanLength = 100;
|
|
var _devtoolsFormatter = new JsonMLFormatter(new DartFormatter());
|
|
|
|
String _typeof(object) => JS('String', 'typeof #', object);
|
|
|
|
List<String> getOwnPropertyNames(object) => JS('List<String>',
|
|
'dart.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);
|
|
}
|
|
|
|
void addMetadataChildren(object, Set<NameValuePair> ret) {
|
|
ret.add(new NameValuePair(
|
|
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) {
|
|
if (_typeof(object) == 'function') {
|
|
return '[[Raw JavaScript Function]]';
|
|
}
|
|
return '<Error getting type name>';
|
|
}
|
|
return getTypeName(reifiedType);
|
|
}
|
|
|
|
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
|
|
// 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, config) {
|
|
try {
|
|
var preview = _devtoolsFormatter._simpleFormatter.preview(object, config);
|
|
if (preview != null) return preview;
|
|
return object.toString();
|
|
} catch (e) {
|
|
return '<Exception thrown> $e';
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// [JsonMLFormatter] consumes [NameValuePair] objects and
|
|
class NameValuePair {
|
|
NameValuePair(
|
|
{this.name,
|
|
this.value,
|
|
this.config: JsonMLConfig.none,
|
|
this.hideName: false});
|
|
|
|
// Define equality and hashCode so that NameValuePair can be used
|
|
// in a Set to dedupe entries with duplicate names.
|
|
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;
|
|
final Object value;
|
|
final JsonMLConfig config;
|
|
final bool hideName;
|
|
|
|
String get displayName => hideName ? '' : name;
|
|
}
|
|
|
|
class MapEntry {
|
|
MapEntry({this.key, this.value});
|
|
|
|
final Object key;
|
|
final Object value;
|
|
}
|
|
|
|
class IterableSpan {
|
|
IterableSpan(this.start, this.end, this.iterable);
|
|
|
|
final int start;
|
|
final int end;
|
|
final Iterable iterable;
|
|
int get length => end - start;
|
|
|
|
/// Using length - .5, a list of length 10000 results in a
|
|
/// maxPowerOfSubsetSize of 1, so the list will be broken up into 100,
|
|
/// 100-length subsets. A list of length 10001 results in a
|
|
/// maxPowerOfSubsetSize of 2, so the list will be broken up into 1
|
|
/// 10000-length subset and 1 1-length subset.
|
|
int get maxPowerOfSubsetSize =>
|
|
(log(length - .5) / log(_maxSpanLength)).truncate();
|
|
int get subsetSize => pow(_maxSpanLength, maxPowerOfSubsetSize);
|
|
|
|
Map<int, dynamic> asMap() =>
|
|
iterable.skip(start).take(length).toList().asMap();
|
|
|
|
List<NameValuePair> children() {
|
|
var children = <NameValuePair>[];
|
|
if (length <= _maxSpanLength) {
|
|
asMap().forEach((i, element) {
|
|
children.add(
|
|
new NameValuePair(name: (i + start).toString(), value: element));
|
|
});
|
|
} else {
|
|
for (var i = start; i < end; i += subsetSize) {
|
|
var subSpan = new IterableSpan(i, min(end, subsetSize + i), iterable);
|
|
if (subSpan.length == 1) {
|
|
children.add(new NameValuePair(
|
|
name: i.toString(), value: iterable.elementAt(i)));
|
|
} else {
|
|
children.add(new NameValuePair(
|
|
name: '[${i}...${subSpan.end - 1}]',
|
|
value: subSpan,
|
|
hideName: true));
|
|
}
|
|
}
|
|
}
|
|
return children;
|
|
}
|
|
}
|
|
|
|
class Library {
|
|
Library(this.name, this.object);
|
|
|
|
final String name;
|
|
final Object object;
|
|
}
|
|
|
|
class NamedConstructor {
|
|
NamedConstructor(this.object);
|
|
|
|
final Object object;
|
|
}
|
|
|
|
class HeritageClause {
|
|
HeritageClause(this.name, this.types);
|
|
|
|
final String name;
|
|
final List types;
|
|
}
|
|
|
|
Object safeGetProperty(Object protoChain, Object name) {
|
|
try {
|
|
return JSNative.getProperty(protoChain, name);
|
|
} catch (e) {
|
|
return '<Exception thrown> $e';
|
|
}
|
|
}
|
|
|
|
safeProperties(object) => new Map.fromIterable(
|
|
getOwnPropertyNames(object)
|
|
.where((each) => safeGetProperty(object, each) != null),
|
|
key: (name) => name,
|
|
value: (name) => safeGetProperty(object, name));
|
|
|
|
/// 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;
|
|
}
|
|
|
|
/// Whether an object is a native JavaScript type where we should display the
|
|
/// JavaScript view of the object instead of the custom Dart specific render
|
|
/// of properties.
|
|
bool isNativeJavaScriptObject(object) {
|
|
var type = _typeof(object);
|
|
if (type != 'object' && type != 'function') return true;
|
|
|
|
// Consider all regular JS objects that do not represent Dart modules native
|
|
// JavaScript objects.
|
|
if (dart.isJsInterop(object) && dart.getModuleName(object) == null) {
|
|
return true;
|
|
}
|
|
|
|
// Treat Node objects as a native JavaScript type as the regular DOM render
|
|
// in devtools is superior to the dart specific view.
|
|
return object is html.Node;
|
|
}
|
|
|
|
/// Class implementing the Devtools Formatter API described by:
|
|
/// https://docs.google.com/document/d/1FTascZXT9cxfetuPRT2eXPQKXui4nWFivUnS_335T3U
|
|
/// 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;
|
|
|
|
bool customFormattersOn = false;
|
|
|
|
JsonMLFormatter(this._simpleFormatter);
|
|
|
|
void setMaxSpanLengthForTestingOnly(int spanLength) {
|
|
_maxSpanLength = spanLength;
|
|
}
|
|
|
|
header(object, config) {
|
|
customFormattersOn = true;
|
|
if (config == JsonMLConfig.skipDart || isNativeJavaScriptObject(object)) {
|
|
return null;
|
|
}
|
|
var c = _simpleFormatter.preview(object, config);
|
|
if (c == null) return null;
|
|
|
|
if (config == JsonMLConfig.keyToString) {
|
|
c = object.toString();
|
|
}
|
|
|
|
// 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, config) => _simpleFormatter.hasChildren(object, config);
|
|
|
|
body(object, config) {
|
|
var body = new JsonMLElement('ol')
|
|
..setStyle('list-style-type: none;'
|
|
'padding-left: 0px;'
|
|
'margin-top: 0px;'
|
|
'margin-bottom: 0px;'
|
|
'margin-left: 12px;');
|
|
if (object is StackTrace) {
|
|
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');
|
|
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') {
|
|
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 {
|
|
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();
|
|
}
|
|
}
|
|
|
|
abstract class Formatter {
|
|
bool accept(object, config);
|
|
String preview(object);
|
|
bool hasChildren(object);
|
|
List<NameValuePair> children(object);
|
|
}
|
|
|
|
class DartFormatter {
|
|
List<Formatter> _formatters;
|
|
|
|
DartFormatter() {
|
|
// The order of formatters matters as formatters earlier in the list take
|
|
// precedence.
|
|
_formatters = [
|
|
new ClassFormatter(),
|
|
new TypeFormatter(),
|
|
new NamedConstructorFormatter(),
|
|
new MapFormatter(),
|
|
new IterableFormatter(),
|
|
new IterableSpanFormatter(),
|
|
new MapEntryFormatter(),
|
|
new StackTraceFormatter(),
|
|
new FunctionFormatter(),
|
|
new HeritageClauseFormatter(),
|
|
new LibraryModuleFormatter(),
|
|
new LibraryFormatter(),
|
|
new ObjectFormatter(),
|
|
];
|
|
}
|
|
|
|
String preview(object, config) {
|
|
try {
|
|
if (object == null ||
|
|
object is num ||
|
|
object is String ||
|
|
isNativeJavaScriptObject(object)) {
|
|
return object.toString();
|
|
}
|
|
for (var formatter in _formatters) {
|
|
if (formatter.accept(object, config)) return formatter.preview(object);
|
|
}
|
|
} catch (e, trace) {
|
|
// Log formatter internal errors as unfortunately the devtools cannot
|
|
// be used to debug formatter errors.
|
|
html.window.console.error("Caught exception $e\n trace:\n$trace");
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
bool hasChildren(object, config) {
|
|
if (object == null) return false;
|
|
try {
|
|
for (var formatter in _formatters) {
|
|
if (formatter.accept(object, config))
|
|
return formatter.hasChildren(object);
|
|
}
|
|
} catch (e, trace) {
|
|
// See comment for preview.
|
|
html.window.console
|
|
.error("[hasChildren] Caught exception $e\n trace:\n$trace");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
List<NameValuePair> children(object, config) {
|
|
try {
|
|
if (object != null) {
|
|
for (var formatter in _formatters) {
|
|
if (formatter.accept(object, config))
|
|
return formatter.children(object);
|
|
}
|
|
}
|
|
} catch (e, trace) {
|
|
// See comment for preview.
|
|
html.window.console.error("Caught exception $e\n trace:\n$trace");
|
|
}
|
|
return <NameValuePair>[];
|
|
}
|
|
}
|
|
|
|
/// Default formatter for Dart Objects.
|
|
class ObjectFormatter extends Formatter {
|
|
bool accept(object, config) => !isNativeJavaScriptObject(object);
|
|
|
|
String preview(object) => getObjectTypeName(object);
|
|
|
|
bool hasChildren(object) => true;
|
|
|
|
List<NameValuePair> children(object) {
|
|
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.getModuleName(object) != null;
|
|
|
|
bool hasChildren(object) => true;
|
|
|
|
String preview(object) {
|
|
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 &&
|
|
libraryNames.last == libraryNames[libraryNames.length - 2]) {
|
|
libraryNames[libraryNames.length - 1] = '';
|
|
}
|
|
return 'Library Module: ${libraryNames.join('/')}';
|
|
}
|
|
|
|
List<NameValuePair> children(object) {
|
|
var children = new LinkedHashSet<NameValuePair>();
|
|
for (var name in getOwnPropertyNames(object)) {
|
|
var value = safeGetProperty(object, name);
|
|
children.add(new NameValuePair(
|
|
name: name, value: new Library(name, value), hideName: true));
|
|
}
|
|
return children.toList();
|
|
}
|
|
}
|
|
|
|
class LibraryFormatter implements Formatter {
|
|
var genericParameters = new HashMap<String, String>();
|
|
|
|
accept(object, config) => object is Library;
|
|
|
|
bool hasChildren(object) => true;
|
|
|
|
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 objectProperties = safeProperties(object.object);
|
|
objectProperties.forEach((name, 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();
|
|
}
|
|
|
|
classChild(String name, Object child) {
|
|
var typeName = getTypeName(child);
|
|
return new NameValuePair(
|
|
name: typeName, value: child, config: JsonMLConfig.asClass);
|
|
}
|
|
}
|
|
|
|
/// 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 implements Formatter {
|
|
accept(object, config) {
|
|
if (_typeof(object) != 'function') return false;
|
|
return dart.getReifiedType(object) != null;
|
|
}
|
|
|
|
bool hasChildren(object) => true;
|
|
|
|
String preview(object) {
|
|
return dart.typeName(dart.getReifiedType(object));
|
|
}
|
|
|
|
List<NameValuePair> children(object) => <NameValuePair>[
|
|
new NameValuePair(name: 'signature', value: preview(object)),
|
|
new NameValuePair(
|
|
name: 'JavaScript Function',
|
|
value: object,
|
|
config: JsonMLConfig.skipDart)
|
|
];
|
|
}
|
|
|
|
/// Formatter for Dart Map objects.
|
|
class MapFormatter implements Formatter {
|
|
accept(object, config) => 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 entries = new LinkedHashSet<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.toList();
|
|
}
|
|
}
|
|
|
|
/// Formatter for Dart Iterable objects including List and Set.
|
|
class IterableFormatter implements Formatter {
|
|
bool accept(object, config) => 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
|
|
var children = new LinkedHashSet<NameValuePair>();
|
|
children.addAll(new IterableSpan(0, object.length, object).children());
|
|
// TODO(jacobr): provide a link to show regular class properties here.
|
|
// required for subclasses of iterable, etc.
|
|
addMetadataChildren(object, children);
|
|
return children.toList();
|
|
}
|
|
}
|
|
|
|
class NamedConstructorFormatter implements Formatter {
|
|
accept(object, config) => object is NamedConstructor;
|
|
|
|
// TODO(bmilligan): Display the signature of the named constructor as the
|
|
// preview.
|
|
String preview(object) => 'Named Constructor';
|
|
|
|
bool hasChildren(object) => true;
|
|
|
|
List<NameValuePair> children(object) => <NameValuePair>[
|
|
new NameValuePair(
|
|
name: 'JavaScript Function',
|
|
value: object,
|
|
config: JsonMLConfig.skipDart)
|
|
];
|
|
}
|
|
|
|
/// Formatter for synthetic MapEntry objects used to display contents of a Map
|
|
/// cleanly.
|
|
class MapEntryFormatter implements Formatter {
|
|
accept(object, config) => object is MapEntry;
|
|
|
|
String preview(object) {
|
|
MapEntry entry = object;
|
|
return '${safePreview(entry.key, JsonMLConfig.none)} => ${safePreview(entry.value, JsonMLConfig.none)}';
|
|
}
|
|
|
|
bool hasChildren(object) => true;
|
|
|
|
List<NameValuePair> children(object) => <NameValuePair>[
|
|
new NameValuePair(
|
|
name: 'key', value: object.key, config: JsonMLConfig.keyToString),
|
|
new NameValuePair(name: 'value', value: object.value)
|
|
];
|
|
}
|
|
|
|
/// Formatter for Dart Iterable objects including List and Set.
|
|
class HeritageClauseFormatter implements Formatter {
|
|
bool accept(object, config) => object is HeritageClause;
|
|
|
|
String preview(object) {
|
|
HeritageClause clause = object;
|
|
var typeNames = clause.types.map(getTypeName);
|
|
return '${clause.name} ${typeNames.join(", ")}';
|
|
}
|
|
|
|
bool hasChildren(object) => true;
|
|
|
|
List<NameValuePair> children(object) {
|
|
HeritageClause clause = object;
|
|
var children = <NameValuePair>[];
|
|
for (var type in clause.types) {
|
|
children
|
|
.add(new NameValuePair(value: type, config: JsonMLConfig.asClass));
|
|
}
|
|
return children;
|
|
}
|
|
}
|
|
|
|
/// Formatter for synthetic IterableSpan objects used to display contents of
|
|
/// an Iterable cleanly.
|
|
class IterableSpanFormatter implements Formatter {
|
|
accept(object, config) => object is IterableSpan;
|
|
|
|
String preview(object) {
|
|
return '[${object.start}...${object.end-1}]';
|
|
}
|
|
|
|
bool hasChildren(object) => true;
|
|
|
|
List<NameValuePair> children(object) => object.children();
|
|
}
|
|
|
|
class StackTraceFormatter implements Formatter {
|
|
accept(object, config) => object is StackTrace;
|
|
|
|
String preview(object) => 'StackTrace';
|
|
|
|
bool hasChildren(object) => true;
|
|
|
|
// Using the stack_trace formatting would be ideal, but adding the
|
|
// dependency or re-writing the code is too messy, so each line of the
|
|
// StackTrace will be added as its own child.
|
|
List<NameValuePair> children(object) => object
|
|
.toString()
|
|
.split('\n')
|
|
.map((line) => new NameValuePair(
|
|
value: line.replaceFirst(new RegExp(r'^\s+at\s'), ''),
|
|
hideName: true))
|
|
.toList();
|
|
}
|
|
|
|
class ClassFormatter implements Formatter {
|
|
accept(object, config) => config == JsonMLConfig.asClass;
|
|
|
|
String preview(type) {
|
|
var implements = dart.getImplements(type);
|
|
var typeName = getTypeName(type);
|
|
if (implements != null) {
|
|
var typeNames = implements().map(getTypeName);
|
|
return '${typeName} implements ${typeNames.join(", ")}';
|
|
} else {
|
|
return typeName;
|
|
}
|
|
}
|
|
|
|
bool hasChildren(object) => true;
|
|
|
|
List<NameValuePair> children(type) {
|
|
// TODO(jacobr): add other entries describing the class such as
|
|
// 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) {
|
|
ret.add(new NameValuePair(
|
|
name: '[[Mixins]]', value: new HeritageClause('mixins', mixins)));
|
|
}
|
|
|
|
var baseProto = JS('', '#.__proto__', type);
|
|
if (baseProto != null && !dart.isJsInterop(baseProto)) {
|
|
ret.add(new NameValuePair(
|
|
name: "[[base class]]",
|
|
value: baseProto,
|
|
config: JsonMLConfig.asClass));
|
|
}
|
|
|
|
// 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) => [];
|
|
}
|
|
|
|
typedef String StackTraceMapper(String stackTrace);
|
|
|
|
/// Hook for other parts of the SDK To use to map JS stack traces to Dart
|
|
/// stack traces.
|
|
///
|
|
/// Raw JS stack traces are used if $dartStackTraceUtility has not been
|
|
/// specified.
|
|
StackTraceMapper get stackTraceMapper {
|
|
var _util = JS('', r'dart.global.$dartStackTraceUtility');
|
|
return _util != null ? JS('StackTraceMapper', '#.mapper', _util) : null;
|
|
}
|
|
|
|
/// 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);
|
|
}
|