| 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'; |
| 11 import 'dart:html' as html; |
| 10 | 12 |
| 11 /// Config object to pass to devtools to signal that an object should not be | 13 /// Config object to pass to devtools to signal that an object should not be |
| 12 /// formatted by the Dart formatter. This is used to specify that an Object | 14 /// formatted by the Dart formatter. This is used to specify that an Object |
| 13 /// should just be displayed using the regular JavaScript view instead of a | 15 /// should just be displayed using the regular JavaScript view instead of a |
| 14 /// custom Dart view. For example, this is used to display the JavaScript view | 16 /// custom Dart view. For example, this is used to display the JavaScript view |
| 15 /// of a Dart Function as a child of the regular Function object. | 17 /// of a Dart Function as a child of the regular Function object. |
| 16 const skipDartConfig = const Object(); | 18 const skipDartConfig = const Object(); |
| 17 final int maxIterableChildrenToDisplay = 50; | 19 final int maxIterableChildrenToDisplay = 50; |
| 18 | 20 |
| 19 var _devtoolsFormatter = new JsonMLFormatter(new DartFormatter()); | 21 var _devtoolsFormatter = new JsonMLFormatter(new DartFormatter()); |
| 20 | 22 |
| 21 String _typeof(object) => JS('String', 'typeof #', object); | 23 String _typeof(object) => JS('String', 'typeof #', object); |
| 22 bool _instanceof(object, clazz) => JS('bool', '# instanceof #', object, clazz); | |
| 23 | 24 |
| 24 List<String> getOwnPropertyNames(object) => JS('List<String>', | 25 List<String> getOwnPropertyNames(object) => JS('List<String>', |
| 25 'dart.list(Object.getOwnPropertyNames(#), #)', object, String); | 26 'dart.list(Object.getOwnPropertyNames(#), #)', object, String); |
| 26 | 27 |
| 27 List getOwnPropertySymbols(object) => | 28 List getOwnPropertySymbols(object) => |
| 28 JS('List', 'Object.getOwnPropertySymbols(#)', object); | 29 JS('List', 'Object.getOwnPropertySymbols(#)', object); |
| 29 | 30 |
| 30 // TODO(jacobr): move this to dart:js and fully implement. | 31 // TODO(jacobr): move this to dart:js and fully implement. |
| 31 class JSNative { | 32 class JSNative { |
| 32 // Name may be a String or a Symbol. | 33 // Name may be a String or a Symbol. |
| 33 static getProperty(object, name) => JS('', '#[#]', object, name); | 34 static getProperty(object, name) => JS('', '#[#]', object, name); |
| 34 // Name may be a String or a Symbol. | 35 // Name may be a String or a Symbol. |
| 35 static setProperty(object, name, value) => | 36 static setProperty(object, name, value) => |
| 36 JS('', '#[#]=#', object, name, value); | 37 JS('', '#[#]=#', object, name, value); |
| 37 } | 38 } |
| 38 | 39 |
| 39 bool isRegularDartObject(object) { | |
| 40 if (_typeof(object) == 'function') return false; | |
| 41 return _instanceof(object, JS('Type', '#', Object)); | |
| 42 } | |
| 43 | |
| 44 String getObjectTypeName(object) { | 40 String getObjectTypeName(object) { |
| 45 var reifiedType = dart.getReifiedType(object); | 41 var reifiedType = dart.getReifiedType(object); |
| 46 if (reifiedType == null) { | 42 if (reifiedType == null) { |
| 47 if (_typeof(object) == 'function') { | 43 if (_typeof(object) == 'function') { |
| 48 return '[[Raw JavaScript Function]]'; | 44 return '[[Raw JavaScript Function]]'; |
| 49 } | 45 } |
| 50 return '<Error getting type name>'; | 46 return '<Error getting type name>'; |
| 51 } | 47 } |
| 52 return getTypeName(reifiedType); | 48 return getTypeName(reifiedType); |
| 53 } | 49 } |
| 54 | 50 |
| 55 String getTypeName(Type type) { | 51 String getTypeName(Type type) { |
| 56 var name = dart.typeName(type); | 52 var name = dart.typeName(type); |
| 57 // Hack to cleanup names for List<dynamic> | 53 // Hack to cleanup names for List<dynamic> |
| 58 // TODO(jacobr): it would be nice if there was a way we could distinguish | 54 // TODO(jacobr): it would be nice if there was a way we could distinguish |
| 59 // between a List<dynamic> created from Dart and an Array passed in from | 55 // between a List<dynamic> created from Dart and an Array passed in from |
| 60 // JavaScript. | 56 // JavaScript. |
| 61 if (name == 'JSArray<dynamic>' || | 57 if (name == 'JSArray<dynamic>' || name == 'JSObject<Array>') |
| 62 name == 'JSObject<Array>') return 'List<dynamic>'; | 58 return 'List<dynamic>'; |
| 63 return name; | 59 return name; |
| 64 } | 60 } |
| 65 | 61 |
| 66 String safePreview(object) { | 62 String safePreview(object) { |
| 67 try { | 63 try { |
| 68 var preview = _devtoolsFormatter._simpleFormatter.preview(object); | 64 var preview = _devtoolsFormatter._simpleFormatter.preview(object); |
| 69 if (preview != null) return preview; | 65 if (preview != null) return preview; |
| 70 return object.toString(); | 66 return object.toString(); |
| 71 } catch (e) { | 67 } catch (e) { |
| 72 return '<Exception thrown>'; | 68 return '<Exception thrown> $e'; |
| 73 } | 69 } |
| 74 } | 70 } |
| 75 | 71 |
| 76 String symbolName(symbol) { | 72 String symbolName(symbol) { |
| 77 var name = symbol.toString(); | 73 var name = symbol.toString(); |
| 78 assert(name.startsWith('Symbol(')); | 74 assert(name.startsWith('Symbol(')); |
| 79 return name.substring('Symbol('.length, name.length - 1); | 75 return name.substring('Symbol('.length, name.length - 1); |
| 80 } | 76 } |
| 81 | 77 |
| 82 bool hasMethod(object, String name) { | 78 bool hasMethod(object, String name) { |
| 83 try { | 79 try { |
| 84 return dart.hasMethod(object, name); | 80 return dart.hasMethod(object, name); |
| 85 } catch (e) { | 81 } catch (e) { |
| 86 return false; | 82 return false; |
| 87 } | 83 } |
| 88 } | 84 } |
| 89 | 85 |
| 90 /// [JsonMLFormatter] consumes [NameValuePair] objects and | 86 /// [JsonMLFormatter] consumes [NameValuePair] objects and |
| 91 class NameValuePair { | 87 class NameValuePair { |
| 92 NameValuePair({this.name, this.value, bool skipDart}) | 88 NameValuePair({this.name, this.value, bool skipDart}) |
| 93 : skipDart = skipDart == true; | 89 : skipDart = skipDart == true; |
| 94 | 90 |
| 91 // Define equality and hashCode so that NameValuePair can be used |
| 92 // in a Set to dedupe entries with duplicate names. |
| 93 operator ==(other) => other is NameValuePair && other.name == name; |
| 94 int get hashCode => name.hashCode; |
| 95 |
| 95 final String name; | 96 final String name; |
| 96 final Object value; | 97 final Object value; |
| 97 final bool skipDart; | 98 final bool skipDart; |
| 98 } | 99 } |
| 99 | 100 |
| 100 class MapEntry { | 101 class MapEntry { |
| 101 MapEntry({this.key, this.value}); | 102 MapEntry({this.key, this.value}); |
| 102 | 103 |
| 103 final String key; | 104 final String key; |
| 104 final Object value; | 105 final Object value; |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 157 JSNative.setProperty(_attributes, key, value); | 158 JSNative.setProperty(_attributes, key, value); |
| 158 } | 159 } |
| 159 | 160 |
| 160 createTextChild(String text) { | 161 createTextChild(String text) { |
| 161 _jsonML.add(text); | 162 _jsonML.add(text); |
| 162 } | 163 } |
| 163 | 164 |
| 164 toJsonML() => _jsonML; | 165 toJsonML() => _jsonML; |
| 165 } | 166 } |
| 166 | 167 |
| 168 /// Whether an object is a native JavaScript type where we should display the |
| 169 /// JavaScript view of the object instead of the custom Dart specific render |
| 170 /// of properties. |
| 171 bool isNativeJavaScriptObject(object) { |
| 172 var type = _typeof(object); |
| 173 // Treat Node objects as a native JavaScript type as the regular DOM render |
| 174 // in devtools is superior to the dart specific view. |
| 175 return (type != 'object' && type != 'function') || |
| 176 object is dart.JSObject || |
| 177 object is html.Node; |
| 178 } |
| 179 |
| 167 /// Class implementing the Devtools Formatter API described by: | 180 /// Class implementing the Devtools Formatter API described by: |
| 168 /// https://docs.google.com/document/d/1FTascZXT9cxfetuPRT2eXPQKXui4nWFivUnS_335
T3U | 181 /// https://docs.google.com/document/d/1FTascZXT9cxfetuPRT2eXPQKXui4nWFivUnS_335
T3U |
| 169 /// Specifically, a formatter implements a header, hasBody, and body method. | 182 /// Specifically, a formatter implements a header, hasBody, and body method. |
| 170 /// This class renders the simple structured format objects [_simpleFormatter] | 183 /// This class renders the simple structured format objects [_simpleFormatter] |
| 171 /// provides as JsonML. | 184 /// provides as JsonML. |
| 172 class JsonMLFormatter { | 185 class JsonMLFormatter { |
| 173 // TODO(jacobr): define a SimpleFormatter base class that DartFormatter | 186 // TODO(jacobr): define a SimpleFormatter base class that DartFormatter |
| 174 // implements if we decide to use this class elsewhere. We specify that the | 187 // implements if we decide to use this class elsewhere. We specify that the |
| 175 // type is DartFormatter here purely to get type checking benefits not because | 188 // type is DartFormatter here purely to get type checking benefits not because |
| 176 // this class is really intended to only support instances of type | 189 // this class is really intended to only support instances of type |
| 177 // DartFormatter. | 190 // DartFormatter. |
| 178 DartFormatter _simpleFormatter; | 191 DartFormatter _simpleFormatter; |
| 179 | 192 |
| 180 JsonMLFormatter(this._simpleFormatter); | 193 JsonMLFormatter(this._simpleFormatter); |
| 181 | 194 |
| 182 header(object, config) { | 195 header(object, config) { |
| 183 if (identical(config, skipDartConfig)) return null; | 196 if (identical(config, skipDartConfig) || isNativeJavaScriptObject(object)) { |
| 197 return null; |
| 198 } |
| 184 | 199 |
| 185 var c = _simpleFormatter.preview(object); | 200 var c = _simpleFormatter.preview(object); |
| 186 if (c == null) return null; | 201 if (c == null) return null; |
| 187 | 202 |
| 188 // Indicate this is a Dart Object by using a Dart background color. | 203 // Indicate this is a Dart Object by using a Dart background color. |
| 189 // This is stylistically a bit ugly but it eases distinguishing Dart and | 204 // This is stylistically a bit ugly but it eases distinguishing Dart and |
| 190 // JS objects. | 205 // JS objects. |
| 191 var element = new JsonMLElement('span') | 206 var element = new JsonMLElement('span') |
| 192 ..setStyle('background-color: #d9edf7') | 207 ..setStyle('background-color: #d9edf7') |
| 193 ..createTextChild(c); | 208 ..createTextChild(c); |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 250 new MapFormatter(), | 265 new MapFormatter(), |
| 251 new IterableFormatter(), | 266 new IterableFormatter(), |
| 252 new MapEntryFormatter(), | 267 new MapEntryFormatter(), |
| 253 new ClassMetadataFormatter(), | 268 new ClassMetadataFormatter(), |
| 254 new HeritageClauseFormatter(), | 269 new HeritageClauseFormatter(), |
| 255 new ObjectFormatter() | 270 new ObjectFormatter() |
| 256 ]; | 271 ]; |
| 257 } | 272 } |
| 258 | 273 |
| 259 String preview(object) { | 274 String preview(object) { |
| 260 if (object == null) return 'null'; | 275 try { |
| 261 if (object is num) return object.toString(); | 276 if (object == null || |
| 262 if (object is String) return '"$object"'; | 277 object is num || |
| 278 object is String || |
| 279 isNativeJavaScriptObject(object)) { |
| 280 return object.toString(); |
| 281 } |
| 263 | 282 |
| 264 for (var formatter in _formatters) { | 283 for (var formatter in _formatters) { |
| 265 if (formatter.accept(object)) return formatter.preview(object); | 284 if (formatter.accept(object)) return formatter.preview(object); |
| 285 } |
| 286 } catch (e, trace) { |
| 287 // Log formatter internal errors as unfortunately the devtools cannot |
| 288 // be used to debug formatter errors. |
| 289 html.window.console.error("Caught exception $e\n trace:\n$trace"); |
| 266 } | 290 } |
| 267 | 291 |
| 268 return null; | 292 return null; |
| 269 } | 293 } |
| 270 | 294 |
| 271 bool hasChildren(object) { | 295 bool hasChildren(object) { |
| 272 if (object == null) return false; | 296 if (object == null) return false; |
| 273 | 297 try { |
| 274 for (var formatter in _formatters) { | 298 for (var formatter in _formatters) { |
| 275 if (formatter.accept(object)) return formatter.hasChildren(object); | 299 if (formatter.accept(object)) return formatter.hasChildren(object); |
| 300 } |
| 301 } catch (e, trace) { |
| 302 // See comment for preview. |
| 303 html.window.console |
| 304 .error("[hasChildren] Caught exception $e\n trace:\n$trace"); |
| 276 } | 305 } |
| 277 | |
| 278 return false; | 306 return false; |
| 279 } | 307 } |
| 280 | 308 |
| 281 List<NameValuePair> children(object) { | 309 List<NameValuePair> children(object) { |
| 282 if (object != null) { | 310 try { |
| 283 for (var formatter in _formatters) { | 311 if (object != null) { |
| 284 if (formatter.accept(object)) return formatter.children(object); | 312 for (var formatter in _formatters) { |
| 313 if (formatter.accept(object)) return formatter.children(object); |
| 314 } |
| 285 } | 315 } |
| 316 } catch (e, trace) { |
| 317 // See comment for preview. |
| 318 html.window.console.error("Caught exception $e\n trace:\n$trace"); |
| 286 } | 319 } |
| 287 return <NameValuePair>[]; | 320 return <NameValuePair>[]; |
| 288 } | 321 } |
| 289 } | 322 } |
| 290 | 323 |
| 291 /// Default formatter for Dart Objects. | 324 /// Default formatter for Dart Objects. |
| 292 class ObjectFormatter extends Formatter { | 325 class ObjectFormatter extends Formatter { |
| 293 bool accept(object) => isRegularDartObject(object); | 326 static Set<String> _customNames = new Set() |
| 327 ..add('constructor') |
| 328 ..add('prototype') |
| 329 ..add('__proto__'); |
| 330 bool accept(object) => !isNativeJavaScriptObject(object); |
| 294 | 331 |
| 295 String preview(object) => getObjectTypeName(object); | 332 String preview(object) => getObjectTypeName(object); |
| 296 | 333 |
| 297 bool hasChildren(object) => true; | 334 bool hasChildren(object) => true; |
| 298 | 335 |
| 299 /// Helper to add members walking up the prototype chain being careful | 336 List<NameValuePair> children(object) { |
| 300 /// to avoid properties that are Dart methods. | 337 var properties = new LinkedHashSet<NameValuePair>(); |
| 301 _addMembers(current, object, List<NameValuePair> properties) { | 338 // Set of property names used to avoid duplicates. |
| 302 // TODO(jacobr): optionally distinguish properties and fields so that | 339 addMetadataChildren(object, properties); |
| 303 // it is safe to expand untrusted objects without side effects. | 340 |
| 304 var className = dart.getReifiedType(current).name; | 341 /// Helper to add members walking up the prototype chain being careful |
| 305 for (var name in getOwnPropertyNames(current)) { | 342 /// to avoid properties that are Dart methods. |
| 306 if (name == 'constructor' || | 343 var protoChain = <Object>[]; |
| 307 name == '__proto__' || | 344 var current = object; |
| 308 name == className) continue; | 345 while (current != null && |
| 309 if (hasMethod(object, name)) { | 346 !isNativeJavaScriptObject(current) && |
| 310 continue; | 347 JS("bool", "# !== Object.prototype", current)) { |
| 348 protoChain.add(current); |
| 349 current = JSNative.getProperty(current, '__proto__'); |
| 350 } |
| 351 |
| 352 // We walk the prototype chain for symbol properties because they take |
| 353 // priority and are accessed instead of Dart properties according to Dart |
| 354 // calling conventions. |
| 355 // TODO(jacobr): where possible use the data stored by dart.setSignature |
| 356 // instead of walking the JavaScript object directly. |
| 357 for (current in protoChain) { |
| 358 for (var symbol in getOwnPropertySymbols(current)) { |
| 359 var dartName = symbolName(symbol); |
| 360 if (hasMethod(object, dartName)) { |
| 361 continue; |
| 362 } |
| 363 // TODO(jacobr): find a cleaner solution than checking for dartx |
| 364 String dartXPrefix = 'dartx.'; |
| 365 if (dartName.startsWith(dartXPrefix)) { |
| 366 dartName = dartName.substring(dartXPrefix.length); |
| 367 } else if (!dartName.startsWith('_')) { |
| 368 // Dart method extension names should either be from dartx or should |
| 369 // start with an _ |
| 370 continue; |
| 371 } |
| 372 var value; |
| 373 try { |
| 374 value = JSNative.getProperty(object, symbol); |
| 375 } catch (e) { |
| 376 value = '<Exception thrown> $e'; |
| 377 } |
| 378 properties.add(new NameValuePair(name: dartName, value: value)); |
| 311 } | 379 } |
| 312 var value; | 380 } |
| 313 try { | 381 |
| 314 value = JSNative.getProperty(object, name); | 382 for (current in protoChain) { |
| 315 } catch (e) { | 383 // TODO(jacobr): optionally distinguish properties and fields so that |
| 316 value = '<Exception thrown>'; | 384 // it is safe to expand untrusted objects without side effects. |
| 385 var className = dart.getReifiedType(current).name; |
| 386 for (var name in getOwnPropertyNames(current)) { |
| 387 if (_customNames.contains(name) || name == className) continue; |
| 388 if (hasMethod(object, name)) { |
| 389 continue; |
| 390 } |
| 391 var value; |
| 392 try { |
| 393 value = JSNative.getProperty(object, name); |
| 394 } catch (e) { |
| 395 value = '<Exception thrown> $e'; |
| 396 } |
| 397 properties.add(new NameValuePair(name: name, value: value)); |
| 317 } | 398 } |
| 318 properties.add(new NameValuePair(name: name, value: value)); | |
| 319 } | 399 } |
| 320 for (var symbol in getOwnPropertySymbols(current)) { | 400 |
| 321 var dartName = symbolName(symbol); | 401 return properties.toList(); |
| 322 if (hasMethod(object, dartName)) { | |
| 323 continue; | |
| 324 } | |
| 325 var value; | |
| 326 try { | |
| 327 value = JSNative.getProperty(object, symbol); | |
| 328 } catch (e) { | |
| 329 value = '<Exception thrown>'; | |
| 330 } | |
| 331 properties.add(new NameValuePair(name: dartName, value: value)); | |
| 332 } | |
| 333 var base = JSNative.getProperty(current, '__proto__'); | |
| 334 if (base == null) return; | |
| 335 if (isRegularDartObject(base)) { | |
| 336 _addMembers(base, object, properties); | |
| 337 } | |
| 338 } | 402 } |
| 339 | 403 |
| 340 List<NameValuePair> children(object) { | 404 addMetadataChildren(object, Set<NameValuePair> ret) { |
| 341 var properties = <NameValuePair>[]; | |
| 342 addMetadataChildren(object, properties); | |
| 343 _addMembers(object, object, properties); | |
| 344 return properties; | |
| 345 } | |
| 346 | |
| 347 addMetadataChildren(object, List<NameValuePair> ret) { | |
| 348 ret.add( | 405 ret.add( |
| 349 new NameValuePair(name: '[[class]]', value: new ClassMetadata(object))); | 406 new NameValuePair(name: '[[class]]', value: new ClassMetadata(object))); |
| 350 } | 407 } |
| 351 } | 408 } |
| 352 | 409 |
| 353 /// Formatter for Dart Function objects. | 410 /// Formatter for Dart Function objects. |
| 354 /// Dart functions happen to be regular JavaScript Function objects but | 411 /// Dart functions happen to be regular JavaScript Function objects but |
| 355 /// we can distinguish them based on whether they have been tagged with | 412 /// we can distinguish them based on whether they have been tagged with |
| 356 /// runtime type information. | 413 /// runtime type information. |
| 357 class FunctionFormatter extends Formatter { | 414 class FunctionFormatter extends Formatter { |
| (...skipping 24 matching lines...) Expand all Loading... |
| 382 String preview(object) { | 439 String preview(object) { |
| 383 Map map = object; | 440 Map map = object; |
| 384 return '${getObjectTypeName(map)} length ${map.length}'; | 441 return '${getObjectTypeName(map)} length ${map.length}'; |
| 385 } | 442 } |
| 386 | 443 |
| 387 List<NameValuePair> children(object) { | 444 List<NameValuePair> children(object) { |
| 388 // TODO(jacobr): be lazier about enumerating contents of Maps that are not | 445 // TODO(jacobr): be lazier about enumerating contents of Maps that are not |
| 389 // the build in LinkedHashMap class. | 446 // the build in LinkedHashMap class. |
| 390 // TODO(jacobr): handle large Maps better. | 447 // TODO(jacobr): handle large Maps better. |
| 391 Map map = object; | 448 Map map = object; |
| 392 var keys = map.keys.toList(); | 449 var entries = new LinkedHashSet<NameValuePair>(); |
| 393 var entries = <NameValuePair>[]; | |
| 394 map.forEach((key, value) { | 450 map.forEach((key, value) { |
| 395 var entryWrapper = new MapEntry(key: key, value: value); | 451 var entryWrapper = new MapEntry(key: key, value: value); |
| 396 entries.add(new NameValuePair( | 452 entries.add(new NameValuePair( |
| 397 name: entries.length.toString(), value: entryWrapper)); | 453 name: entries.length.toString(), value: entryWrapper)); |
| 398 }); | 454 }); |
| 399 addMetadataChildren(object, entries); | 455 addMetadataChildren(object, entries); |
| 400 return entries; | 456 return entries.toList(); |
| 401 } | 457 } |
| 402 } | 458 } |
| 403 | 459 |
| 404 /// Formatter for Dart Iterable objects including List and Set. | 460 /// Formatter for Dart Iterable objects including List and Set. |
| 405 class IterableFormatter extends ObjectFormatter { | 461 class IterableFormatter extends ObjectFormatter { |
| 406 bool accept(object) => object is Iterable; | 462 bool accept(object) => object is Iterable; |
| 407 | 463 |
| 408 String preview(object) { | 464 String preview(object) { |
| 409 Iterable iterable = object; | 465 Iterable iterable = object; |
| 410 try { | 466 try { |
| 411 var length = iterable.length; | 467 var length = iterable.length; |
| 412 return '${getObjectTypeName(iterable)} length $length'; | 468 return '${getObjectTypeName(iterable)} length $length'; |
| 413 } catch (_) { | 469 } catch (_) { |
| 414 return '${getObjectTypeName(iterable)}'; | 470 return '${getObjectTypeName(iterable)}'; |
| 415 } | 471 } |
| 416 } | 472 } |
| 417 | 473 |
| 418 bool hasChildren(object) => true; | 474 bool hasChildren(object) => true; |
| 419 | 475 |
| 420 List<NameValuePair> children(object) { | 476 List<NameValuePair> children(object) { |
| 421 // TODO(jacobr): be lazier about enumerating contents of Iterables that | 477 // TODO(jacobr): be lazier about enumerating contents of Iterables that |
| 422 // are not the built in Set or List types. | 478 // are not the built in Set or List types. |
| 423 // TODO(jacobr): handle large Iterables better. | 479 // TODO(jacobr): handle large Iterables better. |
| 424 // TODO(jacobr): consider only using numeric indices | 480 // TODO(jacobr): consider only using numeric indices |
| 425 Iterable iterable = object; | 481 Iterable iterable = object; |
| 426 var ret = <NameValuePair>[]; | 482 var ret = new LinkedHashSet<NameValuePair>(); |
| 427 var i = 0; | 483 var i = 0; |
| 428 for (var entry in iterable) { | 484 for (var entry in iterable) { |
| 429 if (i > maxIterableChildrenToDisplay) { | 485 if (i > maxIterableChildrenToDisplay) { |
| 430 ret.add(new NameValuePair( | 486 ret.add(new NameValuePair( |
| 431 name: 'Warning', value: 'Truncated Iterable display')); | 487 name: 'Warning', value: 'Truncated Iterable display')); |
| 432 // TODO(jacobr): provide an expandable entry to show more entries. | 488 // TODO(jacobr): provide an expandable entry to show more entries. |
| 433 break; | 489 break; |
| 434 } | 490 } |
| 435 ret.add(new NameValuePair(name: i.toString(), value: entry)); | 491 ret.add(new NameValuePair(name: i.toString(), value: entry)); |
| 436 i++; | 492 i++; |
| 437 } | 493 } |
| 438 // TODO(jacobr): provide a link to show regular class properties here. | 494 // TODO(jacobr): provide a link to show regular class properties here. |
| 439 // required for subclasses of iterable, etc. | 495 // required for subclasses of iterable, etc. |
| 440 addMetadataChildren(object, ret); | 496 addMetadataChildren(object, ret); |
| 441 return ret; | 497 return ret.toList(); |
| 442 } | 498 } |
| 443 } | 499 } |
| 444 | 500 |
| 445 // This class does double duting displaying metadata for | 501 // This class does double duting displaying metadata for |
| 446 class ClassMetadataFormatter implements Formatter { | 502 class ClassMetadataFormatter implements Formatter { |
| 447 accept(object) => object is ClassMetadata; | 503 accept(object) => object is ClassMetadata; |
| 448 | 504 |
| 449 _getType(object) { | 505 _getType(object) { |
| 450 if (object is Type) return object; | 506 if (object is Type) return object; |
| 451 return dart.getReifiedType(object); | 507 return dart.getReifiedType(object); |
| (...skipping 12 matching lines...) Expand all Loading... |
| 464 // links to the superclass, mixins, implemented interfaces, and methods. | 520 // links to the superclass, mixins, implemented interfaces, and methods. |
| 465 var type = _getType(entry.object); | 521 var type = _getType(entry.object); |
| 466 var ret = <NameValuePair>[]; | 522 var ret = <NameValuePair>[]; |
| 467 var implements = dart.getImplements(type); | 523 var implements = dart.getImplements(type); |
| 468 if (implements != null) { | 524 if (implements != null) { |
| 469 ret.add(new NameValuePair( | 525 ret.add(new NameValuePair( |
| 470 name: '[[Implements]]', | 526 name: '[[Implements]]', |
| 471 value: new HeritageClause('implements', implements()))); | 527 value: new HeritageClause('implements', implements()))); |
| 472 } | 528 } |
| 473 var mixins = dart.getMixins(type); | 529 var mixins = dart.getMixins(type); |
| 474 if (mixins != null) { | 530 if (mixins != null && mixins.isNotEmpty) { |
| 475 ret.add(new NameValuePair( | 531 ret.add(new NameValuePair( |
| 476 name: '[[Mixins]]', value: new HeritageClause('mixins', mixins()))); | 532 name: '[[Mixins]]', value: new HeritageClause('mixins', mixins))); |
| 477 } | 533 } |
| 478 ret.add(new NameValuePair( | 534 ret.add(new NameValuePair( |
| 479 name: '[[JavaScript View]]', value: entry.object, skipDart: true)); | 535 name: '[[JavaScript View]]', value: entry.object, skipDart: true)); |
| 480 | 536 |
| 481 // TODO(jacobr): provide a link to the base class or perhaps the entire | 537 // TODO(jacobr): provide a link to the base class or perhaps the entire |
| 482 // base class hierarchy as a flat list. | 538 // base class hierarchy as a flat list. |
| 483 | 539 |
| 484 if (entry.object is! Type) { | 540 if (entry.object is! Type) { |
| 485 ret.add(new NameValuePair( | 541 ret.add(new NameValuePair( |
| 486 name: '[[JavaScript Constructor]]', | 542 name: '[[JavaScript Constructor]]', |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 531 return ret; | 587 return ret; |
| 532 } | 588 } |
| 533 } | 589 } |
| 534 | 590 |
| 535 /// This entry point is automatically invoked by the code generated by | 591 /// This entry point is automatically invoked by the code generated by |
| 536 /// Dart Dev Compiler | 592 /// Dart Dev Compiler |
| 537 registerDevtoolsFormatter() { | 593 registerDevtoolsFormatter() { |
| 538 var formatters = [_devtoolsFormatter]; | 594 var formatters = [_devtoolsFormatter]; |
| 539 JS('', 'dart.global.devtoolsFormatters = #', formatters); | 595 JS('', 'dart.global.devtoolsFormatters = #', formatters); |
| 540 } | 596 } |
| OLD | NEW |