Skip to content

Commit 8d01fe0

Browse files
authored
Add Semantics Property linkUrl (flutter#150639)
The new property allows the user to specify a URI for their semantics link node. On the web, it sets the `href` of the anchor element associated with semantics node. This is going to unlock better semantics support in the Link widget on web ([PR](flutter/packages#6711)). Engine counterpart: flutter/engine#53507 Fixes flutter#150263
1 parent 3bd2967 commit 8d01fe0

File tree

5 files changed

+64
-4
lines changed

5 files changed

+64
-4
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,9 @@ class RenderCustomPaint extends RenderProxyBox {
904904
if (properties.link != null) {
905905
config.isLink = properties.link!;
906906
}
907+
if (properties.linkUrl != null) {
908+
config.linkUrl = properties.linkUrl;
909+
}
907910
if (properties.textField != null) {
908911
config.isTextField = properties.textField!;
909912
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4324,6 +4324,9 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
43244324
if (_properties.link != null) {
43254325
config.isLink = _properties.link!;
43264326
}
4327+
if (_properties.linkUrl != null) {
4328+
config.linkUrl = _properties.linkUrl;
4329+
}
43274330
if (_properties.slider != null) {
43284331
config.isSlider = _properties.slider!;
43294332
}

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

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ class SemanticsData with Diagnosticable {
453453
required this.maxValueLength,
454454
required this.currentValueLength,
455455
required this.headingLevel,
456+
required this.linkUrl,
456457
this.tags,
457458
this.transform,
458459
this.customSemanticsActionIds,
@@ -462,7 +463,8 @@ class SemanticsData with Diagnosticable {
462463
assert(attributedDecreasedValue.string == '' || textDirection != null, 'A SemanticsData object with decreasedValue "${attributedDecreasedValue.string}" had a null textDirection.'),
463464
assert(attributedIncreasedValue.string == '' || textDirection != null, 'A SemanticsData object with increasedValue "${attributedIncreasedValue.string}" had a null textDirection.'),
464465
assert(attributedHint.string == '' || textDirection != null, 'A SemanticsData object with hint "${attributedHint.string}" had a null textDirection.'),
465-
assert(headingLevel >= 0 && headingLevel <= 6, 'Heading level must be between 0 and 6');
466+
assert(headingLevel >= 0 && headingLevel <= 6, 'Heading level must be between 0 and 6'),
467+
assert(linkUrl == null || (flags & SemanticsFlag.isLink.index) != 0, 'A SemanticsData object with a linkUrl must have the isLink flag set to true');
466468

467469
/// A bit field of [SemanticsFlag]s that apply to this node.
468470
final int flags;
@@ -643,6 +645,13 @@ class SemanticsData with Diagnosticable {
643645
/// be set when [maxValueLength] is set.
644646
final int? currentValueLength;
645647

648+
/// The URL that this node links to.
649+
///
650+
/// See also:
651+
///
652+
/// * [SemanticsFlag.isLink], which indicates that this node is a link.
653+
final Uri? linkUrl;
654+
646655
/// The bounding box for this node in its coordinate system.
647656
final Rect rect;
648657

@@ -734,6 +743,7 @@ class SemanticsData with Diagnosticable {
734743
properties.add(DoubleProperty('scrollPosition', scrollPosition, defaultValue: null));
735744
properties.add(DoubleProperty('scrollExtentMax', scrollExtentMax, defaultValue: null));
736745
properties.add(IntProperty('headingLevel', headingLevel, defaultValue: 0));
746+
properties.add(DiagnosticsProperty<Uri>('linkUrl', linkUrl, defaultValue: null));
737747
}
738748

739749
@override
@@ -764,6 +774,7 @@ class SemanticsData with Diagnosticable {
764774
&& other.elevation == elevation
765775
&& other.thickness == thickness
766776
&& other.headingLevel == headingLevel
777+
&& other.linkUrl == linkUrl
767778
&& _sortedListsEqual(other.customSemanticsActionIds, customSemanticsActionIds);
768779
}
769780

@@ -795,6 +806,7 @@ class SemanticsData with Diagnosticable {
795806
elevation,
796807
thickness,
797808
headingLevel,
809+
linkUrl,
798810
customSemanticsActionIds == null ? null : Object.hashAll(customSemanticsActionIds!),
799811
),
800812
);
@@ -908,6 +920,7 @@ class SemanticsProperties extends DiagnosticableTree {
908920
this.toggled,
909921
this.button,
910922
this.link,
923+
this.linkUrl,
911924
this.header,
912925
this.headingLevel,
913926
this.textField,
@@ -969,7 +982,8 @@ class SemanticsProperties extends DiagnosticableTree {
969982
assert(increasedValue == null || attributedIncreasedValue == null, 'Only one of increasedValue or attributedIncreasedValue should be provided'),
970983
assert(decreasedValue == null || attributedDecreasedValue == null, 'Only one of decreasedValue or attributedDecreasedValue should be provided'),
971984
assert(hint == null || attributedHint == null, 'Only one of hint or attributedHint should be provided'),
972-
assert(headingLevel == null || (headingLevel > 0 && headingLevel <= 6), 'Heading level must be between 1 and 6');
985+
assert(headingLevel == null || (headingLevel > 0 && headingLevel <= 6), 'Heading level must be between 1 and 6'),
986+
assert(linkUrl == null || (link ?? false), 'If linkUrl is set then link must be true');
973987

974988
/// If non-null, indicates that this subtree represents something that can be
975989
/// in an enabled or disabled state.
@@ -1432,6 +1446,15 @@ class SemanticsProperties extends DiagnosticableTree {
14321446
/// here will be passed.
14331447
final SemanticsTag? tagForChildren;
14341448

1449+
/// The URL that this node links to.
1450+
///
1451+
/// On the web, this is used to set the `href` attribute of the DOM element.
1452+
///
1453+
/// See also:
1454+
///
1455+
/// * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#href
1456+
final Uri? linkUrl;
1457+
14351458
/// The handler for [SemanticsAction.tap].
14361459
///
14371460
/// This is the semantic equivalent of a user briefly tapping the screen with
@@ -2267,7 +2290,8 @@ class SemanticsNode with DiagnosticableTreeMixin {
22672290
|| _currentValueLength != config._currentValueLength
22682291
|| _mergeAllDescendantsIntoThisNode != config.isMergingSemanticsOfDescendants
22692292
|| _areUserActionsBlocked != config.isBlockingUserActions
2270-
|| _headingLevel != config._headingLevel;
2293+
|| _headingLevel != config._headingLevel
2294+
|| _linkUrl != config._linkUrl;
22712295
}
22722296

22732297
// TAGS, LABELS, ACTIONS
@@ -2577,6 +2601,10 @@ class SemanticsNode with DiagnosticableTreeMixin {
25772601
int get headingLevel => _headingLevel;
25782602
int _headingLevel = _kEmptyConfig._headingLevel;
25792603

2604+
/// The URL that this node links to.
2605+
Uri? get linkUrl => _linkUrl;
2606+
Uri? _linkUrl = _kEmptyConfig._linkUrl;
2607+
25802608
bool _canPerformAction(SemanticsAction action) =>
25812609
_actions.containsKey(action);
25822610

@@ -2637,6 +2665,7 @@ class SemanticsNode with DiagnosticableTreeMixin {
26372665
_currentValueLength = config._currentValueLength;
26382666
_areUserActionsBlocked = config.isBlockingUserActions;
26392667
_headingLevel = config._headingLevel;
2668+
_linkUrl = config._linkUrl;
26402669
_replaceChildren(childrenInInversePaintOrder ?? const <SemanticsNode>[]);
26412670

26422671
if (mergeAllDescendantsIntoThisNodeValueChanged) {
@@ -2685,6 +2714,7 @@ class SemanticsNode with DiagnosticableTreeMixin {
26852714
int headingLevel = _headingLevel;
26862715
final double elevation = _elevation;
26872716
double thickness = _thickness;
2717+
Uri? linkUrl = _linkUrl;
26882718
final Set<int> customSemanticsActionIds = <int>{};
26892719
for (final CustomSemanticsAction action in _customSemanticsActions.keys) {
26902720
customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
@@ -2723,6 +2753,7 @@ class SemanticsNode with DiagnosticableTreeMixin {
27232753
maxValueLength ??= node._maxValueLength;
27242754
currentValueLength ??= node._currentValueLength;
27252755
headingLevel = node._headingLevel;
2756+
linkUrl ??= node._linkUrl;
27262757

27272758
if (identifier == '') {
27282759
identifier = node._identifier;
@@ -2808,6 +2839,7 @@ class SemanticsNode with DiagnosticableTreeMixin {
28082839
currentValueLength: currentValueLength,
28092840
customSemanticsActionIds: customSemanticsActionIds.toList()..sort(),
28102841
headingLevel: headingLevel,
2842+
linkUrl: linkUrl,
28112843
);
28122844
}
28132845

@@ -2884,6 +2916,7 @@ class SemanticsNode with DiagnosticableTreeMixin {
28842916
childrenInHitTestOrder: childrenInHitTestOrder,
28852917
additionalActions: customSemanticsActionIds ?? _kEmptyCustomSemanticsActionsList,
28862918
headingLevel: data.headingLevel,
2919+
linkUrl: data.linkUrl?.toString() ?? '',
28872920
);
28882921
_dirty = false;
28892922
}
@@ -4747,6 +4780,18 @@ class SemanticsConfiguration {
47474780
_setFlag(SemanticsFlag.isLink, value);
47484781
}
47494782

4783+
/// The URL that the owning [RenderObject] links to.
4784+
Uri? get linkUrl => _linkUrl;
4785+
Uri? _linkUrl;
4786+
4787+
set linkUrl(Uri? value) {
4788+
if (value == _linkUrl) {
4789+
return;
4790+
}
4791+
_linkUrl = value;
4792+
_hasBeenAnnotated = true;
4793+
}
4794+
47504795
/// Whether the owning [RenderObject] is a header (true) or not (false).
47514796
bool get isHeader => _hasFlag(SemanticsFlag.isHeader);
47524797
set isHeader(bool value) {
@@ -5102,7 +5147,8 @@ class SemanticsConfiguration {
51025147
.._actions.addAll(_actions)
51035148
.._customSemanticsActions.addAll(_customSemanticsActions)
51045149
..isBlockingUserActions = isBlockingUserActions
5105-
.._headingLevel = _headingLevel;
5150+
.._headingLevel = _headingLevel
5151+
.._linkUrl = _linkUrl;
51065152
}
51075153
}
51085154

packages/flutter/lib/src/widgets/basic.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7109,6 +7109,7 @@ class Semantics extends SingleChildRenderObjectWidget {
71097109
bool? slider,
71107110
bool? keyboardKey,
71117111
bool? link,
7112+
Uri? linkUrl,
71127113
bool? header,
71137114
int? headingLevel,
71147115
bool? textField,
@@ -7181,6 +7182,7 @@ class Semantics extends SingleChildRenderObjectWidget {
71817182
slider: slider,
71827183
keyboardKey: keyboardKey,
71837184
link: link,
7185+
linkUrl: linkUrl,
71847186
header: header,
71857187
headingLevel: headingLevel,
71867188
textField: textField,

packages/flutter_test/test/matchers_test.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,7 @@ void main() {
685685
currentValueLength: 10,
686686
maxValueLength: 15,
687687
headingLevel: 0,
688+
linkUrl: Uri(path: 'l'),
688689
);
689690
final _FakeSemanticsNode node = _FakeSemanticsNode(data);
690691

@@ -973,6 +974,7 @@ void main() {
973974
currentValueLength: 10,
974975
maxValueLength: 15,
975976
headingLevel: 0,
977+
linkUrl: Uri(path: 'l'),
976978
);
977979
final _FakeSemanticsNode node = _FakeSemanticsNode(data);
978980

@@ -1067,6 +1069,7 @@ void main() {
10671069
currentValueLength: 10,
10681070
maxValueLength: 15,
10691071
headingLevel: 0,
1072+
linkUrl: null,
10701073
);
10711074
final _FakeSemanticsNode node = _FakeSemanticsNode(data);
10721075

@@ -1168,6 +1171,7 @@ void main() {
11681171
currentValueLength: 10,
11691172
maxValueLength: 15,
11701173
headingLevel: 0,
1174+
linkUrl: null,
11711175
);
11721176
final _FakeSemanticsNode emptyNode = _FakeSemanticsNode(emptyData);
11731177

@@ -1197,6 +1201,7 @@ void main() {
11971201
maxValueLength: 15,
11981202
customSemanticsActionIds: <int>[CustomSemanticsAction.getIdentifier(action)],
11991203
headingLevel: 0,
1204+
linkUrl: Uri(path: 'l'),
12001205
);
12011206
final _FakeSemanticsNode fullNode = _FakeSemanticsNode(fullData);
12021207

@@ -1288,6 +1293,7 @@ void main() {
12881293
maxValueLength: 15,
12891294
customSemanticsActionIds: <int>[CustomSemanticsAction.getIdentifier(action)],
12901295
headingLevel: 0,
1296+
linkUrl: null,
12911297
);
12921298
final _FakeSemanticsNode node = _FakeSemanticsNode(data);
12931299

0 commit comments

Comments
 (0)