2016-03-25 06:40:52 +08:00
|
|
|
/**
|
2022-10-18 23:19:24 +08:00
|
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
2016-03-25 06:40:52 +08:00
|
|
|
*
|
2017-09-25 04:48:13 +08:00
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
2016-03-25 06:40:52 +08:00
|
|
|
*
|
|
|
|
|
* @flow
|
|
|
|
|
*/
|
|
|
|
|
|
2022-10-17 09:58:58 +08:00
|
|
|
import type {
|
|
|
|
|
AnyNativeEvent,
|
|
|
|
|
LegacyPluginModule,
|
|
|
|
|
} from './legacy-events/PluginModuleType';
|
Split cross-package types from implementation
Some of our internal reconciler types have leaked into other packages.
Usually, these types are treated as opaque; we don't read and write
to its fields. This is good.
However, the type is often passed back to a reconciler method. For
example, React DOM creates a FiberRoot with `createContainer`, then
passes that root to `updateContainer`. It doesn't do anything with the
root except pass it through, but because `updateContainer` expects a
full FiberRoot, React DOM is still coupled to all its fields.
I don't know if there's an idiomatic way to handle this in Flow. Opaque
types are simlar, but those only work within a single file. AFAIK,
there's no way to use a package as the boundary for opaqueness.
The immediate problem this presents is that the reconciler refactor will
involve changes to our internal data structures. I don't want to have to
fork every single package that happens to pass through a Fiber or
FiberRoot, or access any one of its fields. So my current plan is to
share the same Flow type across both forks. The shared type will be a
superset of each implementation's type, e.g. Fiber will have both an
`expirationTime` field and a `lanes` field. The implementations will
diverge, but not the types.
To do this, I lifted the type definitions into a separate module.
2020-04-09 14:48:24 +08:00
|
|
|
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
|
2020-07-01 23:37:13 +08:00
|
|
|
import type {ReactSyntheticEvent} from './legacy-events/ReactSyntheticEventType';
|
|
|
|
|
import type {TopLevelType} from './legacy-events/TopLevelEventTypes';
|
|
|
|
|
|
2022-10-17 09:58:58 +08:00
|
|
|
import {
|
|
|
|
|
registrationNameModules,
|
|
|
|
|
plugins,
|
|
|
|
|
} from './legacy-events/EventPluginRegistry';
|
2020-07-01 23:37:13 +08:00
|
|
|
import {batchedUpdates} from './legacy-events/ReactGenericBatching';
|
|
|
|
|
import {runEventsInBatch} from './legacy-events/EventBatching';
|
2023-03-02 23:54:51 +08:00
|
|
|
import getListener from './ReactNativeGetListener';
|
2020-07-01 23:37:13 +08:00
|
|
|
import accumulateInto from './legacy-events/accumulateInto';
|
2017-10-26 02:07:54 +08:00
|
|
|
|
2017-11-05 19:58:36 +08:00
|
|
|
import {getInstanceFromNode} from './ReactNativeComponentTree';
|
2016-03-25 06:40:52 +08:00
|
|
|
|
2023-03-02 23:54:51 +08:00
|
|
|
export {getListener, registrationNameModules as registrationNames};
|
2017-11-05 19:58:36 +08:00
|
|
|
|
2016-03-25 06:40:52 +08:00
|
|
|
/**
|
|
|
|
|
* Version of `ReactBrowserEventEmitter` that works on the receiving side of a
|
|
|
|
|
* serialized worker boundary.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Shared default empty native event - conserve memory.
|
2018-01-06 02:37:13 +08:00
|
|
|
const EMPTY_NATIVE_EVENT = (({}: any): AnyNativeEvent);
|
2016-03-25 06:40:52 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Selects a subsequence of `Touch`es, without destroying `touches`.
|
|
|
|
|
*
|
|
|
|
|
* @param {Array<Touch>} touches Deserialized touch objects.
|
|
|
|
|
* @param {Array<number>} indices Indices by which to pull subsequence.
|
|
|
|
|
* @return {Array<Touch>} Subsequence of touch objects.
|
|
|
|
|
*/
|
2023-01-10 04:46:48 +08:00
|
|
|
// $FlowFixMe[missing-local-annot]
|
2023-03-15 09:00:22 +08:00
|
|
|
function touchSubsequence(touches, indices) {
|
2017-12-01 05:39:39 +08:00
|
|
|
const ret = [];
|
|
|
|
|
for (let i = 0; i < indices.length; i++) {
|
2016-03-25 06:40:52 +08:00
|
|
|
ret.push(touches[indices[i]]);
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
2023-03-15 09:00:22 +08:00
|
|
|
}
|
2016-03-25 06:40:52 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* TODO: Pool all of this.
|
|
|
|
|
*
|
|
|
|
|
* Destroys `touches` by removing touch objects at indices `indices`. This is
|
|
|
|
|
* to maintain compatibility with W3C touch "end" events, where the active
|
|
|
|
|
* touches don't include the set that has just been "ended".
|
|
|
|
|
*
|
|
|
|
|
* @param {Array<Touch>} touches Deserialized touch objects.
|
|
|
|
|
* @param {Array<number>} indices Indices to remove from `touches`.
|
|
|
|
|
* @return {Array<Touch>} Subsequence of removed touch objects.
|
|
|
|
|
*/
|
2023-03-15 09:00:22 +08:00
|
|
|
function removeTouchesAtIndices(
|
2016-03-25 06:40:52 +08:00
|
|
|
touches: Array<Object>,
|
2017-03-14 08:05:18 +08:00
|
|
|
indices: Array<number>,
|
2016-03-25 06:40:52 +08:00
|
|
|
): Array<Object> {
|
2017-12-01 05:39:39 +08:00
|
|
|
const rippedOut = [];
|
2016-03-25 06:40:52 +08:00
|
|
|
// use an unsafe downcast to alias to nullable elements,
|
|
|
|
|
// so we can delete and then compact.
|
2017-12-01 05:39:39 +08:00
|
|
|
const temp: Array<?Object> = (touches: Array<any>);
|
|
|
|
|
for (let i = 0; i < indices.length; i++) {
|
|
|
|
|
const index = indices[i];
|
2016-03-25 06:40:52 +08:00
|
|
|
rippedOut.push(touches[index]);
|
|
|
|
|
temp[index] = null;
|
|
|
|
|
}
|
2017-12-01 05:39:39 +08:00
|
|
|
let fillAt = 0;
|
|
|
|
|
for (let j = 0; j < temp.length; j++) {
|
|
|
|
|
const cur = temp[j];
|
2016-03-25 06:40:52 +08:00
|
|
|
if (cur !== null) {
|
|
|
|
|
temp[fillAt++] = cur;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
temp.length = fillAt;
|
|
|
|
|
return rippedOut;
|
2023-03-15 09:00:22 +08:00
|
|
|
}
|
2016-03-25 06:40:52 +08:00
|
|
|
|
2017-11-05 19:58:36 +08:00
|
|
|
/**
|
|
|
|
|
* Internal version of `receiveEvent` in terms of normalized (non-tag)
|
|
|
|
|
* `rootNodeID`.
|
|
|
|
|
*
|
|
|
|
|
* @see receiveEvent.
|
|
|
|
|
*
|
|
|
|
|
* @param {rootNodeID} rootNodeID React root node ID that event occurred on.
|
|
|
|
|
* @param {TopLevelType} topLevelType Top level type of event.
|
|
|
|
|
* @param {?object} nativeEventParam Object passed from native.
|
|
|
|
|
*/
|
2018-11-20 07:32:54 +08:00
|
|
|
function _receiveRootNodeIDEvent(
|
2017-11-05 19:58:36 +08:00
|
|
|
rootNodeID: number,
|
2018-05-15 17:38:50 +08:00
|
|
|
topLevelType: TopLevelType,
|
2018-01-06 02:37:13 +08:00
|
|
|
nativeEventParam: ?AnyNativeEvent,
|
2017-11-05 19:58:36 +08:00
|
|
|
) {
|
2017-12-01 05:39:39 +08:00
|
|
|
const nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT;
|
|
|
|
|
const inst = getInstanceFromNode(rootNodeID);
|
2019-11-12 03:35:29 +08:00
|
|
|
|
2019-11-12 04:58:30 +08:00
|
|
|
let target = null;
|
2020-02-29 05:45:42 +08:00
|
|
|
if (inst != null) {
|
|
|
|
|
target = inst.stateNode;
|
2019-11-12 03:35:29 +08:00
|
|
|
}
|
|
|
|
|
|
2023-01-31 21:25:05 +08:00
|
|
|
batchedUpdates(function () {
|
2020-04-03 06:17:10 +08:00
|
|
|
runExtractedPluginEventsInBatch(topLevelType, inst, nativeEvent, target);
|
2017-11-05 19:58:36 +08:00
|
|
|
});
|
|
|
|
|
// React Native doesn't use ReactControlledComponent but if it did, here's
|
|
|
|
|
// where it would do it.
|
|
|
|
|
}
|
2016-03-25 06:40:52 +08:00
|
|
|
|
2020-02-14 16:10:42 +08:00
|
|
|
/**
|
|
|
|
|
* Allows registered plugins an opportunity to extract events from top-level
|
|
|
|
|
* native browser events.
|
|
|
|
|
*
|
|
|
|
|
* @return {*} An accumulation of synthetic events.
|
|
|
|
|
* @internal
|
|
|
|
|
*/
|
|
|
|
|
function extractPluginEvents(
|
|
|
|
|
topLevelType: TopLevelType,
|
|
|
|
|
targetInst: null | Fiber,
|
|
|
|
|
nativeEvent: AnyNativeEvent,
|
|
|
|
|
nativeEventTarget: null | EventTarget,
|
|
|
|
|
): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null {
|
2023-02-10 06:07:39 +08:00
|
|
|
let events: Array<ReactSyntheticEvent> | ReactSyntheticEvent | null = null;
|
2020-05-04 23:55:45 +08:00
|
|
|
const legacyPlugins = ((plugins: any): Array<LegacyPluginModule<Event>>);
|
|
|
|
|
for (let i = 0; i < legacyPlugins.length; i++) {
|
2020-02-14 16:10:42 +08:00
|
|
|
// Not every plugin in the ordering may be loaded at runtime.
|
2020-05-04 23:55:45 +08:00
|
|
|
const possiblePlugin: LegacyPluginModule<AnyNativeEvent> = legacyPlugins[i];
|
2020-02-14 16:10:42 +08:00
|
|
|
if (possiblePlugin) {
|
|
|
|
|
const extractedEvents = possiblePlugin.extractEvents(
|
|
|
|
|
topLevelType,
|
|
|
|
|
targetInst,
|
|
|
|
|
nativeEvent,
|
|
|
|
|
nativeEventTarget,
|
|
|
|
|
);
|
|
|
|
|
if (extractedEvents) {
|
|
|
|
|
events = accumulateInto(events, extractedEvents);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return events;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function runExtractedPluginEventsInBatch(
|
|
|
|
|
topLevelType: TopLevelType,
|
|
|
|
|
targetInst: null | Fiber,
|
|
|
|
|
nativeEvent: AnyNativeEvent,
|
|
|
|
|
nativeEventTarget: null | EventTarget,
|
|
|
|
|
) {
|
|
|
|
|
const events = extractPluginEvents(
|
|
|
|
|
topLevelType,
|
|
|
|
|
targetInst,
|
|
|
|
|
nativeEvent,
|
|
|
|
|
nativeEventTarget,
|
|
|
|
|
);
|
|
|
|
|
runEventsInBatch(events);
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-05 19:58:36 +08:00
|
|
|
/**
|
|
|
|
|
* Publicly exposed method on module for native objc to invoke when a top
|
|
|
|
|
* level event is extracted.
|
|
|
|
|
* @param {rootNodeID} rootNodeID React root node ID that event occurred on.
|
|
|
|
|
* @param {TopLevelType} topLevelType Top level type of event.
|
|
|
|
|
* @param {object} nativeEventParam Object passed from native.
|
|
|
|
|
*/
|
|
|
|
|
export function receiveEvent(
|
|
|
|
|
rootNodeID: number,
|
2018-05-15 17:38:50 +08:00
|
|
|
topLevelType: TopLevelType,
|
2018-01-06 02:37:13 +08:00
|
|
|
nativeEventParam: AnyNativeEvent,
|
2017-11-05 19:58:36 +08:00
|
|
|
) {
|
|
|
|
|
_receiveRootNodeIDEvent(rootNodeID, topLevelType, nativeEventParam);
|
|
|
|
|
}
|
2016-03-25 06:40:52 +08:00
|
|
|
|
2017-11-05 19:58:36 +08:00
|
|
|
/**
|
|
|
|
|
* Simple multi-wrapper around `receiveEvent` that is intended to receive an
|
|
|
|
|
* efficient representation of `Touch` objects, and other information that
|
|
|
|
|
* can be used to construct W3C compliant `Event` and `Touch` lists.
|
|
|
|
|
*
|
|
|
|
|
* This may create dispatch behavior that differs than web touch handling. We
|
|
|
|
|
* loop through each of the changed touches and receive it as a single event.
|
|
|
|
|
* So two `touchStart`/`touchMove`s that occur simultaneously are received as
|
|
|
|
|
* two separate touch event dispatches - when they arguably should be one.
|
|
|
|
|
*
|
|
|
|
|
* This implementation reuses the `Touch` objects themselves as the `Event`s
|
|
|
|
|
* since we dispatch an event for each touch (though that might not be spec
|
|
|
|
|
* compliant). The main purpose of reusing them is to save allocations.
|
|
|
|
|
*
|
|
|
|
|
* TODO: Dispatch multiple changed touches in one event. The bubble path
|
|
|
|
|
* could be the first common ancestor of all the `changedTouches`.
|
|
|
|
|
*
|
|
|
|
|
* One difference between this behavior and W3C spec: cancelled touches will
|
|
|
|
|
* not appear in `.touches`, or in any future `.touches`, though they may
|
|
|
|
|
* still be "actively touching the surface".
|
|
|
|
|
*
|
|
|
|
|
* Web desktop polyfills only need to construct a fake touch event with
|
|
|
|
|
* identifier 0, also abandoning traditional click handlers.
|
|
|
|
|
*/
|
|
|
|
|
export function receiveTouches(
|
2018-05-15 17:38:50 +08:00
|
|
|
eventTopLevelType: TopLevelType,
|
2017-11-05 19:58:36 +08:00
|
|
|
touches: Array<Object>,
|
|
|
|
|
changedIndices: Array<number>,
|
|
|
|
|
) {
|
2017-12-01 05:39:39 +08:00
|
|
|
const changedTouches =
|
2017-11-08 02:09:33 +08:00
|
|
|
eventTopLevelType === 'topTouchEnd' ||
|
2017-11-05 19:58:36 +08:00
|
|
|
eventTopLevelType === 'topTouchCancel'
|
2017-11-08 02:09:33 +08:00
|
|
|
? removeTouchesAtIndices(touches, changedIndices)
|
|
|
|
|
: touchSubsequence(touches, changedIndices);
|
2016-03-25 06:40:52 +08:00
|
|
|
|
2017-12-01 05:39:39 +08:00
|
|
|
for (let jj = 0; jj < changedTouches.length; jj++) {
|
|
|
|
|
const touch = changedTouches[jj];
|
2017-11-05 19:58:36 +08:00
|
|
|
// Touch objects can fulfill the role of `DOM` `Event` objects if we set
|
|
|
|
|
// the `changedTouches`/`touches`. This saves allocations.
|
|
|
|
|
touch.changedTouches = changedTouches;
|
|
|
|
|
touch.touches = touches;
|
2017-12-01 05:39:39 +08:00
|
|
|
const nativeEvent = touch;
|
|
|
|
|
let rootNodeID = null;
|
|
|
|
|
const target = nativeEvent.target;
|
2017-11-05 19:58:36 +08:00
|
|
|
if (target !== null && target !== undefined) {
|
2018-04-10 09:41:13 +08:00
|
|
|
if (target < 1) {
|
2017-11-05 19:58:36 +08:00
|
|
|
if (__DEV__) {
|
2019-12-15 02:09:25 +08:00
|
|
|
console.error(
|
|
|
|
|
'A view is reporting that a touch occurred on tag zero.',
|
|
|
|
|
);
|
2016-03-25 06:40:52 +08:00
|
|
|
}
|
2017-11-05 19:58:36 +08:00
|
|
|
} else {
|
|
|
|
|
rootNodeID = target;
|
2016-03-25 06:40:52 +08:00
|
|
|
}
|
|
|
|
|
}
|
2023-03-27 19:43:04 +08:00
|
|
|
// $FlowFixMe[incompatible-call] Shouldn't we *not* call it if rootNodeID is null?
|
2017-11-05 19:58:36 +08:00
|
|
|
_receiveRootNodeIDEvent(rootNodeID, eventTopLevelType, nativeEvent);
|
|
|
|
|
}
|
|
|
|
|
}
|