# Prism: Building a Zero-Chromium Browser Engine in Rust — Architecture, Internals, and Implementation

> Source: <https://dev.to/adityaadi/prism-building-a-zero-chromium-browser-engine-in-rust-architecture-internals-and-implementation-3n8>
> Published: 2026-11-01 12:48:00+00:00

Modern web browsers are among the most intricate software systems in computing history. A functional browser cannot simply display markup; it must ingest network streams, tokenize and parse non-standard HTML, construct a mutable object tree, parse and cascade stylesheets, execute scripting runtimes, compute geometric layouts across multiple rendering paradigms, rasterize primitives into pixel buffers, register spatial interactions, and maintain strict process-level security boundaries.

The vast majority of modern browsers bypass this complexity by wrapping existing engines, predominantly Google’s Chromium codebase or occasionally Apple’s WebKit. Prism takes an entirely different route.

Prism is an independent, modular browser engine written from scratch in Rust. Rather than embedding an existing rendering core, Prism implements its own decoupled pipelines for resource handling, document object model creation, style calculation, display-list generation, software rasterization, and extension sandboxing. Where external technologies are integrated — such as Boa for ECMAScript interpretation and Wasmtime for WebAssembly extension containment — they are isolated within custom coordination layers rather than serving as black-box rendering backends.

The browser engine recently crossed a critical threshold: bootstrapping an end-to-end rendering loop on a live production website (Hacker News), successfully loading network dependencies, building a sanitized DOM, hydrating JavaScript structures, calculating multi-column table layout, and flushing an 800 by 600 framebuffer with hundreds of painted glyphs.

This document breaks down the internal architecture of Prism, tracing how each distinct subsystem is constructed, how data transitions across boundary layers, and where the current limitations lie.

At startup, the shell initializes tab managers and coordinates thread barriers between window events and engine processing tasks. For native display targets, Prism interfaces with the winit crate to manage platform windows, surface creation, and system event loops.

A central engineering requirement for Prism is environment resilience. When executed inside environments devoid of an active display server or Wayland/X11 socket — such as a remote development container or automated Linux CI pipeline — the native compositor path fails gracefully. Prism automatically falls back to an embedded HTTP-based frame-streaming server bound locally to port 3000. This fallback mechanism ensures that the engine can execute its full layout and rasterization sequence headlessly, piping the generated framebuffer bytes directly to a web interface for visual inspection and regression testing without requiring virtual framebuffers like Xvfb.

Prism’s resource subsystem is built on top of native Rust asynchronous networking primitives. When a navigation event is dispatched by the shell:

The root document is retrieved, following any HTTP redirects to resolve the final canonical base URL.

