| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library dart._debugger; | 5 library dart._debugger; |
| 6 | 6 |
| 7 import 'dart:_foreign_helper' show JS; | 7 import 'dart:_foreign_helper' show JS; |
| 8 import 'dart:_runtime' as dart; | 8 import 'dart:_runtime' as dart; |
| 9 import 'dart:core'; | 9 import 'dart:core'; |
| 10 import 'dart:collection'; | 10 import 'dart:collection'; |
| 11 import 'dart:html' as html; | 11 import 'dart:html' as html; |
| 12 import 'dart:math'; |
| 12 | 13 |
| 13 /// JsonMLConfig object to pass to devtools to specify how an Object should | 14 /// JsonMLConfig object to pass to devtools to specify how an Object should |
| 14 /// be displayed. skipDart signals that an object should not be formatted | 15 /// be displayed. skipDart signals that an object should not be formatted |
| 15 /// by the Dart formatter. This is used to specify that an Object | 16 /// by the Dart formatter. This is used to specify that an Object |
| 16 /// should just be displayed using the regular JavaScript view instead of a | 17 /// should just be displayed using the regular JavaScript view instead of a |
| 17 /// custom Dart view. For example, this is used to display the JavaScript view | 18 /// custom Dart view. For example, this is used to display the JavaScript view |
| 18 /// of a Dart Function as a child of the regular Function object. keyToString | 19 /// of a Dart Function as a child of the regular Function object. keyToString |
| 19 /// signals that a map key object should have its toString() displayed by | 20 /// signals that a map key object should have its toString() displayed by |
| 20 /// the Dart formatter. | 21 /// the Dart formatter. |
| 21 /// | 22 /// |
| 22 /// We'd like this to be an enum, but we can't because it's a dev_compiler bug. | 23 /// We'd like this to be an enum, but we can't because it's a dev_compiler bug. |
| 23 class JsonMLConfig { | 24 class JsonMLConfig { |
| 24 const JsonMLConfig(this.name); | 25 const JsonMLConfig(this.name); |
| 25 | 26 |
| 26 final String name; | 27 final String name; |
| 27 static const none = const JsonMLConfig("none"); | 28 static const none = const JsonMLConfig("none"); |
| 28 static const skipDart = const JsonMLConfig("skipDart"); | 29 static const skipDart = const JsonMLConfig("skipDart"); |
| 29 static const keyToString = const JsonMLConfig("keyToString"); | 30 static const keyToString = const JsonMLConfig("keyToString"); |
| 30 } | 31 } |
| 31 | 32 |
| 32 final int maxIterableChildrenToDisplay = 50; | 33 int maxSpanLength = 100; |
| 33 | 34 |
| 34 var _devtoolsFormatter = new JsonMLFormatter(new DartFormatter()); | 35 var _devtoolsFormatter = new JsonMLFormatter(new DartFormatter()); |
| 35 | 36 |
| 36 String _typeof(object) => JS('String', 'typeof #', object); | 37 String _typeof(object) => JS('String', 'typeof #', object); |
| 37 | 38 |
| 38 List<String> getOwnPropertyNames(object) => JS('List<String>', | 39 List<String> getOwnPropertyNames(object) => JS('List<String>', |
| 39 'dart.list(Object.getOwnPropertyNames(#), #)', object, String); | 40 'dart.list(Object.getOwnPropertyNames(#), #)', object, String); |
| 40 | 41 |
| 41 List getOwnPropertySymbols(object) => | 42 List getOwnPropertySymbols(object) => |
| 42 JS('List', 'Object.getOwnPropertySymbols(#)', object); | 43 JS('List', 'Object.getOwnPropertySymbols(#)', object); |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 91 bool hasMethod(object, String name) { | 92 bool hasMethod(object, String name) { |
| 92 try { | 93 try { |
| 93 return dart.hasMethod(object, name); | 94 return dart.hasMethod(object, name); |
| 94 } catch (e) { | 95 } catch (e) { |
| 95 return false; | 96 return false; |
| 96 } | 97 } |
| 97 } | 98 } |
| 98 | 99 |
| 99 /// [JsonMLFormatter] consumes [NameValuePair] objects and | 100 /// [JsonMLFormatter] consumes [NameValuePair] objects and |
| 100 class NameValuePair { | 101 class NameValuePair { |
| 101 NameValuePair({this.name, this.value, this.config: JsonMLConfig.none}); | 102 NameValuePair( |
| 103 {this.name, |
| 104 this.value, |
| 105 this.config: JsonMLConfig.none, |
| 106 this.hideName: false}); |
| 102 | 107 |
| 103 // Define equality and hashCode so that NameValuePair can be used | 108 // Define equality and hashCode so that NameValuePair can be used |
| 104 // in a Set to dedupe entries with duplicate names. | 109 // in a Set to dedupe entries with duplicate names. |
| 105 operator ==(other) => other is NameValuePair && other.name == name; | 110 operator ==(other) => other is NameValuePair && other.name == name; |
| 106 int get hashCode => name.hashCode; | 111 int get hashCode => name.hashCode; |
| 107 | 112 |
| 108 final String name; | 113 final String name; |
| 109 final Object value; | 114 final Object value; |
| 110 final JsonMLConfig config; | 115 final JsonMLConfig config; |
| 116 final bool hideName; |
| 117 |
| 118 String get displayName => hideName ? '' : name; |
| 111 } | 119 } |
| 112 | 120 |
| 113 class MapEntry { | 121 class MapEntry { |
| 114 MapEntry({this.key, this.value}); | 122 MapEntry({this.key, this.value}); |
| 115 | 123 |
| 116 final Object key; | 124 final Object key; |
| 117 final Object value; | 125 final Object value; |
| 118 } | 126 } |
| 119 | 127 |
| 128 class IterableSpan { |
| 129 IterableSpan({this.start, this.end, this.iterable}); |
| 130 |
| 131 final int start; |
| 132 final int end; |
| 133 final Iterable iterable; |
| 134 } |
| 135 |
| 120 class ClassMetadata { | 136 class ClassMetadata { |
| 121 ClassMetadata(this.object); | 137 ClassMetadata(this.object); |
| 122 | 138 |
| 123 final Object object; | 139 final Object object; |
| 124 } | 140 } |
| 125 | 141 |
| 126 class HeritageClause { | 142 class HeritageClause { |
| 127 HeritageClause(this.name, this.types); | 143 HeritageClause(this.name, this.types); |
| 128 | 144 |
| 129 final String name; | 145 final String name; |
| (...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 231 var body = new JsonMLElement('ol') | 247 var body = new JsonMLElement('ol') |
| 232 ..setStyle('list-style-type: none;' | 248 ..setStyle('list-style-type: none;' |
| 233 'padding-left: 0px;' | 249 'padding-left: 0px;' |
| 234 'margin-top: 0px;' | 250 'margin-top: 0px;' |
| 235 'margin-bottom: 0px;' | 251 'margin-bottom: 0px;' |
| 236 'margin-left: 12px'); | 252 'margin-left: 12px'); |
| 237 var children = _simpleFormatter.children(object); | 253 var children = _simpleFormatter.children(object); |
| 238 for (NameValuePair child in children) { | 254 for (NameValuePair child in children) { |
| 239 var li = body.createChild('li'); | 255 var li = body.createChild('li'); |
| 240 var nameSpan = new JsonMLElement('span') | 256 var nameSpan = new JsonMLElement('span') |
| 241 ..createTextChild(child.name != null ? child.name + ': ' : '') | 257 ..createTextChild( |
| 258 child.displayName.isNotEmpty ? '${child.displayName}: ' : '') |
| 242 ..setStyle('color: rgb(136, 19, 145);'); | 259 ..setStyle('color: rgb(136, 19, 145);'); |
| 243 if (_typeof(child.value) == 'object' || | 260 if (_typeof(child.value) == 'object' || |
| 244 _typeof(child.value) == 'function') { | 261 _typeof(child.value) == 'function') { |
| 245 nameSpan.addStyle("padding-left: 13px;"); | 262 nameSpan.addStyle("padding-left: 13px;"); |
| 246 | 263 |
| 247 li.appendChild(nameSpan); | 264 li.appendChild(nameSpan); |
| 248 var objectTag = li.createObjectTag(child.value); | 265 var objectTag = li.createObjectTag(child.value); |
| 249 objectTag.addAttribute('config', child.config); | 266 objectTag.addAttribute('config', child.config); |
| 250 if (!_simpleFormatter.hasChildren(child.value)) { | 267 if (!_simpleFormatter.hasChildren(child.value)) { |
| 251 li.setStyle("padding-left: 13px;"); | 268 li.setStyle("padding-left: 13px;"); |
| (...skipping 20 matching lines...) Expand all Loading... |
| 272 List<Formatter> _formatters; | 289 List<Formatter> _formatters; |
| 273 | 290 |
| 274 DartFormatter() { | 291 DartFormatter() { |
| 275 // The order of formatters matters as formatters later in the list take | 292 // The order of formatters matters as formatters later in the list take |
| 276 // precidence. | 293 // precidence. |
| 277 _formatters = [ | 294 _formatters = [ |
| 278 new FunctionFormatter(), | 295 new FunctionFormatter(), |
| 279 new MapFormatter(), | 296 new MapFormatter(), |
| 280 new IterableFormatter(), | 297 new IterableFormatter(), |
| 281 new MapEntryFormatter(), | 298 new MapEntryFormatter(), |
| 299 new IterableSpanFormatter(), |
| 282 new ClassMetadataFormatter(), | 300 new ClassMetadataFormatter(), |
| 283 new HeritageClauseFormatter(), | 301 new HeritageClauseFormatter(), |
| 284 new ObjectFormatter() | 302 new ObjectFormatter(), |
| 285 ]; | 303 ]; |
| 286 } | 304 } |
| 287 | 305 |
| 288 String preview(object) { | 306 String preview(object) { |
| 289 try { | 307 try { |
| 290 if (object == null || | 308 if (object == null || |
| 291 object is num || | 309 object is num || |
| 292 object is String || | 310 object is String || |
| 293 isNativeJavaScriptObject(object)) { | 311 isNativeJavaScriptObject(object)) { |
| 294 return object.toString(); | 312 return object.toString(); |
| (...skipping 192 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 487 } | 505 } |
| 488 } | 506 } |
| 489 | 507 |
| 490 bool hasChildren(object) => true; | 508 bool hasChildren(object) => true; |
| 491 | 509 |
| 492 List<NameValuePair> children(object) { | 510 List<NameValuePair> children(object) { |
| 493 // TODO(jacobr): be lazier about enumerating contents of Iterables that | 511 // TODO(jacobr): be lazier about enumerating contents of Iterables that |
| 494 // are not the built in Set or List types. | 512 // are not the built in Set or List types. |
| 495 // TODO(jacobr): handle large Iterables better. | 513 // TODO(jacobr): handle large Iterables better. |
| 496 // TODO(jacobr): consider only using numeric indices | 514 // TODO(jacobr): consider only using numeric indices |
| 497 Iterable iterable = object; | |
| 498 var ret = new LinkedHashSet<NameValuePair>(); | 515 var ret = new LinkedHashSet<NameValuePair>(); |
| 499 var i = 0; | 516 ret.addAll(childrenHelper( |
| 500 for (var entry in iterable) { | 517 new IterableSpan(start: 0, end: object.length, iterable: object))); |
| 501 if (i > maxIterableChildrenToDisplay) { | |
| 502 ret.add(new NameValuePair( | |
| 503 name: 'Warning', value: 'Truncated Iterable display')); | |
| 504 // TODO(jacobr): provide an expandable entry to show more entries. | |
| 505 break; | |
| 506 } | |
| 507 ret.add(new NameValuePair(name: i.toString(), value: entry)); | |
| 508 i++; | |
| 509 } | |
| 510 // TODO(jacobr): provide a link to show regular class properties here. | 518 // TODO(jacobr): provide a link to show regular class properties here. |
| 511 // required for subclasses of iterable, etc. | 519 // required for subclasses of iterable, etc. |
| 512 addMetadataChildren(object, ret); | 520 addMetadataChildren(object, ret); |
| 513 return ret.toList(); | 521 return ret.toList(); |
| 514 } | 522 } |
| 515 } | 523 } |
| 516 | 524 |
| 517 // This class does double duting displaying metadata for | 525 // This class does double duting displaying metadata for |
| 518 class ClassMetadataFormatter implements Formatter { | 526 class ClassMetadataFormatter implements Formatter { |
| 519 accept(object) => object is ClassMetadata; | 527 accept(object) => object is ClassMetadata; |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 600 List<NameValuePair> children(object) { | 608 List<NameValuePair> children(object) { |
| 601 HeritageClause clause = object; | 609 HeritageClause clause = object; |
| 602 var ret = <NameValuePair>[]; | 610 var ret = <NameValuePair>[]; |
| 603 for (var type in clause.types) { | 611 for (var type in clause.types) { |
| 604 ret.add(new NameValuePair(value: new ClassMetadata(type))); | 612 ret.add(new NameValuePair(value: new ClassMetadata(type))); |
| 605 } | 613 } |
| 606 return ret; | 614 return ret; |
| 607 } | 615 } |
| 608 } | 616 } |
| 609 | 617 |
| 618 /// Formatter for synthetic IterableSpan objects used to display contents of |
| 619 /// an Iterable cleanly. |
| 620 class IterableSpanFormatter implements Formatter { |
| 621 accept(object) => object is IterableSpan; |
| 622 |
| 623 String preview(object) { |
| 624 IterableSpan entry = object; |
| 625 return '[${object.start}...${object.end-1}]'; |
| 626 } |
| 627 |
| 628 bool hasChildren(object) => true; |
| 629 |
| 630 List<NameValuePair> children(object) => childrenHelper(object); |
| 631 } |
| 632 |
| 633 List<NameValuePair> childrenHelper(IterableSpan span) { |
| 634 var length = span.end - span.start; |
| 635 var ret = new List<NameValuePair>(); |
| 636 if (length <= maxSpanLength) { |
| 637 for (var i = span.start; i < span.end; i++) { |
| 638 /// TODO(bmilligan): Stop using elementAt if it becomes a performance |
| 639 /// bottleneck in the future. |
| 640 ret.add(new NameValuePair( |
| 641 name: i.toString(), value: span.iterable.elementAt(i))); |
| 642 } |
| 643 } else { |
| 644 /// Using length - .5, a list of length 10000 results in a |
| 645 /// maxPowerOfSubsetSize of 1, so the list will be broken up into 100, |
| 646 /// 100-length subsets. A list of length 10001 results in a |
| 647 /// maxPowerOfSubsetSize of 2, so the list will be broken up into 1 |
| 648 /// 10000-length subset and 1 1-length subset. |
| 649 var maxPowerOfSubsetSize = |
| 650 (log(length - .5) / log(maxSpanLength)).truncate(); |
| 651 var subsetSize = pow(maxSpanLength, maxPowerOfSubsetSize); |
| 652 for (var i = span.start; i < span.end; i += subsetSize) { |
| 653 var endIndex = min(span.end, subsetSize + i); |
| 654 if (endIndex - i == 1) |
| 655 ret.add(new NameValuePair( |
| 656 name: i.toString(), value: span.iterable.elementAt(i))); |
| 657 else { |
| 658 var entryWrapper = |
| 659 new IterableSpan(start: i, end: endIndex, iterable: span.iterable); |
| 660 ret.add(new NameValuePair( |
| 661 name: '[${i}...${endIndex - 1}]', |
| 662 value: entryWrapper, |
| 663 hideName: true)); |
| 664 } |
| 665 } |
| 666 } |
| 667 return ret; |
| 668 } |
| 669 |
| 610 /// This entry point is automatically invoked by the code generated by | 670 /// This entry point is automatically invoked by the code generated by |
| 611 /// Dart Dev Compiler | 671 /// Dart Dev Compiler |
| 612 registerDevtoolsFormatter() { | 672 registerDevtoolsFormatter() { |
| 613 var formatters = [_devtoolsFormatter]; | 673 var formatters = [_devtoolsFormatter]; |
| 614 JS('', 'dart.global.devtoolsFormatters = #', formatters); | 674 JS('', 'dart.global.devtoolsFormatters = #', formatters); |
| 615 } | 675 } |
| OLD | NEW |