Skip to content

Upgrade WebKit to 2603e9eb41f0 - #34373

Merged
Jarred-Sumner merged 19 commits into
mainfrom
claude/webkit-upgrade-c8b6308aaa69
Jul 20, 2026
Merged

Upgrade WebKit to 2603e9eb41f0#34373
Jarred-Sumner merged 19 commits into
mainfrom
claude/webkit-upgrade-c8b6308aaa69

Conversation

@robobun

@robobun robobun commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Upgrades WebKit to upstream 2603e9eb41f0 via oven-sh/WebKit#300 (merged as oven-sh/WebKit@a0e65bf298).

Binding updates for upstream API changes

  • DeferredWorkTimer::Ticket refactor: TicketData renamed to Ticket and is now ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr; addPendingWork returns WeakTicket; scheduleWorkSoon became scheduleWorkSoonIfActive. JSCTaskScheduler and BunClientData hook registration updated to the new Ref<Ticket>&& / Ticket& signatures.
  • DateCache timezone reset: resetIfNecessarySlow() became clearForTimeZoneChange(); lastTimeZoneID moved to WTF::lastTimeZoneID() / WTF::timeZoneDidChange(). Callers in ZigGlobalObject, JSEnvironmentVariableMap, and BunJSCModule now bump WTF::timeZoneDidChange() then clear the per-VM cache.
  • SparseArrayValueMap compaction: now a HashSet<SparseArrayEntry>; iterator yields the entry directly (.key became .index()).
  • ArrayBuffer::isLocked() folded into isDetachable().

WebKit upgrade: upstream range d81bcc3d833c..2603e9eb41f0 (211 commits in JSC/WTF/bmalloc)

Notable changes

JIT / DFG / FTL / B3 / Air

  • 6d70d346c664 Add LoadMegamorphicGetter IC
  • 0d5d36b5f94e Add RegExpExecSticky DFG node
  • 23903f1a1cc7 Add AtomString Array loop in DFG / FTL
  • 082efc20347d Handle String#trim / trimStart / trimEnd in DFG / FTL
  • eeb2440ba085 Inline StringSlice rope construction in FTL
  • 752e7e8b0d05 Convert HasOwnProperty on current for-in key to EnumeratorHasOwnProperty
  • 7f538702bc4e Prove resolved values non-thenable for NewResolvedPromise in DFG constant folding
  • 338e02c8838a Fix DFG strength reduction folding compiler-thread RegExp failure into a definite no-match
  • d801f1e36011 / 674917cb5f4d / 71208eb12b5e Shrink Air::Arg to 16 bytes, Air::Inst to 64 bytes
  • 0f2107b6d205 Remove Air::lowerMacros; 5d5a2e3a036f single-pass Air::simplifyCFG
  • ebe71899e170 Adjust JIT tier-up thresholds for Macs with fewer P-cores

GC / Heap

  • 5ecce0f050d2 / 6fddaa00b614 Manually pipeline memory fetching for GC marking (SlotVisitor prefetch)
  • f0df03efe38c Tweak number of GC threads on Apple platforms
  • 904ba5171187 Strong<T> is now pointer-sized
  • b80bcf43bdce SparseArrayValueMap 33% more compact using HashSet

Runtime / Promises / Async

  • d11c8c7ba675 / 3ac446bb73b3 Polymorphic MicrotaskCallCache for microtask calls, threaded through more functions
  • 5f65bd95a4b6 Cooperative driving of async generators
  • 74cc52330394 Use JSSlimPromiseReaction for JSAsyncGenerator queue
  • 793fd5461d93 async-generator fast consume inserts ExitOK
  • 707b16ac07ea PromiseResolveThenableJob rejects when SpeciesConstructor throws
  • 8490d02c5dcc AsyncFromSyncIterator closes correctly on rejected value promise
  • 8e15aeeee3bc Improve exception unwinding performance
  • 3bd19f0a328e Move object structured-cloning code into JSC

Strings / RegExp / YARR

  • 4e93b58dfd8e Array#join writes rope elements directly into result buffer
  • 485bcf1b176a Fast path for Array#concat with multiple arguments
  • bef2808f4db2 Parse replacement template once in String#replace (global + $ substitutions)
  • 5031c9001ee2 Implement String.raw in C++
  • 7a764d7acc54 Yarr collects BM info for fixed-count terms; e13280f56c44 rearrange YarrJIT registers for regT3
  • 630d6be8962a Fix YARR EOL string-list optimization with multiline flag

WebAssembly

  • c7bfc7c0ca52 Add B3::eliminateWasmGCAllocations phase; a2bb3b59f1cc fold WasmGC object comparisons
  • 1152f3717e9b Implement Table64 JS API
  • 7f065ab4abdb Fix streaming instantiation using result promise as import object when imports omitted
  • 228685d7d2a3 Names in Error stacktrace for instantiateStreaming modules
  • bccb52eace5b / f854abcc8468 BBQ strength-reduce signed div/rem by negative pow2; mask before shr
  • fb30bac88bb4 Wasm debugger: replace polling STW participation with VMBlockingScope

Temporal / Intl

  • 23eb4f85f358 Enable Temporal object by default
  • cfda643c04c0 / 2fb282a82538 / 170ee738c6f1 / bdd3b71e35c2 / d3c7fd25598a / a0433cfeb9ab / 1482a7ad4add / 75d9be0204db / f9f65c7ff4f6 Multiple Temporal spec-alignment & calendar fixes (ZonedDateTime, PlainDateTime, PlainYearMonth, PlainMonthDay, non-ISO calendars, Duration rounding)
  • 6ea58a42a909 Add useIntlEraMonthcode option, narrow AvailableCalendars
  • 824d2b29e233 Use modern unit/ skeleton in Intl.NumberFormat
  • cde1f2154e51 Use IntlCache for Temporal pattern generation in Intl.DateTimeFormat

Parser / Bytecode / LLInt

  • d981384b263d Add TokNumberInt32 to LiteralParser to remove number-conversion overhead
  • 1994bc0cc3e2 Skip unreachable implicit return for arrow function expression bodies
  • c0e48d007a10 LLInt records IterationMode::Generic in op_iterator_next / op_async_iterator_next
  • 69ab6c6330aa Fix CLoop with loadh / storeh

WTF / bmalloc

  • 3aa0615a5649 Fix WTF::divideRoundedUp overflow near type max
  • 39dc095e8f0e Fix calculateBase64EncodedSize() truncation for >4 GB inputs
  • b420bd341074 Fix pairIntHash bit extraction degrading hash distribution
  • 6cccd5ef64ee / 82b692fc69fc / dce31d0f206f libpas: fix OoB paths in MAR code, segregated-heap race, zeroed-alloc bug

Bun-relevant API changes

  • DeferredWorkTimer Ticket refactor (15c117a5444a): TicketData is now ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr. addPendingWork() returns a WeakTicket (ThreadSafeWeakPtr<TicketData>) instead of a raw Ticket. scheduleWorkSoon(Ticket, Task&&) is replaced by scheduleWorkSoonIfActive(WeakTicket&&, Task&&) / mustScheduleWorkSoonIfActive(...), and cancelPendingWork() now takes a WeakTicket&&. Callers that held a raw Ticket must be updated to hold WeakTicket and use the *IfActive variants.
  • Time-zone change detection moved to WTF (65d36770c7a6, a01ee718ca4d): JSDateMath now checks WTF::lastTimeZoneID() / WTF::timeZoneDidChange() guarded by USE(TIME_ZONE_CHANGE_NOTIFICATIONS). Ports without notifications re-read the host zone on each cache reset. Embedders that previously called DateCache::timeZoneChanged() directly should route through the new WTF hooks.
  • Microtask call cache (d11c8c7ba675, 3ac446bb73b3, 5f65bd95a4b6): callMicrotask() in JSMicrotask now takes an additional MicrotaskCallCache* parameter. AsyncFunctionResume's body was factored out into asyncFunctionGeneratorBodyCall() which also threads the cache. Any Bun code calling callMicrotask / resuming async functions directly needs the extra argument.
  • PropertyAttribute::LastAttribute sentinel (b80bcf43bdce): the PropertyAttribute enum gained a LastAttribute sentinel used by the new compact SparseArrayValueMap encoding. static_asserts against attribute bit-width may need updating.
  • New generated header JSCWebPreferenceOptions.h (3af9d4073d75): JSC feature flags are now driven by UnifiedWebPreferences.yaml; the build emits a new JSCWebPreferenceOptions.h that must be on the include path / in generated-sources lists.

