Commit c0d326b1 authored by Ian Hickson's avatar Ian Hickson

Merge pull request #1218 from Hixie/toString

Improve debugging aids for widgets, rendering.
parents a89fdd92 970c8ce8
...@@ -588,8 +588,8 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -588,8 +588,8 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
}); });
} }
String toStringName() { String toString() {
String header = super.toStringName(); String header = super.toString();
if (_overflow is double && _overflow > 0.0) if (_overflow is double && _overflow > 0.0)
header += ' OVERFLOWING'; header += ' OVERFLOWING';
return header; return header;
......
...@@ -1041,18 +1041,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1041,18 +1041,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
// You must not add yourself to /result/ if you return false. // You must not add yourself to /result/ if you return false.
String toString([String prefix = '']) {
RenderObject debugPreviousActiveLayout = _debugActiveLayout;
_debugActiveLayout = null;
String header = toStringName();
prefix += ' ';
String result = '${header}\n${debugDescribeSettings(prefix)}${debugDescribeChildren(prefix)}';
_debugActiveLayout = debugPreviousActiveLayout;
return result;
}
/// Returns a human understandable name /// Returns a human understandable name
String toStringName() { String toString() {
String header = '${runtimeType}'; String header = '${runtimeType}';
if (_relayoutSubtreeRoot != null && _relayoutSubtreeRoot != this) { if (_relayoutSubtreeRoot != null && _relayoutSubtreeRoot != this) {
int count = 1; int count = 1;
...@@ -1070,7 +1060,25 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1070,7 +1060,25 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
return header; return header;
} }
/// Returns a description of the tree rooted at this node.
/// If the prefix argument is provided, then every line in the output
/// will be prefixed by that string.
String toStringDeep([String prefix = '']) {
RenderObject debugPreviousActiveLayout = _debugActiveLayout;
_debugActiveLayout = null;
prefix += ' ';
String result = '$this\n${debugDescribeSettings(prefix)}${debugDescribeChildren(prefix)}';
_debugActiveLayout = debugPreviousActiveLayout;
return result;
}
/// Returns a string describing the current node's fields, one field per line,
/// with each line prefixed by the prefix argument. Subclasses should override
/// this to have their information included in toStringDeep().
String debugDescribeSettings(String prefix) => '${prefix}parentData: ${parentData}\n${prefix}constraints: ${constraints}\n'; String debugDescribeSettings(String prefix) => '${prefix}parentData: ${parentData}\n${prefix}constraints: ${constraints}\n';
/// Returns a string describing the current node's descendants. Each line of
/// the subtree in the output should be indented by the prefix argument.
String debugDescribeChildren(String prefix) => ''; String debugDescribeChildren(String prefix) => '';
} }
...@@ -1112,7 +1120,7 @@ abstract class RenderObjectWithChildMixin<ChildType extends RenderObject> implem ...@@ -1112,7 +1120,7 @@ abstract class RenderObjectWithChildMixin<ChildType extends RenderObject> implem
} }
String debugDescribeChildren(String prefix) { String debugDescribeChildren(String prefix) {
if (child != null) if (child != null)
return '${prefix}child: ${child.toString(prefix)}'; return '${prefix}child: ${child.toStringDeep(prefix)}';
return ''; return '';
} }
} }
...@@ -1352,7 +1360,7 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent ...@@ -1352,7 +1360,7 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent
int count = 1; int count = 1;
ChildType child = _firstChild; ChildType child = _firstChild;
while (child != null) { while (child != null) {
result += '${prefix}child ${count}: ${child.toString(prefix)}'; result += '${prefix}child ${count}: ${child.toStringDeep(prefix)}';
count += 1; count += 1;
child = child.parentData.nextSibling; child = child.parentData.nextSibling;
} }
......
...@@ -171,12 +171,9 @@ class SkyBinding extends HitTestTarget { ...@@ -171,12 +171,9 @@ class SkyBinding extends HitTestTarget {
GestureArena.instance.close(event.pointer); GestureArena.instance.close(event.pointer);
return EventDisposition.processed; return EventDisposition.processed;
} }
}
String toString() => 'Render Tree:\n${_renderView}'; /// Prints a textual representation of the entire render tree
void debugDumpRenderTree() {
/// Prints a textual representation of the entire render tree SkyBinding.instance.renderView.toStringDeep().split('\n').forEach(print);
void debugDumpRenderTree() {
toString().split('\n').forEach(print);
}
} }
...@@ -235,8 +235,10 @@ class Focus extends StatefulComponent { ...@@ -235,8 +235,10 @@ class Focus extends StatefulComponent {
} }
} }
String toStringName() { void debugAddDetails(List<String> details) {
return '${super.toStringName()}(focusedScope=$_focusedScope; focusedWidget=$_focusedWidget)'; super.debugAddDetails(details);
details.add('focusedScope=$_focusedScope');
details.add('focusedWidget=$_focusedWidget');
} }
} }
...@@ -139,8 +139,8 @@ abstract class GlobalKey extends Key { ...@@ -139,8 +139,8 @@ abstract class GlobalKey extends Key {
assert(() { assert(() {
String message = ''; String message = '';
for (GlobalKey key in _debugDuplicates.keys) { for (GlobalKey key in _debugDuplicates.keys) {
message += "Duplicate GlobalKey found amongst mounted widgets: $key (${_debugDuplicates[key]} instances)\n"; message += 'Duplicate GlobalKey found amongst mounted widgets: $key (${_debugDuplicates[key]} instances)\n';
message += "Most recently registered instance is:\n${_registry[key]}\n"; message += 'Most recently registered instance is:\n${_registry[key]}\n';
} }
if (!_debugDuplicates.isEmpty) if (!_debugDuplicates.isEmpty)
throw message; throw message;
...@@ -288,7 +288,7 @@ abstract class Widget { ...@@ -288,7 +288,7 @@ abstract class Widget {
static void _notifyMountStatusChanged() { static void _notifyMountStatusChanged() {
try { try {
sky.tracing.begin("Widget._notifyMountStatusChanged"); sky.tracing.begin('Widget._notifyMountStatusChanged');
_notifyingMountStatus = true; _notifyingMountStatus = true;
for (Widget node in _mountedChanged) { for (Widget node in _mountedChanged) {
if (node._wasMounted != node._mounted) { if (node._wasMounted != node._mounted) {
...@@ -302,7 +302,7 @@ abstract class Widget { ...@@ -302,7 +302,7 @@ abstract class Widget {
_mountedChanged.clear(); _mountedChanged.clear();
} finally { } finally {
_notifyingMountStatus = false; _notifyingMountStatus = false;
sky.tracing.end("Widget._notifyMountStatusChanged"); sky.tracing.end('Widget._notifyMountStatusChanged');
} }
GlobalKey._notifyListeners(); GlobalKey._notifyListeners();
} }
...@@ -404,7 +404,7 @@ abstract class Widget { ...@@ -404,7 +404,7 @@ abstract class Widget {
String debugDetails; String debugDetails;
assert(() { assert(() {
// we save this information early because by the time the exception fires we might have changed everything around // we save this information early because by the time the exception fires we might have changed everything around
debugDetails = " old child: ${oldNode?.toStringName()}\n new child: ${newNode?.toStringName()}"; debugDetails = ' old child: $oldNode\n new child: $newNode';
return true; return true;
}); });
try { try {
...@@ -508,36 +508,28 @@ abstract class Widget { ...@@ -508,36 +508,28 @@ abstract class Widget {
return newNode; return newNode;
} catch (e, stack) { } catch (e, stack) {
_debugReportException('syncing children of ${this.toStringName()}\n$debugDetails', e, stack); _debugReportException('syncing children of $this\n$debugDetails', e, stack);
return null; return null;
} }
} }
String _adjustPrefixWithParentCheck(Widget child, String prefix) { // This function can be safely called when the layout is valid.
if (child.parent == this) // For example Listener or SizeObserver callbacks can safely call
return prefix; // globalToLocal().
if (child.parent == null) Point globalToLocal(Point point) {
return '$prefix [[DISCONNECTED]] '; assert(mounted);
return '$prefix [[PARENT IS ${child.parent.toStringName()}]] '; assert(renderObject is RenderBox);
return (renderObject as RenderBox).globalToLocal(point);
} }
String toString([String prefix = '', String startPrefix = '']) { // See globalToLocal().
String childrenString = ''; Point localToGlobal(Point point) {
List<Widget> children = new List<Widget>(); assert(mounted);
walkChildren(children.add); assert(renderObject is RenderBox);
if (children.length > 0) { return (renderObject as RenderBox).localToGlobal(point);
Widget lastChild = children.removeLast();
String nextStartPrefix = prefix + ' +-';
String nextPrefix = prefix + ' | ';
for (Widget child in children)
childrenString += child.toString(nextPrefix, _adjustPrefixWithParentCheck(child, nextStartPrefix));
nextStartPrefix = prefix + ' \'-';
nextPrefix = prefix + ' ';
childrenString += lastChild.toString(nextPrefix, _adjustPrefixWithParentCheck(lastChild, nextStartPrefix));
}
return '$startPrefix${toStringName()}\n$childrenString';
} }
String toStringName() {
String toString() {
List<String> details = <String>[]; List<String> details = <String>[];
debugAddDetails(details); debugAddDetails(details);
String detailString = details.join('; '); String detailString = details.join('; ');
...@@ -558,21 +550,28 @@ abstract class Widget { ...@@ -558,21 +550,28 @@ abstract class Widget {
} }
} }
} }
String toStringDeep([String prefix = '', String startPrefix = '']) {
// This function can be safely called when the layout is valid. String childrenString = '';
// For example Listener or SizeObserver callbacks can safely call List<Widget> children = new List<Widget>();
// globalToLocal(). walkChildren(children.add);
Point globalToLocal(Point point) { if (children.length > 0) {
assert(mounted); Widget lastChild = children.removeLast();
assert(renderObject is RenderBox); String nextStartPrefix = prefix + ' +-';
return (renderObject as RenderBox).globalToLocal(point); String nextPrefix = prefix + ' | ';
for (Widget child in children)
childrenString += child.toStringDeep(nextPrefix, _adjustPrefixWithParentCheck(child, nextStartPrefix));
nextStartPrefix = prefix + ' \'-';
nextPrefix = prefix + ' ';
childrenString += lastChild.toStringDeep(nextPrefix, _adjustPrefixWithParentCheck(lastChild, nextStartPrefix));
}
return '$startPrefix$this\n$childrenString';
} }
String _adjustPrefixWithParentCheck(Widget child, String prefix) {
// See globalToLocal(). if (child.parent == this)
Point localToGlobal(Point point) { return prefix;
assert(mounted); if (child.parent == null)
assert(renderObject is RenderBox); return '$prefix [[DISCONNECTED]] ';
return (renderObject as RenderBox).localToGlobal(point); return '$prefix [[PARENT IS ${child.parent}]] ';
} }
} }
...@@ -807,7 +806,7 @@ abstract class Component extends Widget { ...@@ -807,7 +806,7 @@ abstract class Component extends Widget {
_child = build(); _child = build();
assert(_child != null); assert(_child != null);
} catch (e, stack) { } catch (e, stack) {
_debugReportException("building ${this.toStringName()}", e, stack); _debugReportException('building $this', e, stack);
} }
_currentOrder = lastOrder; _currentOrder = lastOrder;
assert(() { _debugChildTaken = false; return true; }); assert(() { _debugChildTaken = false; return true; });
...@@ -817,7 +816,7 @@ abstract class Component extends Widget { ...@@ -817,7 +816,7 @@ abstract class Component extends Widget {
assert(!_debugChildTaken); // we shouldn't be able to lose our child when we're syncing it! assert(!_debugChildTaken); // we shouldn't be able to lose our child when we're syncing it!
assert(_child == null || _child.parent == this); assert(_child == null || _child.parent == this);
} catch (e, stack) { } catch (e, stack) {
_debugReportException('syncing build output of ${this.toStringName()}\n old child: ${oldChild?.toStringName()}\n new child: ${_child?.toStringName()}', e, stack); _debugReportException('syncing build output of $this\n old child: $oldChild\n new child: $_child', e, stack);
_child = null; _child = null;
} }
assert(() { assert(() {
...@@ -1100,7 +1099,7 @@ abstract class RenderObjectWrapper extends Widget { ...@@ -1100,7 +1099,7 @@ abstract class RenderObjectWrapper extends Widget {
assert(_renderObject != null); assert(_renderObject != null);
} }
assert(() { assert(() {
_renderObject.debugExceptionContext = Component._debugComponentBuildTree.fold(' Widget build stack:', (String s, Component c) => s + '\n ${c.toStringName()}'); _renderObject.debugExceptionContext = Component._debugComponentBuildTree.fold(' Widget build stack:', (String result, Component component) => result + '\n $component');
return true; return true;
}); });
assert(_renderObject == renderObject); // in case a subclass reintroduces it assert(_renderObject == renderObject); // in case a subclass reintroduces it
...@@ -1443,7 +1442,7 @@ abstract class MultiChildRenderObjectWrapper extends RenderObjectWrapper { ...@@ -1443,7 +1442,7 @@ abstract class MultiChildRenderObjectWrapper extends RenderObjectWrapper {
continue; // when these nodes are reordered, we just reassign the data continue; // when these nodes are reordered, we just reassign the data
if (!idSet.add(child.key)) { if (!idSet.add(child.key)) {
throw '''If multiple keyed nodes exist as children of another node, they must have unique keys. ${toStringName()} has multiple children with key "${child.key}".'''; throw 'If multiple keyed nodes exist as children of another node, they must have unique keys. $this has multiple children with key "${child.key}".';
} }
} }
return false; return false;
...@@ -1579,11 +1578,13 @@ void runApp(App app, { RenderView renderViewOverride, bool enableProfilingLoop: ...@@ -1579,11 +1578,13 @@ void runApp(App app, { RenderView renderViewOverride, bool enableProfilingLoop:
}); });
} }
} }
/// Prints a textual representation of the entire widget tree
void debugDumpApp() { void debugDumpApp() {
if (_container != null) if (_container != null)
_container.toString().split('\n').forEach(print); _container.toStringDeep().split('\n').forEach(print);
else else
print("runApp() not yet called"); print('runApp() not yet called');
} }
...@@ -1639,7 +1640,7 @@ void _debugReportException(String context, dynamic exception, StackTrace stack) ...@@ -1639,7 +1640,7 @@ void _debugReportException(String context, dynamic exception, StackTrace stack)
print('Stack trace:'); print('Stack trace:');
'$stack'.split('\n').forEach(print); '$stack'.split('\n').forEach(print);
print('Build stack:'); print('Build stack:');
Component._debugComponentBuildTree.forEach((Component c) { print(' ${c.toStringName()}'); }); Component._debugComponentBuildTree.forEach((Component component) { print(' $component'); });
print('Current application widget tree:'); print('Current application widget tree:');
debugDumpApp(); debugDumpApp();
print('------------------------------------------------------------------------'); print('------------------------------------------------------------------------');
......
...@@ -171,7 +171,7 @@ class RenderScaffold extends RenderBox { ...@@ -171,7 +171,7 @@ class RenderScaffold extends RenderBox {
} }
String debugDescribeChildren(String prefix) { String debugDescribeChildren(String prefix) {
return _slots.keys.map((slot) => '${prefix}${slot}: ${_slots[slot].toString(prefix)}').join(); return _slots.keys.map((slot) => '${prefix}${slot}: ${_slots[slot].toStringDeep(prefix)}').join();
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment