Skip to content
Open
2 changes: 1 addition & 1 deletion scripts/build/deps/webkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// -lto variants built with ThinLTO (per-module summaries for cross-language
// importing), and the Windows ICU data table filtered + per-item zstd
// compressed (lazily decompressed via bun_icu_decompress.cpp).
export const WEBKIT_VERSION = "4895f45dfbd0d1226c4d41799887bc0ecb9f341b";
export const WEBKIT_VERSION = "autobuild-preview-pr-304-36986ac9";
Comment thread
robobun marked this conversation as resolved.
Outdated

/**
* WebKit (JavaScriptCore) — the JS engine.
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/node/node_crypto_binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,8 +517,8 @@ pub mod random {
}
}

// jsDateNow() is exactly what JS Date.now() returns, so the embedded
// timestamp is never behind a Date.now() sample taken by the caller.
// Same clock source and UUID7 path as Bun.randomUUIDv7(); only the
// option validation and disableEntropyCache differ.
let now_ms = global.js_date_now().max(0.0) as u64;
let mut entropy = [0u8; 10];
if disable_entropy_cache {
Expand Down
5 changes: 2 additions & 3 deletions src/runtime/webcore/Blob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5710,9 +5710,8 @@ pub fn jsdom_file_construct_(
}

if !set_last_modified {
// `lastModified` should be the current date in milliseconds if unspecified.
blob.last_modified
.set(bun_core::time::milli_timestamp() as f64);
// File API spec: default is "the equivalent of Date.now()".
blob.last_modified.set(global_this.js_date_now());
Comment thread
robobun marked this conversation as resolved.
}

if blob.content_type_slice().is_empty() {
Expand Down
7 changes: 6 additions & 1 deletion src/runtime/webcore/Crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,12 @@
.unwrap();
}

break 'brk u64::try_from(bun_core::time::milli_timestamp().max(0)).expect("int cast");
// jsDateNow() is the exact value JS Date.now() would return (same
// precise system clock as milli_timestamp() on every platform since
// oven-sh/WebKit#304, plus any setSystemTime() override), so a caller
// bracketing this call with Date.now() always observes
// before <= embedded-timestamp <= after.
break 'brk global.js_date_now().max(0.0) as u64;

Check warning on line 255 in src/runtime/webcore/Crypto.rs

View check run for this annotation

Claude / Claude Code Review

New comment exceeds CLAUDE.md 3-line limit

This comment is 5 lines; CLAUDE.md rule 13 caps code comments at 3 lines max. The `oven-sh/WebKit#304` reference is also bug history, which per REVIEW.md belongs in the PR description rather than the code. It could be condensed to e.g. `// js_date_now() is exactly Date.now() (same precise clock + any setSystemTime override), so a caller bracketing with Date.now() always sees before <= ts <= after.`
Comment thread
robobun marked this conversation as resolved.
Outdated
};

// SAFETY: `bun_vm()` never returns null for a Bun-owned global.
Expand Down
76 changes: 75 additions & 1 deletion test/js/bun/util/randomUUIDv7.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
import { bunEnv, bunExe, isWindows } from "harness";

describe("randomUUIDv7", () => {
test("basic", () => {
Expand Down Expand Up @@ -210,4 +210,78 @@
expect(Number(stdout.trim())).toBeGreaterThan(1);
expect(exitCode).toBe(0);
});

// https://github.com/oven-sh/WebKit/pull/304
test("default timestamp is bracketed by Date.now()", async () => {
// Before oven-sh/WebKit#304, Windows Date.now() (WTF QPC-interpolated) ran
// up to ~1ms ahead of the native precise clock the runtime read for default
// timestamps, so before > embedded-timestamp in ~80% of samples. With that
// fixed, Bun.randomUUIDv7 / crypto.randomUUIDv7 / File.lastModified all
// default to global.js_date_now(), which is Date.now() exactly.
// Subprocess keeps the process-global UUIDv7 last-timestamp untouched.
const N = isWindows ? 50_000 : 5_000;
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const crypto = require("node:crypto");
const tsOf = buf => buf.readUIntBE(0, 6);
const tsOfHex = s => parseInt(s.replaceAll("-", "").slice(0, 12), 16);
let bad = { bun: null, node: null, file: null };
for (let i = 0; i < ${N}; i++) {
const before = Date.now();
const b = tsOf(Bun.randomUUIDv7("buffer"));
const c = tsOfHex(crypto.randomUUIDv7());
const f = new File([], "x").lastModified;
const after = Date.now();
if (bad.bun === null && !(before <= b && b <= after)) bad.bun = { i, before, b, after };
if (bad.node === null && !(before <= c && c <= after)) bad.node = { i, before, c, after };
if (bad.file === null && !(before <= f && f <= after)) bad.file = { i, before, f, after };
if (bad.bun && bad.node && bad.file) break;

Check failure on line 241 in test/js/bun/util/randomUUIDv7.test.ts

View check run for this annotation

Claude / Claude Code Review

Bracketing test upper bound can flake via UUIDv7 counter rollover

The upper-bound checks `b <= after` / `c <= after` assert an invariant `UUID7::init` does not guarantee: on 12-bit counter rollover (src/jsc/uuid.rs:132-137) it bumps the stored timestamp by +1ms, so if this loop sustains >~1024 iterations per real ms (2 UUID7 calls each) the embedded timestamp will legitimately exceed `after`. The lower bound is what oven-sh/WebKit#304 fixes — either give the UUID upper bounds a small slack (e.g. `b <= after + 2`) or keep the strict upper bound only for `File.l
Comment thread
robobun marked this conversation as resolved.
}
console.log(JSON.stringify(bad));
`,
],
env: bunEnv,
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");

Check warning on line 250 in test/js/bun/util/randomUUIDv7.test.ts

View check run for this annotation

Claude / Claude Code Review

New tests assert stderr is exactly empty

nit: REVIEW.md's subprocess-test guidance says to never assert stderr is exactly empty (ASAN/debug builds emit benign warnings) and to instead assert a combined `{ stdout, stderr, exitCode }` object — this applies here and at line 278. That said, the five pre-existing subprocess tests in this file use the identical pattern, so this matches local convention and the practical flake risk is low given `bunEnv` sets `BUN_DEBUG_QUIET_LOGS=1`; not blocking.
Comment thread
robobun marked this conversation as resolved.
expect(JSON.parse(stdout)).toEqual({ bun: null, node: null, file: null });
expect(exitCode).toBe(0);
});

test("default timestamp respects setSystemTime()", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const { setSystemTime } = require("bun:test");
const crypto = require("node:crypto");
const tsOf = s => parseInt(s.replaceAll("-", "").slice(0, 12), 16);
const pin = 1_700_000_000_000;
setSystemTime(pin);
console.log(JSON.stringify({
dateNow: Date.now(),
bun: tsOf(Bun.randomUUIDv7()),
node: tsOf(crypto.randomUUIDv7()),
file: new File([], "x").lastModified,
}));
`,
],
env: bunEnv,
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(JSON.parse(stdout)).toEqual({
dateNow: 1_700_000_000_000,
bun: 1_700_000_000_000,
node: 1_700_000_000_000,
file: 1_700_000_000_000,
});
expect(exitCode).toBe(0);
});
});
Loading