Skip to content

Blob, Request, Response, structuredClone, alert, confirm, prompt: set the length of the Web IDL - #48470

Open
robobun wants to merge 1 commit into
mainfrom
robobun/807682b6/web-idl-function-lengths
Open

robobun wants to merge 1 commit into
mainfrom
robobun/807682b6/web-idl-function-lengths

Conversation

@robobun

@robobun robobun commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • Eleven Web API functions have a length that differs from their Web IDL: Blob.prototype.slice and stream, Request, Request.prototype.clone, Response.prototype.clone, Response.json, Response.redirect, alert, confirm, prompt, structuredClone. Request.length is 0, where the IDL gives 1.
  • Each site has a hand-written number: src/runtime/webcore/Blob.bind.ts:45 and :48, src/runtime/webcore/response.bind.ts:25, :37 and :65, src/jsc/bindings/ZigGlobalObject.lut.txt:6, :12, :16 and :23.
  • An audit found them, not a user report.

Fix

  • Change each number where it is written, and three copies of the same constants.
  • Only length changes, no call result.
  • clearTimeout and clearInterval keep 1 (IDL: 0): as node:timers exports they follow Node.js. TextEncoder.prototype.encode is TextEncoder: set the length of encode to 0, as its IDL requires #48395.
  • Verified: six new tests in five files. Each fails on main. Self-reviewed: 14 concerns raised, 14 addressed (Notes).

Background

  • Web IDL sets length to the length of the shortest argument list: optional and variadic arguments do not count.
  • A length key in a bindgen schema (*.bind.ts) sets the value. Function N sets it in ZigGlobalObject.lut.txt, the global object's property table.
  • Considered a generator rule, typed optional arguments and an IDL audit test. Each still needs these edits: every argument is b.any, so Blob(parts, options) (0) and Request(input, init) (1) look alike.

Downsides

  • Code that reads one of the 14 values gets another number. structuredClone.length becomes 1, and Node.js v26.3.0 has 2.
  • Response.json() and Response.redirect() still accept no argument. WPT idlharness gains one subtest only: it checks configurable first, and these members are not.
  • No cost per call. CI release size: +4.0 KB on bun-linux-x64, -3.0 KB on bun-windows-aarch64, +0.0 KB elsewhere (Notes).
Notes

The 14 values

Function main This PR Web IDL Node v26.3.0
Blob.prototype.slice 2 0 0 0
Blob.prototype.stream 1 0 0 0
Request 0 1 1 1
Request.prototype.clone 1 0 0 0
Response.prototype.clone 1 0 0 0
Response.json 0 1 1 1
Response.redirect 0 1 1 1
alert, confirm, prompt 1 0 0 not in Node.js
structuredClone 2 1 1 2
clone of the request that a Bun.serve route receives (src/jsc/bindings/JSBunRequest.cpp:52) 1 0 as Request.prototype.clone
BuildArtifact.prototype.slice (src/runtime/api/JSBundler.bind.ts:24) 2 0 as Blob.prototype.slice
BuildArtifact.prototype.stream (src/runtime/api/JSBundler.bind.ts:28) 1 0 as Blob.prototype.stream
  • The last three are not Web IDL functions. The request of a route has its own clone, which hides Request.prototype.clone. BuildArtifact is extends Blob in bun-types and has its own slice and stream.
  • File.prototype, Bun.file(), S3 files, node:buffer and undici use the same function objects as the globals, so they need no edit.

Repro

console.log(Blob.prototype.slice.length, Blob.prototype.stream.length, Request.length, Request.prototype.clone.length, Response.prototype.clone.length, Response.json.length, Response.redirect.length, alert.length, confirm.length, prompt.length, structuredClone.length);
Runtime Output
bun 1.4.3-canary.1 (367d939), and a debug build of main 98cda0d 2 1 0 1 1 0 0 1 1 1 2
This branch: main thread, Worker, ShadowRealm, bun build --compile --aot 0 0 1 0 0 1 1 0 0 0 1
Node v26.3.0 0 0 1 0 0 1 1, no dialogs, then 2

The IDL lines (interfaces/FileAPI.idl, fetch.idl and html.idl of web-platform-tests)

Blob slice(optional [Clamp] long long start, optional [Clamp] long long end, optional DOMString contentType);
[NewObject] ReadableStream stream();
constructor(RequestInfo input, optional RequestInit init = {});
[NewObject] Request clone();
[NewObject] Response clone();
[NewObject] static Response json(any data, optional ResponseInit init = {});
[NewObject] static Response redirect(USVString url, optional unsigned short status = 302);
undefined alert();
undefined alert(DOMString message);
boolean confirm(optional DOMString message = "");
DOMString? prompt(optional DOMString message = "", optional DOMString default = "");
any structuredClone(any value, optional StructuredSerializeOptions options = {});
  • Web IDL, "create an operation function": "Let length be the length of the shortest argument list in the entries in S." The steps for an interface object do the same with the constructor.

