From 96fbbdc3e026d030ee1963d0f736aed5b853c714 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Thu, 14 Nov 2019 13:19:55 -0800 Subject: [PATCH] Fix "node._relayoutBoundary == _relayoutBoundary" crash (#44490) --- .../flutter/lib/src/rendering/object.dart | 10 ++- .../rendering/repaint_boundary_2_test.dart | 63 +++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 packages/flutter/test/rendering/repaint_boundary_2_test.dart diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 797118b8794..5921ff8029c 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -1429,7 +1429,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im static bool debugCheckingIntrinsics = false; bool _debugSubtreeRelayoutRootAlreadyMarkedNeedsLayout() { if (_relayoutBoundary == null) - return true; // we haven't yet done layout even once, so there's nothing for us to do + return true; // we don't know where our relayout boundary is yet RenderObject node = this; while (node != _relayoutBoundary) { assert(node._relayoutBoundary == _relayoutBoundary); @@ -1677,6 +1677,14 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im return; } _constraints = constraints; + if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) { + // The local relayout boundary has changed, must notify children in case + // they also need updating. Otherwise, they will be confused about what + // their actual relayout boundary is later. + visitChildren((RenderObject child) { + child._cleanRelayoutBoundary(); + }); + } _relayoutBoundary = relayoutBoundary; assert(!_debugMutationsLocked); assert(!_doingThisLayoutWithCallback); diff --git a/packages/flutter/test/rendering/repaint_boundary_2_test.dart b/packages/flutter/test/rendering/repaint_boundary_2_test.dart new file mode 100644 index 00000000000..8f1757359c1 --- /dev/null +++ b/packages/flutter/test/rendering/repaint_boundary_2_test.dart @@ -0,0 +1,63 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/widgets.dart'; + +void main() { + testWidgets('repaint boundary with constraint changes', (WidgetTester tester) async { + // Regression test for as https://github.com/flutter/flutter/issues/39151. + await tester.pumpWidget(const RelayoutBoundariesCrash()); + tester.state(find.byType(RelayoutBoundariesCrash))._toggleMode(); + await tester.pump(); + }); +} + +class RelayoutBoundariesCrash extends StatefulWidget { + const RelayoutBoundariesCrash({Key key}) : super(key: key); + + @override + RelayoutBoundariesCrashState createState() => RelayoutBoundariesCrashState(); +} + +class RelayoutBoundariesCrashState extends State { + bool _mode = true; + + void _toggleMode() { + setState(() { + _mode = !_mode; + }); + } + + @override + Widget build(BuildContext context) { + return Center( + child: SizedBox( + // when _mode is true, constraints are tight, otherwise constraints are loose + width: !_mode ? 100.0 : null, + height: !_mode ? 100.0 : null, + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + // Make the outer SizedBoxes relayout without making the Placeholders relayout. + final double dimension = !_mode ? 10.0 : 20.0; + return Column( + children: [ + SizedBox( + width: dimension, + height: dimension, + child: const Placeholder(), + ), + SizedBox( + width: dimension, + height: dimension, + child: const Placeholder(), + ), + ], + ); + }, + ), + ), + ); + } +}