[decide:webkit] gate passed · iteration 30 · 13 files touched

fails on main (without fix)
ASAN without fix: BUILD FAILED (no junit output)
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/jsc/temporal-global.test.ts test/js/node/async_hooks/AsyncLocalStorage.test.ts test/js/web/workers/structured-clone.test.ts
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
ninja: Entering directory `/workspace/bun/build/debug'
[1/161] gen cpp.rs (cppbind)
[2/161] gen generated_host_exports.rs
generated_host_exports.rs: 91 exports (host=3, lazy=10, generic=78, rust=0); 244 extern-C blocks audited
[2/161] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-05-06-x86_64-unknown-linux-gnu unchanged - rustc 1.97.0-nightly (e95e73209 2026-05-05)

[3/161] pch pch/root-pch.h.hxx.pch
FAILED: pch/root-pch.h.hxx.pch 
/usr/lib/llvm-21/bin/clang++ -march=haswell -O0 -g3 -gz=zstd -glldb -fsanitize=address -fno-exceptions -fno-c++-static-destructors -fno-rtti -fno-omit-frame-pointer -mno-omit-leaf-fra
... (truncated)

release without fix: 2 skipped
bun test v1.4.0-canary.1 (baba43f6e)

test/js/bun/jsc/temporal-global.test.ts:
(pass) Temporal is not exposed by default [25.76ms]
(pass) Temporal is exposed when BUN_JSC_useTemporal=1 [24.72ms]

test/js/web/workers/structured-clone.test.ts:
(pass) structuredClone > primitive undefined [0.08ms]
(pass) structuredClone > primitive null
(pass) structuredClone > primitive true
(pass) structuredClone > primitive false
(pass) structuredClone > primitive string, empty string
(pass) structuredClone > primitive string, lone high surrogate
(pass) structuredClone > primitive string, lone low surrogate
(pass) structuredClone > primitive string, NUL
(pass) structuredClone > primitive string, astral character
(pass) structuredClone > primitive number, 0.2
(pass) structuredClone > primitive number, 0
(pass) structuredClone > primitive number, -0
(pass) structuredClone > primitive number, NaN
(pass) structuredClone > primitive number, Infinity
(pass) structuredClone > primitive number, -Infinity
(pass) structuredClone > primitive number, 9007199254740992
(pass) structuredClone > primitive number, -9007199254740992 [0.01ms]
(pass) structuredClone > primitive number, 9007199254740994
... (truncated)
passes on PR (with fix)
ASAN with fix: 2 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/jsc/temporal-global.test.ts test/js/node/async_hooks/AsyncLocalStorage.test.ts test/js/web/workers/structured-clone.test.ts
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (f6a634ca3)

test/js/bun/jsc/temporal-global.test.ts:
(pass) Temporal is not exposed by default [1219.05ms]
(pass) Temporal is exposed when BUN_JSC_useTemporal=1 [1212.83ms]

test/js/web/workers/structured-clone.test.ts:
(pass) structuredClone > primitive undefined [3.94ms]
(pass) structuredClone > primitive null [0.64ms]
(pass) structuredClone > primitive true [0.75ms]
(pass) structuredClone > primitive false [0.48ms]
(pass) structuredClone > primitive string, empty string [0.38ms]
(pass) structuredClone > primitive string, lone high surrogate [0.38ms]
(pass) structuredClone > primitive string, lone low surrogate [0.57ms]
(pass) str
... (truncated)

release with fix: 2 skipped
$ bun scripts/build.ts --profile=release
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
[configured] bun-profile → bun (stripped) in 681ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/120] gen cpp.rs (cppbind)
[2/120] gen generated_host_exports.rs
generated_host_exports.rs: 91 exports (host=3, lazy=10, generic=78, rust=0); 244 extern-C blocks audited
[2/120] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: component rust-std is up to date

  nightly-2026-05-06-x86_64-unknown-linux-gnu unchanged - rustc 1.97.0-nightly (e95e73209 2026-05-05)

info: checking for self-update (current version: 1.29.0)
�[1m�[92m    Blocking�[0m waiting for file lock on build directory
�[1m�[92m   Compiling�[0m bun_runtime v0.0.0 (/
... (truncated)
diff hotspot
scripts/build/deps/webkit.ts                       |  2 +-
 src/jsc/bindings/BunClientData.cpp                 |  8 +--
 src/jsc/bindings/JSCTaskScheduler.cpp              | 28 ++++-----
 src/jsc/bindings/JSCTaskScheduler.h                | 10 ++--
 src/jsc/bindings/JSEnvironmentVariableMap.cpp      |  6 +-
 src/jsc/bindings/ZigGlobalObject.cpp               |  7 ++-
 src/jsc/bindings/bindings.cpp                      |  4 +-
 src/jsc/bindings/webcore/SerializedScriptValue.cpp |  9 ++-
 src/jsc/modules/BunJSCModule.h                     |  3 +-
 src/runtime/dispatch.rs                            |  2 +-
 test/js/bun/jsc/temporal-global.test.ts            | 25 ++++++++
 test/js/node/async_hooks/AsyncLocalStorage.test.ts | 69 ++++++++++++++++++++++
 test/js/web/workers/structured-clone.test.ts       | 35 +++++++++++
 13 files changed, 172 insertions(+), 36 deletions(-)

gate history · 6 passed · 12 rejected · iteration 30

evidence per changed file
file                                                reads  edits  tests
scripts/build/deps/webkit.ts                            5      9      0
src/jsc/bindings/BunClientData.cpp                      1      1      0
src/jsc/bindings/JSCTaskScheduler.cpp                   2      2      0
src/jsc/bindings/JSCTaskScheduler.h                     2      3      0
src/jsc/bindings/JSEnvironmentVariableMap.cpp           1      1      0
src/jsc/bindings/ZigGlobalObject.cpp                    2      2      0
src/jsc/bindings/bindings.cpp                           1      1      0
src/jsc/bindings/webcore/SerializedScriptValue.cpp      4      4      0
src/jsc/modules/BunJSCModule.h                          1      1      0
src/runtime/dispatch.rs                                 1      1      0
test/js/bun/jsc/temporal-global.test.ts                 1      5      0
test/js/node/async_hooks/AsyncLocalStorage.test.ts      2      2      0
test/js/web/workers/structured-clone.test.ts            3      4      0

Bumps WEBKIT_VERSION to the preview build from oven-sh/WebKit#300
(autobuild-preview-pr-300-2cfd017b) and updates bindings for the
upstream API changes in d81bcc3d833c..c8b6308aaa69:

- DeferredWorkTimer::Ticket is now ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr;
  addPendingWork returns WeakTicket, scheduleWorkSoon became
  scheduleWorkSoonIfActive. JSCTaskScheduler hooks updated to the new
  Ref<Ticket>&& / Ticket& signatures.
- DateCache::resetIfNecessarySlow() became clearForTimeZoneChange();
  lastTimeZoneID moved to WTF. Callers now bump WTF::timeZoneDidChange()
  then clear the per-VM cache.
- SparseArrayValueMap is now a HashSet of SparseArrayEntry; iterator
  yields the entry directly (.key became .index()).
- ArrayBuffer::isLocked() folded into isDetachable().

Before merging this PR, WEBKIT_VERSION must be updated to the
autobuild-<sha> tag once oven-sh/WebKit#300 is merged.
@robobun

robobun commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 2:43 AM PT - Jul 19th, 2026

@robobun, your commit f6a634c has 1 failures in Build #75723 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 34373

That installs a local version of the PR into your bun-34373 executable, so you can run:

bun-34373 --bun

@robobun

robobun commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator Author

The WebKit preview tag is now autobuild-preview-pr-300-4559ebe7 (building via run 29600813392), after re-merging upstream WebKit/WebKit main at 2603e9eb41f0 (84 more commits, 22 in JSC/WTF/bmalloc, no conflicts).

Temporal is now disabled by default in Bun (Options::useTemporal() = false in ZigGlobalObject.cpp); BUN_JSC_useTemporal=1 re-enables it.

The gate and CI will fail on fetch WebKit (prebuilt) until that release is published. Verified locally via build:local: temporal-global.test.ts (default-off + opt-in), structured-clone.test.ts (Bun-pinned buffers copy, WebAssembly.Memory buffers throw), and the full AsyncLocalStorage.test.ts suite pass with validateExceptionChecks=1.

Fixes applied to the WebKit branch:

  • ddece060: keep the flat JavaScriptCore/Headers layout on Apple.
  • a67975c4: capture/restore Bun's async context in AsyncGeneratorDriverResume.
  • eaa6af36: merge oven-sh/WebKit main (AsyncContextSwapScope, reifyStaticProperty termination, Dockerfile.windows).
  • 67898ceb: drop cocoa/TimeZoneCocoa.cpp from JSCOnly.
  • 4c5f4e80: drop the ThrowScope from setUpStaticFunctionSlot, key on the reified offset.
  • 4559ebe7: merge upstream/main at 2603e9eb41f0.

Once oven-sh/WebKit#300 is merged, WEBKIT_VERSION must be bumped from the preview tag to the resulting autobuild-<sha> before this PR is merged.

@github-actions

Copy link
Copy Markdown
Contributor

Found 11 issues this PR may fix:

  1. Temporal support (TC39 stage 3 proposal) #15853 - Temporal API is enabled by default in this JSC upgrade, directly delivering the requested TC39 Temporal support
  2. Async generator returns Promise object instead of awaited value #33185 - Upstream "cooperative async generator driving" fixes address async generators returning Promise objects instead of awaited values
  3. The string.trim() method is slow. #32495 - This JSC upgrade adds String#trim in DFG/FTL JIT, directly optimizing the slow trim() performance
  4. Node.js is 300x faster than Bun at isbot regexp #5197 - New RegExpExecSticky DFG node adds JIT optimization for sticky-flag regexp execution, targeting the regexp performance gap
  5. Intl.NumberFormat.resolvedOptions() gives differnet value than NodeJs and Deno #14712 - Intl.NumberFormat "modern skeleton" spec fixes address resolvedOptions() returning different values than Node.js/Deno
  6. Inconsistent Runtime Output for Intl.NumberFormat #6193 - Same Intl.NumberFormat spec-conformance fixes address inconsistent runtime output
  7. SIGSEGV in pas_local_allocator_refill_with_known_config on Windows — JSC bmalloc corruption during DFG JIT compilation #31291 - libpas OoB/race/zeroed-alloc fixes address SIGSEGV in pas_local_allocator_refill during DFG JIT compilation
  8. Segfault in JIT worker thread during DFG LICM phase on macOS aarch64 (Bun v1.3.11) #28842 - libpas allocator fixes address segfault in JIT worker thread during DFG LICM phase on macOS aarch64
  9. Crash in DFG JIT: Illegal instruction in pas_utils.h (WTF::Vector<DFG::AbstractValue> resize) #29412 - libpas fixes address illegal instruction crash in pas_utils.h during DFG JIT CFA phase
  10. Segfault at 0x0 in pas_lock_unlock (libpas) during Uint8Array.subarray allocation — Bun 1.3.14, macOS aarch64, long-running Claude Code session #32219 - libpas fixes address segfault at 0x0 in pas_lock_unlock during Uint8Array.subarray allocation
  11. Segfault in Bun's JIT codegen at startup (v1.3.14, Windows x64) #32684 - libpas/JIT codegen fixes address segfault in JIT codegen at startup on Windows

If this is helpful, copy the block below into the PR description to auto-close these issues on merge.

Fixes #15853
Fixes #33185
Fixes #32495
Fixes #5197
Fixes #14712
Fixes #6193
Fixes #31291
Fixes #28842
Fixes #29412
Fixes #32219
Fixes #32684

🤖 Generated with Claude Code

@robobun

robobun commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator Author

Re: the issue-finder suggestions above: #15853 is directly addressed (Temporal is enabled by default in this JSC range, covered by test/js/bun/jsc/temporal-global.test.ts); added to the PR body. The others (async generator semantics, String#trim JIT perf, Intl.NumberFormat skeleton, the libpas crash cluster) are plausible from the upstream commit log but not yet verified against the specific reproductions in those issues; I'll check them once the preview WebKit release is up and CI can actually build.

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. Upgrade WebKit to 0e86b49069a5 #33956 - Also a general WebKit upgrade (to older commit 0e86b49069a5), fully superseded by this PR's upgrade to the newer c8b6308aaa69

🤖 Generated with Claude Code

@robobun

robobun commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator Author

Re: the duplicate finder: #33956 targets upstream 0e86b49069a5, which is an ancestor of c8b6308aaa69, so this PR includes everything that one would bring in plus ~500 more upstream commits. Both touch the same binding files (JSCTaskScheduler, BunClientData, DateCache callers, etc.) for the same API refactors. Whichever one lands, the other becomes redundant.

robobun added 5 commits July 17, 2026 04:04
oven-sh/WebKit#300 was rebuilt at ddece060 to keep the flat
JavaScriptCore/Headers layout on Apple (upstream's new
USE_FRAMEWORK_BUNDLES block was redirecting it to
JavaScriptCore.framework/Headers, breaking the packaging cp).
oven-sh/WebKit#300 @ a67975c4 captures and restores Bun's async context
in the new InternalMicrotask::AsyncGeneratorDriverResume path (upstream's
cooperative async-generator driving), and fixes the
listenForTimeZoneChangeNotifications() stub guard to key on OS(DARWIN).

Adds AsyncLocalStorage.test.ts coverage for for-await over pristine
async generators (the driver fast path), interleaved stores, and the
throw-from-generator branch.
…hutdown handling

WebKit: merged 8be995561a (AsyncContextSwapScope RAII refactor),
ae5110d307 (reifyStaticProperty termination), 365cb02471 (Dockerfile.windows)
into the upgrade branch. JSMicrotask.cpp resolved by keeping upstream's
microtaskCallCache threading and asyncFunctionGeneratorBodyCall extraction on
top of the new RAII helper; AsyncGeneratorDriverResume now uses
wrapWithCurrent / unwrapContextTuple like its siblings.

Bun: merged origin/main. JSCTaskScheduler.{h,cpp} resolved by keeping the
new m_isShuttingDown gate, dropPendingTicketLocked, markShuttingDown and
Bun__deleteDeferredWorkTask from #34293, updated to the new
DeferredWorkTimer::Ticket / Ref<Ticket>&& / Ticket& API.

WEBKIT_VERSION bumped to the preview tag for the new WebKit head.
oven-sh/WebKit#300 @ 67898ceb drops cocoa/TimeZoneCocoa.cpp from the
JSCOnly build (matching upstream's pending fix for webkit.org/b/319480)
and compiles the no-op listenForTimeZoneChangeNotifications() stub on
every Bun platform.
@robobun
robobun marked this pull request as ready for review July 17, 2026 06:43
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 89c57b0d-f81a-4afd-a54c-a6c338999339

📥 Commits

Reviewing files that changed from the base of the PR and between 5ffbb1a and f6a634c.

📒 Files selected for processing (4)
  • scripts/build/deps/webkit.ts
  • src/jsc/bindings/ZigGlobalObject.cpp
  • src/jsc/bindings/bindings.cpp
  • src/jsc/bindings/webcore/SerializedScriptValue.cpp
💤 Files with no reviewable changes (1)
  • src/jsc/bindings/webcore/SerializedScriptValue.cpp

Walkthrough

Changes

Runtime compatibility

Layer / File(s) Summary
WebKit version and deferred-work tickets
scripts/build/deps/webkit.ts, src/jsc/bindings/JSCTaskScheduler.*, src/jsc/bindings/BunClientData.cpp, src/runtime/dispatch.rs
Updates the WebKit artifact tag and changes deferred-work scheduler ticket handling from TicketData to Ticket.
Timezone change cache invalidation
src/jsc/bindings/JSEnvironmentVariableMap.cpp, src/jsc/bindings/ZigGlobalObject.cpp, src/jsc/modules/BunJSCModule.h
Timezone override paths notify the runtime and clear the JSC date cache with clearForTimeZoneChange().
JSC data and sparse-array behavior
src/jsc/bindings/bindings.cpp, src/jsc/bindings/webcore/SerializedScriptValue.cpp, test/js/web/workers/structured-clone.test.ts
Sparse-array lookup uses entry indices, while pinned ArrayBuffers proceed through transfer handling and WebAssembly.Memory buffers are rejected.
Temporal configuration and coverage
src/jsc/bindings/ZigGlobalObject.cpp, test/js/bun/jsc/temporal-global.test.ts
Disables Temporal by default and tests disabled and opt-in global exposure.
Async generator context coverage
test/js/node/async_hooks/AsyncLocalStorage.test.ts
Adds AsyncLocalStorage tests for async-generator iteration and error handling.

Possibly related PRs

Suggested reviewers: jarred-sumner, cirospaciari

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: upgrading WebKit to commit 2603e9eb41f0.
Description check ✅ Passed The description covers what changed and how it was verified, though it does not use the template headings exactly.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

Comment thread src/jsc/bindings/webcore/SerializedScriptValue.cpp Outdated
Comment thread src/jsc/bindings/JSCTaskScheduler.cpp
oven-sh/WebKit#300 @ 4c5f4e80 drops the ThrowScope from
setUpStaticFunctionSlot and keys on the reified offset instead, so that
getOwnStaticPropertySlot / getOwnNonIndexPropertySlot callers on the
property-lookup fast path are not forced to add RETURN_IF_EXCEPTION (the
previous shape tripped validateExceptionChecks=1 on the ASAN lane).

SerializedScriptValue: the old isLocked() gate let a Bun-pinned
ArrayBuffer through so transferTo() took its copyTo() fallback (the
contract documented at bindings.cpp JSC__JSValue__pinArrayBuffer).
isDetachable() also fires for a nonzero pinCount, so a pinned buffer
would have thrown on structuredClone/postMessage transfer instead of
copying. Drop the gate and let transferTo() handle both.

dispatch.rs: update the stale ~Ref<TicketData> comment to ~Ref<Ticket>.
Comment thread src/jsc/bindings/webcore/SerializedScriptValue.cpp Outdated
Locks in the SerializedScriptValue.cpp divergence from upstream WebCore:
a Bun-pinned (native-borrowed) ArrayBuffer in a transfer list must fall
through to transferTo()'s copyTo() path rather than hit upstream's
!isDetachable() TypeError gate.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/build/deps/webkit.ts`:
- Line 10: Update the WEBKIT_VERSION constant to use the merged autobuild-<sha>
tag instead of the expiring autobuild-preview-pr-300-4c5f4e80 preview pin.