Where the old values come from

Audit

  • Method: parse the IDL files of @webref/idl 3.86.0 with webidl2. Compute the shortest argument list of each constructor, operation and static operation. Compare it with fn.length of each function that exists on globalThis.
  • 237 functions exist in Bun. On main 17 rows differ (14 functions, three of them under both Window and WorkerGlobalScope). On this branch 5 rows differ: clearTimeout and clearInterval (twice each) and TextEncoder.prototype.encode.
  • The roots of the audit are globalThis. It does not see the three copies in the table above. Those came from a search for the same constants.

Not changed here

  • clearTimeout and clearInterval (1, IDL 0). globalThis.clearTimeout is the same function object as require("node:timers").clearTimeout, and Node.js has 1 for both. test/js/node/__snapshots__/visible-surface.test.ts.snap:2696 and :2710 record the 1.
  • structuredClone is different: no node:* module exports it, in Bun or in Node.js. The landing rules of this repo make the specification and WPT the reference for a Web-standard API, not Node.js, so it gets the IDL value. This is the one value that moves away from Node.js. If a maintainer prefers the value of Node.js, I remove that row.
  • TextEncoder.prototype.encode (1, IDL 0) is TextEncoder: set the length of encode to 0, as its IDL requires #48395, from the same audit. Its table is in src/jsc/bindings/webcore/JSTextEncoder.cpp.
  • Response.json() and Response.redirect() with no argument. Node.js throws TypeError: Response.json: 1 argument required, but 0 found. Bun returns a response for both. The comment on factory in response.bind.ts now states this.
  • The members of Blob, Request and Response are not configurable (Object.getOwnPropertyDescriptor(Blob.prototype, "slice").configurable is false). The IDL and Node.js have true. It is the default of a bindgen class. Make Crypto.prototype members configurable #40617 proposes the change for Crypto.
  • Functions that a JS wrapper in src/js defines: require("node-fetch").Request.length is 2 and .Response.length is 2, require("undici").WebSocket.length is 2. Their length comes from a JS parameter list, and the reference is the npm package.

What WPT idlharness measures

  • test/js/third_party/wpt-streams/resources/idlharness.js is the vendored harness. Its runner covers the streams classes only.

  • A stand-alone run of that harness with interfaces/FileAPI.idl and interfaces/fetch.idl, with the global presented as Window (as the WPT runner of Node.js does):

    bun 1.4.3-canary.1 This branch Node v26.3.0
    FileAPI 37 of 111 37 of 111 51 of 111
    fetch 97 of 161 98 of 161 149 of 161
  • The subtest that changes is Request interface object length.

  • For an operation, the harness asserts configurable before length (idlharness.js:2359, then :2370). So slice, stream, clone, json and redirect fail on "property should be configurable" before and after. Their length is the next assertion.

  • alert, confirm and prompt are Window only. No run of the harness in Bun reaches them.

  • This is why the new tests are hand-written tables, in the form of Move Crypto, TextDecoder, Bun.FileSystemRouter and three more classes to bindgen schemas #45888 ("has the lengths of the IDL").

What did not change

  • 28 call results, recorded on main and on this branch, are identical. They include blob.slice() with 0 to 3 arguments, blob.stream(4), new Request() (throws), Response.json(), Response.json(undefined) (throws), Response.redirect(), request.clone(1, 2) and structuredClone() (throws).
  • Blob.length, File.length, Response.length, Response.error.length, Blob.prototype.write.length and Blob.prototype.writer.length stay 0 2 0 0 2 1.
  • .bind() follows: Blob.prototype.slice.bind(blob).length is 0 (was 2).

Who reads these values

  • A search for these names followed by .length finds no reader in the 81,480 tracked files, outside the new tests.
  • The same search finds none in the 46,901 JS and TS files of the packages under test/node_modules and node_modules.
  • core-js 3.37.1 replaces a native function whose length is wrong. It checks queueMicrotask and URL.canParse and cites queueMicrotask.length should be 1 #9249 and URL.canParse.length should be 1 #9250 for them. It has no such check for these eleven.

