| 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'; |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 46 class JSNative { | 46 class JSNative { |
| 47 // Name may be a String or a Symbol. | 47 // Name may be a String or a Symbol. |
| 48 static getProperty(object, name) => JS('', '#[#]', object, name); | 48 static getProperty(object, name) => JS('', '#[#]', object, name); |
| 49 // Name may be a String or a Symbol. | 49 // Name may be a String or a Symbol. |
| 50 static setProperty(object, name, value) => | 50 static setProperty(object, name, value) => |
| 51 JS('', '#[#]=#', object, name, value); | 51 JS('', '#[#]=#', object, name, value); |
| 52 } | 52 } |
| 53 | 53 |
| 54 void addMetadataChildren(object, Set<NameValuePair> ret) { | 54 void addMetadataChildren(object, Set<NameValuePair> ret) { |
| 55 ret.add(new NameValuePair( | 55 ret.add(new NameValuePair( |
| 56 name: getTypeName(_getType(object)), | 56 name: "[[class]]", |
| 57 value: object, | 57 value: dart.getReifiedType(object), |
| 58 config: JsonMLConfig.asClass)); | 58 config: JsonMLConfig.asClass)); |
| 59 } | 59 } |
| 60 | 60 |
| 61 /// Add properties from a signature definition [sig] for [object]. |
| 62 /// Walk the prototype chain if [walkProtypeChain] is set. |
| 63 /// Tag types on function typed properties of [object] if [tagTypes] is set. |
| 64 /// |
| 65 void addPropertiesFromSignature( |
| 66 sig, Set<NameValuePair> properties, object, bool walkPrototypeChain, |
| 67 {tagTypes: false}) { |
| 68 // Including these property names doesn't add any value and just clutters |
| 69 // the debugger output. |
| 70 // TODO(jacobr): consider adding runtimeType to this list. |
| 71 var skippedNames = new Set()..add('hashCode'); |
| 72 |
| 73 while (sig != null) { |
| 74 for (var symbol in getOwnPropertySymbols(sig)) { |
| 75 var dartName = symbolName(symbol); |
| 76 String dartXPrefix = 'dartx.'; |
| 77 if (dartName.startsWith(dartXPrefix)) { |
| 78 dartName = dartName.substring(dartXPrefix.length); |
| 79 } |
| 80 if (skippedNames.contains(dartName)) continue; |
| 81 var value = safeGetProperty(object, symbol); |
| 82 // Tag the function with its runtime type. |
| 83 if (tagTypes && _typeof(value) == 'function') { |
| 84 dart.tag(value, JS('', '#[#]', sig, symbol)); |
| 85 } |
| 86 properties.add(new NameValuePair(name: dartName, value: value)); |
| 87 } |
| 88 |
| 89 for (var name in getOwnPropertyNames(sig)) { |
| 90 var value = safeGetProperty(object, name); |
| 91 if (skippedNames.contains(name)) continue; |
| 92 // Tag the function with its runtime type. |
| 93 if (tagTypes && _typeof(value) == 'function') { |
| 94 dart.tag(value, JS('', '#[#]', sig, name)); |
| 95 } |
| 96 properties.add(new NameValuePair(name: name, value: value)); |
| 97 } |
| 98 |
| 99 if (!walkPrototypeChain) break; |
| 100 |
| 101 sig = safeGetProperty(sig, '__proto__'); |
| 102 } |
| 103 } |
| 104 |
| 105 /// Sort properties sorting public names before private names. |
| 106 List<NameValuePair> sortProperties(Iterable<NameValuePair> properties) { |
| 107 var sortedProperties = properties.toList(); |
| 108 |
| 109 sortedProperties.sort((a, b) { |
| 110 var aPrivate = a.name.startsWith('_'); |
| 111 var bPrivate = b.name.startsWith('_'); |
| 112 if (aPrivate != bPrivate) return aPrivate ? 1 : -1; |
| 113 return a.name.compareTo(b.name); |
| 114 }); |
| 115 return sortedProperties; |
| 116 } |
| 117 |
| 61 String getObjectTypeName(object) { | 118 String getObjectTypeName(object) { |
| 62 var reifiedType = dart.getReifiedType(object); | 119 var reifiedType = dart.getReifiedType(object); |
| 63 if (reifiedType == null) { | 120 if (reifiedType == null) { |
| 64 if (_typeof(object) == 'function') { | 121 if (_typeof(object) == 'function') { |
| 65 return '[[Raw JavaScript Function]]'; | 122 return '[[Raw JavaScript Function]]'; |
| 66 } | 123 } |
| 67 return '<Error getting type name>'; | 124 return '<Error getting type name>'; |
| 68 } | 125 } |
| 69 return getTypeName(reifiedType); | 126 return getTypeName(reifiedType); |
| 70 } | 127 } |
| 71 | 128 |
| 72 String getTypeName(Type type) { | 129 String getTypeName(type) { |
| 73 var name = dart.typeName(type); | 130 var name = dart.typeName(type); |
| 74 // Hack to cleanup names for List<dynamic> | 131 // Hack to cleanup names for List<dynamic> |
| 75 // TODO(jacobr): it would be nice if there was a way we could distinguish | 132 // TODO(jacobr): it would be nice if there was a way we could distinguish |
| 76 // between a List<dynamic> created from Dart and an Array passed in from | 133 // between a List<dynamic> created from Dart and an Array passed in from |
| 77 // JavaScript. | 134 // JavaScript. |
| 78 if (name == 'JSArray<dynamic>' || name == 'JSObject<Array>') | 135 if (name == 'JSArray<dynamic>' || name == 'JSObject<Array>') |
| 79 return 'List<dynamic>'; | 136 return 'List<dynamic>'; |
| 80 return name; | 137 return name; |
| 81 } | 138 } |
| 82 | 139 |
| 83 Object _getType(object) => | |
| 84 object is Type ? object : dart.getReifiedType(object); | |
| 85 | |
| 86 String safePreview(object, config) { | 140 String safePreview(object, config) { |
| 87 try { | 141 try { |
| 88 var preview = _devtoolsFormatter._simpleFormatter.preview(object, config); | 142 var preview = _devtoolsFormatter._simpleFormatter.preview(object, config); |
| 89 if (preview != null) return preview; | 143 if (preview != null) return preview; |
| 90 return object.toString(); | 144 return object.toString(); |
| 91 } catch (e) { | 145 } catch (e) { |
| 92 return '<Exception thrown> $e'; | 146 return '<Exception thrown> $e'; |
| 93 } | 147 } |
| 94 } | 148 } |
| 95 | 149 |
| (...skipping 14 matching lines...) Expand all Loading... |
| 110 /// [JsonMLFormatter] consumes [NameValuePair] objects and | 164 /// [JsonMLFormatter] consumes [NameValuePair] objects and |
| 111 class NameValuePair { | 165 class NameValuePair { |
| 112 NameValuePair( | 166 NameValuePair( |
| 113 {this.name, | 167 {this.name, |
| 114 this.value, | 168 this.value, |
| 115 this.config: JsonMLConfig.none, | 169 this.config: JsonMLConfig.none, |
| 116 this.hideName: false}); | 170 this.hideName: false}); |
| 117 | 171 |
| 118 // Define equality and hashCode so that NameValuePair can be used | 172 // Define equality and hashCode so that NameValuePair can be used |
| 119 // in a Set to dedupe entries with duplicate names. | 173 // in a Set to dedupe entries with duplicate names. |
| 120 operator ==(other) => other is NameValuePair && other.name == name; | 174 bool operator ==(other) { |
| 175 if (other is! NameValuePair) return false; |
| 176 if (this.hideName || other.hideName) return identical(this, other); |
| 177 return other.name == name; |
| 178 } |
| 179 |
| 121 int get hashCode => name.hashCode; | 180 int get hashCode => name.hashCode; |
| 122 | 181 |
| 123 final String name; | 182 final String name; |
| 124 final Object value; | 183 final Object value; |
| 125 final JsonMLConfig config; | 184 final JsonMLConfig config; |
| 126 final bool hideName; | 185 final bool hideName; |
| 127 | 186 |
| 128 String get displayName => hideName ? '' : name; | 187 String get displayName => hideName ? '' : name; |
| 129 } | 188 } |
| 130 | 189 |
| (...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 262 } | 321 } |
| 263 | 322 |
| 264 /// Whether an object is a native JavaScript type where we should display the | 323 /// Whether an object is a native JavaScript type where we should display the |
| 265 /// JavaScript view of the object instead of the custom Dart specific render | 324 /// JavaScript view of the object instead of the custom Dart specific render |
| 266 /// of properties. | 325 /// of properties. |
| 267 bool isNativeJavaScriptObject(object) { | 326 bool isNativeJavaScriptObject(object) { |
| 268 var type = _typeof(object); | 327 var type = _typeof(object); |
| 269 // Treat Node objects as a native JavaScript type as the regular DOM render | 328 // Treat Node objects as a native JavaScript type as the regular DOM render |
| 270 // in devtools is superior to the dart specific view. | 329 // in devtools is superior to the dart specific view. |
| 271 return (type != 'object' && type != 'function') || | 330 return (type != 'object' && type != 'function') || |
| 272 object is dart.JSObject || | 331 dart.isJsInterop(object) || |
| 273 object is html.Node; | 332 object is html.Node; |
| 274 } | 333 } |
| 275 | 334 |
| 276 /// Class implementing the Devtools Formatter API described by: | 335 /// Class implementing the Devtools Formatter API described by: |
| 277 /// https://docs.google.com/document/d/1FTascZXT9cxfetuPRT2eXPQKXui4nWFivUnS_335
T3U | 336 /// https://docs.google.com/document/d/1FTascZXT9cxfetuPRT2eXPQKXui4nWFivUnS_335
T3U |
| 278 /// Specifically, a formatter implements a header, hasBody, and body method. | 337 /// Specifically, a formatter implements a header, hasBody, and body method. |
| 279 /// This class renders the simple structured format objects [_simpleFormatter] | 338 /// This class renders the simple structured format objects [_simpleFormatter] |
| 280 /// provides as JsonML. | 339 /// provides as JsonML. |
| 281 class JsonMLFormatter { | 340 class JsonMLFormatter { |
| 282 // TODO(jacobr): define a SimpleFormatter base class that DartFormatter | 341 // TODO(jacobr): define a SimpleFormatter base class that DartFormatter |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 321 var body = new JsonMLElement('ol') | 380 var body = new JsonMLElement('ol') |
| 322 ..setStyle('list-style-type: none;' | 381 ..setStyle('list-style-type: none;' |
| 323 'padding-left: 0px;' | 382 'padding-left: 0px;' |
| 324 'margin-top: 0px;' | 383 'margin-top: 0px;' |
| 325 'margin-bottom: 0px;' | 384 'margin-bottom: 0px;' |
| 326 'margin-left: 12px;'); | 385 'margin-left: 12px;'); |
| 327 if (object is StackTrace) { | 386 if (object is StackTrace) { |
| 328 body.addStyle('color: rgb(196, 26, 22);'); | 387 body.addStyle('color: rgb(196, 26, 22);'); |
| 329 } | 388 } |
| 330 var children = _simpleFormatter.children(object, config); | 389 var children = _simpleFormatter.children(object, config); |
| 390 if (children == null) return body.toJsonML(); |
| 331 for (NameValuePair child in children) { | 391 for (NameValuePair child in children) { |
| 332 var li = body.createChild('li'); | 392 var li = body.createChild('li'); |
| 333 var nameSpan = new JsonMLElement('span') | 393 li.setStyle("padding-left: 13px;"); |
| 334 ..createTextChild( | 394 |
| 335 child.displayName.isNotEmpty ? '${child.displayName}: ' : '') | 395 // The value is indented when it is on a different line from the name |
| 336 ..setStyle('color: rgb(136, 19, 145);'); | 396 // by setting right padding of the name to -13px and the padding of the |
| 397 // value to 13px. |
| 398 JsonMLElement nameSpan; |
| 399 var valueStyle = ''; |
| 400 if (!child.hideName) { |
| 401 nameSpan = new JsonMLElement('span') |
| 402 ..createTextChild( |
| 403 child.displayName.isNotEmpty ? '${child.displayName}: ' : '') |
| 404 ..setStyle('color: rgb(136, 19, 145); margin-right: -13px'); |
| 405 valueStyle = 'margin-left: 13px'; |
| 406 } |
| 407 |
| 337 if (_typeof(child.value) == 'object' || | 408 if (_typeof(child.value) == 'object' || |
| 338 _typeof(child.value) == 'function') { | 409 _typeof(child.value) == 'function') { |
| 339 nameSpan.addStyle("padding-left: 13px;"); | 410 var valueSpan = new JsonMLElement('span')..setStyle(valueStyle); |
| 340 | 411 valueSpan.createObjectTag(child.value) |
| 341 li.appendChild(nameSpan); | 412 ..addAttribute('config', child.config); |
| 342 var objectTag = li.createObjectTag(child.value); | 413 if (nameSpan != null) { |
| 343 objectTag.addAttribute('config', child.config); | 414 li.appendChild(nameSpan); |
| 344 if (!_simpleFormatter.hasChildren(child.value, child.config)) { | |
| 345 li.setStyle("padding-left: 13px;"); | |
| 346 } | 415 } |
| 416 li.appendChild(valueSpan); |
| 347 } else { | 417 } else { |
| 348 li.setStyle("padding-left: 13px;"); | 418 var line = li.createChild('span'); |
| 349 li.createChild('span') | 419 if (nameSpan != null) { |
| 350 ..appendChild(nameSpan) | 420 line.appendChild(nameSpan); |
| 351 ..createTextChild(safePreview(child.value, child.config)); | 421 } |
| 422 line.appendChild(new JsonMLElement('span') |
| 423 ..createTextChild(safePreview(child.value, child.config)) |
| 424 ..setStyle(valueStyle)); |
| 352 } | 425 } |
| 353 } | 426 } |
| 354 return body.toJsonML(); | 427 return body.toJsonML(); |
| 355 } | 428 } |
| 356 } | 429 } |
| 357 | 430 |
| 358 abstract class Formatter { | 431 abstract class Formatter { |
| 359 bool accept(object, config); | 432 bool accept(object, config); |
| 360 String preview(object); | 433 String preview(object); |
| 361 bool hasChildren(object); | 434 bool hasChildren(object); |
| 362 List<NameValuePair> children(object); | 435 List<NameValuePair> children(object); |
| 363 } | 436 } |
| 364 | 437 |
| 365 class DartFormatter { | 438 class DartFormatter { |
| 366 List<Formatter> _formatters; | 439 List<Formatter> _formatters; |
| 367 | 440 |
| 368 DartFormatter() { | 441 DartFormatter() { |
| 369 // The order of formatters matters as formatters earlier in the list take | 442 // The order of formatters matters as formatters earlier in the list take |
| 370 // precedence. | 443 // precedence. |
| 371 _formatters = [ | 444 _formatters = [ |
| 372 new ClassFormatter(), | 445 new ClassFormatter(), |
| 446 new TypeFormatter(), |
| 373 new NamedConstructorFormatter(), | 447 new NamedConstructorFormatter(), |
| 374 new MapFormatter(), | 448 new MapFormatter(), |
| 375 new IterableFormatter(), | 449 new IterableFormatter(), |
| 376 new IterableSpanFormatter(), | 450 new IterableSpanFormatter(), |
| 377 new MapEntryFormatter(), | 451 new MapEntryFormatter(), |
| 378 new StackTraceFormatter(), | 452 new StackTraceFormatter(), |
| 379 new FunctionFormatter(), | 453 new FunctionFormatter(), |
| 380 new HeritageClauseFormatter(), | 454 new HeritageClauseFormatter(), |
| 381 new LibraryModuleFormatter(), | 455 new LibraryModuleFormatter(), |
| 382 new LibraryFormatter(), | 456 new LibraryFormatter(), |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 430 } catch (e, trace) { | 504 } catch (e, trace) { |
| 431 // See comment for preview. | 505 // See comment for preview. |
| 432 html.window.console.error("Caught exception $e\n trace:\n$trace"); | 506 html.window.console.error("Caught exception $e\n trace:\n$trace"); |
| 433 } | 507 } |
| 434 return <NameValuePair>[]; | 508 return <NameValuePair>[]; |
| 435 } | 509 } |
| 436 } | 510 } |
| 437 | 511 |
| 438 /// Default formatter for Dart Objects. | 512 /// Default formatter for Dart Objects. |
| 439 class ObjectFormatter extends Formatter { | 513 class ObjectFormatter extends Formatter { |
| 440 static Set<String> _customNames = new Set() | |
| 441 ..add('constructor') | |
| 442 ..add('prototype') | |
| 443 ..add('__proto__'); | |
| 444 bool accept(object, config) => !isNativeJavaScriptObject(object); | 514 bool accept(object, config) => !isNativeJavaScriptObject(object); |
| 445 | 515 |
| 446 String preview(object) => getObjectTypeName(object); | 516 String preview(object) => getObjectTypeName(object); |
| 447 | 517 |
| 448 bool hasChildren(object) => true; | 518 bool hasChildren(object) => true; |
| 449 | 519 |
| 450 List<NameValuePair> children(object) { | 520 List<NameValuePair> children(object) { |
| 451 var properties = new LinkedHashSet<NameValuePair>(); | 521 var type = dart.getType(object); |
| 452 // Set of property names used to avoid duplicates. | 522 var ret = new LinkedHashSet<NameValuePair>(); |
| 453 addMetadataChildren(object, properties); | 523 // We use a Set rather than a List to avoid duplicates. |
| 454 | 524 var properties = new Set<NameValuePair>(); |
| 455 var current = object; | 525 addPropertiesFromSignature( |
| 456 | 526 dart.getFieldSig(type), properties, object, true); |
| 457 var protoChain = <Object>[]; | 527 addPropertiesFromSignature( |
| 458 while (current != null && | 528 dart.getGetterSig(type), properties, object, true); |
| 459 !isNativeJavaScriptObject(current) && | 529 ret.addAll(sortProperties(properties)); |
| 460 JS("bool", "# !== Object.prototype", current)) { | 530 addMetadataChildren(object, ret); |
| 461 protoChain.add(current); | 531 return ret.toList(); |
| 462 current = safeGetProperty(current, '__proto__'); | |
| 463 } | |
| 464 | |
| 465 // We walk the prototype chain for symbol properties because they take | |
| 466 // priority and are accessed instead of Dart properties according to Dart | |
| 467 // calling conventions. | |
| 468 // TODO(jacobr): where possible use the data stored by dart.setSignature | |
| 469 // instead of walking the JavaScript object directly. | |
| 470 for (current in protoChain) { | |
| 471 for (var symbol in getOwnPropertySymbols(current)) { | |
| 472 var dartName = symbolName(symbol); | |
| 473 if (hasMethod(object, dartName)) { | |
| 474 continue; | |
| 475 } | |
| 476 // TODO(jacobr): find a cleaner solution than checking for dartx | |
| 477 String dartXPrefix = 'dartx.'; | |
| 478 if (dartName.startsWith(dartXPrefix)) { | |
| 479 dartName = dartName.substring(dartXPrefix.length); | |
| 480 } else if (!dartName.startsWith('_')) { | |
| 481 // Dart method extension names should either be from dartx or should | |
| 482 // start with an _ | |
| 483 continue; | |
| 484 } | |
| 485 var value = safeGetProperty(object, symbol); | |
| 486 properties.add(new NameValuePair(name: dartName, value: value)); | |
| 487 } | |
| 488 } | |
| 489 | |
| 490 for (current in protoChain) { | |
| 491 // TODO(jacobr): optionally distinguish properties and fields so that | |
| 492 // it is safe to expand untrusted objects without side effects. | |
| 493 var className = dart.getReifiedType(current).name; | |
| 494 for (var name in getOwnPropertyNames(current)) { | |
| 495 if (_customNames.contains(name) || name == className) continue; | |
| 496 if (hasMethod(object, name)) { | |
| 497 continue; | |
| 498 } | |
| 499 var value = safeGetProperty(object, name); | |
| 500 properties.add(new NameValuePair(name: name, value: value)); | |
| 501 } | |
| 502 } | |
| 503 | |
| 504 return properties.toList(); | |
| 505 } | 532 } |
| 506 } | 533 } |
| 507 | 534 |
| 508 /// Formatter for module Dart Library objects. | 535 /// Formatter for module Dart Library objects. |
| 509 class LibraryModuleFormatter implements Formatter { | 536 class LibraryModuleFormatter implements Formatter { |
| 510 accept(object, config) => dart.getDartLibraryName(object) != null; | 537 accept(object, config) => dart.getModuleName(object) != null; |
| 511 | 538 |
| 512 bool hasChildren(object) => true; | 539 bool hasChildren(object) => true; |
| 513 | 540 |
| 514 String preview(object) { | 541 String preview(object) { |
| 515 var libraryNames = dart.getDartLibraryName(object).split('/'); | 542 var libraryNames = dart.getModuleName(object).split('/'); |
| 516 // Library names are received with a repeat directory name, so strip the | 543 // Library names are received with a repeat directory name, so strip the |
| 517 // last directory entry here to make the path cleaner. For example, the | 544 // last directory entry here to make the path cleaner. For example, the |
| 518 // library "third_party/dart/utf/utf" shoud display as | 545 // library "third_party/dart/utf/utf" shoud display as |
| 519 // "third_party/dart/utf/". | 546 // "third_party/dart/utf/". |
| 520 if (libraryNames.length > 1) { | 547 if (libraryNames.length > 1 && |
| 548 libraryNames.last == libraryNames[libraryNames.length - 2]) { |
| 521 libraryNames[libraryNames.length - 1] = ''; | 549 libraryNames[libraryNames.length - 1] = ''; |
| 522 } | 550 } |
| 523 return 'Library Module: ${libraryNames.join('/')}'; | 551 return 'Library Module: ${libraryNames.join('/')}'; |
| 524 } | 552 } |
| 525 | 553 |
| 526 List<NameValuePair> children(object) { | 554 List<NameValuePair> children(object) { |
| 527 var children = new LinkedHashSet<NameValuePair>(); | 555 var children = new LinkedHashSet<NameValuePair>(); |
| 528 for (var name in getOwnPropertyNames(object)) { | 556 for (var name in getOwnPropertyNames(object)) { |
| 529 var value = safeGetProperty(object, name); | 557 var value = safeGetProperty(object, name); |
| 530 // Replace __ with / to make file paths more readable. Then | |
| 531 // 'src__result__error' becomes 'src/result/error'. | |
| 532 name = '${name.replaceAll("__", "/")}.dart'; | |
| 533 children.add(new NameValuePair( | 558 children.add(new NameValuePair( |
| 534 name: name, value: new Library(name, value), hideName: true)); | 559 name: name, value: new Library(name, value), hideName: true)); |
| 535 } | 560 } |
| 536 return children.toList(); | 561 return children.toList(); |
| 537 } | 562 } |
| 538 } | 563 } |
| 539 | 564 |
| 540 class LibraryFormatter implements Formatter { | 565 class LibraryFormatter implements Formatter { |
| 541 var genericParameters = new HashMap<String, String>(); | 566 var genericParameters = new HashMap<String, String>(); |
| 542 | 567 |
| 543 accept(object, config) => object is Library; | 568 accept(object, config) => object is Library; |
| 544 | 569 |
| 545 bool hasChildren(object) => true; | 570 bool hasChildren(object) => true; |
| 546 | 571 |
| 547 String preview(object) => object.name; | 572 String preview(object) => object.name; |
| 548 | 573 |
| 549 List<NameValuePair> children(object) { | 574 List<NameValuePair> children(object) { |
| 575 // Maintain library member order rather than sorting members as is the |
| 576 // case for class members. |
| 550 var children = new LinkedHashSet<NameValuePair>(); | 577 var children = new LinkedHashSet<NameValuePair>(); |
| 551 var nonGenericProperties = new LinkedHashMap<String, Object>(); | |
| 552 var objectProperties = safeProperties(object.object); | 578 var objectProperties = safeProperties(object.object); |
| 553 objectProperties.forEach((name, value) { | 579 objectProperties.forEach((name, value) { |
| 554 var genericTypeConstructor = dart.getGenericTypeCtor(value); | 580 // Skip the generic constructors for each class as users are only |
| 555 if (genericTypeConstructor != null) { | 581 // interested in seeing the actual classes. |
| 556 recordGenericParameters(name, genericTypeConstructor); | 582 if (dart.getGenericTypeCtor(value) != null) return; |
| 557 } else { | 583 |
| 558 nonGenericProperties[name] = value; | 584 children.add(dart.isType(value) |
| 559 } | 585 ? classChild(name, value) |
| 560 }); | 586 : new NameValuePair(name: name, value: value)); |
| 561 nonGenericProperties.forEach((name, value) { | |
| 562 if (value is Type) { | |
| 563 children.add(classChild(name, value)); | |
| 564 } else { | |
| 565 children.add(new NameValuePair(name: name, value: value)); | |
| 566 } | |
| 567 }); | 587 }); |
| 568 return children.toList(); | 588 return children.toList(); |
| 569 } | 589 } |
| 570 | 590 |
| 571 recordGenericParameters(String name, Object genericTypeConstructor) { | |
| 572 // Using JS toString() eliminates the leading metadata that is generated | |
| 573 // with the toString function provided in operations.dart. | |
| 574 // Splitting by => and taking the first element gives the list of | |
| 575 // arguments in the constructor. | |
| 576 genericParameters[name] = | |
| 577 JS('String', '#.toString()', genericTypeConstructor) | |
| 578 .split(' =>') | |
| 579 .first | |
| 580 .replaceAll(new RegExp(r'[(|)]'), ''); | |
| 581 } | |
| 582 | |
| 583 classChild(String name, Object child) { | 591 classChild(String name, Object child) { |
| 584 var typeName = getTypeName(child); | 592 var typeName = getTypeName(child); |
| 585 // Generic class names are generated with a $ at the end, so the | 593 return new NameValuePair( |
| 586 // corresponding non-generic class can be identified by adding $. | 594 name: typeName, value: child, config: JsonMLConfig.asClass); |
| 587 var parameterName = '$name\$'; | |
| 588 if (genericParameters.keys.contains(parameterName)) { | |
| 589 typeName = '$typeName<${genericParameters[parameterName]}>'; | |
| 590 // TODO(bmilligan): Add a symbol to classes with generic types at their | |
| 591 // creation so they can be recognized independently by the debugger. | |
| 592 JSNative.setProperty(child, 'genericTypeName', typeName); | |
| 593 } | |
| 594 return new NameValuePair(name: typeName, value: child); | |
| 595 } | 595 } |
| 596 } | 596 } |
| 597 | 597 |
| 598 /// Formatter for Dart Function objects. | 598 /// Formatter for Dart Function objects. |
| 599 /// Dart functions happen to be regular JavaScript Function objects but | 599 /// Dart functions happen to be regular JavaScript Function objects but |
| 600 /// we can distinguish them based on whether they have been tagged with | 600 /// we can distinguish them based on whether they have been tagged with |
| 601 /// runtime type information. | 601 /// runtime type information. |
| 602 class FunctionFormatter implements Formatter { | 602 class FunctionFormatter implements Formatter { |
| 603 accept(object, config) { | 603 accept(object, config) { |
| 604 if (_typeof(object) != 'function') return false; | 604 if (_typeof(object) != 'function') return false; |
| (...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 763 List<NameValuePair> children(object) => object | 763 List<NameValuePair> children(object) => object |
| 764 .toString() | 764 .toString() |
| 765 .split('\n') | 765 .split('\n') |
| 766 .map((line) => new NameValuePair( | 766 .map((line) => new NameValuePair( |
| 767 value: line.replaceFirst(new RegExp(r'^\s+at\s'), ''), | 767 value: line.replaceFirst(new RegExp(r'^\s+at\s'), ''), |
| 768 hideName: true)) | 768 hideName: true)) |
| 769 .toList(); | 769 .toList(); |
| 770 } | 770 } |
| 771 | 771 |
| 772 class ClassFormatter implements Formatter { | 772 class ClassFormatter implements Formatter { |
| 773 accept(object, config) => object is Type || config == JsonMLConfig.asClass; | 773 accept(object, config) => config == JsonMLConfig.asClass; |
| 774 | 774 |
| 775 String preview(object) { | 775 String preview(type) { |
| 776 var typeName = safeGetProperty(object, 'genericTypeName'); | |
| 777 if (typeName != null) return typeName; | |
| 778 var type = _getType(object); | |
| 779 var implements = dart.getImplements(type); | 776 var implements = dart.getImplements(type); |
| 780 typeName = getTypeName(type); | 777 var typeName = getTypeName(type); |
| 781 if (implements != null) { | 778 if (implements != null) { |
| 782 var typeNames = implements().map(getTypeName); | 779 var typeNames = implements().map(getTypeName); |
| 783 return '${typeName} implements ${typeNames.join(", ")}'; | 780 return '${typeName} implements ${typeNames.join(", ")}'; |
| 784 } else { | 781 } else { |
| 785 return typeName; | 782 return typeName; |
| 786 } | 783 } |
| 787 } | 784 } |
| 788 | 785 |
| 789 bool hasChildren(object) => true; | 786 bool hasChildren(object) => true; |
| 790 | 787 |
| 791 List<NameValuePair> children(object) { | 788 List<NameValuePair> children(type) { |
| 792 // TODO(jacobr): add other entries describing the class such as | 789 // TODO(jacobr): add other entries describing the class such as |
| 793 // links to the superclass, mixins, implemented interfaces, and methods. | 790 // implemented interfaces, and methods. |
| 794 var type = _getType(object); | 791 var ret = new LinkedHashSet<NameValuePair>(); |
| 795 var children = <NameValuePair>[]; | 792 |
| 796 var typeName = getTypeName(_getType(object)); | 793 var staticProperties = new Set<NameValuePair>(); |
| 794 var staticMethods = new Set<NameValuePair>(); |
| 795 // Static fields and properties. |
| 796 addPropertiesFromSignature( |
| 797 dart.getStaticFieldSig(type), staticProperties, type, false); |
| 798 addPropertiesFromSignature( |
| 799 dart.getStaticGetterSig(type), staticProperties, type, false); |
| 800 // static methods. |
| 801 addPropertiesFromSignature( |
| 802 dart.getStaticSig(type), staticMethods, type, false); |
| 803 |
| 804 if (staticProperties.isNotEmpty || staticMethods.isNotEmpty) { |
| 805 ret |
| 806 ..add(new NameValuePair(value: '[[Static members]]', hideName: true)) |
| 807 ..addAll(sortProperties(staticProperties)) |
| 808 ..addAll(sortProperties(staticMethods)); |
| 809 } |
| 810 |
| 811 // instance methods. |
| 812 var instanceMethods = new Set<NameValuePair>(); |
| 813 // Instance methods are defined on the prototype not the constructor object. |
| 814 addPropertiesFromSignature(dart.getMethodSig(type), instanceMethods, |
| 815 JS('', '#.prototype', type), false, |
| 816 tagTypes: true); |
| 817 if (instanceMethods.isNotEmpty) { |
| 818 ret |
| 819 ..add(new NameValuePair(value: '[[Instance Methods]]', hideName: true)) |
| 820 ..addAll(sortProperties(instanceMethods)); |
| 821 } |
| 822 |
| 823 var typeName = getTypeName(type); |
| 797 var mixins = dart.getMixins(type); | 824 var mixins = dart.getMixins(type); |
| 798 if (mixins != null && mixins.isNotEmpty) { | 825 if (mixins != null && mixins.isNotEmpty) { |
| 799 children.add(new NameValuePair( | 826 ret.add(new NameValuePair( |
| 800 name: '[[Mixins]]', value: new HeritageClause('mixins', mixins))); | 827 name: '[[Mixins]]', value: new HeritageClause('mixins', mixins))); |
| 801 } | 828 } |
| 802 | 829 |
| 803 var hiddenProperties = ['length', 'name', 'prototype', 'genericTypeName']; | 830 var baseProto = JS('', '#.__proto__', type); |
| 804 // Addition of NameValuePairs for static variables and named constructors. | 831 if (baseProto != null && !dart.isJsInterop(baseProto)) { |
| 805 for (var name in getOwnPropertyNames(object)) { | 832 ret.add(new NameValuePair( |
| 806 // TODO(bmilligan): Perform more principled checks to filter out spurious | 833 name: "[[base class]]", |
| 807 // members. | 834 value: baseProto, |
| 808 if (hiddenProperties.contains(name)) continue; | 835 config: JsonMLConfig.asClass)); |
| 809 var value = safeGetProperty(object, name); | |
| 810 if (value != null && dart.getIsNamedConstructor(value) != null) { | |
| 811 value = new NamedConstructor(value); | |
| 812 name = '${typeName}.$name'; | |
| 813 } | |
| 814 children.add(new NameValuePair(name: name, value: value)); | |
| 815 } | 836 } |
| 816 | 837 |
| 817 // TODO(bmilligan): Replace the hard coding of $identityHash. | 838 // TODO(jacobr): add back fields for named constructors. |
| 818 var hiddenPrototypeProperties = ['constructor', 'new', r'$identityHash']; | 839 return ret.toList(); |
| 819 // Addition of class methods. | |
| 820 var prototype = JS('var', '#["prototype"]', object); | |
| 821 if (prototype != null) { | |
| 822 for (var name in getOwnPropertyNames(prototype)) { | |
| 823 if (hiddenPrototypeProperties.contains(name)) continue; | |
| 824 // Simulate dart.bind by using dart.tag and tear off the function | |
| 825 // so it will be recognized by the FunctionFormatter. | |
| 826 var function = safeGetProperty(prototype, name); | |
| 827 var constructor = safeGetProperty(prototype, 'constructor'); | |
| 828 var sigObj = dart.getMethodSig(constructor); | |
| 829 if (sigObj != null) { | |
| 830 var value = safeGetProperty(sigObj, name); | |
| 831 if (getTypeName(dart.getReifiedType(value)) != 'Null') { | |
| 832 dart.tag(function, value); | |
| 833 children.add(new NameValuePair(name: name, value: function)); | |
| 834 } | |
| 835 } | |
| 836 } | |
| 837 } | |
| 838 return children; | |
| 839 } | 840 } |
| 840 } | 841 } |
| 841 | 842 |
| 843 class TypeFormatter implements Formatter { |
| 844 accept(object, config) => object is Type; |
| 845 |
| 846 String preview(object) => object.toString(); |
| 847 |
| 848 bool hasChildren(object) => false; |
| 849 |
| 850 List<NameValuePair> children(object) => []; |
| 851 } |
| 852 |
| 842 /// This entry point is automatically invoked by the code generated by | 853 /// This entry point is automatically invoked by the code generated by |
| 843 /// Dart Dev Compiler | 854 /// Dart Dev Compiler |
| 844 registerDevtoolsFormatter() { | 855 registerDevtoolsFormatter() { |
| 845 var formatters = [_devtoolsFormatter]; | 856 var formatters = [_devtoolsFormatter]; |
| 846 JS('', 'dart.global.devtoolsFormatters = #', formatters); | 857 JS('', 'dart.global.devtoolsFormatters = #', formatters); |
| 847 } | 858 } |
| 859 |
| 860 // Expose these methods here to facilitate writing debugger tests. |
| 861 // If export worked for private SDK libraries we could just export |
| 862 // these methods from dart:_runtime. |
| 863 |
| 864 getModuleNames() { |
| 865 return dart.getModuleNames(); |
| 866 } |
| 867 |
| 868 getModuleLibraries(String name) { |
| 869 return dart.getModuleLibraries(name); |
| 870 } |
| OLD | NEW |