In `@test/js/web/workers/structured-clone.test.ts`:
- Line 459: Update the test around structuredCloneFn(...) and the zlib.deflate
invocation so the deflate promise is awaited from a finally block. Ensure the
promise settles even when structuredCloneFn(...) or assertions throw, preventing
asynchronous rejection after the test completes.
- Around line 458-472: Update the test around the zlib deflate call and
structuredCloneFn assertion so the clone operation and expectations run inside a
try block, with the existing promise always awaited in a finally block. Preserve
the current assertions and success behavior while ensuring deflate is awaited
even when cloning or assertion evaluation throws.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c10e6497-b39f-41cb-82b2-4ba065f44bec

📥 Commits

Reviewing files that changed from the base of the PR and between 6352b79 and 7bb9b46.

📒 Files selected for processing (13)
  • scripts/build/deps/webkit.ts
  • src/jsc/bindings/BunClientData.cpp
  • src/jsc/bindings/JSCTaskScheduler.cpp
  • src/jsc/bindings/JSCTaskScheduler.h
  • src/jsc/bindings/JSEnvironmentVariableMap.cpp
  • src/jsc/bindings/ZigGlobalObject.cpp
  • src/jsc/bindings/bindings.cpp
  • src/jsc/bindings/webcore/SerializedScriptValue.cpp
  • src/jsc/modules/BunJSCModule.h
  • src/runtime/dispatch.rs
  • test/js/bun/jsc/temporal-global.test.ts
  • test/js/node/async_hooks/AsyncLocalStorage.test.ts
  • test/js/web/workers/structured-clone.test.ts

