Skip to content

Commit

Permalink
Add part of the event responder system for experimental event API (#1…
Browse files Browse the repository at this point in the history
…5179)

* Add part of the event responder system
  • Loading branch information
trueadm committed Mar 26, 2019
1 parent d03ac4b commit 80f8b0d
Show file tree
Hide file tree
Showing 15 changed files with 593 additions and 107 deletions.
66 changes: 66 additions & 0 deletions packages/events/EventBatching.js
@@ -0,0 +1,66 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @flow
*/

import invariant from 'shared/invariant';
import {rethrowCaughtError} from 'shared/ReactErrorUtils';

import type {ReactSyntheticEvent} from './ReactSyntheticEventType';
import accumulateInto from './accumulateInto';
import forEachAccumulated from './forEachAccumulated';
import {executeDispatchesInOrder} from './EventPluginUtils';

/**
* Internal queue of events that have accumulated their dispatches and are
* waiting to have their dispatches executed.
*/
let eventQueue: ?(Array<ReactSyntheticEvent> | ReactSyntheticEvent) = null;

/**
* Dispatches an event and releases it back into the pool, unless persistent.
*
* @param {?object} event Synthetic event to be dispatched.
* @private
*/
const executeDispatchesAndRelease = function(event: ReactSyntheticEvent) {
if (event) {
executeDispatchesInOrder(event);

if (!event.isPersistent()) {
event.constructor.release(event);
}
}
};
const executeDispatchesAndReleaseTopLevel = function(e) {
return executeDispatchesAndRelease(e);
};

export function runEventsInBatch(
events: Array<ReactSyntheticEvent> | ReactSyntheticEvent | null,
) {
if (events !== null) {
eventQueue = accumulateInto(eventQueue, events);
}

// Set `eventQueue` to null before processing it so that we can tell if more
// events get enqueued while processing.
const processingEventQueue = eventQueue;
eventQueue = null;

if (!processingEventQueue) {
return;
}

forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
invariant(
!eventQueue,
'processEventQueue(): Additional events were enqueued while processing ' +
'an event queue. Support for this has not yet been implemented.',
);
// This would be a good time to rethrow if any of the event handlers threw.
rethrowCaughtError();
}
65 changes: 5 additions & 60 deletions packages/events/EventPluginHub.js
Expand Up @@ -6,52 +6,23 @@
* @flow
*/

import {rethrowCaughtError} from 'shared/ReactErrorUtils';
import invariant from 'shared/invariant';

import {
injectEventPluginOrder,
injectEventPluginsByName,
plugins,
} from './EventPluginRegistry';
import {
executeDispatchesInOrder,
getFiberCurrentPropsFromNode,
} from './EventPluginUtils';
import {getFiberCurrentPropsFromNode} from './EventPluginUtils';
import accumulateInto from './accumulateInto';
import forEachAccumulated from './forEachAccumulated';
import {runEventsInBatch} from './EventBatching';

import type {PluginModule} from './PluginModuleType';
import type {ReactSyntheticEvent} from './ReactSyntheticEventType';
import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {AnyNativeEvent} from './PluginModuleType';
import type {TopLevelType} from './TopLevelEventTypes';

/**
* Internal queue of events that have accumulated their dispatches and are
* waiting to have their dispatches executed.
*/
let eventQueue: ?(Array<ReactSyntheticEvent> | ReactSyntheticEvent) = null;

/**
* Dispatches an event and releases it back into the pool, unless persistent.
*
* @param {?object} event Synthetic event to be dispatched.
* @private
*/
const executeDispatchesAndRelease = function(event: ReactSyntheticEvent) {
if (event) {
executeDispatchesInOrder(event);

if (!event.isPersistent()) {
event.constructor.release(event);
}
}
};
const executeDispatchesAndReleaseTopLevel = function(e) {
return executeDispatchesAndRelease(e);
};

