A browser engine
that fits inside
a sandbox.
Prism takes an interactive post written as ordinary HTML, CSS and JavaScript, then parses, styles, lays out, scripts, paints and hit-tests it entirely inside WebAssembly. The browser never sees the markup — it gets back only pixels and a short list of actions.
Don't take my word for it — play it.
This Flappy Bird is running inside the same WebAssembly engine: its HTML, CSS and JavaScript are parsed, laid out, scripted and painted in Rust — the browser just blits the pixels. Click the board or press Space to flap.
Source goes in. Pixels come out.
Everything a browser would normally do with untrusted markup happens on the far side of a hard boundary — and only a framebuffer crosses back.
Browser host (JS / React)
- Fetches fixture HTML + declared assets
new Renderer(html, w, h, dpr)- Drives
tick()every animation frame - Blits returned RGBA with
putImageData - Forwards pointer & key events
- Performs actions: navigation, byte-range fetches
Prism runtime (Rust / WASM)
- Parses HTML into a private node arena
- Resolves the CSS cascade & lays out boxes
- Runs author JS in a locked-down QuickJS engine
- Shapes text, decodes images & video
- Paints to a pixel buffer and hit-tests input
- Emits actions through a one-way queue
This panel is the real renderer.
The canvas below is the genuine article — the prebuilt post_renderer.wasm parses this fixture, resolves its CSS, runs taffy layout, executes the QuickJS script and rasterizes with tiny-skia, all in WebAssembly. Drag the slider: pointer coordinates go in, pixels come out. Everything further down is an illustration of internals the single WASM binary doesn't expose — this is the only panel that's actually the engine.
Fly through a single frame.
Scroll to ride one frame the whole way around the loop — out of the browser, down through every layer of the WASM renderer to a single pixel, and back to the glass. The ring tracks the full circle; the gauge shows how deep you are.
HTML becomes a node arena.
html5ever tokenizes the markup; Prism copies it into a flat SlotMap arena where every node has a stable NodeId. Hover the source or the arena to see the mapping — there is no live DOM, just cells Prism owns.
Folding the cascade into one style.
lightningcss parses every rule; Prism matches selectors and collapses them — by specificity — into a single ComputedStyle per node. Toggle the rules and watch the winner change.
Outlines become alpha pixels.
Three typefaces are baked into the binary with include_bytes!. parley shapes the runs; swash rasterizes each glyph outline into an anti-aliased coverage bitmap, cached by glyph, size and sub-pixel offset. Type below and zoom into the coverage.
Boxes find their place.
Images (PNG/JPEG via zune-jpeg) and <video> contribute intrinsic sizes, then taffy solves the box tree with block, flexbox and grid. Drive the same knobs taffy exposes and watch the boxes resolve live.
Author JS runs in a cage.
The page's <script> runs in QuickJS against a narrow DOM shim. A CPU-budget interrupt handler fires every few hundred operations — so a hostile loop can't hang the tab. Try to break it.
A display list, replayed onto pixels.
render() walks the tree into a flat Vec<PaintCommand>, then replays it onto a tiny-skia Pixmap — back to front, painter's algorithm. Step through the commands and watch a post assemble.
It doesn't repaint the world.
When only a few elements change, Prism computes their damage rectangle, clips to it, and repaints just that region over the previous frame — reusing the rest. Click tiles to "change" them; if damage tops half the viewport it bails to a full repaint.
Input is hit-tested in priority order.
A pointer arrives as bare coordinates. Prism checks targets in a fixed order and dispatches into the sandbox; anything it can't do itself leaves as a queued action. Click a region of the post to trace the cascade.
A whole video player, from scratch.
No browser <video>, no MSE, no WebCodecs, no ffmpeg. Prism demuxes WebM, decodes VP8 to RGBA and Vorbis to PCM, all in WASM. Drag the playhead: seeks snap back to the nearest ◆ keyframe and decode forward. Click the byte bar to stream a range.
The runtime heartbeat.
One module, a lot of engine.
An experimental prototype, built for fun.
Prism is a for-fun side project, not a product. It implements a growing subset of HTML, CSS and JavaScript — there is no full CSS coverage and plenty is unsupported or rough around the edges. It is also not open source (yet).
If any of this is interesting — questions, ideas, or you'd like to collaborate — get in touch: