Skip to content

[AppKit Gestures] Double clicking a YouTube video pauses it - #75624

Merged
webkit-commit-queue merged 1 commit into
WebKit:mainfrom
aprotyas:eng/325977
Oct 2, 2026
Merged

webkit-commit-queue merged 1 commit into
WebKit:mainfrom
aprotyas:eng/325977

Conversation

@aprotyas

@aprotyas aprotyas commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

5863297

[AppKit Gestures] Double clicking a YouTube video pauses it
https://bugs.webkit.org/show_bug.cgi?id=325977
rdar://187488912

Reviewed by Wenson Hsieh.

We have a bug where a double click sequence produces this sequence:
{ click, click, dblclick, click }. Thus, content that toggles per click,
such as the youtube.com video player, ends up in the wrong state after a
double click sequence.

Typically, the gesture that sends the double click would not recognize
simultaneously with the single click gesture, so exactly one of them
handles the second click. However, NSGestureRecognizer holds back events
from other gestures while one that requires two clicks is pending (see
319115@main and rdar://184563102), and so we cannot establish the
appropriate simultaneity relation between the two gestures.

Instead, in this patch, we send the double click eagerly before
committing to it, and we have the web process skip the click when it
dispatches the double click. Some more details in-line.

Instead, in this patch, when the second click completes a double click,
we reflect that in the synthetic click commit: the web process
dispatches it with a click count of 2 (a click with detail 2, followed
by a dblclick) to the node the single click targets, if a dblclick
listener can receive it there, and as a regular click otherwise. The
second click is then delivered once, and to the same node either way.

Test: AppKitGesturesTests.DoubleClick.doubleClickWithListenerOnUnselectableContentFiresOneClickPerPress
      AppKitGesturesTests.DoubleClick.doubleClickWithoutListenerOnUnselectableContentFiresTwoSingleClicks
      AppKitGesturesTests.DoubleClick.doubleClickWithAncestorListenerOnStyleAdjustedContentFiresOneClickPerPress
      AppKitGesturesTests.DoubleClick.doubleClickNextToClickableElementTargetsItWithBothClicks

* Source/WebKit/Scripts/webkit/messages.py:
(headers_for_type):
* Source/WebKit/Shared/Cocoa/GestureTypes.h:
* Source/WebKit/Shared/Cocoa/GestureTypes.serialization.in:
* Source/WebKit/UIProcess/Cocoa/WebPageProxyCocoa.mm:
(WebKit::WebPageProxy::commitPotentialTap):
* Source/WebKit/UIProcess/WebPageProxy.h:
* Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView _singleTapRecognized:]):
* Source/WebKit/UIProcess/mac/AppKitGestures/WKAppKitGestureController.h:
* Source/WebKit/UIProcess/mac/AppKitGestures/WKAppKitGestureController.mm:
(-[WKAppKitGestureController domDoubleClickGestureRecognized:]):
(-[WKAppKitGestureController _handleClickEnded:]):
* Source/WebKit/UIProcess/mac/AppKitGestures/WKAppKitGestureController.swift:
(WKAppKitGestureController.takeCompletedDOMDoubleClick):
* Source/WebKit/UIProcess/mac/AppKitGestures/WKDOMDoubleClickGestureRecognizer.swift:
(state):
(reset):
(takeCompletedDoubleClick):

The two click gestures' actions for a second click can come in either
order, and when the single click gesture's comes first, the DOM double
click gesture's state does not reflect the double click yet. So, we now
record whether a click completed a double click as soon as the click
ends, and whichever action comes first sends the double click.

* Source/WebKit/WebProcess/WebPage/Cocoa/WebPageCocoa.mm:
(WebKit::WebPage::commitPotentialTap):
(WebKit::WebPage::completeSyntheticClick):
* Source/WebKit/WebProcess/WebPage/WebPage.h:
* Source/WebKit/WebProcess/WebPage/WebPage.messages.in:
* Tools/TestWebKitAPI/Tests/WebKit/WebPage/AppKit Gesture Tests/DoubleClickGesturesTests.swift:
(AppKitGesturesTests.doubleClickWithListenerOnUnselectableContentFiresOneClickPerPress):
(AppKitGesturesTests.doubleClickWithoutListenerOnUnselectableContentFiresTwoSingleClicks):
(AppKitGesturesTests.doubleClickWithAncestorListenerOnStyleAdjustedContentFiresOneClickPerPress):
(AppKitGesturesTests.doubleClickNextToClickableElementTargetsItWithBothClicks):
(AppKitGesturesTests.loadUnselectableHTML(_:)):

