Skip to content

Conversation

@mdebbar
Copy link
Contributor

@mdebbar mdebbar commented Jul 30, 2025

It turns out iOS Safari in some cases tracks timers that are scheduled from within a pointerdown listener, and it delays the click event until those timers have expired (with a max waiting time of 350ms or so).

The ClickDebouncer sets a timer of 200ms to see if a click event is received by then. But because of the Safari behavior explained above, the click event will always arrive right after the ClickDebouncer's timer, so we always misattribute the click event.

Fixes #172180

@github-actions github-actions bot added engine flutter/engine related. See also e: labels. platform-web Web applications specifically labels Jul 30, 2025
Copy link
Contributor

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

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 introduces a workaround for an iOS Safari behavior where click events are delayed if a timer is scheduled from a pointerdown listener. The fix cleverly delays the start of the ClickDebouncer's timer by using Timer.run, ensuring the click event is correctly handled. The changes are well-implemented and include corresponding test updates. My feedback includes suggestions to add documentation to the new methods in accordance with the Flutter style guide and a minor improvement to the test helper for better robustness.

Copy link
Contributor

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

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 introduces a workaround for an iOS Safari behavior where click events are delayed if a timer is set in a pointerdown listener. The fix defers the timer creation in ClickDebouncer using Timer.run. The approach is sound, but the implementation introduces a race condition that could lead to leaked timers if multiple pointerdown events occur in the same event loop. I've provided a detailed comment with a suggested fix for this issue. The test changes correctly adapt to the new asynchronous behavior.

@mdebbar mdebbar requested a review from harryterkelsen July 31, 2025 16:05
Copy link
Contributor

@harryterkelsen harryterkelsen left a comment

Choose a reason for hiding this comment

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

LGTM! Nice job finding a fix for a tricky bug

@mdebbar mdebbar added the autosubmit Merge PR when tree becomes green via auto submit App label Jul 31, 2025
@auto-submit auto-submit bot added this pull request to the merge queue Jul 31, 2025
Merged via the queue into flutter:master with commit b20149b Jul 31, 2025
176 checks passed
@flutter-dashboard flutter-dashboard bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Jul 31, 2025
@mdebbar mdebbar added the cp: beta cherry pick this pull request to beta release candidate branch label Jul 31, 2025
flutteractionsbot pushed a commit to flutteractionsbot/flutter that referenced this pull request Jul 31, 2025
…r#172995)

It turns out iOS Safari in some cases tracks timers that are scheduled
from within a `pointerdown` listener, and it delays the `click` event
until those timers have expired (with a max waiting time of 350ms or
so).

The `ClickDebouncer` sets a timer of 200ms to see if a `click` event is
received by then. But because of the Safari behavior explained above,
the `click` event will always arrive right after the `ClickDebouncer`'s
timer, so we always misattribute the `click` event.

Fixes flutter#172180
auto-submit bot pushed a commit to flutter/packages that referenced this pull request Aug 1, 2025
Roll Flutter from c3279ca to 871849e (56 revisions)

flutter/flutter@c3279ca...871849e

