Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[a11y][web] Selecting menu items triggers a back navigation #134842

Closed
Renzo-Olivares opened this issue Sep 15, 2023 · 8 comments · Fixed by flutter/engine#47360
Closed

[a11y][web] Selecting menu items triggers a back navigation #134842

Renzo-Olivares opened this issue Sep 15, 2023 · 8 comments · Fixed by flutter/engine#47360
Assignees
Labels
a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) c: regression It was better in the past than it is now f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. P1 High-priority issues at the top of the work list platform-web Web applications specifically team-design Owned by Design Languages team triaged-design Triaged by Design Languages team

Comments

@Renzo-Olivares
Copy link
Contributor

Renzo-Olivares commented Sep 15, 2023

Raised internally at b/299847356

Issue: Selecting a menu item with semantics enabled, for example through a pop up menu or dropdown menu, triggers a back navigation. This not only dismisses the menu, but also pops the next route from the navigator stack.

Expected: Selecting a menu item with semantics enabled dismisses the menu, but does not trigger another route to be popped from the navigator stack.

Bisected to engine roll: #131897
Bisected to engine commit: flutter/engine#43620

Minimal example for reproduction: https://dartpad.dev/?id=41045409a61245de472cb0083fc94742&channel=master

Screen.Recording.2023-09-15.at.1.44.04.PM.mov
Stack trace before change
dart-sdk/lib/_internal/js_dev_runtime/patch/core_patch.dart 941:28                get current
packages/flutter/src/material/popup_menu.dart 347:30                              handleTap
packages/flutter/src/material/ink_well.dart 1188:21                               handleTap
packages/flutter/src/gestures/recognizer.dart 275:24                              invokeCallback
packages/flutter/src/gestures/tap.dart 654:11                                     handleTapUp
packages/flutter/src/gestures/tap.dart 311:5                                      [_checkUp]
packages/flutter/src/gestures/tap.dart 244:7                                      handlePrimaryPointer
packages/flutter/src/gestures/recognizer.dart 630:9                               handleEvent
packages/flutter/src/gestures/pointer_router.dart 98:12                           [_dispatch]
packages/flutter/src/gestures/pointer_router.dart 143:9                           <fn>
dart-sdk/lib/_internal/js_dev_runtime/private/linked_hash_map.dart 21:13          forEach
packages/flutter/src/gestures/pointer_router.dart 141:17                          [_dispatchEventToRoutes]
packages/flutter/src/gestures/pointer_router.dart 127:7                           route
packages/flutter/src/gestures/binding.dart 488:19                                 handleEvent
packages/flutter/src/gestures/binding.dart 468:14                                 dispatchEvent
packages/flutter/src/rendering/binding.dart 439:11                                dispatchEvent
packages/flutter/src/gestures/binding.dart 413:7                                  [_handlePointerEventImmediately]
packages/flutter/src/gestures/binding.dart 376:5                                  handlePointerEvent
packages/flutter/src/gestures/binding.dart 323:7                                  [_flushPointerEventQueue]
packages/flutter/src/gestures/binding.dart 292:9                                  [_handlePointerDataPacket]
lib/_engine/engine/platform_dispatcher.dart 1295:13                               invoke1
lib/_engine/engine/platform_dispatcher.dart 273:5                                 invokeOnPointerDataPacket
lib/_engine/engine/pointer_binding.dart 168:39                                    [_onPointerData]
lib/_engine/engine/pointer_binding.dart 791:20                                    <fn>
lib/_engine/engine/pointer_binding.dart 720:14                                    <fn>
lib/_engine/engine/pointer_binding.dart 317:16                                    loggedHandler
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 574:37  _checkAndCall
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 579:39  dcall
Stack trace after change
dart-sdk/lib/_internal/js_dev_runtime/patch/core_patch.dart 941:28                get current
packages/flutter/src/widgets/navigator.dart 2525:30                               maybePop
packages/flutter/src/widgets/modal_barrier.dart 228:21                            handleDismiss
packages/flutter/src/rendering/proxy_box.dart 4492:24                             [_performTap]
packages/flutter/src/semantics/semantics.dart 3541:14                             <fn>
packages/flutter/src/semantics/semantics.dart 3358:14                             performAction
packages/flutter/src/rendering/binding.dart 447:64                                performSemanticsAction
packages/flutter/src/semantics/binding.dart 119:5                                 [_handleSemanticsActionEvent]
lib/_engine/engine/platform_dispatcher.dart 1295:13                               invoke1
lib/_engine/engine/platform_dispatcher.dart 1173:5                                invokeOnSemanticsAction
lib/_engine/engine/pointer_binding.dart 263:43                                    onClick
lib/_engine/engine/semantics/tappable.dart 34:22                                  <fn>
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 574:37  _checkAndCall
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 579:39  dcall
dart-sdk/lib/_internal/js_dev_runtime/patch/core_patch.dart 941:28                get current
packages/flutter/src/material/popup_menu.dart 347:30                              handleTap
packages/flutter/src/material/ink_well.dart 1188:21                               handleTap
packages/flutter/src/material/ink_well.dart 861:5                                 simulateTap
packages/flutter/src/rendering/proxy_box.dart 4492:24                             [_performTap]
packages/flutter/src/semantics/semantics.dart 3539:14                             <fn>
packages/flutter/src/semantics/semantics.dart 3357:14                             performAction
packages/flutter/src/rendering/binding.dart 447:64                                performSemanticsAction

