Bug 137069

Summary: iOS8 new "slow tap" heuristic fires mouse compat events despite preventDefault on touchend
Product: WebKit Reporter: Patrick H. Lauke <redux>
Component: UI EventsAssignee: Benjamin Poulain <benjamin>
Status: RESOLVED FIXED    
Severity: Normal CC: benjamin, dbates, ddkilzer, mmfrezabakhshi, rbyers, rkerr, rowan, sabouhallawa, sam, simon.fraser
Priority: P2 Keywords: InRadar
Version: 528+ (Nightly build)   
Hardware: iPhone / iPad   
OS: Other   
URL: http://patrickhlauke.github.io/touch/tests/event-listener_prevent-default-touchend.html
Attachments:
Description Flags
Patch simon.fraser: review+

Description Patrick H. Lauke 2014-09-24 10:55:59 PDT
It appears that iOS8/Safari (and the new WKWebView) implements some form of "slow tap" heuristic in an attempt to work around the classic 300ms delay that normally happens between touchend and the mouse compatibility + click events (which other browsers have worked around more explicitly by supporting things like taking hints from viewport https://bugs.webkit.org/show_bug.cgi?id=122212 or taking a hint from touch-action https://bugs.webkit.org/show_bug.cgi?id=133112).

Quick taps behave as they did before in iOS7, meaning that tapping on a button would generate a sequence of touchstart > [touchmove]+ > touchend > [300ms delay] > mouseover > mouseenter > mousemove > mousedown > mouseup > click

However, "slow" taps (where the time between touchstart and touchend is above approximately 125ms) seem to magically optimise the 300ms away - see https://www.youtube.com/watch?v=k-UiuPp2CK0 - which is an interesting approach (just shame that this doesn't appear to be documented anywhere, and has not been communicated to developers, who are already finding mysterious bugs in their existing code because of it, such as https://github.com/ftlabs/fastclick/issues/262)

In general, I can understand the rationale here: a quick tap is more likely to be part of a double-tap sequence, hence the 300ms delay is necessary as before to wait for a possible second tap, while a slower tap is more likely to be an explicit "activation" intent on part of the user.

It seems, though, that this heuristic also has some unintended side effects (or at least, I can't rationalise why Safari would be doing this): traditionally, cancelling the touchend event (e.g. with preventDefault) would suppress any further mouse compatibility events and click from being fired - see iOS7 https://www.youtube.com/watch?v=HmXvwYcFkps. But the new heuristic in iOS8 seems to kick in here as well: "slow" taps, even when touchend has been preventDefault-ed, now fire mouse compatibility and click - see https://www.youtube.com/watch?v=i787lZCL_YQ

Could I get confirmation please if the above heuristic assessment is correct in principle (that the threshold between touchstart and touchend that determines whether it's a quick or slow tap is roughly 125ms), and more importantly if the second behavior - that despite touchend being cancelled the mouse compatibility and click events are being fired - is a bug or a feature (and, if the latter, maybe an indication of the rationale behind it?)
Comment 1 Rick Byers 2014-09-24 12:04:04 PDT
Yeah it seems really unfortunate that safari sends click events in this case even though touchend has been consumed.  Old versions of the android browser behaved like that and it was a huge source of pain for web developers - forcing them to do unnatural things like trying to "bust ghost clicks": https://developers.google.com/mobile/articles/fast_buttons#ghost
Comment 2 Benjamin Poulain 2014-09-24 12:43:40 PDT
Damn :(

Adding this to my "touch TODO list".
Comment 3 Rick Byers 2014-09-24 13:36:00 PDT
Thanks Benjamin!  Sorry I didn't spot this in my experimentation with the earlier beta.  I definitely played with touch consumption modes (http://www.rbyers.net/eventTest.html), but it never occurred to me that tap duration was at all relevant to the behavior.

Will you guys be discussing the behavior / reasoning here around tap duration at some point?  It's certainly an interesting idea.
Comment 4 Benjamin Poulain 2014-09-24 14:24:57 PDT
(In reply to comment #3)
> Thanks Benjamin!  Sorry I didn't spot this in my experimentation with the earlier beta.  I definitely played with touch consumption modes (http://www.rbyers.net/eventTest.html), but it never occurred to me that tap duration was at all relevant to the behavior.

I did test this case as well and I did not catch the bug. The problem is related to the gesture recognizers installed in the app, it is very possible we have all tested a working version and something else changed in the system that broke this later. :(

> Will you guys be discussing the behavior / reasoning here around tap duration at some point?  It's certainly an interesting idea.

I changed the dependency graph of the long press. Another example is that you can now pinch to zoom after the tap highlight is shown, this was impossible before.
Comment 5 Rick Byers 2014-09-24 14:28:11 PDT
Ah thanks, so the "long tap suppresses click delay" behavior isn't intentional either then?
Comment 6 Benjamin Poulain 2014-09-24 14:47:04 PDT
(In reply to comment #5)
> Ah thanks, so the "long tap suppresses click delay" behavior isn't intentional either then?

It is. It is the byproduct of relaxed constraints on the graph. We don't add delays explicitly, they happen because other gesture recognizers (e.g. double tap) add a restriction.

I am not saying long press will always works this way. A new system gesture could redefine how we have to handle the graph. But at the moment, this is something we can do.
Comment 7 Rick Byers 2014-09-24 14:51:28 PDT
> (In reply to comment #5)
> > Ah thanks, so the "long tap suppresses click delay" behavior isn't intentional either then?
> 
> It is. It is the byproduct of relaxed constraints on the graph. We don't add delays explicitly, they happen because other gesture recognizers (e.g. double tap) add a restriction.

Ah, right - we don't have the nice UIGestureRecognizer-style abstraction system you do.  Makes sense, thanks.
Comment 8 Benjamin Poulain 2014-10-24 21:35:42 PDT
Created attachment 240453 [details]
Patch
Comment 9 David Kilzer (:ddkilzer) 2014-10-25 20:31:57 PDT
<rdar://problem/18481464>
Comment 10 Simon Fraser (smfr) 2014-11-18 14:25:41 PST
Comment on attachment 240453 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=240453&action=review

> Source/WebKit2/ChangeLog:15
> +        run concurrently. This causes a race with an incorrect behavior:
> +        -If UIWebTouchEventsGestureRecognizer does not cancel the native gestures on start.
> +        -_UIWebHighlightLongPressGestureRecognizer starts after highlightDelay.
> +        -When the finger leaves the screen, both gestures end.
> +        -> If the touch end sent to JavaScript ask the priority over native events, that no longer stops
> +           the _UIWebHighlightLongPressGestureRecognizer.

The formatting here makes it hard for me to follow the logic of the explanation.

> Source/WebKit2/ChangeLog:19
> +        if the page want the event.

wants

> Source/WebKit2/ChangeLog:29
> +           the gesture can end normally. This is done on timer and not direct input so I don't really have to worry

on a  timer
Comment 11 Benjamin Poulain 2014-11-18 21:06:47 PST
Committed r176305: <http://trac.webkit.org/changeset/176305>