Measurements (debug builds of main 98cda0d and of this branch)

  • Generated code: 13 lines change in 4 files, one integer each (GeneratedBlob.cpp 2, GeneratedResponse.cpp 5, GeneratedJSBundler.cpp 2, ZigGlobalObject.lut.h 4). +0 host functions (2649) and +0 table rows (2375). The generated Rust, Bindgen.d.ts and every other generated file are byte-identical (diff -r of build/debug/codegen).
  • Binary size: text, data and bss of bun-debug are 847392166, 79802913 and 12228904 bytes on both (llvm-size). In four of the five recompiled objects, 9 bytes differ in total (cmp -l). In the fifth, setupRequestClassStructure is 3 bytes shorter at -O0.
  • Instructions: 0 change in host functions and thunks. One immediate operand changes in setupRequestClassStructure, which runs once per realm (llvm-objdump -d of the five objects).
  • Release size, from the CI size check of this PR against its merge base (main build 139578, 98cda0d): +0.0 KB on 10 of the 12 targets, +4.0 KB on bun-linux-x64 and -3.0 KB on bun-windows-aarch64. 4.0 KB is one page.
  • test(xml): shrink the two tests that time out on a debug build #48435 changes one test file on the same base. The same check gives it -3.0 KB on bun-windows-aarch64 too, so that value does not come from this change.
  • The page on bun-linux-x64 is not attributed to a section. My container cannot download the release artifacts of CI, and bloaty, perf, valgrind and strace are not installed there.

Tests

  • New: "Blob and File have the lengths of the IDL" and "the slice and stream of a BuildArtifact have the lengths of those of Blob" (test/js/web/fetch/blob.test.ts), "Request has the lengths of the IDL" (test/js/web/request/request.test.ts), "Response has the lengths of the IDL" (test/js/web/fetch/response.test.ts), "alert, confirm, prompt and structuredClone have the lengths of the IDL" (test/js/web/web-globals.test.js), "request.clone, which is not Request.prototype.clone, has the length of the IDL" (test/js/bun/http/bun-serve-routes.test.ts).
  • Each table also has the members whose value is already right, so that they stay right.
  • With src/ of main, bun bd test fails these six tests and passes the other 326 tests of the five files. With this branch all 332 pass.
  • The BuildArtifact test is in blob.test.ts. test/bundler/bun-build-api.test.ts has a test of 400 builds that does not finish in 180 s on a debug build in my container.
  • The inline snapshot Response (8.81 KB) in response.test.ts is the size of that test file. Each change of the file changes it.
  • Also run on the debug build of this branch: test/internal/bindgen.test.ts (244 pass), and the five structured clone files under test/js/web (451 pass, 1 skip).
  • Run with a release bun on this tree: test/internal/builtins-table/ (149 pass, 1 skip), which runs the generators of the table of built-ins on these schemas, and test/integration/bun-types/bun-types.test.ts (30 pass).
  • test/js/node/visible-surface.test.ts has no line for one of the 14 values.

Open PRs on the same lines

Self-review

…b IDL

Web IDL sets the `length` of an operation to the length of its shortest
argument list, and the `length` of an interface object to that of its
constructor. These functions had another value:

  Blob.prototype.slice       2 -> 0
  Blob.prototype.stream      1 -> 0
  Request                    0 -> 1
  Request.prototype.clone    1 -> 0
  Response.prototype.clone   1 -> 0
  Response.json              0 -> 1
  Response.redirect          0 -> 1
  alert, confirm, prompt     1 -> 0
  structuredClone            2 -> 1

Three functions hold a copy of the same constants and get the same
values: `clone` of the request that a `Bun.serve` route receives, and
`slice` and `stream` of a `BuildArtifact`.

No call changes. `clearTimeout` and `clearInterval` keep 1: they are
the exports of `node:timers`, which have 1 in Node.js.
@robobun

robobun commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status

How I reproduced it:

console.log(Blob.prototype.slice.length, Blob.prototype.stream.length, Request.length, Request.prototype.clone.length, Response.prototype.clone.length, Response.json.length, Response.redirect.length, alert.length, confirm.length, prompt.length, structuredClone.length);
  • bun 1.4.3-canary.1 and a debug build of main 98cda0d print 2 1 0 1 1 0 0 1 1 1 2.
  • The debug build of this branch prints 0 0 1 0 0 1 1 0 0 0 1.
  • With src/ of main, bun bd test fails the six new tests. With this branch, the five test files pass (332 tests).

CI on 81d4d8f (build 139757):

  • 184 of 188 jobs passed. They include every test job on Linux (glibc, musl and ASAN), on Windows and on macOS aarch64.
  • Six tests failed one time and passed on the retry. None of them reads one of these values.
  • The four darwin x64 - test-bun jobs did not start. No agent took a job of that lane in any build since about 12:00 UTC on 2026-11-10.
  • The diff is ready for review.

