Implement performance.timeOrigin (#53660)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53660

Changelog: [internal]

(This is internal because these APIs aren't enabled in OSS yet)

Implements `performance.timeOrigin` to allow converting timestamps from `performance.now()` to be based on the Unix epoch.

This implementation isn't fully spec-compliant to align with the current implementation of `performance.now()`, where the base of the clock is system boot time instead of app startup / navigation time.

Reviewed By: huntie

Differential Revision: D82016724

fbshipit-source-id: e3a066721cecf41e2fd963beb94a0a2f1c5d6493
This commit is contained in:
Rubén Norte
2025-09-10 04:48:55 -07:00
committed by Facebook GitHub Bot
parent 481fb612a2
commit 529f55c8c9
7 changed files with 82 additions and 4 deletions
@@ -131,9 +131,18 @@ NativePerformance::NativePerformance(std::shared_ptr<CallInvoker> jsInvoker)
: NativePerformanceCxxSpec(std::move(jsInvoker)) {}
HighResTimeStamp NativePerformance::now(jsi::Runtime& /*rt*/) {
// This is not spec-compliant, as this is the duration from system boot to
// now, instead of from app startup to now.
// This should be carefully changed eventually.
return HighResTimeStamp::now();
}
HighResDuration NativePerformance::timeOrigin(jsi::Runtime& /*rt*/) {
// This is not spec-compliant, as this is an approximation from Unix epoch to
// system boot, instead of a precise duration from Unix epoch to app startup.
return HighResTimeStamp::unsafeOriginFromUnixTimeStamp();
}
void NativePerformance::reportMark(
jsi::Runtime& rt,
const std::string& name,
@@ -88,6 +88,9 @@ class NativePerformance : public NativePerformanceCxxSpec<NativePerformance> {
// https://www.w3.org/TR/hr-time-3/#now-method
HighResTimeStamp now(jsi::Runtime& rt);
// https://www.w3.org/TR/hr-time-3/#timeorigin-attribute
HighResDuration timeOrigin(jsi::Runtime& rt);
#pragma mark - User Timing Level 3 functions (https://w3c.github.io/user-timing/)
void reportMark(
@@ -201,6 +201,11 @@ class HighResTimeStamp {
return HighResTimeStamp(chronoNow());
}
static HighResDuration unsafeOriginFromUnixTimeStamp() noexcept {
static auto origin = computeUnsafeOriginFromUnixTimeStamp();
return origin;
}
static constexpr HighResTimeStamp min() noexcept {
return HighResTimeStamp(std::chrono::steady_clock::time_point::min());
}
@@ -285,6 +290,13 @@ class HighResTimeStamp {
std::chrono::steady_clock::time_point chronoTimePoint_;
static HighResDuration computeUnsafeOriginFromUnixTimeStamp() noexcept {
auto systemNow = std::chrono::system_clock::now();
auto steadyNow = std::chrono::steady_clock::now();
return HighResDuration(
systemNow.time_since_epoch() - steadyNow.time_since_epoch());
}
#ifdef REACT_NATIVE_DEBUG
static std::function<std::chrono::steady_clock::time_point()>&
getTimeStampProvider() {
+4 -4
View File
@@ -199,11 +199,11 @@ declare interface EventCounts {
}
declare class Performance {
+eventCounts: EventCounts;
+timeOrigin: DOMHighResTimeStamp;
clearMarks(name?: string): void;
clearMeasures(name?: string): void;
eventCounts: EventCounts;
getEntries: (
options?: PerformanceEntryFilterOptions,
) => Array<PerformanceEntry>;
@@ -215,7 +215,7 @@ declare class Performance {
startMarkOrOptions?: string | PerformanceMeasureOptions,
endMark?: string,
): PerformanceMeasure;
now: () => DOMHighResTimeStamp;
now(): DOMHighResTimeStamp;
toJSON(): string;
}
@@ -63,6 +63,7 @@ const cachedReportMeasure = NativePerformance.reportMeasure;
const cachedGetMarkTime = NativePerformance.getMarkTime;
const cachedNativeClearMarks = NativePerformance.clearMarks;
const cachedNativeClearMeasures = NativePerformance.clearMeasures;
let cachedTimeOrigin: ?DOMHighResTimeStamp;
const MARK_OPTIONS_REUSABLE_OBJECT: PerformanceMarkOptions = {
startTime: 0,
@@ -139,6 +140,24 @@ export default class Performance {
});
}
/**
* Returns the high resolution timestamp that is used as the baseline for
* performance-related timestamps.
* https://developer.mozilla.org/en-US/docs/Web/API/Performance/timeOrigin
*/
get timeOrigin(): DOMHighResTimeStamp {
if (cachedTimeOrigin == null) {
if (NativePerformance.timeOrigin) {
cachedTimeOrigin = NativePerformance?.timeOrigin();
} else {
// Very naive polyfill.
cachedTimeOrigin = Date.now() - getCurrentTimeStamp();
}
}
return cachedTimeOrigin;
}
mark(
markName: string,
markOptions?: PerformanceMarkOptions,
@@ -22,4 +22,37 @@ describe('Performance', () => {
return new PerformanceEntry();
}).toThrow("Failed to construct 'PerformanceEntry': Illegal constructor");
});
describe('now', () => {
it('provides increasing timestamps since boot time', () => {
const first = performance.now();
const second = performance.now();
const third = performance.now();
expect(typeof first).toBe('number');
expect(typeof second).toBe('number');
expect(typeof third).toBe('number');
expect(first).toBeGreaterThan(0);
expect(second).toBeGreaterThan(first);
expect(third).toBeGreaterThan(second);
});
});
describe('timeOrigin', () => {
it('allows moving timestamps to Unix epoch', () => {
// We need to truncate timestamps because `Date.now()` only provides
// integer millisecond precision.
const adjustedMonotonicTime = Math.trunc(
performance.now() + performance.timeOrigin,
);
const wallTime = Date.now();
const adjustedMonotonicTimeAfter = Math.trunc(
performance.now() + performance.timeOrigin,
);
expect(wallTime).toBeGreaterThanOrEqual(adjustedMonotonicTime);
expect(adjustedMonotonicTimeAfter).toBeGreaterThanOrEqual(wallTime);
});
});
});
@@ -54,6 +54,8 @@ export type PerformanceObserverInit = {
export interface Spec extends TurboModule {
+now: () => number;
+timeOrigin?: () => number;
+reportMark: (name: string, startTime: number, entry: mixed) => void;
+reportMeasure: (
name: string,