Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Batch updates caused by handlers in multiple roots #1231

Merged
merged 1 commit into from
Apr 12, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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