| // 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:_interceptors' show JSArray; | |
| import 'dart:_runtime' as dart; | |
| import 'dart:core'; | |
| import 'dart:collection'; | |
| import 'dart:html' as html; | |
| import 'dart:math'; | |
| part 'profile.dart'; | |
| /// 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"); | |
| static const asObject = const JsonMLConfig("asObject"); | |
| toString() => "JsonMLConfig($name)"; | |
| } | |
| int _maxSpanLength = 100; | |
| var _devtoolsFormatter = new JsonMLFormatter(new DartFormatter()); | |
| /// We truncate a toString() longer than [maxStringLength]. | |
| int maxFormatterStringLength = 100; | |
| String _typeof(object) => JS('String', 'typeof #', object); | |
| List<String> getOwnPropertyNames(object) => | |
| new JSArray<String>.of(dart.getOwnPropertyNames(object)); | |
| 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'); | |
| var objectPrototype = JS('', 'Object.prototype'); | |
| while (sig != null && !identical(sig, objectPrototype)) { | |
| 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.fn(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.fn(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) { | |
| // 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. | |
| return dart.typeName(type); | |
| } | |
| 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 ObjectInternalsFormatter(), | |
| 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) { | |
| var typeName = getObjectTypeName(object); | |
| try { | |
| // An explicit toString() call might not actually be a string. This way | |
| // we're sure. | |
| var toString = "$object"; | |
| if (toString.length > maxFormatterStringLength) { | |
| toString = toString.substring(0, maxFormatterStringLength - 3) + "..."; | |
| } | |
| // The default toString() will be "Instance of 'Foo'", in which case we | |
| // don't need any further indication of the class. | |
| if (toString.contains(typeName)) { | |
| return toString; | |
| } else { | |
| // If there's no class indication, e.g. an Int64 that just prints as a | |
| // number, then add the class name. | |
| return "$toString ($typeName)"; | |
| } | |
| } catch (e) {} | |
| // We will only get here if there was an error getting the toString, in | |
| // which case we just use the type name. | |
| return typeName; | |
| } | |
| 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 fields = new Set<NameValuePair>(); | |
| addPropertiesFromSignature(dart.getFields(type), fields, object, true); | |
| var getters = new Set<NameValuePair>(); | |
| addPropertiesFromSignature(dart.getGetters(type), getters, object, true); | |
| ret.addAll(sortProperties(fields)); | |
| ret.addAll(sortProperties(getters)); | |
| addMetadataChildren(object, ret); | |
| return ret.toList(); | |
| } | |
| } | |
| /// Show the object instance members and a reduced preview. | |
| /// | |
| /// Used as a sub-entry to show the internals of objects that have a different | |
| /// primary format. For example, a Map shows the key-value pairs, but this makes | |
| /// the internals of the map visible for debugging. | |
| class ObjectInternalsFormatter extends ObjectFormatter { | |
| bool accept(object, config) => | |
| super.accept(object, config) && config == JsonMLConfig.asObject; | |
| // A minimal preview because we expect a full preview is already shown in a | |
| // parent formatter. | |
| String preview(object) { | |
| return getObjectTypeName(object); | |
| } | |
| } | |
| /// 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)); | |
| }); | |
| addInstanceMembers(object, entries); | |
| addMetadataChildren(object, entries); | |
| return entries.toList(); | |
| } | |
| // We've formatted as a Map, but we may want to see the internals | |
| // of the Map, particularly for domain objects that implement Map. | |
| // Add an ObjectFormatter view underneath. | |
| void addInstanceMembers(object, Set<NameValuePair> ret) { | |
| ret.add(new NameValuePair( | |
| name: "[[instance members]]", | |
| value: object, | |
| config: JsonMLConfig.asObject)); | |
| } | |
| } | |
| /// 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.getStaticFields(type), staticProperties, type, false); | |
| addPropertiesFromSignature( | |
| dart.getStaticGetters(type), staticProperties, type, false); | |
| // static methods. | |
| addPropertiesFromSignature( | |
| dart.getStaticMethods(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.getMethods(type), instanceMethods, | |
| JS('', '#.prototype', type), false, | |
| tagTypes: true); | |
| if (instanceMethods.isNotEmpty) { | |
| ret | |
| ..add(new NameValuePair(value: '[[Instance Methods]]', hideName: true)) | |
| ..addAll(sortProperties(instanceMethods)); | |
| } | |
| var mixin = dart.getMixin(type); | |
| if (mixin != null) { | |
| // TODO(jmesserly): this can only be one value. | |
| ret.add(new NameValuePair( | |
| name: '[[Mixins]]', value: new HeritageClause('mixins', [mixin]))); | |
| } | |
| 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); | |
| } |