Skip to content

Commit e316022

Browse files
authored
ReportTiming callback should record the sendFrameToEngine when it was scheduled (#144212)
Fixes flutter/flutter#144261
1 parent a185ff9 commit e316022

File tree

2 files changed

+70
-1
lines changed

2 files changed

+70
-1
lines changed

packages/flutter/lib/src/widgets/binding.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -991,11 +991,12 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
991991
}());
992992

993993
TimingsCallback? firstFrameCallback;
994+
bool debugFrameWasSentToEngine = false;
994995
if (_needToReportFirstFrame) {
995996
assert(!_firstFrameCompleter.isCompleted);
996997

997998
firstFrameCallback = (List<FrameTiming> timings) {
998-
assert(sendFramesToEngine);
999+
assert(debugFrameWasSentToEngine);
9991000
if (!kReleaseMode) {
10001001
// Change the current user tag back to the default tag. At this point,
10011002
// the user tag should be set to "AppStartUp" (originally set in the
@@ -1020,6 +1021,10 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
10201021
buildOwner!.buildScope(rootElement!);
10211022
}
10221023
super.drawFrame();
1024+
assert(() {
1025+
debugFrameWasSentToEngine = sendFramesToEngine;
1026+
return true;
1027+
}());
10231028
buildOwner!.finalizeTree();
10241029
} finally {
10251030
assert(() {
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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+
import 'package:flutter/foundation.dart';
6+
import 'package:flutter/material.dart';
7+
import 'package:flutter_test/flutter_test.dart';
8+
9+
// This file is for tests for WidgetsBinding that require a `LiveTestWidgetsFlutterBinding`.
10+
void main() {
11+
LiveTestWidgetsFlutterBinding();
12+
testWidgets('ReportTiming callback records the sendFramesToEngine when it was scheduled', (WidgetTester tester) async {
13+
// Addresses https://github.com/flutter/flutter/issues/144261
14+
// This test needs LiveTestWidgetsFlutterBinding for multiple reasons.
15+
//
16+
// First, this was the environment that this bug was discovered.
17+
//
18+
// Second, unlike `AutomatedTestWidgetsFlutterBinding`, which overrides
19+
// `scheduleWarmUpFrame` to execute the handlers synchronously,
20+
// `LiveTestWidgetsFlutterBinding` still calls them asynchronously. This
21+
// allows `runApp`, which also schedules a warm-up frame, to bind a widget
22+
// without rendering a frame, which is needed to for `deferFirstFrame` to
23+
// take effect.
24+
25+
// Before `testWidgets` executes the test body, it pumps a frame with a
26+
// fixed dummy widget, then calls `resetFirstFrameSent`. The pumped frame
27+
// schedules a reportTiming call that has yet to arrive.
28+
//
29+
// This puts the test in an inconsistent state: a reportTiming callback is
30+
// supposed to happen only after a frame is rendered, but due to
31+
// `resetFirstFrameSent`, the framework thinks no frames have been rendered.
32+
33+
expect(tester.binding.sendFramesToEngine, true);
34+
// Push the widget with `runApp` instead of `tester.pump`, avoiding
35+
// rendering a frame, which is needed for `deferFirstFrame` later to work.
36+
runApp(const DummyWidget());
37+
// Verify that no widget tree is built and nothing is rendered.
38+
expect(find.text('First frame'), findsNothing);
39+
// Defer the first frame, making `sendFramesToEngine` false, so that widget
40+
// tree will be built but not sent to the engine.
41+
tester.binding.deferFirstFrame();
42+
expect(tester.binding.sendFramesToEngine, false);
43+
// Pump a frame, letting the reportTiming callback to run. If the
44+
// reportTiming callback were to assume that `sendFramesToEngine` is true,
45+
// the callback would crash.
46+
await tester.pump(const Duration(milliseconds: 1));
47+
await tester.binding.waitUntilFirstFrameRasterized;
48+
expect(find.text('First frame'), findsOne);
49+
}, skip: kIsWeb); // [intended] Web doesn't use LiveTestWidgetsFlutterBinding
50+
}
51+
52+
class DummyWidget extends StatelessWidget {
53+
const DummyWidget({super.key});
54+
55+
@override
56+
Widget build(BuildContext context) {
57+
return const Directionality(
58+
textDirection: TextDirection.ltr,
59+
child: Center(
60+
child: Text('First frame'),
61+
),
62+
);
63+
}
64+
}

0 commit comments

Comments
 (0)