Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
1ac17c14
Commit
1ac17c14
authored
Dec 04, 2019
by
Michael Goderbauer
Committed by
Flutter GitHub Bot
Dec 04, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Re-land "Add option to delay rendering the first frame (#45135)" (#45941)
parent
1b05cb2b
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
319 additions
and
37 deletions
+319
-37
binding.dart
packages/flutter/lib/src/rendering/binding.dart
+61
-2
binding.dart
packages/flutter/lib/src/widgets/binding.dart
+26
-26
localizations.dart
packages/flutter/lib/src/widgets/localizations.dart
+9
-8
binding_deferred_first_frame_test.dart
...utter/test/widgets/binding_deferred_first_frame_test.dart
+120
-0
localizations_test.dart
packages/flutter/test/widgets/localizations_test.dart
+73
-0
binding.dart
packages/flutter_test/lib/src/binding.dart
+30
-1
No files found.
packages/flutter/lib/src/rendering/binding.dart
View file @
1ac17c14
...
...
@@ -284,6 +284,62 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
_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 sent to the engine to actually appear on screen.
///
/// See also:
///
/// * [deferFirstFrame], which defers when the first frame is sent 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 initialization 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
/// sent to the engine and will not appear on screen.
///
/// Calling this has no effect after the first frame has been sent 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.
///
/// This method is called by [handleDrawFrame], which itself is called
...
...
@@ -345,8 +401,11 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
pipelineOwner
.
flushLayout
();
pipelineOwner
.
flushCompositingBits
();
pipelineOwner
.
flushPaint
();
renderView
.
compositeFrame
();
// this sends the bits to the GPU
pipelineOwner
.
flushSemantics
();
// this also sends the semantics to the OS.
if
(
sendFramesToEngine
)
{
renderView
.
compositeFrame
();
// this sends the bits to the GPU
pipelineOwner
.
flushSemantics
();
// this also sends the semantics to the OS.
_firstFrameSent
=
true
;
}
}
@override
...
...
packages/flutter/lib/src/widgets/binding.dart
View file @
1ac17c14
...
...
@@ -574,9 +574,6 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
}
bool
_needToReportFirstFrame
=
true
;
int
_deferFirstFrameReportCount
=
0
;
bool
get
_reportFirstFrame
=>
_deferFirstFrameReportCount
==
0
;
final
Completer
<
void
>
_firstFrameCompleter
=
Completer
<
void
>();
...
...
@@ -602,11 +599,6 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
/// 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
/// `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"
/// first frame until there is a corresponding call to [allowFirstFrameReport].
///
/// This is used by [WidgetsApp] to avoid reporting frames that aren't useful
/// during startup as the "first frame".
/// Deprecated. Use [deferFirstFrame]/[allowFirstFrame] to delay rendering the
/// first frame.
@Deprecated
(
'Use deferFirstFrame/allowFirstFrame to delay rendering the first frame. '
'This feature was deprecated after v1.12.4.'
)
void
deferFirstFrameReport
()
{
if
(!
kReleaseMode
)
{
assert
(
_deferFirstFrameReportCount
>=
0
);
_deferFirstFrameReportCount
+=
1
;
deferFirstFrame
();
}
}
/// When called after [deferFirstFrameReport]: tell the framework to report
/// the frame it is building as a "useful" first frame.
///
/// This method may only be called once for each corresponding call
/// to [deferFirstFrameReport].
///
/// This is used by [WidgetsApp] to report when the first useful frame is
/// painted.
/// Deprecated. Use [deferFirstFrame]/[allowFirstFrame] to delay rendering the
/// first frame.
@Deprecated
(
'Use deferFirstFrame/allowFirstFrame to delay rendering the first frame. '
'This feature was deprecated after v1.12.4.'
)
void
allowFirstFrameReport
()
{
if
(!
kReleaseMode
)
{
assert
(
_deferFirstFrameReportCount
>=
1
);
_deferFirstFrameReportCount
-=
1
;
allowFirstFrame
();
}
}
...
...
@@ -755,18 +750,23 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
return
true
;
}());
if
(
_needToReportFirstFrame
&&
_reportFirstFrame
)
{
TimingsCallback
firstFrameCallback
;
if
(
_needToReportFirstFrame
)
{
assert
(!
_firstFrameCompleter
.
isCompleted
);
TimingsCallback
firstFrameCallback
;
firstFrameCallback
=
(
List
<
FrameTiming
>
timings
)
{
assert
(
sendFramesToEngine
);
if
(!
kReleaseMode
)
{
developer
.
Timeline
.
instantSync
(
'Rasterized first useful frame'
);
developer
.
postEvent
(
'Flutter.FirstFrame'
,
<
String
,
dynamic
>{});
}
SchedulerBinding
.
instance
.
removeTimingsCallback
(
firstFrameCallback
);
firstFrameCallback
=
null
;
_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
);
}
...
...
@@ -782,11 +782,14 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
}());
}
if
(!
kReleaseMode
)
{
if
(
_needToReportFirstFrame
&&
_reportFirstFram
e
)
{
if
(
_needToReportFirstFrame
&&
sendFramesToEngin
e
)
{
developer
.
Timeline
.
instantSync
(
'Widgets built first useful frame'
);
}
}
_needToReportFirstFrame
=
false
;
if
(
firstFrameCallback
!=
null
&&
!
sendFramesToEngine
)
{
SchedulerBinding
.
instance
.
removeTimingsCallback
(
firstFrameCallback
);
}
}
/// 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
;
}());
deferFirstFrameReport
();
if
(
renderViewElement
!=
null
)
buildOwner
.
reassemble
(
renderViewElement
);
return
super
.
performReassemble
().
then
((
void
value
)
{
allowFirstFrameReport
();
});
return
super
.
performReassemble
();
}
}
...
...
packages/flutter/lib/src/widgets/localizations.dart
View file @
1ac17c14
...
...
@@ -6,6 +6,7 @@ import 'dart:async';
import
'dart:ui'
show
Locale
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'basic.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.
// - If we're running at app startup time then defer reporting the first
// "useful" frame until after the async load has completed.
WidgetsBinding
.
instance
.
deferFirstFrameReport
();
RendererBinding
.
instance
.
deferFirstFrame
();
typeToResourcesFuture
.
then
<
void
>((
Map
<
Type
,
dynamic
>
value
)
{
WidgetsBinding
.
instance
.
allowFirstFrameReport
();
if
(!
mounted
)
return
;
setState
(()
{
_typeToResources
=
value
;
_locale
=
locale
;
}
);
if
(
mounted
)
{
setState
(()
{
_typeToResources
=
value
;
_locale
=
locale
;
})
;
}
RendererBinding
.
instance
.
allowFirstFrame
(
);
});
}
}
...
...
packages/flutter/test/widgets/binding_deferred_first_frame_test.dart
0 → 100644
View file @
1ac17c14
// 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: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
);
}
}
packages/flutter/test/widgets/localizations_test.dart
0 → 100644
View file @
1ac17c14
// 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: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
;
}
packages/flutter_test/lib/src/binding.dart
View file @
1ac17c14
...
...
@@ -687,6 +687,9 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
runApp
(
Container
(
key:
UniqueKey
(),
child:
_preTestMessage
));
// Reset the tree to a known state.
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
TestExceptionReporter
reportTestExceptionBeforeTest
=
reportTestException
;
...
...
@@ -963,6 +966,31 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
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
;
// Cloned from RendererBinding.drawFrame() but with early-exit semantics.
...
...
@@ -979,7 +1007,8 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
pipelineOwner
.
flushCompositingBits
();
if
(
_phase
!=
EnginePhase
.
compositingBits
)
{
pipelineOwner
.
flushPaint
();
if
(
_phase
!=
EnginePhase
.
paint
)
{
if
(
_phase
!=
EnginePhase
.
paint
&&
sendFramesToEngine
)
{
_firstFrameSent
=
true
;
renderView
.
compositeFrame
();
// this sends the bits to the GPU
if
(
_phase
!=
EnginePhase
.
composite
)
{
pipelineOwner
.
flushSemantics
();
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment