Skip to content

fix: prefer pointer events over semantics tap for mouse clicks on web - #191529

Merged
auto-submit[bot] merged 6 commits into
flutter:masterfrom
rkishan516:web-semantics-mouse-click-position
Nov 3, 2026
Merged

auto-submit[bot] merged 6 commits into
flutter:masterfrom
rkishan516:web-semantics-mouse-click-position

Conversation

@rkishan516

Copy link
Copy Markdown
Contributor

This PR fixes Tap*Details reporting the center of the semantics node and PointerDeviceKind.unknown for mouse clicks on web when semantics is enabled.

When semantics is enabled, ClickDebouncer withholds the pointer events that lead up to a DOM "click" on a tappable semantics node and forwards the click as a SemanticsAction.tap instead. That action carries neither a position nor a device kind, so the framework synthesizes the tap at the center of the semantics node with PointerDeviceKind.unknown. For a merged semantics node that also means the tap is attributed to the merged node rather than to whatever was actually under the cursor.

The debouncer exists for assistive technologies, which drive taps either through clicks that come with no pointer events at all, or through touch. Neither of those paths is affected by this change: when the withheld pointer events came from a mouse, they are now flushed to the framework and the click is dropped, so mouse gestures are recognized exactly like they are when semantics is off.

Fixes: #188859
Fixes: #146063

Pre-launch Checklist

@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label Aug 22, 2026
@github-actions github-actions Bot added engine flutter/engine related. See also e: labels. platform-web Web applications specifically team-web Owned by Web platform team labels Aug 22, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request updates the ClickDebouncer in the Flutter Web engine to flush queued pointer events and drop the click event for mouse-generated clicks, allowing mouse gestures to be recognized precisely instead of using SemanticsAction.tap. Corresponding tests have been updated to use touch events, and a regression test has been added. Feedback is provided to add a defensive check in _isDebouncingMouseEvents to prevent a potential StateError when accessing the first element of an empty queue.

