diff --git a/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js b/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js index 992f74d29e..e33a4ccba1 100644 --- a/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js +++ b/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js @@ -96,7 +96,7 @@ function manualDispatchChangeEvent(nativeEvent) { function runEventInBatch(event) { EventPluginHub.enqueueEvents(event); - EventPluginHub.processEventQueue(); + EventPluginHub.processEventQueue(false); } function startWatchingForChangeEventIE8(target, targetID) { diff --git a/src/renderers/shared/event/EventPluginHub.js b/src/renderers/shared/event/EventPluginHub.js index ce448b701d..41b77dd948 100644 --- a/src/renderers/shared/event/EventPluginHub.js +++ b/src/renderers/shared/event/EventPluginHub.js @@ -35,17 +35,24 @@ var eventQueue = null; * Dispatches an event and releases it back into the pool, unless persistent. * * @param {?object} event Synthetic event to be dispatched. + * @param {boolean} simulated If the event is simulated (changes exn behavior) * @private */ -var executeDispatchesAndRelease = function(event) { +var executeDispatchesAndRelease = function(event, simulated) { if (event) { - EventPluginUtils.executeDispatchesInOrder(event); + EventPluginUtils.executeDispatchesInOrder(event, simulated); if (!event.isPersistent()) { event.constructor.release(event); } } }; +var executeDispatchesAndReleaseSimulated = function(e) { + return executeDispatchesAndRelease(e, true); +}; +var executeDispatchesAndReleaseTopLevel = function(e) { + return executeDispatchesAndRelease(e, false); +}; /** * - `InstanceHandle`: [required] Module that performs logical traversals of DOM @@ -266,12 +273,22 @@ var EventPluginHub = { * * @internal */ - processEventQueue: function() { + processEventQueue: function(simulated) { // Set `eventQueue` to null before processing it so that we can tell if more // events get enqueued while processing. var processingEventQueue = eventQueue; eventQueue = null; - forEachAccumulated(processingEventQueue, executeDispatchesAndRelease); + if (simulated) { + forEachAccumulated( + processingEventQueue, + executeDispatchesAndReleaseSimulated + ); + } else { + forEachAccumulated( + processingEventQueue, + executeDispatchesAndReleaseTopLevel + ); + } invariant( !eventQueue, 'processEventQueue(): Additional events were enqueued while processing ' + diff --git a/src/renderers/shared/event/EventPluginUtils.js b/src/renderers/shared/event/EventPluginUtils.js index 20b5998e7e..bff374799e 100644 --- a/src/renderers/shared/event/EventPluginUtils.js +++ b/src/renderers/shared/event/EventPluginUtils.js @@ -80,20 +80,30 @@ if (__DEV__) { /** * Dispatch the event to the listener. * @param {SyntheticEvent} event SyntheticEvent to handle + * @param {boolean} simulated If the event is simulated (changes exn behavior) * @param {function} listener Application-level callback * @param {string} domID DOM id to pass to the callback. */ -function executeDispatch(event, listener, domID) { +function executeDispatch(event, simulated, listener, domID) { var type = event.type || 'unknown-event'; event.currentTarget = injection.Mount.getNode(domID); - ReactErrorUtils.invokeGuardedCallback(type, listener, event, domID); + if (simulated) { + ReactErrorUtils.invokeGuardedCallbackWithCatch( + type, + listener, + event, + domID + ); + } else { + ReactErrorUtils.invokeGuardedCallback(type, listener, event, domID); + } event.currentTarget = null; } /** * Standard/simple iteration through an event's collected dispatches. */ -function executeDispatchesInOrder(event) { +function executeDispatchesInOrder(event, simulated) { var dispatchListeners = event._dispatchListeners; var dispatchIDs = event._dispatchIDs; if (__DEV__) { @@ -105,10 +115,10 @@ function executeDispatchesInOrder(event) { break; } // Listeners and IDs are two parallel arrays that are always in sync. - executeDispatch(event, dispatchListeners[i], dispatchIDs[i]); + executeDispatch(event, simulated, dispatchListeners[i], dispatchIDs[i]); } } else if (dispatchListeners) { - executeDispatch(event, dispatchListeners, dispatchIDs); + executeDispatch(event, simulated, dispatchListeners, dispatchIDs); } event._dispatchListeners = null; event._dispatchIDs = null; diff --git a/src/renderers/shared/event/eventPlugins/__tests__/ResponderEventPlugin-test.js b/src/renderers/shared/event/eventPlugins/__tests__/ResponderEventPlugin-test.js index 25c5e49ecc..6974cd7c41 100644 --- a/src/renderers/shared/event/eventPlugins/__tests__/ResponderEventPlugin-test.js +++ b/src/renderers/shared/event/eventPlugins/__tests__/ResponderEventPlugin-test.js @@ -306,7 +306,7 @@ var run = function(config, hierarchyConfig, nativeEventConfig) { // extraction process, but not the side effectful events. Below, we dispatch // side effectful events. EventPluginHub.enqueueEvents(extractedEvents); - EventPluginHub.processEventQueue(); + EventPluginHub.processEventQueue(true); // Ensure that every event that declared an `order`, was actually dispatched. expect( diff --git a/src/renderers/shared/reconciler/ReactEventEmitterMixin.js b/src/renderers/shared/reconciler/ReactEventEmitterMixin.js index a865fda7a4..6ab34f0050 100644 --- a/src/renderers/shared/reconciler/ReactEventEmitterMixin.js +++ b/src/renderers/shared/reconciler/ReactEventEmitterMixin.js @@ -15,7 +15,7 @@ var EventPluginHub = require('EventPluginHub'); function runEventQueueInBatch(events) { EventPluginHub.enqueueEvents(events); - EventPluginHub.processEventQueue(); + EventPluginHub.processEventQueue(false); } var ReactEventEmitterMixin = { diff --git a/src/shared/utils/ReactErrorUtils.js b/src/shared/utils/ReactErrorUtils.js index a4fdf8ce9d..2444c31f5b 100644 --- a/src/shared/utils/ReactErrorUtils.js +++ b/src/shared/utils/ReactErrorUtils.js @@ -14,25 +14,33 @@ var caughtError = null; -var ReactErrorUtils = { - /** - * Call a function while guarding against errors that happens within it. - * - * @param {?String} name of the guard to use for logging or debugging - * @param {Function} func The function to invoke - * @param {*} a First argument - * @param {*} b Second argument - */ - invokeGuardedCallback: function(name, func, a, b) { - try { - return func(a, b); - } catch (x) { - if (caughtError === null) { - caughtError = x; - } - return undefined; +/** + * Call a function while guarding against errors that happens within it. + * + * @param {?String} name of the guard to use for logging or debugging + * @param {Function} func The function to invoke + * @param {*} a First argument + * @param {*} b Second argument + */ +function invokeGuardedCallback(name, func, a, b) { + try { + return func(a, b); + } catch (x) { + if (caughtError === null) { + caughtError = x; } - }, + return undefined; + } +} + +var ReactErrorUtils = { + invokeGuardedCallback: invokeGuardedCallback, + + /** + * Invoked by ReactTestUtils.Simulate so that any errors thrown by the event + * handler are sure to be rethrown by rethrowCaughtError. + */ + invokeGuardedCallbackWithCatch: invokeGuardedCallback, /** * During execution of guarded functions we will capture the first error which diff --git a/src/test/ReactTestUtils.js b/src/test/ReactTestUtils.js index 7bc8cd0455..7a368fb540 100644 --- a/src/test/ReactTestUtils.js +++ b/src/test/ReactTestUtils.js @@ -475,7 +475,7 @@ function makeSimulator(eventType) { ReactUpdates.batchedUpdates(function() { EventPluginHub.enqueueEvents(event); - EventPluginHub.processEventQueue(); + EventPluginHub.processEventQueue(true); }); }; }