Hi everyone! I just released v0.1.0 of Falco — a browser engine I’ve been building in Rust on nights and weekends. No WebKit, no Gecko, no Chromium — every module is written from scratch in ~36,000 lines.
🔗 GitHub: https://github.com/poxk/Falco
🔗 Releases (prebuilt binaries for Linux/macOS/Windows): https://github.com/poxk/Falco/releases/tag/v0.1.0
What’s inside
All modules are from scratch, with no dependency on existing browser engines:
HTML5 (html5/ + html.rs)
- Tokenizer — all 80 states of WHATWG §13.2.5, including script-data escape/double-escape state machine, attribute parsing with duplicate detection, named + numeric character references with Windows-1252 quirks table
- Tree builder — all 22 insertion modes of WHATWG §13.2.6, stack of open elements with scope algorithms (default/button/table/select/list-item), active formatting elements list, adoption agency algorithm (8-iteration outer loop with full inner loop and bookmark tracking), foster parenting for table content
- XML parser — strict, with namespace bindings, CDATA, PIs
- Encoding detection — BOM, HTTP Content-Type charset,
<meta charset>,<meta http-equiv>, heuristic UTF-8/UTF-16 detection, decoders for UTF-8 / UTF-16LE / UTF-16BE / Windows-1252 - innerHTML/outerHTML serialization — void elements,
<template>contents fragment, raw text elements, full attribute value escaping
DOM (dom2/)
-
NodeRef = Rc<RefCell<Node>>with parent/firstChild/lastChild/previousSibling/nextSibling pointers per spec - MutationObserver with observe()/disconnect()/take_records(), subtree ancestor matching, attributeFilter
- Shadow DOM — attachShadow() with open/closed modes, host validation, named + default slots, fallback content, slot distribution (flatten tree algorithm)
- Custom elements — customElements.define() with name validation, observedAttributes, lifecycle callbacks (connected/disconnected/adopted/attributeChanged/form-associated), pending upgrades, customized built-in elements (is=”…”)
- Accessibility tree — parallel tree with role/name/description/state/actions, implicit ARIA roles for ~50 HTML tags
CSS (css/ + css2/)
- Selectors Level 4 — type, class, id, universal, descendant, child, adjacent sibling, general sibling, attribute (=, ~=, |=, ^=, $=, *=)
- Pseudo-classes — :hover, :focus, :focus-visible, :focus-within, :active, :visited, :checked, :disabled, :enabled, :empty, :root, :first-child, :last-child, :only-child, :first-of-type, :last-of-type, :only-of-type, :nth-child(an+b), :nth-last-child, :nth-of-type, :nth-last-of-type, :nth-child(an+b of S), :is(), :where(), :not(), :has() (with relative selectors), :lang(), :dir()
- Cascade & specificity — (a, b, c) tuple, :where() zero specificity, :is()/:not()/:has() most specific arg, CascadeOrigin (UA/User/Author) with reversed order for !important, CascadeLayers
- @-rules — @media (parsed), @keyframes with cubic-bezier & steps timing, @font-face, @layer (cascade layers), @container queries with evaluate_container_query()
- Logical properties — margin-inline-start etc. resolved based on writing-mode
JavaScript VM (tjs/ + tjs_ext/)
- Custom bytecode VM with generational GC, inline caching, hidden classes, JIT tier-up (x86_64 only)
- ES2015+ syntax — let/const, arrow functions, template literals, destructuring, default + rest params, spread, for…of, computed property names, optional chaining, nullish coalescing
- Types — Symbol with 13 well-known symbols, BigInt (arbitrary precision, u32 limbs, signed), Promise with then/catch/finally + state machine, Iterator protocol, Generator as state machine
- Built-ins — Math, JSON, Array (map/filter/reduce/forEach/find/findIndex/includes/slice/splice/flat/flatMap), String (split/replace/match/padStart/padEnd/trim/startsWith/endsWith/includes/repeat), Object (keys/values/entries/assign/freeze/fromEntries), Reflect (get/set/has/deleteProperty/ownKeys), WeakMap, WeakSet, Map, Set, Proxy
- Microtask queue — Promise reactions drained as microtasks
- DOM bindings — document.getElementById, querySelector/querySelectorAll, console.log, alert, addEventListener (basic)
- Event loop integration — setTimeout, setInterval, requestAnimationFrame, fetch() (returns Promise), XMLHttpRequest
Layout (layout/)
- Block flow, inline flow (horizontal text wrapping with baselines)
- Flexbox — flex-direction, justify-content, align-items, flex-wrap, gap, flex-grow/shrink/basis
- CSS Grid — grid-template-columns/rows (with fr, auto, minmax(), repeat()), grid-column/grid-row placement, gap, auto-flow
- Table layout —
<table>,<tr>,<td>,<th>,<thead>,<tbody>,<tfoot>,<caption>, column width distribution, border collapse - Float, inline-block, position (static/relative/absolute/fixed)
- Units — px, em, rem, pt, %, vw, vh
- Writing modes — horizontal-tb, vertical-rl/lr, sideways-rl/lr
Painting (paint/ + svg/)
- Backgrounds (solid + linear/radial gradients), borders, border-radius, box-shadow, opacity (alpha blending)
- TrueType font rasterization via ab_glyph, bold and italic synthesis
- SVG — paths, basic shapes (rect/circle/ellipse/line/polyline/polygon), gradients, stroke + fill
- Hand-written PNG encoder (no flate2 dependency)
Security (security/) — implemented, not yet wired into renderer
- Origin / SOP — check_same_origin, check_cors (with credentials/wildcard handling), check_navigation, registrable_domain (with 2-part TLD list)
- Multi-process / site isolation — Process kinds (Browser/Renderer/GPU/Utility/Plugin), ProcessPerSite/ProcessPerTab policies, crash recovery with max-restarts/sad-tab/fatal modes
- Seccomp-bpf sandbox — renderer allowlist (~30 syscalls), blocks execve/fork/ptrace/open/socket/connect/mount, PR_SET_NO_NEW_PRIVS, drop_capabilities
- CSP — directive map, default-src fallback, source expression matching (‘self’/’none’/’unsafe-inline’/’unsafe-eval’/data:/blob:/host/*.wildcard/scheme:), nonce/hash support, allows_inline_script, allows_eval, allows_javascript_url, is_safe_attribute
- TLS certificates — Certificate struct, validity, hostname matching (with wildcards), TrustStore with Mozilla defaults, validate_chain (chain building, signature check, hostname, EKU, path length), OCSP stub, HPKP pinning, Certificate Transparency
- Permissions — 20 permission types, per-(origin, permission) state, pluggable prompt handler, iframe allow parsing
- Extensions — Manifest V3, content scripts, match patterns (/://.host/*), glob matching, permissions, ChromeApi enum
- DevTools protocol — JSON value type, Request/Response/Event/RpcError, Inspector/Page/Runtime/DOM/Network/Console methods, event subscribers, console message buffering
Networking (net/)
- HTTP/1.1 fetch (ureq), HTTP/2 parser, cookie jar with proper domain/path matching, redirect handling with loop detection, HTTP cache with conditional requests, WebSocket (frame parser, masking, ping/pong)
Web runtime (web_runtime/)
- fetch(), XMLHttpRequest (sync + async), event loop (task queues, microtasks, RAF), Promise, WebGL (headless stubs), video, MSE, EME, NDSD, HTTP/2
Interactive --window mode
- Click links, address bar (F6 to focus), reload (r), back/forward (Alt+←/→)
- Form inputs (text/email/password/checkbox/submit/button/textarea), Tab/Shift+Tab cycling, Enter to submit
- Scrolling — mouse wheel, arrow keys, Page Up/Down, Home/End, vim-style g/G
- Focus ring, blinking caret (500ms), hover hint in status bar
- Headless fallback to PNG output if no display available
For comparison
Engine LOC Language Chrome/Blink ~30M C++ Firefox/Gecko ~20M C++/Rust Safari/WebKit ~15M C++ Servo ~1M Rust Ladybird ~500k C++ Falco ~36k RustThe whole Falco codebase fits in a weekend of reading.
Quick start
cargo build --release
./target/release/falco https://example.com --out example.png
./target/release/falco page.html --window
Enter fullscreen mode Exit fullscreen mode
328 unit tests pass, 30 ignored (mostly platform-specific JIT tests that fail on macOS runners due to MAP_JIT quirks).
What’s NOT done yet (being honest)
The spec-compliant HTML5/DOM/CSS modules (html5/, dom2/, css2/) are structurally complete and pass their own unit tests, but not yet wired into the render pipeline — the legacy parser is still used for actual rendering. That’s the v0.2.0 milestone.
The security module (security/) implements all algorithms (SOP, CSP, sandbox, cert validation, permissions, DevTools) but they’re not enforced in the renderer yet — same situation.
What I’d love feedback on
DOM model — I’m using
Rc<RefCell<Node>>with parent/child/sibling pointers, matching the spec. For production I’d probably use a slotmap arena, but Rc is easier to read and matches the spec’s pointer model. Thoughts?-
Architecture — I have both legacy (
html.rs/dom.rs/css.rs) and spec-compliant (html5//dom2//css2/) modules. The spec-compliant ones are not yet wired in. Should I:- (a) Wire them in before any other feature work, or
- (b) Focus on CSS animations/transitions first, or
- (c) Focus on JS DOM mutation (innerHTML/style changes)?
JS VM — I wrote my own bytecode VM (
tjs) instead of usingboa_engineor binding V8/SpiderMonkey. Reasoning: I wanted full control over the GC, the bytecode format, and the DOM integration. The VM is intentionally limited (no real regex engine, no Proxy trap completeness, no real async functions). Is this a reasonable tradeoff for a teaching engine?Sandbox — I implemented seccomp-bpf filters but they’re Linux-only and not yet enforced. For cross-platform sandboxing, is the Windows Job Object approach + macOS sandbox-exec reasonable, or should I look at platform-agnostic alternatives?
Architecture overview
Module Lines Descriptionhtml5/
~4,900
WHATWG HTML5 tokenizer + tree builder + serializer + XML + encoding
html.rs
~540
Legacy HTML parser (still used in render pipeline)
dom2/
~2,280
Spec-compliant DOM, MutationObserver, Shadow DOM, custom elements, a11y
dom.rs
~140
Legacy DOM (still used in render pipeline)
css2/
~2,020
Selectors L4, cascade specificity, @-rules, animations, containment
css/
~1,660
Legacy CSS parser + selector matching + color parsing
style/
~1,720
Style cascade + UA styles + inheritance + flex/grid properties
layout/
~1,950
Block / inline / flex / grid / table / float / absolute layout
paint/
~470
Canvas + font rasterizer + alpha compositing + gradients + shadows
svg/
~1,130
SVG parser + renderer (paths, shapes, gradients)
tjs/
~4,340
Custom JS VM: lexer, parser, interpreter, bytecode VM, JIT, value, builtins
tjs_ext/
~780
Symbol, BigInt, Promise, microtasks, Map/Set, WeakMap/WeakSet, Reflect
web_runtime/
~4,300
fetch, XHR, event loop, Promise, WebGL, video, MSE, EME, NDSD, HTTP/2
net/
~930
HTTP fetch, cookies, cache, websocket, redirect
security/
~3,590
SOP, multi-process, sandbox, CSP, certs, permissions, extensions, DevTools
window/
~1,110
Interactive window: scrolling, forms, navigation, history, address bar
image/
~200
Image loader (HTTP, data: URLs, local files) + cache + scaling
png/
~100
Hand-written PNG encoder (no flate2 dependency)
Total
~36,000
Links
- GitHub: https://github.com/poxk/Falco
- Releases: https://github.com/poxk/Falco/releases
Thanks for any feedback! I’m especially interested in architectural critique from anyone who has worked on Servo, Ladybird, or other browser engines.
답글 남기기