LibWeb: Make TransferArrayBuffer zero-copy

Move owned ArrayBuffer storage directly when transferring stream
buffers instead of copying the bytes before detaching the source.
WebAssembly memory continues to copy because its ArrayBuffer wraps
externally-owned storage.

Preserve the abrupt completion from DetachArrayBuffer before moving
storage so non-transferable buffers, such as WebAssembly.Memory-backed
views, still surface TypeError through stream operations instead of
aborting.

This saves ~130ms of main thread time when loading a YouTube video
on my Linux computer. :^)
This commit is contained in:
Andreas Kling
2026-04-24 20:07:33 +02:00
committed by Andreas Kling
parent 59bf30f17f
commit b629914428
Notes: github-actions[bot] 2026-04-25 08:55:16 +00:00
5 changed files with 91 additions and 3 deletions

View File

@@ -0,0 +1,6 @@
read() rejected with TypeError: true
Read buffer byteLength: 65536
enqueue() threw TypeError: true
Enqueue buffer byteLength: 65536
respondWithNewView() rejected with TypeError: true
RespondWithNewView buffer byteLength: 65536

View File

@@ -0,0 +1,63 @@
<!DOCTYPE html>
<script src="../include.js"></script>
<script>
asyncTest(async done => {
{
const stream = new ReadableStream({ type: "bytes" });
const reader = stream.getReader({ mode: "byob" });
const memory = new WebAssembly.Memory({ initial: 1 });
const view = new Uint8Array(memory.buffer, 0, 8);
try {
await reader.read(view);
println("FAIL: read() should reject");
} catch (error) {
println(`read() rejected with TypeError: ${error instanceof TypeError}`);
}
println(`Read buffer byteLength: ${memory.buffer.byteLength}`);
}
{
let controller;
new ReadableStream({
type: "bytes",
start(streamController) {
controller = streamController;
},
});
const memory = new WebAssembly.Memory({ initial: 1 });
try {
controller.enqueue(new Uint8Array(memory.buffer, 0, 8));
println("FAIL: enqueue() should throw");
} catch (error) {
println(`enqueue() threw TypeError: ${error instanceof TypeError}`);
}
println(`Enqueue buffer byteLength: ${memory.buffer.byteLength}`);
}
{
const memory = new WebAssembly.Memory({ initial: 1 });
const stream = new ReadableStream({
type: "bytes",
pull(controller) {
controller.byobRequest.respondWithNewView(new Uint8Array(memory.buffer, 0, 8));
},
});
const reader = stream.getReader({ mode: "byob" });
try {
await reader.read(new Uint8Array(new ArrayBuffer(memory.buffer.byteLength), 0, 8));
println("FAIL: read() should reject after respondWithNewView()");
} catch (error) {
println(`respondWithNewView() rejected with TypeError: ${error instanceof TypeError}`);
}
println(`RespondWithNewView buffer byteLength: ${memory.buffer.byteLength}`);
}
done();
});
</script>