From e890eb2633f87c488533e93c3d29351cc6d417eb Mon Sep 17 00:00:00 2001 From: kang Date: Mon, 13 Apr 2026 11:30:40 +0800 Subject: [PATCH] =?UTF-8?q?init:=20Lightpanda=20=E6=BA=90=E7=A0=81?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E7=AB=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 基于 lightpanda-io/browser@e6cffae 的 12 段深度解析, 单文件 HTML + sticky nav + 架构图 + 代码引用。 --- Dockerfile | 3 + index.html | 896 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 899 insertions(+) create mode 100644 Dockerfile create mode 100644 index.html diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5ebe438 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,3 @@ +FROM nginx:alpine +COPY index.html /usr/share/nginx/html/index.html +EXPOSE 80 diff --git a/index.html b/index.html new file mode 100644 index 0000000..c95bb6f --- /dev/null +++ b/index.html @@ -0,0 +1,896 @@ + + + + + +Lightpanda 源码深度解析 · Zig 无头浏览器 + + + + +
+
Lightpanda 解析
+ + +
+ + +
+
+
基于 commit e6cffae · 2026-04-13
+

Lightpanda
从源码读懂这只"快 11 倍"的无头熊猫

+

+ 全 Zig 手写、非 Chromium 分支的无头浏览器。 + 用 html5ever 解析 HTML,V8 跑 JS,libcurl 跑网络, + 暴露 CDP + MCP 双协议。
+ 这份文档基于 commit e6cffae 的实际源码,带文件:行号引用。 +

+
+
32
CDP 域
+
217
Web API 文件
+
20+
MCP 工具
+
11×
相对 Chrome
+
+
+ ZigRust (html5ever)C (libcurl/BoringSSL) + V8AGPL-3.0~9 MB 源码 +
+
+
+ + +
+
+
🎯
+
+

不是噱头,但有明确边界

+

+ 11× 是真的——代价是完全砍掉了 CSS 布局、像素渲染、Canvas 绘制。 + Lightpanda 只保留 DOM 正确性、JS 兼容性、网络保真度、语义提取四件事。 + 用它做批量抓取、SSR 测试、AI Agent 驱动非常合适; + 用它做需要截图、视觉校验、重 SPA的场景会翻车。 + AGPL-3.0 license 对商用 SaaS 也要留意。 +

+
+
+
+ + +
+
+
§ 01 / ENTRY POINT
+

入口与进程模型

+

一个 main,三种模式:CDP 服务器、单次 fetch、MCP stdio。所有模式共用同一套 Browser 实例。

+
+ +
+
+
main.zig
+
解析 CLI → 装 allocator → 绑 SIGTERM/SIGINT → 分派模式
+
main.zig:34-170
+
+
+
serve mode
+
bind 127.0.0.1:9222 → Server.init() → 事件循环
+
main.zig:92-118
+
+
+
fetch mode
+
worker 线程跑 lp.fetch(),支持 --wait-until / --dump / --wait-selector
+
main.zig:119-145
+
+
+
mcp mode
+
读 stdin 写 stdout,mcp.router.processRequests()
+
main.zig:147-167
+
+
+ +
+
+

内存策略

+
    +
  • Debug 构建用 GeneralPurposeAllocator 查泄漏 main.zig:34-44
  • +
  • Release 构建用 C allocator,主线程外包 Arena
  • +
  • 分 tiny/small/large 三档 Arena 池 App.zig:40
  • +
+
+
+

默认参数

+
    +
  • Bind: 127.0.0.1:9222(默认只绑环回)
  • +
  • Inactivity timeout: 10s(1~604800)
  • +
  • CDP 消息上限: 512 KB Config.zig:42
  • +
  • HTTP 超时: 5000 ms Config.zig:115
  • +
  • UA 基线: Lightpanda/1.0 Config.zig:325-339
  • +
+
+
+
+ + +
+
+
§ 02 / CDP SERVER
+

CDP 协议层

+

Chrome DevTools Protocol 的 Zig 实现。兼容 Puppeteer/Playwright,但只实现了一个子集。路由用 bit-cast 加速。

+
+ +
+ 巧思cdp/CDP.zig:200-262 的分派器按域名字符数分组, + 对每一组用 @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 域

+
+

Target

目标发现/附加

cdp/domains/target.zig
+

Page

导航、脚本求值、生命周期

cdp/domains/page.zig
+

DOM

树查询、quads、节点搜索

cdp/domains/dom.zig
+

Runtime

JS 求值、属性读取、调用栈

cdp/domains/runtime.zig
+

Network

网络事件、请求/响应捕获

cdp/domains/network.zig
+

Fetch

请求拦截、响应改写

cdp/domains/fetch.zig
+

Input

