Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/jsc/bindings/ZigGlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3378,9 +3378,10 @@
{
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.
auto elapsed = Seconds { static_cast<double>(Bun__readOriginTimer(this->bunVM())) / 1000000000.0 };
auto timeOrigin = MonotonicTime::now() - elapsed;
m_performance = Performance::create(context, timeOrigin);

Check warning on line 3384 in src/jsc/bindings/ZigGlobalObject.cpp

View check run for this annotation

Claude / Claude Code Review

m_timeOrigin derivation reads fake-timer-overridden clock

`Bun__readOriginTimer` short-circuits to `vm.overridden_performance_now` when `jest.useFakeTimers()` is active, so if the first `performance`/`event.timeStamp` access happens under fake timers, `m_timeOrigin` is derived from the fake offset instead of real elapsed time and every `event.timeStamp` is permanently skewed — even after `useRealTimers()`. The comment on line 3381 states the intended invariant; the helper doesn't guarantee it. Add/use a raw export that reads `vm.origin_timer.elapsed()`
Comment thread
robobun marked this conversation as resolved.
Outdated
}

return m_performance;
Expand Down
17 changes: 4 additions & 13 deletions src/jsc/bindings/webcore/Event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <wtf/HexNumber.h>
#include <wtf/TZoneMallocInlines.h>
#include <wtf/text/StringBuilder.h>
Expand Down Expand Up @@ -162,18 +163,8 @@ void Event::setUnderlyingEvent(Event* underlyingEvent)

DOMHighResTimeStamp Event::timeStampForBindings(ScriptExecutionContext& context) const
{
// TODO:
return 0.0;
// Performance* performance = nullptr;
// if (is<WorkerGlobalScope>(context))
// performance = &downcast<WorkerGlobalScope>(context).performance();
// else if (auto* window = downcast<Document>(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()
Expand Down
3 changes: 1 addition & 2 deletions src/jsc/bindings/webcore/Performance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/jsc/bindings/webcore/Performance.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ class Performance final : public RefCounted<Performance>, public ContextDestruct

DOMHighResTimeStamp relativeTimeFromTimeOriginInReducedResolution(MonotonicTime) const;
MonotonicTime monotonicTimeFromRelativeTime(DOMHighResTimeStamp) const;
MonotonicTime monotonicTimeOrigin() const { return m_timeOrigin; }

ScriptExecutionContext* scriptExecutionContext() const final { return ContextDestructionObserver::scriptExecutionContext(); }

Expand Down
4 changes: 2 additions & 2 deletions test/js/bun/util/inspect.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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: {
Expand Down
43 changes: 43 additions & 0 deletions test/js/web/web-globals.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Loading