From the stack trace it looks like a second semantics action is being dispatched and that is causing a second route to be popped from the navigator stack. The first pop that happens in PopUpMenu is expected

void handleTap() {
// Need to pop the navigator first in case onTap may push new route onto navigator.
Navigator.pop<T>(context, widget.value);
widget.onTap?.call();
}
, but the second pop that happens in ModalBarrier
void handleDismiss() {
if (dismissible) {
if (onDismiss != null) {
onDismiss!();
} else {
Navigator.maybePop(context);
}
} else {
SystemSound.play(SystemSoundType.alert);
}
}
is not expected.

@Renzo-Olivares Renzo-Olivares added the c: regression It was better in the past than it is now label Sep 15, 2023
@yjbanov
Copy link
Contributor

yjbanov commented Sep 15, 2023

A few observations:

Root cause

The root cause is that semantics tree contains nested clickable/tappable nodes:

Screenshot 2023-09-15 at 4 12 48 PM

With such a DOM tree when a descendant receives a DOM click event, its ancestor also receives it. This leads to two SemanticsAction.tap events on the framework side, which results in two routes popped instead of one.

The bug is old

The bug is in the semantics, and it predates this manifestation probably by many stable releases. What happened recently was that what was previously delivered via gesture recognition is now delivered via semantics action, and since the bug is in the semantics it now manifests itself in a new situation.

The fix

This should be fixable on the web engine side, but clarification is needed from the framework team on what the expected behaviors are in the presence of nested clickable nodes. However, it's also possible that the bug is in the framework. I imagine we don't have nested clickable widgets in the widget tree. My guess is the semantics merging logic ends up merging nodes in this way, and that may not be a good thing to do.

@HansMuller HansMuller added framework flutter/packages/flutter repository. See also f: labels. f: material design flutter/packages/flutter/material repository. a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) platform-web Web applications specifically P1 High-priority issues at the top of the work list team-design Owned by Design Languages team triaged-design Triaged by Design Languages team labels Sep 15, 2023
@HansMuller
Copy link
Contributor

@chunhtai - please take a look at this

@chunhtai
Copy link
Contributor

I imagine we don't have nested clickable widgets in the widget tree

We do, but we handle it using gesture arena. For tap gesture the inner most one will win.

With such a DOM tree when a descendant receives a DOM click event, its ancestor also receives it

Should whichever receives the click that also has semantics tap prevent the event from bubbling?

@jiahaog jiahaog added P0 Critical issues such as a build break or regression and removed P1 High-priority issues at the top of the work list labels Sep 18, 2023
@jiahaog
Copy link
Member

jiahaog commented Sep 18, 2023

Updating the priority to match the internal bug mapping

@yjbanov
Copy link
Contributor

yjbanov commented Sep 19, 2023

I'm reverting the fix for the merged clicks for now.

Should whichever receives the click that also has semantics tap prevent the event from bubbling?

I'm going to have to test this. One issue I've seen before (which is related to the PR that's being reverted) is that screen readers like to click in the middle of things. If we have overlapping nested clickables, I'm not sure if the screen reader would still end up clicking on the child anyway.

@chunhtai
Copy link
Contributor

chunhtai commented Sep 19, 2023

Yes that is something we should test, if they issue a really click instead of calling the onClick method on the div, this issue probably is not limited to flutter.

auto-submit bot pushed a commit to flutter/engine that referenced this issue Sep 21, 2023
This reverts commit 0c1de9b.

The commit caused flutter/flutter#134842. I'm going to try again, this time accounting for nested clickables/tappables.
@yjbanov yjbanov added P1 High-priority issues at the top of the work list and removed P0 Critical issues such as a build break or regression labels Sep 25, 2023
@yjbanov
Copy link
Contributor

yjbanov commented Sep 25, 2023

Dropping to P1 as the PR that exacerbated the issue has been reverted.

harryterkelsen pushed a commit to flutter/engine that referenced this issue Oct 23, 2023
This reverts commit 0c1de9b.

The commit caused flutter/flutter#134842. I'm going to try again, this time accounting for nested clickables/tappables.
auto-submit bot pushed a commit to flutter/engine that referenced this issue Nov 8, 2023
This relands #43620 with a fix for nested tappable nodes. The first PR introduced this regression: flutter/flutter#134842.

This PR includes the original PR and a fix for the regression. The fix is to call `stopPropagation` on the "click" event so that it is not handled by the ancestor if the child has already decided to send a `SemanticsAction.tap` to the framework. This ensures that there cannot be more than one `SemanticsAction.tap` sent to the framework.

Fixes flutter/flutter#134842
Copy link

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 22, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) c: regression It was better in the past than it is now f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. P1 High-priority issues at the top of the work list platform-web Web applications specifically team-design Owned by Design Languages team triaged-design Triaged by Design Languages team
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants