Repository navigation
Conversation
…lice The File API declares the `type` of the options of `new Blob()` and `new File()`, and the contentType of `slice()`, as a DOMString: `undefined` is absent, and ToString converts any other value. The three bodies first tested `is_string()`, which is true for a string and for a String object, and dropped any other value with no error. The two constructors now convert every `type` that is present. `slice` converts every contentType that is not `undefined`, after it picks Bun's `slice(type)` and `slice(start, type)` forms.
The two constructors had the same block. No behaviour changes.
`options.get(global, "type")` makes and frees a StringImpl at every call to look the name up. `fast_get` does the same lookup with the `BuiltinName` that exists for `type`.
The rows of `new Blob()` and of `Blob.prototype.slice` said that the `type` member and the operands of `slice` are only inspected. A conversion to a string runs the code of an object. That was already so on main for a String object with a `toString` of its own: a compiled program dropped such a conversion in a call whose result nothing uses. The two declarations are the lines that #47605 has for them, so the two changes merge in either order. Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
The comment said that `type` is not yet implemented.
|
Status: ready for review. One question for a maintainer is the first item under Downsides ( How I reproduced it. On main 3f3a9ff (release and debug builds), against Node.js v26.3.0: import { MIMEType } from "node:util";
const type = new MIMEType("text/html");
console.log(new Blob(["<p>"], { type }).type); // Bun: "" Node.js: "text/html"
console.log(new File(["<p>"], "n", { type }).type); // Bun: "" Node.js: "text/html"
console.log(new Blob(["<p>"]).slice(0, 1, type).type); // Bun: "" Node.js: "text/html"
console.log(new Response(new Blob(["<p>"], { type })).headers.get("content-type")); // Bun: null
CI. Build #136872 is red in 40 test files: 39 under |
|
| Layer / File(s) | Summary |
|---|---|
Convert Blob and File type values packages/bun-types/globals.d.ts, src/codegen/builtins/host/web-classes.builtins.ts, src/runtime/webcore/Blob.rs |
Blob and File type options and Blob.slice content types use string conversion. Blob and File constructors share type-setting logic that accepts content types only when every byte is in the specified printable ASCII range. The Blob type comment now describes its MIME type. |
Validate conversion and argument behavior test/aot/parallel/host-classes/a-blob-converts-*, test/js/web/fetch/blob.test.ts |
Tests cover conversion counts, getter reads, thrown errors, conversion order, inherited types, and which slice argument supplies the content type. |
Verify MIME type propagation test/js/web/fetch/blob.test.ts |
Tests cover MIMEType conversion in BuildArtifact.slice and converted types in Response headers, served responses, and uploads. |
Suggested reviewers: jarred-sumner
Merge Risk: 
Low · up to f0454
-
Type conversion works for nonempty slices, but empty Blob slices still skip conversion and its errors. This is a bounded compatibility gap that should be fixed or explicitly accepted before merging.
Pre-merge checks: 
4
-
Full details: Description check
-
Explanation
The description is complete and directly explains the problem, implementation, behavior changes, scope, known limitations, and verification results. It does not use the exact template headings, but it clearly covers both required topics: what the PR does and how the code was verified.
-
- 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.
There was a problem hiding this comment.
Beyond the inline findings, I also checked the switch from options.get(global_this, "type") to fast_get(..., BuiltinName::Type) in the new set_type_from_options helper: it still walks the prototype chain via getIfPropertyExists and maps undefined to absent, so the WebIDL "only undefined is missing" rule holds and behavior is otherwise unchanged. The ? after to_utf8 runs before any Blob field is touched and no raw pointer is held across the user toString call, and the AOT table marking start/end as converted is consistent with native code re-routing a String object in those positions to contentType.
Extended reasoning...
The change makes Blob/File type and slice contentType ToString-convert non-string values in src/runtime/webcore/Blob.rs, updates the AOT built-ins table and a .d.ts JSDoc, and adds tests; it touches no auth, crypto or injection surface. Four inline findings were posted, and the ruled-out note covers the fast_get semantics, exception propagation and the AOT row that were examined separately.
Findings marked 🟡 are optional suggestions and need no follow-up push.
Additional findings (outside the current diff — GitHub can't attach inline comments there):
-
🟡
src/runtime/webcore/Blob.rs— Callers ofBun.file(path, { type })with a MIMEType object, number or array still get a Blob whose type is the extension-derived default, while the same value innew Blob,new Fileandsliceis now converted. Blob.rs:5406-5409 maps every non-stringtypeto BlobType::Ignored and drops it. After this PR a program that builds a type object once and passes it to bothnew FileandBun.filesees two different types. Fix: convert thetypemember of Bun.file options with the same ToString rule (or document and test that Bun.file intentionally differs).Why this was flagged
Input:
Bun.file("a.bin", { type: new MIMEType("text/made-up") })or{ type: 1 }, entered through thefilehost function at Blob.rs:5369. Blob.bind.ts:54 declares BlobType with anignored: b.anyarm, and Blob.rs:5406-5409 turns that arm into None, so the type falls back to the path-derived MIME type. On the base this is the same, so it is pre-existing. What changes with this PR is that the three siblingtypesites (Blob.rs:1807, Blob.rs:5268 via both constructors) now ToString every present value, so a user who moves the same options object betweennew FileandBun.filenow gets "text/made-up" from one and "application/octet-stream" from the other. The PR text scopes the change to the File API, but REVIEW.md asks for every sibling site sharing the pattern to be fixed or explicitly excluded with a stated reason in the PR; the Blob.bind.ts:53 comment is the only statement of intent. Remedy: ToString-convert Bun.file'stypetoo, or state the exclusion and add a test pinning the divergence.Verification: A program passes a non-string
typetoBun.file(path, { type }). src/runtime/webcore/Blob.rs:5404-5410 mapsBlobType::Ignored(_) => None, so the type stays the extension-derived default. This code is untouched by the diff, soBun.filealone behaves exactly as on the base. The new test matrix at test/js/web/fetch/blob.test.ts:145-153 does not coverBun.file(path, { type }). -
🟣
src/runtime/webcore/Blob.rs— pre-existing: callers slicing an empty Blob get no conversion of contentType, unlike every other entry point this PR converts. Blob.rs:1765-1771 returns before the new!content_type.is_undefined()branch at Blob.rs:1807 runs, sonew Blob([]).slice(0, 0, Symbol())does not throw, atoStringis never called, and atypegetter side effect never happens, while a one-byte receiver throws or converts. The AOT row now declaressliceasoperandCodeOnlywith contentTypeconverted, which this path does not do. Fix: convert contentType (and run its code) before the size-0 early return, or at least convert on that path, so all slice receivers behave the same; the WPT casenull type Blob sliceneeds this too.Why this was flagged
Caller invokes
blob.slice(start, end, contentType)on a receiver whoseself.size.get()is 0, for examplenew Blob([]).slice(0, 0, { toString() { throw err } })ornew Blob([]).slice(0, 0, Symbol()). At src/runtime/webcore/Blob.rs:1765-1771 the method returns an empty Blob immediately, never reaching the new conversion at Blob.rs:1807-1814, so no user code runs and no TypeError is thrown, whereasnew Blob(["a"]).slice(0, 0, Symbol())throws "Cannot convert a symbol to a string". The base branch has the same early return, so the inconsistency is pre-existing; the PR description says the web-platform-testnull type Blob slicestill fails for this reason and defers it to #47335. The new table row in src/codegen/builtins/host/web-classes.builtins.ts:455-460 declares the operand code runs, which is not true on this path (safe for the optimizer, but the observable behaviour differs from the constructors).Verification:
slice()on a Blob/File of size 0 with a non-undefined contentType. src/runtime/webcore/Blob.rs:1765-1771: the size-0 early return runs before the conversion branch at 1807. Sonew Blob([]).slice(0, 0, Symbol())returns an empty Blob with no TypeError, while a one-byte receiver throws. The base branch fails identically; the new tests in test/js/web/fetch/blob.test.ts only use non-empty receivers. -
🟣
src/runtime/webcore/Blob.rs— Callers passing a non-number start or end to slice (an object with valueOf, a BigInt, a numeric string) still get a silently wrong range, and this PR now tells the AOT compiler those operands are converted. Blob.rs:1787 and Blob.rs:1796 read start/end only via is_number(), soslice({valueOf:()=>1}, 2)slices from 0 andslice(1n)returns the whole blob, while the File API declares[Clamp] long long(ToNumber). Fix: convert start and end with a coercing to_number()? before clamping, matching theconvertedrow the PR adds at web-classes.builtins.ts:458, and test object, BigInt and string-number inputs.Why this was flagged
Input:
blob.slice(obj, 2)where obj is{ valueOf() { return 1 } }, orblob.slice(1n, 3n), from any user code calling Blob.prototype.slice. Blob.rs:1787if start.is_number()and Blob.rs:1796if end.is_number()skip any non-number without conversion, so relative_start stays 0 and relative_end stays size. The base branch has identical lines (the diff hunk at 1803 leaves 1787-1803 untouched), so this is pre-existing, but the PR changes the sibling guard at Blob.rs:1807 to spec ToString for contentType and rewrites the same function's AOT row at web-classes.builtins.ts:458 tostart: converted, end: converted, deleting the comment 'An operand of another kind than a number or a string counts as absent'. The table now describes a conversion native code does not perform. Node.js and browsers slice from 1 for both inputs; Bun returns bytes 0..2 or the whole blob. The whole-class rule in REVIEW.md asks for every operand of the same WebIDL signature to be fixed together. Remedy: ToNumber-convert start/end (to_number(global)? then truncate/clamp) in slice and keep the table row honest.Verification: pre-existing. Triggering condition: any user code passes a non-number, non-string value as
startorendtoBlob.prototype.slice. Mechanism verified at src/runtime/webcore/Blob.rs:1787if start.is_number() {and :1796if end.is_number() {— any value that is not a JS number is skipped with no ToNumber conversion. The base branch fails identically by the same route.
| if let Some(content_type) = options.fast_get(global_this, jsc::BuiltinName::Type)? { | ||
| let content_type_str = content_type.to_utf8(global_this)?; |
There was a problem hiding this comment.
🔴 Programs that pass a MIME lookup result as type now upload Content-Type: null or Content-Type: false where the base sent no header. set_type_from_options at Blob.rs:5268 calls to_utf8 on every present value, so null and false become the text "null" / "false" and pass is_valid_blob_type. This follows from the stated DOMString purpose and the author flags it as a maintainer decision, but it is a silent wire-level break for servers that reject unknown media types. Fix: a maintainer must accept it explicitly; if the spec behaviour stays, say in the type JSDoc at packages/bun-types/globals.d.ts:1569 and the release notes that null/false become "null"/"false" and are sent as the Content-Type. [also at: src/runtime/webcore/Blob.rs:1807 - Programs that slice with a null or false contentType now get a sub-Blob typed "null" or "false" and upload Content-Type: null, where the base inherited or dropped the type.]
Why this was flagged
A program does new File([buf], name, { type: mimeTypes.lookup(name) }) or new Blob([body], { type: res.headers.get("content-type") }) and the lookup misses, giving false or null. On the base, jsdom_blob_construct/jsdom_file_construct tested content_type.is_string() first and ignored the value, so .type was "" and a fetch upload carried no Content-Type. After the change, set_type_from_options at src/runtime/webcore/Blob.rs:5268 runs to_utf8 on the value, "null"/"false" is all printable ASCII so is_valid_blob_type at Blob.rs:74 accepts it, and Blob.rs:5271-5272 store it as the type. A receiving server that validates Content-Type now gets a 415-class failure that it did not get before. No guard in the diff prevents it; the PR description lists it under Downsides as a question for a maintainer, and the .d.ts hunk at packages/bun-types/globals.d.ts:1569 does not mention it.
Verification: set_type_from_options at src/runtime/webcore/Blob.rs:5262-5276 calls content_type.to_utf8(global_this)? (giving "null"/"false") and is_valid_blob_type at Blob.rs:74-76 only checks printable ASCII, so these pass and content_type_was_set is set. Base code (removed hunks at old lines 2159-2170 and 5318-5329) guarded with content_type.is_string(), leaving .type === "".
There was a problem hiding this comment.
Agreed: a maintainer must accept this. It is the first item under Downsides, and the Notes have the table of what changes on the wire.
The facts for that decision:
- WebIDL takes only
undefinedas absent for aDOMString. Node.js v26.3.0 gives"null"and"false"at the three sites. web-platform-tests asserts"null"forslice(null type Blob sliceinFileAPI/blob/Blob-slice.any.js). - The cost is the one that you name. A program that passed the result of a failed lookup sent no header. It now sends
Content-Type: null.
The code and the JSDoc stay as they are until a maintainer answers, because the two answers need different text. If the conversion stays, I add a sentence on null and false to the JSDoc of BlobPropertyBag.type. If it does not, the constructors keep null and false absent.
This thread stays open for that answer.
|
Replies to the three findings outside the diff in this review. None of them changes the code of this PR.
So Bun's own options already have two rules, and neither is ToString. Which rule they share is a separate decision. The Notes say this under "Not changed". A receiver of size 0. The answer is in this thread. The return drops a string too, so a conversion before it still gives no type. #47335 moves the return below the contentType block. The PR body names the case under Downsides.
On the table row: |
Problem
new Blob(parts, { type }),new File(bits, name, { type })andblob.slice(start, end, type)drop a type that is not a string, with no error. The File API declares each aDOMString.content_type.is_string()first (src/runtime/webcore/Blob.rs:1806,:2161,:5320). No user reported it.Fix
typebutundefined.set_type_from_options, which readstypewithfast_get.bun test --aotfollows #44833's mocks #47605.test/js/web/fetch/blob.test.ts(29 of 46 new tests fail on main), two AOT programs, 18 more suites. Self-reviewed: 27 concerns raised, 24 addressed (Notes).Background
undefinedas absent:nullgives"null", a Symbol throws.lastModifiedin the same options converts already (File: normalize lastModified option (NaN->0, null is present) #33922).bun build --compile --aotwhat a built-in runs. With the old rows it removes an unused conversion.BlobContentType: a type inside aJsResultcost 43 cycles per typed slice.Downsides
type: nullandtype: falsenow give"null"and"false", as in Node.js, andfetchsendsContent-Type: null. Before, Bun sent no header.headers.get("content-type")givesnull.toStringthat throws, now throws. Not fixed:sliceof an empty Blob returns before it reads an operand and drops every type, a string too (Blob.slice: type the slice likenew Blob(parts, { type })#47335).blob.slice()421 to 415 instructions). Binary text: 140,286,972 to 140,286,716 bytes.Notes
Where this comes from. No user reported it. A comparison of Bun with Node.js found it. It is the same in Bun 1.3.14, 1.4.4 and main.
new Blob([], { type: new MIMEType("text/made-up") }).type"text/made-up""""text/made-up"new Blob([], { type: 1 }).type"1""""1"new File([], "n", { type: ["text/made-up"] }).type"text/made-up""""text/made-up"new Blob(["ab"]).slice(0, 1, { toString: () => "text/made-up" }).type"text/made-up""""text/made-up"new Blob([], { type: null }).type"null""""null"new Blob([], { type: false }).type"false""""false"new Blob([], { type: Symbol() })""new Blob([], { type: undefined }).type""""""Visible changes.
typeor contentType that is not a string and notundefinedis converted with ToString. A number, a boolean, a BigInt andnullgive their text. An object gives what itsSymbol.toPrimitive,toStringorvalueOfgives. A Symbol, and an object with no way to a primitive, throw a TypeError. An exception fromtoStringpropagates and no Blob is made. The conversion runs once, inside the call. The text is then checked and lowercased as a string is.new Blob([typedBlob], { type: 1 })kept the type of the part, andtypedBlob.slice(0, 1, 1)kept a registered type of the receiver. A value whose text is empty ([],{ toString: () => "" }) now does what""does.The question for a maintainer.
nullandfalseare the values that existing code can pass:headers.get("content-type")givesnullfor an absent header, and a lookup of a MIME type can givenullorfalsewhen it finds nothing.typegiven tonew Blobnull.typeis"", afetchupload has noContent-Type"null",Content-Type: nullfalse"", noContent-Type"false",Content-Type: falsenull, for aFilein aFormDatabodyContent-Type: application/octet-streamContent-Type: null"null"forslice(null type Blob slice).lastModified: nullin the same options is a present value since File: normalize lastModified option (NaN->0, null is present) #33922.JSValue::get_stringish_allow_emptyis the getter in the tree that takesnullandfalseas absent and converts the rest. It reads by name, so it cannot serveslice.Reference.
dictionary BlobPropertyBag { DOMString type = ""; ... }andBlob slice(optional [Clamp] long long start, optional [Clamp] long long end, optional DOMString contentType). WebIDL converts a DOMString with ToString and takes onlyundefinedas absent.testharness.js(main to this PR):FileAPI/file/File-constructor.any.js42 to 43 of 49 (Property bag propagates exceptions).FileAPI/blob/Blob-slice.any.jsstays at 143 of 150:null type Blob slicehas a receiver of size 0 and needs Blob.slice: type the slice likenew Blob(parts, { type })#47335 too.FileAPI/blob/Blob-constructor.any.jsstays at 56 of 73:options properties should be accessed in lexicographic ordernow seestypeconverted and still fails, because Bun does not readendings.sliceoflib/internal/blob.js.toStringandvalueOfcall at the three entry points prints 27 lines. 20 of them differ from Node.js v26.3.0 on main, and 18 on this PR. The 18 are all items of "Not changed" below: the receiver of size 0 (3), Bun's forms ofslice(5), the types that Bun takes from a part or from the receiver (5),startandend(1),endings(1), the order innew File(1), andtypeonObject.prototype, which Bun does not read (2).Order.
new Blobconverts its parts, then readstype, then converts it. A part that throws leavestypeunread.new Filereads and convertstypebefore it readslastModified.sliceconverts only the operand that is its contentType: inslice("a/b", 1, object)the string is the contentType and the object is not read.slicereads the size of the receiver before it converts, as it does on main for a String object.The table of built-ins.
bun build --compile --aotreads it (src/codegen/builtins/host/web-classes.builtins.ts).bun test --aotfollows #44833's mocks #47605 has: the options ofnew Blob()convert theirtypemember, andBlob.prototype.slicecan run the code of its three operands. A String object in the place ofstartorendis the contentType, so those two are in it.toStringof its own, which main converts.test/aot/parallel/host-classes/a-blob-converts-a-string-object-whose-tostring-nothing-else-calls.jsshows it: on main the compiled program prints1 1 1 1 5where plain Bun prints1 2 3 4 5.test/aot/builtins/differential.test.tsfails forBlob/3andBlobPrototypeSlice/0("code of an operand ran: get @@toPrimitive on argument 2"), and both new programs fail. With the new rows (release build,AOT_BUILTINS_FILTER=Blob): 76 pass, 0 fail, no new line innot-checked.txt.bun test --aotfollows #44833's mocks #47605. It is in this PR so that CI is green without AOT: hangs, wrong answers, failed builds;bun test --aotfollows #44833's mocks #47605. A trial merge of this head with the head of AOT: hangs, wrong answers, failed builds;bun test --aotfollows #44833's mocks #47605 (22621ef) has no conflict.namesetter is wrong on main in the same way. AOT: hangs, wrong answers, failed builds;bun test --aotfollows #44833's mocks #47605 corrects it. This PR does not change it.FileandBuildArtifact.slicehave no hand-written record.Measurements. Release builds (
bun run build:release) of main 3f3a9ff and of this PR, linux-x64.perf,valgrind,straceandbloatyare not installed, so the counts come fromgdband fromperf_event_open.Instructions / mallocs / frees per host call. A
gdbscript single-steps 7 calls from the entry of the host function to its return, after 200 calls, and counts the entries of the allocator. The table gives the mode of the 7.smallisnew Blob([JSON.stringify("hello world ")]),typedisnew Blob(["abcdef"], { type: "text/plain" }),fileisnew File(["abc"], "n.txt"),emptyisnew Blob([]),ois{ toString() { return "x/custom" } }.small.slice()small.slice(0, 5)small.slice(0, 5, "")small.slice(0, 5, "x/custom")small.slice(0, 5, "text/html")small.slice("x/custom")small.slice(0, 5, new String("x/custom"))typed.slice()file.slice(0, 2)new Blob(["abc"])new Blob(["abc"], {})new Blob(["abc"], { type: "" })new Blob(["abc"], { type: "x/custom" })new Blob(["abc"], { type: "text/html" })new Blob(["abc"], { type: new String("x/custom") })new File(["abc"], "n", { lastModified: 1 })new File(["abc"], "n", { type: "x/custom", lastModified: 1 })new File(["abc"], "n", { type: "text/html", lastModified: 1 })empty.slice()empty.slice(0, 0)empty.slice(0, 0, "")empty.slice(0, 0, "x/custom")empty.slice(0, 0, "text/html")small.slice(0, 5, o)small.slice(0, 5, 1)small.slice(0, 5, null)new Blob(["abc"], { type: o })new Blob(["abc"], { type: 1 })new Blob(["abc"], { type: null })new File(["abc"], "n", { type: o, lastModified: 1 })sliceis main's code with another guard: the test forundefinedis shorter than the test for a string.fast_getcommit:options.get(global, "type")made and freed aStringImplat every call to look the name up.new FilewithoutlastModifiedreads the clock, and that read does not take the same number of steps twice undergdb. So theFilerows givelastModified.Cycles per call in user mode (
perf_event_open: the counts of a loop of 1.6 million calls minus those of 400 thousand, one pinned core,BUN_JSC_useConcurrentJIT=0,BUN_JSC_useConcurrentGC=0,BUN_JSC_numberOfGCMarkers=1). Median of 15 runs, with the three binaries taken in turn. The second run of main shows the noise.small.slice()small.slice(0, 5)small.slice(0, 5, "")small.slice(0, 5, "x/custom")small.slice(0, 5, "text/html")small.slice(0, 5, "image/png")small.slice("x/custom")typed.slice()empty.slice()empty.slice(0, 0, "")new Blob(["hello world"])new Blob(["abc"], {})new Blob(["abc"], { type: "x/custom" })new Blob(["abc"], { type: "text/html" })new File(["abc"], "n", { lastModified: 1 })new File(["abc"], "n", { type: "text/html", lastModified: 1 })small.slice(0, 5, o)new Blob(["abc"], { type: o })Code bytes (
llvm-nm -S):BlobExt::slice1195 to 1114,BlobExt::constructor1072 to 673,JSDOMFile__construct1436 to 1111, the newset_type_from_options488,BlobContentType::from_script304 on both. Together 4007 to 3690. Release binary text (size): 140,286,972 to 140,286,716 bytes.What the self-review cut. The first version of this change was larger.
BlobContentTypethat returnedJsResult<Option<BlobContentType>>. It ran fewer instructions than main and was slower:slice(0, 5, "x/custom")went from 390 to 433 cycles. The type is 40 bytes, and the error of aJsResultis one byte inside them, so the compiler copied the type with 16-byte loads at odd offsets, and no 8-byte store is forwarded to such a load. The review then cut the conversion to the three guards on main's text.BlobContentTypeandis_valid_blob_typeare not touched.slicefor a receiver of size 0 too. That is the change of Blob.slice: type the slice likenew Blob(parts, { type })#47335, and it gaveslicea second rule for the mark that a caller gave the type. It is out.slice(0, 5, "")310 to 229 cycles,slice(0, 5, "text/html")397 to 372) and neither is this fix. They are out.bun test --aotfollows #44833's mocks #47605: it is here, in the lines of AOT: hangs, wrong answers, failed builds;bun test --aotfollows #44833's mocks #47605, because the differential test fails without it. It asked to vendor the three files of web-platform-tests: Blob.slice(): convert start/end as WebIDL [Clamp] long long (round half to even) #41961 vendorsBlob-slice.any.jswith a driver, and a second copy would collide. It asked for a maintainer's answer onnullbefore this PR opens: the question is the first Downside.Not changed.
slicereturns before it reads an operand (Blob.rs:1765). The slice has no type, a string included, and no operand code runs:new Blob([]).slice(0, 0, Symbol())does not throw. Blob.slice: type the slice likenew Blob(parts, { type })#47335 moves that return below the contentType block. A conversion before the return would still give no type, sonew Blob([]).slice(0, 0, type)is not among the receivers of the new tests: it needs both PRs.startandendofslicethat are not numbers count as absent. WebIDL converts them with ToNumber.test/js/web/fetch/blob.test.ts:61pins the present rule:blob.slice(Symbol(), "-123")does not throw, and"-123"is a contentType there. Blob.slice(): convert start/end as WebIDL [Clamp] long long (round half to even) #41961 leaves that question to a maintainer.slice(type)andslice(start, type)are chosen by a string, or a String object, in the place ofstartorend. An object there is not a type.optionsthat is not an object (new Blob([], 1)) is ignored (Blob.rs:2158,:5315). WebIDL throws a TypeError: 4 subtests ofBlob-constructor.any.jsand 4 ofFile-constructor.any.js.endingsis not read (Blob constructorendingsoption doen't work. #11796).new Fileconvertsnamebefore the bits.blob.slice(0, 1, "image/png"), does not count as typed by the caller, so afetchupload of it has noContent-Type(Blob.slice: mark the slice as typed when it is given a contentType #46516). A convertednew MIMEType("image/png")is the same.new Blob([], { type: "application/x-www-form-urlencoded;charset=UTF-8" }).typekeeps its upper case, on main and here. Node.js lowercases it.Bun.file(path, { type })ignores a type that is not a string.Bun.file("s3://...", { type }),S3Clientandclient.filethrowERR_INVALID_ARG_TYPEfor one, and so doesBun.file(path).write(data, { type }).Bun.write(path, data, { type })does not readtype. These are not members of the File API, and no specification gives them a conversion. The schema ofBun.filedeclares its rule (src/runtime/webcore/Blob.bind.ts:53): "Anything but a string is ignored, and so is""". Itstypereplaces the type that Bun takes from the path. One rule for Bun's own options is a separate decision.keyUsagesof Web Crypto, andredirect,modeandcacheof aRequest.Open PRs on the same lines. Trial merges of this head.
new Blob(parts, { type })#47335: one conflict hunk, the guard line of the contentType block. Its resolution islet blob_content_type = if !content_type.is_undefined() {. After both, the one block serves both receivers, so a slice of a receiver of size 0 converts too. This is read from the two diffs: the merge is not built.knownFailures = ["null type Blob slice"]. That stays true until Blob.slice: type the slice likenew Blob(parts, { type })#47335 and this PR are both in.b.anyand.variadic#46789: one conflict hunk, the block of the Blob constructor. It declaresBlobPropertyBag.typeasb.string.utf8.owned.otherAsNull, which ignores a value that is not a string. After this PR that member has to be a loose string, or the constructor goes back to what it did. The new tests fail if it does.Blob.rs.bun test --aotfollows #44833's mocks #47605, Blob.slice: mark the slice as typed when it is given a contentType #46516, Blob.slice: resolve an unknown file size before counting a negative index from the end #46633: no conflict. http: consolidate the open fetch, Bun.serve, node:http, node:http2 and HTTP/3 pull requests #46528: no conflict in the files of this PR.Earlier attempt. #33023 converted the contentType of
slicewith ToString, andstartandendwith ToNumber. A cleanup of stale pull requests closed it with no review. This PR takes its contentType half.Suites. Debug build of this head, with
BUN_JSC_validateExceptionChecks=1:test/js/web/fetch/blob.test.ts206 pass. These 18 files together: 754 pass, 2 skipped, 5 todo, 0 fail:bun-file,bun-file-read,file-type,bun-serve-static,bun-serve-file,blob-cow,blob-write,blob-array-fast-path,blob-file-name-ownership,structured-clone-blob-file,FormData,FormData-multipart-serialization,test/js/web/fetch/response.test.ts,body-clone,client-fetch,fetch-args,worker_blob,test/js/deno/fetch/blob.test.ts. Release build:bun test --aot test/js/web/fetch/blob.test.ts204 pass, 2 skipped. The 73 programs undertest/aot/parallelthat nameBloborFile: 73 pass.test/internal/builtins-table/and the table tests undertest/aot/builtins/(34 files): 234 pass, 1 skipped.test/integration/bun-types/bun-types.test.ts: 30 pass.test/internal/source-lints/: 195 pass.[human-review] gate passed · iteration 0 · 6 files touched
fails on main (without fix)
passes on PR (with fix)
diff hotspot
gate history · 1 passed · 0 rejected · iteration 0
evidence per changed file