Skip to content

Commit b08362a

Browse files
JoshuaGrossfacebook-github-bot
authored andcommitted
Send unix timestamp for touch events instead of systemUptime
Summary: We want to be able to instrument touch processing delays in JS, which does not have access to systemUptime; therefore we want a UNIX timestamp, which JS has access to and can compare to the touch time. It only matters that there is relative consistency between multiple touch events in JS, which is still the case; so this should have no impact on product code. Changelog: [Internal] Reviewed By: sammy-SC Differential Revision: D26705430 fbshipit-source-id: 2acd52ae5873a44edf1e0cb126a9a6c87203d8fa
1 parent 69feed5 commit b08362a

File tree

3 files changed

+61
-8
lines changed

3 files changed

+61
-8
lines changed

React/Base/RCTTouchHandler.m

+9-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#import "RCTTouchHandler.h"
99

1010
#import <UIKit/UIGestureRecognizerSubclass.h>
11+
#import <UIKit/UIKit.h>
1112

1213
#import "RCTAssert.h"
1314
#import "RCTBridge.h"
@@ -39,6 +40,10 @@ @implementation RCTTouchHandler {
3940

4041
__weak UIView *_cachedRootView;
4142

43+
// See Touch.h and usage. This gives us a time-basis for a monotonic
44+
// clock that acts like a timestamp of milliseconds elapsed since UNIX epoch.
45+
NSTimeInterval _unixEpochBasisTime;
46+
4247
uint16_t _coalescingKey;
4348
}
4449

@@ -53,6 +58,9 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
5358
_reactTouches = [NSMutableArray new];
5459
_touchViews = [NSMutableArray new];
5560

61+
// Get a UNIX epoch basis time:
62+
_unixEpochBasisTime = [[NSDate date] timeIntervalSince1970] - [NSProcessInfo processInfo].systemUptime;
63+
5664
// `cancelsTouchesInView` and `delaysTouches*` are needed in order to be used as a top level
5765
// event delegated recognizer. Otherwise, lower-level components not built
5866
// using RCT, will fail to recognize gestures.
@@ -159,7 +167,7 @@ - (void)_updateReactTouchAtIndex:(NSInteger)touchIndex
159167
reactTouch[@"pageY"] = @(RCTSanitizeNaNValue(rootViewLocation.y, @"touchEvent.pageY"));
160168
reactTouch[@"locationX"] = @(RCTSanitizeNaNValue(touchViewLocation.x, @"touchEvent.locationX"));
161169
reactTouch[@"locationY"] = @(RCTSanitizeNaNValue(touchViewLocation.y, @"touchEvent.locationY"));
162-
reactTouch[@"timestamp"] = @(nativeTouch.timestamp * 1000); // in ms, for JS
170+
reactTouch[@"timestamp"] = @((_unixEpochBasisTime + nativeTouch.timestamp) * 1000); // in ms, for JS
163171

164172
// TODO: force for a 'normal' touch is usually 1.0;
165173
// should we expose a `normalTouchForce` constant somewhere (which would

React/Fabric/RCTSurfaceTouchHandler.mm

+20-6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#import <React/RCTUtils.h>
1111
#import <React/RCTViewComponentView.h>
1212
#import <UIKit/UIGestureRecognizerSubclass.h>
13+
#import <UIKit/UIKit.h>
1314

