@samitouri / QOS-React-2 / commits / dce430ad92

[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,