{"slug": "prism-building-a-zero-chromium-browser-engine-in-rust-architecture-internals-and", "title": "Prism: Building a Zero-Chromium Browser Engine in Rust — Architecture, Internals, and Implementation", "summary": "A developer built Prism, an independent browser engine written from scratch in Rust that avoids embedding Chromium or WebKit, implementing its own pipelines for resource loading, DOM construction, style calculation, display-list generation, software rasterization, and extension sandboxing. Prism recently completed an end-to-end rendering loop on Hacker News, fetching network dependencies, building a sanitized DOM, hydrating JavaScript structures, computing multi-column table layout, and flushing an 800x600 framebuffer with hundreds of painted glyphs. The engine falls back to an embedded HTTP frame-streaming server on port 3000 when no display server is available, enabling headless layout and rasterization without Xvfb.", "body_md": "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.\n\nThe 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.\n\nPrism 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.\n\nThe 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.\n\nThis 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.\n\nAt 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.\n\nA 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.\n\nPrism’s resource subsystem is built on top of native Rust asynchronous networking primitives. When a navigation event is dispatched by the shell:\n\nThe root document is retrieved, following any HTTP redirects to resolve the final canonical base URL.\n\nThe tokenizer and parser run a discovery pass to extract resource dependencies linked via standard tags (, , <img src=\"...\">).<br>\nRelative resource paths are canonicalized against the base navigation URL.<br>\nAssets are fetched concurrently through an internal asset-preloading broker.<br>\nAsset 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>\n<ol>\n<li>HTML Tokenization\nThe 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>\n</ol>\n<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>\n<p>Start tags, capturing the element name, self-closing state, and raw key-value attribute pairs.<br>\nEnd tags, marking structural termination.<br>\nCharacter data, separating printable text runs from whitespace.<br>\nComments, extracting developer annotations without polluting the render path.<br>\nDoctype nodes, identifying strict or quirks-mode document declarations.<br>\nEnd-of-file indicators, terminating state machines cleanly.<br>\nA 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>\n<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>\n<ol>\n<li>HTML Parser and Stack-Based DOM Construction\nTokens generated by the tokenizer are fed directly into the hierarchical document parser located in components/dom/src/parser.rs.</li>\n</ol>\n<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>\n<p>Because real-world markup frequently deviates from valid XML structures, the parser implements standard HTML structural recovery heuristics:</p>\n<p>Implicit Paragraph Termination: Opening a block-level element inside an active <p> tag automatically generates an implicit closing token for the paragraph.<br>\nList and Definition Management: Consecutive <li>, <dt>, and <dd> items auto-close preceding siblings without requiring explicit closing tags.<br>\nImplicit 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>\nBeyond 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>\n<ol>\n<li>The DOM Engine\nPrism’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>\n</ol>\n<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>\n<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>\n<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>\n<ol>\n<li>JavaScript Integration and the INIX Subsystem\nJavaScript integration in Prism is driven by the INIX subsystem, which pairs the document model with the Boa ECMAScript runtime.</li>\n</ol>\n<p>DOM Hydration<br>\nA 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>\n<p>The Mutation Interception Bridge<br>\nTo 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>\n<p>The unique target node identifier.<br>\nThe property or attribute mutated.<br>\nThe updated payload value.<br>\nStructural metadata for tree rearrangements.<br>\nThese 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>\n<p>Parallel Custom Runtime Path<br>\nAlongside 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>\n<ol>\n<li>CSS Parsing and Style Resolution\nThe CSS architecture is split cleanly between declaration parsing and selector application.</li>\n</ol>\n<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>\n<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>\n<p>Type selectors (element tag names).<br>\nID selectors (#id).<br>\nClass selectors (.class).<br>\nAttribute matchers.<br>\nCombinators (parent, descendant, sibling chains).<br>\nThe 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>\n<p>Current Limitations in the Style Engine<br>\nDiagnostics 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>\n<ol>\n<li>The Layout Engine\nThe 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>\n</ol>\n<p>Intrinsic Display Roles<br>\nPrism maps HTML elements to their default structural layout behaviors:</p>\n<p>Block Elements: Elements like <div> expand horizontally to fill their parent container and stack vertically.<br>\nInline Elements: Elements like <span> flow horizontally along the active text line, wrapping when container boundaries are reached.<br>\nTable Elements: Specialized display types (table, tbody, thead, tfoot, tr, td, th) enter the dedicated table constraint-solving pipeline.<br>\nReplaced / Inline-Block Elements: Form controls (<input>) and visual embeds (<img>) maintain intrinsic dimensions while participating in horizontal inline flows.<br>\nNon-Rendered Elements: Tags such as <br> emit explicit line-break commands without generating visual boxes.<br>\nTable Layout Resolution<br>\nBecause 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>\n<p>It inspects all child rows and aggregates total column counts across all row fragments.<br>\nIt 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>\nIt resolves column widths proportionally against available space while respecting explicit author-specified dimension constraints.<br>\nDuring 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>\n<ol>\n<li>Graphics Engine and Display-List Rasterization\nPrism 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>\n</ol>\n<p>Write on Medium<br>\nA display list is an ordered sequence of draw commands:</p>\n<p>Solid rectangles for element backgrounds.<br>\nStyled boundaries for borders.<br>\nImage blits containing raw pixel buffers and target coordinates.<br>\nText commands carrying font handles, glyph positions, and color values.<br>\nThe rasterizer processes these commands sequentially, drawing into an 800 by 600 32-bit RGBA software framebuffer.</p>\n<p>The Text Rendering Pipeline<br>\nPrism 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>\n<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>\n<ol>\n<li>Spatial Hit Testing\nA 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>\n</ol>\n<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>\n<ol>\n<li>Extension Sandboxing via WebAssembly\nRather than implementing extensions via high-privilege JavaScript bindings that risk browser security, Prism features an extension sandbox built on Wasmtime.</li>\n</ol>\n<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>\n<p>During live tests, Prism loaded and executed adblock_lite.wasm, an internal ad-blocking extension:</p>\n<p>The sandbox loaded the 734-byte binary.<br>\nIt allocated an execution fuel budget of 10,000,000 instructions.<br>\nThe 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>\nThese rules were injected into the layout engine’s rule list to suppress matching nodes before display lists were emitted.<br>\nThis proves the feasibility of running isolated, memory-safe extensions natively within an independent engine core.</p>\n<ol>\n<li>The Aegis Security Pipeline\nPrism incorporates an internal security inspection gate called Aegis, situated directly between the network loader and the parsing pipelines.</li>\n</ol>\n<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>\n<ol>\n<li>Production Milestone: Loading Hacker News\nTo 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>\n</ol>\n<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>\n<p>Pipeline Run Metrics<br>\nViewport Geometry: 800 pixels wide by 600 pixels high.<br>\nDocument Layout Height: 2,797.4 pixels of vertical content mapped.<br>\nRasterized Layout Objects: 429 discrete layout elements.<br>\nText Commands Executed: 420 draw calls.<br>\nGlyphs Processed: 844 individual characters shaped and rendered.<br>\nPainted Pixel Surface: 26,249 active foreground pixels rendered.<br>\nExternal Images Blitted: 2 assets loaded and painted.<br>\nHit-Test Geometry: 1,190 interaction regions mapped.<br>\nAdblock Rules Injected: 9 structural suppression filters applied.<br>\nWhile 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>\n<ol>\n<li>Current Subsystem Status and Next Engineering Steps\nPrism has completed its proof-of-concept bootstrapping phase. The implementation status of the engine breaks down across its subsystems as follows:</li>\n</ol>\n<p>Browser Shell and Tab Orchestrator: Implemented and functional. Manages tabs and provides automatic fallback streaming over local HTTP when headless.<br>\nNetwork Pipeline: Functional. Concurrently fetches HTML, linked CSS stylesheets, external JavaScript, and image assets.<br>\nHTML Tokenizer and Parser: Implemented and functional. Supports streaming tokens, raw-text state isolation, and tree recovery including implicit table structures.<br>\nDOM Engine: Implemented and functional. Uses iterative traversal and sanitization mechanisms to prevent stack overflows.<br>\nINIX JavaScript Runtime: Functional for tested workflows. Uses Boa for ECMAScript compliance, supported by custom hydration and mutation interception layers.<br>\nInternal JavaScript Engine Path: Present. Standalone lexer, parser, compiler, and VM path implemented for research and future integration.<br>\nCSS Parser: Functional. Accurately extracts rulesets, properties, and values from stylesheets.<br>\nStyle Cascade and Selector Engine: Incomplete. Structural tag matching functions, but ID/class specificity calculations and property inheritance need refinement.<br>\nTable Layout Engine: Functional. Computes minimum and preferred cell widths to generate accurate multi-column layouts.<br>\nBlock and Inline Layout Engine: Partially functional. Block flows calculate correctly, but inline cursor tracking and text wrapping suffer from coordinate calculation issues.<br>\nGraphics Rasterizer: Functional. Translates display lists into memory framebuffers, drawing rectangles, borders, images, and text.<br>\nHit-Testing Engine: Functional. Produces coordinate-indexed boundaries for mouse tracking and interaction.<br>\nWasmtime Sandbox: Functional. Safely runs WebAssembly binaries with strict fuel budgets and native API bindings.<br>\nAegis Security Gate: Integrated. Inspects incoming payloads with sub-millisecond execution times.<br>\nThe Next Engineering Target<br>\nWith the end-to-end rendering pipeline proven, development focus shifts away from adding high-level features and toward visual fidelity and layout correctness:</p>\n<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>\nCascade 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>\nDisplay-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>\nConclusion<br>\nPrism 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>\n<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>", "url": "https://wpnews.pro/news/prism-building-a-zero-chromium-browser-engine-in-rust-architecture-internals-and", "canonical_source": "https://dev.to/adityaadi/prism-building-a-zero-chromium-browser-engine-in-rust-architecture-internals-and-implementation-3n8", "published_at": "2026-11-01 12:48:00+00:00", "updated_at": "2026-11-01 12:52:43.715629+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Prism", "Rust", "Chromium", "WebKit", "Boa", "Wasmtime", "Hacker News", "winit"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/prism-building-a-zero-chromium-browser-engine-in-rust-architecture-internals-and", "markdown": "https://wpnews.pro/news/prism-building-a-zero-chromium-browser-engine-in-rust-architecture-internals-and.md", "text": "https://wpnews.pro/news/prism-building-a-zero-chromium-browser-engine-in-rust-architecture-internals-and.txt", "jsonld": "https://wpnews.pro/news/prism-building-a-zero-chromium-browser-engine-in-rust-architecture-internals-and.jsonld"}}