The tokenizer and parser run a discovery pass to extract resource dependencies linked via standard tags (, , <img src="...">).<br>
Relative resource paths are canonicalized against the base navigation URL.<br>
Assets are fetched concurrently through an internal asset-preloading broker.<br>
Asset resolution operates symmetrically across HTML attributes and CSS property declarations. Because styles frequently declare resources via url() tokens (such as background graphics), the resource loader maintains an internal registry keyed by both original relative paths and canonical absolute URLs. When layout and rasterization stages demand image bytes, they resolve them directly from this memory-backed asset pool without blocking on redundant network I/O.</p>
<ol>
<li>HTML Tokenization
The entry point of the document pipeline resides in components/dom/src/tokenizer.rs. This component transforms raw incoming UTF-8 byte streams into discrete syntactic tokens without intermediate allocations where possible.</li>
</ol>
<p>The tokenizer operates via an incremental next_token() interface, supporting both push-based streaming and batch processing over whole documents. It defines a rigorous token set covering:</p>
<p>Start tags, capturing the element name, self-closing state, and raw key-value attribute pairs.<br>
End tags, marking structural termination.<br>
Character data, separating printable text runs from whitespace.<br>
Comments, extracting developer annotations without polluting the render path.<br>
Doctype nodes, identifying strict or quirks-mode document declarations.<br>
End-of-file indicators, terminating state machines cleanly.<br>
A critical design challenge in HTML tokenization is the handling of CDATA, raw text, and entity references. HTML is notoriously forgiving to malformed input, necessitating extensive handling for quoted and unquoted attributes, numeric entities, and named character escapes.</p>
<p>Crucially, Prism implements strict RAWTEXT state management for <script> and <style> blocks. Within these elements, content cannot be tokenized as standard element markup, because characters such as < and > often represent comparison operators or JavaScript logic rather than tag boundaries. The tokenizer switches into a raw-text capture mode until it encounters a matching closing tag sequence, preserving nested code strings for the script and CSS parsers.</p>
<ol>
<li>HTML Parser and Stack-Based DOM Construction
Tokens generated by the tokenizer are fed directly into the hierarchical document parser located in components/dom/src/parser.rs.</li>
</ol>
<p>The parser utilizes an explicit open-element stack to reconstruct tree hierarchies. When a start-tag token arrives, the parser instantiates a node, links it to the current stack head as its child, and pushes the new node onto the stack. When a matching end-tag token arrives, nodes are popped until the matching scope is closed.</p>
<p>Because real-world markup frequently deviates from valid XML structures, the parser implements standard HTML structural recovery heuristics:</p>
<p>Implicit Paragraph Termination: Opening a block-level element inside an active <p> tag automatically generates an implicit closing token for the paragraph.<br>
List and Definition Management: Consecutive <li>, <dt>, and <dd> items auto-close preceding siblings without requiring explicit closing tags.<br>
Implicit Table Wrapping: The HTML specification requires that <tr> elements residing directly within a <table> must be wrapped inside an implicit <tbody> element. Prism checks table child states and dynamically synthesizes <tbody> nodes on the fly. Real-world sites such as Hacker News depend heavily on this implicit wrapping behavior to ensure table geometries do not collapse.<br>
Beyond full-document parsing, the subsystem provides a specialized fragment-parsing path. This allows the JavaScript engine to mutate innerHTML properties dynamically, running partial markup strings through the parser and grafting the resulting subtrees into the live DOM tree.</p>
<ol>
<li>The DOM Engine
Prism’s in-memory document model resides in components/dom/src/lib.rs. It serves as the primary source of truth across the engine, interconnecting the HTML parser, the JavaScript execution context, the CSS style resolver, and the layout tree.</li>
</ol>
<p>The tree consists of element nodes, text nodes, and comment nodes. Element nodes preserve tag identities, parent pointers, sequential sibling chains, and key-value attribute maps.</p>
<p>To avoid stack-overflow crashes when processing deeply nested real-world DOM trees, all critical DOM traversal, node searching, serialization, and mutation functions are implemented iteratively using heap-allocated work stacks rather than naive recursive functions.</p>
<p>Prism also introduces a distinct pre-render sanitization phase. To prevent scripts and metadata from bloating layout passes, the engine runs an iterative filter that strips non-visual elements (such as <script>, <style>, <noscript>, and <template>) from the layout tree. Because asset references are harvested during initial parsing, stripping these nodes prior to layout allows the layout engine to focus strictly on structural and typographic nodes.</p>
<ol>
<li>JavaScript Integration and the INIX Subsystem
JavaScript integration in Prism is driven by the INIX subsystem, which pairs the document model with the Boa ECMAScript runtime.</li>
</ol>
<p>DOM Hydration<br>
A browser must bridge the memory model of its native systems language (Rust) with the object model of its scripting language. Prism solves this through an explicit hydration bridge. Rather than wrapping every single Rust node in a heavy FFI envelope during initial parse, the Rust DOM serializes its structural tree into an optimized payload. The INIX runtime consumes this payload on the JavaScript side, iteratively constructing mirrored DOM objects, populating attributes, and linking parent-child associations.</p>
<p>The Mutation Interception Bridge<br>
To handle dynamic client-side applications, INIX implements an event-driven mutation bridge. When scripts execute operations such as modifying element.className, mutating attributes, or appending children via appendChild, INIX catches these operations through monkey-patched prototypes and native hooks. It serializes mutation records containing:</p>
<p>The unique target node identifier.<br>
The property or attribute mutated.<br>
The updated payload value.<br>
Structural metadata for tree rearrangements.<br>
These mutation batches are queued and dispatched back across the native bridge into Rust, updating the primary DOM tree and signaling the layout engine that a restyle or reflow is required.</p>
<p>Parallel Custom Runtime Path<br>
Alongside the integrated Boa engine, the Prism repository contains an independent, custom-built JavaScript toolchain located in src/lexer.rs, src/parser.rs, and src/compiler.rs. This subsystem consists of a hand-written JavaScript lexer, an AST parser, and an experimental bytecode compiler paired with a stack-based virtual machine. While Boa handles active webpage execution today, this internal path represents Prism’s long-term research vector toward a fully unified, zero-dependency browser runtime.</p>
<ol>
<li>CSS Parsing and Style Resolution
The CSS architecture is split cleanly between declaration parsing and selector application.</li>
</ol>
<p>The stylesheet parser in components/css/src/lib.rs consumes raw CSS text, stripping comments, tokenizing at-rules, and breaking style blocks into structured rulesets containing selector lists and declaration maps.</p>
<p>Style resolution takes place in components/layout/src/style.rs. Here, Prism matches the parsed rules against the live DOM tree. The selector engine contains representation logic for:</p>
<p>Type selectors (element tag names).<br>
ID selectors (#id).<br>
Class selectors (.class).<br>
Attribute matchers.<br>
Combinators (parent, descendant, sibling chains).<br>
The resolver constructs an intermediate StyledNode tree, applying computed values and computing basic inheritance rules (such as propagating font colors and typography defaults down from the <body> element).</p>
<p>Current Limitations in the Style Engine<br>
Diagnostics from production tests reveal that selector matching and cascade resolution remain incomplete. For instance, during the execution of Hacker News, the main content wrapper <table id="hnmain"> failed to receive its authored width rules (width=None), indicating that ID selector specificity weighting or attribute-matching lookups occasionally miss matches during tree walks. Correcting cascade precedence and selector specificity scoring remains an active development priority.</p>
<ol>
<li>The Layout Engine
The layout engine in components/layout/src/lib.rs translates the abstract StyledNode tree into concrete Cartesian geometry, assigning each element an (x,y)(x, y)(x,y) coordinate along with definitive width and height bounding boxes.</li>
</ol>
<p>Intrinsic Display Roles<br>
Prism maps HTML elements to their default structural layout behaviors:</p>
<p>Block Elements: Elements like <div> expand horizontally to fill their parent container and stack vertically.<br>
Inline Elements: Elements like <span> flow horizontally along the active text line, wrapping when container boundaries are reached.<br>
Table Elements: Specialized display types (table, tbody, thead, tfoot, tr, td, th) enter the dedicated table constraint-solving pipeline.<br>
Replaced / Inline-Block Elements: Form controls (<input>) and visual embeds (<img>) maintain intrinsic dimensions while participating in horizontal inline flows.<br>
Non-Rendered Elements: Tags such as <br> emit explicit line-break commands without generating visual boxes.<br>
Table Layout Resolution<br>
Because legacy and data-dense websites rely on complex table formatting, Prism includes a dedicated table formatting context. The table solver operates in multi-pass stages:</p>
<p>It inspects all child rows and aggregates total column counts across all row fragments.<br>
It executes cell measurement passes, calculating both the minimum intrinsic content width (the smallest width an element can adopt without text overflowing) and preferred content width for every cell.<br>
It resolves column widths proportionally against available space while respecting explicit author-specified dimension constraints.<br>
During execution on Hacker News, Prism successfully identified the main three-column news layout, resolving column widths of approximately 31.3 pixels for ranking indices, 24.3 pixels for voting arrows, and 740.4 pixels for headline/metadata content. This multi-pass algorithm prevents columns from collapsing into equal-width divisions that ruin page readability.</p>
<ol>
<li>Graphics Engine and Display-List Rasterization
Prism enforces a strict architectural boundary between layout computation and pixel manipulation. Located in components/graphics/src/lib.rs, the graphics subsystem does not allow the layout engine to paint directly into memory. Instead, layout generates an immutable, serializable Display List.</li>
</ol>
<p>Write on Medium<br>
A display list is an ordered sequence of draw commands:</p>
<p>Solid rectangles for element backgrounds.<br>
Styled boundaries for borders.<br>
Image blits containing raw pixel buffers and target coordinates.<br>
Text commands carrying font handles, glyph positions, and color values.<br>
The rasterizer processes these commands sequentially, drawing into an 800 by 600 32-bit RGBA software framebuffer.</p>
<p>The Text Rendering Pipeline<br>
Prism integrates a CSS-aware glyph rasterizer capable of measuring font metrics and applying typographic rules. While the engine successfully painted 844 glyphs across 420 text commands in production tests, text rendering remains the most visually critical issue.</p>
<p>Because coordinate offsets between inline text boxes, containing block wrappers, and rasterizer baseline expectations can drift, glyphs currently exhibit coordinate displacement and overlapping text baselines. The display list is correctly generating rendering commands, but the geometry calculation within the inline layout context requires baseline alignment fixes.</p>
<ol>
<li>Spatial Hit Testing
A browser must map hardware input events back to the appropriate DOM nodes. Prism generates a unified hit-test spatial index during the layout pass.</li>
</ol>
<p>Each layout object emits an axis-aligned bounding box tied to its originating node reference. During navigation and rendering of Hacker News, Prism generated 1,190 distinct hit-test bounding boxes. This spatial index allows pointer coordinates to be queried against the tree in logarithmic time, providing the foundation for click handling, link navigation, hover states, and future form-control inputs.</p>
<ol>
<li>Extension Sandboxing via WebAssembly
Rather than implementing extensions via high-privilege JavaScript bindings that risk browser security, Prism features an extension sandbox built on Wasmtime.</li>
</ol>
<p>Extensions are compiled to WebAssembly targets and executed inside deterministic memory sandboxes. The host environment provides a strictly defined native shim interface, limiting extension operations to designated system calls while bounding computation via execution fuel.</p>
<p>During live tests, Prism loaded and executed adblock_lite.wasm, an internal ad-blocking extension:</p>
<p>The sandbox loaded the 734-byte binary.<br>
It allocated an execution fuel budget of 10,000,000 instructions.<br>
The extension consumed 159 fuel units, returning 9 active CSS and structural filtering rules targeting ad banners, tracking pixels, sponsored posts, and modal consent overlays.<br>
These rules were injected into the layout engine’s rule list to suppress matching nodes before display lists were emitted.<br>
This proves the feasibility of running isolated, memory-safe extensions natively within an independent engine core.</p>
<ol>
<li>The Aegis Security Pipeline
Prism incorporates an internal security inspection gate called Aegis, situated directly between the network loader and the parsing pipelines.</li>
</ol>
<p>Aegis functions as a zero-trust payload verification boundary. Before fetched bytes are allowed to touch the HTML tokenizer or parser, the Aegis pipeline scans document streams, enforces content policies, validates resource origins, and ensures data payloads do not violate boundary constraints. In live testing runs, this clearance pipeline completed in approximately 38 microseconds, establishing an inline security verification layer that introduces near-zero latency to the content pipeline.</p>
<ol>
<li>Production Milestone: Loading Hacker News
To prove the viability of its architecture, Prism was deployed against a live, external web destination: <a href="https://news.ycombinator.com">https://news.ycombinator.com</a>.</li>
</ol>
<p>The test verified that the browser could navigate the web, retrieve external HTML, download linked CSS (news.css), fetch and decode SVG/PNG image assets, sanitize DOM nodes, hydrate the JavaScript environment, fire its WASM ad blocker, resolve complex multi-column table dimensions, and rasterize a full 800 by 600 framebuffer containing 480,000 pixels.</p>
<p>Pipeline Run Metrics<br>
Viewport Geometry: 800 pixels wide by 600 pixels high.<br>
Document Layout Height: 2,797.4 pixels of vertical content mapped.<br>
Rasterized Layout Objects: 429 discrete layout elements.<br>
Text Commands Executed: 420 draw calls.<br>
Glyphs Processed: 844 individual characters shaped and rendered.<br>
Painted Pixel Surface: 26,249 active foreground pixels rendered.<br>
External Images Blitted: 2 assets loaded and painted.<br>
Hit-Test Geometry: 1,190 interaction regions mapped.<br>
Adblock Rules Injected: 9 structural suppression filters applied.<br>
While the metrics confirm that data flows cleanly through the entire pipeline, the final rendering exposed the engine’s primary current limitations: text baselines frequently overlap due to inline cursor miscalculations, and certain CSS rules (such as #hnmain table widths) do not yet resolve correctly against the style cascade.</p>
<ol>
<li>Current Subsystem Status and Next Engineering Steps
Prism has completed its proof-of-concept bootstrapping phase. The implementation status of the engine breaks down across its subsystems as follows:</li>
</ol>
<p>Browser Shell and Tab Orchestrator: Implemented and functional. Manages tabs and provides automatic fallback streaming over local HTTP when headless.<br>
Network Pipeline: Functional. Concurrently fetches HTML, linked CSS stylesheets, external JavaScript, and image assets.<br>
HTML Tokenizer and Parser: Implemented and functional. Supports streaming tokens, raw-text state isolation, and tree recovery including implicit table structures.<br>
DOM Engine: Implemented and functional. Uses iterative traversal and sanitization mechanisms to prevent stack overflows.<br>
INIX JavaScript Runtime: Functional for tested workflows. Uses Boa for ECMAScript compliance, supported by custom hydration and mutation interception layers.<br>
Internal JavaScript Engine Path: Present. Standalone lexer, parser, compiler, and VM path implemented for research and future integration.<br>
CSS Parser: Functional. Accurately extracts rulesets, properties, and values from stylesheets.<br>
Style Cascade and Selector Engine: Incomplete. Structural tag matching functions, but ID/class specificity calculations and property inheritance need refinement.<br>
Table Layout Engine: Functional. Computes minimum and preferred cell widths to generate accurate multi-column layouts.<br>
Block and Inline Layout Engine: Partially functional. Block flows calculate correctly, but inline cursor tracking and text wrapping suffer from coordinate calculation issues.<br>
Graphics Rasterizer: Functional. Translates display lists into memory framebuffers, drawing rectangles, borders, images, and text.<br>
Hit-Testing Engine: Functional. Produces coordinate-indexed boundaries for mouse tracking and interaction.<br>
Wasmtime Sandbox: Functional. Safely runs WebAssembly binaries with strict fuel budgets and native API bindings.<br>
Aegis Security Gate: Integrated. Inspects incoming payloads with sub-millisecond execution times.<br>
The Next Engineering Target<br>
With the end-to-end rendering pipeline proven, development focus shifts away from adding high-level features and toward visual fidelity and layout correctness:</p>
<p>Inline Cursor Correction: Refactor components/layout/src/lib.rs to ensure relative inline text coordinates correctly track cumulative parent offsets and baseline alignments.<br>
Cascade and Specificity Rectification: Overhaul components/layout/src/style.rs so that ID selectors (#hnmain) and class selectors (.title, .subtext) take proper precedence over generic element styles.<br>
Display-List Coordinate Validation: Audit the transformation path between layout box bounds and TextCommand execution in components/graphics/src/lib.rs to eliminate glyph scrambling.<br>
Conclusion<br>
Prism is an engineering effort to prove that a modern browser engine can still be built from the ground up without relying on the Chromium monoculture. By dividing the problem into modular, decoupled Rust crates — spanning tokenization, DOM engineering, style cascading, table mathematics, software rasterization, and WebAssembly sandboxing — the project establishes that building an independent web engine is not an impossible task.</p>
<p>While substantial work remains before Prism can render arbitrary web applications with pixel-perfect standards compliance, the engine has moved past basic architectural theory. It parses real code, runs real logic, computes complex structural tables, and renders live web pages down to the pixel level through a fully self-authored pipeline.</p>
