Moli核心原理揭秘:FrozenLayoutTree如何让无头浏览器算一次布局永久复用
【免费下载链接】moliBest headless browser for AI agents. Lite, Fast, High-Compatibility. Built in Rust项目地址: https://gitcode.com/gh_mirrors/moli/moli
Moli 是一款用 Rust 编写的开源无头浏览器,专为 AI 智能体(AI Agent)设计,主打轻量、快速和高兼容。它的布局引擎不走"持续维护布局树"的老路,而是把每次布局结果冻结成一份不可变的几何快照FrozenLayoutTree——算一次、读多次,页面变了也照用,截图时才重建。本文带你拆解这套"布局快照复用"机制的核心原理。
为什么无头浏览器要重新设计布局?
AI 智能体驱动浏览器时的高频操作是:读取元素坐标、模拟点击、判断滚动位置、截图校验。而传统浏览器是为"人眼实时渲染"优化的:内部长期驻留布局树、增量失效标记、显示列表和合成器——这些状态在无头场景下几乎全是负担。
Moli 的思路可以概括为官方 Slogan:"Structure first. Pixels on demand."(结构优先,像素按需):布局不是常驻状态,而是一次次按需生成的快照。
布局策略:默认 Mock,按需开启真实布局
Moli 把昂贵的浏览器操作全部设为显式开启,布局也不例外:
| 模式 | 行为 |
|---|---|
| 默认 | LayoutPolicy::Mock:生成确定性的兼容几何数据,不执行真实布局与绘制 |
--layout | LayoutPolicy::OnDemand:真实布局、几何查询、命中测试、坐标输入、截图、录屏 |
只有真正需要几何时,Moli 才会从当前 DOM 和 Stylo 计算样式出发构建一棵临时工作树:Servo/Stylo 负责样式,Taffy 负责盒模型布局,Parley 负责文本排版。布局完成后,Moli 立刻把规范的盒/片段几何"冻结"进FrozenLayoutTree,然后丢弃工作树、样式借用、布局缓存与绘制状态——只留下最新的那一份冻结树。
🔍 FrozenLayoutTree 里到底冻结了什么?
FrozenLayoutTree是一棵不可变、与 DOM 解耦的几何树,定义见 moli-layout/src/layout_tree/tree.rs。它用紧凑的稠密数组存储,节点之间只靠父 ID 关联:
- 盒(boxes):每个盒子保存 content/padding/border/margin 完整盒模型几何、滚动范围、可见性与 pointer-events 标记,精度对齐 Blink 的 1/64 CSS 像素子像素
- 片段(fragments):行盒、内联盒、文本行等几何片段,保留换行后的真实文本位置
- 坐标空间:每个盒子到视口的 2D 仿射变换(另含一套"忽略 CSS transform"的变换),让
getBoundingClientRect这类查询一次查表即可 - 裁剪链、滚动容器、iframe 子树:clip 矩形链、滚动条几何,以及递归内嵌的子文档树
- 来源标记(provenance):只记录"这个几何对应哪个 DOM 节点"的 ID,而不持有 DOM 本身
这套设计的巧妙之处:冻结树不再引用活的 DOM 和样式,DOM 可以随时变化甚至销毁,快照依然能独立存活并被查询。几何数据模型详见 moli-layout/src/layout_tree/model.rs。
算一次,读多次:几何查询如何复用快照
渲染端为每个文档只保留一个最新布局快照槽位(见 layout_snapshot.rs 与 layout_state.rs)。普通几何读取——坐标查询、命中测试、滚动查询——都直接从这份冻结树派生答案,即使页面后续发生变化也允许复用旧快照。
这换来的是极低的稳态开销:AI 智能体连发十几次几何查询,只花一次布局的"钱"。
但"复用"有明确的边界:
- 截图永远重建:每次截图都重新布局并整体替换冻结树,保证像素级真实
- 绘制结果从不复用:录屏订阅只记录一个不透明的"视觉状态令牌",令牌没变就跳过轮询,变了才产出一帧新画面
- 保留上限兜底:单棵冻结树最多保留 100 万盒、400 万片段、约 256 MiB 估算容量(tree.rs),防止极端页面撑爆内存
它故意"没有"的东西
理解 Moli 的布局原理,一半要看它有什么,另一半要看它没有什么。Moli 没有增量维护的布局树、没有脏区域图、没有常驻显示列表、没有 GPU 合成器、没有持久化窗口。DOM 与样式只有单一事实来源(原生 DOM + Stylo),其余一切都是用完即弃的一次性产物。
这让"页面状态"小到只需记住一棵冻结树,也让每次布局的冷启动成本变得可预测——对短平快的智能体任务而言,这比复杂的增量维护机制划算得多。
📊 效果:71 MiB 跑出的高兼容
布局快照机制直接体现在 Moli 的基准表现上。在 1,928 个任务的 Lexbench 无头浏览器测试中,Moli 以约 71 MiB 的中位峰值内存拿到 80.7% 的任务成功率,与 770 MiB 的 Chrome 之间,内存差距远大于兼容性差距:
在另一组 1,308 个可比自动化任务中,Moli 以 81.9% 的任务成功率位列非 Chrome 引擎第一:
想继续深挖源码?🛠️
- 布局通道与快照输出:moli-layout/src/pass.rs、moli-layout/src/lib.rs
- 冻结树结构与内存保留边界:moli-layout/src/layout_tree/tree.rs
- 命中测试与几何查询:moli-layout/src/layout_tree/hit_test.rs、moli-layout/src/layout_tree/query.rs
- 文档级布局状态管理:moli-renderer-v8/src/native_bridge/context_host/layout_state.rs
- 中文文档:docs/README.zh-CN.md
一句话总结:Moli 用"冻结快照 + 只留最新一份"的策略,把无头浏览器的布局从持续开销变成一次性投资——这正是 AI 智能体工作负载下性价比最高的架构选择。
【免费下载链接】moliBest headless browser for AI agents. Lite, Fast, High-Compatibility. Built in Rust项目地址: https://gitcode.com/gh_mirrors/moli/moli
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考