Comment thread scripts/build/deps/webkit.ts Outdated
Comment thread test/js/web/workers/structured-clone.test.ts Outdated
Comment thread test/js/web/workers/structured-clone.test.ts Outdated
Comment thread test/js/web/workers/structured-clone.test.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/js/web/workers/structured-clone.test.ts`:
- Around line 464-475: Update the structured-clone test around the deflate
promise and structuredCloneFn call so ab.byteLength is asserted only after the
finally block awaits completion. Preserve the existing clone assertions, but
move the origLength check into a post-completion assertion to verify the
buffer’s final detached or retained state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8c2aff7a-80c2-40f2-aa73-01eeb377ee52

📥 Commits

Reviewing files that changed from the base of the PR and between 7bb9b46 and 7f0d445.

📒 Files selected for processing (1)
  • test/js/web/workers/structured-clone.test.ts

Comment thread test/js/web/workers/structured-clone.test.ts

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All prior review feedback is addressed (the isDetachable() transfer gate, the missing pinned-buffer test, the stale TicketData comment, and the module-scope deflate import). No new issues found. Deferring to a human reviewer given this is a 211-commit JSC upgrade touching ref-counted concurrency paths (DeferredWorkTimer::Ticket) and a deliberate divergence from upstream in SerializedScriptValue; also note WEBKIT_VERSION still points at the preview tag pending oven-sh/WebKit#300 merge.

What was reviewed:

  • JSCTaskScheduler ticket refactor: Ref<Ticket> ownership across onAddPendingWork/onScheduleWorkSoon/onCancelPendingWork, shutdown drain, and event-loop ref balancing look correct.
  • SerializedScriptValue gate removal: confirmed transferTo()'s copyTo() fallback covers both locked and pinned; new test locks it in.
  • Timezone paths: all three call sites (process.env.TZ, JSGlobalObject__setTimeZone, bun:jsc setTimeZone) now bump WTF::timeZoneDidChange() before clearForTimeZoneChange().
  • SparseArrayValueMap iterator: .key.index() is a straight rename.
Extended reasoning...

Overview

This PR upgrades Bun's WebKit fork across 211 upstream JSC/WTF/bmalloc commits and adapts Bun's bindings to four upstream API changes: the DeferredWorkTimer::Ticket refactor (TicketDataTicket, now ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr, hooks take Ref<Ticket>&&/Ticket&), DateCache::resetIfNecessarySlow()WTF::timeZoneDidChange() + clearForTimeZoneChange(), SparseArrayValueMap becoming a HashSet<SparseArrayEntry> (iterator yields entries directly), and ArrayBuffer::isLocked() folding into isDetachable(). It also adds tests for Temporal (now enabled by default upstream), async-generator AsyncLocalStorage propagation (new cooperative driver microtask), and the pinned-ArrayBuffer structured-clone contract.

Security risks

None introduced by the binding changes themselves. The upstream range includes libpas OoB/race fixes and divideRoundedUp/calculateBase64EncodedSize overflow fixes, which are net security improvements. The SerializedScriptValue change removes a throw gate but restores pre-existing Bun behavior (pinned buffers copy on transfer), which is memory-safe — the pin keeps the native borrow's bytes alive.

Level of scrutiny

High. WebKit upgrades change the JS engine underneath every Bun program; the JSCTaskScheduler changes touch cross-thread ref-counted state under a lock (Atomics.waitAsync/FinalizationRegistry shutdown paths), and the SerializedScriptValue change is a deliberate Bun-specific divergence from upstream WebCore that a future sync could silently revert. These are exactly the areas REVIEW.md flags as most-blocked (memory safety, ref-count balance, thread affinity). A maintainer familiar with prior WebKit bumps should sign off.

Other factors

Over three review rounds I flagged (1) the !isDetachable() gate breaking the pin contract, (2) missing automated coverage for that path, (3) a stale ~Ref<TicketData> comment in dispatch.rs, and (4) a require("zlib") in the new test — all four are now fixed in the current diff. CodeRabbit's finally-await feedback is also addressed. The one intentionally-open item is WEBKIT_VERSION = "autobuild-preview-pr-300-4c5f4e80", which the author has stated will be swapped to the merged autobuild-<sha> tag before this lands. The bug-hunting system found nothing new on the current revision.

Comment thread test/js/node/async_hooks/AsyncLocalStorage.test.ts
So the catch can't swallow a loop-body assertion failure and still pass.
Comment thread src/jsc/bindings/webcore/SerializedScriptValue.cpp Outdated
robobun added 2 commits July 19, 2026 08:11
Replaces the preview tag now that the WebKit upgrade PR has landed on
oven-sh/WebKit main. The merged commit also carries oven-sh/WebKit#304
(Windows WallTime/MonotonicTime), #306 (reifyStaticProperty ThrowScope
drop on main) and #310 (preCommitStackMemory guard).
Resolves the WEBKIT_VERSION conflict: main bumped to 639550acdc via
#34669; this branch already pointed at a0e65bf298 (the squash-merge of
oven-sh/WebKit#300, which has 639550acdc as its parent). Keep
a0e65bf298 in main's bare-sha format.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new findings this pass — the earlier inline issues (Wasm-memory transfer gate, bare-catch in the async-generator test, Temporal env hermeticity, zlib import) are all addressed in the current diff. Deferring to a human for the merge decision: this is a 211-commit JSC/WTF upgrade that reworks DeferredWorkTimer::Ticket ref-counting across the thread-safe scheduler, changes ArrayBuffer transfer-list semantics, and flips Temporal's default — worth a maintainer's eyes before landing.

What was reviewed

  • JSCTaskScheduler Ref<Ticket> signatures: checked that dropPendingTicketLocked still identity-compares by .ptr() and the shutdown / cancel paths balance the event-loop ref.
  • SerializedScriptValue::create: confirmed the isWasmMemory() gate uses the enclosing function's throw scope (the removed inner DECLARE_THROW_SCOPE was redundant) and pinned buffers still reach transferTo()'s copyTo() fallback.
  • The three timezone call sites now consistently pair WTF::timeZoneDidChange() with dateCache.clearForTimeZoneChange().
  • runPendingWork's job->task(job->ticket.get()) matches the new Task signature taking Ticket&.
Extended reasoning...

Overview

Bumps WEBKIT_VERSION to the merged autobuild-a0e65bf298 tag (oven-sh/WebKit#300) and adapts Bun's C++ bindings to four upstream API changes: the DeferredWorkTimer::TicketDataTicket rename with ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr semantics (JSCTaskScheduler.{cpp,h}, BunClientData.cpp, dispatch.rs comment), the DateCache::resetIfNecessarySlow()WTF::timeZoneDidChange() + clearForTimeZoneChange() split (three call sites), the SparseArrayValueMap iterator shape change (.key.index() in bindings.cpp), and ArrayBuffer::isLocked() folding into isDetachable() (SerializedScriptValue.cpp). It also pins useTemporal() = false since upstream flipped it on by default, and adds three test files covering Temporal exposure, async-generator ALS context propagation through the new cooperative driver, and pinned-vs-Wasm ArrayBuffer transfer semantics.

Security risks

None introduced by the binding diff itself. The transfer-list change went through an iteration where dropping the isLocked() gate would have silently copied WebAssembly.Memory linear memory instead of rejecting it — that was caught in an earlier review pass and the isWasmMemory() gate is now reinstated with a test. The upstream range itself carries the usual JIT/GC/Wasm surface (libpas OoB fixes, YARR changes, BBQ strength-reduction) that any engine bump does.

Level of scrutiny

High. This swaps the JS engine (211 JSC/WTF/bmalloc commits) and touches thread-safety-critical code: JSCTaskScheduler::onScheduleWorkSoon / onCancelPendingWork now take Ref<Ticket>&& / Ticket& instead of the old raw-pointer Ticket, and the pending-set removal + event-loop ref balancing must remain correct under concurrent Atomics.notify and worker shutdown. The SerializedScriptValue change alters user-visible transfer semantics. WebKit upgrades in this repo are maintainer-reviewed by convention.

Other factors

  • All prior inline findings from earlier passes are resolved and verified against the current diff.
  • CI (build #75473) is green except a Tart infra failure and test-http2-reset-flood.js on debian x64-asan, which robobun's analysis attributes to a pre-existing worker.terminate() race whose window widened under the new microtask dispatch — not introduced here, but worth a maintainer's judgment on whether it blocks.
  • The WEBKIT_VERSION is now the final merged sha (no longer the preview tag), so the pre-merge blocker noted earlier in the thread is cleared.
  • The removed static getVM(Ticket&) helper was dead after the signature change; JSCDeferredWorkTask::vm() covers the remaining use.

@Jarred-Sumner
Jarred-Sumner merged commit bb095b3 into main Jul 20, 2026
78 of 80 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the claude/webkit-upgrade-c8b6308aaa69 branch July 20, 2026 03:49
robobun added a commit that referenced this pull request Jul 20, 2026
Conflicts were all the WebKit-upgrade (#34373) TZ-change API:
resetIfNecessarySlow() -> WTF::timeZoneDidChange() + clearForTimeZoneChange().
resetDateCachesAfterTimeZoneChange now calls the new pair (and still runs the
per-instance heap-walk); the three call sites keep the
resetDateCachesAfterTimeZoneChange wrapper so the isMainThread gate and
per-instance invalidation stay single-sourced.

Parallelized the two worker probes in the TZ-worker-isolation test so it fits
the default 5s timeout on debug.
robobun added a commit that referenced this pull request Jul 24, 2026
…nstructVersions to RETURN_IF_EXCEPTION

setUpStaticFunctionSlot now wraps reifyStaticProperty in DeferTerminationForAWhile
and treats a pending exception as slot-not-found (oven-sh/WebKit#282, #306, landed
in bun via #34373). reifyStaticProperty itself early-returns on an empty callback
result instead of putDirect'ing it. So PropertyCallback builders can
RETURN_IF_EXCEPTION(scope, {}) and the exception propagates to JS correctly.

This removes the builder-level DeferTerminationForAWhile this PR had added to
constructStdioWriteStream/constructStdin/constructEnv/constructNextTickFn and
callLazyProcessBuilder (now redundant with the JSC-side wrapping), and reverts
constructVersions to main's RETURN_IF_EXCEPTION. The pre-existing clear-and-report
patterns on main are left alone.
dylan-conway pushed a commit that referenced this pull request Aug 5, 2026
### What does this PR do?

Enables JavaScriptCore's `Temporal` implementation by default by setting
`JSC::Options::useTemporal() = true` alongside the other JSC option
defaults in `ZigGlobalObject.cpp`. This exposes the `Temporal` global
and `Date.prototype.toTemporalInstant`. `BUN_JSC_useTemporal=0` still
turns it back off, because the `BUN_JSC_*` environment overrides are
applied after the defaults block.

Fixes #15853.

### Why now

JSC's Temporal implementation has improved a lot since the 40% test262
pass rate reported on #15853 in early 2025. Measured with
`temporal-test262-runner` 0.4.0 (the same tool that produced that 40%
figure) against test262 `de8e621c`, with Node v26.3.0 (which ships
Temporal on by default) for comparison:

| Suite | Tests | Bun | Node 26.3 |
|---|---|---|---|
| `built-ins/Temporal` (core spec) | 4603 | 100.0% | 98.7% |
| `built-ins/Date/prototype/toTemporalInstant` | 8 | 100% | 100% |
| `intl402/DateTimeFormat` + `DurationFormat` on Temporal objects | 259
| 99.2% | 91.9% |
| `intl402/Temporal` (non-ISO calendars) | 2029 | 56.2% | 38.3% |

Every one of the 136 tests where Bun is behind Node is in
`intl402/Temporal`, which is non-ISO calendar behavior. Outside of that
there is not a single test262 test where Bun is behind Node. I also
byte-diffed the output of 20,500 deterministic Temporal operations
(seeded identically in both engines): the iso8601, gregory, and roc
calendars, 2000 DST-disambiguation cases, all offset-option handling,
Instant rounding, and formatting options produced zero divergences.

The full test262 run was repeated on a debug + ASAN build of this
branch's parent: zero crashes, zero ASAN reports, zero assertion
failures, and a pass/fail set identical to release across all 6634
tests.

Chrome 144, Firefox, and Node 26 ship Temporal on by default. TypeScript
6.0 ships the type declarations and its `lib.esnext.d.ts` references
`esnext.temporal`, so nothing is needed in `bun-types`.

### The Windows blocker is resolved

This PR was converted to a draft when CI showed
`Temporal.PlainDate.prototype.since` throwing `RangeError: Duration is
outside the representable range` on trivial input, on Windows x64 and
Windows aarch64 only (the two platforms where WTF's `Int128` is the
emulated `Int128Impl` rather than native `__int128`).

The WebKit upgrade in #34373 (`2603e9eb41f0`) brought in upstream's
Temporal duration-arithmetic and spec-alignment fixes, and the bug no
longer reproduces. Verified on current main's canary
(`1.4.0-canary.1+6b6fb1a33`) on both Windows 2019 x64 and Windows 11
aarch64 with `BUN_JSC_useTemporal=1`: the previously failing expression
returns `P17M14D`, and a 10-expression smoke covering `since`/`until`,
`Duration.round`/`total`/`compare`, `Instant` arithmetic, epoch
nanoseconds, and ZonedDateTime DST transitions produces byte-identical
output to Linux on both.

### Cost

The `Temporal` namespace object is installed the same way `Intl` is (one
line above it in `JSGlobalObject::init`): a single small object whose
nine sub-constructors are `PropertyCallback` lookup-table entries
created on first access, with their structures registered via
`initLater`. Measured over 420 cold starts of `bun -e ''`: 1887 us/start
with `useTemporal` off, 1875 us/start with it on (within measurement
noise). RSS is 31.5 MB vs 31.4 MB. The implementation was already
compiled into the binary behind the runtime flag, so there is no size
change.

### Known gaps, all non-ISO calendars and all upstream JavaScriptCore

- `dayOfYear` ignores the calendar and returns the ISO day of year.
Every other calendar-dependent field getter has a non-ISO branch through
`TemporalCore`; this one does not, and
`TemporalZonedDateTimePrototype.cpp` already has a source comment
acknowledging it.
- The buddhist calendar's `year` returns the ISO year instead of BE,
because `TemporalCore::calendarYear()` uses ICU's `UCAL_EXTENDED_YEAR`,
which is the Gregorian year for the `GregorianCalendar`-derived
calendars. The same function already special-cases `roc` for this exact
quirk.
- Chinese/dangi leap-month placement disagrees with the reference on
2-3% of dates, and the `ad`/`bc` era aliases are not accepted
(`ce`/`bce` are).
- Legacy IANA time zone IDs such as `CET` and `EST5EDT` are rejected.
This is not Temporal-specific: `new Date()` and
`Intl.DateTimeFormat().resolvedOptions().timeZone` already fall back to
`UTC` for them today.

The first two are being filed upstream on bugs.webkit.org with one-line
reproductions. None of these are regressions this PR can introduce; they
are pre-existing in the JSC implementation, which is only reachable
behind `BUN_JSC_useTemporal=1` today.

### Nothing else needed to change

- `test/js/node/test/common/globals.js` already handles the new global
conditionally (`if (global.Temporal) intrinsics.add('Temporal')`).
- `test/js/bun/jsc/temporal-global.test.ts` (added by #34373 to pin the
off-by-default state) is updated to assert the new default and the
`BUN_JSC_useTemporal=0` opt-out; the subprocess flag tests live there,
next to the other JSC option coverage, and the in-process functional
coverage lives in `test/js/web/temporal/temporal.test.ts`.
- `structuredClone` and `postMessage` correctly reject Temporal objects
with a `DataCloneError`, matching Node.
- Temporal string parsing uses its own strict ISO 8601 grammar in
`ISO8601.cpp`. It never touches the date parser selected by
`useV8DateParser`, which has exactly one call site
(`DateCache::parseDate`), so there is no interaction with that override.

### LeakSanitizer

The x64-asan lane flags JSC's Temporal time zone bridge on any test that
touches a named time zone. `JSC::TemporalCore::withTimeZone` keeps a
process-lifetime `LazyNeverDestroyed` LRU of at most 8 open `UCalendar`s
(evicted entries are `ucal_close`d), and the `TimeZoneCacheEntry` that
owns each one lives in bmalloc memory, which LeakSanitizer does not scan
as a root region, so the libc-allocated `UCalendar` is misreported as a
direct leak. This PR adds the one `test/leaksan.supp` entry for it with
a comment. It is bounded and intentional, not a real leak.

### How did you verify your code works?

`test/js/bun/jsc/temporal-global.test.ts` covers the global being
present by default and the `BUN_JSC_useTemporal=0` escape hatch.
`test/js/web/temporal/temporal.test.ts` covers the spec property
attributes, all nine namespaces, `Temporal.Now`,
parsing/arithmetic/DST-disambiguation round-trips,
`Date.prototype.toTemporalInstant`, `Intl.DateTimeFormat` formatting of
Temporal objects, and `structuredClone` rejection.

```
$ USE_SYSTEM_BUN=1 bun test test/js/bun/jsc/temporal-global.test.ts test/js/web/temporal/temporal.test.ts
 1 pass
 9 fail

