From 529f55c8c933fd314f199b0445eb0bc0e8e3dfd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Wed, 10 Sep 2025 04:48:55 -0700 Subject: [PATCH] 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 --- .../webperformance/NativePerformance.cpp | 9 +++++ .../webperformance/NativePerformance.h | 3 ++ .../ReactCommon/react/timing/primitives.h | 12 +++++++ packages/react-native/flow/bom.js.flow | 8 ++--- .../webapis/performance/Performance.js | 19 +++++++++++ .../__tests__/Performance-itest.js | 33 +++++++++++++++++++ .../performance/specs/NativePerformance.js | 2 ++ 7 files changed, 82 insertions(+), 4 deletions(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index 9ba5b10abcb..eb4e9f6b5de 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -131,9 +131,18 @@ NativePerformance::NativePerformance(std::shared_ptr 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, diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h index d5be089cc07..21033caf956 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h @@ -88,6 +88,9 @@ class NativePerformance : public NativePerformanceCxxSpec { // 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( diff --git a/packages/react-native/ReactCommon/react/timing/primitives.h b/packages/react-native/ReactCommon/react/timing/primitives.h index d3e3097674b..6328be8b703 100644 --- a/packages/react-native/ReactCommon/react/timing/primitives.h +++ b/packages/react-native/ReactCommon/react/timing/primitives.h @@ -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& getTimeStampProvider() { diff --git a/packages/react-native/flow/bom.js.flow b/packages/react-native/flow/bom.js.flow index 9b0d4d94f49..59f402968d7 100644 --- a/packages/react-native/flow/bom.js.flow +++ b/packages/react-native/flow/bom.js.flow @@ -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; @@ -215,7 +215,7 @@ declare class Performance { startMarkOrOptions?: string | PerformanceMeasureOptions, endMark?: string, ): PerformanceMeasure; - now: () => DOMHighResTimeStamp; + now(): DOMHighResTimeStamp; toJSON(): string; } diff --git a/packages/react-native/src/private/webapis/performance/Performance.js b/packages/react-native/src/private/webapis/performance/Performance.js index 86c380111ba..827fd8d9a50 100644 --- a/packages/react-native/src/private/webapis/performance/Performance.js +++ b/packages/react-native/src/private/webapis/performance/Performance.js @@ -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, diff --git a/packages/react-native/src/private/webapis/performance/__tests__/Performance-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/Performance-itest.js index 4c8c5447744..1572338f3c0 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/Performance-itest.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/Performance-itest.js @@ -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); + }); + }); }); diff --git a/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js b/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js index 4981b1a31d5..e191d0a6ac4 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js @@ -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,