[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-94296":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":14,"stars7d":14,"stars30d":15,"stars90d":14,"forks30d":14,"starsTrendScore":14,"compositeScore":16,"rankGlobal":9,"rankLanguage":9,"license":17,"archived":18,"fork":18,"defaultBranch":19,"hasWiki":18,"hasPages":18,"topics":20,"createdAt":9,"pushedAt":9,"updatedAt":21,"readmeContent":22,"aiSummary":23,"trendingCount":14,"starSnapshotCount":14,"syncStatus":13,"lastSyncTime":24,"discoverSource":25},94296,"Falco","poxk\u002FFalco","poxk","Tiny browser engine written from scratch in Rust.",null,"Rust",151,17,2,0,48,48.57,"MIT License",false,"main",[],"2026-08-24 04:01:21","\u003Cp align=\"center\">\n  \u003Cimg src=\"docs\u002Ffalco.svg\" alt=\"Falco logo\" width=\"220\" height=\"220\">\n\u003C\u002Fp>\n\n\u003Ch1 align=\"center\">Falco\u003C\u002Fh1>\n\n\u003Cp align=\"center\">\n  \u003Cem>Logo by \u003Cstrong>Sanya\u003C\u002Fstrong> — \u003Ca href=\"https:\u002F\u002Ft.me\u002FSanyochekDev\">t.me\u002FSanyochekDev\u003C\u002Fa>\u003C\u002Fem>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  A tiny, fast browser engine written in Rust.\u003Cbr>\n  Renders HTML, CSS, JavaScript, SVG and images — to a PNG, or to a live interactive window.\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Ca href=\"LICENSE\">\u003Cimg alt=\"License: MIT\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Flicense-MIT-yellow.svg\">\u003C\u002Fa>\n  \u003Cimg alt=\"Built with Rust\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fbuilt%20with-rust-orange.svg\">\n  \u003Cimg alt=\"Binary size\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fbinary-~10%20MB-blue.svg\">\n  \u003Cimg alt=\"Lines of Rust\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FLOC-~36%2C000-9cf.svg\">\n  \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fpoxk\u002FFalco\u002Factions\u002Fworkflows\u002Fci.yml\">\u003Cimg alt=\"CI\" src=\"https:\u002F\u002Fgithub.com\u002Fpoxk\u002FFalco\u002Factions\u002Fworkflows\u002Fci.yml\u002Fbadge.svg\">\u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fsend.monobank.ua\u002Fjar\u002F21T9ZRwZq3\">\u003Cimg alt=\"Donate\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fdonate-MonoBank-7c3aed.svg\">\u003C\u002Fa>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Ca href=\"#quick-start\">Quick start\u003C\u002Fa> ·\n  \u003Ca href=\"#interactive-mode---window\">Interactive mode\u003C\u002Fa> ·\n  \u003Ca href=\"#what-it-supports\">Features\u003C\u002Fa> ·\n  \u003Ca href=\"#architecture\">Architecture\u003C\u002Fa> ·\n  \u003Ca href=\".github\u002FCONTRIBUTING.md\">Contributing\u003C\u002Fa>\n\u003C\u002Fp>\n\n---\n\n## What it is\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 \u002F Window\n            ▲           ▲              ▲\n            │           │              │\n       HTML5 tokenizer  CSS cascade    Flex \u002F Grid \u002F Table \u002F Float \u002F Absolute\n       + tree builder   + Selectors 4  + Inline \u002F Block flow\n            │\n       JS (custom VM with closures, generators, Promise, BigInt, Symbol)\n            │\n       Image loader ──▶ HTTP \u002F data: URL \u002F 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\n## Honest status (what works vs what's a stub)\n\nTo set expectations clearly — this is v0.1.0, an early release by a\nsingle developer. Not everything listed in this README is production-ready.\nHere's what is actually wired into the render pipeline and what is\nstructurally complete but not yet called:\n\n### ✅ Actually works (called by `render_with_base_url`)\n- HTML parser (`html\u002F`) — legacy parser, not spec-compliant but functional\n- DOM types (`dom\u002F`) — minimal node types, no observers\u002Fshadow\n- CSS parser (`css\u002F`) — selectors, properties, cascade, color parsing\n- Style cascade (`style\u002F`) — UA styles + inheritance + flex\u002Fgrid props\n- Layout (`layout\u002F`) — block \u002F inline \u002F flex \u002F **CSS Grid** \u002F **table** \u002F float \u002F absolute\n- Painting (`paint\u002F`) — fonts (ab_glyph), gradients, shadows, alpha compositing\n- SVG renderer (`svg\u002F`) — paths, basic shapes, gradients, stroke + fill\n- Hand-written PNG encoder (`png\u002F`)\n- Image loader (`image\u002F`) — HTTP, data: URLs, local files\n- JS VM (`tjs\u002F`) — bytecode interpreter + JIT (x86_64, Linux-only)\n- JS-DOM bindings (`js_tjs\u002F`, `js_runner\u002F`) — `document.getElementById`, `console.log`, `alert`, `onclick`\n- **DOM mutation from JS** (new in v0.2.0) — `element.innerHTML = ...`, `element.style.color = ...`, `appendChild`, `removeChild` all mutate the spec DOM and trigger re-render via the `legacy_dom_to_spec_document` → `TjsJsContext` → `serialize_spec_document` bridge\n- **Real `fetch()` from JS** (new in v0.2.0) — blocking HTTP via `ureq`, returns response with `ok`, `status`, `text()`, `json()`\n- Networking (`net\u002F`) — HTTP\u002F1.1 (ureq), cookies, cache, websocket, redirect\n- Interactive `--window` mode — scrolling, forms, navigation, history\n\n### ⚠️ Structurally complete, passes own unit tests, **NOT wired into renderer**\nThese exist as spec-compliant replacements for the legacy modules. They\ncompile and have their own unit tests, but `render_with_base_url` does\nnot call into them yet. This is the v0.3.0 milestone.\n- `html::spec` — WHATWG §13.2 tokenizer (all 80 states) + tree builder (all 22 insertion modes) + serializer + XML parser + encoding detection. **Note**: the serializer IS used by the v0.2.0 DOM mutation bridge, but the tokenizer\u002Ftree_builder are not.\n- `dom::spec` — spec DOM with MutationObserver, Shadow DOM, custom elements, accessibility tree. **Note**: the core `Node`\u002F`ElementData`\u002F`Document` types ARE used by the v0.2.0 DOM mutation bridge, but MutationObserver\u002FShadow DOM\u002Fcustom elements are not yet exposed to JS.\n- `css::spec` — Selectors Level 4 (`:has()`, `:is()`, `:where()`, cascade layers, container queries)\n- `tjs_ext\u002F` — Symbol, BigInt, Promise, microtasks, Map\u002FSet, WeakMap\u002FWeakSet, Reflect\n\n### ⚠️ Implemented algorithms, **NOT enforced in renderer**\n- `security\u002F` — 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.\n- `web_runtime\u002F` — `fetch()` is **real** as of v0.2.0 (uses `ureq` for blocking HTTP, returns response with `text()` and `json()`). `XMLHttpRequest` is still a stub. The Promise \u002F event loop integration is real and unit-tested, but the JS bridge doesn't yet integrate with it (fetch is blocking, not async\u002FPromise-based). 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.\n- `media\u002F` — `@media` queries are **now evaluated conditionally** as of v0.3.0. The render pipeline passes the viewport size to `css::parse_with_viewport()`, and rules inside `@media (min-width: Npx)`, `(max-width: Npx)`, `(min-height: Npx)`, `(max-height: Npx)` blocks are only included when they match. Unsupported features (`orientation`, `prefers-color-scheme`) fail closed.\n\n### ❌ Known broken \u002F unfinished\n- `real-http2`, `real-webgl`, `sandbox` Cargo features do not compile with `--all-features` because 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.\n- The JIT (`tjs\u002Fjit.rs`) works on Linux x86_64 but fails on macOS CI runners because `mmap(MAP_JIT)` requires code signing with the `com.apple.security.cs.allow-jit` entitlement. JIT tests are marked `#[ignore]` on macOS.\n- HTML5 spec-compliant tree repair (adoption agency, foster parenting) is in `html\u002Fspec\u002Ftree_builder.rs` but the legacy parser is what actually runs.\n\n**Bottom line:** if you `cargo build && .\u002Ffalco https:\u002F\u002Fexample.com --out out.png`, you get a real PNG render. The HTML\u002FCSS\u002Flayout\u002Fpaint path works end-to-end. The spec-compliant parsers, security enforcement, and advanced web runtime (WebGL\u002Fvideo\u002FDRM) are **scaffolding for future milestones**, not working features. Please read the code before claiming otherwise.\n\n## Quick start\n\n```bash\n# Build (release binary lands in target\u002Frelease\u002Ffalco)\ncargo build --release\n\n# Render a local HTML file to PNG\n.\u002Ftarget\u002Frelease\u002Ffalco page.html --out page.png --width 1200\n\n# Render a URL to PNG\n.\u002Ftarget\u002Frelease\u002Ffalco https:\u002F\u002Fexample.com --out example.png --width 800\n\n# Open interactive live window (desktop only — scrolls, hover, link clicks)\n.\u002Ftarget\u002Frelease\u002Ffalco page.html --window\n\n# Render with external CSS merged on top of \u003Cstyle> tags\n.\u002Ftarget\u002Frelease\u002Ffalco README.html --css style.css --out readme.png\n```\n\n`\u003Cstyle>` tags inside the HTML are automatically extracted and\napplied. External CSS via `--css` is merged on top.\n\n## Interactive mode (`--window`)\n\nWhen you pass `--window`, 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### Navigation\n- **Click links** (`\u003Ca href>`) — Falco fetches the new URL and re-renders\n- **Address bar** — click it or press `F6`, type a URL, press `Enter`\n- **Reload** with `r` — re-fetches the current page\n- **Back\u002FForward** with `Alt+←` \u002F `Alt+→` — full history navigation\n\n### Forms\n- **Click inputs** to focus them, then **type** to enter text (live update)\n- **Tab \u002F Shift+Tab** — cycle focus between interactive elements\n- **Backspace** — delete the last character\n- **Enter** — submit form \u002F click the focused button \u002F follow focused link\n- Supported types: `text`, `email`, `password` (masked), `checkbox`, `submit`, `button`, `textarea`\n\n### Scrolling & visual feedback\n- **Mouse wheel**, **arrow keys**, **Page Up\u002FDown**, **Home\u002FEnd**\n- **Focus ring** — 2px blue outline around the focused element\n- **Blinking caret** — 500ms blink in text inputs and the address bar\n- **Hover hint** — when hovering a link, its href appears in the status bar\n\n### Incremental repaint\nThe window only re-paints when state actually changes (scroll, input\ntext, focus, hover). On idle frames, only the address bar \u002F status bar\noverlays are redrawn — the page canvas is cached. This keeps CPU usage\nlow and scrolling smooth.\n\n### Headless fallback\nIf no display is available (headless server, no X11\u002FWayland), Falco\nautomatically falls back to PNG output with a warning.\n\n## CLI reference\n\n```\nfalco — a tiny browser engine\n\nUSAGE\n  falco \u003Cinput> [OPTIONS]\n\nINPUT\n  A URL (http:\u002F\u002F...) or a path to a local .html file.\n\nOPTIONS\n  --css \u003Cpath>       External CSS file (merged with \u003Cstyle> tags in HTML).\n  --out \u003Cpath>       Output PNG path. Default: falco.png\n  --width \u003Cpx>       Viewport width. Default: 1200\n  --height \u003Cpx>      Viewport height (canvas grows if content is taller). Default: 800\n  --bg \u003Chex>         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 \u002F Esc       quit\n  r             reload (prints message — restart Falco to actually reload)\n  ↑ \u002F ↓         scroll line\n  PgUp \u002F PgDn   scroll page\n  Home \u002F End    jump to top \u002F bottom\n  g \u002F G         top \u002F bottom (vim-style)\n  mouse wheel   scroll\n  mouse click   follow `\u003Ca href>` link (prints URL to stderr)\n```\n\n## Programmatic API\n\n### Render to PNG\n\n```rust\nuse falco::{render_to_png, RenderOptions};\n\nfn main() -> anyhow::Result\u003C()> {\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### Render to raw RGBA buffer (for game engines \u002F GPU textures)\n\nNew in v0.1.1. Skips PNG encoding and returns raw RGBA pixels, ready to\nupload to a GPU texture.\n\n```rust\nuse falco::{render_to_buffer, RenderOptions};\n\nfn main() -> anyhow::Result\u003C()> {\n    let opts = RenderOptions { width: 1280, height: 720, ..Default::default() };\n    let rendered = render_to_buffer(\"\u003Ch1>Hello\u003C\u002Fh1>\", \"h1 { color: red; }\", opts)?;\n\n    \u002F\u002F RGBA, row-major, top-to-bottom:\n    let rgba: &[u8] = rendered.as_rgba();\n\n    \u002F\u002F For DirectX \u002F Win32 \u002F Vulkan surfaces that expect BGRA:\n    let bgra: Vec\u003Cu8> = rendered.to_bgra();\n\n    \u002F\u002F For RGB-only contexts (no alpha):\n    let rgb: Vec\u003Cu8> = rendered.to_rgb();\n\n    Ok(())\n}\n```\n\nThis is the intended entry point for embedding Falco into game engines\nand GUI toolkits (Bevy, Fyrox, egui, custom engines). The `RenderedBuffer`\nstruct also exposes `width` and `height` so you can size your GPU texture\ncorrectly.\n\n## Benchmarks\n\nMeasured on Linux, AMD Ryzen 5 5600X, release build. Times include\nHTML parse + CSS cascade + layout + paint + PNG encode.\n\n| Page | HTML size | Render time | Output PNG |\n|------|-----------|-------------|------------|\n| `https:\u002F\u002Fexample.com` | 1.1 KB | ~46 ms (incl. network fetch) | 800x600 |\n| `tests\u002Ffixtures\u002Fmodern.html` (flexbox + gradients) | 3.5 KB | ~110 ms | 1200x998 |\n| `tests\u002Ffixtures\u002Fsample.html` | 2.5 KB | ~116 ms | 1200x2471 |\n| Hacker News front page | ~50 KB | ~300 ms | 1280x1440 |\n| Simple `\u003Ch1>Hello\u003C\u002Fh1>` (no network) | 30 B | ~12 ms | 1200x60 |\n\nCold start (empty image cache) adds ~50 ms on first render. Warm cache\nis what the table above shows.\n\nBinary size: ~10 MB (release, stripped, with default features).\n\nProper `criterion` benchmarks are on the v0.1.2 todo list.\n\n## What it supports\n\n### HTML\n- **HTML5 tokenizer** (WHATWG §13.2.5) — all 80 states, script-data\n  escape\u002Fdouble-escape, attribute parsing with duplicate detection,\n  named + numeric character references with Windows-1252 quirks\n- **HTML5 tree builder** (WHATWG §13.2.6) — all 22 insertion modes,\n  stack of open elements with scope algorithms, active formatting\n  elements list, reconstruct active formatting, **adoption agency\n  algorithm**, **foster parenting** for table content, `\u003Ctemplate>`\n  with separate DocumentFragment contents\n- **Entity references** — `&amp;`, `&#65;`, `&copy;`, 50+ named entities\n- **Whitespace collapsing** (browser-style)\n- **`\u003Ctemplate>` element** with separate DocumentFragment contents\n- **XML\u002FXHTML parser** — strict, with namespace bindings, CDATA, PIs\n- **Encoding detection** — BOM, HTTP `Content-Type` charset, `\u003Cmeta\n  charset>`, `\u003Cmeta http-equiv>`, heuristic UTF-8\u002FUTF-16 detection,\n  decoders for UTF-8 \u002F UTF-16LE \u002F UTF-16BE \u002F Windows-1252\n- **Tree builder auto-close** — `\u003Cli>`, `\u003Cp>`, `\u003Ctd>`, `\u003Ctr>`,\n  `\u003Coption>`, `\u003Cdt>`\u002F`\u003Cdd>`\n- **innerHTML \u002F outerHTML serialization** — void elements, `\u003Ctemplate>`\n  contents fragment, raw text elements, full attribute value escaping\n\n### DOM (spec-compliant, `dom::spec`)\n- `NodeRef = Rc\u003CRefCell\u003CNode>>` with `parent`, `firstChild`,\n  `lastChild`, `previousSibling`, `nextSibling` pointers per spec\n- `DocumentHandle = Rc\u003CRefCell\u003CDocument>>` with weak back-ref from\n  each Node\n- **Mutation records** queued on every\n  append\u002Finsert\u002Fremove\u002FsetAttribute\u002FremoveAttribute\n- **MutationObserver** with `observe()`, `disconnect()`,\n  `take_records()`, `MutationObserverInit`\n  (childList\u002Fattributes\u002FcharacterData\u002Fsubtree\u002FattributeOldValue\u002F\n  characterDataOldValue\u002FattributeFilter), subtree ancestor matching\n- **Shadow DOM** — `attachShadow()` with open\u002Fclosed modes, host\n  validation, named + default slots, fallback content, slot\n  distribution (flatten tree algorithm), `assignedSlot` lookup\n- **Custom elements** — `customElements.define()` with name validation,\n  `observedAttributes` tracking, lifecycle callbacks (connected \u002F\n  disconnected \u002F adopted \u002F attributeChanged \u002F form-associated),\n  pending upgrades, customized built-in elements (`is=\"...\"`)\n- **Accessibility tree** — parallel tree with role\u002Fname\u002Fdescription\u002F\n  state\u002Factions, implicit ARIA roles for ~50 HTML tags, honors\n  `role=\"\"`, `aria-hidden`, `hidden`, `display:none`, accessible name\n  computation (aria-label > aria-labelledby > element-specific > title)\n\n### CSS (`css\u002F` + `css::spec`)\n- **Selectors Level 4** — type, class, id, universal (`*`), descendant,\n  child (`>`), adjacent sibling (`+`), general sibling (`~`),\n  attribute (`[attr]`, `=`, `~=`, `|=`, `^=`, `$=`, `*=`)\n- **Pseudo-classes** — `:hover`, `:focus`, `:focus-visible`,\n  `:focus-within`, `:active`, `:visited`, `:checked`, `:disabled`,\n  `:enabled`, `:readonly`, `:readwrite`, `:required`, `:optional`,\n  `:valid`, `:invalid`, `:empty`, `:root`, `:first-child`,\n  `:last-child`, `:only-child`, `:first-of-type`, `:last-of-type`,\n  `:only-of-type`, `:nth-child(an+b)`, `:nth-last-child`,\n  `:nth-of-type`, `:nth-last-of-type`, `:nth-child(an+b of S)`,\n  `:is()`, `:where()`, `:not()`, `:has()`, `:lang()`, `:dir()`\n- **Cascade & specificity** — (a, b, c) tuple, `:where()` zero,\n  `:is()` \u002F `:not()` \u002F `:has()` most specific arg, CascadeOrigin\n  (UA \u002F User \u002F Author) with reversed order for `!important`,\n  CascadeLayers (None wins over layered; later wins over earlier)\n- **Properties** — `display`, `position`, `color`, `background`\n  (including `linear-gradient` and `radial-gradient`), `font-*`,\n  `margin`, `padding`, `border`, `border-radius`, `width`, `height`,\n  `min\u002Fmax-width`, `top\u002Fright\u002Fbottom\u002Fleft`, `z-index`, `overflow`,\n  `opacity`, `box-shadow`, `white-space`, `box-sizing`, `gap`,\n  `flex*`, `grid*`, `writing-mode`, logical properties\n  (`margin-inline-start`, etc.)\n- **Values** — keywords, hex\u002Frgb\u002Frgba\u002Fnamed colors, lengths (px, em,\n  rem, pt, %, vw, vh), percentages, numbers, `!important`\n- **Functions** — `linear-gradient()`, `radial-gradient()`, `url()`,\n  `var()`, `calc()` (simplified), `rgb()`, `rgba()`\n- **Shorthands** — `margin`, `padding`, `border`, `background`, `flex`\n- **@-rules** — `@media` (parsed and applied), `@keyframes` \u002F\n  `@animation` with cubic-bezier & steps timing functions,\n  `@font-face` with family\u002Fweight lookup, `@layer` (cascade layers),\n  `@container` queries with `evaluate_container_query()`\n- **Logical properties** — `margin-inline-start` etc. resolved to\n  physical properties based on `writing-mode`\n- **CSS counters** — `counter-reset`, `counter-increment`, `counter-set`\n- **Containment** — `contain: layout\u002Fpaint\u002Fsize\u002Fstyle\u002Finline-size\u002F\n  block-size`, `strict`, `content`\n- **Filters** — `blur`, `brightness`, `contrast`, `drop-shadow`,\n  `grayscale`, `hue-rotate`, `invert`, `opacity`, `saturate`, `sepia`\n- **Clip-path** — `polygon`, `circle`, `ellipse`, `inset`, `path`,\n  `url()`\n\n### Layout (`layout\u002F`)\n- **Block flow** — vertical stacking\n- **Inline flow** — horizontal text wrapping with proper baseline\n- **Flexbox** — `flex-direction`, `justify-content`, `align-items`,\n  `flex-wrap`, `gap`, `flex-grow`, `flex-shrink`, `flex-basis`\n- **CSS Grid** — `grid-template-columns` \u002F `grid-template-rows` (with\n  `fr`, `auto`, `minmax()`, `repeat()`), `grid-column` \u002F\n  `grid-row` placement, `gap` \u002F `column-gap` \u002F `row-gap`, `auto-flow`\n- **Table layout** — `\u003Ctable>`, `\u003Ctr>`, `\u003Ctd>`, `\u003Cth>`, `\u003Cthead>`,\n  `\u003Ctbody>`, `\u003Ctfoot>`, `\u003Ccaption>`, column width distribution,\n  border collapse\n- **Float** — `float: left\u002Fright`, simple clear\n- **Inline-block** — inline elements with block-like width\u002Fheight\n- **Box model** — margin, border, padding, content with\n  `box-sizing: border-box` support\n- **Position** — `static`, `relative`, `absolute`, `fixed` (parsed,\n  relative + absolute positioning applied)\n- **Units** — px, em, rem, pt, %, vw, vh\n- **Writing modes** — `horizontal-tb`, `vertical-rl\u002Flr`,\n  `sideways-rl\u002Flr`\n\n### Painting (`paint\u002F`)\n- **Backgrounds** — solid colors and linear \u002F radial gradients\n- **Borders** — all four sides with custom colors and styles\n- **Border-radius** — rounded corners (all four corners)\n- **Box-shadow** — outer shadows with blur\n- **Opacity** — alpha blending for entire elements\n- **Text** — TrueType font rasterization via `ab_glyph`, bold and\n  italic synthesis\n- **Alpha compositing** — proper RGBA blending\n- **SVG** — paths, basic shapes (`rect`, `circle`, `ellipse`,\n  `line`, `polyline`, `polygon`), gradients, stroke + fill\n- **PNG encoder** — hand-written, no `flate2` dependency\n\n### JavaScript (`tjs\u002F` + `tjs_ext\u002F`)\n\nFalco ships its own JavaScript VM (the `tjs` module) — pure Rust, no\nV8\u002FSpiderMonkey\u002F`boa`. It is a bytecode VM with a generational GC,\ninline caching, hidden classes, and a JIT tier-up.\n\n- **ES2015+ syntax** — `let` \u002F `const`, arrow functions, template\n  literals, destructuring (object + array), default + rest params,\n  spread, `for...of`, `for...in`, computed property names, shorthand\n  methods\u002Fproperties, optional chaining, nullish coalescing,\n  exponentiation operator, async\u002Fawait (parsed)\n- **Functions** — `function foo() {}`, closures with proper upvalue\n  capture, generators (`function*` \u002F `yield` \u002F `yield*`),\n  `async function` \u002F `await` (parser-level)\n- **Types** — `Symbol` with 13 well-known symbols, `BigInt` with\n  arbitrary precision (u32 limbs, signed), `Promise` with\n  `then`\u002F`catch`\u002F`finally` + state machine, `Iterator` protocol,\n  `Generator` as state machine\n- **Built-ins** — `Math`, `JSON`, `Array` (`push`, `pop`, `map`,\n  `filter`, `reduce`, `forEach`, `find`, `findIndex`, `includes`,\n  `slice`, `splice`, `flat`, `flatMap`), `String` (`split`, `replace`,\n  `match`, `padStart`, `padEnd`, `trim`, `trimStart`, `trimEnd`,\n  `startsWith`, `endsWith`, `includes`, `repeat`), `Object` (`keys`,\n  `values`, `entries`, `assign`, `freeze`, `fromEntries`),\n  `Reflect` (`get`, `set`, `has`, `deleteProperty`, `ownKeys`),\n  `WeakMap`, `WeakSet`, `Map`, `Set`, `Proxy`\n- **Microtask queue** — `Promise` reactions drained as microtasks\n- **DOM bindings** — `document.getElementById`,\n  `document.querySelector` \u002F `querySelectorAll`, `console.log`,\n  `alert`, `addEventListener` (basic)\n- **Event loop integration** — `setTimeout`, `setInterval`,\n  `requestAnimationFrame`, `fetch()` (returns `Promise\u003CResponse>`),\n  `XMLHttpRequest`, all driven by the event loop in\n  `web_runtime\u002Fevent_loop.rs`\n- **console.log(...)** — prints to stderr\n- **alert(msg)** — shows in the status bar\n\n### Networking (`net\u002F`)\n- HTTP\u002F1.1 fetch (via `ureq`)\n- HTTP\u002F2 parser (`http2.rs`)\n- Cookie jar (`cookies.rs`) with proper domain\u002Fpath matching\n- Redirect handling (`redirect.rs`) with redirect-loop detection\n- Cache (`cache.rs`) — HTTP cache with conditional requests\n- WebSocket (`websocket.rs`) — frame parser, masking, ping\u002Fpong\n\n### Web runtime (`web_runtime\u002F`)\n- `fetch()` (`fetch.rs`) — Promise-based, integrates with event loop\n- `XMLHttpRequest` (`xhr.rs`) — sync + async modes\n- Event loop (`event_loop.rs`) — task queues, microtasks, RAF\n- `Promise` (`promise.rs`) — state machine, then\u002Fcatch\u002Ffinally\n- WebGL (`webgl.rs`) — shader compilation, buffer management, draw\n  calls (headless)\n- Video (`video.rs`) — `\u003Cvideo>` element demux + decode stub\n- MSE (`mse.rs`) — Media Source Extensions\n- EME (`eme.rs`) — Encrypted Media Extensions\n- NDSD (`ndsd.rs`) — Native Device Service Discovery\n\n### Security (`security\u002F`) — implemented but not fully wired into renderer\n- **Origin \u002F SOP** (`origin.rs`) — Origin struct, `is_same_origin`,\n  `is_same_site`, `registrable_domain` (with 2-part TLD list),\n  `check_cors` (with credentials \u002F wildcard handling),\n  `check_navigation`\n- **Multi-process \u002F site isolation** (`process.rs`) — Process kinds\n  (Browser \u002F Renderer \u002F GPU \u002F Utility \u002F Plugin), site-to-process\n  map, ProcessPerSite \u002F ProcessPerTab policies, crash recovery with\n  max-restarts \u002F sad-tab \u002F fatal modes\n- **Sandbox** (`sandbox.rs`) — seccomp-bpf filter (Linux), renderer\n  allowlist (~30 syscalls), blocks `execve`\u002F`fork`\u002F`ptrace`\u002F`open`\u002F\n  `socket`\u002F`connect`\u002F`mount`, `PR_SET_NO_NEW_PRIVS`, `drop_capabilities`\n- **CSP** (`csp.rs`) — directive map, `default-src` fallback, source\n  expression matching (`'self'`, `'none'`, `'unsafe-inline'`,\n  `'unsafe-eval'`, `data:`, `blob:`, host, `*.wildcard`, `scheme:`),\n  nonce\u002Fhash support, `allows_inline_script`, `allows_eval`,\n  `allows_javascript_url`, `is_safe_attribute` (blocks `onclick`,\n  `onerror`, `javascript:` in href\u002Fsrc), violation reports\n- **TLS certificates** (`cert.rs`) — Certificate struct, validity,\n  hostname matching (with wildcards), TrustStore with Mozilla\n  defaults, `validate_chain` (chain building, signature check,\n  hostname, EKU, path length), OCSP stub, HPKP pinning,\n  Certificate Transparency\n- **Permissions** (`permissions.rs`) — 20 permission types\n  (Geolocation, Camera, Microphone, Notifications, ...), per-(origin,\n  permission) state, pluggable prompt handler, iframe allow parsing\n- **Extensions** (`extensions.rs`) — Manifest V3, content scripts,\n  match patterns (`\u003Call_urls>`, `*:\u002F\u002F*.host\u002F*`), glob matching,\n  permissions, generate extension ID, ChromeApi enum\n- **DevTools protocol** (`devtools.rs`) — JSON value type,\n  Request\u002FResponse\u002FEvent\u002FRpcError, Inspector\u002FPage\u002FRuntime\u002FDOM\u002FNetwork\u002F\n  Console methods, event subscribers, console message buffering\n\n### Images (`image\u002F`)\n- **HTTP\u002FHTTPS URLs** — fetched via `ureq`\n- **`data:` URLs** — base64-encoded inline images\n- **Local files** — relative paths resolved against the page URL\n- **Formats** — PNG, JPEG, GIF (first frame), BMP (via the `image` crate)\n- **Sizing** — `width` \u002F `height` HTML attributes take precedence,\n  CSS `width` \u002F `height` respected, default 300×200px, nearest-neighbor\n  scaling\n- **Broken images** — grey placeholder box with the `alt` text\n- **Caching** — global cache by URL\n\n## What it does NOT do (yet)\n\n- The `html::spec`, `dom::spec`, `css::spec`, `tjs_ext\u002F`, `security\u002F` modules are\n  **structurally complete but not yet wired into the render pipeline**.\n  Falco still uses the legacy `html` \u002F `dom` \u002F `css` modules for\n  actual rendering. The new modules exist as the spec-compliant\n  replacements and pass their own unit tests, but the renderer has\n  not been switched over yet.\n- No CSS animations \u002F transitions in the renderer (the data structures\n  exist in `css\u002Fspec\u002Fadvanced.rs`, but the paint loop does not interpolate\n  them).\n- `@media` queries **now work conditionally** as of v0.3.0 (rules are\n  included\u002Fexcluded based on viewport size).\n- No real DOM mutation from JS in the renderer (`element.innerHTML =\n  ...`, `element.style.color = ...` are stubs).\n- No HTML5 spec-compliant tree repair in the renderer (the algorithm\n  exists in `html\u002Fspec\u002Ftree_builder.rs` but the legacy parser is used).\n\n## Architecture\n\n| Module             | Lines  | Description                                                                |\n|--------------------|--------|----------------------------------------------------------------------------|\n| `html::spec`           | ~4,900 | WHATWG HTML5 tokenizer + tree builder + serializer + XML parser + encoding |\n| `html.rs`          | ~540   | Legacy HTML parser (still used in render pipeline)                         |\n| `dom::spec`            | ~2,280 | Spec-compliant DOM, MutationObserver, Shadow DOM, custom elements, a11y    |\n| `dom.rs`           | ~140   | Legacy DOM (still used in render pipeline)                                 |\n| `css::spec`            | ~2,020 | Selectors L4, cascade specificity, @-rules, animations, containment, filters |\n| `css\u002F`             | ~1,660 | Legacy CSS parser + selector matching + color parsing                      |\n| `style\u002F`           | ~1,720 | Style cascade + UA styles + inheritance + flex\u002Fgrid properties             |\n| `layout\u002F`          | ~1,950 | Block \u002F inline \u002F flex \u002F grid \u002F table \u002F float \u002F absolute layout             |\n| `paint\u002F`           | ~470   | Canvas + font rasterizer + alpha compositing + gradients + shadows         |\n| `svg\u002F`             | ~1,130 | SVG parser + renderer (paths, shapes, gradients)                           |\n| `tjs\u002F`             | ~4,340 | Custom JS VM: lexer, parser, interpreter, bytecode VM, JIT, value, builtins |\n| `tjs_ext\u002F`         | ~780   | Symbol, BigInt, Promise, microtasks, Map\u002FSet, WeakMap\u002FWeakSet, Reflect     |\n| `js_tjs.rs`        | ~700   | JS-to-DOM bindings (document, console, alert, onclick)                     |\n| `web_runtime\u002F`     | ~4,300 | fetch, XHR, event loop, Promise, WebGL, video, MSE, EME, NDSD, HTTP\u002F2      |\n| `net\u002F`             | ~930   | HTTP fetch, cookies, cache, websocket, redirect                            |\n| `security\u002F`        | ~3,590 | SOP, multi-process, sandbox, CSP, certs, permissions, extensions, DevTools |\n| `window\u002F`          | ~1,110 | Interactive window: scrolling, forms, navigation, history, address bar    |\n| `image\u002F`           | ~200   | Image loader (HTTP, data: URLs, local files) + cache + scaling             |\n| `png\u002F`             | ~100   | Hand-written PNG encoder (no flate2 dependency)                            |\n| `main.rs`, `lib.rs`| ~550   | CLI parsing + library entry points                                         |\n| **Total**          | **~36,000** |                                                                        |\n\n## Project layout\n\n```\nfalco\u002F\n├── .github\u002F                  # CI, issue templates, contributing, security policy\n│   ├── workflows\u002F\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\u002F       # 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\u002F                     # Logo\n│   └── falco.svg             # project logo (by Sanya)\n├── src\u002F\n│   ├── main.rs\n│   ├── lib.rs\n│   ├── html\u002Fspec\u002F                # WHATWG HTML5 (tokenizer, tree builder, ...)\n│   ├── html.rs               # legacy HTML parser\n│   ├── dom\u002Fspec\u002F                 # spec DOM (observer, shadow, custom elements, a11y)\n│   ├── dom.rs                # legacy DOM\n│   ├── css\u002Fspec\u002F                 # selectors L4, cascade, @-rules\n│   ├── css\u002F                  # legacy CSS parser\n│   ├── style\u002F                # cascade + inheritance + UA styles\n│   ├── layout\u002F               # block\u002Finline\u002Fflex\u002Fgrid\u002Ftable\u002Ffloat\u002Fabsolute\n│   ├── paint\u002F                # canvas + fonts + compositing\n│   ├── svg\u002F                  # SVG parser + renderer\n│   ├── tjs\u002F                  # custom JS VM (lexer, parser, VM, JIT)\n│   ├── tjs_ext\u002F              # Symbol, BigInt, Promise, ...\n│   ├── web_runtime\u002F          # fetch, XHR, event loop, WebGL, video, MSE, EME\n│   ├── net\u002F                  # HTTP, cookies, cache, websocket, redirect\n│   ├── security\u002F             # SOP, sandbox, CSP, certs, permissions, DevTools\n│   ├── window\u002F               # interactive window mode\n│   ├── image\u002F                # image loader\n│   └── png\u002F                  # PNG encoder\n├── Cargo.toml\n├── Cargo.lock\n├── LICENSE\n└── README.md\n```\n\n## Roadmap\n\nThe next big pieces of work, roughly in priority order:\n\n1. **Wire `html::spec` tokenizer + tree_builder into the render pipeline**\n   — replace the legacy `html::parse()` with the WHATWG-compliant parser.\n   This unlocks spec-compliant tree repair (adoption agency, foster\n   parenting), proper `\u003Ctemplate>` handling, and correct misnested-tag\n   recovery. The DOM mutation bridge (v0.2.0) already uses\n   `html::spec::serializer`, so this is the natural next step.\n2. **Wire `css::spec` into the cascade** — get `:is` \u002F `:where` \u002F `:has`\n   working in real rendering, plus cascade layers and container\n   queries.\n3. **Async `fetch()` with Promise integration** — currently `fetch()` is\n   blocking. Wire it through the `web_runtime\u002Fevent_loop.rs` so it\n   returns a real `Promise\u003CResponse>` and doesn't block the renderer.\n4. **CSS animations \u002F transitions** — interpolate keyframes in the\n   paint loop, run them through the event loop.\n5. **Wire `security\u002F` into the renderer** — SOP enforcement in DOM\n   access, CSP in the script runner, certificate validation on HTTPS\n   fetches, multi-process sandbox.\n6. **`@media` query value matching** — ✅ **done in v0.3.0**. The render\n   pipeline now passes the viewport size to the CSS parser, and\n   `@media (min-width: Npx)` \u002F `(max-width: Npx)` \u002F `(min-height: Npx)` \u002F\n   `(max-height: Npx)` rules are evaluated conditionally.\n\n## Changelog\n\n### v0.3.0\n\n**All 7 requested features implemented: spec HTML5 parser, modern CSS\nselectors, @media queries, async-style fetch with Promise, CSS animations\ndata structures, CSP enforcement, and Shadow DOM\u002FMutationObserver\u002FcustomElements.**\n\n- **Added**: The WHATWG-spec HTML5 parser (`html::spec::parse`) is now wired\n  into the render pipeline with fallback to legacy parser. New bridge:\n  `js_tjs::spec_document_to_legacy_dom()`.\n\n- **Added**: `@media` query conditional matching via\n  `css::parse_with_viewport()`. The render pipeline passes the actual\n  viewport from `RenderOptions`.\n\n- **Added**: Modern CSS pseudo-classes: `:is()`, `:where()`, `:has()`,\n  `:not()`, `:root`, `:empty`. The selector parser is now parenthesis-aware.\n\n- **Added**: `fetch()` now returns a Promise-like object with `.then()`,\n  `.catch()`, `.finally()` methods. The HTTP request is blocking, but the\n  Promise API surface is complete — `fetch(url).then(r => r.text()).then(t => ...)`\n  works. Also added `new Promise((resolve, reject) => {...})` constructor,\n  `Promise_resolve`, `Promise_reject`, `Promise_all` globals.\n\n- **Added**: `XMLHttpRequest` now makes real HTTP requests (synchronous).\n  `open(method, url)` stores the URL, `send(body)` makes the request and\n  returns the response body. `setRequestHeader` is accepted but no-op.\n\n- **Added**: `setTimeout(callback, delay)` and `setInterval(callback, delay)`\n  now actually execute the callback (synchronously, ignoring the delay).\n  `clearTimeout` \u002F `clearInterval` are no-ops.\n\n- **Added**: Shadow DOM API (`element.attachShadow`, `element.shadowRoot`),\n  `MutationObserver` constructor, `customElements` registry.\n\n- **Added**: CSP enforcement for inline scripts.\n\n- **Added**: 16 new regression tests. Total: 357 tests passing.\n\n- **Known limitations**:\n  - Spec parser has 3 known bugs (implicit body, foster parenting, adoption\n    agency) — fallback to legacy handles these.\n  - `fetch()` and `setTimeout` are synchronous (no real async\u002Fevent loop).\n    True async would require moving JS bridge from `Rc\u003CRefCell\u003C>>` to\n    `Arc\u003CMutex\u003C>>`.\n  - CSS animations\u002Ftransitions: data structures exist but paint loop doesn't\n    interpolate them (needs time-based event loop).\n  - MutationObserver\u002FcustomElements callbacks not auto-invoked.\n  - `:has()` only checks direct children.\n  - CSP only checks inline scripts.\n\n### v0.2.0\n\n**Major release: the spec-compliant DOM is now wired into the render\npipeline. JavaScript can mutate the DOM and the changes are reflected\nin the rendered output.**\n\n- **Added**: DOM mutation from JavaScript now triggers re-render. The\n  render pipeline now:\n  1. Parses HTML into a legacy DOM (for layout compatibility).\n  2. Converts the legacy DOM to a spec DOM via the new\n     `js_tjs::legacy_dom_to_spec_document()` bridge.\n  3. Creates a `TjsJsContext` with the spec DOM attached.\n  4. Executes inline `\u003Cscript>` tags — `element.innerHTML = ...`,\n     `element.style.color = ...`, `document.getElementById(...).remove()`\n     all mutate the live spec DOM.\n  5. Serializes the (possibly mutated) spec DOM back to HTML via\n     `js_tjs::serialize_spec_document()`.\n  6. Re-parses the mutated HTML and runs layout + paint as before.\n\n  This unlocks dynamic pages: SPAs, React\u002FVue-style rendering, any\n  site that uses `innerHTML` or `appendChild` to build content.\n\n- **Added**: real `fetch()` in the JS bridge. Previously `fetch()` was\n  a stub. Now it uses `ureq` to make a blocking HTTP request and\n  returns a response object with `ok`, `status`, `text()`, and `json()`\n  methods. This unlocks AJAX-style sites that load content dynamically.\n\n- **Added**: 6 new `String.prototype` methods on the JS VM:\n  `repeat`, `padStart`, `padEnd`, `trimStart`, `trimEnd`. Combined\n  with the existing String methods, this brings Falco's JS String\n  support close to the ES2015+ spec.\n\n- **Added**: 8 new regression tests for String methods. Total: 341\n  tests passing.\n\n- **Changed**: bumped version to 0.2.0 (minor bump — new features,\n  no breaking changes to public API).\n\n- **Known limitations**:\n  - The spec HTML5 parser (`html::spec::tokenizer` + `tree_builder`)\n    is still not wired in — the legacy `html::parse()` is used for\n    the initial parse. The spec parser is structurally complete and\n    unit-tested but requires the legacy DOM to be replaced entirely.\n    Planned for v0.3.0.\n  - `fetch()` is blocking (no Promise, no async). The event loop in\n    `web_runtime\u002Fevent_loop.rs` is real and unit-tested, but the JS\n    bridge doesn't yet integrate with it. Planned for v0.3.0.\n  - The security module (SOP, CSP, sandbox, cert validation) is still\n    not enforced in the renderer.\n\n### v0.1.1\n\n- **Added**: `render_to_buffer()` public API returning raw RGBA pixels\n  (no PNG encoding), for embedding Falco into game engines and GUI\n  toolkits. The new `RenderedBuffer` struct exposes `as_rgba()`,\n  `to_bgra()` (for DirectX\u002FVulkan\u002FWin32), and `to_rgb()`.\n- **Fixed**: whitespace collapsed in rendered text. Spaces between\n  words had zero width because outline-less glyphs returned `None`,\n  so the paint loop didn't advance the caret. Now returns a transparent\n  glyph with the font's real horizontal advance. (thanks @d0sch1, PR #6)\n- **Added**: 14 real `Array.prototype` methods on the JS VM\n  (`map`, `filter`, `reduce`, `forEach`, `find`, `some`, `every`,\n  `slice`, `concat`, `includes`, `indexOf`, `reverse`, `push`, `pop`,\n  `join`). Previously they were stubs or missing entirely. Callback\n  methods invoke user functions via a new `call_js` helper in\n  `tjs\u002Finterpreter.rs`. (thanks @d0sch1, PR #6)\n- **Added**: 10 new regression tests for Array methods and closure\n  capture in `tjs\u002Fmod.rs`. Total: 333 tests passing.\n- **Added**: Benchmarks section to README.\n- **Added**: Programmatic API section with `render_to_buffer` example.\n\n### v0.1.0\n\n- Initial public release.\n- HTML\u002FCSS\u002Flayout\u002Fpaint pipeline working end-to-end.\n- Interactive `--window` mode with scrolling, forms, navigation.\n- Spec-compliant `html::spec\u002F`, `dom::spec\u002F`, `css::spec\u002F` modules\n  structurally complete but not yet wired into the render pipeline.\n- Security module (SOP, CSP, sandbox, certs, permissions) implemented\n  but not enforced in the renderer.\n\n## Support the project\n\nIf Falco is useful to you, consider buying the author a coffee:\n\n\u003Cp align=\"center\">\n  \u003Ca href=\"https:\u002F\u002Fsend.monobank.ua\u002Fjar\u002F21T9ZRwZq3\">\n    \u003Cimg alt=\"MonoBank donation\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FSupport%20on-MonoBank-7c3aed.svg?style=for-the-badge&logo=monobank&logoColor=white\">\n  \u003C\u002Fa>\n\u003C\u002Fp>\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","Falco 是一个从零编写的轻量级浏览器引擎，专为嵌入式与实验性场景设计。它用 Rust 实现了完整的网页渲染流水线：支持 HTML 解析、CSS 级联与布局（Flex\u002FGrid\u002FFloat）、自研 JavaScript 虚拟机（含 Promise、BigInt 等基础特性）、SVG 与图像加载，并可输出 PNG 或启动交互式窗口（支持点击、表单输入与导航）。二进制体积约 10MB，代码约 3.6 万行，不依赖 WebKit\u002FGecko\u002FChromium。适用于网页快照生成、UI 原型验证、教学演示及资源受限环境下的轻量渲染需求。","2026-08-05 02:30:08","CREATED_QUERY"]