I ran the same photo through two browsers, then disabled nine browser features one at a time to find out what it actually needs. Four of them turned out to be load-bearing.
Short answer: four ordinary things — Web Workers, a 2D canvas that can write a JPG (toBlob), blob URLs, and plain JavaScript. That is the whole list, and I confirmed it by breaking each one: disabling Worker, toBlob, getContext("2d") or createObjectURL makes the conversion fail within about a second, while disabling createImageBitmap, fetch, TextDecoder or FinalizationRegistry changes nothing.
What I measured on this site: the same HEIC — 41,465 bytes, 700×476 — converted at the default 85% in Chrome 153 and in Edge 153. Chrome took 1,264 ms, Edge 1,488 ms, and both produced a 38,078-byte JPG with an identical SHA-256 (93859de257dc44a7…). Two different browsers, one file out. I also read through the 2,999,737-byte decoder the page ships: it contains zero references to WebAssembly, ImageDecoder, VideoDecoder, SharedArrayBuffer or OffscreenCanvas.
| Browser | Convert time | Wall clock | JPG out | SHA-256 |
|---|---|---|---|---|
| Chrome 153 | 1,264 ms | 1,309 ms | 38,078 bytes | 93859de257dc44a7… |
| Edge 153 | 1,488 ms | 1,580 ms | 38,078 bytes | 93859de257dc44a7… |
Both are the same engine underneath, so identical output is what you would hope for — but it is worth having measured: the result does not depend on which Chromium browser you use. Both produced 700×476, the full resolution of the source.
This is the useful part, because it says what a browser has to provide. Each row is a fresh page with one browser feature disabled before any page script ran, then the same file pushed through:
| Feature disabled | Result | Time | What the page said |
|---|---|---|---|
| Nothing (control) | Converted — 38,078 bytes | 1,310 ms | — |
Worker | Failed | 117 ms | Worker disabled |
createImageBitmap | Converted — 38,078 bytes | 1,453 ms | — |
canvas.toBlob | Failed | 1,077 ms | canvas.toBlob is not a function |
canvas.getContext("2d") returns null | Failed | 1,233 ms | Cannot read properties of null (reading 'putImageData') |
fetch | Converted (JPG produced) | 972 ms | — |
URL.createObjectURL | Failed | 81 ms | URL.createObjectURL is not a function |
FinalizationRegistry | Converted — 38,078 bytes | 1,077 ms | — |
TextDecoder | Converted — 38,078 bytes | 760 ms | — |
Read the failures in order of how quickly they arrive. URL.createObjectURL kills it in 81 ms — the page cannot even hand you the finished file. Worker kills it in 117 ms, before any decoding starts. The canvas failures come later, after the decode has already happened: the pixel data is there, and it is the writing out that breaks. That last one is the clearest evidence of the pipeline — decode in a worker, draw to a canvas, encode the canvas to JPG, hand it over as a blob URL.
One footnote on the fetch row: the conversion succeeded and the download link appeared, but I could not measure the output size in that run, because my measuring code uses fetch too.
I counted occurrences in the 2,999,737-byte decoder file the page ships and in the page’s own 14,630 bytes:
| Feature | In the decoder | What that means |
|---|---|---|
WebAssembly | 0 | No WASM module is loaded at runtime |
ImageDecoder / VideoDecoder (WebCodecs) | 0 | It does not borrow the operating system’s HEIC support |
SharedArrayBuffer / Atomics | 0 | No shared-memory threading, so no special headers needed |
OffscreenCanvas | 0 | Ordinary canvas is enough |
FileReader | 0 | Not used |
crypto.subtle | 0 | No hashing or encryption |
The word WebAssembly does appear four times in the page — all of it in the site’s own description text, none of it in the decoder. That matters for one specific reason: a converter built on WebCodecs would depend on whether your operating system can decode HEIC, which is exactly the thing that fails on Windows without Apple’s codec or on Linux without the right package. This one carries its own decoder, which is why the same page behaves the same on machines with no HEIC support at all — the reasoning behind the macOS comparison and the Android, Chromebook and Linux runs.
No. Zero references in the decoder, and disabling browser features it does rely on never surfaced a WASM dependency. The decoder is JavaScript on the wire — it is an Emscripten build with isWasm2js set, meaning libheif was compiled to WebAssembly and then translated into JavaScript.
No. Nothing in the decoder calls WebCodecs or any OS decoder, so the page does not care whether your machine can open a HEIC file. That is measured in this page’s API scan, not inferred.
I do not know. Neither was tested today — no Mac for Safari, and I could not automate the installed Firefox. I would rather say that than guess.
Because that is where the decoding happens — disabling the Worker constructor fails the conversion in 117 ms, before anything else runs. It also explains a side effect I found in the mobile measurements: slowing the page’s main thread barely changes the conversion time, because the work is not on it.
Nothing in the list of required features is behind a flag in the browsers I tested: Worker, 2D canvas, toBlob and blob URLs all worked in a stock Chrome and a stock Edge with no settings touched.
Whichever browser you are in: open the converter and drop a HEIC in. It brings its own decoder, so your operating system does not need one.
First published: 27 September 2026