1415
#import "RCTConversions.h"
1516
#import "RCTTouchableComponentViewProtocol.h"
@@ -82,7 +83,8 @@ static void UpdateActiveTouchWithUITouch(
8283
ActiveTouch &activeTouch,
8384
UITouch *uiTouch,
8485
UIView *rootComponentView,
85-
CGPoint rootViewOriginOffset)
86+
CGPoint rootViewOriginOffset,
87+
NSTimeInterval unixTimestampBasis)
8688
{
8789
CGPoint offsetPoint = [uiTouch locationInView:activeTouch.componentView];
8890
CGPoint screenPoint = [uiTouch locationInView:uiTouch.window];
@@ -93,14 +95,18 @@ static void UpdateActiveTouchWithUITouch(
9395
activeTouch.touch.screenPoint = RCTPointFromCGPoint(screenPoint);
9496
activeTouch.touch.pagePoint = RCTPointFromCGPoint(pagePoint);
9597

96-
activeTouch.touch.timestamp = uiTouch.timestamp;
98+
activeTouch.touch.timestamp = unixTimestampBasis + uiTouch.timestamp;
9799

98100
if (RCTForceTouchAvailable()) {
99101
activeTouch.touch.force = RCTZeroIfNaN(uiTouch.force / uiTouch.maximumPossibleForce);
100102
}
101103
}
102104

103-
static ActiveTouch CreateTouchWithUITouch(UITouch *uiTouch, UIView *rootComponentView, CGPoint rootViewOriginOffset)
105+
static ActiveTouch CreateTouchWithUITouch(
106+
UITouch *uiTouch,
107+
UIView *rootComponentView,
108+
CGPoint rootViewOriginOffset,
109+
NSTimeInterval unixTimestampBasis)
104110
{
105111
ActiveTouch activeTouch = {};
106112

@@ -117,7 +123,7 @@ static ActiveTouch CreateTouchWithUITouch(UITouch *uiTouch, UIView *rootComponen
117123
componentView = componentView.superview;
118124
}
119125

120-
UpdateActiveTouchWithUITouch(activeTouch, uiTouch, rootComponentView, rootViewOriginOffset);
126+
UpdateActiveTouchWithUITouch(activeTouch, uiTouch, rootComponentView, rootViewOriginOffset, unixTimestampBasis);
121127
return activeTouch;
122128
}
123129

@@ -167,6 +173,12 @@ @implementation RCTSurfaceTouchHandler {
167173
*/
168174
__weak UIView *_rootComponentView;
169175
IdentifierPool<11> _identifierPool;
176+
177+
/*
178+
* See Touch.h and usage. This gives us a time-basis for a monotonic
179+
* clock that acts like a timestamp of milliseconds elapsed since UNIX epoch.
180+
*/
181+
NSTimeInterval _unixEpochBasisTime;
170182
}
171183

172184
- (instancetype)init
@@ -181,6 +193,8 @@ - (instancetype)init
181193
self.delaysTouchesEnded = NO;
182194

183195
self.delegate = self;
196+
197+
_unixEpochBasisTime = [[NSDate date] timeIntervalSince1970] - [NSProcessInfo processInfo].systemUptime;
184198
}
185199

186200
return self;
@@ -208,7 +222,7 @@ - (void)detachFromView:(UIView *)view
208222
- (void)_registerTouches:(NSSet<UITouch *> *)touches
209223
{
210224
for (UITouch *touch in touches) {
211-
auto activeTouch = CreateTouchWithUITouch(touch, _rootComponentView, _viewOriginOffset);
225+
auto activeTouch = CreateTouchWithUITouch(touch, _rootComponentView, _viewOriginOffset, _unixEpochBasisTime);
212226
activeTouch.touch.identifier = _identifierPool.dequeue();
213227
_activeTouches.emplace(touch, activeTouch);
214228
}
@@ -223,7 +237,7 @@ - (void)_updateTouches:(NSSet<UITouch *> *)touches
223237
continue;
224238
}
225239

226-
UpdateActiveTouchWithUITouch(iterator->second, touch, _rootComponentView, _viewOriginOffset);
240+
UpdateActiveTouchWithUITouch(iterator->second, touch, _rootComponentView, _viewOriginOffset, _unixEpochBasisTime);
227241
}
228242
}
229243

ReactCommon/react/renderer/components/view/Touch.h

+32-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,38 @@ struct Touch {
5252
Float force;
5353

5454
/*
55-
* The time in seconds when the touch occurred or when it was last mutated.
55+
* The time in seconds (with fractional milliseconds) when the touch occurred
56+
* or when it was last mutated.
57+
*
58+
* Whenever possible this should be computed as:
59+
* 1. Pick MONO_CLOCK_NOW, a monotonic system clock. Generally, something like
60+
* `systemUptimeMillis`.
61+
* 2. BASIS_TIME = unix timestamp from unix epoch (ms) - MONO_CLOCK_NOW
62+
* 3. Then assign timestamp = BASIS_TIME + MONO_CLOCK_NOW
63+
*
64+
* The effect should be UNIX timestamp from UNIX epoch, but as a monotonic
65+
* clock (if you just assign to current system time, it can move backwards due
66+
* to clock adjustements, leap seconds, etc etc). So the vast majority of the
67+
* time it will look identical to current UNIX time, but there are some
68+
* edge-cases where it can drift.
69+
*
70+
* If you are not able to use the scheme above for some reason, prefer to use
71+
* a monotonic clock. This timestamp MUST be monotonic. Do NOT just pass along
72+
* system time.
73+
*
74+
* The goal is to allow touch latency to be computed in JS. JS does not have
75+
* access to something like `systemUptimeMillis`, it generally can only access
76+
* the current system time. This *does* mean that the touch latency could be
77+
* computed incorrectly in cases of clock drift, so you should only use this
78+
* as telemetry to get a decent, but not totally perfect, idea of performance.
79+
* Do not use this latency information for anything "mission critical". You
80+
* can assume it's probably reasonably accurate 99% of the time.
81+
*
82+
* Note that we attempt to adhere to the spec of timestamp here:
83+
* https://dom.spec.whatwg.org/#dom-event-timestamp
84+
* Notably, since `global` is not a Window object in React Native, we have
85+
* some flexibility in how we define the time origin:
86+
* https://w3c.github.io/hr-time/#dfn-time-origin
5687
*/
5788
Float timestamp;
5889

0 commit comments

Comments
 (0)