[Flare] Rework the responder dispatching/batching mechanism (#16334)
Dominic Gannaway committed
Aug 19, 2019 at 19:22 UTC
dce430ad92d7ed6be5b934f4263d4a39e068ee29
15 files changed
+149
-565
packages/legacy-events/ReactGenericBatching.js
+17
-3
@@ -11,6 +11,8 @@ import {
11
} from './ReactControlledComponent';
12
import {enableFlareAPI} from 'shared/ReactFeatureFlags';
13
14
+import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
15
+
16
// Used as a way to call batchedUpdates when we don't have a reference to
17
// the renderer. Such as when we're dispatching events or if third party
18
// libraries need to call batchedUpdates. Eventually, this API will go away when
@@ -28,6 +30,7 @@ let flushDiscreteUpdatesImpl = function() {};
30
let batchedEventUpdatesImpl = batchedUpdatesImpl;
31
32
let isInsideEventHandler = false;
33
+let isBatchingEventUpdates = false;
34
35
function finishEventHandler() {
36
// Here we wait until all updates have propagated, which is important
@@ -60,20 +63,31 @@ export function batchedUpdates(fn, bookkeeping) {
63
}
64
65
export function batchedEventUpdates(fn, a, b) {
63
- if (isInsideEventHandler) {
66
+ if (isBatchingEventUpdates) {
67
// If we are currently inside another batch, we need to wait until it
68
// fully completes before restoring state.
69
return fn(a, b);
70
}
68
- isInsideEventHandler = true;
71
+ isBatchingEventUpdates = true;
72
try {
73
return batchedEventUpdatesImpl(fn, a, b);
74
} finally {
72
- isInsideEventHandler = false;
75
+ isBatchingEventUpdates = false;
76
finishEventHandler();
77
}
78
}
79
80
+export function executeUserEventHandler(fn: any => void, value: any) {
81
+ const previouslyInEventHandler = isInsideEventHandler;
82
+ try {
83
+ isInsideEventHandler = true;
84
+ const type = typeof value === 'object' && value !== null ? value.type : '';
85
+ invokeGuardedCallbackAndCatchFirstError(type, fn, undefined, value);
86
+ } finally {
87
+ isInsideEventHandler = previouslyInEventHandler;
88
+ }
89
+}
90
+
91
export function discreteUpdates(fn, a, b, c) {
92
const prevIsInsideEventHandler = isInsideEventHandler;
93
isInsideEventHandler = true;
packages/react-dom/src/events/DOMEventResponderSystem.js
+62
-191
@@ -25,12 +25,12 @@ import {
25
batchedEventUpdates,
26
discreteUpdates,
27
flushDiscreteUpdatesIfNeeded,
28
+ executeUserEventHandler,
29
} from 'legacy-events/ReactGenericBatching';
30
import {enqueueStateRestore} from 'legacy-events/ReactControlledComponent';
31
import type {Fiber} from 'react-reconciler/src/ReactFiber';
32
import warning from 'shared/warning';
33
import {enableFlareAPI} from 'shared/ReactFeatureFlags';
33
-import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
34
import invariant from 'shared/invariant';
35
import {
36
isFiberSuspenseAndTimedOut,
@@ -61,12 +61,6 @@ export function setListenToResponderEventTypes(
61
listenToResponderEventTypesImpl = _listenToResponderEventTypesImpl;
62
}
63
64
-type EventQueueItem = {|
65
- listener: (val: any) => void,
66
- value: any,
67
-|};
68
-type EventQueue = Array<EventQueueItem>;
69
-
64
type ResponderTimeout = {|
65
id: TimeoutID,
66
timers: Map<number, ResponderTimer>,
@@ -84,15 +78,10 @@ const rootEventTypesToEventResponderInstances: Map<
78
DOMTopLevelEventType | string,
79
Set<ReactDOMEventResponderInstance>,
80
> = new Map();
87
-const ownershipChangeListeners: Set<ReactDOMEventResponderInstance> = new Set();
88
-
89
-let globalOwner = null;
81
82
let currentTimeStamp = 0;
83
let currentTimers = new Map();
84
let currentInstance: null | ReactDOMEventResponderInstance = null;
94
-let currentEventQueue: null | EventQueue = null;
95
-let currentEventQueuePriority: EventPriority = ContinuousEvent;
85
let currentTimerIDCounter = 0;
86
let currentDocument: null | Document = null;
87
@@ -104,12 +93,29 @@ const eventResponderContext: ReactDOMResponderContext = {
93
): void {
94
validateResponderContext();
95
validateEventValue(eventValue);
107
- if (eventPriority < currentEventQueuePriority) {
108
- currentEventQueuePriority = eventPriority;
96
+ switch (eventPriority) {
97
+ case DiscreteEvent: {
98
+ flushDiscreteUpdatesIfNeeded(currentTimeStamp);
99
+ discreteUpdates(() =>
100
+ executeUserEventHandler(eventListener, eventValue),
101
+ );
102
+ break;
103
+ }
104
+ case UserBlockingEvent: {
105
+ if (enableUserBlockingEvents) {
106
+ runWithPriority(UserBlockingPriority, () =>
107
+ executeUserEventHandler(eventListener, eventValue),
108
+ );
109
+ } else {
110
+ executeUserEventHandler(eventListener, eventValue);
111
+ }
112
+ break;
113
+ }
114
+ case ContinuousEvent: {
115
+ executeUserEventHandler(eventListener, eventValue);
116
+ break;
117
+ }
118
}
110
- ((currentEventQueue: any): EventQueue).push(
111
- createEventQueueItem(eventValue, eventListener),
112
- );
119
},
120
isTargetWithinResponder(target: Element | Document): boolean {
121
validateResponderContext();
@@ -196,25 +202,6 @@ const eventResponderContext: ReactDOMResponderContext = {
202
}
203
}
204
},
199
- hasOwnership(): boolean {
200
- validateResponderContext();
201
- return globalOwner === currentInstance;
202
- },
203
- requestGlobalOwnership(): boolean {
204
- validateResponderContext();
205
- if (globalOwner !== null) {
206
- return false;
207
- }
208
- globalOwner = currentInstance;
209
- triggerOwnershipListeners();
210
- return true;
211
- },
212
- releaseOwnership(): boolean {
213
- validateResponderContext();
214
- return releaseOwnershipForEventResponderInstance(
215
- ((currentInstance: any): ReactDOMEventResponderInstance),
216
- );
217
- },
205
setTimeout(func: () => void, delay): number {
206
validateResponderContext();
207
if (currentTimers === null) {
@@ -379,16 +366,6 @@ function collectFocusableElements(
366
}
367
}
368
382
-function createEventQueueItem(
383
- value: any,
384
- listener: (val: any) => void,
385
-): EventQueueItem {
386
- return {
387
- value,
388
- listener,
389
- };
390
-}
391
-
369
function doesFiberHaveResponder(
370
fiber: Fiber,
371
responder: ReactDOMEventResponder,
@@ -409,17 +386,6 @@ function getActiveDocument(): Document {
386
return ((currentDocument: any): Document);
387
}
388
412
-function releaseOwnershipForEventResponderInstance(
413
- eventResponderInstance: ReactDOMEventResponderInstance,
414
-): boolean {
415
- if (globalOwner === eventResponderInstance) {
416
- globalOwner = null;
417
- triggerOwnershipListeners();
418
- return true;
419
- }
420
- return false;
421
-}
422
-
389
function isFiberHostComponentFocusable(fiber: Fiber): boolean {
390
if (fiber.tag !== HostComponent) {
391
return false;
@@ -452,24 +418,22 @@ function processTimers(
418
delay: number,
419
): void {
420
const timersArr = Array.from(timers.values());
455
- currentEventQueuePriority = ContinuousEvent;
421
try {
457
- for (let i = 0; i < timersArr.length; i++) {
458
- const {instance, func, id, timeStamp} = timersArr[i];
459
- currentInstance = instance;
460
- currentEventQueue = [];
461
- currentTimeStamp = timeStamp + delay;
462
- try {
463
- func();
464
- } finally {
465
- activeTimeouts.delete(id);
422
+ batchedEventUpdates(() => {
423
+ for (let i = 0; i < timersArr.length; i++) {
424
+ const {instance, func, id, timeStamp} = timersArr[i];
425
+ currentInstance = instance;
426
+ currentTimeStamp = timeStamp + delay;
427
+ try {
428
+ func();
429
+ } finally {
430
+ activeTimeouts.delete(id);
431
+ }
432
}
467
- }
468
- processEventQueue();
433
+ });
434
} finally {
435
currentTimers = null;
436
currentInstance = null;
472
- currentEventQueue = null;
437
currentTimeStamp = 0;
438
}
439
}
@@ -508,45 +472,6 @@ function createDOMResponderEvent(
472
};
473
}
474
511
-function processEvents(eventQueue: EventQueue): void {
512
- for (let i = 0, length = eventQueue.length; i < length; i++) {
513
- const {value, listener} = eventQueue[i];
514
- const type = typeof value === 'object' && value !== null ? value.type : '';
515
- invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, value);
516
- }
517
-}
518
-
519
-function processEventQueue(): void {
520
- const eventQueue = ((currentEventQueue: any): EventQueue);
521
- if (eventQueue.length === 0) {
522
- return;
523
- }
524
- switch (currentEventQueuePriority) {
525
- case DiscreteEvent: {
526
- flushDiscreteUpdatesIfNeeded(currentTimeStamp);
527
- discreteUpdates(() => {
528
- batchedEventUpdates(processEvents, eventQueue);
529
- });
530
- break;
531
- }
532
- case UserBlockingEvent: {
533
- if (enableUserBlockingEvents) {
534
- runWithPriority(
535
- UserBlockingPriority,
536
- batchedEventUpdates.bind(null, processEvents, eventQueue),
537
- );
538
- } else {
539
- batchedEventUpdates(processEvents, eventQueue);
540
- }
541
- break;
542
- }
543
- case ContinuousEvent: {
544
- batchedEventUpdates(processEvents, eventQueue);
545
- break;
546
- }
547
- }
548
-}
549
-
475
function responderEventTypesContainType(
476
eventTypes: Array<string>,
477
type: string,
@@ -571,12 +496,6 @@ function validateResponderTargetEventTypes(
496
return false;
497
}
498
574
-function validateOwnership(
575
- responderInstance: ReactDOMEventResponderInstance,
576
-): boolean {
577
- return globalOwner === null || globalOwner === responderInstance;
578
-}
579
-
499
function traverseAndHandleEventResponderInstances(
500
topLevelType: string,
501
targetFiber: null | Fiber,
@@ -610,22 +529,19 @@ function traverseAndHandleEventResponderInstances(
529
const responderInstances = Array.from(respondersMap.values());
530
for (let i = 0, length = responderInstances.length; i < length; i++) {
531
const responderInstance = responderInstances[i];
613
-
614
- if (validateOwnership(responderInstance)) {
615
- const {props, responder, state, target} = responderInstance;
616
- if (
617
- !visitedResponders.has(responder) &&
618
- validateResponderTargetEventTypes(eventType, responder)
619
- ) {
620
- visitedResponders.add(responder);
621
- const onEvent = responder.onEvent;
622
- if (onEvent !== null) {
623
- currentInstance = responderInstance;
624
- responderEvent.responderTarget = ((target: any):
625
- | Element
626
- | Document);
627
- onEvent(responderEvent, eventResponderContext, props, state);
628
- }
532
+ const {props, responder, state, target} = responderInstance;
533
+ if (
534
+ !visitedResponders.has(responder) &&
535
+ validateResponderTargetEventTypes(eventType, responder)
536
+ ) {
537
+ visitedResponders.add(responder);
538
+ const onEvent = responder.onEvent;
539
+ if (onEvent !== null) {
540
+ currentInstance = responderInstance;
541
+ responderEvent.responderTarget = ((target: any):
542
+ | Element
543
+ | Document);
544
+ onEvent(responderEvent, eventResponderContext, props, state);
545
}
546
}
547
}
@@ -642,9 +558,6 @@ function traverseAndHandleEventResponderInstances(
558
559
for (let i = 0; i < responderInstances.length; i++) {
560
const responderInstance = responderInstances[i];
645
- if (!validateOwnership(responderInstance)) {
646
- continue;
647
- }
561
const {props, responder, state, target} = responderInstance;
562
const onRootEvent = responder.onRootEvent;
563
if (onRootEvent !== null) {
@@ -656,51 +569,20 @@ function traverseAndHandleEventResponderInstances(
569
}
570
}
571
659
-function triggerOwnershipListeners(): void {
660
- const listeningInstances = Array.from(ownershipChangeListeners);
661
- const previousInstance = currentInstance;
662
- const previousEventQueuePriority = currentEventQueuePriority;
663
- const previousEventQueue = currentEventQueue;
664
- try {
665
- for (let i = 0; i < listeningInstances.length; i++) {
666
- const instance = listeningInstances[i];
667
- const {props, responder, state} = instance;
668
- currentInstance = instance;
669
- currentEventQueuePriority = ContinuousEvent;
670
- currentEventQueue = [];
671
- const onOwnershipChange = ((responder: any): ReactDOMEventResponder)
672
- .onOwnershipChange;
673
- if (onOwnershipChange !== null) {
674
- onOwnershipChange(eventResponderContext, props, state);
675
- }
676
- }
677
- processEventQueue();
678
- } finally {
679
- currentInstance = previousInstance;
680
- currentEventQueue = previousEventQueue;
681
- currentEventQueuePriority = previousEventQueuePriority;
682
- }
683
-}
684
-
572
export function mountEventResponder(
573
responder: ReactDOMEventResponder,
574
responderInstance: ReactDOMEventResponderInstance,
575
props: Object,
576
state: Object,
577
) {
691
- if (responder.onOwnershipChange !== null) {
692
- ownershipChangeListeners.add(responderInstance);
693
- }
578
const onMount = responder.onMount;
579
if (onMount !== null) {
696
- currentEventQueuePriority = ContinuousEvent;
580
currentInstance = responderInstance;
698
- currentEventQueue = [];
581
try {
700
- onMount(eventResponderContext, props, state);
701
- processEventQueue();
582
+ batchedEventUpdates(() => {
583
+ onMount(eventResponderContext, props, state);
584
+ });
585
} finally {
703
- currentEventQueue = null;
586
currentInstance = null;
587
currentTimers = null;
588
}
@@ -714,22 +596,16 @@ export function unmountEventResponder(
596
const onUnmount = responder.onUnmount;
597
if (onUnmount !== null) {
598
let {props, state} = responderInstance;
717
- currentEventQueue = [];
718
- currentEventQueuePriority = ContinuousEvent;
599
currentInstance = responderInstance;
600
try {
721
- onUnmount(eventResponderContext, props, state);
722
- processEventQueue();
601
+ batchedEventUpdates(() => {
602
+ onUnmount(eventResponderContext, props, state);
603
+ });
604
} finally {
724
- currentEventQueue = null;
605
currentInstance = null;
606
currentTimers = null;
607
}
608
}
729
- releaseOwnershipForEventResponderInstance(responderInstance);
730
- if (responder.onOwnershipChange !== null) {
731
- ownershipChangeListeners.delete(responderInstance);
732
- }
609
const rootEventTypesSet = responderInstance.rootEventTypes;
610
if (rootEventTypesSet !== null) {
611
const rootEventTypes = Array.from(rootEventTypesSet);
@@ -762,15 +638,11 @@ export function dispatchEventForResponderEventSystem(
638
eventSystemFlags: EventSystemFlags,
639
): void {
640
if (enableFlareAPI) {
765
- const previousEventQueue = currentEventQueue;
641
const previousInstance = currentInstance;
642
const previousTimers = currentTimers;
643
const previousTimeStamp = currentTimeStamp;
644
const previousDocument = currentDocument;
770
- const previousEventQueuePriority = currentEventQueuePriority;
645
currentTimers = null;
772
- currentEventQueue = [];
773
- currentEventQueuePriority = ContinuousEvent;
646
// nodeType 9 is DOCUMENT_NODE
647
currentDocument =
648
(nativeEventTarget: any).nodeType === 9
@@ -779,21 +651,20 @@ export function dispatchEventForResponderEventSystem(
651
// We might want to control timeStamp another way here
652
currentTimeStamp = (nativeEvent: any).timeStamp;
653
try {
782
- traverseAndHandleEventResponderInstances(
783
- topLevelType,
784
- targetFiber,
785
- nativeEvent,
786
- nativeEventTarget,
787
- eventSystemFlags,
788
- );
789
- processEventQueue();
654
+ batchedEventUpdates(() => {
655
+ traverseAndHandleEventResponderInstances(
656
+ topLevelType,
657
+ targetFiber,
658
+ nativeEvent,
659
+ nativeEventTarget,
660
+ eventSystemFlags,
661
+ );
662
+ });
663
} finally {
664
currentTimers = previousTimers;
665
currentInstance = previousInstance;
793
- currentEventQueue = previousEventQueue;
666
currentTimeStamp = previousTimeStamp;
667
currentDocument = previousDocument;
796
- currentEventQueuePriority = previousEventQueuePriority;
668
}
669
}
670
}
packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
-33
@@ -26,7 +26,6 @@ function createEventResponder({
26
targetEventTypes,
27
onMount,
28
onUnmount,
29
- onOwnershipChange,
29
getInitialState,
30
}) {
31
return React.unstable_createResponder('TestEventResponder', {
@@ -36,7 +35,6 @@ function createEventResponder({
35
onRootEvent,
36
onMount,
37
onUnmount,
39
- onOwnershipChange,
38
getInitialState,
39
});
40
}
@@ -644,37 +642,6 @@ describe('DOMEventResponderSystem', () => {
642
expect(counter).toEqual(5);
643
});
644
647
- it('the event responder onOwnershipChange() function should fire', () => {
648
- let onOwnershipChangeFired = 0;
649
- let ownershipGained = false;
650
- const buttonRef = React.createRef();
651
-
652
- const TestResponder = createEventResponder({
653
- targetEventTypes: ['click'],
654
- onEvent: (event, context, props, state) => {
655
- ownershipGained = context.requestGlobalOwnership();
656
- },
657
- onOwnershipChange: () => {
658
- onOwnershipChangeFired++;
659
- },
660
- });
661
-
662
- const Test = () => {
663
- const listener = React.unstable_useResponder(TestResponder, {});
664
- return <button ref={buttonRef} listeners={listener} />;
665
- };
666
-
667
- ReactDOM.render(<Test />, container);
668
-
669
- // Clicking the button should trigger the event responder onEvent()
670
- let buttonElement = buttonRef.current;
671
- dispatchClickEvent(buttonElement);
672
- jest.runAllTimers();
673
-
674
- expect(ownershipGained).toEqual(true);
675
- expect(onOwnershipChangeFired).toEqual(1);
676
- });
677
-
645
it('the event responder root listeners should fire on a root click event', () => {
646
let eventResponderFiredCount = 0;
647
let eventLog = [];
packages/react-events/README.md
-19
@@ -50,10 +50,6 @@ elements within the Event Responder.
50
51
Called after an Event Responder in mounted.
52
53
-### onOwnershipChange?: (context: ResponderContext, props, state)
54
-
55
-Called when ownership is granted or terminated (either globally or for the responder) for an Event Responder instance.
56
-
53
### onRootEvent?: (event: ResponderEvent, context: ResponderContext, props, state)
54
55
Called when any of the `rootEventTypes` are dispatched on the root of the app.
@@ -101,10 +97,6 @@ context.dispatchEvent('onPress', event, DiscreteEvent);
97
Returns every DOM element that can be focused within the scope of the Event
98
Responder instance.
99
104
-### hasOwnership(): boolean
105
-
106
-Returns `true` if the instance has taken ownership of the responder.
107
-
100
### isTargetWithinNode(target: Element, element: Element): boolean
101
102
Returns `true` if `target` is a child of `element`.
@@ -118,21 +110,10 @@ Returns `true` is the target element is within the subtree of the Event Responde
110
Returns `true` is the target element is within the current Event Responder's scope. If the target element
111
is within the scope of the same responder, but owned by another Event Responder instance, this will return `false`.
112
121
-### releaseOwnership(): boolean
122
-
123
-Returns `true` if the instance released ownership of the Event Responder instance.
124
-
113
### removeRootEventTypes(eventTypes: Array<ResponderEventType>)
114
115
Remove the root event types added with `addRootEventTypes`.
116
129
-### requestGlobalOwnership(): boolean
130
-
131
-The current Event Responder instance can request global ownership of the event system. When an Event Responder instance
132
-has global ownership, only that instance and its responder are active. To release ownership to other event responders,
133
-either `releaseOwnership()` must be called or the Event Responder instance that had global ownership must be
134
-unmounted. Calling `requestGlobalOwnership` also returns `true`/`false` if the request was successful.
135
-
117
### setTimeout(func: () => void, delay: number): Symbol
118
119
This can be used to dispatch async events, e.g., those that fire after a delay.
packages/react-events/src/dom/Drag.js
+4
-23
@@ -39,7 +39,6 @@ type DragState = {|
39
startY: number,
40
x: number,
41
y: number,
42
- ownershipClaimed: boolean,
42
|};
43
44
// In the case we don't have PointerEvents (Safari), we listen to touch events
@@ -111,7 +110,6 @@ const dragResponderImpl = {
110
startY: 0,
111
x: 0,
112
y: 0,
114
- ownershipClaimed: false,
113
};
114
},
115
onEvent(
@@ -182,24 +180,10 @@ const dragResponderImpl = {
180
return;
181
}
182
if (!state.isDragging) {
185
- let shouldEnableDragging = true;
186
-
187
- if (props.shouldClaimOwnership && props.shouldClaimOwnership()) {
188
- shouldEnableDragging = context.requestGlobalOwnership();
189
- if (shouldEnableDragging) {
190
- state.ownershipClaimed = true;
191
- }
192
- }
193
- if (shouldEnableDragging) {
194
- state.isDragging = true;
195
- const onDragChange = props.onDragChange;
196
- if (isFunction(onDragChange)) {
197
- context.dispatchEvent(true, onDragChange, UserBlockingEvent);
198
- }
199
- } else {
200
- state.dragTarget = null;
201
- state.isPointerDown = false;
202
- context.removeRootEventTypes(rootEventTypes);
183
+ state.isDragging = true;
184
+ const onDragChange = props.onDragChange;
185
+ if (isFunction(onDragChange)) {
186
+ context.dispatchEvent(true, onDragChange, UserBlockingEvent);
187
}
188
} else {
189
const onDragMove = props.onDragMove;
@@ -228,9 +212,6 @@ const dragResponderImpl = {
212
case 'mouseup':
213
case 'pointerup': {
214
if (state.isDragging) {
231
- if (state.ownershipClaimed) {
232
- context.releaseOwnership();
233
- }
215
const onDragEnd = props.onDragEnd;
216
if (isFunction(onDragEnd)) {
217
dispatchDragEvent(
packages/react-events/src/dom/Focus.js
-14
@@ -341,13 +341,6 @@ const focusResponderImpl = {
341
) {
342
unmountFocusResponder(context, props, state);
343
},
344
- onOwnershipChange(
345
- context: ReactDOMResponderContext,
346
- props: FocusProps,
347
- state: FocusState,
348
- ) {
349
- unmountFocusResponder(context, props, state);
350
- },
344
};
345
346
export const FocusResponder = React.unstable_createResponder(
@@ -485,13 +478,6 @@ const focusWithinResponderImpl = {
478
) {
479
unmountFocusWithinResponder(context, props, state);
480
},
488
- onOwnershipChange(
489
- context: ReactDOMResponderContext,
490
- props: FocusWithinProps,
491
- state: FocusState,
492
- ) {
493
- unmountFocusWithinResponder(context, props, state);
494
- },
481
};
482
483
export const FocusWithinResponder = React.unstable_createResponder(
packages/react-events/src/dom/Hover.js
-2
@@ -260,7 +260,6 @@ const hoverResponderImpl = {
260
}
261
},
262
onUnmount: unmountResponder,
263
- onOwnershipChange: unmountResponder,
263
};
264
265
const hoverResponderFallbackImpl = {
@@ -335,7 +334,6 @@ const hoverResponderFallbackImpl = {
334
}
335
},
336
onUnmount: unmountResponder,
338
- onOwnershipChange: unmountResponder,
337
};
338
339
export const HoverResponder = React.unstable_createResponder(
packages/react-events/src/dom/Press.js
-7
@@ -841,13 +841,6 @@ const pressResponderImpl = {
841
) {
842
unmountResponder(context, props, state);
843
},
844
- onOwnershipChange(
845
- context: ReactDOMResponderContext,
846
- props: PressProps,
847
- state: PressState,
848
- ) {
849
- unmountResponder(context, props, state);
850
- },
844
};
845
846
export const PressResponder = React.unstable_createResponder(
packages/react-events/src/dom/Scroll.js
-7
@@ -303,13 +303,6 @@ const scrollResponderImpl = {
303
) {
304
// TODO
305
},
306
- onOwnershipChange(
307
- context: ReactDOMResponderContext,
308
- props: ScrollProps,
309
- state: ScrollState,
310
- ) {
311
- // TODO
312
- },
306
};
307
308
export const ScrollResponder = React.unstable_createResponder(
packages/react-events/src/dom/Swipe.js
+7
-24
@@ -101,7 +101,6 @@ type SwipeState = {
101
swipeTarget: null | Element | Document,
102
x: number,
103
y: number,
104
- ownershipClaimed: boolean,
104
};
105
106
const swipeResponderImpl = {
@@ -117,7 +116,6 @@ const swipeResponderImpl = {
116
swipeTarget: null,
117
x: 0,
118
y: 0,
120
- ownershipClaimed: false,
119
};
120
},
121
onEvent(
@@ -141,25 +139,13 @@ const swipeResponderImpl = {
139
const x = (obj: any).screenX;
140
const y = (obj: any).screenY;
141
144
- let shouldEnableSwiping = true;
145
-
146
- if (props.shouldClaimOwnership && props.shouldClaimOwnership()) {
147
- shouldEnableSwiping = context.requestGlobalOwnership();
148
- if (shouldEnableSwiping) {
149
- state.ownershipClaimed = true;
150
- }
151
- }
152
- if (shouldEnableSwiping) {
153
- state.isSwiping = true;
154
- state.startX = x;
155
- state.startY = y;
156
- state.x = x;
157
- state.y = y;
158
- state.swipeTarget = target;
159
- context.addRootEventTypes(rootEventTypes);
160
- } else {
161
- state.touchId = null;
162
- }
142
+ state.isSwiping = true;
143
+ state.startX = x;
144
+ state.startY = y;
145
+ state.x = x;
146
+ state.y = y;
147
+ state.swipeTarget = target;
148
+ context.addRootEventTypes(rootEventTypes);
149
}
150
break;
151
}
@@ -238,9 +224,6 @@ const swipeResponderImpl = {
224
if (state.x === state.startX && state.y === state.startY) {
225
return;
226
}
241
- if (state.ownershipClaimed) {
242
- context.releaseOwnership();
243
- }
227
const direction = state.direction;
228
const lastDirection = state.lastDirection;
229
if (direction !== lastDirection) {
packages/react-events/src/dom/__tests__/Drag-test.internal.js
-55
@@ -141,61 +141,6 @@ describe('Drag event responder', () => {
141
expect(events).toEqual(['dragstart', 'dragend']);
142
});
143
144
- it('should support onDragStart and onDragEnd with ownership', () => {
145
- let divRef = React.createRef();
146
- let events = [];
147
-
148
- function handleDragStart() {
149
- events.push('dragstart');
150
- }
151
-
152
- function handleDragEnd() {
153
- events.push('dragend');
154
- }
155
-
156
- function Component() {
157
- const listener = useDragResponder({
158
- onDragStart: handleDragStart,
159
- onDragEnd: handleDragEnd,
160
- shouldClaimOwnership: () => true,
161
- });
162
- return (
163
- <div ref={divRef} listeners={listener}>
164
- Drag me!
165
- </div>
166
- );
167
- }
168
-
169
- ReactDOM.render(<Component />, container);
170
-
171
- const mouseOverEvent = document.createEvent('MouseEvents');
172
- mouseOverEvent.initEvent('mousedown', true, true);
173
- divRef.current.dispatchEvent(mouseOverEvent);
174
-
175
- const mouseMoveEvent = document.createEvent('MouseEvents');
176
- for (let index = 0; index <= 20; index++) {
177
- mouseMoveEvent.initMouseEvent(
178
- 'mousemove',
179
- true,
180
- true,
181
- window,
182
- 1,
183
- index,
184
- index,
185
- 50,
186
- 50,
187
- );
188
- divRef.current.dispatchEvent(mouseMoveEvent);
189
- }
190
- divRef.current.dispatchEvent(mouseMoveEvent);
191
-
192
- const mouseUpEvent = document.createEvent('MouseEvents');
193
- mouseUpEvent.initEvent('mouseup', true, true);
194
- divRef.current.dispatchEvent(mouseUpEvent);
195
-
196
- expect(events).toEqual(['dragstart', 'dragend']);
197
- });
198
-
144
it('should support onDragMove', () => {
145
let divRef = React.createRef();
146
let events = [];
packages/react-native-renderer/src/ReactFabricEventResponderSystem.js
+59
-179
@@ -13,6 +13,7 @@ import {
13
batchedEventUpdates,
14
discreteUpdates,
15
flushDiscreteUpdatesIfNeeded,
16
+ executeUserEventHandler,
17
} from 'legacy-events/ReactGenericBatching';
18
import type {
19
ReactEventResponder,
@@ -30,7 +31,6 @@ import {
31
UserBlockingEvent,
32
DiscreteEvent,
33
} from './ReactNativeTypes';
33
-import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
34
import {enableUserBlockingEvents} from 'shared/ReactFeatureFlags';
35
import warning from 'shared/warning';
36
import invariant from 'shared/invariant';
@@ -43,12 +43,6 @@ const {
43
unstable_runWithPriority: runWithPriority,
44
} = Scheduler;
45
46
-type EventQueueItem = {|
47
- listener: (val: any) => void,
48
- value: any,
49
-|};
50
-type EventQueue = Array<EventQueueItem>;
51
-
46
type ResponderTimeout = {|
47
id: TimeoutID,
48
timers: Map<number, ResponderTimer>,
@@ -78,17 +72,10 @@ const rootEventTypesToEventResponderInstances: Map<
72
string,
73
Set<ReactNativeEventResponderInstance>,
74
> = new Map();
81
-const ownershipChangeListeners: Set<
82
- ReactNativeEventResponderInstance,
83
-> = new Set();
84
-
85
-let globalOwner = null;
75
76
let currentTimeStamp = 0;
77
let currentTimers = new Map();
78
let currentInstance: null | ReactNativeEventResponderInstance = null;
90
-let currentEventQueue: null | EventQueue = null;
91
-let currentEventQueuePriority: EventPriority = ContinuousEvent;
79
let currentTimerIDCounter = 0;
80
81
const eventResponderContext: ReactNativeResponderContext = {
@@ -99,12 +86,29 @@ const eventResponderContext: ReactNativeResponderContext = {
86
): void {
87
validateResponderContext();
88
validateEventValue(eventValue);
102
- if (eventPriority < currentEventQueuePriority) {
103
- currentEventQueuePriority = eventPriority;
89
+ switch (eventPriority) {
90
+ case DiscreteEvent: {
91
+ flushDiscreteUpdatesIfNeeded(currentTimeStamp);
92
+ discreteUpdates(() =>
93
+ executeUserEventHandler(eventListener, eventValue),
94
+ );
95
+ break;
96
+ }
97
+ case UserBlockingEvent: {
98
+ if (enableUserBlockingEvents) {
99
+ runWithPriority(UserBlockingPriority, () =>
100
+ executeUserEventHandler(eventListener, eventValue),
101
+ );
102
+ } else {
103
+ executeUserEventHandler(eventListener, eventValue);
104
+ }
105
+ break;
106
+ }
107
+ case ContinuousEvent: {
108
+ executeUserEventHandler(eventListener, eventValue);
109
+ break;
110
+ }
111
}
105
- ((currentEventQueue: any): EventQueue).push(
106
- createEventQueueItem(eventValue, eventListener),
107
- );
112
},
113
isTargetWithinNode(
114
childTarget: ReactNativeEventTarget,
@@ -215,16 +219,6 @@ const eventResponderContext: ReactNativeResponderContext = {
219
},
220
};
221
218
-function createEventQueueItem(
219
- value: any,
220
- listener: (val: any) => void,
221
-): EventQueueItem {
222
- return {
223
- value,
224
- listener,
225
- };
226
-}
227
-
222
function validateEventValue(eventValue: any): void {
223
if (typeof eventValue === 'object' && eventValue !== null) {
224
const {target, type, timeStamp} = eventValue;
@@ -290,24 +284,22 @@ function processTimers(
284
delay: number,
285
): void {
286
const timersArr = Array.from(timers.values());
293
- currentEventQueuePriority = ContinuousEvent;
287
try {
295
- for (let i = 0; i < timersArr.length; i++) {
296
- const {instance, func, id, timeStamp} = timersArr[i];
297
- currentInstance = instance;
298
- currentEventQueue = [];
299
- currentTimeStamp = timeStamp + delay;
300
- try {
301
- func();
302
- } finally {
303
- activeTimeouts.delete(id);
288
+ batchedEventUpdates(() => {
289
+ for (let i = 0; i < timersArr.length; i++) {
290
+ const {instance, func, id, timeStamp} = timersArr[i];
291
+ currentInstance = instance;
292
+ currentTimeStamp = timeStamp + delay;
293
+ try {
294
+ func();
295
+ } finally {
296
+ activeTimeouts.delete(id);
297
+ }
298
}
305
- }
306
- processEventQueue();
299
+ });
300
} finally {
301
currentTimers = null;
302
currentInstance = null;
310
- currentEventQueue = null;
303
currentTimeStamp = 0;
304
}
305
}
@@ -327,68 +319,12 @@ function createFabricResponderEvent(
319
320
function validateResponderContext(): void {
321
invariant(
330
- currentEventQueue && currentInstance,
322
+ currentInstance,
323
'An event responder context was used outside of an event cycle. ' +
324
'Use context.setTimeout() to use asynchronous responder context outside of event cycle .',
325
);
326
}
327
336
-// TODO this function is almost an exact copy of the DOM version, we should
337
-// somehow share the logic
338
-function processEventQueue(): void {
339
- const eventQueue = ((currentEventQueue: any): EventQueue);
340
- if (eventQueue.length === 0) {
341
- return;
342
- }
343
- switch (currentEventQueuePriority) {
344
- case DiscreteEvent: {
345
- flushDiscreteUpdatesIfNeeded(currentTimeStamp);
346
- discreteUpdates(() => {
347
- batchedEventUpdates(processEvents, eventQueue);
348
- });
349
- break;
350
- }
351
- case UserBlockingEvent: {
352
- if (enableUserBlockingEvents) {
353
- runWithPriority(
354
- UserBlockingPriority,
355
- batchedEventUpdates.bind(null, processEvents, eventQueue),
356
- );
357
- } else {
358
- batchedEventUpdates(processEvents, eventQueue);
359
- }
360
- break;
361
- }
362
- case ContinuousEvent: {
363
- batchedEventUpdates(processEvents, eventQueue);
364
- break;
365
- }
366
- }
367
-}
368
-
369
-// TODO this function is almost an exact copy of the DOM version, we should
370
-// somehow share the logic
371
-function releaseOwnershipForEventResponderInstance(
372
- eventResponderInstance: ReactNativeEventResponderInstance,
373
-): boolean {
374
- if (globalOwner === eventResponderInstance) {
375
- globalOwner = null;
376
- triggerOwnershipListeners();
377
- return true;
378
- }
379
- return false;
380
-}
381
-
382
-// TODO this function is almost an exact copy of the DOM version, we should
383
-// somehow share the logic
384
-function processEvents(eventQueue: EventQueue): void {
385
- for (let i = 0, length = eventQueue.length; i < length; i++) {
386
- const {value, listener} = eventQueue[i];
387
- const type = typeof value === 'object' && value !== null ? value.type : '';
388
- invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, value);
389
- }
390
-}
391
-
328
// TODO this function is almost an exact copy of the DOM version, we should
329
// somehow share the logic
330
function responderEventTypesContainType(
@@ -415,12 +351,6 @@ function validateResponderTargetEventTypes(
351
return false;
352
}
353
418
-function validateOwnership(
419
- responderInstance: ReactNativeEventResponderInstance,
420
-): boolean {
421
- return globalOwner === null || globalOwner === responderInstance;
422
-}
423
-
354
// TODO this function is almost an exact copy of the DOM version, we should
355
// somehow share the logic
356
function traverseAndHandleEventResponderInstances(
@@ -449,20 +379,17 @@ function traverseAndHandleEventResponderInstances(
379
const responderInstances = Array.from(respondersMap.values());
380
for (let i = 0, length = responderInstances.length; i < length; i++) {
381
const responderInstance = responderInstances[i];
452
-
453
- if (validateOwnership(responderInstance)) {
454
- const {props, responder, state, target} = responderInstance;
455
- if (
456
- !visitedResponders.has(responder) &&
457
- validateResponderTargetEventTypes(eventType, responder)
458
- ) {
459
- const onEvent = responder.onEvent;
460
- visitedResponders.add(responder);
461
- if (onEvent !== null) {
462
- currentInstance = responderInstance;
463
- responderEvent.responderTarget = ((target: any): ReactNativeEventTarget);
464
- onEvent(responderEvent, eventResponderContext, props, state);
465
- }
382
+ const {props, responder, state, target} = responderInstance;
383
+ if (
384
+ !visitedResponders.has(responder) &&
385
+ validateResponderTargetEventTypes(eventType, responder)
386
+ ) {
387
+ const onEvent = responder.onEvent;
388
+ visitedResponders.add(responder);
389
+ if (onEvent !== null) {
390
+ currentInstance = responderInstance;
391
+ responderEvent.responderTarget = ((target: any): ReactNativeEventTarget);
392
+ onEvent(responderEvent, eventResponderContext, props, state);
393
}
394
}
395
}
@@ -479,9 +406,6 @@ function traverseAndHandleEventResponderInstances(
406
407
for (let i = 0; i < responderInstances.length; i++) {
408
const responderInstance = responderInstances[i];
482
- if (!validateOwnership(responderInstance)) {
483
- continue;
484
- }
409
const {props, responder, state, target} = responderInstance;
410
const onRootEvent = responder.onRootEvent;
411
if (onRootEvent !== null) {
@@ -500,57 +424,24 @@ export function dispatchEventForResponderEventSystem(
424
targetFiber: null | Fiber,
425
nativeEvent: ReactFaricEvent,
426
): void {
503
- const previousEventQueue = currentEventQueue;
427
const previousInstance = currentInstance;
428
const previousTimers = currentTimers;
429
const previousTimeStamp = currentTimeStamp;
507
- const previousEventQueuePriority = currentEventQueuePriority;
430
currentTimers = null;
509
- currentEventQueue = [];
510
- currentEventQueuePriority = ContinuousEvent;
431
// We might want to control timeStamp another way here
432
currentTimeStamp = Date.now();
433
try {
514
- traverseAndHandleEventResponderInstances(
515
- topLevelType,
516
- targetFiber,
517
- nativeEvent,
518
- );
519
- processEventQueue();
434
+ batchedEventUpdates(() => {
435
+ traverseAndHandleEventResponderInstances(
436
+ topLevelType,
437
+ targetFiber,
438
+ nativeEvent,
439
+ );
440
+ });
441
} finally {
442
currentTimers = previousTimers;
443
currentInstance = previousInstance;
523
- currentEventQueue = previousEventQueue;
444
currentTimeStamp = previousTimeStamp;
525
- currentEventQueuePriority = previousEventQueuePriority;
526
- }
527
-}
528
-
529
-// TODO this function is almost an exact copy of the DOM version, we should
530
-// somehow share the logic
531
-function triggerOwnershipListeners(): void {
532
- const listeningInstances = Array.from(ownershipChangeListeners);
533
- const previousInstance = currentInstance;
534
- const previousEventQueuePriority = currentEventQueuePriority;
535
- const previousEventQueue = currentEventQueue;
536
- try {
537
- for (let i = 0; i < listeningInstances.length; i++) {
538
- const instance = listeningInstances[i];
539
- const {props, responder, state} = instance;
540
- currentInstance = instance;
541
- currentEventQueuePriority = ContinuousEvent;
542
- currentEventQueue = [];
543
- const onOwnershipChange = ((responder: any): ReactNativeEventResponder)
544
- .onOwnershipChange;
545
- if (onOwnershipChange !== null) {
546
- onOwnershipChange(eventResponderContext, props, state);
547
- }
548
- }
549
- processEventQueue();
550
- } finally {
551
- currentInstance = previousInstance;
552
- currentEventQueue = previousEventQueue;
553
- currentEventQueuePriority = previousEventQueuePriority;
445
}
446
}
447
@@ -562,19 +453,14 @@ export function mountEventResponder(
453
props: Object,
454
state: Object,
455
) {
565
- if (responder.onOwnershipChange !== null) {
566
- ownershipChangeListeners.add(responderInstance);
567
- }
456
const onMount = responder.onMount;
457
if (onMount !== null) {
570
- currentEventQueuePriority = ContinuousEvent;
458
currentInstance = responderInstance;
572
- currentEventQueue = [];
459
try {
574
- onMount(eventResponderContext, props, state);
575
- processEventQueue();
460
+ batchedEventUpdates(() => {
461
+ onMount(eventResponderContext, props, state);
462
+ });
463
} finally {
577
- currentEventQueue = null;
464
currentInstance = null;
465
currentTimers = null;
466
}
@@ -590,22 +476,16 @@ export function unmountEventResponder(
476
const onUnmount = responder.onUnmount;
477
if (onUnmount !== null) {
478
let {props, state} = responderInstance;
593
- currentEventQueue = [];
594
- currentEventQueuePriority = ContinuousEvent;
479
currentInstance = responderInstance;
480
try {
597
- onUnmount(eventResponderContext, props, state);
598
- processEventQueue();
481
+ batchedEventUpdates(() => {
482
+ onUnmount(eventResponderContext, props, state);
483
+ });
484
} finally {
600
- currentEventQueue = null;
485
currentInstance = null;
486
currentTimers = null;
487
}
488
}
605
- releaseOwnershipForEventResponderInstance(responderInstance);
606
- if (responder.onOwnershipChange !== null) {
607
- ownershipChangeListeners.delete(responderInstance);
608
- }
489
const rootEventTypesSet = responderInstance.rootEventTypes;
490
if (rootEventTypesSet !== null) {
491
const rootEventTypes = Array.from(rootEventTypesSet);
packages/shared/ReactDOMTypes.js
-3
@@ -64,9 +64,6 @@ export type ReactDOMResponderContext = {
64
isTargetWithinResponderScope: (Element | Document) => boolean,
65
addRootEventTypes: (rootEventTypes: Array<string>) => void,
66
removeRootEventTypes: (rootEventTypes: Array<string>) => void,
67
- hasOwnership: () => boolean,
68
- requestGlobalOwnership: () => boolean,
69
- releaseOwnership: () => boolean,
67
setTimeout: (func: () => void, timeout: number) => number,
68
clearTimeout: (timerId: number) => void,
69
getFocusableElementsInScope(deep: boolean): Array<HTMLElement>,
packages/shared/ReactTypes.js
-3
@@ -107,9 +107,6 @@ export type ReactEventResponder<E, C> = {
107
| ((event: E, context: C, props: Object, state: Object) => void),
108
onMount: null | ((context: C, props: Object, state: Object) => void),
109
onUnmount: null | ((context: C, props: Object, state: Object) => void),
110
- onOwnershipChange:
111
- | null
112
- | ((context: C, props: Object, state: Object) => void),
110
};
111
112
export type EventPriority = 0 | 1 | 2;
packages/shared/createEventResponder.js
-2
@@ -19,7 +19,6 @@ export default function createEventResponder<E, C>(
19
onEvent,
20
onMount,
21
onUnmount,
22
- onOwnershipChange,
22
onRootEvent,
23
rootEventTypes,
24
targetEventTypes,
@@ -30,7 +29,6 @@ export default function createEventResponder<E, C>(
29
getInitialState: getInitialState || null,
30
onEvent: onEvent || null,
31
onMount: onMount || null,
33
- onOwnershipChange: onOwnershipChange || null,
32
onRootEvent: onRootEvent || null,
33
onUnmount: onUnmount || null,
34
rootEventTypes: rootEventTypes || null,