@samitouri / QOS-React / commits / 242a50a652

Fix issue with capture phase non-bubbling events (#19452)

Dominic Gannaway committed Jul 25, 2020 at 12:32 UTC 242a50a652f70df73a9ea4c696c2ed00e55e5bae
3 files changed +51 -1
packages/react-dom/src/__tests__/ReactDOMEventListener-test.js
+32
@@ -590,4 +590,36 @@ describe('ReactDOMEventListener', () => {
590 document.body.removeChild(container);
591 }
592 });
593 +
594 + it('should handle non-bubbling capture events correctly', () => {
595 + const container = document.createElement('div');
596 + const innerRef = React.createRef();
597 + const outerRef = React.createRef();
598 + const onPlayCapture = jest.fn();
599 + document.body.appendChild(container);
600 + try {
601 + ReactDOM.render(
602 + <div ref={outerRef} onPlayCapture={onPlayCapture}>
603 + <div onPlayCapture={onPlayCapture}>
604 + <div ref={innerRef} onPlayCapture={onPlayCapture} />
605 + </div>
606 + </div>,
607 + container,
608 + );
609 + innerRef.current.dispatchEvent(
610 + new Event('play', {
611 + bubbles: false,
612 + }),
613 + );
614 + expect(onPlayCapture).toHaveBeenCalledTimes(3);
615 + outerRef.current.dispatchEvent(
616 + new Event('play', {
617 + bubbles: false,
618 + }),
619 + );
620 + expect(onPlayCapture).toHaveBeenCalledTimes(4);
621 + } finally {
622 + document.body.removeChild(container);
623 + }
624 + });
625 });
packages/react-dom/src/events/DOMPluginEventSystem.js
+7
@@ -712,6 +712,7 @@ export function accumulateSinglePhaseListeners(
712 dispatchQueue: DispatchQueue,
713 event: ReactSyntheticEvent,
714 inCapturePhase: boolean,
715 + accumulateTargetOnly: boolean,
716 ): void {
717 const bubbled = event._reactName;
718 const captured = bubbled !== null ? bubbled + 'Capture' : null;
@@ -809,6 +810,12 @@ export function accumulateSinglePhaseListeners(
810 }
811 }
812 }
813 + // If we are only accumulating events for the target, then we don't
814 + // continue to propagate through the React fiber tree to find other
815 + // listeners.
816 + if (accumulateTargetOnly) {
817 + break;
818 + }
819 instance = instance.return;
820 }
821 if (listeners.length !== 0) {
packages/react-dom/src/events/plugins/SimpleEventPlugin.js
+12 -1
@@ -40,7 +40,7 @@ import {
40 import {IS_EVENT_HANDLE_NON_MANAGED_NODE} from '../EventSystemFlags';
41
42 import getEventCharCode from '../getEventCharCode';
43 -import {IS_CAPTURE_PHASE} from '../EventSystemFlags';
43 +import {IS_CAPTURE_PHASE, IS_NON_DELEGATED} from '../EventSystemFlags';
44
45 import {enableCreateEventHandleAPI} from 'shared/ReactFeatureFlags';
46
@@ -165,12 +165,23 @@ function extractEvents(
165 inCapturePhase,
166 );
167 } else {
168 + // When we encounter a non-delegated event in the capture phase,
169 + // we shouldn't emuluate capture bubbling. This is because we'll
170 + // add a native capture event listener to each element directly,
171 + // not the root, and native capture listeners always fire even
172 + // if the event doesn't bubble.
173 + const isNonDelegatedEvent = (eventSystemFlags & IS_NON_DELEGATED) !== 0;
174 + // TODO: We may also want to re-use the accumulateTargetOnly flag to
175 + // special case bubbling for onScroll/media events at a later point.
176 + const accumulateTargetOnly = inCapturePhase && isNonDelegatedEvent;
177 +
178 // We traverse only capture or bubble phase listeners
179 accumulateSinglePhaseListeners(
180 targetInst,
181 dispatchQueue,
182 event,
183 inCapturePhase,
184 + accumulateTargetOnly,
185 );
186 }
187 return event;