Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 33bb6ef

Browse files
chunhtaiharryterkelsen
authored andcommitted
Add link support in web accessibility (#46117)
fixes flutter/flutter#134795 ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide] and the [C++, Objective-C, Java style guides]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I added new tests to check the change I am making or feature I am adding, or the PR is [test-exempt]. See [testing the engine] for instructions on writing and running engine tests. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I signed the [CLA]. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style [testing the engine]: https://github.com/flutter/flutter/wiki/Testing-the-engine [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat
1 parent 217f6bd commit 33bb6ef

File tree

17 files changed

+260
-121
lines changed

17 files changed

+260
-121
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2721,6 +2721,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/focusable.dart + ..
27212721
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/image.dart + ../../../flutter/LICENSE
27222722
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/incrementable.dart + ../../../flutter/LICENSE
27232723
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/label_and_value.dart + ../../../flutter/LICENSE
2724+
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/link.dart + ../../../flutter/LICENSE
27242725
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/live_region.dart + ../../../flutter/LICENSE
27252726
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/platform_view.dart + ../../../flutter/LICENSE
27262727
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart + ../../../flutter/LICENSE
@@ -5498,6 +5499,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/focusable.dart
54985499
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/image.dart
54995500
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/incrementable.dart
55005501
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/label_and_value.dart
5502+
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/link.dart
55015503
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/live_region.dart
55025504
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/platform_view.dart
55035505
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart

lib/web_ui/lib/src/engine.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ export 'engine/semantics/focusable.dart';
144144
export 'engine/semantics/image.dart';
145145
export 'engine/semantics/incrementable.dart';
146146
export 'engine/semantics/label_and_value.dart';
147+
export 'engine/semantics/link.dart';
147148
export 'engine/semantics/live_region.dart';
148149
export 'engine/semantics/platform_view.dart';
149150
export 'engine/semantics/scrollable.dart';

lib/web_ui/lib/src/engine/semantics.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export 'semantics/focusable.dart';
88
export 'semantics/image.dart';
99
export 'semantics/incrementable.dart';
1010
export 'semantics/label_and_value.dart';
11+
export 'semantics/link.dart';
1112
export 'semantics/live_region.dart';
1213
export 'semantics/platform_view.dart';
1314
export 'semantics/scrollable.dart';

lib/web_ui/lib/src/engine/semantics/checkable.dart

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
import 'package:ui/ui.dart' as ui;
1515

16-
import '../dom.dart';
1716
import 'semantics.dart';
1817

1918
/// The specific type of checkable control.
@@ -63,18 +62,18 @@ class Checkable extends PrimaryRoleManager {
6362
if (semanticsObject.isFlagsDirty) {
6463
switch (_kind) {
6564
case _CheckableKind.checkbox:
66-
semanticsObject.setAriaRole('checkbox');
65+
setAriaRole('checkbox');
6766
case _CheckableKind.radio:
68-
semanticsObject.setAriaRole('radio');
67+
setAriaRole('radio');
6968
case _CheckableKind.toggle:
70-
semanticsObject.setAriaRole('switch');
69+
setAriaRole('switch');
7170
}
7271

7372
/// Adding disabled and aria-disabled attribute to notify the assistive
7473
/// technologies of disabled elements.
7574
_updateDisabledAttribute();
7675

77-
semanticsObject.element.setAttribute(
76+
setAttribute(
7877
'aria-checked',
7978
(semanticsObject.hasFlag(ui.SemanticsFlag.isChecked) ||
8079
semanticsObject.hasFlag(ui.SemanticsFlag.isToggled))
@@ -92,17 +91,15 @@ class Checkable extends PrimaryRoleManager {
9291

9392
void _updateDisabledAttribute() {
9493
if (semanticsObject.enabledState() == EnabledState.disabled) {
95-
final DomElement element = semanticsObject.element;
96-
element
97-
..setAttribute('aria-disabled', 'true')
98-
..setAttribute('disabled', 'true');
94+
setAttribute('aria-disabled', 'true');
95+
setAttribute('disabled', 'true');
9996
} else {
10097
_removeDisabledAttribute();
10198
}
10299
}
103100

104101
void _removeDisabledAttribute() {
105-
final DomElement element = semanticsObject.element;
106-
element..removeAttribute('aria-disabled')..removeAttribute('disabled');
102+
removeAttribute('aria-disabled');
103+
removeAttribute('disabled');
107104
}
108105
}

lib/web_ui/lib/src/engine/semantics/dialog.dart

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ class Dialog extends PrimaryRoleManager {
3838
}
3939
return true;
4040
}());
41-
semanticsObject.element.setAttribute('aria-label', label ?? '');
42-
semanticsObject.setAriaRole('dialog');
41+
setAttribute('aria-label', label ?? '');
42+
setAriaRole('dialog');
4343
}
4444
}
4545

@@ -51,8 +51,8 @@ class Dialog extends PrimaryRoleManager {
5151
return;
5252
}
5353

54-
semanticsObject.setAriaRole('dialog');
55-
semanticsObject.element.setAttribute(
54+
setAriaRole('dialog');
55+
setAttribute(
5656
'aria-describedby',
5757
routeName.semanticsObject.element.id,
5858
);
@@ -61,7 +61,10 @@ class Dialog extends PrimaryRoleManager {
6161

6262
/// Supplies a description for the nearest ancestor [Dialog].
6363
class RouteName extends RoleManager {
64-
RouteName(SemanticsObject semanticsObject) : super(Role.routeName, semanticsObject);
64+
RouteName(
65+
SemanticsObject semanticsObject,
66+
PrimaryRoleManager owner,
67+
) : super(Role.routeName, semanticsObject, owner);
6568

6669
Dialog? _dialog;
6770

lib/web_ui/lib/src/engine/semantics/focusable.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,17 @@ import 'semantics.dart';
2828
///
2929
/// * https://developer.mozilla.org/en-US/docs/Web/Accessibility/Keyboard-navigable_JavaScript_widgets
3030
class Focusable extends RoleManager {
31-
Focusable(SemanticsObject semanticsObject)
31+
Focusable(SemanticsObject semanticsObject, PrimaryRoleManager owner)
3232
: _focusManager = AccessibilityFocusManager(semanticsObject.owner),
33-
super(Role.focusable, semanticsObject);
33+
super(Role.focusable, semanticsObject, owner);
3434

3535
final AccessibilityFocusManager _focusManager;
3636

3737
@override
3838
void update() {
3939
if (semanticsObject.isFocusable) {
4040
if (!_focusManager.isManaging) {
41-
_focusManager.manage(semanticsObject.id, semanticsObject.element);
41+
_focusManager.manage(semanticsObject.id, owner.element);
4242
}
4343
_focusManager.changeFocus(semanticsObject.hasFocus && (!semanticsObject.hasEnabledState || semanticsObject.isEnabled));
4444
} else {

lib/web_ui/lib/src/engine/semantics/image.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,14 @@ class ImageRoleManager extends PrimaryRoleManager {
4949
..height = '${semanticsObject.rect!.height}px';
5050
}
5151
_auxiliaryImageElement!.style.fontSize = '6px';
52-
semanticsObject.element.append(_auxiliaryImageElement!);
52+
append(_auxiliaryImageElement!);
5353
}
5454

5555
_auxiliaryImageElement!.setAttribute('role', 'img');
5656
_setLabel(_auxiliaryImageElement);
5757
} else if (semanticsObject.isVisualOnly) {
58-
semanticsObject.setAriaRole('img');
59-
_setLabel(semanticsObject.element);
58+
setAriaRole('img');
59+
_setLabel(element);
6060
_cleanUpAuxiliaryElement();
6161
} else {
6262
_cleanUpAuxiliaryElement();
@@ -78,7 +78,7 @@ class ImageRoleManager extends PrimaryRoleManager {
7878
}
7979

8080
void _cleanupElement() {
81-
semanticsObject.element.removeAttribute('aria-label');
81+
removeAttribute('aria-label');
8282
}
8383

8484
@override

lib/web_ui/lib/src/engine/semantics/incrementable.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class Incrementable extends PrimaryRoleManager {
2929
addRouteName();
3030
addLabelAndValue();
3131

32-
semanticsObject.element.append(_element);
32+
append(_element);
3333
_element.type = 'range';
3434
_element.setAttribute('role', 'slider');
3535

lib/web_ui/lib/src/engine/semantics/label_and_value.dart

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
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 '../dom.dart';
65
import 'semantics.dart';
76

87
/// Renders [SemanticsObject.label] and/or [SemanticsObject.value] to the semantics DOM.
@@ -26,8 +25,8 @@ import 'semantics.dart';
2625
/// This role manager does not manage images and text fields. See
2726
/// [ImageRoleManager] and [TextField].
2827
class LabelAndValue extends RoleManager {
29-
LabelAndValue(SemanticsObject semanticsObject)
30-
: super(Role.labelAndValue, semanticsObject);
28+
LabelAndValue(SemanticsObject semanticsObject, PrimaryRoleManager owner)
29+
: super(Role.labelAndValue, semanticsObject, owner);
3130

3231
@override
3332
void update() {
@@ -62,12 +61,11 @@ class LabelAndValue extends RoleManager {
6261
combinedValue.write(semanticsObject.value);
6362
}
6463

65-
semanticsObject.element
66-
.setAttribute('aria-label', combinedValue.toString());
64+
owner.setAttribute('aria-label', combinedValue.toString());
6765
}
6866

6967
void _cleanUpDom() {
70-
semanticsObject.element.removeAttribute('aria-label');
68+
owner.removeAttribute('aria-label');
7169
}
7270

7371
@override
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import '../dom.dart';
6+
import '../semantics.dart';
7+
8+
/// Provides accessibility for links.
9+
class Link extends PrimaryRoleManager {
10+
Link(SemanticsObject semanticsObject) : super.withBasics(PrimaryRole.link, semanticsObject);
11+
12+
@override
13+
DomElement createElement() {
14+
final DomElement element = domDocument.createElement('a');
15+
// TODO(chunhtai): Fill in the real link once the framework sends entire uri.
16+
// https://github.com/flutter/flutter/issues/102535.
17+
element.setAttribute('href', '#');
18+
element.style.display = 'block';
19+
return element;
20+
}
21+
}

0 commit comments

Comments
 (0)