mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
@@ -49,6 +49,9 @@ function findParent(node) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
// Used to store ancestor hierarchy in top level callback
|
||||
var topLevelCallbackReusableArray = [];
|
||||
|
||||
/**
|
||||
* Top-level callback creator used to implement event handling using delegation.
|
||||
* This is used via dependency injection.
|
||||
@@ -89,8 +92,21 @@ var ReactEventTopLevelCallback = {
|
||||
getEventTarget(nativeEvent)
|
||||
) || window;
|
||||
|
||||
var ancestors = topLevelCallbackReusableArray;
|
||||
ancestors.length = 0;
|
||||
|
||||
// Loop through the hierarchy, in case there's any nested components.
|
||||
while (topLevelTarget) {
|
||||
// It's important that we build the array of ancestors before calling any
|
||||
// event handlers, because event handlers can modify the DOM, leading to
|
||||
// inconsistencies with ReactMount's node cache. See #1105.
|
||||
var ancestor = topLevelTarget;
|
||||
while (ancestor) {
|
||||
ancestors.push(ancestor);
|
||||
ancestor = findParent(ancestor);
|
||||
}
|
||||
|
||||
for (var i = 0, l = ancestors.length; i < l; i++) {
|
||||
topLevelTarget = ancestors[i];
|
||||
var topLevelTargetID = ReactMount.getID(topLevelTarget) || '';
|
||||
ReactEventEmitter.handleTopLevel(
|
||||
topLevelType,
|
||||
@@ -98,9 +114,12 @@ var ReactEventTopLevelCallback = {
|
||||
topLevelTargetID,
|
||||
nativeEvent
|
||||
);
|
||||
|
||||
topLevelTarget = findParent(topLevelTarget);
|
||||
}
|
||||
|
||||
// Emptying ancestors/topLevelCallbackReusableArray is
|
||||
// not necessary for correctness, but it helps the GC reclaim
|
||||
// any nodes that were left at the end of the search.
|
||||
ancestors.length = 0;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,39 @@ describe('ReactEventTopLevelCallback', function() {
|
||||
expect(calls[2][EVENT_TARGET_PARAM])
|
||||
.toBe(grandParentControl.getDOMNode());
|
||||
});
|
||||
|
||||
it('should not get confused by disappearing elements', function() {
|
||||
var childContainer = document.createElement('div');
|
||||
var childControl = <div>Child</div>;
|
||||
var parentContainer = document.createElement('div');
|
||||
var parentControl = <div>Parent</div>;
|
||||
ReactMount.renderComponent(childControl, childContainer);
|
||||
ReactMount.renderComponent(parentControl, parentContainer);
|
||||
parentControl.getDOMNode().appendChild(childContainer);
|
||||
|
||||
// ReactEventEmitter.handleTopLevel might remove the target from the DOM.
|
||||
// Here, we have handleTopLevel remove the node when the first event
|
||||
// handlers are called; we'll still expect to receive a second call for
|
||||
// the parent control.
|
||||
var childNode = childControl.getDOMNode();
|
||||
ReactEventEmitter.handleTopLevel.mockImplementation(
|
||||
function(topLevelType, topLevelTarget, topLevelTargetID, nativeEvent) {
|
||||
if (topLevelTarget === childNode) {
|
||||
ReactMount.unmountComponentAtNode(childContainer);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
var callback = ReactEventTopLevelCallback.createTopLevelCallback('test');
|
||||
callback({
|
||||
target: childNode
|
||||
});
|
||||
|
||||
var calls = ReactEventEmitter.handleTopLevel.mock.calls;
|
||||
expect(calls.length).toBe(2);
|
||||
expect(calls[0][EVENT_TARGET_PARAM]).toBe(childNode);
|
||||
expect(calls[1][EVENT_TARGET_PARAM]).toBe(parentControl.getDOMNode());
|
||||
});
|
||||
});
|
||||
|
||||
it('should not fire duplicate events for a React DOM tree', function() {
|
||||
|
||||
Reference in New Issue
Block a user