Skip to content

Commit 9307932

Browse files
authored
Refactor event object creation for the experimental event API (#15295)
* Refactor event object creation for the experimental event API
1 parent 6a1e6b2 commit 9307932

File tree

9 files changed

+348
-147
lines changed

9 files changed

+348
-147
lines changed

packages/events/EventTypes.js

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,36 @@
77
* @flow
88
*/
99

10-
import SyntheticEvent from 'events/SyntheticEvent';
1110
import type {AnyNativeEvent} from 'events/PluginModuleType';
1211
import type {ReactEventResponderEventType} from 'shared/ReactTypes';
1312

1413
export type EventResponderContext = {
1514
event: AnyNativeEvent,
16-
eventTarget: EventTarget,
15+
eventTarget: Element | Document,
1716
eventType: string,
1817
isPassive: () => boolean,
1918
isPassiveSupported: () => boolean,
20-
dispatchEvent: (
21-
name: string,
22-
listener: (e: SyntheticEvent) => void | null,
23-
pressTarget: EventTarget | null,
24-
discrete: boolean,
25-
extraProperties?: Object,
19+
dispatchEvent: <E>(
20+
eventObject: E,
21+
{
22+
capture?: boolean,
23+
discrete?: boolean,
24+
stopPropagation?: boolean,
25+
},
2626
) => void,
2727
isTargetWithinElement: (
28-
childTarget: EventTarget,
29-
parentTarget: EventTarget,
28+
childTarget: Element | Document,
29+
parentTarget: Element | Document,
3030
) => boolean,
31-
isTargetOwned: EventTarget => boolean,
32-
isTargetWithinEventComponent: EventTarget => boolean,
31+
isTargetOwned: (Element | Document) => boolean,
32+
isTargetWithinEventComponent: (Element | Document) => boolean,
3333
isPositionWithinTouchHitTarget: (x: number, y: number) => boolean,
3434
addRootEventTypes: (
3535
rootEventTypes: Array<ReactEventResponderEventType>,
3636
) => void,
3737
removeRootEventTypes: (
3838
rootEventTypes: Array<ReactEventResponderEventType>,
3939
) => void,
40-
requestOwnership: (target: EventTarget | null) => boolean,
41-
releaseOwnership: (target: EventTarget | null) => boolean,
40+
requestOwnership: (target: Element | Document | null) => boolean,
41+
releaseOwnership: (target: Element | Document | null) => boolean,
4242
};

packages/react-dom/src/events/DOMEventResponderSystem.js

Lines changed: 132 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,13 @@ import type {
1818
ReactEventResponderEventType,
1919
} from 'shared/ReactTypes';
2020
import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes';
21-
import SyntheticEvent from 'events/SyntheticEvent';
22-
import {runEventsInBatch} from 'events/EventBatching';
23-
import {interactiveUpdates} from 'events/ReactGenericBatching';
24-
import {executeDispatch} from 'events/EventPluginUtils';
21+
import {batchedUpdates, interactiveUpdates} from 'events/ReactGenericBatching';
2522
import type {Fiber} from 'react-reconciler/src/ReactFiber';
26-
2723
import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
2824

2925
import {enableEventAPI} from 'shared/ReactFeatureFlags';
26+
import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
27+
import warning from 'shared/warning';
3028

3129
let listenToResponderEventTypesImpl;
3230

@@ -36,6 +34,8 @@ export function setListenToResponderEventTypes(
3634
listenToResponderEventTypesImpl = _listenToResponderEventTypesImpl;
3735
}
3836

37+
const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
38+
3939
const rootEventTypesToEventComponents: Map<
4040
DOMTopLevelEventType | string,
4141
Set<Fiber>,
@@ -45,12 +45,76 @@ const targetEventTypeCached: Map<
4545
Set<DOMTopLevelEventType>,
4646
> = new Map();
4747
const targetOwnership: Map<EventTarget, Fiber> = new Map();
48+
const eventsWithStopPropagation:
49+
| WeakSet
50+
| Set<$Shape<PartialEventObject>> = new PossiblyWeakSet();
51+
52+
type PartialEventObject = {
53+
listener: ($Shape<PartialEventObject>) => void,
54+
target: Element | Document,
55+
type: string,
56+
};
57+
type EventQueue = {
58+
bubble: null | Array<$Shape<PartialEventObject>>,
59+
capture: null | Array<$Shape<PartialEventObject>>,
60+
discrete: boolean,
61+
phase: EventQueuePhase,
62+
};
63+
type EventQueuePhase = 0 | 1;
64+
65+
const DURING_EVENT_PHASE = 0;
66+
const AFTER_EVENT_PHASE = 1;
4867

49-
type EventListener = (event: SyntheticEvent) => void;
68+
function createEventQueue(phase: EventQueuePhase): EventQueue {
69+
return {
70+
bubble: null,
71+
capture: null,
72+
discrete: false,
73+
phase,
74+
};
75+
}
76+
77+
function processEvent(event: $Shape<PartialEventObject>): void {
78+
const type = event.type;
79+
const listener = event.listener;
80+
invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
81+
}
5082

51-
function copyEventProperties(eventData, syntheticEvent) {
52-
for (let propName in eventData) {
53-
syntheticEvent[propName] = eventData[propName];
83+
function processEvents(
84+
bubble: null | Array<$Shape<PartialEventObject>>,
85+
capture: null | Array<$Shape<PartialEventObject>>,
86+
): void {
87+
let i, length;
88+
89+
if (capture !== null) {
90+
for (i = capture.length; i-- > 0; ) {
91+
const event = capture[i];
92+
processEvent(capture[i]);
93+
if (eventsWithStopPropagation.has(event)) {
94+
return;
95+
}
96+
}
97+
}
98+
if (bubble !== null) {
99+
for (i = 0, length = bubble.length; i < length; ++i) {
100+
const event = bubble[i];
101+
processEvent(event);
102+
if (eventsWithStopPropagation.has(event)) {
103+
return;
104+
}
105+
}
106+
}
107+
}
108+
109+
function processEventQueue(eventQueue: EventQueue): void {
110+
const {bubble, capture, discrete} = eventQueue;
111+
112+
if (discrete) {
113+
interactiveUpdates(() => {
114+
processEvents(bubble, capture);
115+
});
116+
} else {
117+
processEvents(bubble, capture);
54118
}
55119
}
56120

@@ -70,6 +134,7 @@ function DOMEventResponderContext(
70134
this._discreteEvents = null;
71135
this._nonDiscreteEvents = null;
72136
this._isBatching = true;
137+
this._eventQueue = createEventQueue(DURING_EVENT_PHASE);
73138
}
74139

75140
DOMEventResponderContext.prototype.isPassive = function(): boolean {
@@ -81,49 +146,66 @@ DOMEventResponderContext.prototype.isPassiveSupported = function(): boolean {
81146
};
82147

83148
DOMEventResponderContext.prototype.dispatchEvent = function(
84-
eventName: string,
85-
eventListener: EventListener,
86-
eventTarget: AnyNativeEvent,
87-
discrete: boolean,
88-
extraProperties?: Object,
149+
possibleEventObject: Object,
150+
{
151+
capture,
152+
discrete,
153+
stopPropagation,
154+
}: {
155+
capture?: boolean,
156+
discrete?: boolean,
157+
stopPropagation?: boolean,
158+
},
89159
): void {
90-
const eventTargetFiber = getClosestInstanceFromNode(eventTarget);
91-
const syntheticEvent = SyntheticEvent.getPooled(
92-
null,
93-
eventTargetFiber,
94-
this.event,
95-
eventTarget,
96-
);
97-
if (extraProperties !== undefined) {
98-
copyEventProperties(extraProperties, syntheticEvent);
160+
const eventQueue = this._eventQueue;
161+
const {listener, target, type} = possibleEventObject;
162+
163+
if (listener == null || target == null || type == null) {
164+
throw new Error(
165+
'context.dispatchEvent: "listener", "target" and "type" fields on event object are required.',
166+
);
99167
}
100-
syntheticEvent.type = eventName;
101-
syntheticEvent._dispatchInstances = [eventTargetFiber];
102-
syntheticEvent._dispatchListeners = [eventListener];
103-
104-
if (this._isBatching) {
105-
let events;
106-
if (discrete) {
107-
events = this._discreteEvents;
108-
if (events === null) {
109-
events = this._discreteEvents = [];
110-
}
111-
} else {
112-
events = this._nonDiscreteEvents;
113-
if (events === null) {
114-
events = this._nonDiscreteEvents = [];
115-
}
168+
if (__DEV__) {
169+
possibleEventObject.preventDefault = () => {
170+
// Update this warning when we have a story around dealing with preventDefault
171+
warning(
172+
false,
173+
'preventDefault() is no longer available on event objects created from event responder modules.',
174+
);
175+
};
176+
possibleEventObject.stopPropagation = () => {
177+
// Update this warning when we have a story around dealing with stopPropgation
178+
warning(
179+
false,
180+
'stopPropagation() is no longer available on event objects created from event responder modules.',
181+
);
182+
};
183+
}
184+
const eventObject = ((possibleEventObject: any): $Shape<PartialEventObject>);
185+
let events;
186+
187+
if (capture) {
188+
events = eventQueue.capture;
189+
if (events === null) {
190+
events = eventQueue.capture = [];
116191
}
117-
events.push(syntheticEvent);
118192
} else {
119-
if (discrete) {
120-
interactiveUpdates(() => {
121-
executeDispatch(syntheticEvent, eventListener, eventTargetFiber);
122-
});
123-
} else {
124-
executeDispatch(syntheticEvent, eventListener, eventTargetFiber);
193+
events = eventQueue.bubble;
194+
if (events === null) {
195+
events = eventQueue.bubble = [];
125196
}
126197
}
198+
if (discrete) {
199+
eventQueue.discrete = true;
200+
}
201+
events.push(eventObject);
202+
203+
if (stopPropagation) {
204+
eventsWithStopPropagation.add(eventObject);
205+
}
206+
if (eventQueue.phase === AFTER_EVENT_PHASE) {
207+
batchedUpdates(processEventQueue, eventQueue);
208+
}
127209
};
128210

129211
DOMEventResponderContext.prototype.isTargetWithinEventComponent = function(
@@ -318,17 +400,9 @@ export function runResponderEventsInBatch(
318400
);
319401
}
320402
}
321-
// Run batched events
322-
const discreteEvents = context._discreteEvents;
323-
if (discreteEvents !== null) {
324-
interactiveUpdates(() => {
325-
runEventsInBatch(discreteEvents);
326-
});
327-
}
328-
const nonDiscreteEvents = context._nonDiscreteEvents;
329-
if (nonDiscreteEvents !== null) {
330-
runEventsInBatch(nonDiscreteEvents);
331-
}
332-
context._isBatching = false;
403+
processEventQueue(context._eventQueue);
404+
// In order to capture and process async events from responder modules
405+
// we create a new event queue.
406+
context._eventQueue = createEventQueue(AFTER_EVENT_PHASE);
333407
}
334408
}

packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,12 +228,12 @@ describe('DOMEventResponderSystem', () => {
228228
['click'],
229229
(context, props) => {
230230
if (props.onMagicClick) {
231-
context.dispatchEvent(
232-
'magicclick',
233-
props.onMagicClick,
234-
context.eventTarget,
235-
false,
236-
);
231+
const event = {
232+
listener: props.onMagicClick,
233+
target: context.eventTarget,
234+
type: 'magicclick',
235+
};
236+
context.dispatchEvent(event, {discrete: true});
237237
}
238238
},
239239
);

0 commit comments

Comments
 (0)