{"slug": "falco-tiny-browser-engine-written-from-scratch-in-rust", "title": "Falco: Tiny browser engine written from scratch in Rust", "summary": "Falco, a browser engine written from scratch in Rust by a single developer, is now available as v0.1.0, comprising roughly 36,000 lines of code that render HTML, CSS, JavaScript, SVG, and images to PNG or an interactive window. The engine includes a custom JS VM with JIT compilation, a hand-written PNG encoder, and support for flex/grid/table layout, but many spec-compliant modules (e.g., WHATWG tokenizer, Shadow DOM, Selectors Level 4) are compiled but not yet integrated into the render pipeline, with full integration planned for v0.2.0.", "body_md": "*Logo by Sanya — t.me/SanyochekDev*\n\nA tiny, fast browser engine written in Rust.\n\nRenders HTML, CSS, JavaScript, SVG and images — to a PNG, or to a live interactive window.\n\n[Quick start](#quick-start) ·\n[Interactive mode](#interactive-mode---window) ·\n[Features](#what-it-supports) ·\n[Architecture](#architecture) ·\n[Contributing](/poxk/Falco/blob/main/.github/CONTRIBUTING.md)\n\nFalco is a real browser engine in roughly **36,000 lines of Rust**. It\nparses HTML, applies CSS, executes JavaScript, loads images, computes\nlayout, and paints to a canvas — either as a **PNG file** or a live\n**interactive window** where you can scroll, click links, fill out\nforms, and navigate.\n\n```\nHTML ──▶ DOM ──▶ Style tree ──▶ Layout tree ──▶ Paint commands ──▶ Canvas ──▶ PNG / Window\n            ▲           ▲              ▲\n            │           │              │\n       HTML5 tokenizer  CSS cascade    Flex / Grid / Table / Float / Absolute\n       + tree builder   + Selectors 4  + Inline / Block flow\n            │\n       JS (custom VM with closures, generators, Promise, BigInt, Symbol)\n            │\n       Image loader ──▶ HTTP / data: URL / local file\n```\n\nFalco is **not** a wrapper around WebKit, Gecko, or Chromium. Every\nmodule — HTML tokenizer, CSS parser, layout engine, JS VM, font\nrasterizer, PNG encoder — is written from scratch in Rust.\n\nTo set expectations clearly — this is v0.1.0, an early release by a single developer. Not everything listed in this README is production-ready. Here's what is actually wired into the render pipeline and what is structurally complete but not yet called:\n\n- HTML parser (\n`html/`\n\n) — legacy parser, not spec-compliant but functional - DOM types (\n`dom/`\n\n) — minimal node types, no observers/shadow - CSS parser (\n`css/`\n\n) — selectors, properties, cascade, color parsing - Style cascade (\n`style/`\n\n) — UA styles + inheritance + flex/grid props - Layout (\n`layout/`\n\n) — block / inline / flex /**CSS Grid**/** table**/ float / absolute - Painting (\n`paint/`\n\n) — fonts (ab_glyph), gradients, shadows, alpha compositing - SVG renderer (\n`svg/`\n\n) — paths, basic shapes, gradients, stroke + fill - Hand-written PNG encoder (\n`png/`\n\n) - Image loader (\n`image/`\n\n) — HTTP, data: URLs, local files - JS VM (\n`tjs/`\n\n) — bytecode interpreter + JIT (x86_64, Linux-only) - JS-DOM bindings (\n`js_tjs/`\n\n,`js_runner/`\n\n) —`document.getElementById`\n\n,`console.log`\n\n,`alert`\n\n,`onclick`\n\n- Networking (\n`net/`\n\n) — HTTP/1.1 (ureq), cookies, cache, websocket, redirect - Interactive\n`--window`\n\nmode — scrolling, forms, navigation, history\n\nThese exist as spec-compliant replacements for the legacy modules. They\ncompile and have their own unit tests, but `render_with_base_url`\n\ndoes\nnot call into them yet. This is the v0.2.0 milestone.\n\n`html::spec`\n\n— WHATWG §13.2 tokenizer (all 80 states) + tree builder (all 22 insertion modes) + serializer + XML parser + encoding detection`dom::spec`\n\n— spec DOM with MutationObserver, Shadow DOM, custom elements, accessibility tree`css::spec`\n\n— Selectors Level 4 (`:has()`\n\n,`:is()`\n\n,`:where()`\n\n, cascade layers, container queries)`tjs_ext/`\n\n— Symbol, BigInt, Promise, microtasks, Map/Set, WeakMap/WeakSet, Reflect\n\n`security/`\n\n— SOP, multi-process, seccomp sandbox, CSP, TLS cert chain validation, permissions, extensions, DevTools protocol. All algorithms are in place and unit-tested, but the renderer doesn't enforce them yet.`web_runtime/`\n\n—`fetch()`\n\n,`XMLHttpRequest`\n\n, event loop, Promise. The Promise / event loop integration is real and unit-tested, but`fetch`\n\nand`XHR`\n\nare stubbed (no real network behind them in the JS context). WebGL, video, MSE, EME, NDSD are**headless stubs**— they implement the API surface but don't actually render WebGL frames, decode H.264 video, or do DRM. They exist as scaffolding for future work.`media/`\n\n—`@media`\n\nqueries are parsed but always applied (no conditional cascade yet)\n\n`real-http2`\n\n,`real-webgl`\n\n,`sandbox`\n\nCargo features do not compile with`--all-features`\n\nbecause their upstream APIs have drifted (h2::Body removed, glow API changed, seccomp pre_exec is Unix-only). They are disabled by default and the CI clippy step does not check them.- The JIT (\n`tjs/jit.rs`\n\n) works on Linux x86_64 but fails on macOS CI runners because`mmap(MAP_JIT)`\n\nrequires code signing with the`com.apple.security.cs.allow-jit`\n\nentitlement. JIT tests are marked`#[ignore]`\n\non macOS. - DOM mutation from JS (\n`element.innerHTML = ...`\n\n,`element.style.color = ...`\n\n) does not trigger re-render. - HTML5 spec-compliant tree repair (adoption agency, foster parenting) is in\n`html/spec/tree_builder.rs`\n\nbut the legacy parser is what actually runs.\n\n**Bottom line:** if you `cargo build && ./falco https://example.com --out out.png`\n\n, you get a real PNG render. The HTML/CSS/layout/paint path works end-to-end. The spec-compliant parsers, security enforcement, and advanced web runtime (WebGL/video/DRM) are **scaffolding for future milestones**, not working features. Please read the code before claiming otherwise.\n\n```\n# Build (release binary lands in target/release/falco)\ncargo build --release\n\n# Render a local HTML file to PNG\n./target/release/falco page.html --out page.png --width 1200\n\n# Render a URL to PNG\n./target/release/falco https://example.com --out example.png --width 800\n\n# Open interactive live window (desktop only — scrolls, hover, link clicks)\n./target/release/falco page.html --window\n\n# Render with external CSS merged on top of <style> tags\n./target/release/falco README.html --css style.css --out readme.png\n```\n\n`<style>`\n\ntags inside the HTML are automatically extracted and\napplied. External CSS via `--css`\n\nis merged on top.\n\nWhen you pass `--window`\n\n, Falco opens a real browser window with an\n**address bar at the top**, the **page content in the middle**, and a\n**status bar at the bottom**. You can:\n\n**Click links**(`<a href>`\n\n) — Falco fetches the new URL and re-renders**Address bar**— click it or press`F6`\n\n, type a URL, press`Enter`\n\n**Reload** with`r`\n\n— re-fetches the current page**Back/Forward** with`Alt+←`\n\n/`Alt+→`\n\n— full history navigation\n\n**Click inputs** to focus them, then**type** to enter text (live update)**Tab / Shift+Tab**— cycle focus between interactive elements** Backspace**— delete the last character** Enter**— submit form / click the focused button / follow focused link- Supported types:\n`text`\n\n,`email`\n\n,`password`\n\n(masked),`checkbox`\n\n,`submit`\n\n,`button`\n\n,`textarea`\n\n**Mouse wheel**,** arrow keys**,** Page Up/Down**,** Home/End****Focus ring**— 2px blue outline around the focused element** Blinking caret**— 500ms blink in text inputs and the address bar** Hover hint**— when hovering a link, its href appears in the status bar\n\nThe window only re-paints when state actually changes (scroll, input text, focus, hover). On idle frames, only the address bar / status bar overlays are redrawn — the page canvas is cached. This keeps CPU usage low and scrolling smooth.\n\nIf no display is available (headless server, no X11/Wayland), Falco automatically falls back to PNG output with a warning.\n\n```\nfalco — a tiny browser engine\n\nUSAGE\n  falco <input> [OPTIONS]\n\nINPUT\n  A URL (http://...) or a path to a local .html file.\n\nOPTIONS\n  --css <path>       External CSS file (merged with <style> tags in HTML).\n  --out <path>       Output PNG path. Default: falco.png\n  --width <px>       Viewport width. Default: 1200\n  --height <px>      Viewport height (canvas grows if content is taller). Default: 800\n  --bg <hex>         Background color (0xRRGGBBAA). Default: 0xFFFFFFFF\n  --window           Open a live interactive window instead of writing PNG.\n  -h, --help         Show this help\n  -V, --version      Print version\n\nINTERACTIVE MODE KEYS (when --window is used)\n  q / Esc       quit\n  r             reload (prints message — restart Falco to actually reload)\n  ↑ / ↓         scroll line\n  PgUp / PgDn   scroll page\n  Home / End    jump to top / bottom\n  g / G         top / bottom (vim-style)\n  mouse wheel   scroll\n  mouse click   follow `<a href>` link (prints URL to stderr)\nphp\nuse falco::{render_to_png, RenderOptions};\n\nfn main() -> anyhow::Result<()> {\n    let html = std::fs::read_to_string(\"page.html\")?;\n    let css = std::fs::read_to_string(\"style.css\")?;\n    let opts = RenderOptions {\n        width: 1200,\n        height: 800,\n        background: 0xFFFFFFFF,\n    };\n    render_to_png(&html, &css, opts, \"out.png\")?;\n    Ok(())\n}\n```\n\n**HTML5 tokenizer**(WHATWG §13.2.5) — all 80 states, script-data escape/double-escape, attribute parsing with duplicate detection, named + numeric character references with Windows-1252 quirks**HTML5 tree builder**(WHATWG §13.2.6) — all 22 insertion modes, stack of open elements with scope algorithms, active formatting elements list, reconstruct active formatting,**adoption agency algorithm**,** foster parenting**for table content,`<template>`\n\nwith separate DocumentFragment contents**Entity references**—`&`\n\n,`A`\n\n,`©`\n\n, 50+ named entities**Whitespace collapsing**(browser-style)with separate DocumentFragment contents`<template>`\n\nelement**XML/XHTML parser**— strict, with namespace bindings, CDATA, PIs** Encoding detection**— BOM, HTTP`Content-Type`\n\ncharset,`<meta charset>`\n\n,`<meta http-equiv>`\n\n, heuristic UTF-8/UTF-16 detection, decoders for UTF-8 / UTF-16LE / UTF-16BE / Windows-1252**Tree builder auto-close**—`<li>`\n\n,`<p>`\n\n,`<td>`\n\n,`<tr>`\n\n,`<option>`\n\n,`<dt>`\n\n/`<dd>`\n\n**innerHTML / outerHTML serialization**— void elements,`<template>`\n\ncontents fragment, raw text elements, full attribute value escaping\n\n`NodeRef = Rc<RefCell<Node>>`\n\nwith`parent`\n\n,`firstChild`\n\n,`lastChild`\n\n,`previousSibling`\n\n,`nextSibling`\n\npointers per spec`DocumentHandle = Rc<RefCell<Document>>`\n\nwith weak back-ref from each Node**Mutation records** queued on every append/insert/remove/setAttribute/removeAttribute**MutationObserver** with`observe()`\n\n,`disconnect()`\n\n,`take_records()`\n\n,`MutationObserverInit`\n\n(childList/attributes/characterData/subtree/attributeOldValue/ characterDataOldValue/attributeFilter), subtree ancestor matching**Shadow DOM**—`attachShadow()`\n\nwith open/closed modes, host validation, named + default slots, fallback content, slot distribution (flatten tree algorithm),`assignedSlot`\n\nlookup**Custom elements**—`customElements.define()`\n\nwith name validation,`observedAttributes`\n\ntracking, lifecycle callbacks (connected / disconnected / adopted / attributeChanged / form-associated), pending upgrades, customized built-in elements (`is=\"...\"`\n\n)**Accessibility tree**— parallel tree with role/name/description/ state/actions, implicit ARIA roles for ~50 HTML tags, honors`role=\"\"`\n\n,`aria-hidden`\n\n,`hidden`\n\n,`display:none`\n\n, accessible name computation (aria-label > aria-labelledby > element-specific > title)\n\n**Selectors Level 4**— type, class, id, universal (`*`\n\n), descendant, child (`>`\n\n), adjacent sibling (`+`\n\n), general sibling (`~`\n\n), attribute (`[attr]`\n\n,`=`\n\n,`~=`\n\n,`|=`\n\n,`^=`\n\n,`$=`\n\n,`*=`\n\n)**Pseudo-classes**—`:hover`\n\n,`:focus`\n\n,`:focus-visible`\n\n,`:focus-within`\n\n,`:active`\n\n,`:visited`\n\n,`:checked`\n\n,`:disabled`\n\n,`:enabled`\n\n,`:readonly`\n\n,`:readwrite`\n\n,`:required`\n\n,`:optional`\n\n,`:valid`\n\n,`:invalid`\n\n,`:empty`\n\n,`:root`\n\n,`:first-child`\n\n,`:last-child`\n\n,`:only-child`\n\n,`:first-of-type`\n\n,`:last-of-type`\n\n,`:only-of-type`\n\n,`:nth-child(an+b)`\n\n,`:nth-last-child`\n\n,`:nth-of-type`\n\n,`:nth-last-of-type`\n\n,`:nth-child(an+b of S)`\n\n,`:is()`\n\n,`:where()`\n\n,`:not()`\n\n,`:has()`\n\n,`:lang()`\n\n,`:dir()`\n\n**Cascade & specificity**— (a, b, c) tuple,`:where()`\n\nzero,`:is()`\n\n/`:not()`\n\n/`:has()`\n\nmost specific arg, CascadeOrigin (UA / User / Author) with reversed order for`!important`\n\n, CascadeLayers (None wins over layered; later wins over earlier)**Properties**—`display`\n\n,`position`\n\n,`color`\n\n,`background`\n\n(including`linear-gradient`\n\nand`radial-gradient`\n\n),`font-*`\n\n,`margin`\n\n,`padding`\n\n,`border`\n\n,`border-radius`\n\n,`width`\n\n,`height`\n\n,`min/max-width`\n\n,`top/right/bottom/left`\n\n,`z-index`\n\n,`overflow`\n\n,`opacity`\n\n,`box-shadow`\n\n,`white-space`\n\n,`box-sizing`\n\n,`gap`\n\n,`flex*`\n\n,`grid*`\n\n,`writing-mode`\n\n, logical properties (`margin-inline-start`\n\n, etc.)**Values**— keywords, hex/rgb/rgba/named colors, lengths (px, em, rem, pt, %, vw, vh), percentages, numbers,`!important`\n\n**Functions**—`linear-gradient()`\n\n,`radial-gradient()`\n\n,`url()`\n\n,`var()`\n\n,`calc()`\n\n(simplified),`rgb()`\n\n,`rgba()`\n\n**Shorthands**—`margin`\n\n,`padding`\n\n,`border`\n\n,`background`\n\n,`flex`\n\n**@-rules**—`@media`\n\n(parsed and applied),`@keyframes`\n\n/`@animation`\n\nwith cubic-bezier & steps timing functions,`@font-face`\n\nwith family/weight lookup,`@layer`\n\n(cascade layers),`@container`\n\nqueries with`evaluate_container_query()`\n\n**Logical properties**—`margin-inline-start`\n\netc. resolved to physical properties based on`writing-mode`\n\n**CSS counters**—`counter-reset`\n\n,`counter-increment`\n\n,`counter-set`\n\n**Containment**—`contain: layout/paint/size/style/inline-size/ block-size`\n\n,`strict`\n\n,`content`\n\n**Filters**—`blur`\n\n,`brightness`\n\n,`contrast`\n\n,`drop-shadow`\n\n,`grayscale`\n\n,`hue-rotate`\n\n,`invert`\n\n,`opacity`\n\n,`saturate`\n\n,`sepia`\n\n**Clip-path**—`polygon`\n\n,`circle`\n\n,`ellipse`\n\n,`inset`\n\n,`path`\n\n,`url()`\n\n**Block flow**— vertical stacking** Inline flow**— horizontal text wrapping with proper baseline** Flexbox**—`flex-direction`\n\n,`justify-content`\n\n,`align-items`\n\n,`flex-wrap`\n\n,`gap`\n\n,`flex-grow`\n\n,`flex-shrink`\n\n,`flex-basis`\n\n**CSS Grid**—`grid-template-columns`\n\n/`grid-template-rows`\n\n(with`fr`\n\n,`auto`\n\n,`minmax()`\n\n,`repeat()`\n\n),`grid-column`\n\n/`grid-row`\n\nplacement,`gap`\n\n/`column-gap`\n\n/`row-gap`\n\n,`auto-flow`\n\n**Table layout**—`<table>`\n\n,`<tr>`\n\n,`<td>`\n\n,`<th>`\n\n,`<thead>`\n\n,`<tbody>`\n\n,`<tfoot>`\n\n,`<caption>`\n\n, column width distribution, border collapse**Float**—`float: left/right`\n\n, simple clear**Inline-block**— inline elements with block-like width/height** Box model**— margin, border, padding, content with`box-sizing: border-box`\n\nsupport**Position**—`static`\n\n,`relative`\n\n,`absolute`\n\n,`fixed`\n\n(parsed, relative + absolute positioning applied)**Units**— px, em, rem, pt, %, vw, vh** Writing modes**—`horizontal-tb`\n\n,`vertical-rl/lr`\n\n,`sideways-rl/lr`\n\n**Backgrounds**— solid colors and linear / radial gradients** Borders**— all four sides with custom colors and styles** Border-radius**— rounded corners (all four corners)** Box-shadow**— outer shadows with blur** Opacity**— alpha blending for entire elements** Text**— TrueType font rasterization via`ab_glyph`\n\n, bold and italic synthesis**Alpha compositing**— proper RGBA blending** SVG**— paths, basic shapes (`rect`\n\n,`circle`\n\n,`ellipse`\n\n,`line`\n\n,`polyline`\n\n,`polygon`\n\n), gradients, stroke + fill**PNG encoder**— hand-written, no`flate2`\n\ndependency\n\nFalco ships its own JavaScript VM (the `tjs`\n\nmodule) — pure Rust, no\nV8/SpiderMonkey/`boa`\n\n. It is a bytecode VM with a generational GC,\ninline caching, hidden classes, and a JIT tier-up.\n\n**ES2015+ syntax**—`let`\n\n/`const`\n\n, arrow functions, template literals, destructuring (object + array), default + rest params, spread,`for...of`\n\n,`for...in`\n\n, computed property names, shorthand methods/properties, optional chaining, nullish coalescing, exponentiation operator, async/await (parsed)**Functions**—`function foo() {}`\n\n, closures with proper upvalue capture, generators (`function*`\n\n/`yield`\n\n/`yield*`\n\n),`async function`\n\n/`await`\n\n(parser-level)**Types**—`Symbol`\n\nwith 13 well-known symbols,`BigInt`\n\nwith arbitrary precision (u32 limbs, signed),`Promise`\n\nwith`then`\n\n/`catch`\n\n/`finally`\n\n+ state machine,`Iterator`\n\nprotocol,`Generator`\n\nas state machine**Built-ins**—`Math`\n\n,`JSON`\n\n,`Array`\n\n(`push`\n\n,`pop`\n\n,`map`\n\n,`filter`\n\n,`reduce`\n\n,`forEach`\n\n,`find`\n\n,`findIndex`\n\n,`includes`\n\n,`slice`\n\n,`splice`\n\n,`flat`\n\n,`flatMap`\n\n),`String`\n\n(`split`\n\n,`replace`\n\n,`match`\n\n,`padStart`\n\n,`padEnd`\n\n,`trim`\n\n,`trimStart`\n\n,`trimEnd`\n\n,`startsWith`\n\n,`endsWith`\n\n,`includes`\n\n,`repeat`\n\n),`Object`\n\n(`keys`\n\n,`values`\n\n,`entries`\n\n,`assign`\n\n,`freeze`\n\n,`fromEntries`\n\n),`Reflect`\n\n(`get`\n\n,`set`\n\n,`has`\n\n,`deleteProperty`\n\n,`ownKeys`\n\n),`WeakMap`\n\n,`WeakSet`\n\n,`Map`\n\n,`Set`\n\n,`Proxy`\n\n**Microtask queue**—`Promise`\n\nreactions drained as microtasks**DOM bindings**—`document.getElementById`\n\n,`document.querySelector`\n\n/`querySelectorAll`\n\n,`console.log`\n\n,`alert`\n\n,`addEventListener`\n\n(basic)**Event loop integration**—`setTimeout`\n\n,`setInterval`\n\n,`requestAnimationFrame`\n\n,`fetch()`\n\n(returns`Promise<Response>`\n\n),`XMLHttpRequest`\n\n, all driven by the event loop in`web_runtime/event_loop.rs`\n\n**console.log(...)**— prints to stderr** alert(msg)**— shows in the status bar\n\n- HTTP/1.1 fetch (via\n`ureq`\n\n) - HTTP/2 parser (\n`http2.rs`\n\n) - Cookie jar (\n`cookies.rs`\n\n) with proper domain/path matching - Redirect handling (\n`redirect.rs`\n\n) with redirect-loop detection - Cache (\n`cache.rs`\n\n) — HTTP cache with conditional requests - WebSocket (\n`websocket.rs`\n\n) — frame parser, masking, ping/pong\n\n`fetch()`\n\n(`fetch.rs`\n\n) — Promise-based, integrates with event loop`XMLHttpRequest`\n\n(`xhr.rs`\n\n) — sync + async modes- Event loop (\n`event_loop.rs`\n\n) — task queues, microtasks, RAF `Promise`\n\n(`promise.rs`\n\n) — state machine, then/catch/finally- WebGL (\n`webgl.rs`\n\n) — shader compilation, buffer management, draw calls (headless) - Video (\n`video.rs`\n\n) —`<video>`\n\nelement demux + decode stub - MSE (\n`mse.rs`\n\n) — Media Source Extensions - EME (\n`eme.rs`\n\n) — Encrypted Media Extensions - NDSD (\n`ndsd.rs`\n\n) — Native Device Service Discovery\n\n**Origin / SOP**(`origin.rs`\n\n) — Origin struct,`is_same_origin`\n\n,`is_same_site`\n\n,`registrable_domain`\n\n(with 2-part TLD list),`check_cors`\n\n(with credentials / wildcard handling),`check_navigation`\n\n**Multi-process / site isolation**(`process.rs`\n\n) — Process kinds (Browser / Renderer / GPU / Utility / Plugin), site-to-process map, ProcessPerSite / ProcessPerTab policies, crash recovery with max-restarts / sad-tab / fatal modes**Sandbox**(`sandbox.rs`\n\n) — seccomp-bpf filter (Linux), renderer allowlist (~30 syscalls), blocks`execve`\n\n/`fork`\n\n/`ptrace`\n\n/`open`\n\n/`socket`\n\n/`connect`\n\n/`mount`\n\n,`PR_SET_NO_NEW_PRIVS`\n\n,`drop_capabilities`\n\n**CSP**(`csp.rs`\n\n) — directive map,`default-src`\n\nfallback, source expression matching (`'self'`\n\n,`'none'`\n\n,`'unsafe-inline'`\n\n,`'unsafe-eval'`\n\n,`data:`\n\n,`blob:`\n\n, host,`*.wildcard`\n\n,`scheme:`\n\n), nonce/hash support,`allows_inline_script`\n\n,`allows_eval`\n\n,`allows_javascript_url`\n\n,`is_safe_attribute`\n\n(blocks`onclick`\n\n,`onerror`\n\n,`javascript:`\n\nin href/src), violation reports**TLS certificates**(`cert.rs`\n\n) — Certificate struct, validity, hostname matching (with wildcards), TrustStore with Mozilla defaults,`validate_chain`\n\n(chain building, signature check, hostname, EKU, path length), OCSP stub, HPKP pinning, Certificate Transparency**Permissions**(`permissions.rs`\n\n) — 20 permission types (Geolocation, Camera, Microphone, Notifications, ...), per-(origin, permission) state, pluggable prompt handler, iframe allow parsing**Extensions**(`extensions.rs`\n\n) — Manifest V3, content scripts, match patterns (`<all_urls>`\n\n,`*://*.host/*`\n\n), glob matching, permissions, generate extension ID, ChromeApi enum**DevTools protocol**(`devtools.rs`\n\n) — JSON value type, Request/Response/Event/RpcError, Inspector/Page/Runtime/DOM/Network/ Console methods, event subscribers, console message buffering\n\n**HTTP/HTTPS URLs**— fetched via`ureq`\n\n— base64-encoded inline images`data:`\n\nURLs**Local files**— relative paths resolved against the page URL** Formats**— PNG, JPEG, GIF (first frame), BMP (via the`image`\n\ncrate)**Sizing**—`width`\n\n/`height`\n\nHTML attributes take precedence, CSS`width`\n\n/`height`\n\nrespected, default 300×200px, nearest-neighbor scaling**Broken images**— grey placeholder box with the`alt`\n\ntext**Caching**— global cache by URL\n\n- The\n`html::spec`\n\n,`dom::spec`\n\n,`css::spec`\n\n,`tjs_ext/`\n\n,`security/`\n\nmodules are**structurally complete but not yet wired into the render pipeline**. Falco still uses the legacy`html`\n\n/`dom`\n\n/`css`\n\nmodules for actual rendering. The new modules exist as the spec-compliant replacements and pass their own unit tests, but the renderer has not been switched over yet. - No CSS animations / transitions in the renderer (the data structures\nexist in\n`css/spec/advanced.rs`\n\n, but the paint loop does not interpolate them). - No\n`@media`\n\nquery value matching in the renderer (queries are parsed; the cascade does not yet apply them conditionally). - No real DOM mutation from JS in the renderer (\n`element.innerHTML = ...`\n\n,`element.style.color = ...`\n\nare stubs). - No HTML5 spec-compliant tree repair in the renderer (the algorithm\nexists in\n`html/spec/tree_builder.rs`\n\nbut the legacy parser is used).\n\n| Module | Lines | Description |\n|---|---|---|\n`html::spec` |\n~4,900 | WHATWG HTML5 tokenizer + tree builder + serializer + XML parser + encoding |\n`html.rs` |\n~540 | Legacy HTML parser (still used in render pipeline) |\n`dom::spec` |\n~2,280 | Spec-compliant DOM, MutationObserver, Shadow DOM, custom elements, a11y |\n`dom.rs` |\n~140 | Legacy DOM (still used in render pipeline) |\n`css::spec` |\n~2,020 | Selectors L4, cascade specificity, @-rules, animations, containment, filters |\n`css/` |\n~1,660 | Legacy CSS parser + selector matching + color parsing |\n`style/` |\n~1,720 | Style cascade + UA styles + inheritance + flex/grid properties |\n`layout/` |\n~1,950 | Block / inline / flex / grid / table / float / absolute layout |\n`paint/` |\n~470 | Canvas + font rasterizer + alpha compositing + gradients + shadows |\n`svg/` |\n~1,130 | SVG parser + renderer (paths, shapes, gradients) |\n`tjs/` |\n~4,340 | Custom JS VM: lexer, parser, interpreter, bytecode VM, JIT, value, builtins |\n`tjs_ext/` |\n~780 | Symbol, BigInt, Promise, microtasks, Map/Set, WeakMap/WeakSet, Reflect |\n`js_tjs.rs` |\n~700 | JS-to-DOM bindings (document, console, alert, onclick) |\n`web_runtime/` |\n~4,300 | fetch, XHR, event loop, Promise, WebGL, video, MSE, EME, NDSD, HTTP/2 |\n`net/` |\n~930 | HTTP fetch, cookies, cache, websocket, redirect |\n`security/` |\n~3,590 | SOP, multi-process, sandbox, CSP, certs, permissions, extensions, DevTools |\n`window/` |\n~1,110 | Interactive window: scrolling, forms, navigation, history, address bar |\n`image/` |\n~200 | Image loader (HTTP, data: URLs, local files) + cache + scaling |\n`png/` |\n~100 | Hand-written PNG encoder (no flate2 dependency) |\n`main.rs` , `lib.rs` |\n~550 | CLI parsing + library entry points |\nTotal |\n~36,000 |\n\n```\nfalco/\n├── .github/                  # CI, issue templates, contributing, security policy\n│   ├── workflows/\n│   │   ├── ci.yml            # fmt + clippy + build + test on 3 OSes × 2 toolchains\n│   │   └── release.yml       # Build per-OS release binaries on tag push\n│   ├── ISSUE_TEMPLATE/       # bug_report.md, feature_request.md, config.yml\n│   ├── CONTRIBUTING.md\n│   ├── CODE_OF_CONDUCT.md\n│   ├── SECURITY.md\n│   ├── PULL_REQUEST_TEMPLATE.md\n│   ├── FUNDING.yml           # MonoBank donation link\n│   └── dependabot.yml\n├── docs/                     # Logo\n│   └── falco.svg             # project logo (by Sanya)\n├── src/\n│   ├── main.rs\n│   ├── lib.rs\n│   ├── html/spec/                # WHATWG HTML5 (tokenizer, tree builder, ...)\n│   ├── html.rs               # legacy HTML parser\n│   ├── dom/spec/                 # spec DOM (observer, shadow, custom elements, a11y)\n│   ├── dom.rs                # legacy DOM\n│   ├── css/spec/                 # selectors L4, cascade, @-rules\n│   ├── css/                  # legacy CSS parser\n│   ├── style/                # cascade + inheritance + UA styles\n│   ├── layout/               # block/inline/flex/grid/table/float/absolute\n│   ├── paint/                # canvas + fonts + compositing\n│   ├── svg/                  # SVG parser + renderer\n│   ├── tjs/                  # custom JS VM (lexer, parser, VM, JIT)\n│   ├── tjs_ext/              # Symbol, BigInt, Promise, ...\n│   ├── web_runtime/          # fetch, XHR, event loop, WebGL, video, MSE, EME\n│   ├── net/                  # HTTP, cookies, cache, websocket, redirect\n│   ├── security/             # SOP, sandbox, CSP, certs, permissions, DevTools\n│   ├── window/               # interactive window mode\n│   ├── image/                # image loader\n│   └── png/                  # PNG encoder\n├── Cargo.toml\n├── Cargo.lock\n├── LICENSE\n└── README.md\n```\n\nThe next big pieces of work, roughly in priority order:\n\n**Wire**— switch off the legacy`html::spec`\n\n+`dom::spec`\n\ninto the render pipeline`html`\n\n+`dom`\n\nmodules. This unlocks spec-compliant tree repair, full MutationObserver, and real custom elements.**Wire**— get`css::spec`\n\ninto the cascade`:is`\n\n/`:where`\n\n/`:has`\n\nworking in real rendering, plus cascade layers and container queries.**DOM mutation from JS**—`element.innerHTML = ...`\n\n,`element.style.color = ...`\n\n, full re-render on mutation.**CSS animations / transitions**— interpolate keyframes in the paint loop, run them through the event loop.** Wire**— SOP enforcement in DOM access, CSP in the script runner, certificate validation on HTTPS fetches, multi-process sandbox.`security/`\n\ninto the renderer— apply queries conditionally based on viewport size.`@media`\n\nquery value matching\n\nIf Falco is useful to you, consider buying the author a coffee:\n\nMIT — see [LICENSE](/poxk/Falco/blob/main/LICENSE).", "url": "https://wpnews.pro/news/falco-tiny-browser-engine-written-from-scratch-in-rust", "canonical_source": "https://github.com/poxk/Falco", "published_at": "2026-08-02 18:00:52+00:00", "updated_at": "2026-08-02 18:22:51.700012+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence"], "entities": ["Falco", "Rust", "Sanya"], "alternates": {"html": "https://wpnews.pro/news/falco-tiny-browser-engine-written-from-scratch-in-rust", "markdown": "https://wpnews.pro/news/falco-tiny-browser-engine-written-from-scratch-in-rust.md", "text": "https://wpnews.pro/news/falco-tiny-browser-engine-written-from-scratch-in-rust.txt", "jsonld": "https://wpnews.pro/news/falco-tiny-browser-engine-written-from-scratch-in-rust.jsonld"}}