Commit 2e0d5f92 authored by Hixie's avatar Hixie

Improve the debugging information for Widget

If the build stack got long before, it would get cropped.
parent 129238ed
...@@ -11,6 +11,7 @@ export 'package:sky/src/rendering/auto_layout.dart'; ...@@ -11,6 +11,7 @@ export 'package:sky/src/rendering/auto_layout.dart';
export 'package:sky/src/rendering/block.dart'; export 'package:sky/src/rendering/block.dart';
export 'package:sky/src/rendering/box.dart'; export 'package:sky/src/rendering/box.dart';
export 'package:sky/src/rendering/debug.dart'; export 'package:sky/src/rendering/debug.dart';
export 'package:sky/src/rendering/error.dart';
export 'package:sky/src/rendering/flex.dart'; export 'package:sky/src/rendering/flex.dart';
export 'package:sky/src/rendering/grid.dart'; export 'package:sky/src/rendering/grid.dart';
export 'package:sky/src/rendering/hit_test.dart'; export 'package:sky/src/rendering/hit_test.dart';
......
...@@ -21,22 +21,28 @@ bool _initInDebugBuild() { ...@@ -21,22 +21,28 @@ bool _initInDebugBuild() {
bool debugPaintSizeEnabled = false; bool debugPaintSizeEnabled = false;
/// The color to use when painting RenderObject bounds. /// The color to use when painting RenderObject bounds.
const sky.Color debugPaintSizeColor = const sky.Color(0xFF00FFFF); sky.Color debugPaintSizeColor = const sky.Color(0xFF00FFFF);
/// Causes each RenderBox to paint a line at each of its baselines. /// Causes each RenderBox to paint a line at each of its baselines.
bool debugPaintBaselinesEnabled = false; bool debugPaintBaselinesEnabled = false;
/// The color to use when painting alphabetic baselines. /// The color to use when painting alphabetic baselines.
const sky.Color debugPaintAlphabeticBaselineColor = const sky.Color(0xFF00FF00); sky.Color debugPaintAlphabeticBaselineColor = const sky.Color(0xFF00FF00);
/// The color ot use when painting ideographic baselines. /// The color ot use when painting ideographic baselines.
const sky.Color debugPaintIdeographicBaselineColor = const sky.Color(0xFFFFD000); sky.Color debugPaintIdeographicBaselineColor = const sky.Color(0xFFFFD000);
/// Causes each Layer to paint a box around its bounds. /// Causes each Layer to paint a box around its bounds.
bool debugPaintLayerBordersEnabled = false; bool debugPaintLayerBordersEnabled = false;
/// The color to use when painting Layer borders. /// The color to use when painting Layer borders.
const sky.Color debugPaintLayerBordersColor = const sky.Color(0xFFFF9800); sky.Color debugPaintLayerBordersColor = const sky.Color(0xFFFF9800);
/// Causes RenderObjects to paint warnings when painting outside their bounds. /// Causes RenderObjects to paint warnings when painting outside their bounds.
bool debugPaintBoundsEnabled = false; bool debugPaintBoundsEnabled = false;
/// The color to use when painting RenderError boxes in checked mode.
sky.Color debugErrorBoxColor = const sky.Color(0xFFFF0000);
/// How many lines of debugging output to include when an exception is reported.
int debugRenderObjectDumpMaxLength = 10;
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:sky/src/rendering/box.dart';
import 'package:sky/src/rendering/debug.dart';
import 'package:sky/src/rendering/object.dart';
const double _kMaxWidth = 100000.0;
const double _kMaxHeight = 100000.0;
class RenderErrorBox extends RenderBox {
double getMinIntrinsicWidth(BoxConstraints constraints) {
return constraints.constrainWidth(0.0);
}
double getMaxIntrinsicWidth(BoxConstraints constraints) {
return constraints.constrainWidth(_kMaxWidth);
}
double getMinIntrinsicHeight(BoxConstraints constraints) {
return constraints.constrainHeight(0.0);
}
double getMaxIntrinsicHeight(BoxConstraints constraints) {
return constraints.constrainHeight(_kMaxHeight);
}
bool get sizedByParent => true;
void performResize() {
size = constraints.constrain(const Size(_kMaxWidth, _kMaxHeight));
}
void paint(PaintingContext context, Offset offset) {
context.canvas.drawRect(offset & size, new Paint() .. color = debugErrorBoxColor);
}
}
...@@ -439,21 +439,16 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -439,21 +439,16 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
void visitChildren(RenderObjectVisitor visitor) { } void visitChildren(RenderObjectVisitor visitor) { }
dynamic debugExceptionContext = ''; dynamic debugExceptionContext = '';
static dynamic _debugLastException; void _debugReportException(String method, dynamic exception, StackTrace stack) {
bool _debugReportException(dynamic exception, String method) {
if (!inDebugBuild) {
print('Uncaught exception in ${method}():\n$exception');
return false;
}
if (!identical(exception, _debugLastException)) {
print('-- EXCEPTION --'); print('-- EXCEPTION --');
print('An exception was raised during ${method}().'); print('The following exception was raised during ${method}():');
'The following RenderObject was being processed when the exception was fired:\n${this}'.split('\n').forEach(print); print('$exception');
print('Stack trace:');
print('$stack');
'The following RenderObject was being processed when the exception was fired (limited to $debugRenderObjectDumpMaxLength lines):\n${this}'
.split('\n').take(debugRenderObjectDumpMaxLength+1).forEach(print);
if (debugExceptionContext != '') if (debugExceptionContext != '')
'The RenderObject had the following exception context:\n${debugExceptionContext}'.split('\n').forEach(print); 'The RenderObject had the following exception context:\n${debugExceptionContext}'.split('\n').forEach(print);
_debugLastException = exception;
}
return true;
} }
static bool _debugDoingLayout = false; static bool _debugDoingLayout = false;
...@@ -598,7 +593,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -598,7 +593,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
} }
} }
void _layoutWithoutResize() { void _layoutWithoutResize() {
try {
assert(_relayoutSubtreeRoot == this); assert(_relayoutSubtreeRoot == this);
RenderObject debugPreviousActiveLayout; RenderObject debugPreviousActiveLayout;
assert(!_debugMutationsLocked); assert(!_debugMutationsLocked);
...@@ -611,17 +605,17 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -611,17 +605,17 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
_debugActiveLayout = this; _debugActiveLayout = this;
return true; return true;
}); });
try {
performLayout(); performLayout();
} catch (e, stack) {
_debugReportException('performLayout', e, stack);
}
assert(() { assert(() {
_debugActiveLayout = debugPreviousActiveLayout; _debugActiveLayout = debugPreviousActiveLayout;
_debugDoingThisLayout = false; _debugDoingThisLayout = false;
_debugMutationsLocked = false; _debugMutationsLocked = false;
return true; return true;
}); });
} catch (e) {
if (_debugReportException(e, 'layoutWithoutResize'))
rethrow;
}
_needsLayout = false; _needsLayout = false;
markNeedsPaint(); markNeedsPaint();
} }
...@@ -669,7 +663,12 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -669,7 +663,12 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
}); });
if (sizedByParent) { if (sizedByParent) {
assert(() { _debugDoingThisResize = true; return true; }); assert(() { _debugDoingThisResize = true; return true; });
try {
performResize(); performResize();
assert(debugDoesMeetConstraints());
} catch (e, stack) {
_debugReportException('performResize', e, stack);
}
assert(() { _debugDoingThisResize = false; return true; }); assert(() { _debugDoingThisResize = false; return true; });
} }
RenderObject debugPreviousActiveLayout; RenderObject debugPreviousActiveLayout;
...@@ -681,17 +680,16 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -681,17 +680,16 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
}); });
try { try {
performLayout(); performLayout();
assert(debugDoesMeetConstraints());
} catch (e, stack) {
_debugReportException('performLayout', e, stack);
}
assert(() { assert(() {
_debugActiveLayout = debugPreviousActiveLayout; _debugActiveLayout = debugPreviousActiveLayout;
_debugDoingThisLayout = false; _debugDoingThisLayout = false;
_debugMutationsLocked = false; _debugMutationsLocked = false;
return true; return true;
}); });
assert(debugDoesMeetConstraints());
} catch (e) {
if (_debugReportException(e, 'layout'))
rethrow;
}
_needsLayout = false; _needsLayout = false;
markNeedsPaint(); markNeedsPaint();
assert(parent == this.parent); // TODO(ianh): Remove this once the analyzer is cleverer assert(parent == this.parent); // TODO(ianh): Remove this once the analyzer is cleverer
...@@ -938,13 +936,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -938,13 +936,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
_layer.removeAllChildren(); _layer.removeAllChildren();
PaintingContext context = new PaintingContext.withLayer(_layer, paintBounds); PaintingContext context = new PaintingContext.withLayer(_layer, paintBounds);
_layer = context._containerLayer; _layer = context._containerLayer;
try {
_paintWithContext(context, Offset.zero); _paintWithContext(context, Offset.zero);
context.endRecording(); context.endRecording();
} catch (e) {
if (_debugReportException(e, '_repaint'))
rethrow;
}
} }
void _paintWithContext(PaintingContext context, Offset offset) { void _paintWithContext(PaintingContext context, Offset offset) {
assert(!_debugDoingThisPaint); assert(!_debugDoingThisPaint);
...@@ -964,9 +957,13 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -964,9 +957,13 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
return true; return true;
}); });
_needsPaint = false; _needsPaint = false;
try {
paint(context, offset); paint(context, offset);
assert(!_needsLayout); // check that the paint() method didn't mark us dirty again assert(!_needsLayout); // check that the paint() method didn't mark us dirty again
assert(!_needsPaint); // check that the paint() method didn't mark us dirty again assert(!_needsPaint); // check that the paint() method didn't mark us dirty again
} catch (e, stack) {
_debugReportException('paint', e, stack);
}
assert(() { assert(() {
if (debugPaintBoundsEnabled) if (debugPaintBoundsEnabled)
context.canvas.restore(); context.canvas.restore();
......
...@@ -9,6 +9,7 @@ import 'dart:sky' as sky; ...@@ -9,6 +9,7 @@ import 'dart:sky' as sky;
import 'package:sky/animation.dart'; import 'package:sky/animation.dart';
import 'package:sky/mojo/activity.dart'; import 'package:sky/mojo/activity.dart';
import 'package:sky/src/rendering/box.dart'; import 'package:sky/src/rendering/box.dart';
import 'package:sky/src/rendering/error.dart';
import 'package:sky/src/rendering/hit_test.dart'; import 'package:sky/src/rendering/hit_test.dart';
import 'package:sky/src/rendering/object.dart'; import 'package:sky/src/rendering/object.dart';
import 'package:sky/src/rendering/sky_binding.dart'; import 'package:sky/src/rendering/sky_binding.dart';
...@@ -742,7 +743,7 @@ abstract class Component extends Widget { ...@@ -742,7 +743,7 @@ abstract class Component extends Widget {
super._withKey(key); super._withKey(key);
bool _debugIsBuilding = false; bool _debugIsBuilding = false;
static String _debugLastComponent; static Queue<Component> _debugComponentBuildTree = new Queue<Component>();
bool _dirty = true; bool _dirty = true;
...@@ -816,37 +817,48 @@ abstract class Component extends Widget { ...@@ -816,37 +817,48 @@ abstract class Component extends Widget {
Widget oldChild; Widget oldChild;
if (old == null) { if (old == null) {
oldChild = _child; oldChild = _child;
_child = null;
} else { } else {
assert(_child == null);
oldChild = old._child; oldChild = old._child;
assert(_child == null);
} }
String _debugPreviousComponent; String _debugPreviousComponent;
assert(() { assert(() {
_debugIsBuilding = true; _debugIsBuilding = true;
_debugPreviousComponent = _debugLastComponent; _debugComponentBuildTree.add(this);
if (_debugLastComponent != null)
_debugLastComponent = "$_debugPreviousComponent -> ${this.toStringName()}";
else
_debugLastComponent = "Build chain: ${this.toStringName()}";
return true; return true;
}); });
int lastOrder = _currentOrder; int lastOrder = _currentOrder;
_currentOrder = _order; _currentOrder = _order;
try {
_child = build(); _child = build();
_currentOrder = lastOrder;
assert(_child != null); assert(_child != null);
} catch (e, stack) {
_debugReportException("building ${this.toStringName()}", e, stack);
}
_currentOrder = lastOrder;
assert(() { _debugChildTaken = false; return true; }); assert(() { _debugChildTaken = false; return true; });
try {
// even if build() failed (i.e. _child == null), we still call syncChild(), to remove the oldChild
_child = syncChild(_child, oldChild, slot); _child = syncChild(_child, oldChild, slot);
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); assert(_child == null || _child.parent == this);
assert(_child.parent == this); } catch (e, stack) {
_debugReportException('syncing build output of ${this.toStringName()}\n old child: ${oldChild?.toStringName()}\n new child: ${_child?.toStringName()}', e, stack);
_child = null;
}
assert(() { assert(() {
if (_child == null) {
try {
_child = new ErrorWidget()..setParent(this).._sync(null, slot);
} catch (e) {
print('(application is now in an unstable state - ignore any subsequent exceptions)');
}
}
_debugIsBuilding = false; _debugIsBuilding = false;
_debugLastComponent = _debugPreviousComponent; return identical(_debugComponentBuildTree.removeLast(), this);
return true;
}); });
_dirty = false; _dirty = false;
...@@ -1094,7 +1106,7 @@ abstract class RenderObjectWrapper extends Widget { ...@@ -1094,7 +1106,7 @@ abstract class RenderObjectWrapper extends Widget {
assert(_renderObject != null); assert(_renderObject != null);
} }
assert(() { assert(() {
_renderObject.debugExceptionContext = Component._debugLastComponent; _renderObject.debugExceptionContext = Component._debugComponentBuildTree.fold(' Widget build stack:', (String s, Component c) => s + '\n ${c.toStringName()}');
return true; return true;
}); });
assert(_renderObject == renderObject); // in case a subclass reintroduces it assert(_renderObject == renderObject); // in case a subclass reintroduces it
...@@ -1599,3 +1611,20 @@ class RenderBoxToWidgetAdapter extends AbstractWidgetRoot { ...@@ -1599,3 +1611,20 @@ class RenderBoxToWidgetAdapter extends AbstractWidgetRoot {
Widget build() => builder(); Widget build() => builder();
} }
class ErrorWidget extends LeafRenderObjectWrapper {
RenderBox createNode() => new RenderErrorBox();
}
void _debugReportException(String context, dynamic exception, StackTrace stack) {
print('------------------------------------------------------------------------');
print('Exception caught while $context');
print('$exception');
print('Stack trace:');
'$stack'.split('\n').forEach(print);
print('Build stack:');
Component._debugComponentBuildTree.forEach((Component c) { print(' ${c.toStringName()}'); });
print('Current application widget tree:');
debugDumpApp();
print('------------------------------------------------------------------------');
}
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