mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
[RN] Fix timeStamp property of SyntheticEvent in React Native (#35912)
## Summary This fixes the semantics of the `timeStamp` property of events in React Native. Currently, most events just assign `Date.now()` (at the time of creating the event object in JavaScript) as the `timeStamp` property. This is a divergence with Web and most native platforms, that use a monotonic timestamp for the value (on Web, the same timestamp provided by `performance.now()`). Additionally, many native events specify a timestamp in the event data object as `timestamp` and gets ignored by the logic in JS as it only looks at properties named `timeStamp` specifically (camel case). This PR fixes both issues by: 1. Using `performance.now()` instead of `Date.now()` by default (if available). 2. Checking for a `timestamp` property before falling back to the default (apart from `timeStamp`). ## How did you test this change? Added unit tests for verify the new behavior.
This commit is contained in:
@@ -1153,6 +1153,138 @@ describe('ReactFabric', () => {
|
||||
expect.assertions(6);
|
||||
});
|
||||
|
||||
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(
|
||||
<>
|
||||
<View
|
||||
ref={ref1}
|
||||
id="default"
|
||||
onTouchEnd={event => {
|
||||
expect(event.timeStamp).toBe(performanceNowValue);
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
ref={ref2}
|
||||
id="explicitTimeStampCamelCase"
|
||||
onTouchEnd={event => {
|
||||
expect(event.timeStamp).toBe(explicitTimeStampCamelCase);
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
ref={ref2}
|
||||
id="explicitTimeStampLowerCase"
|
||||
onTouchEnd={event => {
|
||||
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 () => {
|
||||
const View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {foo: true},
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user