This commit is contained in:
parent
088fa24463
commit
c59151b1cd
@ -284,6 +284,62 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
|
|||||||
_mouseTracker.schedulePostFrameCheck();
|
_mouseTracker.schedulePostFrameCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int _firstFrameDeferredCount = 0;
|
||||||
|
bool _firstFrameSent = false;
|
||||||
|
|
||||||
|
/// Whether frames produced by [drawFrame] are sent to the engine.
|
||||||
|
///
|
||||||
|
/// If false the framework will do all the work to produce a frame,
|
||||||
|
/// but the frame is never send to the engine to actually appear on screen.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [deferFirstFrame], which defers when the first frame is send to the
|
||||||
|
/// engine.
|
||||||
|
bool get sendFramesToEngine => _firstFrameSent || _firstFrameDeferredCount == 0;
|
||||||
|
|
||||||
|
/// Tell the framework to not send the first frames to the engine until there
|
||||||
|
/// is a corresponding call to [allowFirstFrame].
|
||||||
|
///
|
||||||
|
/// Call this to perform asynchronous initialisation work before the first
|
||||||
|
/// frame is rendered (which takes down the splash screen). The framework
|
||||||
|
/// will still do all the work to produce frames, but those frames are never
|
||||||
|
/// send to the engine and will not appear on screen.
|
||||||
|
///
|
||||||
|
/// Calling this has no effect after the first frame has been send to the
|
||||||
|
/// engine.
|
||||||
|
void deferFirstFrame() {
|
||||||
|
assert(_firstFrameDeferredCount >= 0);
|
||||||
|
_firstFrameDeferredCount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called after [deferFirstFrame] to tell the framework that it is ok to
|
||||||
|
/// send the first frame to the engine now.
|
||||||
|
///
|
||||||
|
/// For best performance, this method should only be called while the
|
||||||
|
/// [schedulerPhase] is [SchedulerPhase.idle].
|
||||||
|
///
|
||||||
|
/// This method may only be called once for each corresponding call
|
||||||
|
/// to [deferFirstFrame].
|
||||||
|
void allowFirstFrame() {
|
||||||
|
assert(_firstFrameDeferredCount > 0);
|
||||||
|
_firstFrameDeferredCount -= 1;
|
||||||
|
// Always schedule a warm up frame even if the deferral count is not down to
|
||||||
|
// zero yet since the removal of a deferral may uncover new deferrals that
|
||||||
|
// are lower in the widget tree.
|
||||||
|
if (!_firstFrameSent)
|
||||||
|
scheduleWarmUpFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call this to pretend that no frames have been sent to the engine yet.
|
||||||
|
///
|
||||||
|
/// This is useful for tests that want to call [deferFirstFrame] and
|
||||||
|
/// [allowFirstFrame] since those methods only have an effect if no frames
|
||||||
|
/// have been sent to the engine yet.
|
||||||
|
void resetFirstFrameSent() {
|
||||||
|
_firstFrameSent = false;
|
||||||
|
}
|
||||||
|
|
||||||
/// Pump the rendering pipeline to generate a frame.
|
/// Pump the rendering pipeline to generate a frame.
|
||||||
///
|
///
|
||||||
/// This method is called by [handleDrawFrame], which itself is called
|
/// This method is called by [handleDrawFrame], which itself is called
|
||||||
@ -345,8 +401,11 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
|
|||||||
pipelineOwner.flushLayout();
|
pipelineOwner.flushLayout();
|
||||||
pipelineOwner.flushCompositingBits();
|
pipelineOwner.flushCompositingBits();
|
||||||
pipelineOwner.flushPaint();
|
pipelineOwner.flushPaint();
|
||||||
|
if (sendFramesToEngine) {
|
||||||
renderView.compositeFrame(); // this sends the bits to the GPU
|
renderView.compositeFrame(); // this sends the bits to the GPU
|
||||||
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
|
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
|
||||||
|
_firstFrameSent = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -574,9 +574,6 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _needToReportFirstFrame = true;
|
bool _needToReportFirstFrame = true;
|
||||||
int _deferFirstFrameReportCount = 0;
|
|
||||||
bool get _reportFirstFrame => _deferFirstFrameReportCount == 0;
|
|
||||||
|
|
||||||
|
|
||||||
final Completer<void> _firstFrameCompleter = Completer<void>();
|
final Completer<void> _firstFrameCompleter = Completer<void>();
|
||||||
|
|
||||||
@ -602,11 +599,6 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||||||
|
|
||||||
/// Whether the first frame has finished building.
|
/// Whether the first frame has finished building.
|
||||||
///
|
///
|
||||||
/// Only useful in profile and debug builds; in release builds, this always
|
|
||||||
/// return false. This can be deferred using [deferFirstFrameReport] and
|
|
||||||
/// [allowFirstFrameReport]. The value is set at the end of the call to
|
|
||||||
/// [drawFrame].
|
|
||||||
///
|
|
||||||
/// This value can also be obtained over the VM service protocol as
|
/// This value can also be obtained over the VM service protocol as
|
||||||
/// `ext.flutter.didSendFirstFrameEvent`.
|
/// `ext.flutter.didSendFirstFrameEvent`.
|
||||||
///
|
///
|
||||||
@ -618,27 +610,30 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||||||
/// Tell the framework not to report the frame it is building as a "useful"
|
/// Tell the framework not to report the frame it is building as a "useful"
|
||||||
/// first frame until there is a corresponding call to [allowFirstFrameReport].
|
/// first frame until there is a corresponding call to [allowFirstFrameReport].
|
||||||
///
|
///
|
||||||
/// This is used by [WidgetsApp] to avoid reporting frames that aren't useful
|
/// Deprecated. Use [deferFirstFrame]/[allowFirstFrame] to delay rendering the
|
||||||
/// during startup as the "first frame".
|
/// first frame.
|
||||||
|
@Deprecated(
|
||||||
|
'Use deferFirstFrame/allowFirstFrame to delay rendering the first frame. '
|
||||||
|
'This feature was deprecated after v1.12.4.'
|
||||||
|
)
|
||||||
void deferFirstFrameReport() {
|
void deferFirstFrameReport() {
|
||||||
if (!kReleaseMode) {
|
if (!kReleaseMode) {
|
||||||
assert(_deferFirstFrameReportCount >= 0);
|
deferFirstFrame();
|
||||||
_deferFirstFrameReportCount += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When called after [deferFirstFrameReport]: tell the framework to report
|
/// When called after [deferFirstFrameReport]: tell the framework to report
|
||||||
/// the frame it is building as a "useful" first frame.
|
/// the frame it is building as a "useful" first frame.
|
||||||
///
|
///
|
||||||
/// This method may only be called once for each corresponding call
|
/// Deprecated. Use [deferFirstFrame]/[allowFirstFrame] to delay rendering the
|
||||||
/// to [deferFirstFrameReport].
|
/// first frame.
|
||||||
///
|
@Deprecated(
|
||||||
/// This is used by [WidgetsApp] to report when the first useful frame is
|
'Use deferFirstFrame/allowFirstFrame to delay rendering the first frame. '
|
||||||
/// painted.
|
'This feature was deprecated after v1.12.4.'
|
||||||
|
)
|
||||||
void allowFirstFrameReport() {
|
void allowFirstFrameReport() {
|
||||||
if (!kReleaseMode) {
|
if (!kReleaseMode) {
|
||||||
assert(_deferFirstFrameReportCount >= 1);
|
allowFirstFrame();
|
||||||
_deferFirstFrameReportCount -= 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -755,18 +750,23 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
|
|
||||||
if (_needToReportFirstFrame && _reportFirstFrame) {
|
TimingsCallback firstFrameCallback;
|
||||||
|
if (_needToReportFirstFrame) {
|
||||||
assert(!_firstFrameCompleter.isCompleted);
|
assert(!_firstFrameCompleter.isCompleted);
|
||||||
|
|
||||||
TimingsCallback firstFrameCallback;
|
|
||||||
firstFrameCallback = (List<FrameTiming> timings) {
|
firstFrameCallback = (List<FrameTiming> timings) {
|
||||||
|
assert(sendFramesToEngine);
|
||||||
if (!kReleaseMode) {
|
if (!kReleaseMode) {
|
||||||
developer.Timeline.instantSync('Rasterized first useful frame');
|
developer.Timeline.instantSync('Rasterized first useful frame');
|
||||||
developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
|
developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
|
||||||
}
|
}
|
||||||
SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback);
|
SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback);
|
||||||
|
firstFrameCallback = null;
|
||||||
_firstFrameCompleter.complete();
|
_firstFrameCompleter.complete();
|
||||||
};
|
};
|
||||||
|
// Callback is only invoked when [Window.render] is called. When
|
||||||
|
// [sendFramesToEngine] is set to false during the frame, it will not
|
||||||
|
// be called and we need to remove the callback (see below).
|
||||||
SchedulerBinding.instance.addTimingsCallback(firstFrameCallback);
|
SchedulerBinding.instance.addTimingsCallback(firstFrameCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -782,11 +782,14 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||||||
}());
|
}());
|
||||||
}
|
}
|
||||||
if (!kReleaseMode) {
|
if (!kReleaseMode) {
|
||||||
if (_needToReportFirstFrame && _reportFirstFrame) {
|
if (_needToReportFirstFrame && sendFramesToEngine) {
|
||||||
developer.Timeline.instantSync('Widgets built first useful frame');
|
developer.Timeline.instantSync('Widgets built first useful frame');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_needToReportFirstFrame = false;
|
_needToReportFirstFrame = false;
|
||||||
|
if (firstFrameCallback != null && !sendFramesToEngine) {
|
||||||
|
SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The [Element] that is at the root of the hierarchy (and which wraps the
|
/// The [Element] that is at the root of the hierarchy (and which wraps the
|
||||||
@ -834,12 +837,9 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
|
|
||||||
deferFirstFrameReport();
|
|
||||||
if (renderViewElement != null)
|
if (renderViewElement != null)
|
||||||
buildOwner.reassemble(renderViewElement);
|
buildOwner.reassemble(renderViewElement);
|
||||||
return super.performReassemble().then((void value) {
|
return super.performReassemble();
|
||||||
allowFirstFrameReport();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import 'dart:async';
|
|||||||
import 'dart:ui' show Locale;
|
import 'dart:ui' show Locale;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
import 'basic.dart';
|
import 'basic.dart';
|
||||||
import 'binding.dart';
|
import 'binding.dart';
|
||||||
@ -519,15 +520,15 @@ class _LocalizationsState extends State<Localizations> {
|
|||||||
// have finished loading. Until then the old locale will continue to be used.
|
// have finished loading. Until then the old locale will continue to be used.
|
||||||
// - If we're running at app startup time then defer reporting the first
|
// - If we're running at app startup time then defer reporting the first
|
||||||
// "useful" frame until after the async load has completed.
|
// "useful" frame until after the async load has completed.
|
||||||
WidgetsBinding.instance.deferFirstFrameReport();
|
RendererBinding.instance.deferFirstFrame();
|
||||||
typeToResourcesFuture.then<void>((Map<Type, dynamic> value) {
|
typeToResourcesFuture.then<void>((Map<Type, dynamic> value) {
|
||||||
WidgetsBinding.instance.allowFirstFrameReport();
|
if (mounted) {
|
||||||
if (!mounted)
|
|
||||||
return;
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_typeToResources = value;
|
_typeToResources = value;
|
||||||
_locale = locale;
|
_locale = locale;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
RendererBinding.instance.allowFirstFrame();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,120 @@
|
|||||||
|
// Copyright 2019 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 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
const String _actualContent = 'Actual Content';
|
||||||
|
const String _loading = 'Loading...';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('deferFirstFrame/allowFirstFrame stops sending frames to engine', (WidgetTester tester) async {
|
||||||
|
expect(RendererBinding.instance.sendFramesToEngine, isTrue);
|
||||||
|
|
||||||
|
final Completer<void> completer = Completer<void>();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: _DeferringWidget(
|
||||||
|
key: UniqueKey(),
|
||||||
|
loader: completer.future,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final _DeferringWidgetState state = tester.state<_DeferringWidgetState>(find.byType(_DeferringWidget));
|
||||||
|
|
||||||
|
expect(find.text(_loading), findsOneWidget);
|
||||||
|
expect(find.text(_actualContent), findsNothing);
|
||||||
|
expect(RendererBinding.instance.sendFramesToEngine, isFalse);
|
||||||
|
|
||||||
|
await tester.pump();
|
||||||
|
expect(find.text(_loading), findsOneWidget);
|
||||||
|
expect(find.text(_actualContent), findsNothing);
|
||||||
|
expect(RendererBinding.instance.sendFramesToEngine, isFalse);
|
||||||
|
expect(state.doneLoading, isFalse);
|
||||||
|
|
||||||
|
// Complete the future to start sending frames.
|
||||||
|
completer.complete();
|
||||||
|
await tester.idle();
|
||||||
|
expect(state.doneLoading, isTrue);
|
||||||
|
expect(RendererBinding.instance.sendFramesToEngine, isTrue);
|
||||||
|
|
||||||
|
await tester.pump();
|
||||||
|
expect(find.text(_loading), findsNothing);
|
||||||
|
expect(find.text(_actualContent), findsOneWidget);
|
||||||
|
expect(RendererBinding.instance.sendFramesToEngine, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Two widgets can defer frames', (WidgetTester tester) async {
|
||||||
|
expect(RendererBinding.instance.sendFramesToEngine, isTrue);
|
||||||
|
|
||||||
|
final Completer<void> completer1 = Completer<void>();
|
||||||
|
final Completer<void> completer2 = Completer<void>();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
_DeferringWidget(
|
||||||
|
key: UniqueKey(),
|
||||||
|
loader: completer1.future,
|
||||||
|
),
|
||||||
|
_DeferringWidget(
|
||||||
|
key: UniqueKey(),
|
||||||
|
loader: completer2.future,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(find.text(_loading), findsNWidgets(2));
|
||||||
|
expect(find.text(_actualContent), findsNothing);
|
||||||
|
expect(RendererBinding.instance.sendFramesToEngine, isFalse);
|
||||||
|
|
||||||
|
completer1.complete();
|
||||||
|
completer2.complete();
|
||||||
|
await tester.idle();
|
||||||
|
|
||||||
|
await tester.pump();
|
||||||
|
expect(find.text(_loading), findsNothing);
|
||||||
|
expect(find.text(_actualContent), findsNWidgets(2));
|
||||||
|
expect(RendererBinding.instance.sendFramesToEngine, isTrue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DeferringWidget extends StatefulWidget {
|
||||||
|
const _DeferringWidget({Key key, this.loader}) : super(key: key);
|
||||||
|
|
||||||
|
final Future<void> loader;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_DeferringWidget> createState() => _DeferringWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DeferringWidgetState extends State<_DeferringWidget> {
|
||||||
|
bool doneLoading = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
RendererBinding.instance.deferFirstFrame();
|
||||||
|
widget.loader.then((_) {
|
||||||
|
setState(() {
|
||||||
|
doneLoading = true;
|
||||||
|
RendererBinding.instance.allowFirstFrame();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return doneLoading
|
||||||
|
? const Text(_actualContent)
|
||||||
|
: const Text(_loading);
|
||||||
|
}
|
||||||
|
}
|
73
packages/flutter/test/widgets/localizations_test.dart
Normal file
73
packages/flutter/test/widgets/localizations_test.dart
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// Copyright 2013 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:async';
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final TestAutomatedTestWidgetsFlutterBinding binding = TestAutomatedTestWidgetsFlutterBinding();
|
||||||
|
|
||||||
|
testWidgets('Locale is available when Localizations widget stops defering frames', (WidgetTester tester) async {
|
||||||
|
final FakeLocalizationsDelegate delegate = FakeLocalizationsDelegate();
|
||||||
|
await tester.pumpWidget(Localizations(
|
||||||
|
locale: const Locale('fo'),
|
||||||
|
delegates: <LocalizationsDelegate<dynamic>>[
|
||||||
|
WidgetsLocalizationsDelegate(),
|
||||||
|
delegate,
|
||||||
|
],
|
||||||
|
child: const Text('loaded')
|
||||||
|
));
|
||||||
|
final dynamic state = tester.state(find.byType(Localizations));
|
||||||
|
expect(state.locale, isNull);
|
||||||
|
expect(find.text('loaded'), findsNothing);
|
||||||
|
|
||||||
|
Locale locale;
|
||||||
|
binding.onAllowFrame = () {
|
||||||
|
locale = state.locale;
|
||||||
|
};
|
||||||
|
delegate.completer.complete('foo');
|
||||||
|
await tester.idle();
|
||||||
|
expect(locale, const Locale('fo'));
|
||||||
|
await tester.pump();
|
||||||
|
expect(find.text('loaded'), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeLocalizationsDelegate extends LocalizationsDelegate<String> {
|
||||||
|
final Completer<String> completer = Completer<String>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isSupported(Locale locale) => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> load(Locale locale) => completer.future;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldReload(LocalizationsDelegate<String> old) => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestAutomatedTestWidgetsFlutterBinding extends AutomatedTestWidgetsFlutterBinding {
|
||||||
|
|
||||||
|
VoidCallback onAllowFrame;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void allowFirstFrame() {
|
||||||
|
if (onAllowFrame != null)
|
||||||
|
onAllowFrame();
|
||||||
|
super.allowFirstFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
|
||||||
|
@override
|
||||||
|
bool isSupported(Locale locale) => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<WidgetsLocalizations> load(Locale locale) => DefaultWidgetsLocalizations.load(locale);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldReload(WidgetsLocalizationsDelegate old) => false;
|
||||||
|
}
|
@ -687,6 +687,9 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
|
|||||||
|
|
||||||
runApp(Container(key: UniqueKey(), child: _preTestMessage)); // Reset the tree to a known state.
|
runApp(Container(key: UniqueKey(), child: _preTestMessage)); // Reset the tree to a known state.
|
||||||
await pump();
|
await pump();
|
||||||
|
// Pretend that the first frame produced in the test body is the first frame
|
||||||
|
// sent to the engine.
|
||||||
|
resetFirstFrameSent();
|
||||||
|
|
||||||
final bool autoUpdateGoldensBeforeTest = autoUpdateGoldenFiles && !isBrowser;
|
final bool autoUpdateGoldensBeforeTest = autoUpdateGoldenFiles && !isBrowser;
|
||||||
final TestExceptionReporter reportTestExceptionBeforeTest = reportTestException;
|
final TestExceptionReporter reportTestExceptionBeforeTest = reportTestException;
|
||||||
@ -963,6 +966,31 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int _firstFrameDeferredCount = 0;
|
||||||
|
bool _firstFrameSent = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get sendFramesToEngine => _firstFrameSent || _firstFrameDeferredCount == 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void deferFirstFrame() {
|
||||||
|
assert(_firstFrameDeferredCount >= 0);
|
||||||
|
_firstFrameDeferredCount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void allowFirstFrame() {
|
||||||
|
assert(_firstFrameDeferredCount > 0);
|
||||||
|
_firstFrameDeferredCount -= 1;
|
||||||
|
// Unlike in RendererBinding.allowFirstFrame we do not force a frame her
|
||||||
|
// to give the test full control over frame scheduling.
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void resetFirstFrameSent() {
|
||||||
|
_firstFrameSent = false;
|
||||||
|
}
|
||||||
|
|
||||||
EnginePhase _phase = EnginePhase.sendSemanticsUpdate;
|
EnginePhase _phase = EnginePhase.sendSemanticsUpdate;
|
||||||
|
|
||||||
// Cloned from RendererBinding.drawFrame() but with early-exit semantics.
|
// Cloned from RendererBinding.drawFrame() but with early-exit semantics.
|
||||||
@ -979,7 +1007,8 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
|
|||||||
pipelineOwner.flushCompositingBits();
|
pipelineOwner.flushCompositingBits();
|
||||||
if (_phase != EnginePhase.compositingBits) {
|
if (_phase != EnginePhase.compositingBits) {
|
||||||
pipelineOwner.flushPaint();
|
pipelineOwner.flushPaint();
|
||||||
if (_phase != EnginePhase.paint) {
|
if (_phase != EnginePhase.paint && sendFramesToEngine) {
|
||||||
|
_firstFrameSent = true;
|
||||||
renderView.compositeFrame(); // this sends the bits to the GPU
|
renderView.compositeFrame(); // this sends the bits to the GPU
|
||||||
if (_phase != EnginePhase.composite) {
|
if (_phase != EnginePhase.composite) {
|
||||||
pipelineOwner.flushSemantics();
|
pipelineOwner.flushSemantics();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user