$ bun bd test test/js/bun/jsc/temporal-global.test.ts test/js/web/temporal/temporal.test.ts
 10 pass
 0 fail
```

`test/js/web/workers/structured-clone.test.ts` (231 tests) also passes
with the flag on.

<!-- robobun:evidence:begin -->

---

**[review]** gate passed · iteration 4 · 4 files touched

<details><summary>fails on main (without fix)</summary>

```console
ASAN without fix: 9 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/jsc/temporal-global.test.ts test/js/web/temporal/temporal.test.ts
bun test v1.4.0 (f2e0cd5)

test/js/bun/jsc/temporal-global.test.ts:
 9 |     cmd: [bunExe(), "-e", `process.stdout.write(typeof Temporal + " " + typeof Temporal.Now.instant)`],
10 |     env: { ...bunEnv, BUN_JSC_useTemporal: undefined },
11 |     stderr: "pipe",
12 |   });
13 |   const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
14 |   expect({ stdout, stderr, exitCode }).toEqual({ stdout: "object function", stderr: expect.any(String), exitCode: 0 });
                                            ^
error: expect(received).toEqual(expected)

  {
-   "exitCode": 0,
-   "stderr": Any<String>,
-   "stdout": "object function",
+   "exitCode": 1,
+   "stderr": 
+ "1 | process.stdout.write(typeof Temporal + " " + typeof Temporal.Now.instant)
+                                                         ^
+ ReferenceError: Temporal is not defined
+       at /workspace/bun/[eval]:1:53
+ 
+ Bun v1.4.0-debug+f2e0cd5
... (truncated)

release without fix: all passed
bun test v1.4.0-canary.1 (2f503b5)

test/js/bun/jsc/temporal-global.test.ts:
(pass) Temporal is exposed by default [18.29ms]
(pass) BUN_JSC_useTemporal=0 disables Temporal [17.95ms]

test/js/web/temporal/temporal.test.ts:
(pass) Temporal global > is installed with the spec property attributes [0.03ms]
(pass) Temporal global > exposes all nine namespaces [0.05ms]
(pass) Temporal core operations > Temporal.Now [5.26ms]
(pass) Temporal core operations > parsing, arithmetic, and formatting round-trip [0.17ms]
(pass) Temporal core operations > DST disambiguation [0.09ms]
(pass) Temporal core operations > Date.prototype.toTemporalInstant [0.04ms]
(pass) Temporal core operations > Intl.DateTimeFormat formats Temporal objects [2.06ms]
(pass) Temporal core operations > structuredClone rejects Temporal objects [0.12ms]

 10 pass
 0 fail
 27 expect() calls
Ran 10 tests across 2 files. [175.00ms]
__F:0:S:0
```

</details>

<details><summary>passes on PR (with fix)</summary>

```console
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/jsc/temporal-global.test.ts test/js/web/temporal/temporal.test.ts
bun test v1.4.0 (f2e0cd5)

test/js/bun/jsc/temporal-global.test.ts:
(pass) Temporal is exposed by default [1284.32ms]
(pass) BUN_JSC_useTemporal=0 disables Temporal [1302.12ms]

test/js/web/temporal/temporal.test.ts:
(pass) Temporal global > is installed with the spec property attributes [2.15ms]
(pass) Temporal global > exposes all nine namespaces [3.39ms]
(pass) Temporal core operations > Temporal.Now [168.15ms]
(pass) Temporal core operations > parsing, arithmetic, and formatting round-trip [9.26ms]
(pass) Temporal core operations > DST disambiguation [6.56ms]
(pass) Temporal core operations > Date.prototype.toTemporalInstant [2.74ms]
(pass) Temporal core operations > Intl.DateTimeFormat formats Temporal objects [27.37ms]
(pass) Temporal core operations > structuredClone rejects Temporal objects [7.59ms]

 10 pass
 0 fail
 27 expect() calls
Ran 10 tests across 2 files. [3.65s]
__F:0:S:0

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 711ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/7] cxx obj/src/jsc/bindings/ZigGlobalObject.cpp.o
[2/7] gen cpp.rs (cppbind)
[2/7] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

