Flare: Fix listener upgrade bug (#18270)
Dominic Gannaway committed
Mar 10, 2020 at 17:59 UTC
a3bf6688121e69cfb377c25f362000b33d7984cd
5 files changed
+75
-32
packages/react-dom/src/client/ReactDOMComponent.js
+2
-2
@@ -59,7 +59,7 @@ import {
59
import {getListenerMapForElement} from '../events/DOMEventListenerMap';
60
import {
61
addResponderEventSystemEvent,
62
- removeActiveResponderEventSystemEvent,
62
+ removeTrappedPassiveEventListener,
63
} from '../events/ReactDOMEventListener.js';
64
import {mediaEventTypes} from '../events/DOMTopLevelEventTypes';
65
import {
@@ -1360,7 +1360,7 @@ export function listenToEventResponderEventTypes(
1360
const passiveKey = targetEventType + '_passive';
1361
const passiveListener = listenerMap.get(passiveKey);
1362
if (passiveListener != null) {
1363
- removeActiveResponderEventSystemEvent(
1363
+ removeTrappedPassiveEventListener(
1364
document,
1365
targetEventType,
1366
passiveListener,
packages/react-dom/src/events/DOMLegacyEventPluginSystem.js
+3
-3
@@ -39,7 +39,7 @@ import {
39
getRawEventName,
40
mediaEventTypes,
41
} from './DOMTopLevelEventTypes';
42
-import {trapEventForPluginEventSystem} from './ReactDOMEventListener';
42
+import {addTrappedEventListener} from './ReactDOMEventListener';
43
44
/**
45
* Summary of `DOMEventPluginSystem` event handling:
@@ -368,12 +368,12 @@ export function legacyTrapBubbledEvent(
368
topLevelType: DOMTopLevelEventType,
369
element: Document | Element,
370
): void {
371
- trapEventForPluginEventSystem(element, topLevelType, false);
371
+ addTrappedEventListener(element, topLevelType, false);
372
}
373
374
export function legacyTrapCapturedEvent(
375
topLevelType: DOMTopLevelEventType,
376
element: Document | Element,
377
): void {
378
- trapEventForPluginEventSystem(element, topLevelType, true);
378
+ addTrappedEventListener(element, topLevelType, true);
379
}
packages/react-dom/src/events/DOMModernPluginEventSystem.js
+3
-7
@@ -21,7 +21,7 @@ import {plugins} from 'legacy-events/EventPluginRegistry';
21
22
import {HostRoot, HostPortal} from 'shared/ReactWorkTags';
23
24
-import {trapEventForPluginEventSystem} from './ReactDOMEventListener';
24
+import {addTrappedEventListener} from './ReactDOMEventListener';
25
import getEventTarget from './getEventTarget';
26
import {getListenerMapForElement} from './DOMEventListenerMap';
27
import {
@@ -149,11 +149,7 @@ export function listenToTopLevelEvent(
149
): void {
150
if (!listenerMap.has(topLevelType)) {
151
const isCapturePhase = capturePhaseEvents.has(topLevelType);
152
- trapEventForPluginEventSystem(
153
- rootContainerElement,
154
- topLevelType,
155
- isCapturePhase,
156
- );
152
+ addTrappedEventListener(rootContainerElement, topLevelType, isCapturePhase);
153
listenerMap.set(topLevelType, null);
154
}
155
}
@@ -196,7 +192,7 @@ function willDeferLaterForFBLegacyPrimer(nativeEvent: any): boolean {
192
if (node.tagName === 'A' && validFBLegacyPrimerRels.has(node.rel)) {
193
const legacyFBSupport = true;
194
const isCapture = nativeEvent.eventPhase === 1;
199
- trapEventForPluginEventSystem(
195
+ addTrappedEventListener(
196
document,
197
((type: any): DOMTopLevelEventType),
198
isCapture,
packages/react-dom/src/events/ReactDOMEventListener.js
+32
-20
@@ -105,6 +105,7 @@ export function addResponderEventSystemEvent(
105
} else {
106
eventFlags |= IS_ACTIVE;
107
}
108
+ let fbListener;
109
// Check if interactive and wrap in discreteUpdates
110
const listener = dispatchEvent.bind(
111
null,
@@ -113,39 +114,27 @@ export function addResponderEventSystemEvent(
114
document,
115
);
116
if (passiveBrowserEventsSupported) {
116
- addEventCaptureListenerWithPassiveFlag(
117
+ fbListener = addEventCaptureListenerWithPassiveFlag(
118
document,
119
topLevelType,
120
listener,
121
passive,
122
);
123
} else {
123
- addEventCaptureListener(document, topLevelType, listener);
124
- }
125
- return listener;
126
-}
127
-
128
-export function removeActiveResponderEventSystemEvent(
129
- document: Document,
130
- topLevelType: string,
131
- listener: any => void,
132
-) {
133
- if (passiveBrowserEventsSupported) {
134
- document.removeEventListener(topLevelType, listener, {
135
- capture: true,
136
- passive: false,
137
- });
138
- } else {
139
- document.removeEventListener(topLevelType, listener, true);
124
+ fbListener = addEventCaptureListener(document, topLevelType, listener);
125
}
126
+ // If we have an fbListener, then use that.
127
+ // We'll only have one if we use the forked
128
+ // EventListener-www module in FB builds.
129
+ return fbListener || listener;
130
}
131
143
-export function trapEventForPluginEventSystem(
132
+export function addTrappedEventListener(
133
container: Document | Element,
134
topLevelType: DOMTopLevelEventType,
135
capture: boolean,
136
legacyFBSupport?: boolean,
148
-): void {
137
+): any => void {
138
let listener;
139
let listenerWrapper;
140
switch (getEventPriorityForPluginSystem(topLevelType)) {
@@ -203,6 +192,29 @@ export function trapEventForPluginEventSystem(
192
} else {
193
fbListener = addEventBubbleListener(container, rawEventName, listener);
194
}
195
+ // If we have an fbListener, then use that.
196
+ // We'll only have one if we use the forked
197
+ // EventListener-www module in FB builds.
198
+ return fbListener || listener;
199
+}
200
+
201
+export function removeTrappedPassiveEventListener(
202
+ document: Document,
203
+ topLevelType: string,
204
+ listener: any => void,
205
+) {
206
+ if (listener.remove != null) {
207
+ listener.remove();
208
+ } else {
209
+ if (passiveBrowserEventsSupported) {
210
+ document.removeEventListener(topLevelType, listener, {
211
+ capture: true,
212
+ passive: true,
213
+ });
214
+ } else {
215
+ document.removeEventListener(topLevelType, listener, true);
216
+ }
217
+ }
218
}
219
220
function dispatchDiscreteEvent(
packages/react-dom/src/events/__tests__/DeprecatedDOMEventResponderSystem-test.internal.js
+35
@@ -948,4 +948,39 @@ describe('DOMEventResponderSystem', () => {
948
document.body.removeChild(domNode);
949
expect(onEvent).toBeCalled();
950
});
951
+
952
+ it('event upgrading should work correctly', () => {
953
+ let eventResponderFiredCount = 0;
954
+ const buttonRef = React.createRef();
955
+
956
+ const TestResponder = createEventResponder({
957
+ targetEventTypes: ['click'],
958
+ onEvent: (event, context, props, state) => {
959
+ eventResponderFiredCount++;
960
+ if (!state.addedRootEventTypes) {
961
+ context.addRootEventTypes(['click_active']);
962
+ }
963
+ state.addedRootEventTypes = true;
964
+ },
965
+ });
966
+
967
+ function Test() {
968
+ const listener = React.DEPRECATED_useResponder(TestResponder, {});
969
+
970
+ return (
971
+ <button ref={buttonRef} DEPRECATED_flareListeners={listener}>
972
+ Click me!
973
+ </button>
974
+ );
975
+ }
976
+
977
+ ReactDOM.render(<Test />, container);
978
+ expect(container.innerHTML).toBe('<button>Click me!</button>');
979
+
980
+ let buttonElement = buttonRef.current;
981
+ dispatchClickEvent(buttonElement);
982
+ expect(eventResponderFiredCount).toBe(1);
983
+ dispatchClickEvent(buttonElement);
984
+ expect(eventResponderFiredCount).toBe(2);
985
+ });
986
});