Commit ed189ba9 authored by Ian Hickson's avatar Ian Hickson

Merge pull request #1653 from Hixie/debugging

Sundry debugging aids and fixes
parents c701ad6a d0d84e16
......@@ -7,6 +7,7 @@ library rendering;
export 'src/rendering/auto_layout.dart';
export 'src/rendering/basic_types.dart';
export 'src/rendering/binding.dart';
export 'src/rendering/block.dart';
export 'src/rendering/box.dart';
export 'src/rendering/debug.dart';
......@@ -22,7 +23,6 @@ export 'src/rendering/object.dart';
export 'src/rendering/paragraph.dart';
export 'src/rendering/proxy_box.dart';
export 'src/rendering/shifted_box.dart';
export 'src/rendering/binding.dart';
export 'src/rendering/stack.dart';
export 'src/rendering/statistics_box.dart';
export 'src/rendering/toggleable.dart';
......
......@@ -31,6 +31,8 @@ typedef void PerformanceStatusListener(PerformanceStatus status);
/// want to watch a performance but should not be able to change the
/// performance's state.
abstract class PerformanceView {
const PerformanceView();
/// Update the given variable according to the current progress of the performance
void updateVariable(Animatable variable);
/// Calls the listener every time the progress of the performance changes
......@@ -45,6 +47,10 @@ abstract class PerformanceView {
/// The current status of this animation
PerformanceStatus get status;
/// The current progress of this animation (a value from 0.0 to 1.0).
/// This is the value that is used to update any variables when using updateVariable().
double get progress;
/// Whether this animation is stopped at the beginning
bool get isDismissed => status == PerformanceStatus.dismissed;
......@@ -61,12 +67,16 @@ abstract class PerformanceView {
/// [fling] the timeline causing a physics-based simulation to take over the
/// progression.
class Performance extends PerformanceView {
Performance({ this.duration, double progress }) {
Performance({ this.duration, double progress, this.debugLabel }) {
_timeline = new SimulationStepper(_tick);
if (progress != null)
_timeline.value = progress.clamp(0.0, 1.0);
}
/// A label that is used in the toString() output. Intended to aid with
/// identifying performance instances in debug output.
final String debugLabel;
/// Returns a [PerformanceView] for this performance,
/// so that a pointer to this object can be passed around without
/// allowing users of that pointer to mutate the AnimationPerformance state.
......@@ -220,6 +230,12 @@ class Performance extends PerformanceView {
_notifyListeners();
_checkStatusChanged();
}
String toString() {
if (debugLabel != null)
return '$runtimeType at $progress for $debugLabel';
return '$runtimeType at $progress';
}
}
/// An animation performance with an animated variable with a concrete type
......
......@@ -106,7 +106,7 @@ class _DrawerRoute extends Route {
final int level;
PerformanceView get performance => _performance?.view;
Performance _performance = new Performance(duration: _kBaseSettleDuration);
Performance _performance = new Performance(duration: _kBaseSettleDuration, debugLabel: 'Drawer');
bool get opaque => false;
......
......@@ -6,6 +6,7 @@ import 'dart:ui' as ui;
import 'package:flutter/animation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'box.dart';
import 'hit_test.dart';
......@@ -252,5 +253,5 @@ class FlutterBinding extends HitTestTarget {
/// Prints a textual representation of the entire render tree
void debugDumpRenderTree() {
FlutterBinding.instance.renderView.toStringDeep().split('\n').forEach(print);
debugPrint(FlutterBinding.instance.renderView.toStringDeep());
}
......@@ -359,9 +359,11 @@ abstract class RenderBox extends RenderObject {
final _DebugSize _size = this._size;
assert(_size._owner == this);
if (RenderObject.debugActiveLayout != null) {
// we are always allowed to access our own size (for print debugging and asserts if nothing else)
// other than us, the only object that's allowed to read our size is our parent, if they're said they will
// if you hit this assert trying to access a child's size, pass parentUsesSize: true in layout()
// We are always allowed to access our own size (for print debugging
// and asserts if nothing else). Other than us, the only object that's
// allowed to read our size is our parent, if they've said they will.
// If you hit this assert trying to access a child's size, pass
// "parentUsesSize: true" to that child's layout().
assert(debugDoingThisResize || debugDoingThisLayout ||
(RenderObject.debugActiveLayout == parent && _size._canBeUsedByParent));
}
......@@ -377,8 +379,12 @@ abstract class RenderBox extends RenderObject {
assert((sizedByParent && debugDoingThisResize) ||
(!sizedByParent && debugDoingThisLayout));
assert(() {
if (value is _DebugSize)
return value._canBeUsedByParent && value._owner.parent == this;
if (value is _DebugSize) {
if (value._owner != this) {
assert(value._owner.parent == this);
assert(value._canBeUsedByParent);
}
}
return true;
});
_size = value;
......@@ -389,6 +395,11 @@ abstract class RenderBox extends RenderObject {
assert(debugDoesMeetConstraints());
}
void debugResetSize() {
// updates the value of size._canBeUsedByParent if necessary
size = size;
}
Map<TextBaseline, double> _cachedBaselines;
bool _ancestorUsesBaseline = false;
static bool _debugDoingBaseline = false;
......@@ -473,7 +484,7 @@ abstract class RenderBox extends RenderObject {
});
bool result = constraints.isSatisfiedBy(_size);
if (!result)
print("${this.runtimeType} does not meet its constraints. Constraints: $constraints, size: $_size");
debugPrint("${this.runtimeType} does not meet its constraints. Constraints: $constraints, size: $_size");
return result;
}
......
......@@ -3,6 +3,8 @@
// found in the LICENSE file.
import 'dart:ui' as ui;
import 'dart:async';
import 'dart:collection';
/// Causes each RenderBox to paint a box around its bounds.
bool debugPaintSizeEnabled = false;
......@@ -30,3 +32,34 @@ bool debugPaintBoundsEnabled = false;
/// The color to use when painting RenderError boxes in checked mode.
ui.Color debugErrorBoxColor = const ui.Color(0xFFFF0000);
/// Prints a message to the console, which you can access using the "flutter"
/// tool's "logs" command ("flutter logs").
///
/// This function very crudely attempts to throttle the rate at which messages
/// are sent to avoid data loss on Android. This means that interleaving calls
/// to this function (directly or indirectly via [debugDumpRenderTree] or
/// [debugDumpApp]) and to the Dart [print] method can result in out-of-order
/// messages in the logs.
void debugPrint(String message) {
_debugPrintBuffer.addAll(message.split('\n'));
if (!_debugPrintScheduled)
_debugPrintTask();
}
int _debugPrintedCharacters = 0;
int _kDebugPrintCapacity = 32 * 1024;
Queue<String> _debugPrintBuffer = new Queue<String>();
bool _debugPrintScheduled = false;
void _debugPrintTask() {
_debugPrintScheduled = false;
while (_debugPrintedCharacters < _kDebugPrintCapacity && _debugPrintBuffer.length > 0) {
String line = _debugPrintBuffer.removeFirst();
_debugPrintedCharacters += line.length; // TODO(ianh): Use the UTF-8 byte length instead
print(line);
}
if (_debugPrintBuffer.length > 0) {
_debugPrintScheduled = true;
_debugPrintedCharacters = 0;
new Timer(new Duration(seconds: 1), _debugPrintTask);
}
}
......@@ -513,14 +513,14 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
dynamic debugExceptionContext = '';
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');
print('The following RenderObject was being processed when the exception was fired:\n${this}');
debugPrint('-- EXCEPTION --');
debugPrint('The following exception was raised during $method():');
debugPrint('$exception');
debugPrint('Stack trace:');
debugPrint('$stack');
debugPrint('The following RenderObject was being processed when the exception was fired:\n${this}');
if (debugExceptionContext != '')
'That RenderObject had the following exception context:\n$debugExceptionContext'.split('\n').forEach(print);
debugPrint('That RenderObject had the following exception context:\n$debugExceptionContext');
if (debugRenderingExceptionHandler != null)
debugRenderingExceptionHandler(this, method, exception, stack);
}
......@@ -723,15 +723,30 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
else
relayoutSubtreeRoot = parent._relayoutSubtreeRoot;
assert(parent == this.parent);
if (!needsLayout && constraints == _constraints && relayoutSubtreeRoot == _relayoutSubtreeRoot)
assert(() {
_debugCanParentUseSize = parentUsesSize;
return true;
});
if (!needsLayout && constraints == _constraints && relayoutSubtreeRoot == _relayoutSubtreeRoot) {
assert(() {
// in case parentUsesSize changed since the last invocation, set size
// to itself, so it has the right internal debug values.
_debugDoingThisLayout = true;
RenderObject debugPreviousActiveLayout = _debugActiveLayout;
_debugActiveLayout = this;
debugResetSize();
_debugActiveLayout = debugPreviousActiveLayout;
_debugDoingThisLayout = false;
return true;
});
return;
}
_constraints = constraints;
_relayoutSubtreeRoot = relayoutSubtreeRoot;
assert(!_debugMutationsLocked);
assert(!_doingThisLayoutWithCallback);
assert(() {
_debugMutationsLocked = true;
_debugCanParentUseSize = parentUsesSize;
return true;
});
if (sizedByParent) {
......@@ -768,6 +783,14 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
assert(parent == this.parent);
}
/// If a subclass has a "size" (the state controlled by "parentUsesSize",
/// whatever it is in the subclass, e.g. the actual "size" property of
/// RenderBox), and the subclass verifies that in checked mode this "size"
/// property isn't used when debugCanParentUseSize isn't set, then that
/// subclass should override debugResetSize() to reapply the current values of
/// debugCanParentUseSize to that state.
void debugResetSize() { }
/// Whether the constraints are the only input to the sizing algorithm (in
/// particular, child nodes have no impact)
///
......
......@@ -1018,7 +1018,7 @@ class RenderTransform extends RenderProxyBox {
String debugDescribeSettings(String prefix) {
List<String> result = _transform.toString().split('\n').map((String s) => '$prefix $s\n').toList();
result.removeLast();
return '${super.debugDescribeSettings(prefix)}${prefix}transform matrix:\n${result.join()}\n${prefix}origin: $origin\nalignment: $alignment\n';
return '${super.debugDescribeSettings(prefix)}${prefix}transform matrix:\n${result.join()}\n${prefix}origin: $origin\n${prefix}alignment: $alignment\n';
}
}
......
......@@ -76,6 +76,11 @@ class Opacity extends OneChildRenderObjectWidget {
void updateRenderObject(RenderOpacity renderObject, Opacity oldWidget) {
renderObject.opacity = opacity;
}
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('opacity: $opacity');
}
}
class ColorFilter extends OneChildRenderObjectWidget {
......@@ -283,6 +288,14 @@ class SizedBox extends OneChildRenderObjectWidget {
void updateRenderObject(RenderConstrainedBox renderObject, SizedBox oldWidget) {
renderObject.additionalConstraints = _additionalConstraints;
}
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
if (width != null)
description.add('width: $width');
if (height != null)
description.add('height: $height');
}
}
class ConstrainedBox extends OneChildRenderObjectWidget {
......@@ -298,6 +311,11 @@ class ConstrainedBox extends OneChildRenderObjectWidget {
void updateRenderObject(RenderConstrainedBox renderObject, ConstrainedBox oldWidget) {
renderObject.additionalConstraints = constraints;
}
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('constraints: $constraints');
}
}
class FractionallySizedBox extends OneChildRenderObjectWidget {
......@@ -316,6 +334,14 @@ class FractionallySizedBox extends OneChildRenderObjectWidget {
renderObject.widthFactor = width;
renderObject.heightFactor = height;
}
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
if (width != null)
description.add('width: $width');
if (height != null)
description.add('height: $height');
}
}
class OverflowBox extends OneChildRenderObjectWidget {
......@@ -355,6 +381,11 @@ class AspectRatio extends OneChildRenderObjectWidget {
void updateRenderObject(RenderAspectRatio renderObject, AspectRatio oldWidget) {
renderObject.aspectRatio = aspectRatio;
}
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('aspectRatio: $aspectRatio');
}
}
class IntrinsicWidth extends OneChildRenderObjectWidget {
......@@ -644,6 +675,18 @@ class Positioned extends ParentDataWidget {
if (needsLayout)
renderObject.markNeedsLayout();
}
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
if (left != null)
description.add('left: $left');
if (top != null)
description.add('top: $top');
if (right != null)
description.add('right: $right');
if (bottom != null)
description.add('bottom: $bottom');
}
}
class Grid extends MultiChildRenderObjectWidget {
......@@ -728,6 +771,11 @@ class Flexible extends ParentDataWidget {
renderObject.markNeedsLayout();
}
}
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('flex: $flex');
}
}
class Paragraph extends LeafRenderObjectWidget {
......@@ -1077,6 +1125,11 @@ class MetaData extends OneChildRenderObjectWidget {
void updateRenderObject(RenderMetaData renderObject, MetaData oldWidget) {
renderObject.metaData = metaData;
}
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('$metaData');
}
}
class KeyedSubtree extends StatelessComponent {
......
......@@ -94,8 +94,8 @@ void debugDumpApp() {
assert(WidgetFlutterBinding.instance.renderViewElement != null);
String mode = 'RELEASE MODE';
assert(() { mode = 'CHECKED MODE'; return true; });
print('${WidgetFlutterBinding.instance.runtimeType} - $mode');
WidgetFlutterBinding.instance.renderViewElement.toStringDeep().split('\n').forEach(print);
debugPrint('${WidgetFlutterBinding.instance.runtimeType} - $mode');
debugPrint(WidgetFlutterBinding.instance.renderViewElement.toStringDeep());
}
/// This class provides a bridge from a RenderObject to an Element tree. The
......
......@@ -7,6 +7,8 @@ import 'dart:collection';
import 'package:flutter/rendering.dart';
export 'package:flutter/rendering.dart' show debugPrint;
// KEYS
/// A Key is an identifier for [Widget]s and [Element]s. A new Widget will only
......@@ -184,7 +186,7 @@ class GlobalObjectKey extends GlobalKey {
return identical(value, typedOther.value);
}
int get hashCode => identityHashCode(value);
String toString() => '[GlobalKey ${value.runtimeType}(${value.hashCode})]';
String toString() => '[$runtimeType ${value.runtimeType}(${value.hashCode})]';
}
......@@ -823,10 +825,13 @@ abstract class Element<T extends Widget> implements BuildContext {
void debugFillDescription(List<String> description) {
if (depth == null)
description.add('no depth');
if (widget == null)
if (widget == null) {
description.add('no widget');
else
} else {
if (widget.key != null)
description.add('${widget.key}');
widget.debugFillDescription(description);
}
}
String toStringDeep([String prefixLineOne = '', String prefixOtherLines = '']) {
......@@ -1090,7 +1095,7 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>>
assert(() {
if (_state._debugLifecycleState == _StateLifecycle.initialized)
return true;
print('${_state.runtimeType}.initState failed to call super.initState');
debugPrint('${_state.runtimeType}.initState failed to call super.initState');
return false;
});
assert(() { _state._debugLifecycleState = _StateLifecycle.ready; return true; });
......@@ -1123,7 +1128,7 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>>
assert(() {
if (_state._debugLifecycleState == _StateLifecycle.defunct)
return true;
print('${_state.runtimeType}.dispose failed to call super.dispose');
debugPrint('${_state.runtimeType}.dispose failed to call super.dispose');
return false;
});
assert(!dirty); // See BuildableElement.unmount for why this is important.
......@@ -1267,7 +1272,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab
// dirty, e.g. if they have a builder callback. (Builder callbacks have a
// 'BuildContext' argument which you can pass to Theme.of() and other
// InheritedWidget APIs which eventually trigger a rebuild.)
print('$runtimeType failed to implement reinvokeBuilders(), but got marked dirty');
debugPrint('$runtimeType failed to implement reinvokeBuilders(), but got marked dirty');
assert(() {
'reinvokeBuilders() not implemented';
return false;
......@@ -1609,11 +1614,11 @@ void _debugReportException(String context, dynamic exception, StackTrace stack)
if (debugWidgetsExceptionHandler != null) {
debugWidgetsExceptionHandler(context, exception, stack);
} else {
print('------------------------------------------------------------------------');
'Exception caught while $context'.split('\n').forEach(print);
print('$exception');
print('Stack trace:');
'$stack'.split('\n').forEach(print);
print('------------------------------------------------------------------------');
debugPrint('------------------------------------------------------------------------');
debugPrint('Exception caught while $context');
debugPrint('$exception');
debugPrint('Stack trace:');
debugPrint('$stack');
debugPrint('------------------------------------------------------------------------');
}
}
......@@ -333,7 +333,7 @@ class _MixedViewportElement extends RenderObjectElement<MixedViewport> {
double newExtent = _getElementExtent(element, innerConstraints);
bool result = _childExtents[index] == newExtent;
if (!result)
print("Element $element at index $index was size ${_childExtents[index]} but is now size $newExtent yet no invalidate() was received to that effect");
debugPrint("Element $element at index $index was size ${_childExtents[index]} but is now size $newExtent yet no invalidate() was received to that effect");
return result;
}
......
......@@ -14,7 +14,7 @@ void main() {
routes: <String, RouteBuilder>{
'/': (RouteArguments args) {
navigator = args.navigator;
new Container();
return new Container();
}
}
)
......@@ -43,7 +43,7 @@ void main() {
routes: <String, RouteBuilder>{
'/': (RouteArguments args) {
navigator = args.navigator;
new Container();
return new Container();
}
}
)
......
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