Skip to content

Commit

Permalink
Fix issue with capture phase non-bubbling events
Browse files Browse the repository at this point in the history
Revise
  • Loading branch information
trueadm committed Jul 25, 2020
1 parent 125d1a1 commit 4d77e67
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 1 deletion.
32 changes: 32 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMEventListener-test.js
Expand Up @@ -557,4 +557,36 @@ describe('ReactDOMEventListener', () => {
document.body.removeChild(container);
}
});

it('should handle non-bubbling capture events correctly', () => {
const container = document.createElement('div');
const innerRef = React.createRef();
const outerRef = React.createRef();
const onPlayCapture = jest.fn();
document.body.appendChild(container);
try {
ReactDOM.render(
<div ref={outerRef} onPlayCapture={onPlayCapture}>
<div onPlayCapture={onPlayCapture}>
<div ref={innerRef} onPlayCapture={onPlayCapture} />
</div>
</div>,
container,
);
innerRef.current.dispatchEvent(
new Event('play', {
bubbles: false,
}),
);
expect(onPlayCapture).toHaveBeenCalledTimes(3);
outerRef.current.dispatchEvent(
new Event('play', {
bubbles: false,
}),
);
expect(onPlayCapture).toHaveBeenCalledTimes(4);
} finally {
document.body.removeChild(container);
}
});
});
9 changes: 9 additions & 0 deletions packages/react-dom/src/events/DOMPluginEventSystem.js
Expand Up @@ -712,6 +712,7 @@ export function accumulateSinglePhaseListeners(
dispatchQueue: DispatchQueue,
event: ReactSyntheticEvent,
inCapturePhase: boolean,
isNonDelegatedEvent: boolean,
): void {
const bubbled = event._reactName;
const captured = bubbled !== null ? bubbled + 'Capture' : null;
Expand Down Expand Up @@ -808,6 +809,14 @@ export function accumulateSinglePhaseListeners(
}
}
}
// When we encounter a non-delegated event in the capture phase,
// we shouldn't emuluate capture bubbling. This is because we'll
// add a native capture event listener to each element directly,
// not the root, and native capture listeners always fire even
// if the event doesn't bubble.
if (inCapturePhase && isNonDelegatedEvent) {
break;
}
instance = instance.return;
}
if (listeners.length !== 0) {
Expand Down
4 changes: 3 additions & 1 deletion packages/react-dom/src/events/plugins/SimpleEventPlugin.js
Expand Up @@ -11,7 +11,7 @@ import type {TopLevelType} from '../../events/TopLevelEventTypes';
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import type {AnyNativeEvent} from '../../events/PluginModuleType';
import type {DispatchQueue} from '../DOMPluginEventSystem';
import type {EventSystemFlags} from '../EventSystemFlags';
import {EventSystemFlags, IS_NON_DELEGATED} from '../EventSystemFlags';

import {
SyntheticEvent,
Expand Down Expand Up @@ -165,12 +165,14 @@ function extractEvents(
inCapturePhase,
);
} else {
const isNonDelegatedEvent = (eventSystemFlags & IS_NON_DELEGATED) !== 0;
// We traverse only capture or bubble phase listeners
accumulateSinglePhaseListeners(
targetInst,
dispatchQueue,
event,
inCapturePhase,
isNonDelegatedEvent,
);
}
return event;
Expand Down

0 comments on commit 4d77e67

Please sign in to comment.