-
Notifications
You must be signed in to change notification settings - Fork 6k
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
[web] fix clicks on merged semantic nodes (attempt #2) #47360
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
Added a small note in case the EventTarget generating the click/tap events may have multiple listeners (not just bubble up)
// https://github.com/flutter/flutter/issues/134842 | ||
click.stopPropagation(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another way I've seen this handled (in jQuery, admittedly) is with stopImmediatePropagation
which was a "flag for the framework" so other listeners attached to the same EventTarget
wouldn't run (even without bubbling). (IIRC this was just a boolean flag written into the event object itself that other bits of the framework knew how to check).
We may need something like this in Flutter web, now that we're juggling potentially multiple sets of listeners into the same dom element? maybe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh, TIL! Looks like it's a standard API, not just jQuery. In this case we don't have multiple listeners on the same element. All the elements in question are created by the engine and we are attaching just one click listener (unless there's a bug, but that's a different story). So we only need to stop propagation to ancestor elements. Do you think we should still do something like this in this case?
dc326f0
to
56c1246
Compare
auto label is removed for flutter/engine/47360, due to - The status or check suite Linux linux_web_engine has failed. Please fix the issues identified (or deflake) before re-applying this label. |
auto label is removed for flutter/engine/47360, due to - The status or check suite Linux linux_android_debug_engine has failed. Please fix the issues identified (or deflake) before re-applying this label. |
…138091) flutter/engine@3e3be5e...117d47a 2023-11-08 skia-flutter-autoroll@skia.org Roll Skia from fce71a80b0a2 to a4cce5236dcf (1 revision) (flutter/engine#47807) 2023-11-08 chris@bracken.jp [macOS] Clean up resources in ViewController tests (flutter/engine#47792) 2023-11-08 skia-flutter-autoroll@skia.org Roll Skia from f3d250126ba9 to fce71a80b0a2 (1 revision) (flutter/engine#47796) 2023-11-08 skia-flutter-autoroll@skia.org Roll Skia from b4fa927468e6 to f3d250126ba9 (1 revision) (flutter/engine#47793) 2023-11-08 flar@google.com [Impeller] Add Rect::GetNormalizingTransform to handle UV coordinate conversion (flutter/engine#47775) 2023-11-08 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Remove Fuchsia Mac SDK from DEPS" (flutter/engine#47791) 2023-11-08 yjbanov@google.com [web] fix clicks on merged semantic nodes (attempt #2) (flutter/engine#47360) 2023-11-08 skia-flutter-autoroll@skia.org Roll Skia from 0f78e5f765d3 to b4fa927468e6 (1 revision) (flutter/engine#47788) 2023-11-08 skia-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from VcFEJiUUTYwkhEAlJ... to sD8HRA4JmXczujkqO... (flutter/engine#47785) 2023-11-08 jiahaog@users.noreply.github.com Fix narrowing conversion lint (flutter/engine#47740) 2023-11-08 chris@bracken.jp [macOS] Bail out of tests if engine not running (flutter/engine#47771) 2023-11-08 skia-flutter-autoroll@skia.org Roll Skia from f91d39395e85 to 0f78e5f765d3 (1 revision) (flutter/engine#47776) 2023-11-08 chris@bracken.jp [testing] Extract StreamCapture test utility (flutter/engine#47774) 2023-11-08 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Promote fuchsia build v2 to prod." (flutter/engine#47779) 2023-11-08 aam@google.com Include updated locations for dart third_party components into license ignore-list. (flutter/engine#47770) 2023-11-07 chillers@google.com Remove Fuchsia Mac SDK from DEPS (flutter/engine#47700) 2023-11-07 skia-flutter-autoroll@skia.org Roll Skia from 030e21befbc9 to f91d39395e85 (6 revisions) (flutter/engine#47769) 2023-11-07 chinmaygarde@google.com [Impeller] Support static thread safety analysis with condition variables. (flutter/engine#47763) 2023-11-07 godofredoc@google.com Promote fuchsia build v2 to prod. (flutter/engine#47729) 2023-11-07 737941+loic-sharma@users.noreply.github.com [Windows] Reduce warnings produced by unit tests (flutter/engine#47724) Also rolling transitive DEPS: fuchsia/sdk/core/linux-amd64 from VcFEJiUUTYwk to sD8HRA4JmXcz If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-engine-flutter-autoroll Please CC bdero@google.com,rmistry@google.com,zra@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I revisited this PR after encountering issues with semantics on the web. Although it’s 8 months old, the code is still relevant, so I want to document here what I’ve found
// The event landed on an non-tappable target. Assume this won't lead to | ||
// double clicks and forward the event to the framework. | ||
_sendToFramework(event, data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This actually leads to double clicks :)
Consider the following scenario:
<flt-semantics flt-tappable id="flt-semantic-node-1">
<flt-semantics id="flt-semantic-node-2"> </flt-semantics>
</flt-semantics>
If pointer events are triggered on flt-semantic-node-2
, we send them to framework in this condition (flt-tappable
is not present), and isDebouncing
will be false
.
On the other hand, click
event on the flt-semantic-node-2
propagates to flt-semantic-node-1
that has flt-tappable
and is also sent to framework (isDebouncing
is false
).
(flutter/flutter#147050 (comment) and most likely issue flutter/flutter#150020)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I fixed one variant of this in #53694. Just using the event.target
is probably naive. It should start with event.target
and then walk up to the nearest tappable ancestor.
void onClick(DomEvent click, int semanticsNodeId, bool isListening) { | ||
assert(click.type == 'click'); | ||
|
||
if (!isDebouncing) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it is possible that isDebouncing
is true
but _state.target
is not equal to click.target
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Due to the problem you pointed out above (the sketchy target detection), it seems quite possible.
// The 200ms duration was chosen empirically by testing tapping, mouse | ||
// clicking, trackpad tapping and clicking, as well as the following | ||
// screen readers: TalkBack on Android, VoiceOver on macOS, Narrator/ | ||
// NVDA/JAWS on Windows. 200ms seemed to hit the sweet spot by | ||
// satisfying the following: | ||
// * It was short enough that delaying the `pointerdown` still allowed | ||
// drag gestures to begin reasonably soon (e.g. scrolling). | ||
// * It was long enough to register taps and clicks. | ||
// * It was successful at detecting taps generated by all tested | ||
// screen readers. | ||
timer: Timer(const Duration(milliseconds: 200), _onTimerExpired), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not working for long taps (issue flutter/flutter#147050).
In long tap case, it is possible that pointerup
event is fired after 200 milliseconds since pointerdown
, and as a result isDebounce
will be false
when click
event is received, leading to duplication.
In the previous solution (that is still exists in parallel), timer was added after pointerup
(and others) as well, so it is probably should fix the issue
engine/lib/web_ui/lib/src/engine/semantics/semantics.dart
Lines 2232 to 2250 in e5215e6
const List<String> pointerEventTypes = <String>[ | |
'pointerdown', | |
'pointermove', | |
'pointerleave', | |
'pointerup', | |
'pointercancel', | |
'touchstart', | |
'touchend', | |
'touchmove', | |
'touchcancel', | |
'mousedown', | |
'mousemove', | |
'mouseleave', | |
'mouseup', | |
]; | |
if (pointerEventTypes.contains(event.type)) { | |
_temporarilyDisableBrowserGestureMode(); | |
} |
engine/lib/web_ui/lib/src/engine/semantics/semantics.dart
Lines 2184 to 2195 in e5215e6
/// Disables browser gestures temporarily because pointer events were detected. | |
/// | |
/// This is used to deduplicate gestures detected by Flutter and gestures | |
/// detected by the browser. Flutter-detected gestures have higher precedence. | |
void _temporarilyDisableBrowserGestureMode() { | |
const Duration kDebounceThreshold = Duration(milliseconds: 500); | |
_getGestureModeClock()!.datetime = _now().add(kDebounceThreshold); | |
if (_gestureMode != GestureMode.pointerEvents) { | |
_gestureMode = GestureMode.pointerEvents; | |
_notifyGestureModeListeners(); | |
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is correct, and it's on my radar to fix. This method was enough for the screen reader case. However, we're trying to make it so semantics can be always enabled, and therefore it must also work for all interactions without the screen reader. This code needs a revision.
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 aSemanticsAction.tap
to the framework. This ensures that there cannot be more than oneSemanticsAction.tap
sent to the framework.Fixes flutter/flutter#134842