w3c / pointerevents

Pointer Events
https://w3c.github.io/pointerevents/
Other
68 stars 33 forks source link

Release Implicit Capture, Capture at Ancestor Element #327

Closed msalsbery closed 3 years ago

msalsbery commented 4 years ago

Scenario: A parent and a child element, both with pointerdown event handlers. Depending on application's need, when a pointerdown occurs on the child, it should be ignored and the parent (when pointer down bubbles) will want to capture the pointer:

  1. pointerdown on child
  2. child calls hasPointerCapture and determines pointer is captured implicitly (touch)
  3. child immediately calls releasePointerCapture
  4. pointerdown bubbles to parent
  5. Parent calls setPointerCapture
  6. Parent processes pointermove events until pointerup, pointercancel, or lostpointercapture

The way the W3C Implicit Pointer Capture docs read to me, this should be a reasonable approach.

When there's no implicit capture (mouse device, for example) there's no issue. It works great.

With touch and implicit capture on pointerdown, however, this does not work (only tested on iOS Safari/Firefox/Chrome/Chromium Edge so far). While the gotpointercapture events indicate the parent received capture, the pointermove/pointerup events following capture are not targeted to the parent - in other words, the parent never really got capture. pointermove/pointerup events outside the parent cease, instead of continuing like they should when an element has a pointer captured.

Is this something that should be doable? Should it work as described and the implementation in the browsers is flawed, or is the approach flawed?

Thank you!

NavidZ commented 4 years ago
  1. pointerdown on child
  2. child calls hasPointerCapture and determines pointer is captured implicitly (touch)
  3. child immediately calls releasePointerCapture
  4. pointerdown bubbles to parent
  5. Parent calls setPointerCapture
  6. Parent processes pointermove events until pointerup, pointercancel, or lostpointercapture

Actually the events always bubble regarding of the explicit/implicit capturing. So if the child really doesn't care about any event they could just not have a handler. In other words, your code should work even if you remove the first 3 steps. So for the touch case at first implicit capture goes to the child. Then when the event bubbles and parent calls setpointercapture it effectively steals the capture and after that child will not get the event (as it is not in the propagation path anymore).

With touch and implicit capture on pointerdown, however, this does not work (only tested on iOS Safari/Firefox/Chrome/Chromium Edge so far). While the gotpointercapture events indicate the parent received capture, the pointermove/pointerup events following capture are not targeted to the parent - in other words, the parent never really got capture. pointermove/pointerup events outside the parent cease, instead of continuing like they should when an element has a pointer captured.

This could be a bug in WebKit. One thing I can suggest is to try this on your desktop or on an Android and see if that passes. I expect it to pass. If it did we can then file a bug for Webkit I guess.

msalsbery commented 4 years ago

So for the touch case at first implicit capture goes to the child. Then when the event bubbles and parent calls setpointercapture it effectively steals the capture and after that child will not get the event (as it is not in the propagation path anymore)

Right, got that, but note I'm not even trying to steal capture - step 3 (done by the child) is relinquishing implicit capture. My understanding of the W3C docs is that that's valid.

For context, I'm a maintainer (the "events guy") for a well-used open source project that makes heavy use of pointer events (converted to "gestures") and we're getting more and more users adding overlay elements (custom implementations, other frameworks, etc) who need finer control over pointer event handling in the scenario I described. I raised the "issue" here because first - I'm not sure if I'm missing something in (or misunderstanding) the specification, and second - there seems to be many persons here that know the engines and would know if it's a bug or not.

So if the child really doesn't care about any event they could just not have a handler

That may be the workaround I have to recommend to users, at least for now. It "feels" like a Webkit bug to me...I'll need to get my hands on some other touch devices

Thanks much for the reply! I've followed the Pointer Events specification from the beginning... great work you guys have done, and something I've been wanting for a long time (great to see wide adoption FINALLY!)

NavidZ commented 4 years ago

It "feels" like a Webkit bug to me...I'll need to get my hands on some other touch devices

cc @mustaqahmed @liviutinta in case they have something to test this on like an Android phone or something to confirm it is working on Chrome on other platforms.

cc @graouts for Webkit

Thanks much for the reply! I've followed the Pointer Events specification from the beginning... great work you guys have done, and something I've been wanting for a long time (great to see wide adoption FINALLY!)

