Build1 distinct publisher3 min readUpdated
The HTML spec mandates the silent substitution, blob.type is the only record that it happened, and a user-agent lookup table cannot stand in for the check.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
The substitution survives every guard a careful person already writes. The callback fires, the blob is not null, and its size looks plausible [5]. Nothing in the API reports the swap, because the specification requires the fallback and says nothing about disclosing it: no exception, no warning, no second argument [2]. The single witness is `blob.type`, which on iOS below 16.4 reads `image/png` after you asked for `image/webp` [3].
There is an accident of history worth noting here. `toDataURL` falls back identically, but the string it returns opens with `data:image/png;base64,`, so the mismatch is readable without tooling [4]. The binary path is the quiet one.
The measured numbers are where this stops being theoretical. On desktop Chromium, six requested types produced two real encodes: WebP at 564 bytes and JPEG at 796 [9]. AVIF, HEIC, TIFF and GIF each came back as `image/png` at 95 bytes, and those four blobs were byte-for-byte identical, being the same 2x2 PNG four times over [9][10]. Two thirds of the requested list was mislabelled [15]. A pipeline whose only test is "did I get a blob" scores six successes out of six on that run while writing four wrong files [17]. Even the size heuristic is no help at real dimensions: the fallback is 17 percent of the WebP at the probe's 2x2 size [16], a gap that says nothing about a 4000-pixel export.
The AVIF row is the one that reorders priorities. Chrome decodes AVIF perfectly well in an `img` element and will not produce one from canvas [11], because encode and decode are separate code paths shipped on separate schedules [12]. Safari 16 has the same asymmetry, and nothing mainstream encodes HEIC at all [12]. An export menu offering HEIC is offering PNG with a different suffix.
Which is why the version table is the wrong instrument. Every browser on iOS is WebKit underneath, so "is this Safari" answers nothing useful, and in-app webviews report system versions that do not line up with the standalone browser. A user who enables Request Desktop Website will hand a phone's capabilities over a macOS user agent [6]. The string answers who the client claims to be; the question is whether it can encode WebP at this moment, and engine version, OS version, host app and build flags all sit in between [7]. Canvas has no `isTypeSupported` and no `encodingInfo`, though MediaRecorder and Media Capabilities both do [8], so the information has to be produced rather than looked up: a 2x2 canvas, one semi-transparent pixel so alpha is probed at the same time, one `toBlob` per MIME type, and the comparison `blob.type === mime`, cached [13].
The deeper defect in the original code is naming. If `blob.type` is the only field that tells the truth [3], then it, not the requested MIME, should decide the extension. Code that writes `output.webp` because it asked for WebP is asserting a fact it never verified, and the person who discovers the error is downstream with a hex editor [1].
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
A user reported that the .webp files produced by the author's tool would not open on their desktop; opened in a hex editor, the first four bytes were 89 50 4E 47, a PNG carrying a .webp extension.
The HTML spec explicitly requires that if the user agent does not support the requested type, canvas.toBlob must create the file using the PNG format instead, with no exception, no warning, and no second argument reporting what happened.
blob.type is the only place the substitution is recorded; on iOS below 16.4 a request for image/webp returns a blob whose type is image/png.
toDataURL performs the same fallback, but there it is visible to the naked eye because the returned data URL starts with data:image/png;base64,.
When the fallback occurs the callback still fires, the blob is not null, and its size looks reasonable.
User-agent detection fails on iOS: every browser there is WebKit underneath, embedded webviews inside apps track the system version in ways that do not always match the standalone browser, and a user can enable Request Desktop Website and present a macOS user agent from an iPhone.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Reproducible mechanism, one unverified run
The core mechanism is strong: the substitution is described as spec-mandated behaviour, the detection test is a two-line blob.type comparison anyone can rerun, and a six-format result table with byte sizes is published alongside runnable probe code. Evidence is capped by provenance: one publisher, one author, one desktop Chromium run, with the iOS 16.4 and Safari 16 statements asserted rather than measured.
No adoption data supplied
The cluster contains no deployment, usage-disclosure, release or telemetry evidence about how widely the blob.type check or the probe pattern is used, nor about how many pipelines are affected. The only observation is the author's own probe run on his own tool, which measures browser capability rather than adoption, so no adoption score can be computed without inference.
Claims sized to the evidence
Framing and evidence line up. The headline promise — toBlob can hand you a PNG and only blob.type records it — is exactly what the spec sentence and the probe table show, and the author explicitly narrows scope (a null check is not proof, a 1x1 canvas is not representative, probing costs time). The only mild overreach is generalising from one Chromium run to Safari and HEIC without output, which is offset by understating nothing material.
Practitioner post, mild self-promotion
The author writes as the maintainer of an unnamed conversion tool on a developer-blogging platform; the incentive is reputational and audience-building rather than commercial. No vendor, sponsor, product for sale, pricing or funding interest appears, and the recommendations (check blob.type, cache probes, load open-source WASM codecs, disclose the path taken) point away from any proprietary dependency.
Mechanism solid, breadth unverified
High confidence that the described fallback exists, that blob.type is the signal, and that desktop Chromium refused four of the six requested encodings as reported, because all three are cheap to reproduce and internally consistent. Confidence is held down by the single-publisher cluster, the absence of independent replication, and unmeasured claims about iOS, Safari and HEIC.
build
Pin `formats` before you take the next/image v4 bump1 distinct publisher
build
Your .ai viewer is a pdf.js problem, and its worst bugs never throw1 distinct publisher
build
The exit ping that never left: fetch in beforeunload loses to the browser's unload policy1 distinct publisher
build
The stroke width that never rendered: SVG attributes lose every cascade fight1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 23, 2026