Comment on lines +252 to +255
bool get _isDebouncingMouseEvents {
final List<ui.PointerData>? data = _state?.queue.first.data;
return data != null && data.isNotEmpty && data.first.kind == ui.PointerDeviceKind.mouse;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

To prevent potential StateError crashes when accessing .first on an empty queue, it is safer to use defensive programming and check if the queue is null or empty before accessing its elements.

Suggested change
bool get _isDebouncingMouseEvents {
final List<ui.PointerData>? data = _state?.queue.first.data;
return data != null && data.isNotEmpty && data.first.kind == ui.PointerDeviceKind.mouse;
}
bool get _isDebouncingMouseEvents {
final queue = _state?.queue;
if (queue == null || queue.isEmpty) {
return false;
}
final List<ui.PointerData>? data = queue.first.data;
return data != null && data.isNotEmpty && data.first.kind == ui.PointerDeviceKind.mouse;
}

@flutter-zl flutter-zl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for working on this, the #188859 problem is real and the patch is nice and small.

One concern before it can land: on Chrome, screen reader activation also produces mouse pointer events. Blink sends pointerdown, pointerup and click with pointerType: 'mouse' at the element center, all in one task. That sequence
now takes the new mouse branch, so the click is dropped and nodes that rely on SemanticsAction.tap, such as Semantics(onTap: ...) with no gesture detector underneath, stop responding.

I verified it with VoiceOver on macOS Chrome:

- Before: https://flutter-demo-82-before.web.app
- After, with this PR: https://flutter-demo-82-after.web.app

Press VO+U, pick "Semantics only button" from the Buttons list, then press VO+Space. The counter goes up on the before app but stays at 0 on the after app.

It would also be great to add a test for the one-task mouse sequence that still expects SemanticsAction.tap.

@rkishan516

Copy link
Copy Markdown
Contributor Author

@flutter-zl Good catch, thanks for the repro. Updated the PR so the mouse branch is skipped when pointerdown, pointerup and click arrive in the same task, so screen readers still get SemanticsAction.tap, and added a test for it.

Chrome turns screen reader activation into mouse pointerdown, pointerup and
click events dispatched in one task. Only flush pointer events for mouse
clicks once debouncing has started, which happens after the pointerdown's
task, so screen reader activation keeps producing SemanticsAction.tap.
@rkishan516
rkishan516 force-pushed the web-semantics-mouse-click-position branch from 50e3f8f to 0792f49 Compare October 31, 2026 05:35
@rkishan516
rkishan516 requested a review from flutter-zl October 31, 2026 05:35

@flutter-zl flutter-zl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the quick update, this looks good to me.

I rebuilt my demos on the new head and verified with VoiceOver on macOS Chrome: activating "Semantics only button" now counts up on https://flutter-demo-82-after.web.app, so screen reader taps on Semantics(onTap: ...) nodes work again. A physical mouse click is flushed as pointer events, matching the behavior with semantics off.

@rkishan516 rkishan516 added the autosubmit Merge PR when tree becomes green via auto submit App label Nov 3, 2026
@auto-submit
auto-submit Bot added this pull request to the merge queue Nov 3, 2026
Merged via the queue into flutter:master with commit c664bdd Nov 3, 2026
22 of 23 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Nov 3, 2026
auto-submit Bot pushed a commit to flutter/packages that referenced this pull request Nov 3, 2026
Roll Flutter from fcfb4f203eae to 934720261be0 (38 revisions)

flutter/flutter@fcfb4f2...9347202

2026-11-03 [email protected] Roll Skia from 5bceaa033999 to c733ad3e37cb (5 revisions) (flutter/flutter#195872)
2026-11-03 [email protected] Roll Skia from 34d2bf479385 to 5bceaa033999 (9 revisions) (flutter/flutter#195864)
2026-11-03 [email protected] fix: prefer pointer events over semantics tap for mouse clicks on web (flutter/flutter#191529)
2026-11-03 [email protected] [VPAT] Expose a semantics text field value while it is not being edited (flutter/flutter#195445)
2026-11-03 [email protected] [Impeller] Make a GL context current before constructing an AiksContext without a surface (flutter/flutter#195629)
2026-11-03 [email protected] [flutter_tools] Eliminate globals.dart from project.dart (flutter/flutter#194066)
2026-11-03 [email protected] [tool] Complete context runner elimination and migrate AppContext usages to ToolContext (flutter/flutter#190797)
2026-11-03 [email protected] [web] Unify font collections between CanvasKit and Skwasm (flutter/flutter#195539)
2026-11-03 [email protected] [Android] Replay predictive back state for retained engines (flutter/flutter#192286)
2026-11-03 [email protected] [vulkan]: Adds depth/stencil synchronization for subpasses (flutter/flutter#195689)
2026-11-03 [email protected] [web] Honor requested upscaling in instantiateImageCodecWithSize and ImageDescriptor.instantiateCodec (flutter/flutter#195836)
2026-11-03 [email protected] Roll Skia from 866b61ca92c9 to 34d2bf479385 (9 revisions) (flutter/flutter#195848)
2026-11-03 [email protected] Roll Fuchsia Test Scripts from xl1LWBuppzu-BnOzV... to B6zarykZp6PKWRueg... (flutter/flutter#195847)
2026-11-03 [email protected] Remove unused ffi (flutter/flutter#195710)
2026-11-02 [email protected] Revert: [Android] Let the raster thread use every core of a chip without little cores (flutter/flutter#195838)
2026-11-02 [email protected] Run 10 impeller iOS benchmarks as one devicelab task (flutter/flutter#195661)
2026-11-02 [email protected] Use named uniforms instead of setFloat in StretchEffect (flutter/flutter#195372)
2026-11-02 [email protected] [flutter_tools] Report when the browser disconnects during a web test run (flutter/flutter#192198)
2026-11-02 [email protected] fix: Resolve memory leak in image_list example (flutter/flutter#189526)
2026-11-02 [email protected] Only run the common local engine configurations in presubmit (flutter/flutter#195688)
2026-11-02 [email protected] Update gcloud_version to version [email protected] (flutter/flutter#195769)
2026-11-02 [email protected] [Impeller] Order the Vulkan copy-texture-to-texture layout transition after the copy (flutter/flutter#195814)
2026-11-02 [email protected] [Impeller] Order the Vulkan ResizeTexture layout transition after its blit (flutter/flutter#195721)
2026-11-02 [email protected] [web] Fix CanvasKit fragment shader uniforms being freed or mutated after recording (flutter/flutter#195718)
2026-11-02 [email protected] Roll Fuchsia Linux SDK from MqypI6RNyVZ_FLUE-... to T_-6-6X1lDMghhAvX... (flutter/flutter#195809)
2026-11-02 [email protected] [Android] Log why Impeller falls back to OpenGLES (flutter/flutter#194764)
2026-11-02 [email protected] Roll Skia from 3f35fb6e591b to 866b61ca92c9 (11 revisions) (flutter/flutter#195802)
2026-11-02 [email protected] Roll Skia from 3f35fb6e591b to 866b61ca92c9 (11 revisions) (flutter/flutter#195802)
2026-11-02 [email protected] Only run the common local engine configurations in presubmit (flutter/flutter#195688)
2026-11-02 [email protected] Balance tool_integration_tests subshards by expected duration (flutter/flutter#195628)
2026-11-02 [email protected] [web_paragraph] Map code units without a cluster to a cluster (flutter/flutter#195384)
2026-11-02 [email protected] [web] Hide synthetic autofill inputs from assistive technology (flutter/flutter#195618)
2026-11-02 [email protected] Roll Packages from 3742a4e to 3f34a6f (8 revisions) (flutter/flutter#195795)
2026-11-02 [email protected] [web] Unskip web tests that pass on CanvasKit and Skwasm: platform_view_test, route_notification_messages_test (flutter/flutter#195605)
2026-11-02 [email protected] Roll Skia from e5ad3868b8db to 3f35fb6e591b (1 revision) (flutter/flutter#195789)
2026-11-02 [email protected] Roll Skia from f5215e74aa16 to e5ad3868b8db (3 revisions) (flutter/flutter#195779)
2026-11-02 [email protected] Roll Skia from e43478a2f9f8 to f5215e74aa16 (1 revision) (flutter/flutter#195776)
2026-11-01 [email protected] Roll Fuchsia Linux SDK from kW0A0xxNkqf38ljm4... to MqypI6RNyVZ_FLUE-... (flutter/flutter#195768)

If this roll has caused a breakage, revert this CL and set the roller
to dry run mode using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose
...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD engine flutter/engine related. See also e: labels. platform-web Web applications specifically team-web Owned by Web platform team

Projects

None yet

2 participants