And we are very glad to see it being used.

msalsbery commented 4 years ago

in case they have something to test this on like an Android phone or something to confirm it is working on Chrome on other platforms

Tested on Chrome 84.0.4147.111 (Galaxy S7)...works fine (at least to my expectations) there. Capture is released by the child and set to the parent

liviutinta commented 4 years ago

in case they have something to test this on like an Android phone or something to confirm it is working on Chrome on other platforms

Tested on Chrome 84.0.4147.111 (Galaxy S7 and Pixel 3), Chrome 84.0.4147.110 on Google Pixelbook and it works fine. I don't have access to an iOS device. The test page I used is here.

msalsbery commented 4 years ago

I extended @liviutinta 's test page code to something very close to the real-life scenario that brought this issue to my attention:

TEST PAGE

The grey box is the parent, green box is child (from my original description)

If the pointerdown is initiated on the child (green box) in a Webkit browser, the parent (grey box) doesn't truly get capture despite the gotpointercapture event indicating it does...this is shown by the inability to drag the parent if pointer moves outside of the container (black) element (and perhaps worst of all, if pointerup outside of container it's never dispatched to the parent).

On the one non-Webkit device I tested on (Chrome 84 Galaxy S7) and all mouse devices (no implicit capture) it works fine

msalsbery commented 3 years ago

Since this seems to be a Webkit bug, is it something I should file, has it been filed, or is there someone here following who is much more qualified than me to file it?

patrickhlauke commented 3 years ago

@graouts not looked deeply into this myself, but ... does this sound/smell like a webkit bug?

joyzhong commented 3 years ago

FYI we ran across something similar in https://github.com/material-components/material-components-web/issues/6715. I've filed https://bugs.webkit.org/show_bug.cgi?id=220196, please feel free to comment with additional info.

msalsbery commented 3 years ago

@joyzhong great thank you!

msalsbery commented 3 years ago

@joyzhong

I'm finally digging into trying to figure out a workaround for this bug, since unfortunately there's a few devices that use Webkit browsers ;)

This doesn't help my use case necessarily, but for use cases like your CodePen (linked to in your bug report) where there's a child element that doesn't do any pointer event handling, you can try setting pointer-events: none; in the css. That seemed to work in my testing, allowing the parent element to get the capture as expected.

I'll be joining in the conversation on the bug report soon...

msalsbery commented 3 years ago
  1. pointerdown on child
  2. child calls hasPointerCapture and determines pointer is captured implicitly (touch)
  3. child immediately calls releasePointerCapture
  4. pointerdown bubbles to parent
  5. Parent calls setPointerCapture
  6. Parent processes pointermove events until pointerup, pointercancel, or lostpointercapture

Actually the events always bubble regarding of the explicit/implicit capturing. So if the child really doesn't care about any event they could just not have a handler. In other words, your code should work even if you remove the first 3 steps. So for the touch case at first implicit capture goes to the child. Then when the event bubbles and parent calls setpointercapture it effectively steals the capture and after that child will not get the event (as it is not in the propagation path anymore).

@NavidZ

Just an update, as I'm now trying to figure out a workaround for this Webkit bug.

Although your suggestion sounds good, and I mentioned I may have to recommend that to users, it doesn't work. Even without handlers on the child element, the result is the same: the parent calls getPointerCapture successfully, a gotpointercapture is fired for the parent, but the parent doesn't actually get the capture (as shown on the test page I posted). The only workaround I found is to set pointer-events:none on any child elements, which is fine for valid use cases but doesn't apply here.

Edit: Removed last comments...I was incorrect - the gotpointercapture events ARE being fired and bubbling for implicit capture as expected on all tested browsers

patrickhlauke commented 3 years ago

Closing, as this seems related to a Webkit bug and not an issue with the spec itself. Feel free to shout if I've got the wrong impression and I'll reopen.

msalsbery commented 2 years ago

Closing, as this seems related to a Webkit bug and not an issue with the spec itself. Feel free to shout if I've got the wrong impression and I'll reopen.

FYI for everyone experiencing this issue...it has been resolved in Safari 15.5 (see https://bugs.webkit.org/show_bug.cgi?id=220196)

patrickhlauke commented 2 years ago

thanks for the follow-up, @msalsbery