"Fossies" - the Fresh Open Source Software Archive

Member "flutter-1.22.4/packages/flutter/lib/src/widgets/widget_inspector.dart" (13 Nov 2020, 108318 Bytes) of package /linux/misc/flutter-1.22.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Dart source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

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