鼠标、键盘、触摸

cdp/domains/input.zig
+

Log

Console 日志

cdp/domains/log.zig
+

Storage

Cookie、LocalStorage

cdp/domains/storage.zig
+

CSS

规则读取(部分)

cdp/domains/css.zig
+

Browser

版本、窗口边界(多为桩)

cdp/domains/browser.zig
+

Inspector

状态(桩)

cdp/domains/inspector.zig
+

Security

安全信息

cdp/domains/security.zig
+

Emulation

设备模拟(桩——无布局)

cdp/domains/emulation.zig
+

Accessibility

AXNode 可访问性树

cdp/domains/accessibility.zig
+

Performance

性能指标

cdp/domains/performance.zig
+

LP (自扩)

Markdown dump 等

cdp/domains/lp.zig
+
+ +
+ 关键约束cdp/CDP.zig:270-281 强制只允许一个 BrowserContext 同时存在—— + Lightpanda 任何时刻只有一个活动上下文、一个 Session、一个活动 Page。 + 不支持多页面并发。这是为了简化状态管理,但比 Chrome 的标签架构弱。 +
+
+ + +
+
+
§ 03 / BROWSER CORE
+

浏览器核心三件套

+

Browser / Session / Page 三层结构,加上 Runner 事件循环。

+
+ +
+
+
Browser
+
持有 V8 环境,创建 Session
+
Browser.zig:43-119
+
+
+
  └─ Session
+
cookie jar / history / origins / Page 生命周期
+
Session.zig:109-187
+
+
+
      └─ Page
+
DOM 树 + frame 层级(iframes)
+
Page.zig:200-600+
+
+
+
Runner
+
事件循环: tick HttpClient → runMacrotasks → runMicrotasks
+
Runner.zig:57-150
+
+
+ +
+
+

导航流程

+
    +
  • Client 调 Page.navigate(url)(CDP 或直接 API)
  • +
  • Page 入队导航
  • +
  • Runner.wait() 驱动事件循环直到满足等待条件
  • +
  • HTTP 进度 → Parser → JS 任务队列 → 微任务
  • +
  • Page 发出 load / DOMContentLoaded / networkidle
  • +
  • 通知 CDP 的 Page.navigated
  • +
+
+
+

Arena 策略

+

Session 持有两个 Arena:

+
    +
  • page_arena — 每次导航重建,销毁快
  • +
  • arena — Session 生命周期
  • +
+

+ Session.zig:116-117 + 代价:无跨页面复用,但换来干净快速的 teardown +

+
+
+
+ + +
+
+
§ 04 / HTML PARSING
+

HTML 解析 · Zig ↔ Rust FFI

+

直接用 Mozilla 的 html5ever(Rust 写的 HTML5 spec 实现),通过 C FFI 被 Zig 调用。

+
+ +
+ 为什么不自己写? html5ever 是 Mozilla Firefox 的产线级 HTML parser, + 实现了 adoption agency、template 内容模型、script insertion point 等所有奇葩边缘情况。 + 从零复刻一个正确的 HTML parser 是多人年级别的工程,Lightpanda 直接用就对了。 +
+ +
+
+

构建集成

+
    +
  • build.zig:235-265cargo build 编译 src/html5ever/Cargo.toml
  • +
  • 产出 liblitefetch_html5ever.a
  • +
  • Zig 侧 mod.addObjectFile(obj) 静态链入
  • +
  • Cargo.toml + lib.rs 作为 input 追踪,变化自动重编
  • +
+
+
+

C API(Zig 侧声明)

+
    +
  • html5ever_parse_document()
  • +
  • html5ever_parse_document_with_encoding() — charset aware
  • +
  • html5ever_parse_fragment() — innerHTML
  • +
  • html5ever_streaming_parser_create/feed/finish()
  • +
  • browser/parser/html5ever.zig:21-90
  • +
+
+
+ +

Rust → Zig 回调列表

+
// 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 和源码位置归属。 +

+ +
+ DOM 节点表示browser/webapi/Node.zig):tagged union 包含 + Document、DocumentFragment、Element、Text、Comment、PI、DocumentType、CDATA。 + Element 带 qualified name、懒创建的 Attribute 节点、双向链表子节点、cached namespace URI。 +
+
+ + +
+
+
§ 05 / JS RUNTIME
+

JS 运行时 · V8 绑定

+

用 V8 跑 JS,通过代码生成为每个 WebAPI 类型自动产出绑定。Zig 对象和 V8 对象通过 internal field 指针互映。

+
+ +
+
+

V8 集成

