diff --git a/packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js b/packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js index 42cc136a7202..5d0c8b8b9e97 100644 --- a/packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js +++ b/packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js @@ -1099,6 +1099,8 @@ describe('ReactFabric', () => { // Check for referential equality expect(ref1.current).toBe(event.target); expect(ref1.current).toBe(event.currentTarget); + + expect(global.event).toBe(event); }} onStartShouldSetResponder={() => true} /> @@ -1110,6 +1112,8 @@ describe('ReactFabric', () => { // Check for referential equality expect(ref2.current).toBe(event.target); expect(ref2.current).toBe(event.currentTarget); + + expect(global.event).toBe(event); }} onStartShouldSetResponder={() => true} /> @@ -1123,6 +1127,9 @@ describe('ReactFabric', () => { const [dispatchEvent] = nativeFabricUIManager.registerEventHandler.mock.calls[0]; + const preexistingEvent = {}; + global.event = preexistingEvent; + dispatchEvent(getViewById('one').instanceHandle, 'topTouchStart', { target: getViewById('one').reactTag, identifier: 17, @@ -1150,7 +1157,141 @@ describe('ReactFabric', () => { changedTouches: [], }); - expect.assertions(6); + expect(global.event).toBe(preexistingEvent); + + expect.assertions(9); + }); + + it('propagates timeStamps from native events and sets defaults', async () => { + const View = createReactNativeComponentClass('RCTView', () => ({ + validAttributes: { + id: true, + }, + uiViewClassName: 'RCTView', + directEventTypes: { + topTouchStart: { + registrationName: 'onTouchStart', + }, + topTouchEnd: { + registrationName: 'onTouchEnd', + }, + }, + })); + + function getViewById(id) { + const [reactTag, , , , instanceHandle] = + nativeFabricUIManager.createNode.mock.calls.find( + args => args[3] && args[3].id === id, + ); + + return {reactTag, instanceHandle}; + } + + const ref1 = React.createRef(); + const ref2 = React.createRef(); + const ref3 = React.createRef(); + + const explicitTimeStampCamelCase = 'explicit-timestamp-camelcase'; + const explicitTimeStampLowerCase = 'explicit-timestamp-lowercase'; + const performanceNowValue = 'performance-now-timestamp'; + + jest.spyOn(performance, 'now').mockReturnValue(performanceNowValue); + + await act(() => { + ReactFabric.render( + <> + { + expect(event.timeStamp).toBe(performanceNowValue); + }} + /> + { + expect(event.timeStamp).toBe(explicitTimeStampCamelCase); + }} + /> + { + expect(event.timeStamp).toBe(explicitTimeStampLowerCase); + }} + /> + , + 1, + null, + true, + ); + }); + + const [dispatchEvent] = + nativeFabricUIManager.registerEventHandler.mock.calls[0]; + + dispatchEvent(getViewById('default').instanceHandle, 'topTouchStart', { + target: getViewById('default').reactTag, + identifier: 17, + touches: [], + changedTouches: [], + }); + dispatchEvent(getViewById('default').instanceHandle, 'topTouchEnd', { + target: getViewById('default').reactTag, + identifier: 17, + touches: [], + changedTouches: [], + // No timeStamp property + }); + + dispatchEvent( + getViewById('explicitTimeStampCamelCase').instanceHandle, + 'topTouchStart', + { + target: getViewById('explicitTimeStampCamelCase').reactTag, + identifier: 17, + touches: [], + changedTouches: [], + }, + ); + + dispatchEvent( + getViewById('explicitTimeStampCamelCase').instanceHandle, + 'topTouchEnd', + { + target: getViewById('explicitTimeStampCamelCase').reactTag, + identifier: 17, + touches: [], + changedTouches: [], + timeStamp: explicitTimeStampCamelCase, + }, + ); + + dispatchEvent( + getViewById('explicitTimeStampLowerCase').instanceHandle, + 'topTouchStart', + { + target: getViewById('explicitTimeStampLowerCase').reactTag, + identifier: 17, + touches: [], + changedTouches: [], + }, + ); + + dispatchEvent( + getViewById('explicitTimeStampLowerCase').instanceHandle, + 'topTouchEnd', + { + target: getViewById('explicitTimeStampLowerCase').reactTag, + identifier: 17, + touches: [], + changedTouches: [], + timestamp: explicitTimeStampLowerCase, + }, + ); + + expect.assertions(3); }); it('findHostInstance_DEPRECATED should warn if used to find a host component inside StrictMode', async () => { diff --git a/packages/react-native-renderer/src/legacy-events/EventPluginUtils.js b/packages/react-native-renderer/src/legacy-events/EventPluginUtils.js index 64a05cef33fa..2027ac2e20fc 100644 --- a/packages/react-native-renderer/src/legacy-events/EventPluginUtils.js +++ b/packages/react-native-renderer/src/legacy-events/EventPluginUtils.js @@ -67,6 +67,9 @@ function validateEventDispatches(event) { */ export function executeDispatch(event, listener, inst) { event.currentTarget = getNodeFromInstance(inst); + const currentEvent = global.event; + global.event = event; + try { listener(event); } catch (error) { @@ -77,6 +80,8 @@ export function executeDispatch(event, listener, inst) { // TODO: Make sure this error gets logged somehow. } } + + global.event = currentEvent; event.currentTarget = null; } diff --git a/packages/react-native-renderer/src/legacy-events/SyntheticEvent.js b/packages/react-native-renderer/src/legacy-events/SyntheticEvent.js index 723daa0dc9e5..b6a4bf4e7cbb 100644 --- a/packages/react-native-renderer/src/legacy-events/SyntheticEvent.js +++ b/packages/react-native-renderer/src/legacy-events/SyntheticEvent.js @@ -11,6 +11,21 @@ import assign from 'shared/assign'; const EVENT_POOL_SIZE = 10; +let currentTimeStamp = () => { + // Lazily define the function based on the existence of performance.now() + if ( + typeof performance === 'object' && + performance !== null && + typeof performance.now === 'function' + ) { + currentTimeStamp = () => performance.now(); + } else { + currentTimeStamp = () => Date.now(); + } + + return currentTimeStamp(); +}; + /** * @interface Event * @see http://www.w3.org/TR/DOM-Level-3-Events/ @@ -26,7 +41,7 @@ const EventInterface = { bubbles: null, cancelable: null, timeStamp: function (event) { - return event.timeStamp || Date.now(); + return event.timeStamp || event.timestamp || currentTimeStamp(); }, defaultPrevented: null, isTrusted: null,