diff --git a/packages/react-native/src/private/webapis/dom/events/Event.js b/packages/react-native/src/private/webapis/dom/events/Event.js index f97ead512ab9..f918f9796e56 100644 --- a/packages/react-native/src/private/webapis/dom/events/Event.js +++ b/packages/react-native/src/private/webapis/dom/events/Event.js @@ -21,6 +21,7 @@ import {setPlatformObject} from '../../webidl/PlatformObjects'; import { COMPOSED_PATH_KEY, CURRENT_TARGET_KEY, + EVENT_INIT_TIMESTAMP_KEY, EVENT_PHASE_KEY, IN_PASSIVE_LISTENER_FLAG_KEY, IS_TRUSTED_KEY, @@ -60,7 +61,7 @@ export default class Event { _type: string; _defaultPrevented: boolean = false; - _timeStamp: number = performance.now(); + _timeStamp: number; // $FlowExpectedError[unsupported-syntax] [COMPOSED_PATH_KEY]: boolean = []; @@ -109,6 +110,14 @@ export default class Event { this._bubbles = Boolean(options?.bubbles); this._cancelable = Boolean(options?.cancelable); this._composed = Boolean(options?.composed); + + // For internal construction of events using a custom timestamp (instead of + // event object creation), for use cases like dispatching events from the + // host platform using the original timestamps. + // $FlowExpectedError[prop-missing] + const initTimestamp: number | void = options?.[EVENT_INIT_TIMESTAMP_KEY]; + this._timeStamp = + initTimestamp !== undefined ? initTimestamp : performance.now(); } get bubbles(): boolean { diff --git a/packages/react-native/src/private/webapis/dom/events/__tests__/Event-itest.js b/packages/react-native/src/private/webapis/dom/events/__tests__/Event-itest.js index 21609b28a039..4baa3d2eec41 100644 --- a/packages/react-native/src/private/webapis/dom/events/__tests__/Event-itest.js +++ b/packages/react-native/src/private/webapis/dom/events/__tests__/Event-itest.js @@ -11,7 +11,10 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; import Event from 'react-native/src/private/webapis/dom/events/Event'; -import {setInPassiveListenerFlag} from 'react-native/src/private/webapis/dom/events/internals/EventInternals'; +import { + setEventInitTimeStamp, + setInPassiveListenerFlag, +} from 'react-native/src/private/webapis/dom/events/internals/EventInternals'; describe('Event', () => { it('provides read-only constants for event phases', () => { @@ -233,6 +236,23 @@ describe('Event', () => { expect(event.timeStamp).toBeLessThanOrEqual(upperBoundTimestamp); }); + it('should use a custom timestamp when set via setEventInitTimeStamp', () => { + const customTimestamp = 12345.678; + const options = {}; + setEventInitTimeStamp(options, customTimestamp); + const event = new Event('custom', options); + + expect(event.timeStamp).toBe(customTimestamp); + }); + + it('should accept zero as a valid custom timestamp', () => { + const options = {}; + setEventInitTimeStamp(options, 0); + const event = new Event('custom', options); + + expect(event.timeStamp).toBe(0); + }); + describe('preventDefault', () => { let originalConsoleError; let consoleErrorMock; diff --git a/packages/react-native/src/private/webapis/dom/events/internals/EventInternals.js b/packages/react-native/src/private/webapis/dom/events/internals/EventInternals.js index f16806795857..32f36e2013ee 100644 --- a/packages/react-native/src/private/webapis/dom/events/internals/EventInternals.js +++ b/packages/react-native/src/private/webapis/dom/events/internals/EventInternals.js @@ -14,7 +14,7 @@ * (only with public exports). */ -import type Event, {EventPhase} from '../Event'; +import type Event, {EventInit, EventPhase} from '../Event'; import type EventTarget from '../EventTarget'; export const COMPOSED_PATH_KEY: symbol = Symbol('composedPath'); @@ -30,6 +30,11 @@ export const STOP_IMMEDIATE_PROPAGATION_FLAG_KEY: symbol = Symbol( export const STOP_PROPAGATION_FLAG_KEY: symbol = Symbol('stopPropagationFlag'); export const TARGET_KEY: symbol = Symbol('target'); +// For internal construction of events using a custom timestamp (instead of +// event object creation), for use cases like dispatching events from the host +// platform using the original timestamps. +export const EVENT_INIT_TIMESTAMP_KEY: symbol = Symbol('eventInitTimestamp'); + export function getCurrentTarget(event: Event): EventTarget | null { // $FlowExpectedError[prop-missing] return event[CURRENT_TARGET_KEY]; @@ -118,3 +123,15 @@ export function setTarget(event: Event, target: EventTarget | null): void { // $FlowExpectedError[prop-missing] event[TARGET_KEY] = target; } + +export function setEventInitTimeStamp( + eventInit: EventInit, + timeStamp: number, +): void { + if (typeof timeStamp !== 'number') { + return; + } + // $FlowExpectedError[prop-missing] + // $FlowExpectedError[invalid-computed-prop] + eventInit[EVENT_INIT_TIMESTAMP_KEY] = timeStamp; +}