+
    +
  • build.zig:213-233 --prebuilt-v8-path 优先,否则从 zig-v8 包从源码编译(~1 小时)
  • +
  • browser/js/js.zig:19-72 导出 V8 C API、TypedArray wrapper
  • +
  • browser/js/Platform.zig 平台初始化,快照加载
  • +
  • browser/js/Env.zig 每页多 Context(支持同源隔离),Global = Window
  • +
+
+
+

绑定桥

+

browser/js/bridge.zig 是代码生成式绑定,对每个 WebAPI 类型自动产出:

+
    +
  • Constructor(new Element(...)
  • +
  • Property accessor(getter/setter)
  • +
  • Method
  • +
  • Callback dispatch
  • +
+

JS 对象在 V8 internal field 存 Zig 指针,identity 稳定:同一个 Zig 对象永远映射到同一个 V8 对象。

+
+
+ +

类型转换规则(Value.zig)

+ + + + + + + + + + +
Zig 类型V8 类型
boolBoolean
i32 / u32 / f64Number
[]const u8String
*MyTypeObject with internal field = 指针
?Tnull / Object
error!TJS 异常 / 值
+ +
+ 调用回传browser/js/Caller.zig):JS 调 Zig 方法 → 从 V8 internal field 取 Zig 指针 → + 从 V8 参数数组取参数并转 Zig 类型 → 调 Zig 函数 → 返回值转 V8 value。 + Zig error 直接 throw 成 JS 异常。 +
+
+ + +
+
+
§ 06 / WEB API
+

Web API 实现矩阵

+

src/browser/webapi/217 个 .zig 文件。完整、半实现、缺失三档。

+
+ +
+
+

✓ 完整实现

+
+ Node/Element + Document + querySelector(All) + classList + dataset + EventTarget + MouseEvent + KeyboardEvent + LocalStorage + SessionStorage + Cookie jar + URL / URLSearchParams + fetch() + XMLHttpRequest + WebSocket + SubtleCrypto + FileReader + Performance + setTimeout + MutationObserver + Navigation/History + CustomElementRegistry + ShadowRoot + Selection/Range +
+
+
+

△ 桩实现

+
+ OffscreenCanvas (blob=空) + Canvas2D (方法空) + CSSStyleSheet (解析但不级联) + IntersectionObserver (永远可见) + AXNode (部分) + Emulation (no-op) +
+

+ webapi/canvas/OffscreenCanvas.zig:74-77 + convertToBlob() 返回空 Blob +

+

+ webapi/canvas/OffscreenCanvas.zig:80 + transferToImageBitmap() 返回 null +

+
+
+

✗ 完全缺失

+
+ CSS 布局引擎 + 盒模型 / 级联 / 继承 + Grid / Flex + 像素渲染 + WebGL + getUserMedia + WebRTC + Web Audio + IndexedDB + ServiceWorker + SVG 渲染 + CSS Animations 执行 + @media print + getComputedStyle (真实值) +
+
+
+ +
+ 语义警告webapi/IntersectionObserver.zig + 因为没有布局引擎,所有元素一律视为完全可见。 + 任何依赖"滚动到视口才触发加载"的脚本(懒加载、无限列表)都会一次性全触发—— + 这通常对爬虫来说是好事,因为页面一上来就全展开了,但对 A/B 测试脚本是错误行为。 +
+
+ + +
+
+
§ 07 / NETWORK
+

网络栈 · libcurl 中心化

+

libcurl + BoringSSL + nghttp2 + brotli + zlib。关掉所有非 HTTP 协议。

+
+ +
+
libcurl
HTTP/HTTPS/WebSocket(关掉 FTP/IMAP/LDAP)
build.zig:439-600+
+
TLS
BoringSSL (Google OpenSSL fork)
build.zig:296-437
+
HTTP/2
nghttp2
build.zig:296-437
+
压缩
brotli + zlib + zstd
build.zig:296-437
+
事件循环
CurlM multi handle + epoll/kqueue + wakeup pipe
network/Network.zig:52-150
+
+ +
+
+

HTTP 客户端

+
    +
  • browser/HttpClient.zig
  • +
  • 持久连接池(configurable)
  • +
  • 并发限流: --http-max-concurrent, --http-max-host-open
  • +
  • 超时: 连接 + 传输分开
  • +
  • 响应大小上限: --http-max-response-size
  • +
  • 最多 10 次重定向
  • +
+
+
+

安全相关

+
    +
  • SSRF 防护: network/IpFilter.zig + --block-private-networks 屏蔽 RFC 1918 + IPv6 ULA,在 DNS 解析后执行
  • +
  • 自定义 CIDR: --block-cidrs
  • +
  • Robots.txt: network/Robots.zig --obey-robots
  • +
  • WebBot Auth: network/WebBotAuth.zig Ed25519 签名
  • +
+
+
+

缓存

+
    +
  • network/cache/Cache.zig + FsCache.zig
  • +
  • 可选文件系统缓存 --http-cache-dir
  • +
  • 尊重 HTTP 缓存头(Cache-Control / ETag / Last-Modified)
  • +
  • 按 URL + request headers 为 key
  • +
+
+
+

请求拦截

+
    +
  • cdp/domains/fetch.zig
  • +
  • CDP Fetch 域可拦截/改写任何请求
  • +
  • Pending transfer 挂在 BrowserContext.intercept_state
  • +
  • 客户端可 abort / allow / mock response
  • +
+
+
+
+ + +
+
+
§ 08 / MCP
+

MCP 集成(独特卖点)

+

Model 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 — 读资源
  • +
+
+
+

资源(2 种)

+
    +
  • mcp://page/html — 完整序列化 DOM
  • +
  • mcp://page/markdown — token-efficient Markdown
  • +
  • mcp/resources.zig:9-22
  • +
+
+
+ +

给 AI Agent 的工具(20+)

+ + + + + + + + + + +
类别工具
导航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
JSevaluate(script, url, timeout, waitUntil), eval()
等待waitForSelector(selector, timeout)
+

mcp/tools.zig:48-300+

+ +
+ 为什么有价值? 用 Puppeteer 驱动 Chrome 时,AI 要学 CDP 协议 + 自己管 nodeId。 + 用 Lightpanda MCP,Claude 直接用 click(5) / fill(3, "hello") 这类语义化 tool call, + 不需要学协议细节markdown(url) 还把 DOM 降维成 LLM 友好的 token 密集型文本。 +
+
+ + +
+
+
§ 09 / BUILD
+

构建系统

+

build.zig 34 KB,非平凡。把 V8 / Rust crate / libcurl + 依赖链全串起来。

+
+ +
+
+

外部依赖(build.zig.zon)

+
    +
  • v8 — 预编译或源码(源码 ~1h)
  • +
  • curl — 加 boringssl + nghttp2 + brotli + zlib + zstd
  • +
  • html5ever — Rust crate,仓内自带
  • +
+
+
+

产物

+
    +
  • lightpanda — 主程序
  • +
  • lightpanda-snapshot-creator — V8 snapshot 生成
  • +
  • legacy_test — 集成测试 runner
  • +
+
+
+

主要步骤

+
    +
  • build.zig:213-233 V8 链接 + ASAN/TSAN 选项
  • +
  • build.zig:235-265 跑 cargo build 编译 html5ever → .aaddObjectFile
  • +
  • build.zig:439-600+ 编译 libcurl(HTTP/2 + WebSocket + HTTPS + IPv6,关掉 FTP/IMAP/LDAP)
  • +
  • build.zig:296-437 编译 zlib/brotli/nghttp2/BoringSSL
  • +
+
+
+

常用命令

+
zig build             # 编译
+zig build test        # 测试
+zig build fmt         # 格式化
+zig build -Doptimize=ReleaseSafe
+
+
+
+ + +
+
+
§ 10 / LIMITS & TRADE-OFFS
+

已知限制与架构权衡

+

从代码里读出来的真实状况,不看营销。TODOs、桩方法、unreachable 都是一手线索。

+
+ +

有意为之的设计取舍

+
+
+

零渲染

+

Canvas 桩、CSS 解析但不布局、无盒模型、无计算样式。
+ 代价:截图、视觉校验、依赖元素位置的反爬全部失效。
+ 收益:11× 速度来源。

+
+
+

无布局引擎

+

IntersectionObserver 永远"可见",媒体查询被忽略,@font-face 注册但不加载。
+ 影响:懒加载一次全展开(爬虫友好),但 A/B 脚本可能出错。

+
+
+

单页上下文

+

cdp/CDP.zig:270-281 BrowserContext 唯一、Session 唯一、Page 唯一活动。
+ 影响:没有真正的多标签并发,需要并发时必须多进程。

+
+
+

Fetch body 常驻内存

+

cdp/CDP.zig:384 捕获的响应 body 不流到磁盘。
+ 影响:抓巨型文件会内存爆炸,典型页面无忧。

+
+
+ +

代码里的 TODO 线索

+ + + + + + + + + + + + +
位置TODO / 桩
cdp/domains/page.zigtransitionTypereferrerPolicy 枚举
cdp/domains/dom.zigquads 即使元素应隐藏也照填
cdp/domains/network.zig子 frame 没进 Network.getCertificateDetails
cdp/domains/fetch.zig跨页面请求回复可能跨 context 泄漏
cdp/domains/emulation.zigDevice emulation 是 no-op(本来就没布局)
cdp/domains/browser.zig窗口尺寸硬编码
cdp/AXNode.zigAccessibility 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)△ 注意自托管可能触发源码披露义务
+
+ + + + +