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';
export 'package:sky/src/rendering/block.dart';
export 'package:sky/src/rendering/box.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/grid.dart';
export 'package:sky/src/rendering/hit_test.dart';
......
......@@ -21,22 +21,28 @@ bool _initInDebugBuild() {
bool debugPaintSizeEnabled = false;
/// 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.
bool debugPaintBaselinesEnabled = false;
/// 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.
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.
bool debugPaintLayerBordersEnabled = false;
/// 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.
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 {
void visitChildren(RenderObjectVisitor visitor) { }
dynamic debugExceptionContext = '';
static dynamic _debugLastException;
bool _debugReportException(dynamic exception, String method) {
if (!inDebugBuild) {
print('Uncaught exception in ${method}():\n$exception');
return false;
}
if (!identical(exception, _debugLastException)) {
print('-- EXCEPTION --');
print('An exception was raised during ${method}().');
'The following RenderObject was being processed when the exception was fired:\n${this}'.split('\n').forEach(print);
if (debugExceptionContext != '')
'The RenderObject had the following exception context:\n${debugExceptionContext}'.split('\n').forEach(print);
_debugLastException = exception;
}
return true;
void _debugReportException(String method, dynamic exception, StackTrace stack) {
print('-- EXCEPTION --');
print('The following exception was raised during ${method}():');
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 != '')
'The RenderObject had the following exception context:\n${debugExceptionContext}'.split('\n').forEach(print);
}
static bool _debugDoingLayout = false;
......@@ -598,30 +593,29 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
}
}
void _layoutWithoutResize() {
assert(_relayoutSubtreeRoot == this);
RenderObject debugPreviousActiveLayout;
assert(!_debugMutationsLocked);
assert(!_doingThisLayoutWithCallback);
assert(_debugCanParentUseSize != null);
assert(() {
_debugMutationsLocked = true;
_debugDoingThisLayout = true;
debugPreviousActiveLayout = _debugActiveLayout;
_debugActiveLayout = this;
return true;
});
try {
assert(_relayoutSubtreeRoot == this);
RenderObject debugPreviousActiveLayout;
assert(!_debugMutationsLocked);
assert(!_doingThisLayoutWithCallback);
assert(_debugCanParentUseSize != null);
assert(() {
_debugMutationsLocked = true;
_debugDoingThisLayout = true;
debugPreviousActiveLayout = _debugActiveLayout;
_debugActiveLayout = this;
return true;
});
performLayout();
assert(() {
_debugActiveLayout = debugPreviousActiveLayout;
_debugDoingThisLayout = false;
_debugMutationsLocked = false;
return true;
});
} catch (e) {
if (_debugReportException(e, 'layoutWithoutResize'))
rethrow;
} catch (e, stack) {
_debugReportException('performLayout', e, stack);
}
assert(() {
_debugActiveLayout = debugPreviousActiveLayout;
_debugDoingThisLayout = false;
_debugMutationsLocked = false;
return true;
});
_needsLayout = false;
markNeedsPaint();
}
......@@ -669,7 +663,12 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
});
if (sizedByParent) {
assert(() { _debugDoingThisResize = true; return true; });
performResize();
try {
performResize();
assert(debugDoesMeetConstraints());
} catch (e, stack) {
_debugReportException('performResize', e, stack);
}
assert(() { _debugDoingThisResize = false; return true; });
}
RenderObject debugPreviousActiveLayout;
......@@ -681,17 +680,16 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
});
try {
performLayout();
assert(() {
_debugActiveLayout = debugPreviousActiveLayout;
_debugDoingThisLayout = false;
_debugMutationsLocked = false;
return true;
});
assert(debugDoesMeetConstraints());
} catch (e) {
if (_debugReportException(e, 'layout'))
rethrow;
} catch (e, stack) {
_debugReportException('performLayout', e, stack);
}
assert(() {
_debugActiveLayout = debugPreviousActiveLayout;
_debugDoingThisLayout = false;
_debugMutationsLocked = false;
return true;
});
_needsLayout = false;
markNeedsPaint();
assert(parent == this.parent); // TODO(ianh): Remove this once the analyzer is cleverer
......@@ -938,13 +936,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
_layer.removeAllChildren();
PaintingContext context = new PaintingContext.withLayer(_layer, paintBounds);
_layer = context._containerLayer;
try {
_paintWithContext(context, Offset.zero);
context.endRecording();
} catch (e) {
if (_debugReportException(e, '_repaint'))
rethrow;
}
_paintWithContext(context, Offset.zero);
context.endRecording();
}
void _paintWithContext(PaintingContext context, Offset offset) {
assert(!_debugDoingThisPaint);
......@@ -964,9 +957,13 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
return true;
});
_needsPaint = false;
paint(context, offset);
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
try {
paint(context, offset);
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
} catch (e, stack) {
_debugReportException('paint', e, stack);
}
assert(() {
if (debugPaintBoundsEnabled)
context.canvas.restore();
......
......@@ -9,6 +9,7 @@ import 'dart:sky' as sky;
import 'package:sky/animation.dart';
import 'package:sky/mojo/activity.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/object.dart';
import 'package:sky/src/rendering/sky_binding.dart';
......@@ -742,7 +743,7 @@ abstract class Component extends Widget {
super._withKey(key);
bool _debugIsBuilding = false;
static String _debugLastComponent;
static Queue<Component> _debugComponentBuildTree = new Queue<Component>();
bool _dirty = true;
......@@ -816,37 +817,48 @@ abstract class Component extends Widget {
Widget oldChild;
if (old == null) {
oldChild = _child;
_child = null;
} else {
assert(_child == null);
oldChild = old._child;
assert(_child == null);
}
String _debugPreviousComponent;
assert(() {
_debugIsBuilding = true;
_debugPreviousComponent = _debugLastComponent;
if (_debugLastComponent != null)
_debugLastComponent = "$_debugPreviousComponent -> ${this.toStringName()}";
else
_debugLastComponent = "Build chain: ${this.toStringName()}";
_debugComponentBuildTree.add(this);
return true;
});
int lastOrder = _currentOrder;
_currentOrder = _order;
_child = build();
try {
_child = build();
assert(_child != null);
} catch (e, stack) {
_debugReportException("building ${this.toStringName()}", e, stack);
}
_currentOrder = lastOrder;
assert(_child != null);
assert(() { _debugChildTaken = false; return true; });
_child = syncChild(_child, oldChild, slot);
assert(!_debugChildTaken); // we shouldn't be able to lose our child when we're syncing it!
assert(_child != null);
assert(_child.parent == this);
try {
// even if build() failed (i.e. _child == null), we still call syncChild(), to remove the oldChild
_child = syncChild(_child, oldChild, slot);
assert(!_debugChildTaken); // we shouldn't be able to lose our child when we're syncing it!
assert(_child == null || _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(() {
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;
_debugLastComponent = _debugPreviousComponent;
return true;
return identical(_debugComponentBuildTree.removeLast(), this);
});
_dirty = false;
......@@ -1094,7 +1106,7 @@ abstract class RenderObjectWrapper extends Widget {
assert(_renderObject != null);
}
assert(() {
_renderObject.debugExceptionContext = Component._debugLastComponent;
_renderObject.debugExceptionContext = Component._debugComponentBuildTree.fold(' Widget build stack:', (String s, Component c) => s + '\n ${c.toStringName()}');
return true;
});
assert(_renderObject == renderObject); // in case a subclass reintroduces it
......@@ -1599,3 +1611,20 @@ class RenderBoxToWidgetAdapter extends AbstractWidgetRoot {
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