diff --git a/src/jsc/bindings/ZigGlobalObject.cpp b/src/jsc/bindings/ZigGlobalObject.cpp index b2b5535f071..701c8c63472 100644 --- a/src/jsc/bindings/ZigGlobalObject.cpp +++ b/src/jsc/bindings/ZigGlobalObject.cpp @@ -3378,8 +3378,10 @@ RefPtr GlobalObject::performance() { if (!m_performance) { auto* context = this->scriptExecutionContext(); - double nanoTimeOrigin = Bun__readOriginTimerStart(this->bunVM()); - auto timeOrigin = MonotonicTime::fromRawSeconds(nanoTimeOrigin / 1000.0); + // WTF MonotonicTime value at the instant bunVM()->origin_timer was captured. + // Raw variant: must ignore the fake-timers override of performance.now(). + auto elapsed = Seconds { static_cast(Bun__readOriginTimerRaw(this->bunVM())) / 1000000000.0 }; + auto timeOrigin = MonotonicTime::now() - elapsed; m_performance = Performance::create(context, timeOrigin); } diff --git a/src/jsc/bindings/webcore/Event.cpp b/src/jsc/bindings/webcore/Event.cpp index 40a6f418efb..9a2d72b72f7 100644 --- a/src/jsc/bindings/webcore/Event.cpp +++ b/src/jsc/bindings/webcore/Event.cpp @@ -29,9 +29,10 @@ #include "EventPath.h" #include "EventTarget.h" // #include "InspectorInstrumentation.h" -// #include "Performance.h" +#include "Performance.h" // #include "UserGestureIndicator.h" // #include "WorkerGlobalScope.h" +#include "ZigGlobalObject.h" #include #include #include @@ -162,18 +163,8 @@ void Event::setUnderlyingEvent(Event* underlyingEvent) DOMHighResTimeStamp Event::timeStampForBindings(ScriptExecutionContext& context) const { - // TODO: - return 0.0; - // Performance* performance = nullptr; - // if (is(context)) - // performance = &downcast(context).performance(); - // else if (auto* window = downcast(context).domWindow()) - // performance = &window->performance(); - - // if (!performance) - // return 0; - - // return std::max(performance->relativeTimeFromTimeOriginInReducedResolution(m_createTime), 0.); + auto performance = defaultGlobalObject(context.jsGlobalObject())->performance(); + return std::max((m_createTime - performance->monotonicTimeOrigin()).milliseconds(), 0.0); } void Event::resetBeforeDispatch() diff --git a/src/jsc/bindings/webcore/Performance.cpp b/src/jsc/bindings/webcore/Performance.cpp index f5250082177..528d9009bfe 100644 --- a/src/jsc/bindings/webcore/Performance.cpp +++ b/src/jsc/bindings/webcore/Performance.cpp @@ -85,8 +85,7 @@ DOMHighResTimeStamp Performance::now() const DOMHighResTimeStamp Performance::timeOrigin() const { - // return reduceTimeResolution(m_timeOrigin.approximateWallTime().secondsSinceEpoch()).milliseconds(); - return m_timeOrigin.secondsSinceEpoch().milliseconds(); + return Bun__readOriginTimerStart(bunVM(scriptExecutionContext()->vm())); } // ReducedResolutionSeconds Performance::nowInReducedResolutionSeconds() const diff --git a/src/jsc/bindings/webcore/Performance.h b/src/jsc/bindings/webcore/Performance.h index da4c463554b..49146ff2027 100644 --- a/src/jsc/bindings/webcore/Performance.h +++ b/src/jsc/bindings/webcore/Performance.h @@ -45,6 +45,7 @@ #include extern "C" uint64_t Bun__readOriginTimer(void*); +extern "C" uint64_t Bun__readOriginTimerRaw(void*); extern "C" double Bun__readOriginTimerStart(void*); namespace JSC { @@ -123,6 +124,7 @@ class Performance final : public RefCounted, public ContextDestruct DOMHighResTimeStamp relativeTimeFromTimeOriginInReducedResolution(MonotonicTime) const; MonotonicTime monotonicTimeFromRelativeTime(DOMHighResTimeStamp) const; + MonotonicTime monotonicTimeOrigin() const { return m_timeOrigin; } ScriptExecutionContext* scriptExecutionContext() const final { return ContextDestructionObserver::scriptExecutionContext(); } diff --git a/src/jsc/virtual_machine_exports.rs b/src/jsc/virtual_machine_exports.rs index 89258ef3d57..f5f4b8e2b60 100644 --- a/src/jsc/virtual_machine_exports.rs +++ b/src/jsc/virtual_machine_exports.rs @@ -51,6 +51,12 @@ pub fn read_origin_timer(vm: &VirtualMachine) -> u64 { vm.origin_timer.elapsed().as_nanos() as u64 } +// HOST_EXPORT(Bun__readOriginTimerRaw, c) +pub fn read_origin_timer_raw(vm: &VirtualMachine) -> u64 { + // Ignores the fake-timers override; used to derive Performance::m_timeOrigin. + vm.origin_timer.elapsed().as_nanos() as u64 +} + // HOST_EXPORT(Bun__readOriginTimerStart, c) pub fn read_origin_timer_start(vm: &VirtualMachine) -> f64 { // timespce to milliseconds diff --git a/test/js/bun/util/inspect.test.js b/test/js/bun/util/inspect.test.js index e76766e7f42..9ea8f730a8e 100644 --- a/test/js/bun/util/inspect.test.js +++ b/test/js/bun/util/inspect.test.js @@ -692,7 +692,7 @@ it("CloseEvent", () => { code: 1000, reason: "Normal", }); - expect(Bun.inspect(closeEvent)).toMatchInlineSnapshot(` + expect(Bun.inspect(closeEvent).replace(/timeStamp: [\d.]+,/, "timeStamp: 0,")).toMatchInlineSnapshot(` "CloseEvent { isTrusted: false, wasClean: false, @@ -771,7 +771,7 @@ it("CustomEvent", () => { bubbles: true, cancelable: true, }); - expect(Bun.inspect(customEvent)).toMatchInlineSnapshot(` + expect(Bun.inspect(customEvent).replace(/timeStamp: [\d.]+,/, "timeStamp: 0,")).toMatchInlineSnapshot(` "CustomEvent { isTrusted: false, detail: { diff --git a/test/js/web/web-globals.test.js b/test/js/web/web-globals.test.js index df0f2badc3d..4c79f6615ed 100644 --- a/test/js/web/web-globals.test.js +++ b/test/js/web/web-globals.test.js @@ -112,6 +112,49 @@ test("MessageEvent", () => { expect(called).toBe(true); }); +test("Event.prototype.timeStamp", async () => { + await using proc = spawn({ + cmd: [ + bunExe(), + "-e", + ` + while (performance.now() < 10) {} + const before = performance.now(); + const samples = []; + samples.push(new Event("x").timeStamp); + samples.push(new CustomEvent("x").timeStamp); + samples.push(new MessageEvent("x").timeStamp); + samples.push(new ErrorEvent("x").timeStamp); + const target = new EventTarget(); + target.addEventListener("go", e => samples.push(e.timeStamp)); + target.dispatchEvent(new Event("go")); + const ac = new AbortController(); + ac.signal.addEventListener("abort", e => samples.push(e.timeStamp)); + ac.abort(); + const after = performance.now(); + const ev = new Event("stable"); + const first = ev.timeStamp; + while (performance.now() < after + 5) {} + console.log(JSON.stringify({ before, after, samples, first, second: ev.timeStamp })); + `, + ], + env: bunEnv, + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + const { before, after, samples, first, second } = JSON.parse(stdout); + expect(samples.length).toBe(6); + // timeStamp is relative to performance.timeOrigin. 1ms of slack covers the + // one-time gap between the two clock samples that derive m_timeOrigin. + for (const ts of samples) { + expect(ts).toBeGreaterThan(before - 1); + expect(ts).toBeLessThan(after + 1); + } + expect(first).toBeGreaterThanOrEqual(samples[samples.length - 1]); + expect(second).toBe(first); + expect({ stderr, exitCode }).toEqual({ stderr: expect.any(String), exitCode: 0 }); +}); + it("crypto.getRandomValues", () => { var foo = new Uint8Array(32);