Skip to content

Commit 71b9d46

Browse files
authored
Fix inline text span semantics (flutter#34434)
1 parent 300e8f8 commit 71b9d46

File tree

3 files changed

+68
-6
lines changed

3 files changed

+68
-6
lines changed

packages/flutter/lib/src/rendering/paragraph.dart

+26-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:math' as math;
56
import 'dart:ui' as ui show Gradient, Shader, TextBox, PlaceholderAlignment;
67

78
import 'package:flutter/foundation.dart';
@@ -766,12 +767,23 @@ class RenderParagraph extends RenderBox
766767
final TextDirection initialDirection = currentDirection;
767768
final TextSelection selection = TextSelection(baseOffset: start, extentOffset: end);
768769
final List<ui.TextBox> rects = getBoxesForSelection(selection);
769-
Rect rect;
770-
for (ui.TextBox textBox in rects) {
771-
rect ??= textBox.toRect();
770+
if (rects.isEmpty) {
771+
return null;
772+
}
773+
Rect rect = rects.first.toRect();
774+
currentDirection = rects.first.direction;
775+
for (ui.TextBox textBox in rects.skip(1)) {
772776
rect = rect.expandToInclude(textBox.toRect());
773777
currentDirection = textBox.direction;
774778
}
779+
// Any of the text boxes may have had infinite dimensions.
780+
// We shouldn't pass infinite dimensions up to the bridges.
781+
rect = Rect.fromLTWH(
782+
math.max(0.0, rect.left),
783+
math.max(0.0, rect.top),
784+
math.min(rect.width, constraints.maxWidth),
785+
math.min(rect.height, constraints.maxHeight),
786+
);
775787
// round the current rectangle to make this API testable and add some
776788
// padding so that the accessibility rects do not overlap with the text.
777789
// TODO(jonahwilliams): implement this for all text accessibility rects.
@@ -798,12 +810,18 @@ class RenderParagraph extends RenderBox
798810
if (current != start) {
799811
final SemanticsNode node = SemanticsNode();
800812
final SemanticsConfiguration configuration = buildSemanticsConfig(current, start);
813+
if (configuration == null) {
814+
continue;
815+
}
801816
node.updateWith(config: configuration);
802817
node.rect = currentRect;
803818
newChildren.add(node);
804819
}
805820
final dynamic inlineElement = _inlineSemanticsElements[j];
806821
final SemanticsConfiguration configuration = buildSemanticsConfig(start, end);
822+
if (configuration == null) {
823+
continue;
824+
}
807825
if (inlineElement != null) {
808826
// Add semantics for this recognizer.
809827
final SemanticsNode node = SemanticsNode();
@@ -842,9 +860,11 @@ class RenderParagraph extends RenderBox
842860
if (current < rawLabel.length) {
843861
final SemanticsNode node = SemanticsNode();
844862
final SemanticsConfiguration configuration = buildSemanticsConfig(current, rawLabel.length);
845-
node.updateWith(config: configuration);
846-
node.rect = currentRect;
847-
newChildren.add(node);
863+
if (configuration != null) {
864+
node.updateWith(config: configuration);
865+
node.rect = currentRect;
866+
newChildren.add(node);
867+
}
848868
}
849869
node.updateWith(config: config, childrenInInversePaintOrder: newChildren);
850870
}

packages/flutter/lib/src/semantics/semantics.dart

+3
Original file line numberDiff line numberDiff line change
@@ -1162,6 +1162,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
11621162
Rect _rect = Rect.zero;
11631163
set rect(Rect value) {
11641164
assert(value != null);
1165+
assert(value.isFinite, '$this (with $owner) tried to set a non-finite rect.');
11651166
if (_rect != value) {
11661167
_rect = value;
11671168
_markDirty();
@@ -2178,6 +2179,7 @@ class _BoxEdge implements Comparable<_BoxEdge> {
21782179
@required this.node,
21792180
}) : assert(isLeadingEdge != null),
21802181
assert(offset != null),
2182+
assert(offset.isFinite),
21812183
assert(node != null);
21822184

21832185
/// True if the edge comes before the seconds edge along the traversal
@@ -2384,6 +2386,7 @@ Offset _pointInParentCoordinates(SemanticsNode node, Offset point) {
23842386
List<SemanticsNode> _childrenInDefaultOrder(List<SemanticsNode> children, TextDirection textDirection) {
23852387
final List<_BoxEdge> edges = <_BoxEdge>[];
23862388
for (SemanticsNode child in children) {
2389+
assert(child.rect.isFinite);
23872390
// Using a small delta to shrink child rects removes overlapping cases.
23882391
final Rect childRect = child.rect.deflate(0.1);
23892392
edges.add(_BoxEdge(

packages/flutter/test/widgets/text_test.dart

+39
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,45 @@ void main() {
185185
semantics.dispose();
186186
});
187187

188+
testWidgets('recognizers split semantic node when TextSpan overflows', (WidgetTester tester) async {
189+
final SemanticsTester semantics = SemanticsTester(tester);
190+
const TextStyle textStyle = TextStyle(fontFamily: 'Ahem');
191+
await tester.pumpWidget(
192+
SizedBox(
193+
height: 10,
194+
child: Text.rich(
195+
TextSpan(
196+
children: <TextSpan>[
197+
const TextSpan(text: '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n'),
198+
TextSpan(text: 'world', recognizer: TapGestureRecognizer()..onTap = () { }),
199+
],
200+
style: textStyle,
201+
),
202+
textDirection: TextDirection.ltr,
203+
),
204+
),
205+
);
206+
final TestSemantics expectedSemantics = TestSemantics.root(
207+
children: <TestSemantics>[
208+
TestSemantics.rootChild(
209+
children: <TestSemantics>[
210+
TestSemantics(
211+
label: '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n',
212+
textDirection: TextDirection.ltr,
213+
),
214+
TestSemantics(
215+
label: 'world',
216+
textDirection: TextDirection.ltr,
217+
actions: <SemanticsAction>[SemanticsAction.tap]
218+
),
219+
],
220+
),
221+
],
222+
);
223+
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreId: true, ignoreRect: true));
224+
semantics.dispose();
225+
});
226+
188227
testWidgets('recognizers split semantic nodes with text span labels', (WidgetTester tester) async {
189228
final SemanticsTester semantics = SemanticsTester(tester);
190229
const TextStyle textStyle = TextStyle(fontFamily: 'Ahem');

0 commit comments

Comments
 (0)