Skip to content

Commit

Permalink
Merge pull request #1231 from spicyj/gh-1227
Browse files Browse the repository at this point in the history
Batch updates caused by handlers in multiple roots
  • Loading branch information
zpao committed Apr 12, 2014
2 parents 3bf12bb + 933dde9 commit 3e2182f
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 13 deletions.
31 changes: 21 additions & 10 deletions src/browser/ui/ReactEventTopLevelCallback.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var PooledClass = require('PooledClass');
var ReactEventEmitter = require('ReactEventEmitter');
var ReactInstanceHandles = require('ReactInstanceHandles');
var ReactMount = require('ReactMount');
var ReactUpdates = require('ReactUpdates');

var getEventTarget = require('getEventTarget');
var mixInto = require('mixInto');
Expand Down Expand Up @@ -56,13 +57,11 @@ function findParent(node) {
* ancestor list. Separated from createTopLevelCallback to avoid try/finally
* deoptimization.
*
* @param {string} topLevelType
* @param {DOMEvent} nativeEvent
* @param {TopLevelCallbackBookKeeping} bookKeeping
*/
function handleTopLevelImpl(topLevelType, nativeEvent, bookKeeping) {
function handleTopLevelImpl(bookKeeping) {
var topLevelTarget = ReactMount.getFirstReactDOM(
getEventTarget(nativeEvent)
getEventTarget(bookKeeping.nativeEvent)
) || window;

// Loop through the hierarchy, in case there's any nested components.
Expand All @@ -79,24 +78,31 @@ function handleTopLevelImpl(topLevelType, nativeEvent, bookKeeping) {
topLevelTarget = bookKeeping.ancestors[i];
var topLevelTargetID = ReactMount.getID(topLevelTarget) || '';
ReactEventEmitter.handleTopLevel(
topLevelType,
bookKeeping.topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent
bookKeeping.nativeEvent
);
}
}

// Used to store ancestor hierarchy in top level callback
function TopLevelCallbackBookKeeping() {
function TopLevelCallbackBookKeeping(topLevelType, nativeEvent) {
this.topLevelType = topLevelType;
this.nativeEvent = nativeEvent;
this.ancestors = [];
}
mixInto(TopLevelCallbackBookKeeping, {
destructor: function() {
this.topLevelType = null;
this.nativeEvent = null;
this.ancestors.length = 0;
}
});
PooledClass.addPoolingTo(TopLevelCallbackBookKeeping);
PooledClass.addPoolingTo(
TopLevelCallbackBookKeeping,
PooledClass.twoArgumentPooler
);

/**
* Top-level callback creator used to implement event handling using delegation.
Expand Down Expand Up @@ -135,9 +141,14 @@ var ReactEventTopLevelCallback = {
return;
}

var bookKeeping = TopLevelCallbackBookKeeping.getPooled();
var bookKeeping = TopLevelCallbackBookKeeping.getPooled(
topLevelType,
nativeEvent
);
try {
handleTopLevelImpl(topLevelType, nativeEvent, bookKeeping);
// Event queue being processed in the same cycle allows
// `preventDefault`.
ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
} finally {
TopLevelCallbackBookKeeping.release(bookKeeping);
}
Expand Down
37 changes: 37 additions & 0 deletions src/browser/ui/__tests__/ReactEventTopLevelCallback-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,43 @@ describe('ReactEventTopLevelCallback', function() {
expect(calls[0][EVENT_TARGET_PARAM]).toBe(childNode);
expect(calls[1][EVENT_TARGET_PARAM]).toBe(parentControl.getDOMNode());
});

it('should batch between handlers from different roots', function() {
var childContainer = document.createElement('div');
var parentContainer = document.createElement('div');
var childControl = ReactMount.renderComponent(
<div>Child</div>,
childContainer
);
var parentControl = ReactMount.renderComponent(
<div>Parent</div>,
parentContainer
);
parentControl.getDOMNode().appendChild(childContainer);

// Suppose an event handler in each root enqueues an update to the
// childControl element -- the two updates should get batched together.
var childNode = childControl.getDOMNode();
ReactEventEmitter.handleTopLevel.mockImplementation(
function(topLevelType, topLevelTarget, topLevelTargetID, nativeEvent) {
ReactMount.renderComponent(
<div>{topLevelTarget === childNode ? '1' : '2'}</div>,
childContainer
);
// Since we're batching, neither update should yet have gone through.
expect(childNode.textContent).toBe('Child');
}
);

var callback = ReactEventTopLevelCallback.createTopLevelCallback('test');
callback({
target: childNode
});

var calls = ReactEventEmitter.handleTopLevel.mock.calls;
expect(calls.length).toBe(2);
expect(childNode.textContent).toBe('2');
});
});

it('should not fire duplicate events for a React DOM tree', function() {
Expand Down
4 changes: 1 addition & 3 deletions src/core/ReactEventEmitterMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
"use strict";

var EventPluginHub = require('EventPluginHub');
var ReactUpdates = require('ReactUpdates');

function runEventQueueInBatch(events) {
EventPluginHub.enqueueEvents(events);
Expand Down Expand Up @@ -49,8 +48,7 @@ var ReactEventEmitterMixin = {
nativeEvent
);

// Event queue being processed in the same cycle allows `preventDefault`.
ReactUpdates.batchedUpdates(runEventQueueInBatch, events);
runEventQueueInBatch(events);
}
};

Expand Down

0 comments on commit 3e2182f

Please sign in to comment.