Canonical link: https://commits.webkit.org/322530@main

a386f15

Misc iOS, visionOS, tvOS & watchOS macOS Linux Windows Apple Internal
✅ 🧪 style   🛠 ios   🛠 mac ✅ 🛠 wpe   🛠 win ✅ 🛠 ios-apple
✅ 🧪 bindings ✅ 🛠 ios-sim ✅ 🛠 mac-AS-debug   🧪 wpe-wk2   🧪 win-tests ✅ 🛠 mac-apple
✅ 🧪 webkitperl   🧪 ios-wk2   🧪 api-mac ✅ 🧪 api-wpe ✅ 🛠 vision-apple
✅ 🧪 webkitpy ✅ 🧪 ios-wk2-wpt ⏳ 🧪 api-mac-debug
  🧪 api-ios   🧪 mac-wk2 ✅ 🛠 gtk3-gcc
  🛠 ios-safer-cpp   🧪 mac-AS-debug-wk2 ✅ 🛠 gtk
✅ 🛠 vision   🧪 mac-wk2-stress   🧪 gtk-wk2
  🛠 vision-sim   🧪 mac-intel-wk2   🧪 api-gtk
✅ 🛠 🧪 unsafe-merge   🧪 vision-wk2   🛠 mac-safer-cpp ✅ 🛠 playstation
✅ 🛠 tv   🧪 mac-site-isolation
  🛠 tv-sim
  🛠 watch
✅ 🛠 watch-sim

@aprotyas aprotyas self-assigned this Oct 2, 2026
@aprotyas aprotyas added the UI Events For bugs related to user interactions like keyboard, mouse, and touch events. label Oct 2, 2026
if (protect([webView _impl])->ignoresAllEvents())
return;

auto location = [gesture locationInView:webView];
auto location = [_domDoubleClickGestureRecognizer locationInView:webView];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit - do we need to protect _domDoubleClickGestureRecognizer on the stack, as gesture?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

good point. Yes, probably, given -locationInView: is non-trivial

