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
80ee3c1e
Unverified
Commit
80ee3c1e
authored
Jul 30, 2022
by
LongCatIsLooong
Committed by
GitHub
Jul 30, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
TextPainter throw with stack trace to help track down read-before-layout (#108571)
parent
eb2b0acc
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
139 additions
and
19 deletions
+139
-19
text_painter.dart
packages/flutter/lib/src/painting/text_painter.dart
+36
-15
paragraph.dart
packages/flutter/lib/src/rendering/paragraph.dart
+30
-2
system_fonts_test.dart
packages/flutter/test/painting/system_fonts_test.dart
+38
-2
text_painter_test.dart
packages/flutter/test/painting/text_painter_test.dart
+35
-0
No files found.
packages/flutter/lib/src/painting/text_painter.dart
View file @
80ee3c1e
...
...
@@ -206,7 +206,18 @@ class TextPainter {
// rebuilt before painting.
bool
_rebuildParagraphForPaint
=
true
;
bool
get
_debugNeedsLayout
=>
_paragraph
==
null
;
bool
get
_debugAssertTextLayoutIsValid
{
if
(
_paragraph
==
null
)
{
throw
FlutterError
.
fromParts
(<
DiagnosticsNode
>[
ErrorSummary
(
'Text layout not available'
),
if
(
_debugMarkNeedsLayoutCallStack
!=
null
)
DiagnosticsStackTrace
(
'The calls that first invalidated the text layout were'
,
_debugMarkNeedsLayoutCallStack
)
else
ErrorDescription
(
'The TextPainter has never been laid out.'
)
]);
}
return
true
;
}
StackTrace
?
_debugMarkNeedsLayoutCallStack
;
/// Marks this text painter's layout information as dirty and removes cached
/// information.
...
...
@@ -215,6 +226,12 @@ class TextPainter {
/// layout changes in engine. In most cases, updating text painter properties
/// in framework will automatically invoke this method.
void
markNeedsLayout
()
{
assert
(()
{
if
(
_paragraph
!=
null
)
{
_debugMarkNeedsLayoutCallStack
??=
StackTrace
.
current
;
}
return
true
;
}());
_paragraph
=
null
;
_lineMetricsCache
=
null
;
_previousCaretPosition
=
null
;
...
...
@@ -540,7 +557,7 @@ class TextPainter {
///
/// Valid only after [layout] has been called.
double
get
minIntrinsicWidth
{
assert
(
!
_debugNeedsLayout
);
assert
(
_debugAssertTextLayoutIsValid
);
return
_applyFloatingPointHack
(
_paragraph
!.
minIntrinsicWidth
);
}
...
...
@@ -548,7 +565,7 @@ class TextPainter {
///
/// Valid only after [layout] has been called.
double
get
maxIntrinsicWidth
{
assert
(
!
_debugNeedsLayout
);
assert
(
_debugAssertTextLayoutIsValid
);
return
_applyFloatingPointHack
(
_paragraph
!.
maxIntrinsicWidth
);
}
...
...
@@ -556,7 +573,7 @@ class TextPainter {
///
/// Valid only after [layout] has been called.
double
get
width
{
assert
(
!
_debugNeedsLayout
);
assert
(
_debugAssertTextLayoutIsValid
);
return
_applyFloatingPointHack
(
textWidthBasis
==
TextWidthBasis
.
longestLine
?
_paragraph
!.
longestLine
:
_paragraph
!.
width
,
);
...
...
@@ -566,7 +583,7 @@ class TextPainter {
///
/// Valid only after [layout] has been called.
double
get
height
{
assert
(
!
_debugNeedsLayout
);
assert
(
_debugAssertTextLayoutIsValid
);
return
_applyFloatingPointHack
(
_paragraph
!.
height
);
}
...
...
@@ -574,7 +591,7 @@ class TextPainter {
///
/// Valid only after [layout] has been called.
Size
get
size
{
assert
(
!
_debugNeedsLayout
);
assert
(
_debugAssertTextLayoutIsValid
);
return
Size
(
width
,
height
);
}
...
...
@@ -583,7 +600,7 @@ class TextPainter {
///
/// Valid only after [layout] has been called.
double
computeDistanceToActualBaseline
(
TextBaseline
baseline
)
{
assert
(
!
_debugNeedsLayout
);
assert
(
_debugAssertTextLayoutIsValid
);
assert
(
baseline
!=
null
);
switch
(
baseline
)
{
case
TextBaseline
.
alphabetic
:
...
...
@@ -605,7 +622,7 @@ class TextPainter {
///
/// Valid only after [layout] has been called.
bool
get
didExceedMaxLines
{
assert
(
!
_debugNeedsLayout
);
assert
(
_debugAssertTextLayoutIsValid
);
return
_paragraph
!.
didExceedMaxLines
;
}
...
...
@@ -623,6 +640,10 @@ class TextPainter {
final
ui
.
ParagraphBuilder
builder
=
ui
.
ParagraphBuilder
(
_createParagraphStyle
());
text
.
build
(
builder
,
textScaleFactor:
textScaleFactor
,
dimensions:
_placeholderDimensions
);
_inlinePlaceholderScales
=
builder
.
placeholderScales
;
assert
(()
{
_debugMarkNeedsLayoutCallStack
=
null
;
return
true
;
}());
_paragraph
=
builder
.
build
();
_rebuildParagraphForPaint
=
false
;
}
...
...
@@ -859,7 +880,7 @@ class TextPainter {
}
Offset
get
_emptyOffset
{
assert
(
!
_debugNeedsLayout
);
// implies textDirection is non-null
assert
(
_debugAssertTextLayoutIsValid
);
// implies textDirection is non-null
assert
(
textAlign
!=
null
);
switch
(
textAlign
)
{
case
TextAlign
.
left
:
...
...
@@ -920,7 +941,7 @@ class TextPainter {
// Checks if the [position] and [caretPrototype] have changed from the cached
// version and recomputes the metrics required to position the caret.
void
_computeCaretMetrics
(
TextPosition
position
,
Rect
caretPrototype
)
{
assert
(
!
_debugNeedsLayout
);
assert
(
_debugAssertTextLayoutIsValid
);
if
(
position
==
_previousCaretPosition
&&
caretPrototype
==
_previousCaretPrototype
)
{
return
;
}
...
...
@@ -969,7 +990,7 @@ class TextPainter {
ui
.
BoxHeightStyle
boxHeightStyle
=
ui
.
BoxHeightStyle
.
tight
,
ui
.
BoxWidthStyle
boxWidthStyle
=
ui
.
BoxWidthStyle
.
tight
,
})
{
assert
(
!
_debugNeedsLayout
);
assert
(
_debugAssertTextLayoutIsValid
);
assert
(
boxHeightStyle
!=
null
);
assert
(
boxWidthStyle
!=
null
);
return
_paragraph
!.
getBoxesForRange
(
...
...
@@ -982,7 +1003,7 @@ class TextPainter {
/// Returns the position within the text for the given pixel offset.
TextPosition
getPositionForOffset
(
Offset
offset
)
{
assert
(
!
_debugNeedsLayout
);
assert
(
_debugAssertTextLayoutIsValid
);
return
_paragraph
!.
getPositionForOffset
(
offset
);
}
...
...
@@ -996,7 +1017,7 @@ class TextPainter {
/// <http://www.unicode.org/reports/tr29/#Word_Boundaries>.
/// {@endtemplate}
TextRange
getWordBoundary
(
TextPosition
position
)
{
assert
(
!
_debugNeedsLayout
);
assert
(
_debugAssertTextLayoutIsValid
);
return
_paragraph
!.
getWordBoundary
(
position
);
}
...
...
@@ -1004,7 +1025,7 @@ class TextPainter {
///
/// The newline (if any) is not returned as part of the range.
TextRange
getLineBoundary
(
TextPosition
position
)
{
assert
(
!
_debugNeedsLayout
);
assert
(
_debugAssertTextLayoutIsValid
);
return
_paragraph
!.
getLineBoundary
(
position
);
}
...
...
@@ -1021,7 +1042,7 @@ class TextPainter {
///
/// Valid only after [layout] has been called.
List
<
ui
.
LineMetrics
>
computeLineMetrics
()
{
assert
(
!
_debugNeedsLayout
);
assert
(
_debugAssertTextLayoutIsValid
);
return
_lineMetricsCache
??=
_paragraph
!.
computeLineMetrics
();
}
}
packages/flutter/lib/src/rendering/paragraph.dart
View file @
80ee3c1e
...
...
@@ -8,6 +8,7 @@ import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle, Gradient, Placeholder
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/scheduler.dart'
;
import
'package:flutter/semantics.dart'
;
import
'box.dart'
;
...
...
@@ -640,10 +641,37 @@ class RenderParagraph extends RenderBox
);
}
bool
_systemFontsChangeScheduled
=
false
;
@override
void
systemFontsDidChange
()
{
super
.
systemFontsDidChange
();
_textPainter
.
markNeedsLayout
();
final
SchedulerPhase
phase
=
SchedulerBinding
.
instance
.
schedulerPhase
;
switch
(
phase
)
{
case
SchedulerPhase
.
idle
:
case
SchedulerPhase
.
postFrameCallbacks
:
if
(
_systemFontsChangeScheduled
)
{
return
;
}
_systemFontsChangeScheduled
=
true
;
SchedulerBinding
.
instance
.
scheduleFrameCallback
((
Duration
timeStamp
)
{
assert
(
_systemFontsChangeScheduled
);
_systemFontsChangeScheduled
=
false
;
assert
(
attached
||
(
debugDisposed
??
true
),
'
$this
is detached during
$phase
but not disposed.'
,
);
if
(
attached
)
{
super
.
systemFontsDidChange
();
_textPainter
.
markNeedsLayout
();
}
});
break
;
case
SchedulerPhase
.
transientCallbacks
:
case
SchedulerPhase
.
midFrameMicrotasks
:
case
SchedulerPhase
.
persistentCallbacks
:
super
.
systemFontsDidChange
();
_textPainter
.
markNeedsLayout
();
break
;
}
}
// Placeholder dimensions representing the sizes of child inline widgets.
...
...
packages/flutter/test/painting/system_fonts_test.dart
View file @
80ee3c1e
...
...
@@ -2,9 +2,12 @@
// 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/cupertino.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
...
...
@@ -15,6 +18,8 @@ void main() {
home:
Text
(
'text widget'
),
),
);
final
RenderObject
renderObject
=
tester
.
renderObject
(
find
.
text
(
'text widget'
));
const
Map
<
String
,
dynamic
>
data
=
<
String
,
dynamic
>{
'type'
:
'fontsChange'
,
};
...
...
@@ -23,8 +28,39 @@ void main() {
SystemChannels
.
system
.
codec
.
encodeMessage
(
data
),
(
ByteData
?
data
)
{
},
);
final
RenderObject
renderObject
=
tester
.
renderObject
(
find
.
text
(
'text widget'
));
expect
(
renderObject
.
debugNeedsLayout
,
isTrue
);
final
Completer
<
bool
>
animation
=
Completer
<
bool
>();
tester
.
binding
.
scheduleFrameCallback
((
Duration
timeStamp
)
{
animation
.
complete
(
renderObject
.
debugNeedsLayout
);
});
expect
(
renderObject
.
debugNeedsLayout
,
isFalse
);
await
tester
.
pump
();
expect
(
await
animation
.
future
,
isTrue
);
});
testWidgets
(
'Safe to query RenderParagraph for text layout after system fonts changes'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
MaterialApp
(
home:
Text
(
'text widget'
),
),
);
const
Map
<
String
,
dynamic
>
data
=
<
String
,
dynamic
>{
'type'
:
'fontsChange'
,
};
await
ServicesBinding
.
instance
.
defaultBinaryMessenger
.
handlePlatformMessage
(
'flutter/system'
,
SystemChannels
.
system
.
codec
.
encodeMessage
(
data
),
(
ByteData
?
data
)
{
},
);
final
RenderParagraph
paragraph
=
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
'text widget'
));
Object
?
exception
;
try
{
paragraph
.
getPositionForOffset
(
Offset
.
zero
);
paragraph
.
hitTest
(
BoxHitTestResult
(),
position:
Offset
.
zero
);
}
catch
(
e
)
{
exception
=
e
;
}
expect
(
exception
,
isNull
);
});
testWidgets
(
'RenderEditable relayout upon system fonts changes'
,
(
WidgetTester
tester
)
async
{
...
...
packages/flutter/test/painting/text_painter_test.dart
View file @
80ee3c1e
...
...
@@ -1034,6 +1034,41 @@ void main() {
lines
=
painter
.
computeLineMetrics
();
expect
(
lines
.
length
,
1
);
},
skip:
kIsWeb
&&
!
isCanvasKit
);
// https://github.com/flutter/flutter/issues/62819
test
(
'TextPainter throws with stack trace when accessing text layout'
,
()
{
final
TextPainter
painter
=
TextPainter
()
..
text
=
const
TextSpan
(
text:
'TEXT'
)
..
textDirection
=
TextDirection
.
ltr
;
FlutterError
?
exception
;
try
{
painter
.
getPositionForOffset
(
Offset
.
zero
);
}
on
FlutterError
catch
(
e
)
{
exception
=
e
;
}
expect
(
exception
?.
message
,
contains
(
'The TextPainter has never been laid out.'
));
exception
=
null
;
try
{
painter
.
layout
();
painter
.
getPositionForOffset
(
Offset
.
zero
);
}
on
FlutterError
catch
(
e
)
{
exception
=
e
;
}
expect
(
exception
,
isNull
);
exception
=
null
;
try
{
painter
.
markNeedsLayout
();
painter
.
getPositionForOffset
(
Offset
.
zero
);
}
on
FlutterError
catch
(
e
)
{
exception
=
e
;
}
expect
(
exception
?.
message
,
contains
(
'The calls that first invalidated the text layout were:'
));
exception
=
null
;
});
}
class
MockCanvas
extends
Fake
implements
Canvas
{
...
...
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