From 1ff52630f12e01e013aed2593f6175faa33135ef Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Mon, 14 Sep 2026 05:48:22 -0500 Subject: [PATCH 1/2] Performance: keep the time origin per environment performance.now() measured from one process-wide start time that every Initialize() rewrote. With Workers each runtime initializes the polyfill on its own thread, so ThreadSanitizer reports the write race, and every new worker also moved the main realm's clock backwards. Capture the origin per environment in the now() closure, as each realm has its own time origin on the web, and use steady_clock, which cannot step. (cherry picked from commit 2391b0d3cd8e7261f0a4bca3a55f95af9259c8bc) --- Polyfills/Performance/Source/Performance.cpp | 22 +++++++------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/Polyfills/Performance/Source/Performance.cpp b/Polyfills/Performance/Source/Performance.cpp index 6fa142d6..b2e4a729 100644 --- a/Polyfills/Performance/Source/Performance.cpp +++ b/Polyfills/Performance/Source/Performance.cpp @@ -6,16 +6,6 @@ namespace { constexpr const char* JS_INSTANCE_NAME{"performance"}; - - // Store the start time when the polyfill is initialized - std::chrono::high_resolution_clock::time_point g_startTime; - - Napi::Value Now(const Napi::CallbackInfo& info) - { - auto now = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration(now - g_startTime); - return Napi::Number::New(info.Env(), duration.count()); - } } namespace Babylon::Polyfills::Performance @@ -24,9 +14,6 @@ namespace Babylon::Polyfills::Performance { Napi::HandleScope scope{env}; - // Initialize the start time - g_startTime = std::chrono::high_resolution_clock::now(); - auto performance = env.Global().Get(JS_INSTANCE_NAME).As(); if (!performance.IsUndefined()) { @@ -36,6 +23,13 @@ namespace Babylon::Polyfills::Performance performance = Napi::Object::New(env); env.Global().Set(JS_INSTANCE_NAME, performance); - performance.Set("now", Napi::Function::New(env, Now, "now")); + // The time origin belongs to this environment, as it does to each browsing context and + // worker: a process-wide origin would be reset by every runtime (worker) that initializes + // the polyfill, moving performance.now() backwards elsewhere and racing with it. + const auto timeOrigin = std::chrono::steady_clock::now(); + performance.Set("now", Napi::Function::New(env, [timeOrigin](const Napi::CallbackInfo& info) { + const auto elapsed = std::chrono::duration(std::chrono::steady_clock::now() - timeOrigin); + return Napi::Number::New(info.Env(), elapsed.count()); + }, "now")); } } From 5c6cd00c57c86874d02a8dbdd24d73aaabc653c6 Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Mon, 14 Sep 2026 07:04:18 -0500 Subject: [PATCH 2/2] Tests: cover the per-environment performance.now() origin Two runtimes: after the second initializes the polyfill, the first one's performance.now() must not go backwards. Fails against the process-wide origin (the second Initialize reset it to zero). --- Tests/UnitTests/Shared/Shared.cpp | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Tests/UnitTests/Shared/Shared.cpp b/Tests/UnitTests/Shared/Shared.cpp index 1c7e9ff7..f11b9edc 100644 --- a/Tests/UnitTests/Shared/Shared.cpp +++ b/Tests/UnitTests/Shared/Shared.cpp @@ -829,6 +829,40 @@ TEST(NodeApi, AdjacentEscapableScopesEscapeIndependently) #endif +TEST(Performance, TimeOriginIsPerEnvironment) +{ + // Regression: performance.now() measured from one process-wide start time that every + // Initialize() rewrote, so bringing up a second runtime moved the first one's clock back to + // zero. Each environment keeps its own origin now, so the first clock stays monotonic. + const auto initialize = [](Babylon::AppRuntime& runtime) { + std::promise done; + runtime.Dispatch([&done](Napi::Env env) { + Babylon::Polyfills::Performance::Initialize(env); + done.set_value(); + }); + done.get_future().get(); + }; + const auto now = [](Babylon::AppRuntime& runtime) { + std::promise value; + runtime.Dispatch([&value](Napi::Env env) { + auto performance = env.Global().Get("performance").As(); + value.set_value(performance.Get("now").As().Call(performance, {}).As().DoubleValue()); + }); + return value.get_future().get(); + }; + + Babylon::AppRuntime first{}; + initialize(first); + std::this_thread::sleep_for(std::chrono::milliseconds{50}); + const double before = now(first); + + Babylon::AppRuntime second{}; + initialize(second); + const double after = now(first); + + EXPECT_GE(after, before); +} + int RunTests() { testing::InitGoogleTest();