function isInteractive(tag) {
return (
tag === 'button' ||
Expand Down Expand Up @@ -158,7 +129,7 @@ export function getListener(inst: Fiber, registrationName: string) {
* @return {*} An accumulation of synthetic events.
* @internal
*/
function extractEvents(
function extractPluginEvents(
topLevelType: TopLevelType,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
Expand All @@ -183,39 +154,13 @@ function extractEvents(
return events;
}

export function runEventsInBatch(
events: Array<ReactSyntheticEvent> | ReactSyntheticEvent | null,
) {
if (events !== null) {
eventQueue = accumulateInto(eventQueue, events);
}

// Set `eventQueue` to null before processing it so that we can tell if more
// events get enqueued while processing.
const processingEventQueue = eventQueue;
eventQueue = null;

if (!processingEventQueue) {
return;
}

forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
invariant(
!eventQueue,
'processEventQueue(): Additional events were enqueued while processing ' +
'an event queue. Support for this has not yet been implemented.',
);
// This would be a good time to rethrow if any of the event handlers threw.
rethrowCaughtError();
}

export function runExtractedEventsInBatch(
export function runExtractedPluginEventsInBatch(
topLevelType: TopLevelType,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: EventTarget,
) {
const events = extractEvents(
const events = extractPluginEvents(
topLevelType,
targetInst,
nativeEvent,
Expand Down
Expand Up @@ -11,7 +11,7 @@

const {HostComponent} = require('shared/ReactWorkTags');

let EventPluginHub;
let EventBatching;
let EventPluginUtils;
let ResponderEventPlugin;

Expand Down Expand Up @@ -321,7 +321,7 @@ const run = function(config, hierarchyConfig, nativeEventConfig) {
// At this point the negotiation events have been dispatched as part of the
// extraction process, but not the side effectful events. Below, we dispatch
// side effectful events.
EventPluginHub.runEventsInBatch(extractedEvents);
EventBatching.runEventsInBatch(extractedEvents);

// Ensure that every event that declared an `order`, was actually dispatched.
expect('number of events dispatched:' + runData.dispatchCount).toBe(
Expand Down Expand Up @@ -403,7 +403,7 @@ describe('ResponderEventPlugin', () => {
jest.resetModules();

const ReactDOMUnstableNativeDependencies = require('react-dom/unstable-native-dependencies');
EventPluginHub = require('events/EventPluginHub');
EventBatching = require('events/EventBatching');
EventPluginUtils = require('events/EventPluginUtils');
ResponderEventPlugin =
ReactDOMUnstableNativeDependencies.ResponderEventPlugin;
Expand Down
6 changes: 2 additions & 4 deletions packages/react-dom/src/client/ReactDOM.js
Expand Up @@ -44,10 +44,8 @@ import {
enqueueStateRestore,
restoreStateIfNeeded,
} from 'events/ReactControlledComponent';
import {
injection as EventPluginHubInjection,
runEventsInBatch,
} from 'events/EventPluginHub';
import {injection as EventPluginHubInjection} from 'events/EventPluginHub';
import {runEventsInBatch} from 'events/EventBatching';
import {eventNameDispatchConfigs} from 'events/EventPluginRegistry';
import {
accumulateTwoPhaseDispatches,
Expand Down
36 changes: 20 additions & 16 deletions packages/react-dom/src/client/ReactDOMHostConfig.js
Expand Up @@ -862,29 +862,33 @@ export function handleEventComponent(
rootContainerInstance: Container,
internalInstanceHandle: Object,
): void {
const rootElement = rootContainerInstance.ownerDocument;
listenToEventResponderEvents(eventResponder, rootElement);
if (enableEventAPI) {
const rootElement = rootContainerInstance.ownerDocument;
listenToEventResponderEvents(eventResponder, rootElement);
}
}

export function handleEventTarget(
type: Symbol | number,
props: Props,
internalInstanceHandle: Object,
): void {
// Touch target hit slop handling
if (type === REACT_EVENT_TARGET_TOUCH_HIT) {
// Validates that there is a single element
const element = getElementFromTouchHitTarget(internalInstanceHandle);
if (element !== null) {
// We update the event target state node to be that of the element.
// We can then diff this entry to determine if we need to add the
// hit slop element, or change the dimensions of the hit slop.
const lastElement = internalInstanceHandle.stateNode;
if (lastElement !== element) {
internalInstanceHandle.stateNode = element;
// TODO: Create the hit slop element and attach it to the element
} else {
// TODO: Diff the left, top, right, bottom props
if (enableEventAPI) {
// Touch target hit slop handling
if (type === REACT_EVENT_TARGET_TOUCH_HIT) {
// Validates that there is a single element
const element = getElementFromTouchHitTarget(internalInstanceHandle);
if (element !== null) {
// We update the event target state node to be that of the element.
// We can then diff this entry to determine if we need to add the
// hit slop element, or change the dimensions of the hit slop.
const lastElement = internalInstanceHandle.stateNode;
if (lastElement !== element) {
internalInstanceHandle.stateNode = element;
// TODO: Create the hit slop element and attach it to the element
} else {
// TODO: Diff the left, top, right, bottom props
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/react-dom/src/events/ChangeEventPlugin.js
Expand Up @@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

import {runEventsInBatch} from 'events/EventPluginHub';
import {runEventsInBatch} from 'events/EventBatching';
import {accumulateTwoPhaseDispatches} from 'events/EventPropagators';
import {enqueueStateRestore} from 'events/ReactControlledComponent';
import {batchedUpdates} from 'events/ReactGenericBatching';
Expand Down

0 comments on commit 80f8b0d

Please sign in to comment.