Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 = "639550acdcb2a5fba8a5812b03ff4184522e73fa";
export const WEBKIT_VERSION = "a0e65bf298499d828f0c60d4557899b94a69d7ae";

/**
* 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
7 changes: 6 additions & 1 deletion src/jsc/bindings/ZigGlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,10 @@ extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(c
JSC::Options::useAsyncStackTrace() = true;
JSC::Options::useExplicitResourceManagement() = true;
JSC::Options::useImportDefer() = true;
// Upstream enabled Temporal by default; keep it off in Bun until
// the remaining integration work lands. BUN_JSC_useTemporal=1
// re-enables it for opt-in testing.
JSC::Options::useTemporal() = false;
JSC::dangerouslyOverrideJSCBytecodeCacheVersion(getWebKitBytecodeCacheVersion());

#ifdef BUN_DEBUG
Expand Down Expand Up @@ -3324,7 +3328,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 @@ -6606,8 +6606,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
9 changes: 7 additions & 2 deletions src/jsc/bindings/webcore/SerializedScriptValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6403,11 +6403,16 @@ ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalOb
if (auto arrayBuffer = toPossiblySharedArrayBuffer(vm, transferable.get())) {
if (arrayBuffer->isDetached() || arrayBuffer->isShared())
return Exception { DataCloneError };
if (arrayBuffer->isLocked()) {
auto scope = DECLARE_THROW_SCOPE(vm);
if (arrayBuffer->isWasmMemory()) {
throwVMTypeError(&lexicalGlobalObject, scope, errorMessageForTransfer(arrayBuffer));
RELEASE_AND_RETURN(scope, Exception { ExistingExceptionError });
}
// No generic isDetachable() gate: Bun's native borrows call
// ArrayBuffer::pin(), which clears isDetachable() without setting
// the lock flag. A pinned buffer falls through so transferTo()
// takes its copyTo() fallback (see bindings.cpp
// JSC__JSValue__pinArrayBuffer). WebAssembly.Memory stays rejected
// above per the spec's [[ArrayBufferDetachKey]] requirement.
arrayBuffers.append(WTF::move(arrayBuffer));
continue;
}
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
2 changes: 1 addition & 1 deletion src/runtime/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1202,7 +1202,7 @@ pub(crate) fn __bun_release_task_at_shutdown(task: bun_event_loop::Task) -> bool
// completion) enqueued this after the event loop's last tick. The
// dispatch arm above would have `delete`d it; mirror that here so the
// re-queue path doesn't keep it alive past worker VM dealloc. Runs
// before JSC teardown, so ~Ref<TicketData> is safe.
// before JSC teardown, so ~Ref<Ticket> is safe.
task_tag::JSCDeferredWorkTask => {
unsafe extern "C" {
fn Bun__deleteDeferredWorkTask(task: *mut JSCDeferredWorkTask);
Expand Down
25 changes: 25 additions & 0 deletions test/js/bun/jsc/temporal-global.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";

// Upstream WebKit enabled Temporal by default; Bun overrides useTemporal to
// false in ZigGlobalObject.cpp until the remaining integration work lands.
// BUN_JSC_useTemporal=1 re-enables it for opt-in testing.
test("Temporal is not exposed by default", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", `process.stdout.write(typeof Temporal)`],
env: { ...bunEnv, BUN_JSC_useTemporal: undefined },
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: "undefined", stderr: expect.any(String), exitCode: 0 });
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});

test("Temporal is exposed when BUN_JSC_useTemporal=1", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", `process.stdout.write(typeof Temporal + " " + typeof Temporal.Now.instant)`],
env: { ...bunEnv, BUN_JSC_useTemporal: "1" },
stderr: "pipe",
});
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 });
});
69 changes: 69 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,72 @@ 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 (e) {
expect((e as Error).message).toBe("boom");
caughtStore = als.getStore();
}
Comment thread
robobun marked this conversation as resolved.
});
expect(caughtStore).toBe("STORE_X");
});
});
35 changes: 35 additions & 0 deletions test/js/web/workers/structured-clone.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { openSync } from "fs";
import { bunEnv, bunExe, tls } from "harness";
import { createPrivateKey, createPublicKey, createSecretKey, KeyObject, X509Certificate } from "node:crypto";
import { BlockList } from "node:net";
import { deflate } from "node:zlib";
import { join } from "path";

// Terminal object types that were never entered into the structured clone object
Expand Down Expand Up @@ -449,6 +450,40 @@ for (const structuredCloneFn of [structuredClone, jscSerializeRoundtrip, jscSeri
structuredCloneFn(buffer, { transfer: [buffer] });
}).toThrow(DOMException);
});
// Bun's native borrows call ArrayBuffer::pin(), which makes the buffer
// non-detachable without setting the C-API lock flag. Transferring a
// pinned buffer must copy via transferTo()'s copyTo() fallback, not
// throw (see bindings.cpp JSC__JSValue__pinArrayBuffer). Locks this in
// so a future WebKit sync that re-adds upstream's !isDetachable() gate
// in SerializedScriptValue::create fails CI.
test("A Bun-pinned ArrayBuffer copies on transfer instead of detaching", async () => {
const ab = new ArrayBuffer(64);
new Uint8Array(ab).fill(42);
const { promise, resolve, reject } = Promise.withResolvers<void>();
// Starting the async deflate pins ab for the duration of the call.
deflate(new Uint8Array(ab), e => (e ? reject(e) : resolve()));
try {
const clone = structuredCloneFn(ab, { transfer: [ab] });
expect({
cloneLength: clone.byteLength,
origLength: ab.byteLength,
sameObject: clone === ab,
cloneFirst: new Uint8Array(clone)[0],
}).toEqual({ cloneLength: 64, origLength: 64, sameObject: false, cloneFirst: 42 });
} finally {
await promise;
}
Comment thread
robobun marked this conversation as resolved.
expect(ab.byteLength).toBe(64);
});
// WebAssembly.Memory buffers carry a non-undefined [[ArrayBufferDetachKey]]
// and must be rejected from a transfer list (per HTML's
// StructuredSerializeWithTransfer), unlike a Bun-pinned buffer above.
test("A WebAssembly.Memory buffer is rejected from the transfer list", () => {
const mem = new WebAssembly.Memory({ initial: 1 });
const buf = mem.buffer;
expect(() => structuredCloneFn(buf, { transfer: [buf] })).toThrow(TypeError);
expect(buf.byteLength).toBe(65536);
});
// https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
// Serializing (not transferring) a detached ArrayBuffer must throw a
// "DataCloneError" DOMException, not a TypeError.
Expand Down
Loading