2025-08-01 engine-flutter-autoroll@skia.org Roll Dart SDK from 6832e04cf2f9 to 6e1bb159c860 (8 revisions) (flutter/flutter#173119)
2025-08-01 derekx@google.com Add `--profile-startup` flag to `flutter run` (flutter/flutter#172990)
2025-08-01 32538273+ValentinVignal@users.noreply.github.com Add `side` to `RadioThemeData` (flutter/flutter#171945)
2025-08-01 katelovett@google.com Update GCA instructions (flutter/flutter#173001)
2025-08-01 jssaadeh@outlook.com [engine] Null aware elements clean-ups (flutter/flutter#173075)
2025-08-01 engine-flutter-autoroll@skia.org Roll Packages from db6988d to f0645d8 (3 revisions) (flutter/flutter#173111)
2025-08-01 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Reland licenses cpp switch 3 (#173063)" (flutter/flutter#173113)
2025-08-01 magder@google.com Update embedder API CODEOWNERS (flutter/flutter#173081)
2025-08-01 magder@google.com Move android_obfuscate_test from devicelab into tools integration.shard (flutter/flutter#169798)
2025-08-01 jhy03261997@gmail.com [A11y] RangeSlider should have 2 focus node (flutter/flutter#172729)
2025-07-31 chinmaygarde@google.com Upload the linux arm64 embedder to cloud buckets. (flutter/flutter#173068)
2025-07-31 30870216+gaaclarke@users.noreply.github.com Reland licenses cpp switch 3 (flutter/flutter#173063)
2025-07-31 bkonyi@google.com [ Tool ] Mark IOOverrides subclasses as `final` (flutter/flutter#173078)
2025-07-31 chenmingding.cmd@alibaba-inc.com [macOS] Remove duplicate object initialization (flutter/flutter#171767)
2025-07-31 magder@google.com Redistribute Android test owners (flutter/flutter#172886)
2025-07-31 magder@google.com Avoid negatives in the styleguide.md (flutter/flutter#172917)
2025-07-31 engine-flutter-autoroll@skia.org Roll Skia from 42e3bed42110 to 49e39cd3cf18 (7 revisions) (flutter/flutter#173069)
2025-07-31 ybz975218925@gmail.com Fix the issue where calling showOnScreen on a sliver after a pinned child in SliverMainAxisGroup does not reveal it. (flutter/flutter#171339)
2025-07-31 31685655+SalehTZ@users.noreply.github.com Improve assertion message in `EdgeInsetsDirectional.resolve` (flutter/flutter#172099)
2025-07-31 30870216+gaaclarke@users.noreply.github.com licenses_cpp: Switched to lexically_relative for 2x speed boost. (flutter/flutter#173048)
2025-07-31 a-siva@users.noreply.github.com Add dartvm to the dart_sdk_entitlement_config list. (flutter/flutter#173044)
2025-07-31 mdebbar@google.com [web] ClickDebouncer workaround for iOS Safari click behavior (flutter/flutter#172995)
2025-07-31 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Reland licenses cpp switch 2 (#172996)" (flutter/flutter#173059)
2025-07-31 mdebbar@google.com [web] Text editing test accepts both behaviors in Firefox (flutter/flutter#172767)
2025-07-31 30870216+gaaclarke@users.noreply.github.com Reland licenses cpp switch 2 (flutter/flutter#172996)
2025-07-31 engine-flutter-autoroll@skia.org Roll Packages from d914120 to db6988d (2 revisions) (flutter/flutter#173039)
2025-07-31 737941+loic-sharma@users.noreply.github.com Add a SliverList code sample (flutter/flutter#172986)
2025-07-31 1083941774@qq.com fix: adjust scrollable size assertion with tolerance (flutter/flutter#171426)
2025-07-31 30870216+gaaclarke@users.noreply.github.com impeller: Shrink `Command` 40 bytes (flutter/flutter#173004)
2025-07-31 mdebbar@google.com [web] Remove outdated comment about HTML renderer (flutter/flutter#172877)
2025-07-31 engine-flutter-autoroll@skia.org Roll Skia from ff4da49305c5 to 42e3bed42110 (1 revision) (flutter/flutter#173038)
2025-07-31 engine-flutter-autoroll@skia.org Roll Skia from 7d468a4868cb to ff4da49305c5 (3 revisions) (flutter/flutter#173028)
2025-07-31 engine-flutter-autoroll@skia.org Roll Skia from 951277895c86 to 7d468a4868cb (1 revision) (flutter/flutter#173027)
2025-07-31 a-siva@users.noreply.github.com Manual roll of Dart from 14ea8d342149 to 6832e04cf2f9 (flutter/flutter#173015)
2025-07-31 engine-flutter-autoroll@skia.org Roll Skia from 8bdf4cdcf4ed to 951277895c86 (2 revisions) (flutter/flutter#173022)
2025-07-31 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from bQVQlLssTxxLjoDU0... to xo_bqOoFuBKFmgKxn... (flutter/flutter#173021)
2025-07-31 31685655+SalehTZ@users.noreply.github.com feat: Add `cursorHeight` to `DropdownMenu` (flutter/flutter#172615)
2025-07-31 engine-flutter-autoroll@skia.org Roll Skia from a3347cee2d73 to 8bdf4cdcf4ed (3 revisions) (flutter/flutter#173013)
2025-07-31 bkonyi@google.com [ Widget Preview ] Add `--web-server` support (flutter/flutter#172978)
2025-07-30 jmccandless@google.com Bump customer tests for zulip fix 2 (flutter/flutter#173003)
2025-07-30 jssaadeh@outlook.com Migrate to null aware elements - Part 4 (flutter/flutter#172322)
2025-07-30 fluttergithubbot@gmail.com Marks Linux linux_feature_flags_test to be unflaky (flutter/flutter#172629)
2025-07-30 bkonyi@google.com [ Widget Previews ] Add support for `MultiPreview`s (flutter/flutter#172852)
2025-07-30 30870216+gaaclarke@users.noreply.github.com Made licenses_cpp simpatico with google licenses (flutter/flutter#172991)
2025-07-30 engine-flutter-autoroll@skia.org Roll Skia from d579722d65c6 to a3347cee2d73 (6 revisions) (flutter/flutter#172989)
2025-07-30 ricardodalarme@outlook.com Migrate to null aware elements - Part 5 (flutter/flutter#172418)
...
korca0220 added a commit to korca0220/flutter that referenced this pull request Aug 4, 2025
…lutter into add-non-uniform-table-border

* 'add-non-uniform-table-border' of github.com:korca0220/flutter: (185 commits)
  Refactor `distinctVisibleOuterColors` -> private method
  Refactor Modify docs and method name
  Refactor Remove comments
  Refactor Modify docs and method parameters
  Feat Add tests
  Feat Add to non-uniform TableBorder - Add method for check outerUniform - Add internal helper method
  Add dartvm to the dart_sdk_entitlement_config list. (flutter#173044)
  [web] ClickDebouncer workaround for iOS Safari click behavior (flutter#172995)
  Reverts "Reland licenses cpp switch 2 (flutter#172996)" (flutter#173059)
  [web] Text editing test accepts both behaviors in Firefox (flutter#172767)
  Reland licenses cpp switch 2 (flutter#172996)
  Roll Packages from d914120 to db6988d (2 revisions) (flutter#173039)
  Add a SliverList code sample (flutter#172986)
  fix: adjust scrollable size assertion with tolerance (flutter#171426)
  impeller: Shrink `Command` 40 bytes (flutter#173004)
  [web] Remove outdated comment about HTML renderer (flutter#172877)
  Roll Skia from ff4da49305c5 to 42e3bed42110 (1 revision) (flutter#173038)
  Roll Skia from 7d468a4868cb to ff4da49305c5 (3 revisions) (flutter#173028)
  Roll Skia from 951277895c86 to 7d468a4868cb (1 revision) (flutter#173027)
  Manual roll of Dart from 14ea8d342149 to 6832e04cf2f9 (flutter#173015)
  ...

# Conflicts:
#	packages/flutter/lib/src/rendering/table_border.dart
#	packages/flutter/test/rendering/table_border_test.dart
@mdebbar mdebbar deleted the semantics_menu_timer_in_timer branch August 5, 2025 17:30
ksokolovskyi pushed a commit to ksokolovskyi/flutter that referenced this pull request Aug 19, 2025
…r#172995)

It turns out iOS Safari in some cases tracks timers that are scheduled
from within a `pointerdown` listener, and it delays the `click` event
until those timers have expired (with a max waiting time of 350ms or
so).

The `ClickDebouncer` sets a timer of 200ms to see if a `click` event is
received by then. But because of the Safari behavior explained above,
the `click` event will always arrive right after the `ClickDebouncer`'s
timer, so we always misattribute the `click` event.

Fixes flutter#172180
github-merge-queue bot pushed a commit that referenced this pull request Aug 19, 2025
When using VoiceOver, clicking the button through `ctrl+opt+space`
causes the browser to send `pointerdown`, `pointerup` and `click` events
successively within the same event loop. This case wasn't handled
correct by the recent `ClickDebouncer` change here:
#172995

More details:

We currently wait until the end of the event loop to set the
`ClickDebouncer`'s state. When other events arrive before the end of the
event loop, they expect the `state` to already be set.

The fix is to set the `state` immediately to allow events to be queued
right away, but still keep the debouncing delayed until the end of the
event loop so that Safari continues to work correctly (issue:
#172180)

Fixes #173741
SydneyBao pushed a commit to SydneyBao/flutter that referenced this pull request Aug 22, 2025
When using VoiceOver, clicking the button through `ctrl+opt+space`
causes the browser to send `pointerdown`, `pointerup` and `click` events
successively within the same event loop. This case wasn't handled
correct by the recent `ClickDebouncer` change here:
flutter#172995

More details:

We currently wait until the end of the event loop to set the
`ClickDebouncer`'s state. When other events arrive before the end of the
event loop, they expect the `state` to already be set.

The fix is to set the `state` immediately to allow events to be queued
right away, but still keep the debouncing delayed until the end of the
event loop so that Safari continues to work correctly (issue:
flutter#172180)

Fixes flutter#173741
SydneyBao pushed a commit to SydneyBao/flutter that referenced this pull request Aug 22, 2025
When using VoiceOver, clicking the button through `ctrl+opt+space`
causes the browser to send `pointerdown`, `pointerup` and `click` events
successively within the same event loop. This case wasn't handled
correct by the recent `ClickDebouncer` change here:
flutter#172995

More details:

We currently wait until the end of the event loop to set the
`ClickDebouncer`'s state. When other events arrive before the end of the
event loop, they expect the `state` to already be set.

The fix is to set the `state` immediately to allow events to be queued
right away, but still keep the debouncing delayed until the end of the
event loop so that Safari continues to work correctly (issue:
flutter#172180)

Fixes flutter#173741
mboetger pushed a commit to mboetger/flutter that referenced this pull request Sep 18, 2025
…r#172995)

It turns out iOS Safari in some cases tracks timers that are scheduled
from within a `pointerdown` listener, and it delays the `click` event
until those timers have expired (with a max waiting time of 350ms or
so).

The `ClickDebouncer` sets a timer of 200ms to see if a `click` event is
received by then. But because of the Safari behavior explained above,
the `click` event will always arrive right after the `ClickDebouncer`'s
timer, so we always misattribute the `click` event.

Fixes flutter#172180
mboetger pushed a commit to mboetger/flutter that referenced this pull request Sep 18, 2025
When using VoiceOver, clicking the button through `ctrl+opt+space`
causes the browser to send `pointerdown`, `pointerup` and `click` events
successively within the same event loop. This case wasn't handled
correct by the recent `ClickDebouncer` change here:
flutter#172995

More details:

We currently wait until the end of the event loop to set the
`ClickDebouncer`'s state. When other events arrive before the end of the
event loop, they expect the `state` to already be set.

The fix is to set the `state` immediately to allow events to be queued
right away, but still keep the debouncing delayed until the end of the
event loop so that Safari continues to work correctly (issue:
flutter#172180)

Fixes flutter#173741
korca0220 pushed a commit to korca0220/flutter that referenced this pull request Sep 22, 2025
…r#172995)

It turns out iOS Safari in some cases tracks timers that are scheduled
from within a `pointerdown` listener, and it delays the `click` event
until those timers have expired (with a max waiting time of 350ms or
so).

The `ClickDebouncer` sets a timer of 200ms to see if a `click` event is
received by then. But because of the Safari behavior explained above,
the `click` event will always arrive right after the `ClickDebouncer`'s
timer, so we always misattribute the `click` event.

Fixes flutter#172180
korca0220 pushed a commit to korca0220/flutter that referenced this pull request Sep 22, 2025
When using VoiceOver, clicking the button through `ctrl+opt+space`
causes the browser to send `pointerdown`, `pointerup` and `click` events
successively within the same event loop. This case wasn't handled
correct by the recent `ClickDebouncer` change here:
flutter#172995

More details:

We currently wait until the end of the event loop to set the
`ClickDebouncer`'s state. When other events arrive before the end of the
event loop, they expect the `state` to already be set.

The fix is to set the `state` immediately to allow events to be queued
right away, but still keep the debouncing delayed until the end of the
event loop so that Safari continues to work correctly (issue:
flutter#172180)

Fixes flutter#173741
Jaineel-Mamtora pushed a commit to Jaineel-Mamtora/flutter_forked that referenced this pull request Sep 24, 2025
When using VoiceOver, clicking the button through `ctrl+opt+space`
causes the browser to send `pointerdown`, `pointerup` and `click` events
successively within the same event loop. This case wasn't handled
correct by the recent `ClickDebouncer` change here:
flutter#172995

More details:

We currently wait until the end of the event loop to set the
`ClickDebouncer`'s state. When other events arrive before the end of the
event loop, they expect the `state` to already be set.

The fix is to set the `state` immediately to allow events to be queued
right away, but still keep the debouncing delayed until the end of the
event loop so that Safari continues to work correctly (issue:
flutter#172180)

Fixes flutter#173741
lucaantonelli pushed a commit to lucaantonelli/flutter that referenced this pull request Nov 21, 2025
…r#172995)

It turns out iOS Safari in some cases tracks timers that are scheduled
from within a `pointerdown` listener, and it delays the `click` event
until those timers have expired (with a max waiting time of 350ms or
so).

The `ClickDebouncer` sets a timer of 200ms to see if a `click` event is
received by then. But because of the Safari behavior explained above,
the `click` event will always arrive right after the `ClickDebouncer`'s
timer, so we always misattribute the `click` event.

Fixes flutter#172180
lucaantonelli pushed a commit to lucaantonelli/flutter that referenced this pull request Nov 21, 2025
When using VoiceOver, clicking the button through `ctrl+opt+space`
causes the browser to send `pointerdown`, `pointerup` and `click` events
successively within the same event loop. This case wasn't handled
correct by the recent `ClickDebouncer` change here:
flutter#172995

More details:

We currently wait until the end of the event loop to set the
`ClickDebouncer`'s state. When other events arrive before the end of the
event loop, they expect the `state` to already be set.

The fix is to set the `state` immediately to allow events to be queued
right away, but still keep the debouncing delayed until the end of the
event loop so that Safari continues to work correctly (issue:
flutter#172180)

Fixes flutter#173741
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cp: beta cherry pick this pull request to beta release candidate branch engine flutter/engine related. See also e: labels. platform-web Web applications specifically

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DropdownButton touchscreen selection blanks iPhone web browser screen when SemanticsBinding.instance.EnsureSemantics() is enabled

2 participants