debug.dart 17.1 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'box.dart';
6 7
import 'object.dart';

8
export 'package:flutter/foundation.dart' show debugPrint;
9

10 11 12
// Any changes to this file should be reflected in the debugAssertAllRenderVarsUnset()
// function below.

13
const HSVColor _kDebugDefaultRepaintColor = HSVColor.fromAHSV(0.4, 60.0, 1.0, 1.0);
14

15
/// Causes each RenderBox to paint a box around its bounds, and some extra
Ian Hickson's avatar
Ian Hickson committed
16
/// boxes, such as [RenderPadding], to draw construction lines.
17
///
18
/// The edges of the boxes are painted as a one-pixel-thick `const Color(0xFF00FFFF)` outline.
19
///
20
/// Spacing is painted as a solid `const Color(0x90909090)` area.
21
///
22 23 24
/// Padding is filled in solid `const Color(0x900090FF)`, with the inner edge
/// outlined in `const Color(0xFF0090FF)`, using [debugPaintPadding].
bool debugPaintSizeEnabled = false;
25

Adam Barth's avatar
Adam Barth committed
26
/// Causes each RenderBox to paint a line at each of its baselines.
27
bool debugPaintBaselinesEnabled = false;
Adam Barth's avatar
Adam Barth committed
28 29

/// Causes each Layer to paint a box around its bounds.
30
bool debugPaintLayerBordersEnabled = false;
Adam Barth's avatar
Adam Barth committed
31

32 33 34 35 36 37
/// Causes objects like [RenderPointerListener] to flash while they are being
/// tapped. This can be useful to see how large the hit box is, e.g. when
/// debugging buttons that are harder to hit than expected.
///
/// For details on how to support this in your [RenderBox] subclass, see
/// [RenderBox.debugHandleEvent].
38
bool debugPaintPointersEnabled = false;
39

40
/// Overlay a rotating set of colors when repainting layers in debug mode.
41 42 43 44 45
///
/// See also:
///
///  * [RepaintBoundary], which can be used to contain repaints when unchanged
///    areas are being excessively repainted.
46
bool debugRepaintRainbowEnabled = false;
47

48
/// Overlay a rotating set of colors when repainting text in debug mode.
49 50
bool debugRepaintTextRainbowEnabled = false;

51
/// The current color to overlay when repainting a layer.
52 53 54 55 56 57 58
///
/// This is used by painting debug code that implements
/// [debugRepaintRainbowEnabled] or [debugRepaintTextRainbowEnabled].
///
/// The value is incremented by [RenderView.compositeFrame] if either of those
/// flags is enabled.
HSVColor debugCurrentRepaintColor = _kDebugDefaultRepaintColor;
59

60
/// Log the call stacks that mark render objects as needing layout.
61 62 63 64 65
///
/// For sanity, this only logs the stack traces of cases where an object is
/// added to the list of nodes needing layout. This avoids printing multiple
/// redundant stack traces as a single [RenderObject.markNeedsLayout] call walks
/// up the tree.
66 67
bool debugPrintMarkNeedsLayoutStacks = false;

68 69 70 71 72 73 74 75 76 77 78 79 80 81
/// Log the call stacks that mark render objects as needing paint.
bool debugPrintMarkNeedsPaintStacks = false;

/// Log the dirty render objects that are laid out each frame.
///
/// Combined with [debugPrintBeginFrameBanner], this allows you to distinguish
/// layouts triggered by the initial mounting of a render tree (e.g. in a call
/// to [runApp]) from the regular layouts triggered by the pipeline.
///
/// Combined with [debugPrintMarkNeedsLayoutStacks], this lets you watch a
/// render object's dirty/clean lifecycle.
///
/// See also:
///
82 83 84 85
///  * [debugProfileLayoutsEnabled], which does something similar for layout
///    but using the timeline view.
///  * [debugProfilePaintsEnabled], which does something similar for painting
///    but using the timeline view.
86 87 88 89 90
///  * [debugPrintRebuildDirtyWidgets], which does something similar for widgets
///    being rebuilt.
///  * The discussion at [RendererBinding.drawFrame].
bool debugPrintLayouts = false;

91
/// Check the intrinsic sizes of each [RenderBox] during layout.
92
///
93 94 95
/// By default this is turned off since these checks are expensive. If you are
/// implementing your own children of [RenderBox] with custom intrinsics, turn
/// this on in your unit tests for additional validations.
96 97
bool debugCheckIntrinsicSizes = false;

