Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/web_ui/lib/src/engine/semantics/checkable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ _CheckableKind _checkableKindFromSemanticsFlag(
/// See also [ui.SemanticsFlag.hasCheckedState], [ui.SemanticsFlag.isChecked],
/// [ui.SemanticsFlag.isInMutuallyExclusiveGroup], [ui.SemanticsFlag.isToggled],
/// [ui.SemanticsFlag.hasToggledState]
class Checkable extends PrimaryRoleManager {
class Checkable extends SemanticRole {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is indeed much better. It's a shame that the name of the top-level classes sound like java interfaces (*-able), but the alternative (CheckableSemanticRole) is probably too verbose?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made all concrete role type names use the Semantic* prefix. Looks much better now.

Checkable(SemanticsObject semanticsObject)
: _kind = _checkableKindFromSemanticsFlag(semanticsObject),
super.withBasics(
PrimaryRole.checkable,
SemanticRoleId.checkable,
semanticsObject,
preferredLabelRepresentation: LabelRepresentation.ariaLabel,
) {
Expand Down
32 changes: 17 additions & 15 deletions lib/web_ui/lib/src/engine/semantics/dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ import '../dom.dart';
import '../semantics.dart';
import '../util.dart';

/// Provides accessibility for dialogs.
///
/// See also [Role.dialog].
class Dialog extends PrimaryRoleManager {
Dialog(SemanticsObject semanticsObject) : super.blank(PrimaryRole.dialog, semanticsObject) {
// The following secondary roles can coexist with dialog. Generic `RouteName`
/// Provides accessibility for routes, including dialogs and pop-up menus.
class Dialog extends SemanticRole {
Dialog(SemanticsObject semanticsObject) : super.blank(SemanticRoleId.dialog, semanticsObject) {
// The following behaviors can coexist with dialog. Generic `RouteName`
// and `LabelAndValue` are not used by this role because when the dialog
// names its own route an `aria-label` is used instead of `aria-describedby`.
addFocusManagement();
Expand Down Expand Up @@ -39,7 +37,7 @@ class Dialog extends PrimaryRoleManager {

void _setDefaultFocus() {
semanticsObject.visitDepthFirstInTraversalOrder((SemanticsObject node) {
final PrimaryRoleManager? roleManager = node.primaryRole;
final SemanticRole? roleManager = node.semanticRole;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider doing a pass renaming these roleManager names. They don't map very well to what they contain anymore, now that the "Manager" name is going away from this class hierarchy.

if (roleManager == null) {
return true;
}
Expand Down Expand Up @@ -100,11 +98,15 @@ class Dialog extends PrimaryRoleManager {
}

/// Supplies a description for the nearest ancestor [Dialog].
class RouteName extends RoleManager {
RouteName(
SemanticsObject semanticsObject,
PrimaryRoleManager owner,
) : super(Role.routeName, semanticsObject, owner);
///
/// This role is assigned to nodes that have `namesRoute` set but not
/// `scopesRoute`. When both flags are set the node only gets the [Dialog] role.
///
/// If the ancestor dialog is missing, this role has no effect. It is up to the
/// framework, widget, and app authors to make sure a route name is scoped under
/// a route.
class RouteName extends SemanticBehavior {
RouteName(super.semanticsObject, super.owner);

Dialog? _dialog;

Expand Down Expand Up @@ -143,11 +145,11 @@ class RouteName extends RoleManager {

void _lookUpNearestAncestorDialog() {
SemanticsObject? parent = semanticsObject.parent;
while (parent != null && parent.primaryRole?.role != PrimaryRole.dialog) {
while (parent != null && parent.semanticRole?.role != SemanticRoleId.dialog) {
parent = parent.parent;
}
if (parent != null && parent.primaryRole?.role == PrimaryRole.dialog) {
_dialog = parent.primaryRole! as Dialog;
if (parent != null && parent.semanticRole?.role == SemanticRoleId.dialog) {
_dialog = parent.semanticRole! as Dialog;
}
}
}
19 changes: 9 additions & 10 deletions lib/web_ui/lib/src/engine/semantics/focusable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import 'semantics.dart';
/// Supplies generic accessibility focus features to semantics nodes that have
/// [ui.SemanticsFlag.isFocusable] set.
///
/// Assumes that the element being focused on is [SemanticsObject.element]. Role
/// managers with special needs can implement custom focus management and
/// exclude this role manager.
/// Assumes that the element being focused on is [SemanticsObject.element].
/// Semantic roles with special needs can implement custom focus management and
/// exclude this behavior.
///
/// `"tab-index=0"` is used because `<flt-semantics>` is not intrinsically
/// focusable. Examples of intrinsically focusable elements include:
Expand All @@ -27,10 +27,9 @@ import 'semantics.dart';
/// See also:
///
/// * https://developer.mozilla.org/en-US/docs/Web/Accessibility/Keyboard-navigable_JavaScript_widgets
class Focusable extends RoleManager {
Focusable(SemanticsObject semanticsObject, PrimaryRoleManager owner)
: _focusManager = AccessibilityFocusManager(semanticsObject.owner),
super(Role.focusable, semanticsObject, owner);
class Focusable extends SemanticBehavior {
Focusable(super.semanticsObject, super.owner)
: _focusManager = AccessibilityFocusManager(semanticsObject.owner);

final AccessibilityFocusManager _focusManager;

Expand All @@ -44,9 +43,9 @@ class Focusable extends RoleManager {
/// programmatically, simulating the screen reader choosing a default element
/// to focus on.
///
/// Returns `true` if the role manager took the focus. Returns `false` if
/// this role manager did not take the focus. The return value can be used to
/// decide whether to stop searching for a node that should take focus.
/// Returns `true` if the node took the focus. Returns `false` if the node did
/// not take the focus. The return value can be used to decide whether to stop
/// searching for a node that should take focus.
bool focusAsRouteDefault() {
_focusManager._lastEvent = AccessibilityFocusManagerEvent.requestedFocus;
owner.element.focusWithoutScroll();
Expand Down
4 changes: 2 additions & 2 deletions lib/web_ui/lib/src/engine/semantics/heading.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import 'semantics.dart';

/// Renders semantics objects as headings with the corresponding
/// level (h1 ... h6).
class Heading extends PrimaryRoleManager {
class Heading extends SemanticRole {
Heading(SemanticsObject semanticsObject)
: super.blank(PrimaryRole.heading, semanticsObject) {
: super.blank(SemanticRoleId.heading, semanticsObject) {
addFocusManagement();
addLiveRegion();
addRouteName();
Expand Down
10 changes: 5 additions & 5 deletions lib/web_ui/lib/src/engine/semantics/image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import 'semantics.dart';
/// Uses aria img role to convey this semantic information to the element.
///
/// Screen-readers takes advantage of "aria-label" to describe the visual.
class ImageRoleManager extends PrimaryRoleManager {
ImageRoleManager(SemanticsObject semanticsObject)
: super.blank(PrimaryRole.image, semanticsObject) {
// The following secondary roles can coexist with images. `LabelAndValue` is
// not used because this role manager uses special auxiliary elements to
class ImageSemanticRole extends SemanticRole {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that Image clashes with other 47 classes in the engine, but... should all the extends SemanticRole be SomethingSomethingSemanticRole, just for consistency of everything inside "engine/semantics"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (using the Semantic* prefix)

ImageSemanticRole(SemanticsObject semanticsObject)
: super.blank(SemanticRoleId.image, semanticsObject) {
// The following behaviors can coexist with images. `LabelAndValue` is
// not used because this behavior uses special auxiliary elements to
// supply ARIA labels.
// TODO(yjbanov): reevaluate usage of aux elements, https://github.com/flutter/flutter/issues/129317
addFocusManagement();
Expand Down
4 changes: 2 additions & 2 deletions lib/web_ui/lib/src/engine/semantics/incrementable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import 'semantics.dart';
/// The input element is disabled whenever the gesture mode switches to pointer
/// events. This is to prevent the browser from taking over drag gestures. Drag
/// gestures must be interpreted by the Flutter framework.
class Incrementable extends PrimaryRoleManager {
class Incrementable extends SemanticRole {
Incrementable(SemanticsObject semanticsObject)
: _focusManager = AccessibilityFocusManager(semanticsObject.owner),
super.blank(PrimaryRole.incrementable, semanticsObject) {
super.blank(SemanticRoleId.incrementable, semanticsObject) {
// The following generic roles can coexist with incrementables. Generic focus
// management is not used by this role because the root DOM element is not
// the one being focused on, but the internal `<input>` element.
Expand Down
29 changes: 14 additions & 15 deletions lib/web_ui/lib/src/engine/semantics/label_and_value.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ enum LabelRepresentation {
sizedSpan;

/// Creates the behavior for this label representation.
LabelRepresentationBehavior createBehavior(PrimaryRoleManager owner) {
LabelRepresentationBehavior createBehavior(SemanticRole owner) {
return switch (this) {
ariaLabel => AriaLabelRepresentation._(owner),
domText => DomTextRepresentation._(owner),
Expand All @@ -63,8 +63,8 @@ abstract final class LabelRepresentationBehavior {

final LabelRepresentation kind;

/// The role manager that this label representation is attached to.
final PrimaryRoleManager owner;
/// The role that this label representation is attached to.
final SemanticRole owner;

/// Convenience getter for the corresponding semantics object.
SemanticsObject get semanticsObject => owner.semanticsObject;
Expand Down Expand Up @@ -109,7 +109,7 @@ abstract final class LabelRepresentationBehavior {
///
/// <flt-semantics aria-label="Hello, World!"></flt-semantics>
final class AriaLabelRepresentation extends LabelRepresentationBehavior {
AriaLabelRepresentation._(PrimaryRoleManager owner) : super(LabelRepresentation.ariaLabel, owner);
AriaLabelRepresentation._(SemanticRole owner) : super(LabelRepresentation.ariaLabel, owner);

String? _previousLabel;

Expand Down Expand Up @@ -143,7 +143,7 @@ final class AriaLabelRepresentation extends LabelRepresentationBehavior {
/// no ARIA role set, or the role does not size the element, then the
/// [SizedSpanRepresentation] representation can be used.
final class DomTextRepresentation extends LabelRepresentationBehavior {
DomTextRepresentation._(PrimaryRoleManager owner) : super(LabelRepresentation.domText, owner);
DomTextRepresentation._(SemanticRole owner) : super(LabelRepresentation.domText, owner);

DomText? _domText;
String? _previousLabel;
Expand Down Expand Up @@ -233,7 +233,7 @@ typedef _Measurement = ({
/// * Use an existing non-text role, e.g. "heading". Sizes correctly, but breaks
/// the message (reads "heading").
final class SizedSpanRepresentation extends LabelRepresentationBehavior {
SizedSpanRepresentation._(PrimaryRoleManager owner) : super(LabelRepresentation.sizedSpan, owner) {
SizedSpanRepresentation._(SemanticRole owner) : super(LabelRepresentation.sizedSpan, owner) {
_domText.style
// `inline-block` is needed for two reasons:
// - It supports measuring the true size of the text. Pure `block` would
Expand Down Expand Up @@ -433,14 +433,13 @@ final class SizedSpanRepresentation extends LabelRepresentationBehavior {
DomElement get focusTarget => _domText;
}

/// Renders [SemanticsObject.label] and/or [SemanticsObject.value] to the semantics DOM.
/// Renders the label for a [SemanticsObject] that can be scanned by screen
/// readers, web crawlers, and other automated agents.
///
/// The value is not always rendered. Some semantics nodes correspond to
/// interactive controls. In such case the value is reported via that element's
/// `value` attribute rather than rendering it separately.
class LabelAndValue extends RoleManager {
LabelAndValue(SemanticsObject semanticsObject, PrimaryRoleManager owner, { required this.preferredRepresentation })
: super(Role.labelAndValue, semanticsObject, owner);
/// See [computeDomSemanticsLabel] for the exact logic that constructs the label
/// of a semantic node.
class LabelAndValue extends SemanticBehavior {
LabelAndValue(super.semanticsObject, super.owner, { required this.preferredRepresentation });

/// The preferred representation of the label in the DOM.
///
Expand Down Expand Up @@ -471,7 +470,7 @@ class LabelAndValue extends RoleManager {
/// If the node has children always use an `aria-label`. Using extra child
/// nodes to represent the label will cause layout shifts and confuse the
/// screen reader. If the are no children, use the representation preferred
/// by the primary role manager.
/// by the role.
LabelRepresentationBehavior _getEffectiveRepresentation() {
final LabelRepresentation effectiveRepresentation = semanticsObject.hasChildren
? LabelRepresentation.ariaLabel
Expand All @@ -491,7 +490,7 @@ class LabelAndValue extends RoleManager {
/// combination is present.
String? _computeLabel() {
// If the node is incrementable the value is reported to the browser via
// the respective role manager. We do not need to also render it again here.
// the respective role. We do not need to also render it again here.
final bool shouldDisplayValue = !semanticsObject.isIncrementable && semanticsObject.hasValue;

return computeDomSemanticsLabel(
Expand Down
4 changes: 2 additions & 2 deletions lib/web_ui/lib/src/engine/semantics/link.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import '../dom.dart';
import '../semantics.dart';

/// Provides accessibility for links.
class Link extends PrimaryRoleManager {
class Link extends SemanticRole {
Link(SemanticsObject semanticsObject) : super.withBasics(
PrimaryRole.link,
SemanticRoleId.link,
semanticsObject,
preferredLabelRepresentation: LabelRepresentation.domText,
) {
Expand Down
16 changes: 11 additions & 5 deletions lib/web_ui/lib/src/engine/semantics/live_region.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,21 @@ import 'semantics.dart';

/// Manages semantics configurations that represent live regions.
///
/// Assistive technologies treat "aria-live" attribute differently. To keep
/// the behavior consistent, [AccessibilityAnnouncements.announce] is used.
/// A live region is a region whose changes will be announced by the screen
/// reader without the user moving focus onto the node.
///
/// Examples of live regions include snackbars and text field errors. Once
/// identified with this role, they will be able to get the assistive
/// technology's attention right away.
///
/// Different assistive technologies treat "aria-live" attribute differently. To
/// keep the behavior consistent, [AccessibilityAnnouncements.announce] is used.
///
/// When there is an update to [LiveRegion], assistive technologies read the
/// label of the element. See [LabelAndValue]. If there is no label provided
/// no content will be read.
class LiveRegion extends RoleManager {
LiveRegion(SemanticsObject semanticsObject, PrimaryRoleManager owner)
: super(Role.liveRegion, semanticsObject, owner);
class LiveRegion extends SemanticBehavior {
LiveRegion(super.semanticsObject, super.owner);

String? _lastAnnouncement;

Expand Down
6 changes: 3 additions & 3 deletions lib/web_ui/lib/src/engine/semantics/platform_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import 'semantics.dart';
/// See also:
/// * https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-owns
/// * https://bugs.webkit.org/show_bug.cgi?id=223798
class PlatformViewRoleManager extends PrimaryRoleManager {
PlatformViewRoleManager(SemanticsObject semanticsObject)
class PlatformViewSemanticRole extends SemanticRole {
PlatformViewSemanticRole(SemanticsObject semanticsObject)
: super.withBasics(
PrimaryRole.platformView,
SemanticRoleId.platformView,
semanticsObject,
preferredLabelRepresentation: LabelRepresentation.ariaLabel,
);
Expand Down
4 changes: 2 additions & 2 deletions lib/web_ui/lib/src/engine/semantics/scrollable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ import 'package:ui/ui.dart' as ui;
/// contents is less than the size of the viewport the browser snaps
/// "scrollTop" back to zero. If there is more content than available in the
/// viewport "scrollTop" may take positive values.
class Scrollable extends PrimaryRoleManager {
class Scrollable extends SemanticRole {
Scrollable(SemanticsObject semanticsObject)
: super.withBasics(
PrimaryRole.scrollable,
SemanticRoleId.scrollable,
semanticsObject,
preferredLabelRepresentation: LabelRepresentation.ariaLabel,
) {
Expand Down
Loading