diff --git a/packages/flutter/lib/src/scheduler/binding.dart b/packages/flutter/lib/src/scheduler/binding.dart index 14d0b18329..7fde6ceadd 100644 --- a/packages/flutter/lib/src/scheduler/binding.dart +++ b/packages/flutter/lib/src/scheduler/binding.dart @@ -774,7 +774,7 @@ mixin SchedulerBinding on BindingBase, ServicesBinding { /// * [scheduleWarmUpFrame], which ignores the "Vsync" signal entirely and /// triggers a frame immediately. void scheduleFrame() { - if (_hasScheduledFrame || !_framesEnabled) + if (_hasScheduledFrame || !framesEnabled) return; assert(() { if (debugPrintScheduleFrameStacks) @@ -808,7 +808,7 @@ mixin SchedulerBinding on BindingBase, ServicesBinding { void scheduleForcedFrame() { // TODO(chunhtai): Removes the if case once the issue is fixed // https://github.com/flutter/flutter/issues/45131 - if (!_framesEnabled) + if (!framesEnabled) return; if (_hasScheduledFrame) diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart index 43285025e1..d511001d6e 100644 --- a/packages/flutter/lib/src/widgets/binding.dart +++ b/packages/flutter/lib/src/widgets/binding.dart @@ -907,6 +907,11 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB Element get renderViewElement => _renderViewElement; Element _renderViewElement; + bool _readyToProduceFrames = false; + + @override + bool get framesEnabled => super.framesEnabled && _readyToProduceFrames; + /// Schedules a [Timer] for attaching the root widget. /// /// This is called by [runApp] to configure the widget tree. Consider using @@ -928,6 +933,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB /// * [RenderObjectToWidgetAdapter.attachToRenderTree], which inflates a /// widget and attaches it to the render tree. void attachRootWidget(Widget rootWidget) { + _readyToProduceFrames = true; _renderViewElement = RenderObjectToWidgetAdapter( container: renderView, debugShortDescription: '[root]', diff --git a/packages/flutter/test/widgets/binding_cannot_schedule_frame_test.dart b/packages/flutter/test/widgets/binding_cannot_schedule_frame_test.dart new file mode 100644 index 0000000000..1c26f3cfcc --- /dev/null +++ b/packages/flutter/test/widgets/binding_cannot_schedule_frame_test.dart @@ -0,0 +1,33 @@ +// 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 'dart:typed_data'; + +import 'package:flutter/scheduler.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/widgets.dart'; + +void main() { + test('Can only schedule frames after widget binding attaches the root widget', () async { + final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized(); + expect(SchedulerBinding.instance.framesEnabled, isFalse); + expect(SchedulerBinding.instance.hasScheduledFrame, isFalse); + // Sends a message to notify that the engine is ready to accept frames. + final ByteData message = const StringCodec().encodeMessage('AppLifecycleState.resumed'); + await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { }); + + // Enables the semantics should not schedule any frames if the root widget + // has not been attached. + binding.setSemanticsEnabled(true); + expect(SchedulerBinding.instance.framesEnabled, isFalse); + expect(SchedulerBinding.instance.hasScheduledFrame, isFalse); + + // The widget binding should be ready to produce frames after it attaches + // the root widget. + binding.attachRootWidget(const Placeholder()); + expect(SchedulerBinding.instance.framesEnabled, isTrue); + expect(SchedulerBinding.instance.hasScheduledFrame, isTrue); + }); +} diff --git a/packages/flutter/test/widgets/binding_frame_scheduling_test.dart b/packages/flutter/test/widgets/binding_frame_scheduling_test.dart index 99af4eed8b..7d44430e88 100644 --- a/packages/flutter/test/widgets/binding_frame_scheduling_test.dart +++ b/packages/flutter/test/widgets/binding_frame_scheduling_test.dart @@ -28,6 +28,9 @@ void main() { final ByteData message = const StringCodec().encodeMessage('AppLifecycleState.resumed'); await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { }); + // A frame can only be scheduled when there is a root widget. + binding.attachRootWidget(const Placeholder()); + // Frame callbacks are registered lazily when a frame is scheduled. binding.scheduleFrame(); expect(window.onBeginFrame, isNotNull);