Toollance

Firefox Runs in WebAssembly Now — Three Lessons for Your Own Port

dev-toolswebassemblyperformance

Puter published Firefox in WebAssembly: Mozilla’s Gecko engine compiled to WASM, rendering the real Firefox interface inside another browser tab. Simon Willison covered it, and it hit the Hacker News front page with 259 points.

As a product, it isn’t one. The WASM payload is around 233MB and the JIT is unreliable. As a case study in porting a large native application to the browser, it’s more useful — because the three hard problems it hit are the same three you’ll hit.

1. Single-process support decides which codebase you can port

The project chose Gecko over Chromium, and the stated reason is that Gecko has strong single-process support.

That’s the constraint most people underestimate. Modern desktop applications are multi-process by design — a main process, worker processes, sandboxed renderers, IPC between them. The browser has no equivalent of fork(). You get one main thread, plus Web Workers with a message-passing boundary and no shared address space beyond SharedArrayBuffer.

If your application can already run meaningfully in a single process, a port is difficult. If it fundamentally requires multiple cooperating processes, you’re not porting it — you’re rewriting its architecture first.

Practical version of this question for your own codebase: can it run today with --single-process or the equivalent? If the answer is no, that’s the first work item, not the WASM build.

2. You cannot open a socket, so you’ll be running a proxy

Gecko’s whole job is opening network connections. A browser tab is not allowed to. It gets fetch() and WebSockets, both bounded by CORS.

The workaround here is to tunnel everything over a WebSocket using the Wisp protocol to a server that opens the real connections. HTTPS still works end-to-end from the browser’s perspective, and the tunnel carries the encrypted traffic.

This is the detail that quietly changes what a WASM port is. A “runs entirely in the browser” application that needs raw sockets is not actually serverless — it comes with infrastructure, bandwidth costs, and a proxy that sees your connection metadata. Any port of a network-oriented tool inherits that.

Tools that operate purely on data you already have don’t have this problem at all, which is why pure computation is where client-side WASM genuinely shines — the same reasoning behind our own JSON formatter and hash generator running entirely in your tab.

3. A JIT inside WASM is possible and currently painful

The project includes a JavaScript-to-WebAssembly JIT compiler — generating WASM at runtime and instantiating it, since WASM has no way to emit native machine code.

The README describes it as experimental and unreliable on many websites, and provides an escape hatch:

GECKO_NOWASMJIT=1

That flag is the honest summary of the state of the art. Dynamic code generation inside WASM works, it’s just not yet at the reliability level where you’d leave it on by default.

The build, if you want to try it

From the repository, the documented requirements:

Emscripten remains the answer for large C++ codebases. The Rust target choice is worth noting — wasm32-unknown-emscripten rather than wasm32-unknown-unknown, because the Rust portions have to link against the same Emscripten runtime as the C++ ones.

What to take from it

Nobody should ship Gecko-in-WASM to users. But the port is a useful upper bound: with Emscripten, a WebSocket tunnel, and a single-process build, you can get a browser engine running in a browser tab.

If your application is smaller than Firefox, needs less network access than Firefox, and generates less code at runtime than Firefox — and almost every application meets all three — then the tooling is not what stands between you and a browser port. The architecture is.

Source: Firefox in WebAssembly by Puter, HeyPuter/firefox-wasm on GitHub, and Simon Willison’s write-up.