• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2017 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6import 'dart:convert';
7import 'dart:developer' as developer;
8import 'dart:math' as math;
9import 'dart:typed_data';
10import 'dart:ui' as ui
11    show
12        ClipOp,
13        Image,
14        ImageByteFormat,
15        Paragraph,
16        Picture,
17        PictureRecorder,
18        PointMode,
19        SceneBuilder,
20        Vertices;
21import 'dart:ui' show Canvas, Offset;
22
23import 'package:flutter/foundation.dart';
24import 'package:flutter/painting.dart';
25import 'package:flutter/rendering.dart';
26import 'package:flutter/scheduler.dart';
27import 'package:vector_math/vector_math_64.dart';
28
29import 'app.dart';
30import 'basic.dart';
31import 'binding.dart';
32import 'debug.dart';
33import 'framework.dart';
34import 'gesture_detector.dart';
35
36/// Signature for the builder callback used by
37/// [WidgetInspector.selectButtonBuilder].
38typedef InspectorSelectButtonBuilder = Widget Function(BuildContext context, VoidCallback onPressed);
39
40typedef _RegisterServiceExtensionCallback = void Function({
41  @required String name,
42  @required ServiceExtensionCallback callback,
43});
44
45/// A layer that mimics the behavior of another layer.
46///
47/// A proxy layer is used for cases where a layer needs to be placed into
48/// multiple trees of layers.
49class _ProxyLayer extends Layer {
50  _ProxyLayer(this._layer);
51
52  final Layer _layer;
53
54  @override
55  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
56    _layer.addToScene(builder, layerOffset);
57  }
58
59  @override
60  S find<S>(Offset regionOffset) => _layer.find(regionOffset);
61
62  @override
63  Iterable<S> findAll<S>(Offset regionOffset) => <S>[];
64}
65
66/// A [Canvas] that multicasts all method calls to a main canvas and a
67/// secondary screenshot canvas so that a screenshot can be recorded at the same
68/// time as performing a normal paint.
69class _MulticastCanvas implements Canvas {
70  _MulticastCanvas({
71    @required Canvas main,
72    @required Canvas screenshot,
73  }) : assert(main != null),
74       assert(screenshot != null),
75       _main = main,
76       _screenshot = screenshot;
77
78  final Canvas _main;
79  final Canvas _screenshot;
80
81  @override
82  void clipPath(Path path, { bool doAntiAlias = true }) {
83    _main.clipPath(path, doAntiAlias: doAntiAlias);
84    _screenshot.clipPath(path, doAntiAlias: doAntiAlias);
85  }
86
87  @override
88  void clipRRect(RRect rrect, { bool doAntiAlias = true }) {
89    _main.clipRRect(rrect, doAntiAlias: doAntiAlias);
90    _screenshot.clipRRect(rrect, doAntiAlias: doAntiAlias);
91  }
92
93  @override
94  void clipRect(Rect rect, { ui.ClipOp clipOp = ui.ClipOp.intersect, bool doAntiAlias = true }) {
95    _main.clipRect(rect, clipOp: clipOp, doAntiAlias: doAntiAlias);
96    _screenshot.clipRect(rect, clipOp: clipOp, doAntiAlias: doAntiAlias);
97  }
98
99  @override
100  void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint) {
101    _main.drawArc(rect, startAngle, sweepAngle, useCenter, paint);
102    _screenshot.drawArc(rect, startAngle, sweepAngle, useCenter, paint);
103  }
104
105  @override
106  void drawAtlas(ui.Image atlas, List<RSTransform> transforms, List<Rect> rects, List<Color> colors, BlendMode blendMode, Rect cullRect, Paint paint) {
107    _main.drawAtlas(atlas, transforms, rects, colors, blendMode, cullRect, paint);
108    _screenshot.drawAtlas(atlas, transforms, rects, colors, blendMode, cullRect, paint);
109  }
110
111  @override
112  void drawCircle(Offset c, double radius, Paint paint) {
113    _main.drawCircle(c, radius, paint);
114    _screenshot.drawCircle(c, radius, paint);
115  }
116
117  @override
118  void drawColor(Color color, BlendMode blendMode) {
119    _main.drawColor(color, blendMode);
120    _screenshot.drawColor(color, blendMode);
121  }
122
123  @override
124  void drawDRRect(RRect outer, RRect inner, Paint paint) {
125    _main.drawDRRect(outer, inner, paint);
126    _screenshot.drawDRRect(outer, inner, paint);
127  }
128
129  @override
130  void drawImage(ui.Image image, Offset p, Paint paint) {
131    _main.drawImage(image, p, paint);
132    _screenshot.drawImage(image, p, paint);
133  }
134
135  @override
136  void drawImageNine(ui.Image image, Rect center, Rect dst, Paint paint) {
137    _main.drawImageNine(image, center, dst, paint);
138    _screenshot.drawImageNine(image, center, dst, paint);
139  }
140
141  @override
142  void drawImageRect(ui.Image image, Rect src, Rect dst, Paint paint) {
143    _main.drawImageRect(image, src, dst, paint);
144    _screenshot.drawImageRect(image, src, dst, paint);
145  }
146
147  @override
148  void drawLine(Offset p1, Offset p2, Paint paint) {
149    _main.drawLine(p1, p2, paint);
150    _screenshot.drawLine(p1, p2, paint);
151  }
152
153  @override
154  void drawOval(Rect rect, Paint paint) {
155    _main.drawOval(rect, paint);
156    _screenshot.drawOval(rect, paint);
157  }
158
159  @override
160  void drawPaint(Paint paint) {
161    _main.drawPaint(paint);
162    _screenshot.drawPaint(paint);
163  }
164
165  @override
166  void drawParagraph(ui.Paragraph paragraph, Offset offset) {
167    _main.drawParagraph(paragraph, offset);
168    _screenshot.drawParagraph(paragraph, offset);
169  }
170
171  @override
172  void drawPath(Path path, Paint paint) {
173    _main.drawPath(path, paint);
174    _screenshot.drawPath(path, paint);
175  }
176
177  @override
178  void drawPicture(ui.Picture picture) {
179    _main.drawPicture(picture);
180    _screenshot.drawPicture(picture);
181  }
182
183  @override
184  void drawPoints(ui.PointMode pointMode, List<Offset> points, Paint paint) {
185    _main.drawPoints(pointMode, points, paint);
186    _screenshot.drawPoints(pointMode, points, paint);
187  }
188
189  @override
190  void drawRRect(RRect rrect, Paint paint) {
191    _main.drawRRect(rrect, paint);
192    _screenshot.drawRRect(rrect, paint);
193  }
194
195  @override
196  void drawRawAtlas(ui.Image atlas, Float32List rstTransforms, Float32List rects, Int32List colors, BlendMode blendMode, Rect cullRect, Paint paint) {
197    _main.drawRawAtlas(atlas, rstTransforms, rects, colors, blendMode, cullRect, paint);
198    _screenshot.drawRawAtlas(atlas, rstTransforms, rects, colors, blendMode, cullRect, paint);
199  }
200
201  @override
202  void drawRawPoints(ui.PointMode pointMode, Float32List points, Paint paint) {
203    _main.drawRawPoints(pointMode, points, paint);
204    _screenshot.drawRawPoints(pointMode, points, paint);
205  }
206
207  @override
208  void drawRect(Rect rect, Paint paint) {
209    _main.drawRect(rect, paint);
210    _screenshot.drawRect(rect, paint);
211  }
212
213  @override
214  void drawShadow(Path path, Color color, double elevation, bool transparentOccluder) {
215    _main.drawShadow(path, color, elevation, transparentOccluder);
216    _screenshot.drawShadow(path, color, elevation, transparentOccluder);
217  }
218
219  @override
220  void drawVertices(ui.Vertices vertices, BlendMode blendMode, Paint paint) {
221    _main.drawVertices(vertices, blendMode, paint);
222    _screenshot.drawVertices(vertices, blendMode, paint);
223  }
224
225  @override
226  int getSaveCount() {
227    // The main canvas is used instead of the screenshot canvas as the main
228    // canvas is guaranteed to be consistent with the canvas expected by the
229    // normal paint pipeline so any logic depending on getSaveCount() will
230    // behave the same as for the regular paint pipeline.
231    return _main.getSaveCount();
232  }
233
234  @override
235  void restore() {
236    _main.restore();
237    _screenshot.restore();
238  }
239
240  @override
241  void rotate(double radians) {
242    _main.rotate(radians);
243    _screenshot.rotate(radians);
244  }
245
246  @override
247  void save() {
248    _main.save();
249    _screenshot.save();
250  }
251
252  @override
253  void saveLayer(Rect bounds, Paint paint) {
254    _main.saveLayer(bounds, paint);
255    _screenshot.saveLayer(bounds, paint);
256  }
257
258  @override
259  void scale(double sx, [ double sy ]) {
260    _main.scale(sx, sy);
261    _screenshot.scale(sx, sy);
262  }
263
264  @override
265  void skew(double sx, double sy) {
266    _main.skew(sx, sy);
267    _screenshot.skew(sx, sy);
268  }
269
270  @override
271  void transform(Float64List matrix4) {
272    _main.transform(matrix4);
273    _screenshot.transform(matrix4);
274  }
275
276  @override
277  void translate(double dx, double dy) {
278    _main.translate(dx, dy);
279    _screenshot.translate(dx, dy);
280  }
281}
282
283Rect _calculateSubtreeBoundsHelper(RenderObject object, Matrix4 transform) {
284  Rect bounds = MatrixUtils.transformRect(transform, object.semanticBounds);
285
286  object.visitChildren((RenderObject child) {
287    final Matrix4 childTransform = transform.clone();
288    object.applyPaintTransform(child, childTransform);
289    Rect childBounds = _calculateSubtreeBoundsHelper(child, childTransform);
290    final Rect paintClip = object.describeApproximatePaintClip(child);
291    if (paintClip != null) {
292      final Rect transformedPaintClip = MatrixUtils.transformRect(
293        transform,
294        paintClip,
295      );
296      childBounds = childBounds.intersect(transformedPaintClip);
297    }
298
299    if (childBounds.isFinite && !childBounds.isEmpty) {
300      bounds = bounds.isEmpty ? childBounds : bounds.expandToInclude(childBounds);
301    }
302  });
303
304  return bounds;
305}
306
307/// Calculate bounds for a render object and all of its descendants.
308Rect _calculateSubtreeBounds(RenderObject object) {
309  return _calculateSubtreeBoundsHelper(object, Matrix4.identity());
310}
311
312/// A layer that omits its own offset when adding children to the scene so that
313/// screenshots render to the scene in the local coordinate system of the layer.
314class _ScreenshotContainerLayer extends OffsetLayer {
315  @override
316  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
317    addChildrenToScene(builder, layerOffset);
318  }
319}
320
321/// Data shared between nested [_ScreenshotPaintingContext] objects recording
322/// a screenshot.
323class _ScreenshotData {
324  _ScreenshotData({
325    @required this.target,
326  }) : assert(target != null),
327       containerLayer = _ScreenshotContainerLayer();
328
329  /// Target to take a screenshot of.
330  final RenderObject target;
331
332  /// Root of the layer tree containing the screenshot.
333  final OffsetLayer containerLayer;
334
335  /// Whether the screenshot target has already been found in the render tree.
336  bool foundTarget = false;
337
338  /// Whether paint operations should record to the screenshot.
339  ///
340  /// At least one of [includeInScreenshot] and [includeInRegularContext] must
341  /// be true.
342  bool includeInScreenshot = false;
343
344  /// Whether paint operations should record to the regular context.
345  ///
346  /// This should only be set to false before paint operations that should only
347  /// apply to the screenshot such rendering debug information about the
348  /// [target].
349  ///
350  /// At least one of [includeInScreenshot] and [includeInRegularContext] must
351  /// be true.
352  bool includeInRegularContext = true;
353
354  /// Offset of the screenshot corresponding to the offset [target] was given as
355  /// part of the regular paint.
356  Offset get screenshotOffset {
357    assert(foundTarget);
358    return containerLayer.offset;
359  }
360  set screenshotOffset(Offset offset) {
361    containerLayer.offset = offset;
362  }
363}
364
365/// A place to paint to build screenshots of [RenderObject]s.
366///
367/// Requires that the render objects have already painted successfully as part
368/// of the regular rendering pipeline.
369/// This painting context behaves the same as standard [PaintingContext] with
370/// instrumentation added to compute a screenshot of a specified [RenderObject]
371/// added. To correctly mimic the behavior of the regular rendering pipeline, the
372/// full subtree of the first [RepaintBoundary] ancestor of the specified
373/// [RenderObject] will also be rendered rather than just the subtree of the
374/// render object.
375class _ScreenshotPaintingContext extends PaintingContext {
376  _ScreenshotPaintingContext({
377    @required ContainerLayer containerLayer,
378    @required Rect estimatedBounds,
379    @required _ScreenshotData screenshotData,
380  }) : _data = screenshotData,
381       super(containerLayer, estimatedBounds);
382
383  final _ScreenshotData _data;
384
385  // Recording state
386  PictureLayer _screenshotCurrentLayer;
387  ui.PictureRecorder _screenshotRecorder;
388  Canvas _screenshotCanvas;
389  _MulticastCanvas _multicastCanvas;
390
391  @override
392  Canvas get canvas {
393    if (_data.includeInScreenshot) {
394      if (_screenshotCanvas == null) {
395        _startRecordingScreenshot();
396      }
397      assert(_screenshotCanvas != null);
398      return _data.includeInRegularContext ? _multicastCanvas : _screenshotCanvas;
399    } else {
400      assert(_data.includeInRegularContext);
401      return super.canvas;
402    }
403  }
404
405  bool get _isScreenshotRecording {
406    final bool hasScreenshotCanvas = _screenshotCanvas != null;
407    assert(() {
408      if (hasScreenshotCanvas) {
409        assert(_screenshotCurrentLayer != null);
410        assert(_screenshotRecorder != null);
411        assert(_screenshotCanvas != null);
412      } else {
413        assert(_screenshotCurrentLayer == null);
414        assert(_screenshotRecorder == null);
415        assert(_screenshotCanvas == null);
416      }
417      return true;
418    }());
419    return hasScreenshotCanvas;
420  }
421
422  void _startRecordingScreenshot() {
423    assert(_data.includeInScreenshot);
424    assert(!_isScreenshotRecording);
425    _screenshotCurrentLayer = PictureLayer(estimatedBounds);
426    _screenshotRecorder = ui.PictureRecorder();
427    _screenshotCanvas = Canvas(_screenshotRecorder);
428    _data.containerLayer.append(_screenshotCurrentLayer);
429    if (_data.includeInRegularContext) {
430      _multicastCanvas = _MulticastCanvas(
431        main: super.canvas,
432        screenshot: _screenshotCanvas,
433      );
434    } else {
435      _multicastCanvas = null;
436    }
437  }
438
439  @override
440  void stopRecordingIfNeeded() {
441    super.stopRecordingIfNeeded();
442    _stopRecordingScreenshotIfNeeded();
443  }
444
445  void _stopRecordingScreenshotIfNeeded() {
446    if (!_isScreenshotRecording)
447      return;
448    // There is no need to ever draw repaint rainbows as part of the screenshot.
449    _screenshotCurrentLayer.picture = _screenshotRecorder.endRecording();
450    _screenshotCurrentLayer = null;
451    _screenshotRecorder = null;
452    _multicastCanvas = null;
453    _screenshotCanvas = null;
454  }
455
456  @override
457  void appendLayer(Layer layer) {
458    if (_data.includeInRegularContext) {
459      super.appendLayer(layer);
460      if (_data.includeInScreenshot) {
461        assert(!_isScreenshotRecording);
462        // We must use a proxy layer here as the layer is already attached to
463        // the regular layer tree.
464        _data.containerLayer.append(_ProxyLayer(layer));
465      }
466    } else {
467      // Only record to the screenshot.
468      assert(!_isScreenshotRecording);
469      assert(_data.includeInScreenshot);
470      layer.remove();
471      _data.containerLayer.append(layer);
472      return;
473    }
474  }
475
476  @override
477  PaintingContext createChildContext(ContainerLayer childLayer, Rect bounds) {
478    if (_data.foundTarget) {
479      // We have already found the screenshotTarget in the layer tree
480      // so we can optimize and use a standard PaintingContext.
481      return super.createChildContext(childLayer, bounds);
482    } else {
483      return _ScreenshotPaintingContext(
484        containerLayer: childLayer,
485        estimatedBounds: bounds,
486        screenshotData: _data,
487      );
488    }
489  }
490
491  @override
492  void paintChild(RenderObject child, Offset offset) {
493    final bool isScreenshotTarget = identical(child, _data.target);
494    if (isScreenshotTarget) {
495      assert(!_data.includeInScreenshot);
496      assert(!_data.foundTarget);
497      _data.foundTarget = true;
498      _data.screenshotOffset = offset;
499      _data.includeInScreenshot = true;
500    }
501    super.paintChild(child, offset);
502    if (isScreenshotTarget) {
503      _stopRecordingScreenshotIfNeeded();
504      _data.includeInScreenshot = false;
505    }
506  }
507
508  /// Captures an image of the current state of [renderObject] and its children.
509  ///
510  /// The returned [ui.Image] has uncompressed raw RGBA bytes, will be offset
511  /// by the top-left corner of [renderBounds], and have dimensions equal to the
512  /// size of [renderBounds] multiplied by [pixelRatio].
513  ///
514  /// To use [toImage], the render object must have gone through the paint phase
515  /// (i.e. [debugNeedsPaint] must be false).
516  ///
517  /// The [pixelRatio] describes the scale between the logical pixels and the
518  /// size of the output image. It is independent of the
519  /// [window.devicePixelRatio] for the device, so specifying 1.0 (the default)
520  /// will give you a 1:1 mapping between logical pixels and the output pixels
521  /// in the image.
522  ///
523  /// The [debugPaint] argument specifies whether the image should include the
524  /// output of [RenderObject.debugPaint] for [renderObject] with
525  /// [debugPaintSizeEnabled] set to true. Debug paint information is not
526  /// included for the children of [renderObject] so that it is clear precisely
527  /// which object the debug paint information references.
528  ///
529  /// See also:
530  ///
531  ///  * [RenderRepaintBoundary.toImage] for a similar API for [RenderObject]s
532  ///    that are repaint boundaries that can be used outside of the inspector.
533  ///  * [OffsetLayer.toImage] for a similar API at the layer level.
534  ///  * [dart:ui.Scene.toImage] for more information about the image returned.
535  static Future<ui.Image> toImage(
536    RenderObject renderObject,
537    Rect renderBounds, {
538    double pixelRatio = 1.0,
539    bool debugPaint = false,
540  }) {
541    RenderObject repaintBoundary = renderObject;
542    while (repaintBoundary != null && !repaintBoundary.isRepaintBoundary) {
543      repaintBoundary = repaintBoundary.parent;
544    }
545    assert(repaintBoundary != null);
546    final _ScreenshotData data = _ScreenshotData(target: renderObject);
547    final _ScreenshotPaintingContext context = _ScreenshotPaintingContext(
548      containerLayer: repaintBoundary.debugLayer,
549      estimatedBounds: repaintBoundary.paintBounds,
550      screenshotData: data,
551    );
552
553    if (identical(renderObject, repaintBoundary)) {
554      // Painting the existing repaint boundary to the screenshot is sufficient.
555      // We don't just take a direct screenshot of the repaint boundary as we
556      // want to capture debugPaint information as well.
557      data.containerLayer.append(_ProxyLayer(repaintBoundary.debugLayer));
558      data.foundTarget = true;
559      final OffsetLayer offsetLayer = repaintBoundary.debugLayer;
560      data.screenshotOffset = offsetLayer.offset;
561    } else {
562      // Repaint everything under the repaint boundary.
563      // We call debugInstrumentRepaintCompositedChild instead of paintChild as
564      // we need to force everything under the repaint boundary to repaint.
565      PaintingContext.debugInstrumentRepaintCompositedChild(
566        repaintBoundary,
567        customContext: context,
568      );
569    }
570
571    // The check that debugPaintSizeEnabled is false exists to ensure we only
572    // call debugPaint when it wasn't already called.
573    if (debugPaint && !debugPaintSizeEnabled) {
574      data.includeInRegularContext = false;
575      // Existing recording may be to a canvas that draws to both the normal and
576      // screenshot canvases.
577      context.stopRecordingIfNeeded();
578      assert(data.foundTarget);
579      data.includeInScreenshot = true;
580
581      debugPaintSizeEnabled = true;
582      try {
583        renderObject.debugPaint(context, data.screenshotOffset);
584      } finally {
585        debugPaintSizeEnabled = false;
586        context.stopRecordingIfNeeded();
587      }
588    }
589
590    // We must build the regular scene before we can build the screenshot
591    // scene as building the screenshot scene assumes addToScene has already
592    // been called successfully for all layers in the regular scene.
593    repaintBoundary.debugLayer.buildScene(ui.SceneBuilder());
594
595    return data.containerLayer.toImage(renderBounds, pixelRatio: pixelRatio);
596  }
597}
598
599/// A class describing a step along a path through a tree of [DiagnosticsNode]
600/// objects.
601///
602/// This class is used to bundle all data required to display the tree with just
603/// the nodes along a path expanded into a single JSON payload.
604class _DiagnosticsPathNode {
605  /// Creates a full description of a step in a path through a tree of
606  /// [DiagnosticsNode] objects.
607  ///
608  /// The [node] and [child] arguments must not be null.
609  _DiagnosticsPathNode({
610    @required this.node,
611    @required this.children,
612    this.childIndex,
613  }) : assert(node != null),
614       assert(children != null);
615
616  /// Node at the point in the path this [_DiagnosticsPathNode] is describing.
617  final DiagnosticsNode node;
618
619  /// Children of the [node] being described.
620  ///
621  /// This value is cached instead of relying on `node.getChildren()` as that
622  /// method call might create new [DiagnosticsNode] objects for each child
623  /// and we would prefer to use the identical [DiagnosticsNode] for each time
624  /// a node exists in the path.
625  final List<DiagnosticsNode> children;
626
627  /// Index of the child that the path continues on.
628  ///
629  /// Equal to null if the path does not continue.
630  final int childIndex;
631}
632
633List<_DiagnosticsPathNode> _followDiagnosticableChain(
634  List<Diagnosticable> chain, {
635  String name,
636  DiagnosticsTreeStyle style,
637}) {
638  final List<_DiagnosticsPathNode> path = <_DiagnosticsPathNode>[];
639  if (chain.isEmpty)
640    return path;
641  DiagnosticsNode diagnostic = chain.first.toDiagnosticsNode(name: name, style: style);
642  for (int i = 1; i < chain.length; i += 1) {
643    final Diagnosticable target = chain[i];
644    bool foundMatch = false;
645    final List<DiagnosticsNode> children = diagnostic.getChildren();
646    for (int j = 0; j < children.length; j += 1) {
647      final DiagnosticsNode child = children[j];
648      if (child.value == target) {
649        foundMatch = true;
650        path.add(_DiagnosticsPathNode(
651          node: diagnostic,
652          children: children,
653          childIndex: j,
654        ));
655        diagnostic = child;
656        break;
657      }
658    }
659    assert(foundMatch);
660  }
661  path.add(_DiagnosticsPathNode(node: diagnostic, children: diagnostic.getChildren()));
662  return path;
663}
664
665/// Signature for the selection change callback used by
666/// [WidgetInspectorService.selectionChangedCallback].
667typedef InspectorSelectionChangedCallback = void Function();
668
669/// Structure to help reference count Dart objects referenced by a GUI tool
670/// using [WidgetInspectorService].
671class _InspectorReferenceData {
672  _InspectorReferenceData(this.object);
673
674  final Object object;
675  int count = 1;
676}
677
678// Production implementation of [WidgetInspectorService].
679class _WidgetInspectorService = Object with WidgetInspectorService;
680
681/// Service used by GUI tools to interact with the [WidgetInspector].
682///
683/// Calls to this object are typically made from GUI tools such as the [Flutter
684/// IntelliJ Plugin](https://github.com/flutter/flutter-intellij/blob/master/README.md)
685/// using the [Dart VM Service protocol](https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md).
686/// This class uses its own object id and manages object lifecycles itself
687/// instead of depending on the [object ids](https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#getobject)
688/// specified by the VM Service Protocol because the VM Service Protocol ids
689/// expire unpredictably. Object references are tracked in groups so that tools
690/// that clients can use dereference all objects in a group with a single
691/// operation making it easier to avoid memory leaks.
692///
693/// All methods in this class are appropriate to invoke from debugging tools
694/// using the Observatory service protocol to evaluate Dart expressions of the
695/// form `WidgetInspectorService.instance.methodName(arg1, arg2, ...)`. If you
696/// make changes to any instance method of this class you need to verify that
697/// the [Flutter IntelliJ Plugin](https://github.com/flutter/flutter-intellij/blob/master/README.md)
698/// widget inspector support still works with the changes.
699///
700/// All methods returning String values return JSON.
701mixin WidgetInspectorService {
702  /// Ring of cached JSON values to prevent json from being garbage
703  /// collected before it can be requested over the Observatory protocol.
704  final List<String> _serializeRing = List<String>(20);
705  int _serializeRingIndex = 0;
706
707  /// The current [WidgetInspectorService].
708  static WidgetInspectorService get instance => _instance;
709  static WidgetInspectorService _instance = _WidgetInspectorService();
710  @protected
711  static set instance(WidgetInspectorService instance) {
712    _instance = instance;
713  }
714
715  static bool _debugServiceExtensionsRegistered = false;
716
717  /// Ground truth tracking what object(s) are currently selected used by both
718  /// GUI tools such as the Flutter IntelliJ Plugin and the [WidgetInspector]
719  /// displayed on the device.
720  final InspectorSelection selection = InspectorSelection();
721
722  /// Callback typically registered by the [WidgetInspector] to receive
723  /// notifications when [selection] changes.
724  ///
725  /// The Flutter IntelliJ Plugin does not need to listen for this event as it
726  /// instead listens for `dart:developer` `inspect` events which also trigger
727  /// when the inspection target changes on device.
728  InspectorSelectionChangedCallback selectionChangedCallback;
729
730  /// The Observatory protocol does not keep alive object references so this
731  /// class needs to manually manage groups of objects that should be kept
732  /// alive.
733  final Map<String, Set<_InspectorReferenceData>> _groups = <String, Set<_InspectorReferenceData>>{};
734  final Map<String, _InspectorReferenceData> _idToReferenceData = <String, _InspectorReferenceData>{};
735  final Map<Object, String> _objectToId = Map<Object, String>.identity();
736  int _nextId = 0;
737
738  List<String> _pubRootDirectories;
739
740  bool _trackRebuildDirtyWidgets = false;
741  bool _trackRepaintWidgets = false;
742
743  _RegisterServiceExtensionCallback _registerServiceExtensionCallback;
744  /// Registers a service extension method with the given name (full
745  /// name "ext.flutter.inspector.name").
746  ///
747  /// The given callback is called when the extension method is called. The
748  /// callback must return a value that can be converted to JSON using
749  /// `json.encode()` (see [JsonEncoder]). The return value is stored as a
750  /// property named `result` in the JSON. In case of failure, the failure is
751  /// reported to the remote caller and is dumped to the logs.
752  @protected
753  void registerServiceExtension({
754    @required String name,
755    @required ServiceExtensionCallback callback,
756  }) {
757    _registerServiceExtensionCallback(
758      name: 'inspector.$name',
759      callback: callback,
760    );
761  }
762
763  /// Registers a service extension method with the given name (full
764  /// name "ext.flutter.inspector.name"), which takes no arguments.
765  void _registerSignalServiceExtension({
766    @required String name,
767    @required FutureOr<Object> callback(),
768  }) {
769    registerServiceExtension(
770      name: name,
771      callback: (Map<String, String> parameters) async {
772        return <String, Object>{'result': await callback()};
773      },
774    );
775  }
776
777  /// Registers a service extension method with the given name (full
778  /// name "ext.flutter.inspector.name"), which takes a single optional argument
779  /// "objectGroup" specifying what group is used to manage lifetimes of
780  /// object references in the returned JSON (see [disposeGroup]).
781  /// If "objectGroup" is omitted, the returned JSON will not include any object
782  /// references to avoid leaking memory.
783  void _registerObjectGroupServiceExtension({
784    @required String name,
785    @required FutureOr<Object> callback(String objectGroup),
786  }) {
787    registerServiceExtension(
788      name: name,
789      callback: (Map<String, String> parameters) async {
790        return <String, Object>{'result': await callback(parameters['objectGroup'])};
791      },
792    );
793  }
794
795  /// Registers a service extension method with the given name (full
796  /// name "ext.flutter.inspector.name"), which takes a single argument
797  /// "enabled" which can have the value "true" or the value "false"
798  /// or can be omitted to read the current value. (Any value other
799  /// than "true" is considered equivalent to "false". Other arguments
800  /// are ignored.)
801  ///
802  /// Calls the `getter` callback to obtain the value when
803  /// responding to the service extension method being called.
804  ///
805  /// Calls the `setter` callback with the new value when the
806  /// service extension method is called with a new value.
807  void _registerBoolServiceExtension({
808    @required String name,
809    @required AsyncValueGetter<bool> getter,
810    @required AsyncValueSetter<bool> setter,
811  }) {
812    assert(name != null);
813    assert(getter != null);
814    assert(setter != null);
815    registerServiceExtension(
816      name: name,
817      callback: (Map<String, String> parameters) async {
818        if (parameters.containsKey('enabled')) {
819          final bool value = parameters['enabled'] == 'true';
820          await setter(value);
821          _postExtensionStateChangedEvent(name, value);
822        }
823        return <String, dynamic>{'enabled': await getter() ? 'true' : 'false'};
824      },
825    );
826  }
827
828  /// Sends an event when a service extension's state is changed.
829  ///
830  /// Clients should listen for this event to stay aware of the current service
831  /// extension state. Any service extension that manages a state should call
832  /// this method on state change.
833  ///
834  /// `value` reflects the newly updated service extension value.
835  ///
836  /// This will be called automatically for service extensions registered via
837  /// [registerBoolServiceExtension].
838  void _postExtensionStateChangedEvent(String name, dynamic value) {
839    postEvent(
840      'Flutter.ServiceExtensionStateChanged',
841      <String, dynamic>{
842        'extension': 'ext.flutter.inspector.$name',
843        'value': value,
844      },
845    );
846  }
847
848  /// Registers a service extension method with the given name (full
849  /// name "ext.flutter.inspector.name") which takes an optional parameter named
850  /// "arg" and a required parameter named "objectGroup" used to control the
851  /// lifetimes of object references in the returned JSON (see [disposeGroup]).
852  void _registerServiceExtensionWithArg({
853    @required String name,
854    @required FutureOr<Object> callback(String objectId, String objectGroup),
855  }) {
856    registerServiceExtension(
857      name: name,
858      callback: (Map<String, String> parameters) async {
859        assert(parameters.containsKey('objectGroup'));
860        return <String, Object>{
861          'result': await callback(parameters['arg'], parameters['objectGroup']),
862        };
863      },
864    );
865  }
866
867  /// Registers a service extension method with the given name (full
868  /// name "ext.flutter.inspector.name"), that takes arguments
869  /// "arg0", "arg1", "arg2", ..., "argn".
870  void _registerServiceExtensionVarArgs({
871    @required String name,
872    @required FutureOr<Object> callback(List<String> args),
873  }) {
874    registerServiceExtension(
875      name: name,
876      callback: (Map<String, String> parameters) async {
877        const String argPrefix = 'arg';
878        final List<String> args = <String>[];
879        parameters.forEach((String name, String value) {
880          if (name.startsWith(argPrefix)) {
881            final int index = int.parse(name.substring(argPrefix.length));
882            if (index >= args.length) {
883              args.length = index + 1;
884            }
885            args[index] = value;
886          }
887        });
888        return <String, Object>{'result': await callback(args)};
889      },
890    );
891  }
892
893  /// Cause the entire tree to be rebuilt. This is used by development tools
894  /// when the application code has changed and is being hot-reloaded, to cause
895  /// the widget tree to pick up any changed implementations.
896  ///
897  /// This is expensive and should not be called except during development.
898  @protected
899  Future<void> forceRebuild() {
900    final WidgetsBinding binding = WidgetsBinding.instance;
901    if (binding.renderViewElement != null) {
902      binding.buildOwner.reassemble(binding.renderViewElement);
903      return binding.endOfFrame;
904    }
905    return Future<void>.value();
906  }
907
908  static const String _consoleObjectGroup = 'console-group';
909
910  int _errorsSinceReload = 0;
911
912  void _reportError(FlutterErrorDetails details) {
913    final Map<String, Object> errorJson = _nodeToJson(
914      details.toDiagnosticsNode(),
915      _SerializationDelegate(
916        groupName: _consoleObjectGroup,
917        subtreeDepth: 5,
918        includeProperties: true,
919        expandPropertyValues: true,
920        maxDescendentsTruncatableNode: 5,
921        service: this,
922      ),
923    );
924
925    errorJson['errorsSinceReload'] = _errorsSinceReload;
926    _errorsSinceReload += 1;
927
928    postEvent('Flutter.Error', errorJson);
929  }
930
931  /// Resets the count of errors since the last hot reload.
932  ///
933  /// This data is sent to clients as part of the 'Flutter.Error' service
934  /// protocol event. Clients may choose to display errors received after the
935  /// first error differently.
936  void _resetErrorCount() {
937    _errorsSinceReload = 0;
938  }
939
940  /// Called to register service extensions.
941  ///
942  /// See also:
943  ///
944  ///  * <https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#rpcs-requests-and-responses>
945  ///  * [BindingBase.initServiceExtensions], which explains when service
946  ///    extensions can be used.
947  void initServiceExtensions(_RegisterServiceExtensionCallback registerServiceExtensionCallback) {
948    _registerServiceExtensionCallback = registerServiceExtensionCallback;
949    assert(!_debugServiceExtensionsRegistered);
950    assert(() { _debugServiceExtensionsRegistered = true; return true; }());
951
952    SchedulerBinding.instance.addPersistentFrameCallback(_onFrameStart);
953
954    final FlutterExceptionHandler structuredExceptionHandler = _reportError;
955    final FlutterExceptionHandler defaultExceptionHandler = FlutterError.onError;
956
957    _registerBoolServiceExtension(
958      name: 'structuredErrors',
959      getter: () async => FlutterError.onError == structuredExceptionHandler,
960      setter: (bool value) {
961        FlutterError.onError = value ? structuredExceptionHandler : defaultExceptionHandler;
962        return Future<void>.value();
963      },
964    );
965
966    _registerBoolServiceExtension(
967      name: 'show',
968      getter: () async => WidgetsApp.debugShowWidgetInspectorOverride,
969      setter: (bool value) {
970        if (WidgetsApp.debugShowWidgetInspectorOverride == value) {
971          return Future<void>.value();
972        }
973        WidgetsApp.debugShowWidgetInspectorOverride = value;
974        return forceRebuild();
975      },
976    );
977
978    if (isWidgetCreationTracked()) {
979      // Service extensions that are only supported if widget creation locations
980      // are tracked.
981      _registerBoolServiceExtension(
982        name: 'trackRebuildDirtyWidgets',
983        getter: () async => _trackRebuildDirtyWidgets,
984        setter: (bool value) async {
985          if (value == _trackRebuildDirtyWidgets) {
986            return;
987          }
988          _rebuildStats.resetCounts();
989          _trackRebuildDirtyWidgets = value;
990          if (value) {
991            assert(debugOnRebuildDirtyWidget == null);
992            debugOnRebuildDirtyWidget = _onRebuildWidget;
993            // Trigger a rebuild so there are baseline stats for rebuilds
994            // performed by the app.
995            await forceRebuild();
996            return;
997          } else {
998            debugOnRebuildDirtyWidget = null;
999            return;
1000          }
1001        },
1002      );
1003
1004      _registerBoolServiceExtension(
1005        name: 'trackRepaintWidgets',
1006        getter: () async => _trackRepaintWidgets,
1007        setter: (bool value) async {
1008          if (value == _trackRepaintWidgets) {
1009            return;
1010          }
1011          _repaintStats.resetCounts();
1012          _trackRepaintWidgets = value;
1013          if (value) {
1014            assert(debugOnProfilePaint == null);
1015            debugOnProfilePaint = _onPaint;
1016            // Trigger an immediate paint so the user has some baseline painting
1017            // stats to view.
1018            void markTreeNeedsPaint(RenderObject renderObject) {
1019              renderObject.markNeedsPaint();
1020              renderObject.visitChildren(markTreeNeedsPaint);
1021            }
1022            final RenderObject root = RendererBinding.instance.renderView;
1023            if (root != null) {
1024              markTreeNeedsPaint(root);
1025            }
1026          } else {
1027            debugOnProfilePaint = null;
1028          }
1029        },
1030      );
1031    }
1032
1033    _registerSignalServiceExtension(
1034      name: 'disposeAllGroups',
1035      callback: disposeAllGroups,
1036    );
1037    _registerObjectGroupServiceExtension(
1038      name: 'disposeGroup',
1039      callback: disposeGroup,
1040    );
1041    _registerSignalServiceExtension(
1042      name: 'isWidgetTreeReady',
1043      callback: isWidgetTreeReady,
1044    );
1045    _registerServiceExtensionWithArg(
1046      name: 'disposeId',
1047      callback: disposeId,
1048    );
1049    _registerServiceExtensionVarArgs(
1050      name: 'setPubRootDirectories',
1051      callback: setPubRootDirectories,
1052    );
1053    _registerServiceExtensionWithArg(
1054      name: 'setSelectionById',
1055      callback: setSelectionById,
1056    );
1057    _registerServiceExtensionWithArg(
1058      name: 'getParentChain',
1059      callback: _getParentChain,
1060    );
1061    _registerServiceExtensionWithArg(
1062      name: 'getProperties',
1063      callback: _getProperties,
1064    );
1065    _registerServiceExtensionWithArg(
1066      name: 'getChildren',
1067      callback: _getChildren,
1068    );
1069
1070    _registerServiceExtensionWithArg(
1071      name: 'getChildrenSummaryTree',
1072      callback: _getChildrenSummaryTree,
1073    );
1074
1075    _registerServiceExtensionWithArg(
1076      name: 'getChildrenDetailsSubtree',
1077      callback: _getChildrenDetailsSubtree,
1078    );
1079
1080    _registerObjectGroupServiceExtension(
1081      name: 'getRootWidget',
1082      callback: _getRootWidget,
1083    );
1084    _registerObjectGroupServiceExtension(
1085      name: 'getRootRenderObject',
1086      callback: _getRootRenderObject,
1087    );
1088    _registerObjectGroupServiceExtension(
1089      name: 'getRootWidgetSummaryTree',
1090      callback: _getRootWidgetSummaryTree,
1091    );
1092    _registerServiceExtensionWithArg(
1093      name: 'getDetailsSubtree',
1094      callback: _getDetailsSubtree,
1095    );
1096    _registerServiceExtensionWithArg(
1097      name: 'getSelectedRenderObject',
1098      callback: _getSelectedRenderObject,
1099    );
1100    _registerServiceExtensionWithArg(
1101      name: 'getSelectedWidget',
1102      callback: _getSelectedWidget,
1103    );
1104    _registerServiceExtensionWithArg(
1105      name: 'getSelectedSummaryWidget',
1106      callback: _getSelectedSummaryWidget,
1107    );
1108
1109    _registerSignalServiceExtension(
1110      name: 'isWidgetCreationTracked',
1111      callback: isWidgetCreationTracked,
1112    );
1113    registerServiceExtension(
1114      name: 'screenshot',
1115      callback: (Map<String, String> parameters) async {
1116        assert(parameters.containsKey('id'));
1117        assert(parameters.containsKey('width'));
1118        assert(parameters.containsKey('height'));
1119
1120        final ui.Image image = await screenshot(
1121          toObject(parameters['id']),
1122          width: double.parse(parameters['width']),
1123          height: double.parse(parameters['height']),
1124          margin: parameters.containsKey('margin') ?
1125              double.parse(parameters['margin']) : 0.0,
1126          maxPixelRatio: parameters.containsKey('maxPixelRatio') ?
1127              double.parse(parameters['maxPixelRatio']) : 1.0,
1128          debugPaint: parameters['debugPaint'] == 'true',
1129        );
1130        if (image == null) {
1131          return <String, Object>{'result': null};
1132        }
1133        final ByteData byteData = await image.toByteData(format:ui.ImageByteFormat.png);
1134
1135        return <String, Object>{
1136          'result': base64.encoder.convert(Uint8List.view(byteData.buffer)),
1137        };
1138      },
1139    );
1140  }
1141
1142  void _clearStats() {
1143    _rebuildStats.resetCounts();
1144    _repaintStats.resetCounts();
1145  }
1146
1147  /// Clear all InspectorService object references.
1148  ///
1149  /// Use this method only for testing to ensure that object references from one
1150  /// test case do not impact other test cases.
1151  @protected
1152  void disposeAllGroups() {
1153    _groups.clear();
1154    _idToReferenceData.clear();
1155    _objectToId.clear();
1156    _nextId = 0;
1157  }
1158
1159  /// Free all references to objects in a group.
1160  ///
1161  /// Objects and their associated ids in the group may be kept alive by
1162  /// references from a different group.
1163  @protected
1164  void disposeGroup(String name) {
1165    final Set<_InspectorReferenceData> references = _groups.remove(name);
1166    if (references == null)
1167      return;
1168    references.forEach(_decrementReferenceCount);
1169  }
1170
1171  void _decrementReferenceCount(_InspectorReferenceData reference) {
1172    reference.count -= 1;
1173    assert(reference.count >= 0);
1174    if (reference.count == 0) {
1175      final String id = _objectToId.remove(reference.object);
1176      assert(id != null);
1177      _idToReferenceData.remove(id);
1178    }
1179  }
1180
1181  /// Returns a unique id for [object] that will remain live at least until
1182  /// [disposeGroup] is called on [groupName] or [dispose] is called on the id
1183  /// returned by this method.
1184  @protected
1185  String toId(Object object, String groupName) {
1186    if (object == null)
1187      return null;
1188
1189    final Set<_InspectorReferenceData> group = _groups.putIfAbsent(groupName, () => Set<_InspectorReferenceData>.identity());
1190    String id = _objectToId[object];
1191    _InspectorReferenceData referenceData;
1192    if (id == null) {
1193      id = 'inspector-$_nextId';
1194      _nextId += 1;
1195      _objectToId[object] = id;
1196      referenceData = _InspectorReferenceData(object);
1197      _idToReferenceData[id] = referenceData;
1198      group.add(referenceData);
1199    } else {
1200      referenceData = _idToReferenceData[id];
1201      if (group.add(referenceData))
1202        referenceData.count += 1;
1203    }
1204    return id;
1205  }
1206
1207  /// Returns whether the application has rendered its first frame and it is
1208  /// appropriate to display the Widget tree in the inspector.
1209  @protected
1210  bool isWidgetTreeReady([ String groupName ]) {
1211    return WidgetsBinding.instance != null &&
1212           WidgetsBinding.instance.debugDidSendFirstFrameEvent;
1213  }
1214
1215  /// Returns the Dart object associated with a reference id.
1216  ///
1217  /// The `groupName` parameter is not required by is added to regularize the
1218  /// API surface of the methods in this class called from the Flutter IntelliJ
1219  /// Plugin.
1220  @protected
1221  Object toObject(String id, [ String groupName ]) {
1222    if (id == null)
1223      return null;
1224
1225    final _InspectorReferenceData data = _idToReferenceData[id];
1226    if (data == null) {
1227      throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('Id does not exist.')]);
1228    }
1229    return data.object;
1230  }
1231
1232  /// Returns the object to introspect to determine the source location of an
1233  /// object's class.
1234  ///
1235  /// The Dart object for the id is returned for all cases but [Element] objects
1236  /// where the [Widget] configuring the [Element] is returned instead as the
1237  /// class of the [Widget] is more relevant than the class of the [Element].
1238  ///
1239  /// The `groupName` parameter is not required by is added to regularize the
1240  /// API surface of methods called from the Flutter IntelliJ Plugin.
1241  @protected
1242  Object toObjectForSourceLocation(String id, [ String groupName ]) {
1243    final Object object = toObject(id);
1244    if (object is Element) {
1245      return object.widget;
1246    }
1247    return object;
1248  }
1249
1250  /// Remove the object with the specified `id` from the specified object
1251  /// group.
1252  ///
1253  /// If the object exists in other groups it will remain alive and the object
1254  /// id will remain valid.
1255  @protected
1256  void disposeId(String id, String groupName) {
1257    if (id == null)
1258      return;
1259
1260    final _InspectorReferenceData referenceData = _idToReferenceData[id];
1261    if (referenceData == null)
1262      throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('Id does not exist')]);
1263    if (_groups[groupName]?.remove(referenceData) != true)
1264      throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('Id is not in group')]);
1265    _decrementReferenceCount(referenceData);
1266  }
1267
1268  /// Set the list of directories that should be considered part of the local
1269  /// project.
1270  ///
1271  /// The local project directories are used to distinguish widgets created by
1272  /// the local project over widgets created from inside the framework.
1273  @protected
1274  void setPubRootDirectories(List<Object> pubRootDirectories) {
1275    _pubRootDirectories = pubRootDirectories.map<String>(
1276      (Object directory) => Uri.parse(directory).path,
1277    ).toList();
1278  }
1279
1280  /// Set the [WidgetInspector] selection to the object matching the specified
1281  /// id if the object is valid object to set as the inspector selection.
1282  ///
1283  /// Returns true if the selection was changed.
1284  ///
1285  /// The `groupName` parameter is not required by is added to regularize the
1286  /// API surface of methods called from the Flutter IntelliJ Plugin.
1287  @protected
1288  bool setSelectionById(String id, [ String groupName ]) {
1289    return setSelection(toObject(id), groupName);
1290  }
1291
1292  /// Set the [WidgetInspector] selection to the specified `object` if it is
1293  /// a valid object to set as the inspector selection.
1294  ///
1295  /// Returns true if the selection was changed.
1296  ///
1297  /// The `groupName` parameter is not needed but is specified to regularize the
1298  /// API surface of methods called from the Flutter IntelliJ Plugin.
1299  @protected
1300  bool setSelection(Object object, [ String groupName ]) {
1301    if (object is Element || object is RenderObject) {
1302      if (object is Element) {
1303        if (object == selection.currentElement) {
1304          return false;
1305        }
1306        selection.currentElement = object;
1307        developer.inspect(selection.currentElement);
1308      } else {
1309        if (object == selection.current) {
1310          return false;
1311        }
1312        selection.current = object;
1313        developer.inspect(selection.current);
1314      }
1315      if (selectionChangedCallback != null) {
1316        if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.idle) {
1317          selectionChangedCallback();
1318        } else {
1319          // It isn't safe to trigger the selection change callback if we are in
1320          // the middle of rendering the frame.
1321          SchedulerBinding.instance.scheduleTask(
1322            selectionChangedCallback,
1323            Priority.touch,
1324          );
1325        }
1326      }
1327      return true;
1328    }
1329    return false;
1330  }
1331
1332  /// Returns JSON representing the chain of [DiagnosticsNode] instances from
1333  /// root of thee tree to the [Element] or [RenderObject] matching `id`.
1334  ///
1335  /// The JSON contains all information required to display a tree view with
1336  /// all nodes other than nodes along the path collapsed.
1337  @protected
1338  String getParentChain(String id, String groupName) {
1339    return _safeJsonEncode(_getParentChain(id, groupName));
1340  }
1341
1342  List<Object> _getParentChain(String id, String groupName) {
1343    final Object value = toObject(id);
1344    List<_DiagnosticsPathNode> path;
1345    if (value is RenderObject)
1346      path = _getRenderObjectParentChain(value, groupName);
1347    else if (value is Element)
1348      path = _getElementParentChain(value, groupName);
1349    else
1350      throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('Cannot get parent chain for node of type ${value.runtimeType}')]);
1351
1352    return path.map<Object>((_DiagnosticsPathNode node) => _pathNodeToJson(
1353      node,
1354      _SerializationDelegate(groupName: groupName, service: this),
1355    )).toList();
1356  }
1357
1358  Map<String, Object> _pathNodeToJson(_DiagnosticsPathNode pathNode, _SerializationDelegate delegate) {
1359    if (pathNode == null)
1360      return null;
1361    return <String, Object>{
1362      'node': _nodeToJson(pathNode.node, delegate),
1363      'children': _nodesToJson(pathNode.children, delegate, parent: pathNode.node),
1364      'childIndex': pathNode.childIndex,
1365    };
1366  }
1367
1368  List<Element> _getRawElementParentChain(Element element, { int numLocalParents }) {
1369    List<Element> elements = element?.debugGetDiagnosticChain();
1370    if (numLocalParents != null) {
1371      for (int i = 0; i < elements.length; i += 1) {
1372        if (_isValueCreatedByLocalProject(elements[i])) {
1373          numLocalParents--;
1374          if (numLocalParents <= 0) {
1375            elements = elements.take(i + 1).toList();
1376            break;
1377          }
1378        }
1379      }
1380    }
1381    return elements?.reversed?.toList();
1382  }
1383
1384  List<_DiagnosticsPathNode> _getElementParentChain(Element element, String groupName, { int numLocalParents }) {
1385    return _followDiagnosticableChain(
1386      _getRawElementParentChain(element, numLocalParents: numLocalParents),
1387    ) ?? const <_DiagnosticsPathNode>[];
1388  }
1389
1390  List<_DiagnosticsPathNode> _getRenderObjectParentChain(RenderObject renderObject, String groupName, { int maxparents }) {
1391    final List<RenderObject> chain = <RenderObject>[];
1392    while (renderObject != null) {
1393      chain.add(renderObject);
1394      renderObject = renderObject.parent;
1395    }
1396    return _followDiagnosticableChain(chain.reversed.toList());
1397  }
1398
1399  Map<String, Object> _nodeToJson(
1400    DiagnosticsNode node,
1401    _SerializationDelegate delegate,
1402  ) {
1403    return node?.toJsonMap(delegate);
1404  }
1405
1406  bool _isValueCreatedByLocalProject(Object value) {
1407    final _Location creationLocation = _getCreationLocation(value);
1408    if (creationLocation == null) {
1409      return false;
1410    }
1411    return _isLocalCreationLocation(creationLocation);
1412  }
1413
1414  bool _isLocalCreationLocation(_Location location) {
1415    if (location == null || location.file == null) {
1416      return false;
1417    }
1418    final String file = Uri.parse(location.file).path;
1419
1420    // By default check whether the creation location was within package:flutter.
1421    if (_pubRootDirectories == null) {
1422      // TODO(chunhtai): Make it more robust once
1423      // https://github.com/flutter/flutter/issues/32660 is fixed.
1424      return !file.contains('packages/flutter/');
1425    }
1426    for (String directory in _pubRootDirectories) {
1427      if (file.startsWith(directory)) {
1428        return true;
1429      }
1430    }
1431    return false;
1432  }
1433
1434  /// Wrapper around `json.encode` that uses a ring of cached values to prevent
1435  /// the Dart garbage collector from collecting objects between when
1436  /// the value is returned over the Observatory protocol and when the
1437  /// separate observatory protocol command has to be used to retrieve its full
1438  /// contents.
1439  //
1440  // TODO(jacobr): Replace this with a better solution once
1441  // https://github.com/dart-lang/sdk/issues/32919 is fixed.
1442  String _safeJsonEncode(Object object) {
1443    final String jsonString = json.encode(object);
1444    _serializeRing[_serializeRingIndex] = jsonString;
1445    _serializeRingIndex = (_serializeRingIndex + 1)  % _serializeRing.length;
1446    return jsonString;
1447  }
1448
1449  List<DiagnosticsNode> _truncateNodes(Iterable<DiagnosticsNode> nodes, int maxDescendentsTruncatableNode) {
1450    if (nodes.every((DiagnosticsNode node) => node.value is Element) && isWidgetCreationTracked()) {
1451      final List<DiagnosticsNode> localNodes = nodes.where((DiagnosticsNode node) =>
1452          _isValueCreatedByLocalProject(node.value)).toList();
1453      if (localNodes.isNotEmpty) {
1454        return localNodes;
1455      }
1456    }
1457    return nodes.take(maxDescendentsTruncatableNode).toList();
1458  }
1459
1460  List<Map<String, Object>> _nodesToJson(
1461    Iterable<DiagnosticsNode> nodes,
1462    _SerializationDelegate delegate, {
1463    @required DiagnosticsNode parent,
1464  }) {
1465    return DiagnosticsNode.toJsonList(nodes, parent, delegate);
1466  }
1467
1468  /// Returns a JSON representation of the properties of the [DiagnosticsNode]
1469  /// object that `diagnosticsNodeId` references.
1470  @protected
1471  String getProperties(String diagnosticsNodeId, String groupName) {
1472    return _safeJsonEncode(_getProperties(diagnosticsNodeId, groupName));
1473  }
1474
1475  List<Object> _getProperties(String diagnosticsNodeId, String groupName) {
1476    final DiagnosticsNode node = toObject(diagnosticsNodeId);
1477    return _nodesToJson(node == null ? const <DiagnosticsNode>[] : node.getProperties(), _SerializationDelegate(groupName: groupName, service: this), parent: node);
1478  }
1479
1480  /// Returns a JSON representation of the children of the [DiagnosticsNode]
1481  /// object that `diagnosticsNodeId` references.
1482  String getChildren(String diagnosticsNodeId, String groupName) {
1483    return _safeJsonEncode(_getChildren(diagnosticsNodeId, groupName));
1484  }
1485
1486  List<Object> _getChildren(String diagnosticsNodeId, String groupName) {
1487    final DiagnosticsNode node = toObject(diagnosticsNodeId);
1488    final _SerializationDelegate delegate = _SerializationDelegate(groupName: groupName, service: this);
1489    return _nodesToJson(node == null ? const <DiagnosticsNode>[] : _getChildrenFiltered(node, delegate), delegate, parent: node);
1490  }
1491
1492  /// Returns a JSON representation of the children of the [DiagnosticsNode]
1493  /// object that `diagnosticsNodeId` references only including children that
1494  /// were created directly by user code.
1495  ///
1496  /// Requires [Widget] creation locations which are only available for debug
1497  /// mode builds when the `--track-widget-creation` flag is passed to
1498  /// `flutter_tool`.
1499  ///
1500  /// See also:
1501  ///
1502  ///  * [isWidgetCreationTracked] which indicates whether this method can be
1503  ///    used.
1504  String getChildrenSummaryTree(String diagnosticsNodeId, String groupName) {
1505    return _safeJsonEncode(_getChildrenSummaryTree(diagnosticsNodeId, groupName));
1506  }
1507
1508  List<Object> _getChildrenSummaryTree(String diagnosticsNodeId, String groupName) {
1509    final DiagnosticsNode node = toObject(diagnosticsNodeId);
1510    final _SerializationDelegate delegate = _SerializationDelegate(groupName: groupName, summaryTree: true, service: this);
1511    return _nodesToJson(node == null ? const <DiagnosticsNode>[] : _getChildrenFiltered(node, delegate), delegate, parent: node);
1512  }
1513
1514  /// Returns a JSON representation of the children of the [DiagnosticsNode]
1515  /// object that `diagnosticsNodeId` references providing information needed
1516  /// for the details subtree view.
1517  ///
1518  /// The details subtree shows properties inline and includes all children
1519  /// rather than a filtered set of important children.
1520  String getChildrenDetailsSubtree(String diagnosticsNodeId, String groupName) {
1521    return _safeJsonEncode(_getChildrenDetailsSubtree(diagnosticsNodeId, groupName));
1522  }
1523
1524  List<Object> _getChildrenDetailsSubtree(String diagnosticsNodeId, String groupName) {
1525    final DiagnosticsNode node = toObject(diagnosticsNodeId);
1526    // With this value of minDepth we only expand one extra level of important nodes.
1527    final _SerializationDelegate delegate = _SerializationDelegate(groupName: groupName, subtreeDepth: 1, includeProperties: true, service: this);
1528    return _nodesToJson(node == null ? const <DiagnosticsNode>[] : _getChildrenFiltered(node, delegate), delegate, parent: node);
1529  }
1530
1531  bool _shouldShowInSummaryTree(DiagnosticsNode node) {
1532    if (node.level == DiagnosticLevel.error) {
1533      return true;
1534    }
1535    final Object value = node.value;
1536    if (value is! Diagnosticable) {
1537      return true;
1538    }
1539    if (value is! Element || !isWidgetCreationTracked()) {
1540      // Creation locations are not available so include all nodes in the
1541      // summary tree.
1542      return true;
1543    }
1544    return _isValueCreatedByLocalProject(value);
1545  }
1546
1547  List<DiagnosticsNode> _getChildrenFiltered(
1548    DiagnosticsNode node,
1549    _SerializationDelegate delegate,
1550  ) {
1551    return _filterChildren(node.getChildren(), delegate);
1552  }
1553
1554  List<DiagnosticsNode> _filterChildren(
1555    List<DiagnosticsNode> nodes,
1556    _SerializationDelegate delegate,
1557  ) {
1558    final List<DiagnosticsNode> children = <DiagnosticsNode>[
1559      for (DiagnosticsNode child in nodes)
1560        if (!delegate.summaryTree || _shouldShowInSummaryTree(child))
1561          child
1562        else
1563          ..._getChildrenFiltered(child, delegate),
1564    ];
1565    return children;
1566  }
1567
1568  /// Returns a JSON representation of the [DiagnosticsNode] for the root
1569  /// [Element].
1570  String getRootWidget(String groupName) {
1571    return _safeJsonEncode(_getRootWidget(groupName));
1572  }
1573
1574  Map<String, Object> _getRootWidget(String groupName) {
1575    return _nodeToJson(WidgetsBinding.instance?.renderViewElement?.toDiagnosticsNode(), _SerializationDelegate(groupName: groupName, service: this));
1576  }
1577
1578  /// Returns a JSON representation of the [DiagnosticsNode] for the root
1579  /// [Element] showing only nodes that should be included in a summary tree.
1580  String getRootWidgetSummaryTree(String groupName) {
1581    return _safeJsonEncode(_getRootWidgetSummaryTree(groupName));
1582  }
1583
1584  Map<String, Object> _getRootWidgetSummaryTree(String groupName) {
1585    return _nodeToJson(
1586      WidgetsBinding.instance?.renderViewElement?.toDiagnosticsNode(),
1587      _SerializationDelegate(groupName: groupName, subtreeDepth: 1000000, summaryTree: true, service: this),
1588    );
1589  }
1590
1591  /// Returns a JSON representation of the [DiagnosticsNode] for the root
1592  /// [RenderObject].
1593  @protected
1594  String getRootRenderObject(String groupName) {
1595    return _safeJsonEncode(_getRootRenderObject(groupName));
1596  }
1597
1598  Map<String, Object> _getRootRenderObject(String groupName) {
1599    return _nodeToJson(RendererBinding.instance?.renderView?.toDiagnosticsNode(), _SerializationDelegate(groupName: groupName, service: this));
1600  }
1601
1602  /// Returns a JSON representation of the subtree rooted at the
1603  /// [DiagnosticsNode] object that `diagnosticsNodeId` references providing
1604  /// information needed for the details subtree view.
1605  ///
1606  /// See also:
1607  ///
1608  ///  * [getChildrenDetailsSubtree], a method to get children of a node
1609  ///    in the details subtree.
1610  String getDetailsSubtree(String id, String groupName) {
1611    return _safeJsonEncode(_getDetailsSubtree( id, groupName));
1612  }
1613
1614  Map<String, Object> _getDetailsSubtree(String id, String groupName) {
1615    final DiagnosticsNode root = toObject(id);
1616    if (root == null) {
1617      return null;
1618    }
1619    return _nodeToJson(
1620      root,
1621      _SerializationDelegate(
1622        groupName: groupName,
1623        summaryTree: false,
1624        subtreeDepth: 2,  // TODO(jacobr): make subtreeDepth configurable.
1625        includeProperties: true,
1626        service: this,
1627      ),
1628    );
1629  }
1630
1631  /// Returns a [DiagnosticsNode] representing the currently selected
1632  /// [RenderObject].
1633  ///
1634  /// If the currently selected [RenderObject] is identical to the
1635  /// [RenderObject] referenced by `previousSelectionId` then the previous
1636  /// [DiagnosticNode] is reused.
1637  @protected
1638  String getSelectedRenderObject(String previousSelectionId, String groupName) {
1639    return _safeJsonEncode(_getSelectedRenderObject(previousSelectionId, groupName));
1640  }
1641
1642  Map<String, Object> _getSelectedRenderObject(String previousSelectionId, String groupName) {
1643    final DiagnosticsNode previousSelection = toObject(previousSelectionId);
1644    final RenderObject current = selection?.current;
1645    return _nodeToJson(current == previousSelection?.value ? previousSelection : current?.toDiagnosticsNode(), _SerializationDelegate(groupName: groupName, service: this));
1646  }
1647
1648  /// Returns a [DiagnosticsNode] representing the currently selected [Element].
1649  ///
1650  /// If the currently selected [Element] is identical to the [Element]
1651  /// referenced by `previousSelectionId` then the previous [DiagnosticNode] is
1652  /// reused.
1653  @protected
1654  String getSelectedWidget(String previousSelectionId, String groupName) {
1655    return _safeJsonEncode(_getSelectedWidget(previousSelectionId, groupName));
1656  }
1657
1658  /// Captures an image of the current state of an [object] that is a
1659  /// [RenderObject] or [Element].
1660  ///
1661  /// The returned [ui.Image] has uncompressed raw RGBA bytes and will be scaled
1662  /// to be at most [width] pixels wide and [height] pixels tall. The returned
1663  /// image will never have a scale between logical pixels and the
1664  /// size of the output image larger than maxPixelRatio.
1665  /// [margin] indicates the number of pixels relative to the unscaled size of
1666  /// the [object] to include as a margin to include around the bounds of the
1667  /// [object] in the screenshot. Including a margin can be useful to capture
1668  /// areas that are slightly outside of the normal bounds of an object such as
1669  /// some debug paint information.
1670  @protected
1671  Future<ui.Image> screenshot(
1672    Object object, {
1673    @required double width,
1674    @required double height,
1675    double margin = 0.0,
1676    double maxPixelRatio = 1.0,
1677    bool debugPaint = false,
1678  }) async {
1679    if (object is! Element && object is! RenderObject) {
1680      return null;
1681    }
1682    final RenderObject renderObject = object is Element ? object.renderObject : object;
1683    if (renderObject == null || !renderObject.attached) {
1684      return null;
1685    }
1686
1687    if (renderObject.debugNeedsLayout) {
1688      final PipelineOwner owner = renderObject.owner;
1689      assert(owner != null);
1690      assert(!owner.debugDoingLayout);
1691      owner
1692        ..flushLayout()
1693        ..flushCompositingBits()
1694        ..flushPaint();
1695
1696      // If we still need layout, then that means that renderObject was skipped
1697      // in the layout phase and therefore can't be painted. It is clearer to
1698      // return null indicating that a screenshot is unavailable than to return
1699      // an empty image.
1700      if (renderObject.debugNeedsLayout) {
1701        return null;
1702      }
1703    }
1704
1705    Rect renderBounds = _calculateSubtreeBounds(renderObject);
1706    if (margin != 0.0) {
1707      renderBounds = renderBounds.inflate(margin);
1708    }
1709    if (renderBounds.isEmpty) {
1710      return null;
1711    }
1712
1713    final double pixelRatio = math.min(
1714      maxPixelRatio,
1715      math.min(
1716        width / renderBounds.width,
1717        height / renderBounds.height,
1718      ),
1719    );
1720
1721    return _ScreenshotPaintingContext.toImage(
1722      renderObject,
1723      renderBounds,
1724      pixelRatio: pixelRatio,
1725      debugPaint: debugPaint,
1726    );
1727  }
1728
1729  Map<String, Object> _getSelectedWidget(String previousSelectionId, String groupName) {
1730    final DiagnosticsNode previousSelection = toObject(previousSelectionId);
1731    final Element current = selection?.currentElement;
1732    return _nodeToJson(current == previousSelection?.value ? previousSelection : current?.toDiagnosticsNode(), _SerializationDelegate(groupName: groupName, service: this));
1733  }
1734
1735  /// Returns a [DiagnosticsNode] representing the currently selected [Element]
1736  /// if the selected [Element] should be shown in the summary tree otherwise
1737  /// returns the first ancestor of the selected [Element] shown in the summary
1738  /// tree.
1739  ///
1740  /// If the currently selected [Element] is identical to the [Element]
1741  /// referenced by `previousSelectionId` then the previous [DiagnosticNode] is
1742  /// reused.
1743  String getSelectedSummaryWidget(String previousSelectionId, String groupName) {
1744    return _safeJsonEncode(_getSelectedSummaryWidget(previousSelectionId, groupName));
1745  }
1746
1747  Map<String, Object> _getSelectedSummaryWidget(String previousSelectionId, String groupName) {
1748    if (!isWidgetCreationTracked()) {
1749      return _getSelectedWidget(previousSelectionId, groupName);
1750    }
1751    final DiagnosticsNode previousSelection = toObject(previousSelectionId);
1752    Element current = selection?.currentElement;
1753    if (current != null && !_isValueCreatedByLocalProject(current)) {
1754      Element firstLocal;
1755      for (Element candidate in current.debugGetDiagnosticChain()) {
1756        if (_isValueCreatedByLocalProject(candidate)) {
1757          firstLocal = candidate;
1758          break;
1759        }
1760      }
1761      current = firstLocal;
1762    }
1763    return _nodeToJson(current == previousSelection?.value ? previousSelection : current?.toDiagnosticsNode(), _SerializationDelegate(groupName: groupName, service: this));
1764  }
1765
1766  /// Returns whether [Widget] creation locations are available.
1767  ///
1768  /// [Widget] creation locations are only available for debug mode builds when
1769  /// the `--track-widget-creation` flag is passed to `flutter_tool`. Dart 2.0
1770  /// is required as injecting creation locations requires a
1771  /// [Dart Kernel Transformer](https://github.com/dart-lang/sdk/wiki/Kernel-Documentation).
1772  bool isWidgetCreationTracked() {
1773    _widgetCreationTracked ??= _WidgetForTypeTests() is _HasCreationLocation;
1774    return _widgetCreationTracked;
1775  }
1776
1777  bool _widgetCreationTracked;
1778
1779  Duration _frameStart;
1780
1781  void _onFrameStart(Duration timeStamp) {
1782    _frameStart = timeStamp;
1783    SchedulerBinding.instance.addPostFrameCallback(_onFrameEnd);
1784  }
1785
1786  void _onFrameEnd(Duration timeStamp) {
1787    if (_trackRebuildDirtyWidgets) {
1788      _postStatsEvent('Flutter.RebuiltWidgets', _rebuildStats);
1789    }
1790    if (_trackRepaintWidgets) {
1791      _postStatsEvent('Flutter.RepaintWidgets', _repaintStats);
1792    }
1793  }
1794
1795  void _postStatsEvent(String eventName, _ElementLocationStatsTracker stats) {
1796    postEvent(eventName, stats.exportToJson(_frameStart));
1797  }
1798
1799  /// All events dispatched by a [WidgetInspectorService] use this method
1800  /// instead of calling [developer.postEvent] directly so that tests for
1801  /// [WidgetInspectorService] can track which events were dispatched by
1802  /// overriding this method.
1803  @protected
1804  void postEvent(String eventKind, Map<Object, Object> eventData) {
1805    developer.postEvent(eventKind, eventData);
1806  }
1807
1808  final _ElementLocationStatsTracker _rebuildStats = _ElementLocationStatsTracker();
1809  final _ElementLocationStatsTracker _repaintStats = _ElementLocationStatsTracker();
1810
1811  void _onRebuildWidget(Element element, bool builtOnce) {
1812    _rebuildStats.add(element);
1813  }
1814
1815  void _onPaint(RenderObject renderObject) {
1816    try {
1817      final Element element = renderObject.debugCreator?.element;
1818      if (element is! RenderObjectElement) {
1819        // This branch should not hit as long as all RenderObjects were created
1820        // by Widgets. It is possible there might be some render objects
1821        // created directly without using the Widget layer so we add this check
1822        // to improve robustness.
1823        return;
1824      }
1825      _repaintStats.add(element);
1826
1827      // Give all ancestor elements credit for repainting as long as they do
1828      // not have their own associated RenderObject.
1829      element.visitAncestorElements((Element ancestor) {
1830        if (ancestor is RenderObjectElement) {
1831          // This ancestor has its own RenderObject so we can precisely track
1832          // when it repaints.
1833          return false;
1834        }
1835        _repaintStats.add(ancestor);
1836        return true;
1837      });
1838    }
1839    catch (exception, stack) {
1840      FlutterError.reportError(
1841        FlutterErrorDetails(
1842          exception: exception,
1843          stack: stack,
1844        ),
1845      );
1846    }
1847  }
1848
1849  /// This method is called by [WidgetBinding.performReassemble] to flush caches
1850  /// of obsolete values after a hot reload.
1851  ///
1852  /// Do not call this method directly. Instead, use
1853  /// [BindingBase.reassembleApplication].
1854  void performReassemble() {
1855    _clearStats();
1856    _resetErrorCount();
1857  }
1858}
1859
1860/// Accumulator for a count associated with a specific source location.
1861///
1862/// The accumulator stores whether the source location is [local] and what its
1863/// [id] for efficiency encoding terse JSON payloads describing counts.
1864class _LocationCount {
1865  _LocationCount({
1866    @required this.location,
1867    @required this.id,
1868    @required this.local,
1869  });
1870
1871  /// Location id.
1872  final int id;
1873
1874  /// Whether the location is local to the current project.
1875  final bool local;
1876
1877  final _Location location;
1878
1879  int get count => _count;
1880  int _count = 0;
1881
1882  /// Reset the count.
1883  void reset() {
1884    _count = 0;
1885  }
1886
1887  /// Increment the count.
1888  void increment() {
1889    _count++;
1890  }
1891}
1892
1893/// A stat tracker that aggregates a performance metric for [Element] objects at
1894/// the granularity of creation locations in source code.
1895///
1896/// This class is optimized to minimize the size of the JSON payloads describing
1897/// the aggregate statistics, for stable memory usage, and low CPU usage at the
1898/// expense of somewhat higher overall memory usage. Stable memory usage is more
1899/// important than peak memory usage to avoid the false impression that the
1900/// user's app is leaking memory each frame.
1901///
1902/// The number of unique widget creation locations tends to be at most in the
1903/// low thousands for regular flutter apps so the peak memory usage for this
1904/// class is not an issue.
1905class _ElementLocationStatsTracker {
1906  // All known creation location tracked.
1907  //
1908  // This could also be stored as a `Map<int, _LocationCount>` but this
1909  // representation is more efficient as all location ids from 0 to n are
1910  // typically present.
1911  //
1912  // All logic in this class assumes that if `_stats[i]` is not null
1913  // `_stats[i].id` equals `i`.
1914  final List<_LocationCount> _stats = <_LocationCount>[];
1915
1916  /// Locations with a non-zero count.
1917  final List<_LocationCount> active = <_LocationCount>[];
1918
1919  /// Locations that were added since stats were last exported.
1920  ///
1921  /// Only locations local to the current project are included as a performance
1922  /// optimization.
1923  final List<_LocationCount> newLocations = <_LocationCount>[];
1924
1925  /// Increments the count associated with the creation location of [element] if
1926  /// the creation location is local to the current project.
1927  void add(Element element) {
1928    final Object widget = element.widget;
1929    if (widget is! _HasCreationLocation) {
1930      return;
1931    }
1932    final _HasCreationLocation creationLocationSource = widget;
1933    final _Location location = creationLocationSource._location;
1934    final int id = _toLocationId(location);
1935
1936    _LocationCount entry;
1937    if (id >= _stats.length || _stats[id] == null) {
1938      // After the first frame, almost all creation ids will already be in
1939      // _stats so this slow path will rarely be hit.
1940      while (id >= _stats.length) {
1941        _stats.add(null);
1942      }
1943      entry = _LocationCount(
1944        location: location,
1945        id: id,
1946        local: WidgetInspectorService.instance._isLocalCreationLocation(location),
1947      );
1948      if (entry.local) {
1949        newLocations.add(entry);
1950      }
1951      _stats[id] = entry;
1952    } else {
1953      entry = _stats[id];
1954    }
1955
1956    // We could in the future add an option to track stats for all widgets but
1957    // that would significantly increase the size of the events posted using
1958    // [developer.postEvent] and current use cases for this feature focus on
1959    // helping users find problems with their widgets not the platform
1960    // widgets.
1961    if (entry.local) {
1962      if (entry.count == 0) {
1963        active.add(entry);
1964      }
1965      entry.increment();
1966    }
1967  }
1968
1969  /// Clear all aggregated statistics.
1970  void resetCounts() {
1971    // We chose to only reset the active counts instead of clearing all data
1972    // to reduce the number memory allocations performed after the first frame.
1973    // Once an app has warmed up, location stats tracking should not
1974    // trigger significant additional memory allocations. Avoiding memory
1975    // allocations is important to minimize the impact this class has on cpu
1976    // and memory performance of the running app.
1977    for (_LocationCount entry in active) {
1978      entry.reset();
1979    }
1980    active.clear();
1981  }
1982
1983  /// Exports the current counts and then resets the stats to prepare to track
1984  /// the next frame of data.
1985  Map<String, dynamic> exportToJson(Duration startTime) {
1986    final List<int> events = List<int>.filled(active.length * 2, 0);
1987    int j = 0;
1988    for (_LocationCount stat in active) {
1989      events[j++] = stat.id;
1990      events[j++] = stat.count;
1991    }
1992
1993    final Map<String, dynamic> json = <String, dynamic>{
1994      'startTime': startTime.inMicroseconds,
1995      'events': events,
1996    };
1997
1998    if (newLocations.isNotEmpty) {
1999      // Add all newly used location ids to the JSON.
2000      final Map<String, List<int>> locationsJson = <String, List<int>>{};
2001      for (_LocationCount entry in newLocations) {
2002        final _Location location = entry.location;
2003        final List<int> jsonForFile = locationsJson.putIfAbsent(
2004          location.file,
2005          () => <int>[],
2006        );
2007        jsonForFile..add(entry.id)..add(location.line)..add(location.column);
2008      }
2009      json['newLocations'] = locationsJson;
2010    }
2011    resetCounts();
2012    newLocations.clear();
2013    return json;
2014  }
2015}
2016
2017class _WidgetForTypeTests extends Widget {
2018  @override
2019  Element createElement() => null;
2020}
2021
2022/// A widget that enables inspecting the child widget's structure.
2023///
2024/// Select a location on your device or emulator and view what widgets and
2025/// render object that best matches the location. An outline of the selected
2026/// widget and terse summary information is shown on device with detailed
2027/// information is shown in the observatory or in IntelliJ when using the
2028/// Flutter Plugin.
2029///
2030/// The inspector has a select mode and a view mode.
2031///
2032/// In the select mode, tapping the device selects the widget that best matches
2033/// the location of the touch and switches to view mode. Dragging a finger on
2034/// the device selects the widget under the drag location but does not switch
2035/// modes. Touching the very edge of the bounding box of a widget triggers
2036/// selecting the widget even if another widget that also overlaps that
2037/// location would otherwise have priority.
2038///
2039/// In the view mode, the previously selected widget is outlined, however,
2040/// touching the device has the same effect it would have if the inspector
2041/// wasn't present. This allows interacting with the application and viewing how
2042/// the selected widget changes position. Clicking on the select icon in the
2043/// bottom left corner of the application switches back to select mode.
2044class WidgetInspector extends StatefulWidget {
2045  /// Creates a widget that enables inspection for the child.
2046  ///
2047  /// The [child] argument must not be null.
2048  const WidgetInspector({
2049    Key key,
2050    @required this.child,
2051    @required this.selectButtonBuilder,
2052  }) : assert(child != null),
2053       super(key: key);
2054
2055  /// The widget that is being inspected.
2056  final Widget child;
2057
2058  /// A builder that is called to create the select button.
2059  ///
2060  /// The `onPressed` callback passed as an argument to the builder should be
2061  /// hooked up to the returned widget.
2062  final InspectorSelectButtonBuilder selectButtonBuilder;
2063
2064  @override
2065  _WidgetInspectorState createState() => _WidgetInspectorState();
2066}
2067
2068class _WidgetInspectorState extends State<WidgetInspector>
2069    with WidgetsBindingObserver {
2070
2071  _WidgetInspectorState() : selection = WidgetInspectorService.instance.selection;
2072
2073  Offset _lastPointerLocation;
2074
2075  final InspectorSelection selection;
2076
2077  /// Whether the inspector is in select mode.
2078  ///
2079  /// In select mode, pointer interactions trigger widget selection instead of
2080  /// normal interactions. Otherwise the previously selected widget is
2081  /// highlighted but the application can be interacted with normally.
2082  bool isSelectMode = true;
2083
2084  final GlobalKey _ignorePointerKey = GlobalKey();
2085
2086  /// Distance from the edge of of the bounding box for an element to consider
2087  /// as selecting the edge of the bounding box.
2088  static const double _edgeHitMargin = 2.0;
2089
2090  InspectorSelectionChangedCallback _selectionChangedCallback;
2091  @override
2092  void initState() {
2093    super.initState();
2094
2095    _selectionChangedCallback = () {
2096      setState(() {
2097        // The [selection] property which the build method depends on has
2098        // changed.
2099      });
2100    };
2101    WidgetInspectorService.instance.selectionChangedCallback = _selectionChangedCallback;
2102  }
2103
2104  @override
2105  void dispose() {
2106    if (WidgetInspectorService.instance.selectionChangedCallback == _selectionChangedCallback) {
2107      WidgetInspectorService.instance.selectionChangedCallback = null;
2108    }
2109    super.dispose();
2110  }
2111
2112  bool _hitTestHelper(
2113    List<RenderObject> hits,
2114    List<RenderObject> edgeHits,
2115    Offset position,
2116    RenderObject object,
2117    Matrix4 transform,
2118  ) {
2119    bool hit = false;
2120    final Matrix4 inverse = Matrix4.tryInvert(transform);
2121    if (inverse == null) {
2122      // We cannot invert the transform. That means the object doesn't appear on
2123      // screen and cannot be hit.
2124      return false;
2125    }
2126    final Offset localPosition = MatrixUtils.transformPoint(inverse, position);
2127
2128    final List<DiagnosticsNode> children = object.debugDescribeChildren();
2129    for (int i = children.length - 1; i >= 0; i -= 1) {
2130      final DiagnosticsNode diagnostics = children[i];
2131      assert(diagnostics != null);
2132      if (diagnostics.style == DiagnosticsTreeStyle.offstage ||
2133          diagnostics.value is! RenderObject)
2134        continue;
2135      final RenderObject child = diagnostics.value;
2136      final Rect paintClip = object.describeApproximatePaintClip(child);
2137      if (paintClip != null && !paintClip.contains(localPosition))
2138        continue;
2139
2140      final Matrix4 childTransform = transform.clone();
2141      object.applyPaintTransform(child, childTransform);
2142      if (_hitTestHelper(hits, edgeHits, position, child, childTransform))
2143        hit = true;
2144    }
2145
2146    final Rect bounds = object.semanticBounds;
2147    if (bounds.contains(localPosition)) {
2148      hit = true;
2149      // Hits that occur on the edge of the bounding box of an object are
2150      // given priority to provide a way to select objects that would
2151      // otherwise be hard to select.
2152      if (!bounds.deflate(_edgeHitMargin).contains(localPosition))
2153        edgeHits.add(object);
2154    }
2155    if (hit)
2156      hits.add(object);
2157    return hit;
2158  }
2159
2160  /// Returns the list of render objects located at the given position ordered
2161  /// by priority.
2162  ///
2163  /// All render objects that are not offstage that match the location are
2164  /// included in the list of matches. Priority is given to matches that occur
2165  /// on the edge of a render object's bounding box and to matches found by
2166  /// [RenderBox.hitTest].
2167  List<RenderObject> hitTest(Offset position, RenderObject root) {
2168    final List<RenderObject> regularHits = <RenderObject>[];
2169    final List<RenderObject> edgeHits = <RenderObject>[];
2170
2171    _hitTestHelper(regularHits, edgeHits, position, root, root.getTransformTo(null));
2172    // Order matches by the size of the hit area.
2173    double _area(RenderObject object) {
2174      final Size size = object.semanticBounds?.size;
2175      return size == null ? double.maxFinite : size.width * size.height;
2176    }
2177    regularHits.sort((RenderObject a, RenderObject b) => _area(a).compareTo(_area(b)));
2178    final Set<RenderObject> hits = <RenderObject>{
2179      ...edgeHits,
2180      ...regularHits,
2181    };
2182    return hits.toList();
2183  }
2184
2185  void _inspectAt(Offset position) {
2186    if (!isSelectMode)
2187      return;
2188
2189    final RenderIgnorePointer ignorePointer = _ignorePointerKey.currentContext.findRenderObject();
2190    final RenderObject userRender = ignorePointer.child;
2191    final List<RenderObject> selected = hitTest(position, userRender);
2192
2193    setState(() {
2194      selection.candidates = selected;
2195    });
2196  }
2197
2198  void _handlePanDown(DragDownDetails event) {
2199    _lastPointerLocation = event.globalPosition;
2200    _inspectAt(event.globalPosition);
2201  }
2202
2203  void _handlePanUpdate(DragUpdateDetails event) {
2204    _lastPointerLocation = event.globalPosition;
2205    _inspectAt(event.globalPosition);
2206  }
2207
2208  void _handlePanEnd(DragEndDetails details) {
2209    // If the pan ends on the edge of the window assume that it indicates the
2210    // pointer is being dragged off the edge of the display not a regular touch
2211    // on the edge of the display. If the pointer is being dragged off the edge
2212    // of the display we do not want to select anything. A user can still select
2213    // a widget that is only at the exact screen margin by tapping.
2214    final Rect bounds = (Offset.zero & (WidgetsBinding.instance.window.physicalSize / WidgetsBinding.instance.window.devicePixelRatio)).deflate(_kOffScreenMargin);
2215    if (!bounds.contains(_lastPointerLocation)) {
2216      setState(() {
2217        selection.clear();
2218      });
2219    }
2220  }
2221
2222  void _handleTap() {
2223    if (!isSelectMode)
2224      return;
2225    if (_lastPointerLocation != null) {
2226      _inspectAt(_lastPointerLocation);
2227
2228      if (selection != null) {
2229        // Notify debuggers to open an inspector on the object.
2230        developer.inspect(selection.current);
2231      }
2232    }
2233    setState(() {
2234      // Only exit select mode if there is a button to return to select mode.
2235      if (widget.selectButtonBuilder != null)
2236        isSelectMode = false;
2237    });
2238  }
2239
2240  void _handleEnableSelect() {
2241    setState(() {
2242      isSelectMode = true;
2243    });
2244  }
2245
2246  @override
2247  Widget build(BuildContext context) {
2248    final List<Widget> children = <Widget>[];
2249    children.add(GestureDetector(
2250      onTap: _handleTap,
2251      onPanDown: _handlePanDown,
2252      onPanEnd: _handlePanEnd,
2253      onPanUpdate: _handlePanUpdate,
2254      behavior: HitTestBehavior.opaque,
2255      excludeFromSemantics: true,
2256      child: IgnorePointer(
2257        ignoring: isSelectMode,
2258        key: _ignorePointerKey,
2259        ignoringSemantics: false,
2260        child: widget.child,
2261      ),
2262    ));
2263    if (!isSelectMode && widget.selectButtonBuilder != null) {
2264      children.add(Positioned(
2265        left: _kInspectButtonMargin,
2266        bottom: _kInspectButtonMargin,
2267        child: widget.selectButtonBuilder(context, _handleEnableSelect),
2268      ));
2269    }
2270    children.add(_InspectorOverlay(selection: selection));
2271    return Stack(children: children);
2272  }
2273}
2274
2275/// Mutable selection state of the inspector.
2276class InspectorSelection {
2277  /// Render objects that are candidates to be selected.
2278  ///
2279  /// Tools may wish to iterate through the list of candidates.
2280  List<RenderObject> get candidates => _candidates;
2281  List<RenderObject> _candidates = <RenderObject>[];
2282  set candidates(List<RenderObject> value) {
2283    _candidates = value;
2284    _index = 0;
2285    _computeCurrent();
2286  }
2287
2288  /// Index within the list of candidates that is currently selected.
2289  int get index => _index;
2290  int _index = 0;
2291  set index(int value) {
2292    _index = value;
2293    _computeCurrent();
2294  }
2295
2296  /// Set the selection to empty.
2297  void clear() {
2298    _candidates = <RenderObject>[];
2299    _index = 0;
2300    _computeCurrent();
2301  }
2302
2303  /// Selected render object typically from the [candidates] list.
2304  ///
2305  /// Setting [candidates] or calling [clear] resets the selection.
2306  ///
2307  /// Returns null if the selection is invalid.
2308  RenderObject get current => _current;
2309  RenderObject _current;
2310  set current(RenderObject value) {
2311    if (_current != value) {
2312      _current = value;
2313      _currentElement = value.debugCreator.element;
2314    }
2315  }
2316
2317  /// Selected [Element] consistent with the [current] selected [RenderObject].
2318  ///
2319  /// Setting [candidates] or calling [clear] resets the selection.
2320  ///
2321  /// Returns null if the selection is invalid.
2322  Element get currentElement => _currentElement;
2323  Element _currentElement;
2324  set currentElement(Element element) {
2325    if (currentElement != element) {
2326      _currentElement = element;
2327      _current = element.findRenderObject();
2328    }
2329  }
2330
2331  void _computeCurrent() {
2332    if (_index < candidates.length) {
2333      _current = candidates[index];
2334      _currentElement = _current.debugCreator.element;
2335    } else {
2336      _current = null;
2337      _currentElement = null;
2338    }
2339  }
2340
2341  /// Whether the selected render object is attached to the tree or has gone
2342  /// out of scope.
2343  bool get active => _current != null && _current.attached;
2344}
2345
2346class _InspectorOverlay extends LeafRenderObjectWidget {
2347  const _InspectorOverlay({
2348    Key key,
2349    @required this.selection,
2350  }) : super(key: key);
2351
2352  final InspectorSelection selection;
2353
2354  @override
2355  _RenderInspectorOverlay createRenderObject(BuildContext context) {
2356    return _RenderInspectorOverlay(selection: selection);
2357  }
2358
2359  @override
2360  void updateRenderObject(BuildContext context, _RenderInspectorOverlay renderObject) {
2361    renderObject.selection = selection;
2362  }
2363}
2364
2365class _RenderInspectorOverlay extends RenderBox {
2366  /// The arguments must not be null.
2367  _RenderInspectorOverlay({ @required InspectorSelection selection })
2368    : _selection = selection,
2369      assert(selection != null);
2370
2371  InspectorSelection get selection => _selection;
2372  InspectorSelection _selection;
2373  set selection(InspectorSelection value) {
2374    if (value != _selection) {
2375      _selection = value;
2376    }
2377    markNeedsPaint();
2378  }
2379
2380  @override
2381  bool get sizedByParent => true;
2382
2383  @override
2384  bool get alwaysNeedsCompositing => true;
2385
2386  @override
2387  void performResize() {
2388    size = constraints.constrain(const Size(double.infinity, double.infinity));
2389  }
2390
2391  @override
2392  void paint(PaintingContext context, Offset offset) {
2393    assert(needsCompositing);
2394    context.addLayer(_InspectorOverlayLayer(
2395      overlayRect: Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height),
2396      selection: selection,
2397    ));
2398  }
2399}
2400
2401class _TransformedRect {
2402  _TransformedRect(RenderObject object)
2403    : rect = object.semanticBounds,
2404      transform = object.getTransformTo(null);
2405
2406  final Rect rect;
2407  final Matrix4 transform;
2408
2409  @override
2410  bool operator ==(dynamic other) {
2411    if (other.runtimeType != runtimeType)
2412      return false;
2413    final _TransformedRect typedOther = other;
2414    return rect == typedOther.rect && transform == typedOther.transform;
2415  }
2416
2417  @override
2418  int get hashCode => hashValues(rect, transform);
2419}
2420
2421/// State describing how the inspector overlay should be rendered.
2422///
2423/// The equality operator can be used to determine whether the overlay needs to
2424/// be rendered again.
2425class _InspectorOverlayRenderState {
2426  _InspectorOverlayRenderState({
2427    @required this.overlayRect,
2428    @required this.selected,
2429    @required this.candidates,
2430    @required this.tooltip,
2431    @required this.textDirection,
2432  });
2433
2434  final Rect overlayRect;
2435  final _TransformedRect selected;
2436  final List<_TransformedRect> candidates;
2437  final String tooltip;
2438  final TextDirection textDirection;
2439
2440  @override
2441  bool operator ==(dynamic other) {
2442    if (other.runtimeType != runtimeType)
2443      return false;
2444
2445    final _InspectorOverlayRenderState typedOther = other;
2446    return overlayRect == typedOther.overlayRect
2447        && selected == typedOther.selected
2448        && listEquals<_TransformedRect>(candidates, typedOther.candidates)
2449        && tooltip == typedOther.tooltip;
2450  }
2451
2452  @override
2453  int get hashCode => hashValues(overlayRect, selected, hashList(candidates), tooltip);
2454}
2455
2456const int _kMaxTooltipLines = 5;
2457const Color _kTooltipBackgroundColor = Color.fromARGB(230, 60, 60, 60);
2458const Color _kHighlightedRenderObjectFillColor = Color.fromARGB(128, 128, 128, 255);
2459const Color _kHighlightedRenderObjectBorderColor = Color.fromARGB(128, 64, 64, 128);
2460
2461/// A layer that outlines the selected [RenderObject] and candidate render
2462/// objects that also match the last pointer location.
2463///
2464/// This approach is horrific for performance and is only used here because this
2465/// is limited to debug mode. Do not duplicate the logic in production code.
2466class _InspectorOverlayLayer extends Layer {
2467  /// Creates a layer that displays the inspector overlay.
2468  _InspectorOverlayLayer({
2469    @required this.overlayRect,
2470    @required this.selection,
2471  }) : assert(overlayRect != null),
2472       assert(selection != null) {
2473    bool inDebugMode = false;
2474    assert(() {
2475      inDebugMode = true;
2476      return true;
2477    }());
2478    if (inDebugMode == false) {
2479      throw FlutterError.fromParts(<DiagnosticsNode>[
2480        ErrorSummary(
2481          'The inspector should never be used in production mode due to the '
2482          'negative performance impact.'
2483        )
2484      ]);
2485    }
2486  }
2487
2488  InspectorSelection selection;
2489
2490  /// The rectangle in this layer's coordinate system that the overlay should
2491  /// occupy.
2492  ///
2493  /// The scene must be explicitly recomposited after this property is changed
2494  /// (as described at [Layer]).
2495  final Rect overlayRect;
2496
2497  _InspectorOverlayRenderState _lastState;
2498
2499  /// Picture generated from _lastState.
2500  ui.Picture _picture;
2501
2502  TextPainter _textPainter;
2503  double _textPainterMaxWidth;
2504
2505  @override
2506  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
2507    if (!selection.active)
2508      return;
2509
2510    final RenderObject selected = selection.current;
2511    final List<_TransformedRect> candidates = <_TransformedRect>[];
2512    for (RenderObject candidate in selection.candidates) {
2513      if (candidate == selected || !candidate.attached)
2514        continue;
2515      candidates.add(_TransformedRect(candidate));
2516    }
2517
2518    final _InspectorOverlayRenderState state = _InspectorOverlayRenderState(
2519      overlayRect: overlayRect,
2520      selected: _TransformedRect(selected),
2521      tooltip: selection.currentElement.toStringShort(),
2522      textDirection: TextDirection.ltr,
2523      candidates: candidates,
2524    );
2525
2526    if (state != _lastState) {
2527      _lastState = state;
2528      _picture = _buildPicture(state);
2529    }
2530    builder.addPicture(layerOffset, _picture);
2531  }
2532
2533  ui.Picture _buildPicture(_InspectorOverlayRenderState state) {
2534    final ui.PictureRecorder recorder = ui.PictureRecorder();
2535    final Canvas canvas = Canvas(recorder, state.overlayRect);
2536    final Size size = state.overlayRect.size;
2537
2538    final Paint fillPaint = Paint()
2539      ..style = PaintingStyle.fill
2540      ..color = _kHighlightedRenderObjectFillColor;
2541
2542    final Paint borderPaint = Paint()
2543      ..style = PaintingStyle.stroke
2544      ..strokeWidth = 1.0
2545      ..color = _kHighlightedRenderObjectBorderColor;
2546
2547    // Highlight the selected renderObject.
2548    final Rect selectedPaintRect = state.selected.rect.deflate(0.5);
2549    canvas
2550      ..save()
2551      ..transform(state.selected.transform.storage)
2552      ..drawRect(selectedPaintRect, fillPaint)
2553      ..drawRect(selectedPaintRect, borderPaint)
2554      ..restore();
2555
2556    // Show all other candidate possibly selected elements. This helps selecting
2557    // render objects by selecting the edge of the bounding box shows all
2558    // elements the user could toggle the selection between.
2559    for (_TransformedRect transformedRect in state.candidates) {
2560      canvas
2561        ..save()
2562        ..transform(transformedRect.transform.storage)
2563        ..drawRect(transformedRect.rect.deflate(0.5), borderPaint)
2564        ..restore();
2565    }
2566
2567    final Rect targetRect = MatrixUtils.transformRect(
2568        state.selected.transform, state.selected.rect);
2569    final Offset target = Offset(targetRect.left, targetRect.center.dy);
2570    const double offsetFromWidget = 9.0;
2571    final double verticalOffset = (targetRect.height) / 2 + offsetFromWidget;
2572
2573    _paintDescription(canvas, state.tooltip, state.textDirection, target, verticalOffset, size, targetRect);
2574
2575    // TODO(jacobr): provide an option to perform a debug paint of just the
2576    // selected widget.
2577    return recorder.endRecording();
2578  }
2579
2580  void _paintDescription(
2581    Canvas canvas,
2582    String message,
2583    TextDirection textDirection,
2584    Offset target,
2585    double verticalOffset,
2586    Size size,
2587    Rect targetRect,
2588  ) {
2589    canvas.save();
2590    final double maxWidth = size.width - 2 * (_kScreenEdgeMargin + _kTooltipPadding);
2591    final TextSpan textSpan = _textPainter?.text;
2592    if (_textPainter == null || textSpan.text != message || _textPainterMaxWidth != maxWidth) {
2593      _textPainterMaxWidth = maxWidth;
2594      _textPainter = TextPainter()
2595        ..maxLines = _kMaxTooltipLines
2596        ..ellipsis = '...'
2597        ..text = TextSpan(style: _messageStyle, text: message)
2598        ..textDirection = textDirection
2599        ..layout(maxWidth: maxWidth);
2600    }
2601
2602    final Size tooltipSize = _textPainter.size + const Offset(_kTooltipPadding * 2, _kTooltipPadding * 2);
2603    final Offset tipOffset = positionDependentBox(
2604      size: size,
2605      childSize: tooltipSize,
2606      target: target,
2607      verticalOffset: verticalOffset,
2608      preferBelow: false,
2609    );
2610
2611    final Paint tooltipBackground = Paint()
2612      ..style = PaintingStyle.fill
2613      ..color = _kTooltipBackgroundColor;
2614    canvas.drawRect(
2615      Rect.fromPoints(
2616        tipOffset,
2617        tipOffset.translate(tooltipSize.width, tooltipSize.height),
2618      ),
2619      tooltipBackground,
2620    );
2621
2622    double wedgeY = tipOffset.dy;
2623    final bool tooltipBelow = tipOffset.dy > target.dy;
2624    if (!tooltipBelow)
2625      wedgeY += tooltipSize.height;
2626
2627    const double wedgeSize = _kTooltipPadding * 2;
2628    double wedgeX = math.max(tipOffset.dx, target.dx) + wedgeSize * 2;
2629    wedgeX = math.min(wedgeX, tipOffset.dx + tooltipSize.width - wedgeSize * 2);
2630    final List<Offset> wedge = <Offset>[
2631      Offset(wedgeX - wedgeSize, wedgeY),
2632      Offset(wedgeX + wedgeSize, wedgeY),
2633      Offset(wedgeX, wedgeY + (tooltipBelow ? -wedgeSize : wedgeSize)),
2634    ];
2635    canvas.drawPath(Path()..addPolygon(wedge, true,), tooltipBackground);
2636    _textPainter.paint(canvas, tipOffset + const Offset(_kTooltipPadding, _kTooltipPadding));
2637    canvas.restore();
2638  }
2639
2640  @override
2641  S find<S>(Offset regionOffset) => null;
2642
2643  @override
2644  Iterable<S> findAll<S>(Offset regionOffset) => <S>[];
2645}
2646
2647const double _kScreenEdgeMargin = 10.0;
2648const double _kTooltipPadding = 5.0;
2649const double _kInspectButtonMargin = 10.0;
2650
2651/// Interpret pointer up events within with this margin as indicating the
2652/// pointer is moving off the device.
2653const double _kOffScreenMargin = 1.0;
2654
2655const TextStyle _messageStyle = TextStyle(
2656  color: Color(0xFFFFFFFF),
2657  fontSize: 10.0,
2658  height: 1.2,
2659);
2660
2661/// Interface for classes that track the source code location the their
2662/// constructor was called from.
2663///
2664/// A [Dart Kernel Transformer](https://github.com/dart-lang/sdk/wiki/Kernel-Documentation).
2665/// adds this interface to the [Widget] class when the
2666/// `--track-widget-creation` flag is passed to `flutter_tool`. Dart 2.0 is
2667/// required as injecting creation locations requires a
2668/// [Dart Kernel Transformer](https://github.com/dart-lang/sdk/wiki/Kernel-Documentation).
2669// ignore: unused_element
2670abstract class _HasCreationLocation {
2671  _Location get _location;
2672}
2673
2674/// A tuple with file, line, and column number, for displaying human-readable
2675/// file locations.
2676class _Location {
2677  const _Location({
2678    this.file,
2679    this.line,
2680    this.column,
2681    this.name,
2682    this.parameterLocations,
2683  });
2684
2685  /// File path of the location.
2686  final String file;
2687
2688  /// 1-based line number.
2689  final int line;
2690  /// 1-based column number.
2691  final int column;
2692
2693  /// Optional name of the parameter or function at this location.
2694  final String name;
2695
2696  /// Optional locations of the parameters of the member at this location.
2697  final List<_Location> parameterLocations;
2698
2699  Map<String, Object> toJsonMap() {
2700    final Map<String, Object> json = <String, Object>{
2701      'file': file,
2702      'line': line,
2703      'column': column,
2704    };
2705    if (name != null) {
2706      json['name'] = name;
2707    }
2708    if (parameterLocations != null) {
2709      json['parameterLocations'] = parameterLocations.map<Map<String, Object>>(
2710          (_Location location) => location.toJsonMap()).toList();
2711    }
2712    return json;
2713  }
2714
2715  @override
2716  String toString() {
2717    final List<String> parts = <String>[];
2718    if (name != null) {
2719      parts.add(name);
2720    }
2721    if (file != null) {
2722      parts.add(file);
2723    }
2724    parts..add('$line')..add('$column');
2725    return parts.join(':');
2726  }
2727}
2728
2729bool _isDebugCreator(DiagnosticsNode node) => node is DiagnosticsDebugCreator;
2730
2731/// Transformer to parse and gather information about [DiagnosticsDebugCreator].
2732///
2733/// This function will be registered to [FlutterErrorDetails.propertiesTransformers]
2734/// in [WidgetsBinding.initInstances].
2735Iterable<DiagnosticsNode> transformDebugCreator(Iterable<DiagnosticsNode> properties) sync* {
2736  final List<DiagnosticsNode> pending = <DiagnosticsNode>[];
2737  bool foundStackTrace = false;
2738  for (DiagnosticsNode node in properties) {
2739    if (!foundStackTrace && node is DiagnosticsStackTrace)
2740      foundStackTrace = true;
2741    if (_isDebugCreator(node)) {
2742      yield* _parseDiagnosticsNode(node);
2743    } else {
2744      if (foundStackTrace) {
2745        pending.add(node);
2746      } else {
2747        yield node;
2748      }
2749    }
2750  }
2751  yield* pending;
2752}
2753
2754/// Transform the input [DiagnosticsNode].
2755///
2756/// Return null if input [DiagnosticsNode] is not applicable.
2757Iterable<DiagnosticsNode> _parseDiagnosticsNode(DiagnosticsNode node) {
2758  if (!_isDebugCreator(node))
2759    return null;
2760  final DebugCreator debugCreator = node.value;
2761  final Element element = debugCreator.element;
2762  return _describeRelevantUserCode(element);
2763}
2764
2765Iterable<DiagnosticsNode> _describeRelevantUserCode(Element element) {
2766  if (!WidgetInspectorService.instance.isWidgetCreationTracked()) {
2767    return <DiagnosticsNode>[
2768      ErrorDescription(
2769        'Widget creation tracking is currently disabled. Enabling '
2770        'it enables improved error messages. It can be enabled by passing '
2771        '`--track-widget-creation` to `flutter run` or `flutter test`.',
2772      ),
2773      ErrorSpacer(),
2774    ];
2775  }
2776  final List<DiagnosticsNode> nodes = <DiagnosticsNode>[];
2777  element.visitAncestorElements((Element ancestor) {
2778    // TODO(chunhtai): should print out all the widgets that are about to cross
2779    // package boundaries.
2780    if (_isLocalCreationLocation(ancestor)) {
2781      nodes.add(
2782        DiagnosticsBlock(
2783          name: 'User-created ancestor of the error-causing widget was',
2784          children: <DiagnosticsNode>[
2785            ErrorDescription('${ancestor.widget.toStringShort()} ${_describeCreationLocation(ancestor)}'),
2786          ],
2787        )
2788      );
2789      nodes.add(ErrorSpacer());
2790      return false;
2791    }
2792    return true;
2793  });
2794  return nodes;
2795}
2796
2797/// Returns if an object is user created.
2798///
2799/// This function will only work in debug mode builds when
2800/// the `--track-widget-creation` flag is passed to `flutter_tool`. Dart 2.0 is
2801/// required as injecting creation locations requires a
2802/// [Dart Kernel Transformer](https://github.com/dart-lang/sdk/wiki/Kernel-Documentation).
2803///
2804/// Currently is local creation locations are only available for
2805/// [Widget] and [Element].
2806bool _isLocalCreationLocation(Object object) {
2807  final _Location location = _getCreationLocation(object);
2808  if (location == null)
2809    return false;
2810  return WidgetInspectorService.instance._isLocalCreationLocation(location);
2811}
2812
2813/// Returns the creation location of an object in String format if one is available.
2814///
2815/// ex: "file:///path/to/main.dart:4:3"
2816///
2817/// Creation locations are only available for debug mode builds when
2818/// the `--track-widget-creation` flag is passed to `flutter_tool`. Dart 2.0 is
2819/// required as injecting creation locations requires a
2820/// [Dart Kernel Transformer](https://github.com/dart-lang/sdk/wiki/Kernel-Documentation).
2821///
2822/// Currently creation locations are only available for [Widget] and [Element].
2823String _describeCreationLocation(Object object) {
2824  final _Location location = _getCreationLocation(object);
2825  return location?.toString();
2826}
2827
2828/// Returns the creation location of an object if one is available.
2829///
2830/// Creation locations are only available for debug mode builds when
2831/// the `--track-widget-creation` flag is passed to `flutter_tool`. Dart 2.0 is
2832/// required as injecting creation locations requires a
2833/// [Dart Kernel Transformer](https://github.com/dart-lang/sdk/wiki/Kernel-Documentation).
2834///
2835/// Currently creation locations are only available for [Widget] and [Element].
2836_Location _getCreationLocation(Object object) {
2837  final Object candidate =  object is Element ? object.widget : object;
2838  return candidate is _HasCreationLocation ? candidate._location : null;
2839}
2840
2841// _Location objects are always const so we don't need to worry about the GC
2842// issues that are a concern for other object ids tracked by
2843// [WidgetInspectorService].
2844final Map<_Location, int> _locationToId = <_Location, int>{};
2845final List<_Location> _locations = <_Location>[];
2846
2847int _toLocationId(_Location location) {
2848  int id = _locationToId[location];
2849  if (id != null) {
2850    return id;
2851  }
2852  id = _locations.length;
2853  _locations.add(location);
2854  _locationToId[location] = id;
2855  return id;
2856}
2857
2858class _SerializationDelegate implements DiagnosticsSerializationDelegate {
2859  _SerializationDelegate({
2860    this.groupName,
2861    this.summaryTree = false,
2862    this.maxDescendentsTruncatableNode = -1,
2863    this.expandPropertyValues = true,
2864    this.subtreeDepth = 1,
2865    this.includeProperties = false,
2866    @required this.service,
2867  });
2868
2869  final WidgetInspectorService service;
2870  final String groupName;
2871  final bool summaryTree;
2872  final int maxDescendentsTruncatableNode;
2873
2874  @override
2875  final bool includeProperties;
2876
2877  @override
2878  final int subtreeDepth;
2879
2880  @override
2881  final bool expandPropertyValues;
2882
2883  final List<DiagnosticsNode> _nodesCreatedByLocalProject = <DiagnosticsNode>[];
2884
2885  bool get interactive => groupName != null;
2886
2887  @override
2888  Map<String, Object> additionalNodeProperties(DiagnosticsNode node) {
2889    final Map<String, Object> result = <String, Object>{};
2890    final Object value = node.value;
2891    if (interactive) {
2892      result['objectId'] = service.toId(node, groupName);
2893      result['valueId'] = service.toId(value, groupName);
2894    }
2895    if (summaryTree) {
2896      result['summaryTree'] = true;
2897    }
2898    final _Location creationLocation = _getCreationLocation(value);
2899    if (creationLocation != null) {
2900      result['locationId'] = _toLocationId(creationLocation);
2901      result['creationLocation'] = creationLocation.toJsonMap();
2902      if (service._isLocalCreationLocation(creationLocation)) {
2903        _nodesCreatedByLocalProject.add(node);
2904        result['createdByLocalProject'] = true;
2905      }
2906    }
2907    return result;
2908  }
2909
2910  @override
2911  DiagnosticsSerializationDelegate delegateForNode(DiagnosticsNode node) {
2912    // The tricky special case here is that when in the detailsTree,
2913    // we keep subtreeDepth from going down to zero until we reach nodes
2914    // that also exist in the summary tree. This ensures that every time
2915    // you expand a node in the details tree, you expand the entire subtree
2916    // up until you reach the next nodes shared with the summary tree.
2917    return summaryTree || subtreeDepth > 1 || service._shouldShowInSummaryTree(node)
2918        ? copyWith(subtreeDepth: subtreeDepth - 1)
2919        : this;
2920  }
2921
2922  @override
2923  List<DiagnosticsNode> filterChildren(List<DiagnosticsNode> children, DiagnosticsNode owner) {
2924    return service._filterChildren(children, this);
2925  }
2926
2927  @override
2928  List<DiagnosticsNode> filterProperties(List<DiagnosticsNode> properties, DiagnosticsNode owner) {
2929    final bool createdByLocalProject = _nodesCreatedByLocalProject.contains(owner);
2930    return properties.where((DiagnosticsNode node) {
2931      return !node.isFiltered(createdByLocalProject ? DiagnosticLevel.fine : DiagnosticLevel.info);
2932    }).toList();
2933  }
2934
2935  @override
2936  List<DiagnosticsNode> truncateNodesList(List<DiagnosticsNode> nodes, DiagnosticsNode owner) {
2937    if (maxDescendentsTruncatableNode >= 0 &&
2938        owner?.allowTruncate == true &&
2939        nodes.length > maxDescendentsTruncatableNode) {
2940      nodes = service._truncateNodes(nodes, maxDescendentsTruncatableNode);
2941    }
2942    return nodes;
2943  }
2944
2945  @override
2946  DiagnosticsSerializationDelegate copyWith({int subtreeDepth, bool includeProperties}) {
2947    return _SerializationDelegate(
2948      groupName: groupName,
2949      summaryTree: summaryTree,
2950      maxDescendentsTruncatableNode: maxDescendentsTruncatableNode,
2951      expandPropertyValues: expandPropertyValues,
2952      subtreeDepth: subtreeDepth ?? this.subtreeDepth,
2953      includeProperties: includeProperties ?? this.includeProperties,
2954      service: service,
2955    );
2956  }
2957}
2958