Skip to content

Commit 37ff5a3

Browse files
chunhtaimdebbar
andauthored
Wires up expanded state in web engine (flutter#164048)
<!-- Thanks for filing a pull request! Reviewers are typically assigned within a week of filing a request. To learn more about code review, see our documentation on Tree Hygiene: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md --> fixes flutter#162141 ## 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], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] 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/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Mouad Debbar <[email protected]>
1 parent b8f274d commit 37ff5a3

File tree

7 files changed

+132
-0
lines changed

7 files changed

+132
-0
lines changed

engine/src/flutter/ci/licenses_golden/licenses_flutter

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42685,6 +42685,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/scene_view.dart + ../../../fl
4268542685
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics.dart + ../../../flutter/LICENSE
4268642686
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/accessibility.dart + ../../../flutter/LICENSE
4268742687
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/checkable.dart + ../../../flutter/LICENSE
42688+
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/expandable.dart + ../../../flutter/LICENSE
4268842689
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/focusable.dart + ../../../flutter/LICENSE
4268942690
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/header.dart + ../../../flutter/LICENSE
4269042691
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/heading.dart + ../../../flutter/LICENSE
@@ -45617,6 +45618,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/scene_view.dart
4561745618
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics.dart
4561845619
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/accessibility.dart
4561945620
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/checkable.dart
45621+
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/expandable.dart
4562045622
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/focusable.dart
4562145623
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/header.dart
4562245624
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/heading.dart

engine/src/flutter/lib/web_ui/lib/src/engine.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export 'engine/scene_painting.dart';
105105
export 'engine/scene_view.dart';
106106
export 'engine/semantics/accessibility.dart';
107107
export 'engine/semantics/checkable.dart';
108+
export 'engine/semantics/expandable.dart';
108109
export 'engine/semantics/focusable.dart';
109110
export 'engine/semantics/header.dart';
110111
export 'engine/semantics/heading.dart';

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
export 'semantics/accessibility.dart';
66
export 'semantics/checkable.dart';
7+
export 'semantics/expandable.dart';
78
export 'semantics/focusable.dart';
89
export 'semantics/header.dart';
910
export 'semantics/heading.dart';
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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 'semantics.dart';
6+
7+
/// Adds expandability behavior to a semantic node.
8+
///
9+
/// An expandable node would have the `aria-expanded` attribute set to "true" if the node
10+
/// is currently expanded (i.e. [SemanticsObject.isExpanded] is true), and set
11+
/// to "false" if it's not expanded (i.e. [SemanticsObject.isExpanded] is
12+
/// false). If the node is not expandable (i.e. [SemanticsObject.isExpandable]
13+
/// is false), then `aria-expanded` is unset.
14+
class Expandable extends SemanticBehavior {
15+
Expandable(super.semanticsObject, super.owner);
16+
17+
@override
18+
void update() {
19+
if (semanticsObject.isFlagsDirty) {
20+
if (semanticsObject.isExpandable) {
21+
owner.setAttribute('aria-expanded', semanticsObject.isExpanded);
22+
} else {
23+
owner.removeAttribute('aria-expanded');
24+
}
25+
}
26+
}
27+
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import '../vector_math.dart';
2020
import '../window.dart';
2121
import 'accessibility.dart';
2222
import 'checkable.dart';
23+
import 'expandable.dart';
2324
import 'focusable.dart';
2425
import 'header.dart';
2526
import 'heading.dart';
@@ -461,6 +462,7 @@ abstract class SemanticRole {
461462
addRouteName();
462463
addLabelAndValue(preferredRepresentation: preferredLabelRepresentation);
463464
addSelectableBehavior();
465+
addExpandableBehavior();
464466
}
465467

466468
/// Initializes a blank role for a [semanticsObject].
@@ -627,6 +629,10 @@ abstract class SemanticRole {
627629
}
628630
}
629631

632+
void addExpandableBehavior() {
633+
addSemanticBehavior(Expandable(semanticsObject, this));
634+
}
635+
630636
/// Adds a semantic behavior to this role.
631637
///
632638
/// This method should be called by concrete implementations of
@@ -1947,6 +1953,19 @@ class SemanticsObject {
19471953
/// selected.
19481954
bool get isSelected => hasFlag(ui.SemanticsFlag.isSelected);
19491955

1956+
/// If true, this node represents something that can be annotated as
1957+
/// "expanded", such as a expansion tile or drop down menu
1958+
///
1959+
/// Expandability is managed by `aria-expanded`.
1960+
///
1961+
/// See also:
1962+
///
1963+
/// * [isExpanded], which indicates whether the node is currently selected.
1964+
bool get isExpandable => hasFlag(ui.SemanticsFlag.hasExpandedState);
1965+
1966+
/// Indicates whether the node is currently expanded.
1967+
bool get isExpanded => hasFlag(ui.SemanticsFlag.isExpanded);
1968+
19501969
/// Role-specific adjustment of the vertical position of the child container.
19511970
///
19521971
/// This is used, for example, by the [SemanticScrollable] to compensate for the

engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ void runSemanticsTests() {
8686
group('selectables', () {
8787
_testSelectables();
8888
});
89+
group('expandables', () {
90+
_testExpandables();
91+
});
8992
group('tappable', () {
9093
_testTappable();
9194
});
@@ -2328,6 +2331,77 @@ void _testSelectables() {
23282331
});
23292332
}
23302333

2334+
void _testExpandables() {
2335+
test('renders and updates non-expandable, expanded, and unexpanded nodes', () async {
2336+
semantics()
2337+
..debugOverrideTimestampFunction(() => _testTime)
2338+
..semanticsEnabled = true;
2339+
2340+
final tester = SemanticsTester(owner());
2341+
tester.updateNode(
2342+
id: 0,
2343+
rect: const ui.Rect.fromLTRB(0, 0, 100, 60),
2344+
children: <SemanticsNodeUpdate>[
2345+
tester.updateNode(id: 1, isSelectable: false, rect: const ui.Rect.fromLTRB(0, 0, 100, 20)),
2346+
tester.updateNode(
2347+
id: 2,
2348+
isExpandable: true,
2349+
isExpanded: false,
2350+
rect: const ui.Rect.fromLTRB(0, 20, 100, 40),
2351+
),
2352+
tester.updateNode(
2353+
id: 3,
2354+
isExpandable: true,
2355+
isExpanded: true,
2356+
rect: const ui.Rect.fromLTRB(0, 40, 100, 60),
2357+
),
2358+
],
2359+
);
2360+
tester.apply();
2361+
2362+
expectSemanticsTree(owner(), '''
2363+
<sem>
2364+
<sem-c>
2365+
<sem></sem>
2366+
<sem aria-expanded="false"></sem>
2367+
<sem aria-expanded="true"></sem>
2368+
</sem-c>
2369+
</sem>
2370+
''');
2371+
2372+
// Missing attributes cannot be expressed using HTML patterns, so check directly.
2373+
final nonExpandable = owner().debugSemanticsTree![1]!.element;
2374+
expect(nonExpandable.getAttribute('aria-expanded'), isNull);
2375+
2376+
// Flip the values and check that that ARIA attribute is updated.
2377+
tester.updateNode(
2378+
id: 2,
2379+
isExpandable: true,
2380+
isExpanded: true,
2381+
rect: const ui.Rect.fromLTRB(0, 20, 100, 40),
2382+
);
2383+
tester.updateNode(
2384+
id: 3,
2385+
isExpandable: true,
2386+
isExpanded: false,
2387+
rect: const ui.Rect.fromLTRB(0, 40, 100, 60),
2388+
);
2389+
tester.apply();
2390+
2391+
expectSemanticsTree(owner(), '''
2392+
<sem>
2393+
<sem-c>
2394+
<sem></sem>
2395+
<sem aria-expanded="true"></sem>
2396+
<sem aria-expanded="false"></sem>
2397+
</sem-c>
2398+
</sem>
2399+
''');
2400+
2401+
semantics().semanticsEnabled = false;
2402+
});
2403+
}
2404+
23312405
void _testTappable() {
23322406
test('renders an enabled button', () async {
23332407
semantics()

engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_tester.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class SemanticsTester {
3434
bool? isChecked,
3535
bool? isSelectable,
3636
bool? isSelected,
37+
bool? isExpandable,
38+
bool? isExpanded,
3739
bool? isButton,
3840
bool? isLink,
3941
bool? isTextField,
@@ -130,6 +132,12 @@ class SemanticsTester {
130132
if (isSelected ?? false) {
131133
flags |= ui.SemanticsFlag.isSelected.index;
132134
}
135+
if (isExpandable ?? false) {
136+
flags |= ui.SemanticsFlag.hasExpandedState.index;
137+
}
138+
if (isExpanded ?? false) {
139+
flags |= ui.SemanticsFlag.isExpanded.index;
140+
}
133141
if (isButton ?? false) {
134142
flags |= ui.SemanticsFlag.isButton.index;
135143
}

0 commit comments

Comments
 (0)