PR: #48470

@robobun

robobun commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 8:52 AM PT - Nov 10th, 2026

✅ @robobun, your commit 81d4d8f4c4703b9816ed86b31b26d70250cff4ab passed in Build #139757! 🎉


🧪   To try this PR locally:

bunx bun-pr 48470

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

bun-48470 --bun

@coderabbitai

coderabbitai Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

This change updates declared JavaScript function lengths for selected Bun globals, request and response APIs, Blob methods, and BuildArtifact methods. Tests add length assertions for these APIs and update one response-size snapshot.

Changes

Function length metadata

Layer / File(s) Summary
Update API function lengths
src/jsc/bindings/JSBunRequest.cpp, src/jsc/bindings/ZigGlobalObject.lut.txt, src/runtime/api/JSBundler.bind.ts, src/runtime/webcore/Blob.bind.ts, src/runtime/webcore/response.bind.ts
Binding declarations change function-length metadata for selected globals, Bun request clone, and web API constructors and methods.
Assert function lengths
test/js/bun/http/bun-serve-routes.test.ts, test/js/web/fetch/blob.test.ts, test/js/web/fetch/response.test.ts, test/js/web/request/request.test.ts, test/js/web/web-globals.test.js
Tests check function lengths for selected APIs. The response test also updates an inline snapshot from 8.0 KB to 8.81 KB.

Possibly related PRs

  • oven-sh/bun#46540: Moves JavaScript-callable Rust functions to bindgen schemas and changes binding-generation machinery used by the binding declarations in this change.

Suggested reviewers: jarred-sumner, dylan-conway

Merge Risk: Minimal · up to 81d4d

The changes align function-length metadata; no merge-blocking risk is established.

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title identifies the Web API function length changes and names the main affected APIs. It is longer than ideal, but it clearly describes the change.
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.
Description check Passed The description explains the problem, the changes, and how the author verified them. It does not use the template’s exact headings, but it covers both required sections with detailed information.
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 98cda0d and 81d4d8f.

Files selected for processing: 10

  • src/jsc/bindings/JSBunRequest.cpp
  • src/jsc/bindings/ZigGlobalObject.lut.txt
  • src/runtime/api/JSBundler.bind.ts
  • src/runtime/webcore/Blob.bind.ts
  • src/runtime/webcore/response.bind.ts
  • test/js/bun/http/bun-serve-routes.test.ts
  • test/js/web/fetch/blob.test.ts
  • test/js/web/fetch/response.test.ts
  • test/js/web/request/request.test.ts
  • test/js/web/web-globals.test.js

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.

@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.

I reviewed this PR and didn't find any bugs; the constant edits and tests look correct. A maintainer should still weigh in because structuredClone.length 2 -> 1 is a deliberate move away from Node.js (the description offers to drop that row), which is a policy call rather than a correctness one.

What was reviewed:

  • Each new value against the quoted Web IDL (shortest argument list), and the bindgen default for the clone helper once its explicit length: 1 is removed (0 required args, requiredCount in src/codegen/bindgen-lib-internal.ts); Response.error does not use the factory helper, so the test's error: 0 is consistent.
  • The 8.81 KB inline snapshot: the post-edit test/js/web/fetch/response.test.ts is 8808 bytes, which SizeFormatter in src/bun_core/fmt.rs renders as 8.81 KB (and the old 8026 bytes as 8.0 KB).
  • Searched test/ for assertions or snapshots on the old lengths (including visible-surface.test.ts.snap): none reference these functions, so nothing stale remains.
  • Not run: the new tests themselves (bun bd test / USE_SYSTEM_BUN=1); failure-on-main is inferred from the old constants, not observed.
Extended reasoning...

The change edits 13 integer length values across one C++ HashTableValue entry (src/jsc/bindings/JSBunRequest.cpp), four rows of src/jsc/bindings/ZigGlobalObject.lut.txt, and three bindgen schemas (Blob.bind.ts, response.bind.ts, JSBundler.bind.ts), plus new .length assertions in five existing test files and one snapshot size update. It touches no security-sensitive surface: no call behavior, argument handling, or memory management changes, only the JS-visible Function.prototype.length. The values match the Web IDL rules cited and the bindgen default was confirmed by reading requiredCount; the snapshot size was verified from the blob size at HEAD. The decision to defer rather than approve rests on the one user-visible compat divergence from Node.js (structuredClone.length) that the author explicitly leaves to maintainer preference, and on the fact that no tests were executed in this review.

This branch has not been deployed

No deployments
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.

1 participant