From 7ab17e5ef30d9b2fc4d639972855bb8f2074ef7a Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Wed, 2 Dec 2015 09:05:39 -0800 Subject: [PATCH] Fix for long press state transition error in Touchable.js Summary: public This diff fixes an occasional JS exception thrown by Touchable.js when it attempts to transitions to the RESPONDER_ACTIVE_LONG_PRESS_IN state from the RESPONDER_INACTIVE_PRESS_IN state. Although I wasn't able to reproduce the error while testing, I was able to identify the likely cause: the LONG_PRESS_DETECTED state transition is triggered by a timer that is started on touch-down. This timer should be cancelled if the gesture is interrupted, however I identified a code path where the state can be changed to RESPONDER_INACTIVE_PRESS_IN without the longPressDelayTimeout being cancelled. To fix this, I've added some logic to cancel the timer in that case. I've also added a test for the error scenario that will display a redbox in __DEV__ mode, but will fail gracefully in production mode. Reviewed By: jingc Differential Revision: D2709750 fb-gh-sync-id: aeea1a31de5e92eb394c2ea177f556b131d50790 --- Libraries/Components/Touchable/Touchable.js | 24 ++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/Libraries/Components/Touchable/Touchable.js b/Libraries/Components/Touchable/Touchable.js index 0a2f554c4..0493311c5 100644 --- a/Libraries/Components/Touchable/Touchable.js +++ b/Libraries/Components/Touchable/Touchable.js @@ -456,6 +456,11 @@ var TouchableMixin = { pressExpandBottom; if (isTouchWithinActive) { this._receiveSignal(Signals.ENTER_PRESS_RECT, e); + var curState = this.state.touchable.touchState; + if (curState === States.RESPONDER_INACTIVE_PRESS_IN) { + // fix for t7967420 + this._cancelLongPressDelayTimeout(); + } } else { this._cancelLongPressDelayTimeout(); this._receiveSignal(Signals.LEAVE_PRESS_RECT, e); @@ -564,7 +569,20 @@ var TouchableMixin = { _handleLongDelay: function(e) { this.longPressDelayTimeout = null; - this._receiveSignal(Signals.LONG_PRESS_DETECTED, e); + var curState = this.state.touchable.touchState; + if (curState !== States.RESPONDER_ACTIVE_PRESS_IN && + curState !== States.RESPONDER_ACTIVE_LONG_PRESS_IN) { + if (__DEV__) { + throw new Error( + 'Attempted to transition from state `' + curState + '` to `' + + States.RESPONDER_ACTIVE_LONG_PRESS_IN + '`, which is not supported. ' + + 'This is most likely due to `Touchable.longPressDelayTimeout` not ' + + 'being cancelled.' + ); + } + } else { + this._receiveSignal(Signals.LONG_PRESS_DETECTED, e); + } }, /** @@ -577,13 +595,13 @@ var TouchableMixin = { */ _receiveSignal: function(signal, e) { var curState = this.state.touchable.touchState; - if (!(Transitions[curState] && Transitions[curState][signal])) { + var nextState = Transitions[curState] && Transitions[curState][signal]; + if (!nextState) { throw new Error( 'Unrecognized signal `' + signal + '` or state `' + curState + '` for Touchable responder `' + this.state.touchable.responderID + '`' ); } - var nextState = Transitions[curState][signal]; if (nextState === States.ERROR) { throw new Error( 'Touchable cannot transition from `' + curState + '` to `' + signal +