Skip to content

Commit

Permalink
Refactor EventComponent logic + add onOwnershipChange callback (#15354)
Browse files Browse the repository at this point in the history
  • Loading branch information
trueadm authored Apr 9, 2019
1 parent 183d1f4 commit aece811
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 117 deletions.
16 changes: 10 additions & 6 deletions packages/react-art/src/ReactARTHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as Scheduler from 'scheduler';
import invariant from 'shared/invariant';

import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
import type {ReactEventComponentInstance} from 'shared/ReactTypes';

// Intentionally not named imports because Rollup would
// use dynamic dispatch for CommonJS interop named imports.
Expand Down Expand Up @@ -439,17 +440,20 @@ export function unhideTextInstance(textInstance, text): void {
// Noop
}

export function handleEventComponent(
eventResponder: ReactEventResponder,
rootContainerInstance: Container,
export function mountEventComponent(
eventComponentInstance: ReactEventComponentInstance,
) {
throw new Error('Not yet implemented.');
}

export function updateEventComponent(
eventComponentInstance: ReactEventComponentInstance,
) {
throw new Error('Not yet implemented.');
}

export function unmountEventComponent(
eventResponder: ReactEventResponder,
rootContainerInstance: Container,
internalInstanceHandle: Object,
eventComponentInstance: ReactEventComponentInstance,
): void {
throw new Error('Not yet implemented.');
}
Expand Down
30 changes: 20 additions & 10 deletions packages/react-dom/src/client/ReactDOMHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ import {
import dangerousStyleValue from '../shared/dangerousStyleValue';

import type {DOMContainer} from './ReactDOM';
import type {ReactEventResponder} from 'shared/ReactTypes';
import {unmountEventResponder} from '../events/DOMEventResponderSystem';
import type {ReactEventComponentInstance} from 'shared/ReactTypes';
import {
mountEventResponder,
unmountEventResponder,
} from '../events/DOMEventResponderSystem';
import {REACT_EVENT_TARGET_TOUCH_HIT} from 'shared/ReactSymbols';
import {canUseDOM} from 'shared/ExecutionEnvironment';

Expand Down Expand Up @@ -888,27 +891,34 @@ export function didNotFindHydratableSuspenseInstance(
}
}

export function handleEventComponent(
eventResponder: ReactEventResponder,
rootContainerInstance: Container,
export function mountEventComponent(
eventComponentInstance: ReactEventComponentInstance,
): void {
if (enableEventAPI) {
mountEventResponder(eventComponentInstance);
updateEventComponent(eventComponentInstance);
}
}

export function updateEventComponent(
eventComponentInstance: ReactEventComponentInstance,
): void {
if (enableEventAPI) {
const rootContainerInstance = ((eventComponentInstance.rootInstance: any): Container);
const rootElement = rootContainerInstance.ownerDocument;
listenToEventResponderEventTypes(
eventResponder.targetEventTypes,
eventComponentInstance.responder.targetEventTypes,
rootElement,
);
}
}

export function unmountEventComponent(
eventResponder: ReactEventResponder,
rootContainerInstance: Container,
internalInstanceHandle: Object,
eventComponentInstance: ReactEventComponentInstance,
): void {
if (enableEventAPI) {
// TODO stop listening to targetEventTypes
unmountEventResponder(eventResponder, internalInstanceHandle);
unmountEventResponder(eventComponentInstance);
}
}

Expand Down
121 changes: 76 additions & 45 deletions packages/react-dom/src/events/DOMEventResponderSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import {
EventTarget as EventTargetWorkTag,
} from 'shared/ReactWorkTags';
import type {
ReactEventResponder,
ReactEventResponderEventType,
ReactEventComponentInstance,
} from 'shared/ReactTypes';
import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes';
import {batchedUpdates, interactiveUpdates} from 'events/ReactGenericBatching';
Expand Down Expand Up @@ -55,7 +55,7 @@ type PartialEventObject = {
};

let currentOwner = null;
let currentFiber: Fiber;
let currentInstance: ReactEventComponentInstance;
let currentEventQueue: EventQueue;

const eventResponderContext: ResponderContext = {
Expand Down Expand Up @@ -140,12 +140,10 @@ const eventResponderContext: ResponderContext = {
return false;
},
isTargetWithinEventComponent(target: Element | Document): boolean {
const eventFiber = currentFiber;

if (target != null) {
let fiber = getClosestInstanceFromNode(target);
while (fiber !== null) {
if (fiber === eventFiber || fiber === eventFiber.alternate) {
if (fiber.stateNode === currentInstance) {
return true;
}
fiber = fiber.return;
Expand Down Expand Up @@ -174,78 +172,78 @@ const eventResponderContext: ResponderContext = {
rootEventTypes: Array<ReactEventResponderEventType>,
): void {
listenToResponderEventTypesImpl(rootEventTypes, doc);
const eventComponent = currentFiber;
for (let i = 0; i < rootEventTypes.length; i++) {
const rootEventType = rootEventTypes[i];
const topLevelEventType =
typeof rootEventType === 'string' ? rootEventType : rootEventType.name;
let rootEventComponents = rootEventTypesToEventComponents.get(
let rootEventComponentInstances = rootEventTypesToEventComponentInstances.get(
topLevelEventType,
);
if (rootEventComponents === undefined) {
rootEventComponents = new Set();
rootEventTypesToEventComponents.set(
if (rootEventComponentInstances === undefined) {
rootEventComponentInstances = new Set();
rootEventTypesToEventComponentInstances.set(
topLevelEventType,
rootEventComponents,
rootEventComponentInstances,
);
}
rootEventComponents.add(eventComponent);
rootEventComponentInstances.add(currentInstance);
}
},
removeRootEventTypes(
rootEventTypes: Array<ReactEventResponderEventType>,
): void {
const eventComponent = currentFiber;
for (let i = 0; i < rootEventTypes.length; i++) {
const rootEventType = rootEventTypes[i];
const topLevelEventType =
typeof rootEventType === 'string' ? rootEventType : rootEventType.name;
let rootEventComponents = rootEventTypesToEventComponents.get(
let rootEventComponents = rootEventTypesToEventComponentInstances.get(
topLevelEventType,
);
if (rootEventComponents !== undefined) {
rootEventComponents.delete(eventComponent);
rootEventComponents.delete(currentInstance);
}
}
},
hasOwnership(): boolean {
return currentOwner === currentFiber;
return currentOwner === currentInstance;
},
requestOwnership(): boolean {
if (currentOwner !== null) {
return false;
}
currentOwner = currentFiber;
currentOwner = currentInstance;
triggerOwnershipListeners();
return true;
},
releaseOwnership(): boolean {
if (currentOwner !== currentFiber) {
if (currentOwner !== currentInstance) {
return false;
}
currentOwner = null;
triggerOwnershipListeners();
return false;
},
setTimeout(func: () => void, delay): TimeoutID {
const contextFiber = currentFiber;
const contextInstance = currentInstance;
return setTimeout(() => {
const previousEventQueue = currentEventQueue;
const previousFiber = currentFiber;
const previousInstance = currentInstance;
currentEventQueue = createEventQueue();
currentFiber = contextFiber;
currentInstance = contextInstance;
try {
func();
batchedUpdates(processEventQueue, currentEventQueue);
} finally {
currentFiber = previousFiber;
currentInstance = previousInstance;
currentEventQueue = previousEventQueue;
}
}, delay);
},
};

const rootEventTypesToEventComponents: Map<
const rootEventTypesToEventComponentInstances: Map<
DOMTopLevelEventType | string,
Set<Fiber>,
Set<ReactEventComponentInstance>,
> = new Map();
const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
const eventsWithStopPropagation:
Expand All @@ -255,6 +253,7 @@ const targetEventTypeCached: Map<
Array<ReactEventResponderEventType>,
Set<DOMTopLevelEventType>,
> = new Map();
const ownershipChangeListeners: Set<ReactEventComponentInstance> = new Set();

function createResponderEvent(
topLevelType: string,
Expand Down Expand Up @@ -343,25 +342,24 @@ function getTargetEventTypes(

function handleTopLevelType(
topLevelType: DOMTopLevelEventType,
fiber: Fiber,
responderEvent: ResponderEvent,
eventComponentInstance: ReactEventComponentInstance,
isRootLevelEvent: boolean,
): void {
const responder: ReactEventResponder = fiber.type.responder;
let {props, responder, state} = eventComponentInstance;
if (!isRootLevelEvent) {
// Validate the target event type exists on the responder
const targetEventTypes = getTargetEventTypes(responder.targetEventTypes);
if (!targetEventTypes.has(topLevelType)) {
return;
}
}
let {props, state} = fiber.stateNode;
const previousFiber = currentFiber;
currentFiber = fiber;
const previousInstance = currentInstance;
currentInstance = eventComponentInstance;
try {
responder.onEvent(responderEvent, eventResponderContext, props, state);
} finally {
currentFiber = previousFiber;
currentInstance = previousInstance;
}
}

Expand All @@ -384,23 +382,29 @@ export function runResponderEventsInBatch(
// Traverse up the fiber tree till we find event component fibers.
while (node !== null) {
if (node.tag === EventComponent) {
handleTopLevelType(topLevelType, node, responderEvent, false);
const eventComponentInstance = node.stateNode;
handleTopLevelType(
topLevelType,
responderEvent,
eventComponentInstance,
false,
);
}
node = node.return;
}
// Handle root level events
const rootEventComponents = rootEventTypesToEventComponents.get(
const rootEventInstances = rootEventTypesToEventComponentInstances.get(
topLevelType,
);
if (rootEventComponents !== undefined) {
const rootEventComponentFibers = Array.from(rootEventComponents);
if (rootEventInstances !== undefined) {
const rootEventComponentInstances = Array.from(rootEventInstances);

for (let i = 0; i < rootEventComponentFibers.length; i++) {
const rootEventComponentFiber = rootEventComponentFibers[i];
for (let i = 0; i < rootEventComponentInstances.length; i++) {
const rootEventComponentInstance = rootEventComponentInstances[i];
handleTopLevelType(
topLevelType,
rootEventComponentFiber,
responderEvent,
rootEventComponentInstance,
true,
);
}
Expand All @@ -409,26 +413,53 @@ export function runResponderEventsInBatch(
}
}

function triggerOwnershipListeners(): void {
const listeningInstances = Array.from(ownershipChangeListeners);
const previousInstance = currentInstance;
for (let i = 0; i < listeningInstances.length; i++) {
const instance = listeningInstances[i];
const {props, responder, state} = instance;
currentInstance = instance;
try {
responder.onOwnershipChange(eventResponderContext, props, state);
} finally {
currentInstance = previousInstance;
}
}
}

export function mountEventResponder(
eventComponentInstance: ReactEventComponentInstance,
) {
const responder = eventComponentInstance.responder;
if (responder.onOwnershipChange !== undefined) {
ownershipChangeListeners.add(eventComponentInstance);
}
}

export function unmountEventResponder(
responder: ReactEventResponder,
fiber: Fiber,
eventComponentInstance: ReactEventComponentInstance,
): void {
const responder = eventComponentInstance.responder;
const onUnmount = responder.onUnmount;
if (onUnmount !== undefined) {
let {props, state} = fiber.stateNode;
let {props, state} = eventComponentInstance;
const previousEventQueue = currentEventQueue;
const previousFiber = currentFiber;
const previousInstance = currentInstance;
currentEventQueue = createEventQueue();
currentFiber = fiber;
currentInstance = eventComponentInstance;
try {
onUnmount(eventResponderContext, props, state);
} finally {
currentEventQueue = previousEventQueue;
currentFiber = previousFiber;
currentInstance = previousInstance;
}
}
if (currentOwner === fiber) {
// TODO fire owner changed callback
if (currentOwner === eventComponentInstance) {
currentOwner = null;
triggerOwnershipListeners();
}
if (responder.onOwnershipChange !== undefined) {
ownershipChangeListeners.delete(eventComponentInstance);
}
}
Loading

0 comments on commit aece811

Please sign in to comment.