@@ -30,6 +30,7 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber';
30
30
import warning from 'shared/warning' ;
31
31
import { enableEventAPI } from 'shared/ReactFeatureFlags' ;
32
32
import { invokeGuardedCallbackAndCatchFirstError } from 'shared/ReactErrorUtils' ;
33
+ import invariant from 'shared/invariant' ;
33
34
34
35
import { getClosestInstanceFromNode } from '../client/ReactDOMComponentTree' ;
35
36
@@ -50,7 +51,6 @@ type EventQueue = {
50
51
} ;
51
52
52
53
type PartialEventObject = {
53
- listener : ( $Shape < PartialEventObject > ) => void ,
54
54
target : Element | Document ,
55
55
type : string ,
56
56
} ;
@@ -76,22 +76,31 @@ const targetEventTypeCached: Map<
76
76
Set< DOMTopLevelEventType > ,
77
77
> = new Map ( ) ;
78
78
const ownershipChangeListeners : Set < ReactEventComponentInstance > = new Set();
79
+ const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
80
+ const eventListeners:
81
+ | WeakMap
82
+ | Map<
83
+ $Shape < PartialEventObject > ,
84
+ ($Shape< PartialEventObject > ) => void ,
85
+ > = new PossiblyWeakMap ( ) ;
79
86
80
87
let currentTimers = new Map ( ) ;
81
88
let currentOwner = null ;
82
- let currentInstance: ReactEventComponentInstance;
83
- let currentEventQueue: EventQueue;
89
+ let currentInstance : null | ReactEventComponentInstance = null ;
90
+ let currentEventQueue : null | EventQueue = null ;
84
91
85
92
const eventResponderContext : ReactResponderContext = {
86
93
dispatchEvent (
87
94
possibleEventObject : Object ,
95
+ listener : ( $Shape < PartialEventObject > ) => void ,
88
96
{ capture, discrete} : ReactResponderDispatchEventOptions ,
89
97
) : void {
90
- const { listener, target, type} = possibleEventObject ;
98
+ validateResponderContext ( ) ;
99
+ const { target , type } = possibleEventObject ;
91
100
92
- if ( listener == null || target == null || type == null ) {
101
+ if ( target == null || type == null ) {
93
102
throw new Error (
94
- 'context.dispatchEvent: "listener", " target" and "type" fields on event object are required.' ,
103
+ 'context.dispatchEvent: "target" and "type" fields on event object are required.' ,
95
104
) ;
96
105
}
97
106
if ( __DEV__ ) {
@@ -115,15 +124,18 @@ const eventResponderContext: ReactResponderContext = {
115
124
> ) ;
116
125
const events = getEventsFromEventQueue ( capture ) ;
117
126
if ( discrete ) {
118
- currentEventQueue . discrete = true ;
127
+ ( ( currentEventQueue : any ) : EventQueue ) . discrete = true ;
119
128
}
129
+ eventListeners . set ( eventObject , listener ) ;
120
130
events . push ( eventObject ) ;
121
131
} ,
122
132
dispatchStopPropagation ( capture ? : boolean ) {
133
+ validateResponderContext ( ) ;
123
134
const events = getEventsFromEventQueue ( ) ;
124
135
events . push ( { stopPropagation : true } ) ;
125
136
} ,
126
137
isPositionWithinTouchHitTarget ( doc : Document , x : number , y : number ) : boolean {
138
+ validateResponderContext ( ) ;
127
139
// This isn't available in some environments (JSDOM)
128
140
if ( typeof doc . elementFromPoint !== 'function' ) {
129
141
return false ;
@@ -151,6 +163,7 @@ const eventResponderContext: ReactResponderContext = {
151
163
return false ;
152
164
} ,
153
165
isTargetWithinEventComponent ( target : Element | Document ) : boolean {
166
+ validateResponderContext ( ) ;
154
167
if ( target != null ) {
155
168
let fiber = getClosestInstanceFromNode ( target ) ;
156
169
while ( fiber !== null ) {
@@ -182,6 +195,7 @@ const eventResponderContext: ReactResponderContext = {
182
195
doc : Document ,
183
196
rootEventTypes : Array < ReactEventResponderEventType > ,
184
197
) : void {
198
+ validateResponderContext ( ) ;
185
199
listenToResponderEventTypesImpl ( rootEventTypes , doc ) ;
186
200
for ( let i = 0 ; i < rootEventTypes . length ; i ++ ) {
187
201
const rootEventType = rootEventTypes [ i ] ;
@@ -197,12 +211,15 @@ const eventResponderContext: ReactResponderContext = {
197
211
rootEventComponentInstances ,
198
212
) ;
199
213
}
200
- rootEventComponentInstances . add ( currentInstance ) ;
214
+ rootEventComponentInstances . add (
215
+ ( ( currentInstance : any ) : ReactEventComponentInstance ) ,
216
+ ) ;
201
217
}
202
218
} ,
203
219
removeRootEventTypes (
204
220
rootEventTypes : Array < ReactEventResponderEventType > ,
205
221
) : void {
222
+ validateResponderContext ( ) ;
206
223
for ( let i = 0 ; i < rootEventTypes . length ; i ++ ) {
207
224
const rootEventType = rootEventTypes [ i ] ;
208
225
const topLevelEventType =
@@ -211,14 +228,18 @@ const eventResponderContext: ReactResponderContext = {
211
228
topLevelEventType ,
212
229
) ;
213
230
if ( rootEventComponents !== undefined ) {
214
- rootEventComponents . delete ( currentInstance ) ;
231
+ rootEventComponents . delete (
232
+ ( ( currentInstance : any ) : ReactEventComponentInstance ) ,
233
+ ) ;
215
234
}
216
235
}
217
236
} ,
218
237
hasOwnership ( ) : boolean {
238
+ validateResponderContext ( ) ;
219
239
return currentOwner === currentInstance ;
220
240
} ,
221
241
requestOwnership ( ) : boolean {
242
+ validateResponderContext ( ) ;
222
243
if ( currentOwner !== null ) {
223
244
return false ;
224
245
}
@@ -227,6 +248,7 @@ const eventResponderContext: ReactResponderContext = {
227
248
return true ;
228
249
} ,
229
250
releaseOwnership ( ) : boolean {
251
+ validateResponderContext ( ) ;
230
252
if ( currentOwner !== currentInstance ) {
231
253
return false ;
232
254
}
@@ -235,6 +257,7 @@ const eventResponderContext: ReactResponderContext = {
235
257
return false ;
236
258
} ,
237
259
setTimeout ( func : ( ) = > void , delay ) : Symbol {
260
+ validateResponderContext ( ) ;
238
261
if ( currentTimers === null ) {
239
262
currentTimers = new Map ( ) ;
240
263
}
@@ -253,14 +276,15 @@ const eventResponderContext: ReactResponderContext = {
253
276
currentTimers . set ( delay , timeout ) ;
254
277
}
255
278
timeout . timers . set ( timerId , {
256
- instance : currentInstance ,
279
+ instance : ( ( currentInstance : any ) : ReactEventComponentInstance ) ,
257
280
func,
258
281
id : timerId ,
259
282
} ) ;
260
283
activeTimeouts . set ( timerId , timeout ) ;
261
284
return timerId ;
262
285
} ,
263
286
clearTimeout ( timerId : Symbol ) : void {
287
+ validateResponderContext ( ) ;
264
288
const timeout = activeTimeouts . get ( timerId ) ;
265
289
266
290
if ( timeout !== undefined ) {
@@ -279,6 +303,7 @@ const eventResponderContext: ReactResponderContext = {
279
303
node : Element ,
280
304
props : null | Object ,
281
305
} > {
306
+ validateResponderContext ( ) ;
282
307
const eventTargetHostComponents = [ ] ;
283
308
let node = getClosestInstanceFromNode ( target ) ;
284
309
// We traverse up the fiber tree from the target fiber, to the
@@ -326,28 +351,26 @@ const eventResponderContext: ReactResponderContext = {
326
351
} ;
327
352
328
353
function getEventsFromEventQueue ( capture ?: boolean ) : Array < EventObjectTypes > {
354
+ const eventQueue = ( ( currentEventQueue : any ) : EventQueue ) ;
329
355
let events ;
330
356
if ( capture ) {
331
- events = currentEventQueue . capture ;
357
+ events = eventQueue . capture ;
332
358
if ( events === null ) {
333
- events = currentEventQueue . capture = [ ] ;
359
+ events = eventQueue . capture = [ ] ;
334
360
}
335
361
} else {
336
- events = currentEventQueue . bubble ;
362
+ events = eventQueue . bubble ;
337
363
if ( events === null ) {
338
- events = currentEventQueue . bubble = [ ] ;
364
+ events = eventQueue . bubble = [ ] ;
339
365
}
340
366
}
341
367
return events ;
342
368
}
343
369
344
370
function processTimers ( timers : Map < Symbol , ResponderTimer > ) : void {
345
- const previousEventQueue = currentEventQueue ;
346
- const previousInstance = currentInstance ;
371
+ const timersArr = Array . from ( timers . values ( ) ) ;
347
372
currentEventQueue = createEventQueue ( ) ;
348
-
349
373
try {
350
- const timersArr = Array . from ( timers . values ( ) ) ;
351
374
for ( let i = 0 ; i < timersArr . length ; i ++ ) {
352
375
const { instance, func, id} = timersArr [ i ] ;
353
376
currentInstance = instance ;
@@ -359,9 +382,9 @@ function processTimers(timers: Map<Symbol, ResponderTimer>): void {
359
382
}
360
383
batchedUpdates ( processEventQueue , currentEventQueue ) ;
361
384
} finally {
362
- currentInstance = previousInstance ;
363
- currentEventQueue = previousEventQueue ;
364
385
currentTimers = null ;
386
+ currentInstance = null ;
387
+ currentEventQueue = null ;
365
388
}
366
389
}
367
390
@@ -404,7 +427,9 @@ function createEventQueue(): EventQueue {
404
427
405
428
function processEvent ( event : $Shape < PartialEventObject > ) : void {
406
429
const type = event . type ;
407
- const listener = event . listener ;
430
+ const listener = ( ( eventListeners . get ( event ) : any ) : (
431
+ $Shape < PartialEventObject > ,
432
+ ) = > void ) ;
408
433
invokeGuardedCallbackAndCatchFirstError ( type , listener , undefined , event ) ;
409
434
}
410
435
@@ -435,7 +460,7 @@ function processEvents(
435
460
}
436
461
437
462
export function processEventQueue ( ) : void {
438
- const { bubble , capture , discrete } = currentEventQueue ;
463
+ const { bubble , capture , discrete } = ( ( currentEventQueue : any ) : EventQueue ) ;
439
464
440
465
if ( discrete ) {
441
466
interactiveUpdates ( ( ) => {
@@ -478,13 +503,8 @@ function handleTopLevelType(
478
503
return ;
479
504
}
480
505
}
481
- const previousInstance = currentInstance ;
482
506
currentInstance = eventComponentInstance ;
483
- try {
484
- responder . onEvent ( responderEvent , eventResponderContext , props , state ) ;
485
- } finally {
486
- currentInstance = previousInstance ;
487
- }
507
+ responder . onEvent ( responderEvent , eventResponderContext , props , state ) ;
488
508
}
489
509
490
510
export function runResponderEventsInBatch (
@@ -502,54 +522,60 @@ export function runResponderEventsInBatch(
502
522
( ( nativeEventTarget : any ) : Element | Document ) ,
503
523
eventSystemFlags ,
504
524
) ;
505
- let node = targetFiber ;
506
- // Traverse up the fiber tree till we find event component fibers.
507
- while ( node !== null ) {
508
- if ( node . tag === EventComponent ) {
509
- const eventComponentInstance = node . stateNode ;
510
- handleTopLevelType (
511
- topLevelType ,
512
- responderEvent ,
513
- eventComponentInstance ,
514
- false ,
515
- ) ;
525
+
526
+ try {
527
+ let node = targetFiber ;
528
+ // Traverse up the fiber tree till we find event component fibers.
529
+ while ( node !== null ) {
530
+ if ( node . tag === EventComponent ) {
531
+ const eventComponentInstance = node . stateNode ;
532
+ handleTopLevelType (
533
+ topLevelType ,
534
+ responderEvent ,
535
+ eventComponentInstance ,
536
+ false ,
537
+ ) ;
538
+ }
539
+ node = node . return ;
516
540
}
517
- node = node . return ;
518
- }
519
- // Handle root level events
520
- const rootEventInstances = rootEventTypesToEventComponentInstances . get (
521
- topLevelType ,
522
- ) ;
523
- if ( rootEventInstances !== undefined ) {
524
- const rootEventComponentInstances = Array . from ( rootEventInstances ) ;
525
-
526
- for ( let i = 0 ; i < rootEventComponentInstances . length ; i ++ ) {
527
- const rootEventComponentInstance = rootEventComponentInstances [ i ] ;
528
- handleTopLevelType (
529
- topLevelType ,
530
- responderEvent ,
531
- rootEventComponentInstance ,
532
- true ,
533
- ) ;
541
+ // Handle root level events
542
+ const rootEventInstances = rootEventTypesToEventComponentInstances . get (
543
+ topLevelType ,
544
+ ) ;
545
+ if ( rootEventInstances !== undefined ) {
546
+ const rootEventComponentInstances = Array . from ( rootEventInstances ) ;
547
+
548
+ for ( let i = 0 ; i < rootEventComponentInstances . length ; i ++ ) {
549
+ const rootEventComponentInstance = rootEventComponentInstances [ i ] ;
550
+ handleTopLevelType (
551
+ topLevelType ,
552
+ responderEvent ,
553
+ rootEventComponentInstance ,
554
+ true ,
555
+ ) ;
556
+ }
534
557
}
558
+ processEventQueue ( ) ;
559
+ } finally {
560
+ currentTimers = null ;
561
+ currentInstance = null ;
562
+ currentEventQueue = null ;
535
563
}
536
- processEventQueue ( ) ;
537
- currentTimers = null ;
538
564
}
539
565
}
540
566
541
567
function triggerOwnershipListeners ( ) : void {
542
568
const listeningInstances = Array . from ( ownershipChangeListeners ) ;
543
569
const previousInstance = currentInstance ;
544
- for ( let i = 0 ; i < listeningInstances . length ; i ++ ) {
545
- const instance = listeningInstances [ i ] ;
546
- const { props , responder , state } = instance ;
547
- currentInstance = instance ;
548
- try {
570
+ try {
571
+ for ( let i = 0 ; i < listeningInstances . length ; i ++ ) {
572
+ const instance = listeningInstances [ i ] ;
573
+ const { props , responder , state } = instance ;
574
+ currentInstance = instance ;
549
575
responder . onOwnershipChange ( eventResponderContext , props , state ) ;
550
- } finally {
551
- currentInstance = previousInstance ;
552
576
}
577
+ } finally {
578
+ currentInstance = previousInstance ;
553
579
}
554
580
}
555
581
@@ -569,15 +595,13 @@ export function unmountEventResponder(
569
595
const onUnmount = responder . onUnmount ;
570
596
if ( onUnmount !== undefined ) {
571
597
let { props, state} = eventComponentInstance ;
572
- const previousEventQueue = currentEventQueue ;
573
- const previousInstance = currentInstance ;
574
598
currentEventQueue = createEventQueue ( ) ;
575
599
currentInstance = eventComponentInstance ;
576
600
try {
577
601
onUnmount ( eventResponderContext , props , state ) ;
578
602
} finally {
579
- currentEventQueue = previousEventQueue ;
580
- currentInstance = previousInstance ;
603
+ currentEventQueue = null ;
604
+ currentInstance = null ;
581
605
currentTimers = null ;
582
606
}
583
607
}
@@ -589,3 +613,11 @@ export function unmountEventResponder(
589
613
ownershipChangeListeners . delete ( eventComponentInstance ) ;
590
614
}
591
615
}
616
+
617
+ function validateResponderContext ( ) : void {
618
+ invariant (
619
+ currentEventQueue && currentInstance ,
620
+ 'An event responder context was used outside of an event cycle. ' +
621
+ 'Use context.setTimeout() to use asynchronous responder context outside of event cycle .' ,
622
+ ) ;
623
+ }
0 commit comments