ftlabs / fastclick

Polyfill to remove click delays on browsers with touch UIs
MIT License
18.66k stars 3.22k forks source link

Input example broken with 0.6.1 on iOS 4 #82

Closed mattcg closed 11 years ago

mattcg commented 11 years ago

Safari on iOS 4 will only allow focus to be triggered on other elements from within a function if the first function in the call stack was triggered by a non-programmatic event. The call to setTimeout starts a new call stack, and some mechanism kicks in to prevent focus being set on the input.

dryabov commented 11 years ago

I introduced setTimeout to have correct order of events, otherwise it's possible to get click before touchend in code like (as FastClick is bound to document.body by default)

$(window).on('touchend click', function(){...});

It's rare case and compatibility has higher priority of course.

mattcg commented 11 years ago

Thank you @dryabov. I'd like to keep your contribution so maybe we can add a different code path for iOS 4 that avoids the setTimeout and sends the click event instantly.

restyler commented 11 years ago

Actually in the http://ftlabs.github.com/fastclick/examples/focus.html example only "B" button works for me, but it's not iOs 4 - I'm using iOs6.1.2 with iphone 4s.

mattcg commented 11 years ago

@restyler Noted, thanks for that. The regression was indeed caused by the setTimeout, which we introduced to preserve event order as @dryabov pointed out (some UI frameworks rely on that order). I'll try jQuery mobile/UI and Twitter Bootstrap without the setTimeout and see what we get.

dryabov commented 11 years ago

Maybe the following flow would work:

  1. Stop "touchend" event propagation.
  2. Trigger "touchend" programmatically for this.layer to correctly fire handlers of parent elements (note: check that FastClick doesn't process this event twice).
  3. Trigger "click" event on target element.
mattcg commented 11 years ago

That sounds like a good idea, and a better one than the one I was thinking of which was that in the special case of touchend we let it bubble all the way up to window, giving other third-party listeners a chance to do their thing before continuing. The problem with this is that those listeners may stop propagation before the event bubbles up to window, or there may be UI framework listeners on window.

Thinking about your idea, what if at stage 2 there is a UI framework listener on this.layer that gets triggered twice - once when the original touchend event first comes through and a second time when we fire the synthetic touchend event? I suppose we could use the same technique that we use for click, which is to make the listener capturing.

dryabov commented 11 years ago

Yes, it's the problem, we cannot be sure that our event is the first in the list (moreover, DOM Event specification doesn't assert that events will be called in the order of addEventListener calls). Workaround here could be to process touchend event in capturing phase as it's rarely used by frameworks (and, correspondingly, trigger touchend on target element).

mattcg commented 11 years ago

Closing for now.