From c62d103bb651a32f6a502c4ace955a7a05a34510 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Wed, 13 Apr 2022 09:34:06 -0700 Subject: [PATCH] Always finish the timeline event logged by Element.inflateWidget (#101794) --- .../test/inflate_widget_update_test.dart | 77 +++++++++++++++++++ .../flutter/lib/src/widgets/framework.dart | 52 +++++++------ 2 files changed, 104 insertions(+), 25 deletions(-) create mode 100644 dev/tracing_tests/test/inflate_widget_update_test.dart diff --git a/dev/tracing_tests/test/inflate_widget_update_test.dart b/dev/tracing_tests/test/inflate_widget_update_test.dart new file mode 100644 index 00000000000..acfd014082f --- /dev/null +++ b/dev/tracing_tests/test/inflate_widget_update_test.dart @@ -0,0 +1,77 @@ +// Copyright 2014 The Flutter 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/scheduler.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'common.dart'; + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + initTimelineTests(); + test('Widgets with updated keys produce well formed timelines', () async { + await runFrame(() { runApp(const TestRoot()); }); + await SchedulerBinding.instance.endOfFrame; + + debugProfileBuildsEnabled = true; + + await runFrame(() { + TestRoot.state.updateKey(); + }); + + int buildCount = 0; + for (final TimelineEvent event in await fetchTimelineEvents()) { + if (event.json!['name'] == 'BUILD') { + final String ph = event.json!['ph'] as String; + if (ph == 'B') { + buildCount++; + } else if (ph == 'E') { + buildCount--; + } + } + } + expect(buildCount, 0); + + debugProfileBuildsEnabled = false; + }, skip: isBrowser); // [intended] uses dart:isolate and io. +} + +class TestRoot extends StatefulWidget { + const TestRoot({super.key}); + + static late TestRootState state; + + @override + State createState() => TestRootState(); +} + +class TestRootState extends State { + final Key _globalKey = GlobalKey(); + Key _localKey = UniqueKey(); + + @override + void initState() { + super.initState(); + TestRoot.state = this; + } + + void updateKey() { + setState(() { + _localKey = UniqueKey(); + }); + } + + @override + Widget build(BuildContext context) { + return Center( + key: _localKey, + child: SizedBox( + key: _globalKey, + width: 100, + height: 100, + ), + ); + } +} diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 5bc0ecb272c..f1e03b4a0e3 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -3792,33 +3792,35 @@ abstract class Element extends DiagnosticableTree implements BuildContext { ); } - final Key? key = newWidget.key; - if (key is GlobalKey) { - final Element? newChild = _retakeInactiveElement(key, newWidget); - if (newChild != null) { - assert(newChild._parent == null); - assert(() { - _debugCheckForCycles(newChild); - return true; - }()); - newChild._activateWithParent(this, newSlot); - final Element? updatedChild = updateChild(newChild, newWidget, newSlot); - assert(newChild == updatedChild); - return updatedChild!; + try { + final Key? key = newWidget.key; + if (key is GlobalKey) { + final Element? newChild = _retakeInactiveElement(key, newWidget); + if (newChild != null) { + assert(newChild._parent == null); + assert(() { + _debugCheckForCycles(newChild); + return true; + }()); + newChild._activateWithParent(this, newSlot); + final Element? updatedChild = updateChild(newChild, newWidget, newSlot); + assert(newChild == updatedChild); + return updatedChild!; + } } + final Element newChild = newWidget.createElement(); + assert(() { + _debugCheckForCycles(newChild); + return true; + }()); + newChild.mount(this, newSlot); + assert(newChild._lifecycleState == _ElementLifecycle.active); + + return newChild; + } finally { + if (isTimelineTracked) + Timeline.finishSync(); } - final Element newChild = newWidget.createElement(); - assert(() { - _debugCheckForCycles(newChild); - return true; - }()); - newChild.mount(this, newSlot); - assert(newChild._lifecycleState == _ElementLifecycle.active); - - if (isTimelineTracked) - Timeline.finishSync(); - - return newChild; } void _debugCheckForCycles(Element newChild) {