Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor event object creation for the experimental event API #15295

Merged
merged 4 commits into from
Apr 2, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 16 additions & 13 deletions packages/events/EventTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,39 @@
* @flow
*/

import SyntheticEvent from 'events/SyntheticEvent';
import type {AnyNativeEvent} from 'events/PluginModuleType';
import type {ReactEventResponderEventType} from 'shared/ReactTypes';

type ParitalEventObject = {
trueadm marked this conversation as resolved.
Show resolved Hide resolved
listener: ($Shape<ParitalEventObject>) => void,
target: Element | Document,
type: string,
};

export type EventResponderContext = {
event: AnyNativeEvent,
eventTarget: EventTarget,
eventTarget: Element | Document,
eventType: string,
isPassive: () => boolean,
isPassiveSupported: () => boolean,
dispatchEvent: (
name: string,
listener: (e: SyntheticEvent) => void | null,
pressTarget: EventTarget | null,
dispatchEvent: <E>(
eventObject: E,
discrete: boolean,
extraProperties?: Object,
capture: boolean,
) => void,
isTargetWithinElement: (
childTarget: EventTarget,
parentTarget: EventTarget,
childTarget: Element | Document,
parentTarget: Element | Document,
) => boolean,
isTargetOwned: EventTarget => boolean,
isTargetWithinEventComponent: EventTarget => boolean,
isTargetOwned: (Element | Document) => boolean,
isTargetWithinEventComponent: (Element | Document) => boolean,
isPositionWithinTouchHitTarget: (x: number, y: number) => boolean,
addRootEventTypes: (
rootEventTypes: Array<ReactEventResponderEventType>,
) => void,
removeRootEventTypes: (
rootEventTypes: Array<ReactEventResponderEventType>,
) => void,
requestOwnership: (target: EventTarget | null) => boolean,
releaseOwnership: (target: EventTarget | null) => boolean,
requestOwnership: (target: Element | Document | null) => boolean,
releaseOwnership: (target: Element | Document | null) => boolean,
};
173 changes: 116 additions & 57 deletions packages/react-dom/src/events/DOMEventResponderSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@ import type {
ReactEventResponderEventType,
} from 'shared/ReactTypes';
import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes';
import SyntheticEvent from 'events/SyntheticEvent';
import {runEventsInBatch} from 'events/EventBatching';
import {interactiveUpdates} from 'events/ReactGenericBatching';
import {executeDispatch} from 'events/EventPluginUtils';
import {batchedUpdates, interactiveUpdates} from 'events/ReactGenericBatching';
import type {Fiber} from 'react-reconciler/src/ReactFiber';

import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';

import {enableEventAPI} from 'shared/ReactFeatureFlags';
import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
import warning from 'shared/warning';

let listenToResponderEventTypesImpl;

Expand All @@ -46,11 +44,70 @@ const targetEventTypeCached: Map<
> = new Map();
const targetOwnership: Map<EventTarget, Fiber> = new Map();

type EventListener = (event: SyntheticEvent) => void;
type ParitalEventObject = {
trueadm marked this conversation as resolved.
Show resolved Hide resolved
listener: ($Shape<ParitalEventObject>) => void,
target: Element | Document,
type: string,
};
type EventQueue = {
bubble: Array<$Shape<ParitalEventObject>>,
capture: Array<$Shape<ParitalEventObject>>,
};
type BatchedEventQueue = {
discrete: null | EventQueue,
phase: EventQueuePhase,
nonDiscrete: null | EventQueue,
};
type EventQueuePhase = 0 | 1;

const DURING_EVENT_PHASE = 0;
const AFTER_EVENT_PHASE = 1;

function createEventQueue(): EventQueue {
return {
bubble: [],
capture: [],
};
}

function createBatchedEventQueue(phase: EventQueuePhase): BatchedEventQueue {
return {
discrete: null,
phase,
nonDiscrete: null,
};
}

function processEvent(event: $Shape<ParitalEventObject>): void {
const type = event.type;
const listener = event.listener;
invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
}

function processEventQueue(eventQueue: EventQueue): void {
const {bubble, capture} = eventQueue;
let i, length;

function copyEventProperties(eventData, syntheticEvent) {
for (let propName in eventData) {
syntheticEvent[propName] = eventData[propName];
// TODO support stopPropagation via an alternative approach
// Process events in two phases
for (i = capture.length; i-- > 0; ) {
processEvent(capture[i]);
}
for (i = 0, length = bubble.length; i < length; ++i) {
processEvent(bubble[i]);
}
}

function processBatchedEventQueue(batchedEventQueue: BatchedEventQueue): void {
const {discrete, nonDiscrete} = batchedEventQueue;

if (discrete !== null) {
interactiveUpdates(() => {
processEventQueue(discrete);
});
}
if (nonDiscrete !== null) {
processEventQueue(nonDiscrete);
}
}

Expand All @@ -70,6 +127,7 @@ function DOMEventResponderContext(
this._discreteEvents = null;
this._nonDiscreteEvents = null;
this._isBatching = true;
this._batchedEventQueue = createBatchedEventQueue(DURING_EVENT_PHASE);
}

DOMEventResponderContext.prototype.isPassive = function(): boolean {
Expand All @@ -81,49 +139,58 @@ DOMEventResponderContext.prototype.isPassiveSupported = function(): boolean {
};

DOMEventResponderContext.prototype.dispatchEvent = function(
eventName: string,
eventListener: EventListener,
eventTarget: AnyNativeEvent,
possibleEventObject: Object,
discrete: boolean,
extraProperties?: Object,
capture: boolean,
): void {
const eventTargetFiber = getClosestInstanceFromNode(eventTarget);
const syntheticEvent = SyntheticEvent.getPooled(
null,
eventTargetFiber,
this.event,
eventTarget,
);
if (extraProperties !== undefined) {
copyEventProperties(extraProperties, syntheticEvent);
const batchedEventQueue = this._batchedEventQueue;
const {listener, target, type} = possibleEventObject;

if (listener == null || target == null || type == null) {
throw new Error(
'context.dispatchEvent: "listener", "target" and "type" fields on event object are required.',
);
}
syntheticEvent.type = eventName;
syntheticEvent._dispatchInstances = [eventTargetFiber];
syntheticEvent._dispatchListeners = [eventListener];

if (this._isBatching) {
let events;
if (discrete) {
events = this._discreteEvents;
if (events === null) {
events = this._discreteEvents = [];
}
} else {
events = this._nonDiscreteEvents;
if (events === null) {
events = this._nonDiscreteEvents = [];
}
if (__DEV__) {
possibleEventObject.preventDefault = () => {
// Update this warning when we have a story around dealing with preventDefault
warning(
false,
'preventDefault() is no longer available on event objects created from event responder modules.',
);
};
possibleEventObject.stopPropagation = () => {
// Update this warning when we have a story around dealing with stopPropgation
warning(
false,
'stopPropagation() is no longer available on event objects created from event responder modules.',
);
};
}
const eventObject = ((possibleEventObject: any): $Shape<ParitalEventObject>);
let eventQueue;
if (discrete) {
eventQueue = batchedEventQueue.discrete;
if (eventQueue === null) {
eventQueue = batchedEventQueue.discrete = createEventQueue();
}
events.push(syntheticEvent);
} else {
if (discrete) {
interactiveUpdates(() => {
executeDispatch(syntheticEvent, eventListener, eventTargetFiber);
});
} else {
executeDispatch(syntheticEvent, eventListener, eventTargetFiber);
eventQueue = batchedEventQueue.nonDiscrete;
if (eventQueue === null) {
eventQueue = batchedEventQueue.nonDiscrete = createEventQueue();
}
}
let eventQueueArr;
if (capture) {
eventQueueArr = eventQueue.capture;
} else {
eventQueueArr = eventQueue.bubble;
}
eventQueueArr.push(eventObject);

if (batchedEventQueue.phase === AFTER_EVENT_PHASE) {
batchedUpdates(processBatchedEventQueue, batchedEventQueue);
}
};

DOMEventResponderContext.prototype.isTargetWithinEventComponent = function(
Expand Down Expand Up @@ -318,17 +385,9 @@ export function runResponderEventsInBatch(
);
}
}
// Run batched events
const discreteEvents = context._discreteEvents;
if (discreteEvents !== null) {
interactiveUpdates(() => {
runEventsInBatch(discreteEvents);
});
}
const nonDiscreteEvents = context._nonDiscreteEvents;
if (nonDiscreteEvents !== null) {
runEventsInBatch(nonDiscreteEvents);
}
context._isBatching = false;
processBatchedEventQueue(context._batchedEventQueue);
// In order to capture and process async events from responder modules
// we create a new event queue.
context._batchedEventQueue = createBatchedEventQueue(AFTER_EVENT_PHASE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,12 @@ describe('DOMEventResponderSystem', () => {
['click'],
(context, props) => {
if (props.onMagicClick) {
context.dispatchEvent(
'magicclick',
props.onMagicClick,
context.eventTarget,
false,
);
const event = {
listener: props.onMagicClick,
target: context.eventTarget,
type: 'magicclick',
};
context.dispatchEvent(event, true, false);
}
},
);
Expand Down
53 changes: 41 additions & 12 deletions packages/react-events/src/Drag.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const targetEventTypes = ['pointerdown', 'pointercancel'];
const rootEventTypes = ['pointerup', {name: 'pointermove', passive: false}];

type DragState = {
dragTarget: null | EventTarget,
dragTarget: null | Element | Document,
isPointerDown: boolean,
isDragging: boolean,
startX: number,
Expand All @@ -33,18 +33,45 @@ if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
});
}

type EventData = {
diffX: number,
diffY: number,
};
type DragEventType = 'dragend' | 'dragchange' | 'dragmove';

type DragEvent = {|
listener: DragEvent => void,
target: Element | Document,
type: DragEventType,
diffX?: number,
diffY?: number,
|};

function createDragEvent(
type: DragEventType,
target: Element | Document,
listener: DragEvent => void,
eventData?: EventData,
): DragEvent {
return {
listener,
target,
type,
...eventData,
};
}

function dispatchDragEvent(
context: EventResponderContext,
name: string,
listener: (e: Object) => void,
name: DragEventType,
listener: DragEvent => void,
state: DragState,
discrete: boolean,
eventData?: {
diffX: number,
diffY: number,
},
eventData?: EventData,
): void {
context.dispatchEvent(name, listener, state.dragTarget, discrete, eventData);
const target = ((state.dragTarget: any): Element | Document);
const syntheticEvent = createDragEvent(name, target, listener, eventData);
context.dispatchEvent(syntheticEvent, discrete, false);
}

const DragResponder = {
Expand Down Expand Up @@ -112,10 +139,11 @@ const DragResponder = {
const dragChangeEventListener = () => {
props.onDragChange(true);
};
context.dispatchEvent(
dispatchDragEvent(
context,
'dragchange',
dragChangeEventListener,
state.dragTarget,
state,
true,
);
}
Expand Down Expand Up @@ -160,10 +188,11 @@ const DragResponder = {
const dragChangeEventListener = () => {
props.onDragChange(false);
};
context.dispatchEvent(
dispatchDragEvent(
context,
'dragchange',
dragChangeEventListener,
state.dragTarget,
state,
true,
);
}
Expand Down
Loading