Commit daf5c312 authored by Hixie's avatar Hixie

Improve exception reporting in Widgets framework

This specifically improves the reporting of exceptions in syncChild(),
and makes the way we've been adding information to toStringName() less
ad-hoc and easier to extend.
parent dfd821e5
...@@ -140,7 +140,6 @@ abstract class GlobalKey extends Key { ...@@ -140,7 +140,6 @@ abstract class GlobalKey extends Key {
if (_syncedKeys.isEmpty && _removedKeys.isEmpty) if (_syncedKeys.isEmpty && _removedKeys.isEmpty)
return; return;
try { try {
for (GlobalKey key in _syncedKeys) { for (GlobalKey key in _syncedKeys) {
Widget widget = _registry[key]; Widget widget = _registry[key];
if (widget != null && _syncListeners.containsKey(key)) { if (widget != null && _syncListeners.containsKey(key)) {
...@@ -149,7 +148,6 @@ abstract class GlobalKey extends Key { ...@@ -149,7 +148,6 @@ abstract class GlobalKey extends Key {
listener(key, widget); listener(key, widget);
} }
} }
for (GlobalKey key in _removedKeys) { for (GlobalKey key in _removedKeys) {
if (!_registry.containsKey(key) && _removeListeners.containsKey(key)) { if (!_registry.containsKey(key) && _removeListeners.containsKey(key)) {
Set<GlobalKeyRemoveListener> localListeners = new Set<GlobalKeyRemoveListener>.from(_removeListeners[key]); Set<GlobalKeyRemoveListener> localListeners = new Set<GlobalKeyRemoveListener>.from(_removeListeners[key]);
...@@ -388,6 +386,13 @@ abstract class Widget { ...@@ -388,6 +386,13 @@ abstract class Widget {
// Returns the child which should be retained as the child of this node. // Returns the child which should be retained as the child of this node.
Widget syncChild(Widget newNode, Widget oldNode, dynamic slot) { Widget syncChild(Widget newNode, Widget oldNode, dynamic slot) {
String debugDetails;
assert(() {
// 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()}";
return true;
});
try {
if (newNode == oldNode) { if (newNode == oldNode) {
// TODO(ianh): Simplify next few asserts once the analyzer is cleverer // TODO(ianh): Simplify next few asserts once the analyzer is cleverer
...@@ -483,6 +488,11 @@ abstract class Widget { ...@@ -483,6 +488,11 @@ abstract class Widget {
newNode._sync(oldNode, slot); newNode._sync(oldNode, slot);
assert(newNode.renderObject is RenderObject); assert(newNode.renderObject is RenderObject);
return newNode; return newNode;
} catch (e, stack) {
_debugReportException('syncing children of ${this.toStringName()}\n$debugDetails', e, stack);
return null;
}
} }
String _adjustPrefixWithParentCheck(Widget child, String prefix) { String _adjustPrefixWithParentCheck(Widget child, String prefix) {
...@@ -507,19 +517,25 @@ abstract class Widget { ...@@ -507,19 +517,25 @@ abstract class Widget {
nextPrefix = prefix + ' '; nextPrefix = prefix + ' ';
childrenString += lastChild.toString(nextPrefix, _adjustPrefixWithParentCheck(lastChild, nextStartPrefix)); childrenString += lastChild.toString(nextPrefix, _adjustPrefixWithParentCheck(lastChild, nextStartPrefix));
} }
String suffix = ''; return '$startPrefix${toStringName()}\n$childrenString';
}
String toStringName() {
List<String> details = <String>[];
debugAddDetails(details);
String detailString = details.join('; ');
return '$runtimeType($detailString})';
}
void debugAddDetails(List<String> details) {
if (key != null)
details.add('$key');
details.add('hashCode=$hashCode');
details.add(mounted ? 'mounted' : 'not mounted');
String generationString = '';
if (_generation != _currentGeneration) { if (_generation != _currentGeneration) {
int delta = _generation - _currentGeneration; int delta = _generation - _currentGeneration;
String sign = delta < 0 ? '' : '+'; String sign = delta < 0 ? '' : '+';
suffix = ' gen$sign$delta'; details.add('gen$sign$delta');
}
return '$startPrefix${toStringName()}$suffix\n$childrenString';
} }
String toStringName() {
String keyString = key == null ? '' : '$key; ';
String hashCodeString = 'hashCode=$hashCode';
String mountedString = mounted ? '; mounted' : '; not mounted';
return '$runtimeType($keyString$hashCodeString$mountedString)';
} }
// This function can be safely called when the layout is valid. // This function can be safely called when the layout is valid.
...@@ -902,6 +918,11 @@ abstract class Component extends Widget { ...@@ -902,6 +918,11 @@ abstract class Component extends Widget {
Widget build(); Widget build();
void debugAddDetails(List<String> details) {
super.debugAddDetails(details);
if (_dirty)
details.add('dirty');
}
} }
abstract class StatefulComponent extends Component { abstract class StatefulComponent extends Component {
...@@ -964,10 +985,9 @@ abstract class StatefulComponent extends Component { ...@@ -964,10 +985,9 @@ abstract class StatefulComponent extends Component {
_scheduleBuild(); _scheduleBuild();
} }
String toStringName() { void debugAddDetails(List<String> details) {
if (_isStateInitialized) super.debugAddDetails(details);
return 'Stateful ${super.toStringName()}'; details.add(_isStateInitialized ? 'stateful' : 'stateless');
return 'Stateless ${super.toStringName()}';
} }
} }
......
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