全 Zig 手写、非 Chromium 分支的无头浏览器。
用 html5ever 解析 HTML,V8 跑 JS,libcurl 跑网络,
暴露 CDP + MCP 双协议。
这份文档基于 commit e6cffae 的实际源码,带文件:行号引用。
11× 是真的——代价是完全砍掉了 CSS 布局、像素渲染、Canvas 绘制。 Lightpanda 只保留 DOM 正确性、JS 兼容性、网络保真度、语义提取四件事。 用它做批量抓取、SSR 测试、AI Agent 驱动非常合适; 用它做需要截图、视觉校验、重 SPA的场景会翻车。 AGPL-3.0 license 对商用 SaaS 也要留意。
一个 main,三种模式:CDP 服务器、单次 fetch、MCP stdio。所有模式共用同一套 Browser 实例。
Lightpanda/1.0 Config.zig:325-339Chrome DevTools Protocol 的 Zig 实现。兼容 Puppeteer/Playwright,但只实现了一个子集。路由用 bit-cast 加速。
@bitCast(domain[0..N].*) 把字符串整体转成整数,再在整数上 switch,
省掉字符串比较,O(1) 路由。
// 伪代码示意(实际在 CDP.zig:200-262)
switch (domain.len) {
2 => switch (@bitCast(u16, domain[0..2].*)) { "LP" => ... },
3 => switch (@bitCast(u24, domain[0..3].*)) { "DOM", "Log", "CSS" => ... },
4 => ..., 5 => ..., 6 => ..., 7 => ...,
}
目标发现/附加
cdp/domains/target.zig导航、脚本求值、生命周期
cdp/domains/page.zig树查询、quads、节点搜索
cdp/domains/dom.zigJS 求值、属性读取、调用栈
cdp/domains/runtime.zig网络事件、请求/响应捕获
cdp/domains/network.zig请求拦截、响应改写
cdp/domains/fetch.zig鼠标、键盘、触摸
cdp/domains/input.zigConsole 日志
cdp/domains/log.zigCookie、LocalStorage
cdp/domains/storage.zig规则读取(部分)
cdp/domains/css.zig版本、窗口边界(多为桩)
cdp/domains/browser.zig状态(桩)
cdp/domains/inspector.zig安全信息
cdp/domains/security.zig设备模拟(桩——无布局)
cdp/domains/emulation.zigAXNode 可访问性树
cdp/domains/accessibility.zig性能指标
cdp/domains/performance.zigMarkdown dump 等
cdp/domains/lp.zigBrowser / Session / Page 三层结构,加上 Runner 事件循环。
Page.navigate(url)(CDP 或直接 API)Runner.wait() 驱动事件循环直到满足等待条件load / DOMContentLoaded / networkidlePage.navigatedSession 持有两个 Arena:
Session.zig:116-117 代价:无跨页面复用,但换来干净快速的 teardown
直接用 Mozilla 的 html5ever(Rust 写的 HTML5 spec 实现),通过 C FFI 被 Zig 调用。
cargo build 编译 src/html5ever/Cargo.tomlliblitefetch_html5ever.amod.addObjectFile(obj) 静态链入html5ever_parse_document()html5ever_parse_document_with_encoding() — charset awarehtml5ever_parse_fragment() — innerHTMLhtml5ever_streaming_parser_create/feed/finish()// browser/parser/html5ever.zig:21-40
extern fn createElementCallback(ctx: *Parser, tag: *const u8, attrs: *const Attr, n: usize) *Node;
extern fn appendCallback(ctx: *Parser, parent: *Node, child: *Node) void;
extern fn popCallback(ctx: *Parser, node: *Node) void;
extern fn createCommentCallback(ctx: *Parser, text: *const u8, len: usize) *Node;
extern fn createProcessingInstruction(ctx: *Parser, target: [], data: []) *Node;
extern fn appendDoctypeToDocument(ctx: *Parser, name: [], publicId: [], systemId: []) void;
extern fn getTemplateContentsCallback(ctx: *Parser, node: *Node) *Node;
extern fn reparentChildrenCallback(ctx: *Parser, old: *Node, new: *Node) void;
extern fn addAttrsIfMissingCallback(ctx: *Parser, node: *Node, attrs: []Attr) void;
每个回调在 Zig 端实现为 callconv(.c) fn (ctx: *Parser, ...)
Parser.zig:42-176。
ParsedNode 包装 Node* 加可选 element 数据。
错误通过 Parser.err union 和源码位置归属。
用 V8 跑 JS,通过代码生成为每个 WebAPI 类型自动产出绑定。Zig 对象和 V8 对象通过 internal field 指针互映。
--prebuilt-v8-path 优先,否则从 zig-v8 包从源码编译(~1 小时)browser/js/bridge.zig 是代码生成式绑定,对每个 WebAPI 类型自动产出:
new Element(...))JS 对象在 V8 internal field 存 Zig 指针,identity 稳定:同一个 Zig 对象永远映射到同一个 V8 对象。
| Zig 类型 | V8 类型 |
|---|---|
bool | Boolean |
i32 / u32 / f64 | Number |
[]const u8 | String |
*MyType | Object with internal field = 指针 |
?T | null / Object |
error!T | JS 异常 / 值 |
src/browser/webapi/ 下 217 个 .zig 文件。完整、半实现、缺失三档。
webapi/canvas/OffscreenCanvas.zig:74-77
convertToBlob() 返回空 Blob
webapi/canvas/OffscreenCanvas.zig:80
transferToImageBitmap() 返回 null
libcurl + BoringSSL + nghttp2 + brotli + zlib。关掉所有非 HTTP 协议。
--http-max-concurrent, --http-max-host-open--http-max-response-size--block-private-networks 屏蔽 RFC 1918 + IPv6 ULA,在 DNS 解析后执行--block-cidrs--obey-robotsFsCache.zig--http-cache-dirBrowserContext.intercept_stateModel Context Protocol 服务器,面向 Claude / Cursor / Cline 这类 AI Agent 工具。大部分浏览器只暴露 CDP 或 WebDriver,Lightpanda 把 MCP 当一等公民。
// 启动流
$ lightpanda mcp [--cdp-port 9223]
↓
main() 起 mcpThread() // main.zig:179-194
↓
mcp.Server.init(browser, session, http) // mcp/Server.zig:27-54
↓
mcp.router.processRequests() // 读 stdin / 写 stdout
↓
JSON-RPC 2.0 请求 → 路由 → handler → 响应
initialize — 握手,返回协议版本ping — 心跳resources/list — 枚举资源resources/read — 读资源mcp://page/html — 完整序列化 DOMmcp://page/markdown — token-efficient Markdown| 类别 | 工具 |
|---|---|
| 导航 | goto(url, timeout, waitUntil), navigate() |
| 提取 | markdown(url), links(url), semantic_tree(url, maxDepth), interactiveElements(url), structuredData(url), detectForms(url) |
| 交互 | click(backendNodeId), fill(backendNodeId, text), hover, press(key), scroll(x, y) |
| 检查 | nodeDetails(backendNodeId) — tag/role/name/interactivity/value/href/checked/options |
| JS | evaluate(script, url, timeout, waitUntil), eval() |
| 等待 | waitForSelector(selector, timeout) |
mcp/tools.zig:48-300+
click(5) / fill(3, "hello") 这类语义化 tool call,
不需要学协议细节。markdown(url) 还把 DOM 降维成 LLM 友好的 token 密集型文本。
build.zig 34 KB,非平凡。把 V8 / Rust crate / libcurl + 依赖链全串起来。
lightpanda — 主程序lightpanda-snapshot-creator — V8 snapshot 生成legacy_test — 集成测试 runner.a → addObjectFilezig build # 编译
zig build test # 测试
zig build fmt # 格式化
zig build -Doptimize=ReleaseSafe
从代码里读出来的真实状况,不看营销。TODOs、桩方法、unreachable 都是一手线索。
Canvas 桩、CSS 解析但不布局、无盒模型、无计算样式。
代价:截图、视觉校验、依赖元素位置的反爬全部失效。
收益:11× 速度来源。
IntersectionObserver 永远"可见",媒体查询被忽略,@font-face 注册但不加载。
影响:懒加载一次全展开(爬虫友好),但 A/B 脚本可能出错。
cdp/CDP.zig:270-281 BrowserContext 唯一、Session 唯一、Page 唯一活动。
影响:没有真正的多标签并发,需要并发时必须多进程。
cdp/CDP.zig:384 捕获的响应 body 不流到磁盘。
影响:抓巨型文件会内存爆炸,典型页面无忧。
| 位置 | TODO / 桩 |
|---|---|
| cdp/domains/page.zig | 缺 transitionType、referrerPolicy 枚举 |
| cdp/domains/dom.zig | quads 即使元素应隐藏也照填 |
| cdp/domains/network.zig | 子 frame 没进 Network.getCertificateDetails |
| cdp/domains/fetch.zig | 跨页面请求回复可能跨 context 泄漏 |
| cdp/domains/emulation.zig | Device emulation 是 no-op(本来就没布局) |
| cdp/domains/browser.zig | 窗口尺寸硬编码 |
| cdp/AXNode.zig | Accessibility tree 在 label_element / label_wrap 有 TODO |
| webapi/selector/Parser.zig | 复杂选择器 :has() 等可能桩 |
| 场景 | Lightpanda | 说明 |
|---|---|---|
| 批量抓静态/半动态页面 | ✓ 强烈推荐 | 比 Chrome 省 9× 内存 |
| SSR 测试 | ✓ 合适 | DOM 正确性为一等公民 |
| 给 AI Agent 当"浏览器臂" | ✓ 原生支持 | MCP 一等公民,20+ 语义工具 |
| 跑 Playwright/Puppeteer 脚本 | △ 大部分能跑 | 兼容 CDP,但不支持需要截图/布局的 API |
| 需要截图或像素校验 | ✗ 不行 | 没有渲染管线 |
| 重 SPA(依赖可见性懒加载) | △ 语义偏差 | 所有元素"可见",懒加载一次全触发 |
| WebRTC / WebGL / ServiceWorker | ✗ 不行 | 全部未实现 |
| 多标签并发 | ✗ 不行 | 单 BrowserContext 约束 |
| 公司 SaaS 后端(AGPL) | △ 注意 | 自托管可能触发源码披露义务 |