�[1m�[92m   Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m   Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m   Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[92m   Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys)
�[1m�[92m   Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety)
�[1m�[92m   Compiling�[0m bun_zlib_sys v0.0.0 (/workspace/bun/src/zlib_sys)
�[1m�[92m   Compiling�[0m bun_cares_sys v0.0.0 (/workspace/bun/src/cares_sys)
�[1m�[92m   Compiling�[0m bun_zstd v0.0.0 (/workspace/bun/src/zstd)
�[1m�[92m   Compiling�[0m bun_picohttp v0.0.0 (/workspace/bun/src/picohttp)
�[1m�[92m   Compiling�[0m bun_brotli v0.0.0 (/workspace/bun/src/brotli)
�[1m�[92m   Compiling�[0m b
... (truncated)
```

</details>

<details><summary>diff hotspot</summary>

```
src/jsc/bindings/ZigGlobalObject.cpp    |  5 +-
 test/js/bun/jsc/temporal-global.test.ts | 20 ++++----
 test/js/web/temporal/temporal.test.ts   | 88 +++++++++++++++++++++++++++++++++
 test/leaksan.supp                       |  9 +++-
 4 files changed, 107 insertions(+), 15 deletions(-)
```

</details>

**gate history** · 2 passed · 0 rejected · iteration 4

<details><summary>evidence per changed file</summary>

```
file                                     reads  edits  tests
src/jsc/bindings/ZigGlobalObject.cpp         2      1      8
test/js/bun/jsc/temporal-global.test.ts      0      0      0
test/js/web/temporal/temporal.test.ts        0      2      7
test/leaksan.supp                            0      0     10
```

</details>

<!-- robobun:evidence:end -->
springmin pushed a commit to springmin/bun that referenced this pull request Aug 5, 2026
### What does this PR do?

Enables JavaScriptCore's `Temporal` implementation by default by setting
`JSC::Options::useTemporal() = true` alongside the other JSC option
defaults in `ZigGlobalObject.cpp`. This exposes the `Temporal` global
and `Date.prototype.toTemporalInstant`. `BUN_JSC_useTemporal=0` still
turns it back off, because the `BUN_JSC_*` environment overrides are
applied after the defaults block.

Fixes oven-sh#15853.

### Why now

JSC's Temporal implementation has improved a lot since the 40% test262
pass rate reported on oven-sh#15853 in early 2025. Measured with
`temporal-test262-runner` 0.4.0 (the same tool that produced that 40%
figure) against test262 `de8e621c`, with Node v26.3.0 (which ships
Temporal on by default) for comparison:

| Suite | Tests | Bun | Node 26.3 |
|---|---|---|---|
| `built-ins/Temporal` (core spec) | 4603 | 100.0% | 98.7% |
| `built-ins/Date/prototype/toTemporalInstant` | 8 | 100% | 100% |
| `intl402/DateTimeFormat` + `DurationFormat` on Temporal objects | 259
| 99.2% | 91.9% |
| `intl402/Temporal` (non-ISO calendars) | 2029 | 56.2% | 38.3% |

Every one of the 136 tests where Bun is behind Node is in
`intl402/Temporal`, which is non-ISO calendar behavior. Outside of that
there is not a single test262 test where Bun is behind Node. I also
byte-diffed the output of 20,500 deterministic Temporal operations
(seeded identically in both engines): the iso8601, gregory, and roc
calendars, 2000 DST-disambiguation cases, all offset-option handling,
Instant rounding, and formatting options produced zero divergences.

The full test262 run was repeated on a debug + ASAN build of this
branch's parent: zero crashes, zero ASAN reports, zero assertion
failures, and a pass/fail set identical to release across all 6634
tests.

Chrome 144, Firefox, and Node 26 ship Temporal on by default. TypeScript
6.0 ships the type declarations and its `lib.esnext.d.ts` references
`esnext.temporal`, so nothing is needed in `bun-types`.

### The Windows blocker is resolved

This PR was converted to a draft when CI showed
`Temporal.PlainDate.prototype.since` throwing `RangeError: Duration is
outside the representable range` on trivial input, on Windows x64 and
Windows aarch64 only (the two platforms where WTF's `Int128` is the
emulated `Int128Impl` rather than native `__int128`).

The WebKit upgrade in oven-sh#34373 (`2603e9eb41f0`) brought in upstream's
Temporal duration-arithmetic and spec-alignment fixes, and the bug no
longer reproduces. Verified on current main's canary
(`1.4.0-canary.1+6b6fb1a33`) on both Windows 2019 x64 and Windows 11
aarch64 with `BUN_JSC_useTemporal=1`: the previously failing expression
returns `P17M14D`, and a 10-expression smoke covering `since`/`until`,
`Duration.round`/`total`/`compare`, `Instant` arithmetic, epoch
nanoseconds, and ZonedDateTime DST transitions produces byte-identical
output to Linux on both.

### Cost

The `Temporal` namespace object is installed the same way `Intl` is (one
line above it in `JSGlobalObject::init`): a single small object whose
nine sub-constructors are `PropertyCallback` lookup-table entries
created on first access, with their structures registered via
`initLater`. Measured over 420 cold starts of `bun -e ''`: 1887 us/start
with `useTemporal` off, 1875 us/start with it on (within measurement
noise). RSS is 31.5 MB vs 31.4 MB. The implementation was already
compiled into the binary behind the runtime flag, so there is no size
change.

### Known gaps, all non-ISO calendars and all upstream JavaScriptCore

- `dayOfYear` ignores the calendar and returns the ISO day of year.
Every other calendar-dependent field getter has a non-ISO branch through
`TemporalCore`; this one does not, and
`TemporalZonedDateTimePrototype.cpp` already has a source comment
acknowledging it.
- The buddhist calendar's `year` returns the ISO year instead of BE,
because `TemporalCore::calendarYear()` uses ICU's `UCAL_EXTENDED_YEAR`,
which is the Gregorian year for the `GregorianCalendar`-derived
calendars. The same function already special-cases `roc` for this exact
quirk.
- Chinese/dangi leap-month placement disagrees with the reference on
2-3% of dates, and the `ad`/`bc` era aliases are not accepted
(`ce`/`bce` are).
- Legacy IANA time zone IDs such as `CET` and `EST5EDT` are rejected.
This is not Temporal-specific: `new Date()` and
`Intl.DateTimeFormat().resolvedOptions().timeZone` already fall back to
`UTC` for them today.

The first two are being filed upstream on bugs.webkit.org with one-line
reproductions. None of these are regressions this PR can introduce; they
are pre-existing in the JSC implementation, which is only reachable
behind `BUN_JSC_useTemporal=1` today.

### Nothing else needed to change

- `test/js/node/test/common/globals.js` already handles the new global
conditionally (`if (global.Temporal) intrinsics.add('Temporal')`).
- `test/js/bun/jsc/temporal-global.test.ts` (added by oven-sh#34373 to pin the
off-by-default state) is updated to assert the new default and the
`BUN_JSC_useTemporal=0` opt-out; the subprocess flag tests live there,
next to the other JSC option coverage, and the in-process functional
coverage lives in `test/js/web/temporal/temporal.test.ts`.
- `structuredClone` and `postMessage` correctly reject Temporal objects
with a `DataCloneError`, matching Node.
- Temporal string parsing uses its own strict ISO 8601 grammar in
`ISO8601.cpp`. It never touches the date parser selected by
`useV8DateParser`, which has exactly one call site
(`DateCache::parseDate`), so there is no interaction with that override.

### LeakSanitizer

The x64-asan lane flags JSC's Temporal time zone bridge on any test that
touches a named time zone. `JSC::TemporalCore::withTimeZone` keeps a
process-lifetime `LazyNeverDestroyed` LRU of at most 8 open `UCalendar`s
(evicted entries are `ucal_close`d), and the `TimeZoneCacheEntry` that
owns each one lives in bmalloc memory, which LeakSanitizer does not scan
as a root region, so the libc-allocated `UCalendar` is misreported as a
direct leak. This PR adds the one `test/leaksan.supp` entry for it with
a comment. It is bounded and intentional, not a real leak.

### How did you verify your code works?

`test/js/bun/jsc/temporal-global.test.ts` covers the global being
present by default and the `BUN_JSC_useTemporal=0` escape hatch.
`test/js/web/temporal/temporal.test.ts` covers the spec property
attributes, all nine namespaces, `Temporal.Now`,
parsing/arithmetic/DST-disambiguation round-trips,
`Date.prototype.toTemporalInstant`, `Intl.DateTimeFormat` formatting of
Temporal objects, and `structuredClone` rejection.

```
$ USE_SYSTEM_BUN=1 bun test test/js/bun/jsc/temporal-global.test.ts test/js/web/temporal/temporal.test.ts
 1 pass
 9 fail

$ bun bd test test/js/bun/jsc/temporal-global.test.ts test/js/web/temporal/temporal.test.ts
 10 pass
 0 fail
```

`test/js/web/workers/structured-clone.test.ts` (231 tests) also passes
with the flag on.

<!-- robobun:evidence:begin -->

---

**[review]** gate passed · iteration 4 · 4 files touched

<details><summary>fails on main (without fix)</summary>

```console
ASAN without fix: 9 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/jsc/temporal-global.test.ts test/js/web/temporal/temporal.test.ts
bun test v1.4.0 (f2e0cd5)

test/js/bun/jsc/temporal-global.test.ts:
 9 |     cmd: [bunExe(), "-e", `process.stdout.write(typeof Temporal + " " + typeof Temporal.Now.instant)`],
10 |     env: { ...bunEnv, BUN_JSC_useTemporal: undefined },
11 |     stderr: "pipe",
12 |   });
13 |   const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
14 |   expect({ stdout, stderr, exitCode }).toEqual({ stdout: "object function", stderr: expect.any(String), exitCode: 0 });
                                            ^
error: expect(received).toEqual(expected)

  {
-   "exitCode": 0,
-   "stderr": Any<String>,
-   "stdout": "object function",
+   "exitCode": 1,
+   "stderr": 
+ "1 | process.stdout.write(typeof Temporal + " " + typeof Temporal.Now.instant)
+                                                         ^
+ ReferenceError: Temporal is not defined
+       at /workspace/bun/[eval]:1:53
+ 
+ Bun v1.4.0-debug+f2e0cd5
... (truncated)

release without fix: all passed
bun test v1.4.0-canary.1 (2f503b5)

test/js/bun/jsc/temporal-global.test.ts:
(pass) Temporal is exposed by default [18.29ms]
(pass) BUN_JSC_useTemporal=0 disables Temporal [17.95ms]

test/js/web/temporal/temporal.test.ts:
(pass) Temporal global > is installed with the spec property attributes [0.03ms]
(pass) Temporal global > exposes all nine namespaces [0.05ms]
(pass) Temporal core operations > Temporal.Now [5.26ms]
(pass) Temporal core operations > parsing, arithmetic, and formatting round-trip [0.17ms]
(pass) Temporal core operations > DST disambiguation [0.09ms]
(pass) Temporal core operations > Date.prototype.toTemporalInstant [0.04ms]
(pass) Temporal core operations > Intl.DateTimeFormat formats Temporal objects [2.06ms]
(pass) Temporal core operations > structuredClone rejects Temporal objects [0.12ms]

 10 pass
 0 fail
 27 expect() calls
Ran 10 tests across 2 files. [175.00ms]
__F:0:S:0
```

</details>

<details><summary>passes on PR (with fix)</summary>

```console
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/jsc/temporal-global.test.ts test/js/web/temporal/temporal.test.ts
bun test v1.4.0 (f2e0cd5)

test/js/bun/jsc/temporal-global.test.ts:
(pass) Temporal is exposed by default [1284.32ms]
(pass) BUN_JSC_useTemporal=0 disables Temporal [1302.12ms]

test/js/web/temporal/temporal.test.ts:
(pass) Temporal global > is installed with the spec property attributes [2.15ms]
(pass) Temporal global > exposes all nine namespaces [3.39ms]
(pass) Temporal core operations > Temporal.Now [168.15ms]
(pass) Temporal core operations > parsing, arithmetic, and formatting round-trip [9.26ms]
(pass) Temporal core operations > DST disambiguation [6.56ms]
(pass) Temporal core operations > Date.prototype.toTemporalInstant [2.74ms]
(pass) Temporal core operations > Intl.DateTimeFormat formats Temporal objects [27.37ms]
(pass) Temporal core operations > structuredClone rejects Temporal objects [7.59ms]

 10 pass
 0 fail
 27 expect() calls
Ran 10 tests across 2 files. [3.65s]
__F:0:S:0

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 711ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/7] cxx obj/src/jsc/bindings/ZigGlobalObject.cpp.o
[2/7] gen cpp.rs (cppbind)
[2/7] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

�[1m�[92m   Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m   Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m   Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[92m   Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys)
�[1m�[92m   Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety)
�[1m�[92m   Compiling�[0m bun_zlib_sys v0.0.0 (/workspace/bun/src/zlib_sys)
�[1m�[92m   Compiling�[0m bun_cares_sys v0.0.0 (/workspace/bun/src/cares_sys)
�[1m�[92m   Compiling�[0m bun_zstd v0.0.0 (/workspace/bun/src/zstd)
�[1m�[92m   Compiling�[0m bun_picohttp v0.0.0 (/workspace/bun/src/picohttp)
�[1m�[92m   Compiling�[0m bun_brotli v0.0.0 (/workspace/bun/src/brotli)
�[1m�[92m   Compiling�[0m b
... (truncated)
```

</details>

<details><summary>diff hotspot</summary>

```
src/jsc/bindings/ZigGlobalObject.cpp    |  5 +-
 test/js/bun/jsc/temporal-global.test.ts | 20 ++++----
 test/js/web/temporal/temporal.test.ts   | 88 +++++++++++++++++++++++++++++++++
 test/leaksan.supp                       |  9 +++-
 4 files changed, 107 insertions(+), 15 deletions(-)
```

</details>

**gate history** · 2 passed · 0 rejected · iteration 4

<details><summary>evidence per changed file</summary>

```
file                                     reads  edits  tests
src/jsc/bindings/ZigGlobalObject.cpp         2      1      8
test/js/bun/jsc/temporal-global.test.ts      0      0      0
test/js/web/temporal/temporal.test.ts        0      2      7
test/leaksan.supp                            0      0     10
```

</details>

<!-- robobun:evidence:end -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants