Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
afb8033
Upgrade WebKit to c8b6308aaa69
robobun Jul 16, 2026
acdc329
test: Temporal global enabled by default after WebKit c8b6308aaa69
robobun Jul 16, 2026
23f04d8
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 16, 2026
0918139
Bump WEBKIT_VERSION to autobuild-preview-pr-300-ddece060
robobun Jul 17, 2026
410a8dc
Bump WEBKIT_VERSION to a67975c4; cover ALS across for-await
robobun Jul 17, 2026
4d813a1
Merge origin/main; adopt AsyncContextSwapScope and JSCTaskScheduler s…
robobun Jul 17, 2026
9c5638d
Bump WEBKIT_VERSION to autobuild-preview-pr-300-67898ceb
robobun Jul 17, 2026
c596ca8
ci: retrigger (WebKit preview release autobuild-preview-pr-300-67898c…
robobun Jul 17, 2026
61078f5
Bump WEBKIT_VERSION to 4c5f4e80; drop the isDetachable transfer gate
robobun Jul 17, 2026
7bb9b46
test: pinned ArrayBuffer copies (not throws) on structuredClone transfer
robobun Jul 17, 2026
7f0d445
test: await the pin deflate in finally; use module-scope import
robobun Jul 17, 2026
7176808
ci: retrigger (preview release assets now fully uploaded)
robobun Jul 17, 2026
7bb2ba1
test: assert the caught error is the generator's 'boom'
robobun Jul 17, 2026
f386a31
Merge upstream 2603e9eb41f0; disable Temporal by default; reject Wasm…
robobun Jul 17, 2026
5ffbb1a
test: unset BUN_JSC_useTemporal for the default-off assertion
robobun Jul 17, 2026
2c12d3d
ci: retrigger (preview release autobuild-preview-pr-300-4559ebe7 publ…
robobun Jul 18, 2026
baba43f
Fix nested ThrowScope in Wasm memory transfer gate
robobun Jul 18, 2026
1b198e1
Bump WEBKIT_VERSION to autobuild-a0e65bf298 (oven-sh/WebKit#300 merged)
robobun Jul 19, 2026
f6a634c
Merge origin/main into claude/webkit-upgrade-c8b6308aaa69
robobun Jul 19, 2026
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
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-300-67898ceb";

/**
* WebKit (JavaScriptCore) — the JS engine.
Expand Down
8 changes: 4 additions & 4 deletions src/jsc/bindings/BunClientData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,13 @@ void JSVMClientData::create(VM* vm, void* bunVM)
auto provider = WebCore::createBuiltinsSourceProvider();
JSVMClientData* clientData = new JSVMClientData(*vm, provider);
clientData->bunVM = bunVM;
vm->deferredWorkTimer->onAddPendingWork = [clientData](Ref<JSC::DeferredWorkTimer::TicketData>&& ticket, JSC::DeferredWorkTimer::WorkType kind) -> void {
vm->deferredWorkTimer->onAddPendingWork = [clientData](Ref<JSC::DeferredWorkTimer::Ticket>&& ticket, JSC::DeferredWorkTimer::WorkType kind) -> void {
Bun::JSCTaskScheduler::onAddPendingWork(clientData, WTF::move(ticket), kind);
};
vm->deferredWorkTimer->onScheduleWorkSoon = [clientData](JSC::DeferredWorkTimer::Ticket ticket, JSC::DeferredWorkTimer::Task&& task) -> void {
Bun::JSCTaskScheduler::onScheduleWorkSoon(clientData, ticket, WTF::move(task));
vm->deferredWorkTimer->onScheduleWorkSoon = [clientData](Ref<JSC::DeferredWorkTimer::Ticket>&& ticket, JSC::DeferredWorkTimer::Task&& task) -> void {
Bun::JSCTaskScheduler::onScheduleWorkSoon(clientData, WTF::move(ticket), WTF::move(task));
};
vm->deferredWorkTimer->onCancelPendingWork = [clientData](JSC::DeferredWorkTimer::Ticket ticket) -> void {
vm->deferredWorkTimer->onCancelPendingWork = [clientData](JSC::DeferredWorkTimer::Ticket& ticket) -> void {
Bun::JSCTaskScheduler::onCancelPendingWork(clientData, ticket);
};

Expand Down
28 changes: 11 additions & 17 deletions src/jsc/bindings/JSCTaskScheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

using Ticket = JSC::DeferredWorkTimer::Ticket;
using Task = JSC::DeferredWorkTimer::Task;
using TicketData = JSC::DeferredWorkTimer::TicketData;

namespace Bun {
using namespace JSC;
Expand All @@ -15,13 +14,13 @@ extern "C" void Bun__eventLoop__incrementRefConcurrently(void* bunVM, int delta)

class JSCDeferredWorkTask {
public:
JSCDeferredWorkTask(Ref<TicketData> ticket, Task&& task)
JSCDeferredWorkTask(Ref<Ticket> ticket, Task&& task)
: ticket(WTF::move(ticket))
, task(WTF::move(task))
{
}

Ref<TicketData> ticket;
Ref<Ticket> ticket;
Task task;
~JSCDeferredWorkTask()
{
Expand All @@ -32,14 +31,9 @@ class JSCDeferredWorkTask {
WTF_MAKE_TZONE_ALLOCATED(JSCDeferredWorkTask);
};

static JSC::VM& getVM(Ticket& ticket)
{
return ticket->scriptExecutionOwner()->vm();
}

// Drop `ticket` from whichever pending set holds it. Caller holds m_lock; the
// event-loop ref is balanced after the caller releases the lock.
static bool dropPendingTicketLocked(Bun::JSCTaskScheduler& scheduler, Ticket ticket) WTF_REQUIRES_LOCK(scheduler.m_lock)
static bool dropPendingTicketLocked(Bun::JSCTaskScheduler& scheduler, Ticket* ticket) WTF_REQUIRES_LOCK(scheduler.m_lock)
{
bool isKeepingEventLoopAlive = scheduler.m_pendingTicketsKeepingEventLoopAlive.removeIf([ticket](auto pendingTicket) {
return pendingTicket.ptr() == ticket;
Expand All @@ -53,7 +47,7 @@ static bool dropPendingTicketLocked(Bun::JSCTaskScheduler& scheduler, Ticket tic
return isKeepingEventLoopAlive;
}

void JSCTaskScheduler::onAddPendingWork(WebCore::JSVMClientData* clientData, Ref<TicketData>&& ticket, JSC::DeferredWorkTimer::WorkType kind)
void JSCTaskScheduler::onAddPendingWork(WebCore::JSVMClientData* clientData, Ref<Ticket>&& ticket, JSC::DeferredWorkTimer::WorkType kind)
{
auto& scheduler = clientData->deferredWorkTimer;
Locker<Lock> holder { scheduler.m_lock };
Expand All @@ -66,7 +60,7 @@ void JSCTaskScheduler::onAddPendingWork(WebCore::JSVMClientData* clientData, Ref
scheduler.m_pendingTicketsOther.add(WTF::move(ticket));
}
}
void JSCTaskScheduler::onScheduleWorkSoon(WebCore::JSVMClientData* clientData, Ticket ticket, Task&& task)
void JSCTaskScheduler::onScheduleWorkSoon(WebCore::JSVMClientData* clientData, Ref<Ticket>&& ticket, Task&& task)
{
auto& scheduler = clientData->deferredWorkTimer;
Locker<Lock> holder { scheduler.m_lock };
Expand All @@ -80,23 +74,23 @@ void JSCTaskScheduler::onScheduleWorkSoon(WebCore::JSVMClientData* clientData, T
// across the check and the enqueue so the transition in markShuttingDown
// cannot race a cross-thread Atomics.notify.
if (scheduler.m_isShuttingDown) [[unlikely]] {
bool wasKeepingAlive = dropPendingTicketLocked(scheduler, ticket);
bool wasKeepingAlive = dropPendingTicketLocked(scheduler, ticket.ptr());
holder.unlockEarly();
if (wasKeepingAlive)
Bun__eventLoop__incrementRefConcurrently(clientData->bunVM, -1);
return;
}
auto* job = new JSCDeferredWorkTask(*ticket, WTF::move(task));
auto* job = new JSCDeferredWorkTask(WTF::move(ticket), WTF::move(task));
Bun__queueJSCDeferredWorkTaskConcurrently(clientData->bunVM, job);
}

void JSCTaskScheduler::onCancelPendingWork(WebCore::JSVMClientData* clientData, Ticket ticket)
void JSCTaskScheduler::onCancelPendingWork(WebCore::JSVMClientData* clientData, Ticket& ticket)
{
auto* bunVM = clientData->bunVM;
auto& scheduler = clientData->deferredWorkTimer;

Locker<Lock> holder { scheduler.m_lock };
bool wasKeepingAlive = dropPendingTicketLocked(scheduler, ticket);
bool wasKeepingAlive = dropPendingTicketLocked(scheduler, &ticket);
holder.unlockEarly();
if (wasKeepingAlive)
Bun__eventLoop__incrementRefConcurrently(bunVM, -1);
Expand All @@ -114,7 +108,7 @@ static void runPendingWork(void* bunVM, Bun::JSCTaskScheduler& scheduler, JSCDef
holder.unlockEarly();

if (pendingTicket && !pendingTicket->isCancelled()) {
job->task(job->ticket.ptr());
job->task(job->ticket.get());
}

delete job;
Expand All @@ -138,7 +132,7 @@ extern "C" void Bun__JSCTaskScheduler__markShuttingDown(JSC::JSGlobalObject* glo
}

// Reclaim a queued-but-never-dispatched job during shutdown. Called while the
// JSC VM is still alive, so ~Ref<TicketData> and the captured Task lambda may
// JSC VM is still alive, so ~Ref<Ticket> and the captured Task lambda may
Comment thread
robobun marked this conversation as resolved.
// safely touch TZone-allocated / JSC-owned state. Mirrors runPendingWork's
// ticket take() so the pending set and event-loop ref stay balanced.
extern "C" void Bun__deleteDeferredWorkTask(Bun::JSCDeferredWorkTask* job)
Expand Down
10 changes: 5 additions & 5 deletions src/jsc/bindings/JSCTaskScheduler.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ class JSCTaskScheduler {
{
}

static void onAddPendingWork(WebCore::JSVMClientData* clientData, Ref<JSC::DeferredWorkTimer::TicketData>&& ticket, JSC::DeferredWorkTimer::WorkType kind);
static void onScheduleWorkSoon(WebCore::JSVMClientData* clientData, JSC::DeferredWorkTimer::Ticket ticket, JSC::DeferredWorkTimer::Task&& task);
static void onCancelPendingWork(WebCore::JSVMClientData* clientData, JSC::DeferredWorkTimer::Ticket ticket);
static void onAddPendingWork(WebCore::JSVMClientData* clientData, Ref<JSC::DeferredWorkTimer::Ticket>&& ticket, JSC::DeferredWorkTimer::WorkType kind);
static void onScheduleWorkSoon(WebCore::JSVMClientData* clientData, Ref<JSC::DeferredWorkTimer::Ticket>&& ticket, JSC::DeferredWorkTimer::Task&& task);
static void onCancelPendingWork(WebCore::JSVMClientData* clientData, JSC::DeferredWorkTimer::Ticket& ticket);

// Set once the owning VM's event loop has taken its last tick. After this,
// onScheduleWorkSoon drops the task instead of enqueueing a ConcurrentTask
Expand All @@ -35,8 +35,8 @@ class JSCTaskScheduler {
public:
Lock m_lock;
bool m_isShuttingDown WTF_GUARDED_BY_LOCK(m_lock) { false };
UncheckedKeyHashSet<Ref<JSC::DeferredWorkTimer::TicketData>> m_pendingTicketsKeepingEventLoopAlive;
UncheckedKeyHashSet<Ref<JSC::DeferredWorkTimer::TicketData>> m_pendingTicketsOther;
UncheckedKeyHashSet<Ref<JSC::DeferredWorkTimer::Ticket>> m_pendingTicketsKeepingEventLoopAlive;
UncheckedKeyHashSet<Ref<JSC::DeferredWorkTimer::Ticket>> m_pendingTicketsOther;
};

}
6 changes: 4 additions & 2 deletions src/jsc/bindings/JSEnvironmentVariableMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -487,8 +487,10 @@ static constexpr ASCIILiteral kProxyEnvVarNames[] = {
// side-effecting var need only be added in one place.
static void applyTZFromString(JSGlobalObject* globalObject, const String& value)
{
if (value.length() < 32 && WTF::setTimeZoneOverride(value))
JSC::getVM(globalObject).dateCache.resetIfNecessarySlow();
if (value.length() < 32 && WTF::setTimeZoneOverride(value)) {
WTF::timeZoneDidChange();
JSC::getVM(globalObject).dateCache.clearForTimeZoneChange();
}
}
static void applyTLSRejectFromString(JSGlobalObject*, const String& value)
{
Expand Down
3 changes: 2 additions & 1 deletion src/jsc/bindings/ZigGlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3301,7 +3301,8 @@ extern "C" bool JSGlobalObject__setTimeZone(JSC::JSGlobalObject* globalObject, c
auto& vm = JSC::getVM(globalObject);

if (WTF::setTimeZoneOverride(Zig::toString(*timeZone))) {
vm.dateCache.resetIfNecessarySlow();
WTF::timeZoneDidChange();
vm.dateCache.clearForTimeZoneChange();
return true;
}

Expand Down
4 changes: 2 additions & 2 deletions src/jsc/bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6594,8 +6594,8 @@ extern "C" uint64_t Bun__JSArray__nextPresentIndex(
uint64_t result = notFound;
if (JSC::SparseArrayValueMap* map = storage->m_sparseMap.get()) {
for (const auto& entry : *map) {
if (entry.key >= start && entry.key < result)
result = entry.key;
if (entry.index() >= start && entry.index() < result)
result = entry.index();
}
}
return result;
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/webcore/SerializedScriptValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6403,7 +6403,7 @@ ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalOb
if (auto arrayBuffer = toPossiblySharedArrayBuffer(vm, transferable.get())) {
if (arrayBuffer->isDetached() || arrayBuffer->isShared())
return Exception { DataCloneError };
if (arrayBuffer->isLocked()) {
if (!arrayBuffer->isDetachable()) {
auto scope = DECLARE_THROW_SCOPE(vm);
throwVMTypeError(&lexicalGlobalObject, scope, errorMessageForTransfer(arrayBuffer));
RELEASE_AND_RETURN(scope, Exception { ExistingExceptionError });
Comment thread
robobun marked this conversation as resolved.
Outdated
Expand Down
3 changes: 2 additions & 1 deletion src/jsc/modules/BunJSCModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,8 @@ JSC_DEFINE_HOST_FUNCTION(functionSetTimeZone, (JSGlobalObject * globalObject, Ca
makeString("Invalid timezone: \""_s, timeZoneName, "\""_s));
return {};
}
vm.dateCache.resetIfNecessarySlow();
WTF::timeZoneDidChange();
vm.dateCache.clearForTimeZoneChange();
WTF::Vector<char16_t, 32> buffer;
WTF::getTimeZoneOverride(buffer);
WTF::String timeZoneString(buffer.span());
Expand Down
23 changes: 23 additions & 0 deletions test/js/bun/jsc/temporal-global.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";

// Upstream WebKit c8b6308aaa69 ships Temporal enabled by default
// (https://bugs.webkit.org/show_bug.cgi?id=318234). Before this
// WebKit bump the global was only reachable with --useTemporal=1.
test("Temporal is exposed on the global object", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", `process.stdout.write(typeof Temporal + " " + typeof Temporal.Now.instant)`],
env: bunEnv,
stderr: "pipe",
});
Comment thread
robobun marked this conversation as resolved.
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect({ stdout, stderr, exitCode }).toEqual({ stdout: "object function", stderr: expect.any(String), exitCode: 0 });
});

test("Temporal.Now.instant returns a Temporal.Instant", () => {
// @ts-expect-error: Temporal types land with the WebKit bump
const instant = Temporal.Now.instant();
// @ts-expect-error
expect(instant instanceof Temporal.Instant).toBe(true);
expect(typeof instant.epochNanoseconds).toBe("bigint");
});
68 changes: 68 additions & 0 deletions test/js/node/async_hooks/AsyncLocalStorage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1152,3 +1152,71 @@ describe("async context passes through", () => {
expect(a).toBe("value");
});
});

describe("async generators", () => {
// WebKit c8b6308aaa69 introduced a cooperative async-generator driver
// (InternalMicrotask::AsyncGeneratorDriverResume) as the fast path for
// `for await` over a pristine async generator. It must capture and
// restore Bun's async context like every other resume-body microtask.
test("for await over an async generator preserves the store", async () => {
const als = new AsyncLocalStorage();
async function* gen() {
yield 1;
yield 2;
yield 3;
}
const seen: unknown[] = [];
await als.run("STORE_A", async () => {
for await (const x of gen()) {
seen.push([x, als.getStore()]);
}
seen.push(["done", als.getStore()]);
});
expect(seen).toEqual([
[1, "STORE_A"],
[2, "STORE_A"],
[3, "STORE_A"],
["done", "STORE_A"],
]);
});

test("for await body sees its own store, not an interleaved one", async () => {
const als = new AsyncLocalStorage();
async function* gen() {
yield 1;
yield 2;
}
const seen: unknown[] = [];
await Promise.all([
als.run("A", async () => {
for await (const x of gen()) seen.push(["A-loop", x, als.getStore()]);
}),
als.run("B", async () => {
for await (const x of gen()) seen.push(["B-loop", x, als.getStore()]);
}),
]);
for (const [tag, , store] of seen) {
expect(store).toBe(tag === "A-loop" ? "A" : "B");
}
expect(seen.length).toBe(4);
});

test("thrown from async generator preserves the store in the catch", async () => {
const als = new AsyncLocalStorage();
async function* gen() {
yield 1;
throw new Error("boom");
}
let caughtStore: unknown;
await als.run("STORE_X", async () => {
try {
for await (const _ of gen()) {
expect(als.getStore()).toBe("STORE_X");
}
} catch {
caughtStore = als.getStore();
}
Comment thread
robobun marked this conversation as resolved.
});
expect(caughtStore).toBe("STORE_X");
});
});
Loading