@@ -3449,6 +3450,16 @@ static void dispatchSyntheticMouseMove(LocalFrame& localFrame, const WebCore::Fl

Awaitable<std::optional<WebCore::FrameIdentifier>> WebPage::commitPotentialTap(std::optional<WebCore::FrameIdentifier> frameID, OptionSet<WebEventModifier> modifiers, TransactionID lastLayerTreeTransactionId, WebCore::PointerID pointerId)
{
if (std::exchange(m_syntheticClickWasHandledAsDoubleClick, false)) {

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.

I think this can drop a click that was delivered before this change because the commit is suppressed whenever a double click was sent anywhere, but the double click and the single click don't necessarily target the same node (for example elements within the 15px radius of each other)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, good catch. I fixed it with your suggestion below.

@@ -1626,6 +1638,10 @@ - (void)_handleClickEnded:(NSGestureRecognizer *)gesture
return;
}

// If this click completes a double click, send the double click first. The web process then only delivers this

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.

Rather than sending both HandleDoubleClickForDoubleClickAtPoint and CommitPotentialClick and then deduplicating them with a flag the web process, maybe instead just send a single message, like CommitPotentialClick(…, isDoubleClick), and let a single handler choose between double-click and single-click dispatch?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done.

I added a CompletesDoubleClick value that we use as a signal in the web process.

@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Oct 2, 2026
@aprotyas aprotyas removed the merging-blocked Applied to prevent a change from being merged label Oct 2, 2026
@aprotyas aprotyas added the unsafe-merge-queue Applied to send a pull request to merge-queue, but skip building and testing label Oct 2, 2026
@aprotyas

aprotyas commented Oct 2, 2026

Copy link
Copy Markdown
Member Author

Thank you for the reviews!

https://bugs.webkit.org/show_bug.cgi?id=325977
rdar://187488912

Reviewed by Wenson Hsieh.

We have a bug where a double click sequence produces this sequence:
{ click, click, dblclick, click }. Thus, content that toggles per click,
such as the youtube.com video player, ends up in the wrong state after a
double click sequence.

Typically, the gesture that sends the double click would not recognize
simultaneously with the single click gesture, so exactly one of them
handles the second click. However, NSGestureRecognizer holds back events
from other gestures while one that requires two clicks is pending (see
319115@main and rdar://184563102), and so we cannot establish the
appropriate simultaneity relation between the two gestures.

Instead, in this patch, we send the double click eagerly before
committing to it, and we have the web process skip the click when it
dispatches the double click. Some more details in-line.

Instead, in this patch, when the second click completes a double click,
we reflect that in the synthetic click commit: the web process
dispatches it with a click count of 2 (a click with detail 2, followed
by a dblclick) to the node the single click targets, if a dblclick
listener can receive it there, and as a regular click otherwise. The
second click is then delivered once, and to the same node either way.

Test: AppKitGesturesTests.DoubleClick.doubleClickWithListenerOnUnselectableContentFiresOneClickPerPress
      AppKitGesturesTests.DoubleClick.doubleClickWithoutListenerOnUnselectableContentFiresTwoSingleClicks
      AppKitGesturesTests.DoubleClick.doubleClickWithAncestorListenerOnStyleAdjustedContentFiresOneClickPerPress
      AppKitGesturesTests.DoubleClick.doubleClickNextToClickableElementTargetsItWithBothClicks

* Source/WebKit/Scripts/webkit/messages.py:
(headers_for_type):
* Source/WebKit/Shared/Cocoa/GestureTypes.h:
* Source/WebKit/Shared/Cocoa/GestureTypes.serialization.in:
* Source/WebKit/UIProcess/Cocoa/WebPageProxyCocoa.mm:
(WebKit::WebPageProxy::commitPotentialTap):
* Source/WebKit/UIProcess/WebPageProxy.h:
* Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView _singleTapRecognized:]):
* Source/WebKit/UIProcess/mac/AppKitGestures/WKAppKitGestureController.h:
* Source/WebKit/UIProcess/mac/AppKitGestures/WKAppKitGestureController.mm:
(-[WKAppKitGestureController domDoubleClickGestureRecognized:]):
(-[WKAppKitGestureController _handleClickEnded:]):
* Source/WebKit/UIProcess/mac/AppKitGestures/WKAppKitGestureController.swift:
(WKAppKitGestureController.takeCompletedDOMDoubleClick):
* Source/WebKit/UIProcess/mac/AppKitGestures/WKDOMDoubleClickGestureRecognizer.swift:
(state):
(reset):
(takeCompletedDoubleClick):

The two click gestures' actions for a second click can come in either
order, and when the single click gesture's comes first, the DOM double
click gesture's state does not reflect the double click yet. So, we now
record whether a click completed a double click as soon as the click
ends, and whichever action comes first sends the double click.

* Source/WebKit/WebProcess/WebPage/Cocoa/WebPageCocoa.mm:
(WebKit::WebPage::commitPotentialTap):
(WebKit::WebPage::completeSyntheticClick):
* Source/WebKit/WebProcess/WebPage/WebPage.h:
* Source/WebKit/WebProcess/WebPage/WebPage.messages.in:
* Tools/TestWebKitAPI/Tests/WebKit/WebPage/AppKit Gesture Tests/DoubleClickGesturesTests.swift:
(AppKitGesturesTests.doubleClickWithListenerOnUnselectableContentFiresOneClickPerPress):
(AppKitGesturesTests.doubleClickWithoutListenerOnUnselectableContentFiresTwoSingleClicks):
(AppKitGesturesTests.doubleClickWithAncestorListenerOnStyleAdjustedContentFiresOneClickPerPress):
(AppKitGesturesTests.doubleClickNextToClickableElementTargetsItWithBothClicks):
(AppKitGesturesTests.loadUnselectableHTML(_:)):

Canonical link: https://commits.webkit.org/322530@main
@webkit-commit-queue

Copy link
Copy Markdown
Collaborator

Committed 322530@main (5863297): https://commits.webkit.org/322530@main

Reviewed commits have been landed. Closing PR #75624 and removing active labels.

@webkit-commit-queue
webkit-commit-queue merged commit 5863297 into WebKit:main Oct 2, 2026
@webkit-commit-queue webkit-commit-queue removed the unsafe-merge-queue Applied to send a pull request to merge-queue, but skip building and testing label Oct 2, 2026
@aprotyas
aprotyas deleted the eng/325977 branch October 2, 2026 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

UI Events For bugs related to user interactions like keyboard, mouse, and touch events.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants