I have come across what seems to be a very, very strange issue in Mobile Safari.
We use the Flux dispatcher from Facebook, which has some very simple dispatching logic, roughly as follows (everything is synchronous):
Dispatcher.dispatch = function (payload) {
if (this.isDispatching) {
throw new Error("Cannot dispatch while dispatching")
}
this.isDispatching = true
try {
this.notifyAllObservers(payload)
} finally {
this.isDispatching = false
}
}
This means that if one of the observers triggers another dispatch, you go into the Error("Cannot dispatch...") branch.
The problem is, we're ending up with this error, not because an observer is dispatching, but because a touchend event seems to pop up in the call stack out of nowhere if you hammer a particular button very fast.
By putting a debugger
statement in the guard clause, above, we get the stack trace that caused the "double-dispatch", and it seems plain wrong.
The stack trace looks roughly like this (annotated and boring bits removed):
_settlePromise - from an XHR
Dispatcher.dispatch - we dispatch a "searchResults" action
recalculate - the UI needs to recalculate some layout (this includes an element.offsetLeft access)
touchendHandler - where the hell did this come from??
Dispatcher.dispatch - BANG! (we haven't been able to set Dispatcher.isDispatching to false)
Reading the call stack from root to leaf, you can see every call in the code reflected on the next layer of the stack, so in _settlePromise
we can see the call to Dispatcher.dispatch(...)
, and in Dispatcher.dispatch
we can see ui.recalulate()
.
This suddenly is not true once we get to the recalculate
layer. recalculate
reads the offsetLeft of some UI element which causes a forced synchronous layout. The next call in the stack is a touch event handler, which I just can't understand. Surely any UI events should happen on the next tick?
To me, this seems like a browser problem. Maybe I don't understand the full intricacies of the event loop, but it seems odd that an unrelated call could suddenly appear in the stack. This only happens on Mobile Safari as far as we can tell.
Any thoughts? Does this call stack make sense to anyone?