98
/// Adds [Timeline] events for every [RenderObject] layout.
99
///
Ian Hickson's avatar
Ian Hickson committed
100 101 102 103 104 105 106 107 108 109 110 111
/// The timing information this flag exposes is not representative of the actual
/// cost of layout, because the overhead of adding timeline events is
/// significant relative to the time each object takes to lay out. However, it
/// can expose unexpected layout behavior in the timeline.
///
/// In debug builds, additional information is included in the trace (such as
/// the properties of render objects being laid out). Collecting this data is
/// expensive and further makes these traces non-representative of actual
/// performance. This data is omitted in profile builds.
///
/// For more information about performance debugging in Flutter, see
/// <https://flutter.dev/docs/perf/rendering>.
112 113 114 115 116 117 118 119
///
/// See also:
///
///  * [debugPrintLayouts], which does something similar for layout but using
///    console output.
///  * [debugProfileBuildsEnabled], which does something similar for widgets
///    being rebuilt.
///  * [debugProfilePaintsEnabled], which does something similar for painting.
120 121
///  * [debugEnhanceLayoutTimelineArguments], which enhances the trace with
///    debugging information related to [RenderObject] layouts.
122 123
bool debugProfileLayoutsEnabled = false;

124
/// Adds [Timeline] events for every [RenderObject] painted.
125
///
126
/// The timing information this flag exposes is not representative of actual
Ian Hickson's avatar
Ian Hickson committed
127 128 129 130 131 132 133 134
/// paints, because the overhead of adding timeline events is significant
/// relative to the time each object takes to paint. However, it can expose
/// unexpected painting in the timeline.
///
/// In debug builds, additional information is included in the trace (such as
/// the properties of render objects being painted). Collecting this data is
/// expensive and further makes these traces non-representative of actual
/// performance. This data is omitted in profile builds.
135
///
Ian Hickson's avatar
Ian Hickson committed
136 137
/// For more information about performance debugging in Flutter, see
/// <https://flutter.dev/docs/perf/rendering>.
138 139 140
///
/// See also:
///
141 142 143
///  * [debugProfileBuildsEnabled], which does something similar for widgets
///    being rebuilt, and [debugPrintRebuildDirtyWidgets], its console
///    equivalent.
144 145
///  * [debugProfileLayoutsEnabled], which does something similar for layout,
///    and [debugPrintLayouts], its console equivalent.
146
///  * The discussion at [RendererBinding.drawFrame].
147 148
///  * [RepaintBoundary], which can be used to contain repaints when unchanged
///    areas are being excessively repainted.
149 150
///  * [debugEnhancePaintTimelineArguments], which enhances the trace with
///    debugging information related to [RenderObject] paints.
151 152
bool debugProfilePaintsEnabled = false;

153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
/// Adds debugging information to [Timeline] events related to [RenderObject]
/// layouts.
///
/// This flag will only add [Timeline] event arguments for debug builds.
/// Additional arguments will be added for the "LAYOUT" timeline event and for
/// all [RenderObject] layout [Timeline] events, which are the events that are
/// added when [debugProfileLayoutsEnabled] is true. The debugging information
/// that will be added in trace arguments includes stats around [RenderObject]
/// dirty states and [RenderObject] diagnostic information (i.e. [RenderObject]
/// properties).
///
/// See also:
///
///  * [debugProfileLayoutsEnabled], which adds [Timeline] events for every
///    [RenderObject] layout.
///  * [debugEnhancePaintTimelineArguments], which does something similar for
///    events related to [RenderObject] paints.
///  * [debugEnhanceBuildTimelineArguments], which does something similar for
///    events related to [Widget] builds.
bool debugEnhanceLayoutTimelineArguments = false;

/// Adds debugging information to [Timeline] events related to [RenderObject]
/// paints.
///
/// This flag will only add [Timeline] event arguments for debug builds.
/// Additional arguments will be added for the "PAINT" timeline event and for
/// all [RenderObject] paint [Timeline] events, which are the [Timeline] events
/// that are added when [debugProfilePaintsEnabled] is true. The debugging
/// information that will be added in trace arguments includes stats around
/// [RenderObject] dirty states and [RenderObject] diagnostic information
/// (i.e. [RenderObject] properties).
///
/// See also:
///
///  * [debugProfilePaintsEnabled], which adds [Timeline] events for every
///    [RenderObject] paint.
///  * [debugEnhanceLayoutTimelineArguments], which does something similar for
///    events related to [RenderObject] layouts.
///  * [debugEnhanceBuildTimelineArguments], which does something similar for
///    events related to [Widget] builds.
bool debugEnhancePaintTimelineArguments = false;

195
/// Signature for [debugOnProfilePaint] implementations.
196
typedef ProfilePaintCallback = void Function(RenderObject renderObject);
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211

/// Callback invoked for every [RenderObject] painted each frame.
///
/// This callback is only invoked in debug builds.
///
/// See also:
///
///  * [debugProfilePaintsEnabled], which does something similar but adds
///    [dart:developer.Timeline] events instead of invoking a callback.
///  * [debugOnRebuildDirtyWidget], which does something similar for widgets
///    being built.
///  * [WidgetInspectorService], which uses the [debugOnProfilePaint]
///    callback to generate aggregate profile statistics describing what paints
///    occurred when the `ext.flutter.inspector.trackRepaintWidgets` service
///    extension is enabled.
212
ProfilePaintCallback? debugOnProfilePaint;
213

214 215
/// Setting to true will cause all clipping effects from the layer tree to be
/// ignored.
216 217
///
/// Can be used to debug whether objects being clipped are painting excessively
218 219 220 221 222 223
/// in clipped areas. Can also be used to check whether excessive use of
/// clipping is affecting performance.
///
/// This will not reduce the number of [Layer] objects created; the compositing
/// strategy is unaffected. It merely causes the clipping layers to be skipped
/// when building the scene.
224 225
bool debugDisableClipLayers = false;

226 227
/// Setting to true will cause all physical modeling effects from the layer
/// tree, such as shadows from elevations, to be ignored.
228 229 230
///
/// Can be used to check whether excessive use of physical models is affecting
/// performance.
231 232 233 234
///
/// This will not reduce the number of [Layer] objects created; the compositing
/// strategy is unaffected. It merely causes the physical shape layers to be
/// skipped when building the scene.
235 236
bool debugDisablePhysicalShapeLayers = false;

237 238
/// Setting to true will cause all opacity effects from the layer tree to be
/// ignored.
239 240
///
/// An optimization to not paint the child at all when opacity is 0 will still
241 242 243 244 245 246 247 248
/// remain.
///
/// Can be used to check whether excessive use of opacity effects is affecting
/// performance.
///
/// This will not reduce the number of [Layer] objects created; the compositing
/// strategy is unaffected. It merely causes the opacity layers to be skipped
/// when building the scene.
249
bool debugDisableOpacityLayers = false;
250

251
void _debugDrawDoubleRect(Canvas canvas, Rect outerRect, Rect innerRect, Color color) {
252
  final Path path = Path()
253 254 255
    ..fillType = PathFillType.evenOdd
    ..addRect(outerRect)
    ..addRect(innerRect);
256
  final Paint paint = Paint()
257 258 259 260
    ..color = color;
  canvas.drawPath(path, paint);
}

261
/// Paint a diagram showing the given area as padding.
262
///
263 264 265 266 267 268 269 270 271 272 273
/// The `innerRect` argument represents the position of the child, if any.
///
/// When `innerRect` is null, the method draws the entire `outerRect` in a
/// grayish color representing _spacing_.
///
/// When `innerRect` is non-null, the method draws the padding region around the
/// `innerRect` in a tealish color, with a solid outline around the inner
/// region.
///
/// This method is used by [RenderPadding.debugPaintSize] when
/// [debugPaintSizeEnabled] is true.
274
void debugPaintPadding(Canvas canvas, Rect outerRect, Rect? innerRect, { double outlineWidth = 2.0 }) {
275 276
  assert(() {
    if (innerRect != null && !innerRect.isEmpty) {
277 278
      _debugDrawDoubleRect(canvas, outerRect, innerRect, const Color(0x900090FF));
      _debugDrawDoubleRect(canvas, innerRect.inflate(outlineWidth).intersect(outerRect), innerRect, const Color(0xFF0090FF));
279
    } else {
280
      final Paint paint = Paint()
281
        ..color = const Color(0x90909090);
282 283 284
      canvas.drawRect(outerRect, paint);
    }
    return true;
285
  }());
286 287
}

288 289 290 291 292
/// Returns true if none of the rendering library debug variables have been changed.
///
/// This function is used by the test framework to ensure that debug variables
/// haven't been inadvertently changed.
///
293 294
/// See [the rendering library](rendering/rendering-library.html) for a complete
/// list.
295 296 297 298
///
/// The `debugCheckIntrinsicSizesOverride` argument can be provided to override
/// the expected value for [debugCheckIntrinsicSizes]. (This exists because the
/// test framework itself overrides this value in some cases.)
299
bool debugAssertAllRenderVarsUnset(String reason, { bool debugCheckIntrinsicSizesOverride = false }) {
300 301 302 303 304 305
  assert(() {
    if (debugPaintSizeEnabled ||
        debugPaintBaselinesEnabled ||
        debugPaintLayerBordersEnabled ||
        debugPaintPointersEnabled ||
        debugRepaintRainbowEnabled ||
306
        debugRepaintTextRainbowEnabled ||
307
        debugCurrentRepaintColor != _kDebugDefaultRepaintColor ||
308
        debugPrintMarkNeedsLayoutStacks ||
309 310
        debugPrintMarkNeedsPaintStacks ||
        debugPrintLayouts ||
311
        debugCheckIntrinsicSizes != debugCheckIntrinsicSizesOverride ||
312
        debugProfileLayoutsEnabled ||
313
        debugProfilePaintsEnabled ||
314 315 316 317
        debugOnProfilePaint != null ||
        debugDisableClipLayers ||
        debugDisablePhysicalShapeLayers ||
        debugDisableOpacityLayers) {
318
      throw FlutterError(reason);
319 320
    }
    return true;
321
  }());
322 323
  return true;
}
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398

/// Returns true if the given [Axis] is bounded within the given
/// [BoxConstraints] in both the main and cross axis, throwing an exception
/// otherwise.
///
/// This is used by viewports during `performLayout` and `computeDryLayout`
/// because bounded constraints are required in order to layout their children.
bool debugCheckHasBoundedAxis(Axis axis, BoxConstraints constraints) {
  assert(() {
    if (!constraints.hasBoundedHeight || !constraints.hasBoundedWidth) {
      switch (axis) {
        case Axis.vertical:
          if (!constraints.hasBoundedHeight) {
            throw FlutterError.fromParts(<DiagnosticsNode>[
              ErrorSummary('Vertical viewport was given unbounded height.'),
              ErrorDescription(
                'Viewports expand in the scrolling direction to fill their container. '
                'In this case, a vertical viewport was given an unlimited amount of '
                'vertical space in which to expand. This situation typically happens '
                'when a scrollable widget is nested inside another scrollable widget.',
              ),
              ErrorHint(
                'If this widget is always nested in a scrollable widget there '
                'is no need to use a viewport because there will always be enough '
                'vertical space for the children. In this case, consider using a '
                'Column or Wrap instead. Otherwise, consider using a '
                'CustomScrollView to concatenate arbitrary slivers into a '
                'single scrollable.',
              ),
            ]);
          }
          if (!constraints.hasBoundedWidth) {
            throw FlutterError(
              'Vertical viewport was given unbounded width.\n'
              'Viewports expand in the cross axis to fill their container and '
              'constrain their children to match their extent in the cross axis. '
              'In this case, a vertical viewport was given an unlimited amount of '
              'horizontal space in which to expand.',
            );
          }
        case Axis.horizontal:
          if (!constraints.hasBoundedWidth) {
            throw FlutterError.fromParts(<DiagnosticsNode>[
              ErrorSummary('Horizontal viewport was given unbounded width.'),
              ErrorDescription(
                'Viewports expand in the scrolling direction to fill their container. '
                'In this case, a horizontal viewport was given an unlimited amount of '
                'horizontal space in which to expand. This situation typically happens '
                'when a scrollable widget is nested inside another scrollable widget.',
              ),
              ErrorHint(
                'If this widget is always nested in a scrollable widget there '
                'is no need to use a viewport because there will always be enough '
                'horizontal space for the children. In this case, consider using a '
                'Row or Wrap instead. Otherwise, consider using a '
                'CustomScrollView to concatenate arbitrary slivers into a '
                'single scrollable.',
              ),
            ]);
          }
          if (!constraints.hasBoundedHeight) {
            throw FlutterError(
              'Horizontal viewport was given unbounded height.\n'
              'Viewports expand in the cross axis to fill their container and '
              'constrain their children to match their extent in the cross axis. '
              'In this case, a horizontal viewport was given an unlimited amount of '
              'vertical space in which to expand.',
            );
          }
      }
    }
    return true;
  }());
  return true;
}