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
c59151b1
Commit
c59151b1
authored
Dec 02, 2019
by
Michael Goderbauer
Committed by
Kate Lovett
Dec 02, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Re-land "Add option to delay rendering the first frame (#45135)" (#45588)
parent
088fa244
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 @
c59151b1
...
...
@@ -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 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.
///
/// 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 @
c59151b1
...
...
@@ -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 @
c59151b1
...
...
@@ -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 @
c59151b1
// 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
);
}
}
packages/flutter/test/widgets/localizations_test.dart
0 → 100644
View file @
c59151b1
// 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
;
}
packages/flutter_test/lib/src/binding.dart
View file @
c59151b1
...
...
@@ -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