尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Ladybird浏览器引擎:从零构建、源码解析与本地编译实践

Ladybird浏览器引擎:从零构建、源码解析与本地编译实践 Ladybird 是一个正在快速发展的开源浏览器项目托管在 GitHub 的 LadybirdBrowser/ladybird 仓库。它和常见的 Chrome、Edge、Firefox 最大的区别在于没有直接复用 Chromium、Gecko 或 WebKit 的引擎代码而是从零开始实现网页渲染、JavaScript 解析、布局和绘制等核心环节。对于长期使用现成内核做 Web 开发的人来说这样的项目看起来推进很慢但恰恰因为它从头写起浏览器引擎中最底层的解析、样式计算、脚本执行和布局绘制之间的耦合关系才会被完整地暴露出来。这篇博客会围绕 Ladybird 的技术设计、源码构建、常见问题和参与方式展开目标不是让你用它替代 Chrome而是让你能把代码拉到本地跑起来并找到一条适合自己深入浏览器源码的路径。1. Ladybird 是什么一个从零开始的浏览器引擎项目1.1 浏览器引擎为什么需要“从零开始”浏览器引擎是一整套把 HTML、CSS、JavaScript 变成可交互页面的系统。主流浏览器基本由几大引擎控制Blink、WebKit、Gecko。Ladybird 的路线是另起炉灶用自己的 LibWeb 处理 HTML、CSS、DOM 和渲染用 LibJS 执行 JavaScript网络、编码、布局等模块也拆成独立的 Lib 库。从零开始意味着工作量巨大。这个项目值得关注不只是因为“多了一个浏览器选择”而是因为当团队从第一行代码开始实现一个 Web 标准时很多被现有引擎隐藏起来的复杂度必须重新回答。比如一个div的 CSS 属性如何映射到内部数据结构JavaScript 的垃圾回收如何配合 DOM 节点生命周期布局引擎如何根据视口宽度计算折行。这些问题在成熟浏览器里都有答案但阅读成熟引擎的代码时你看到的往往是高度优化过的结果历史包袱和性能代码混在一起。Ladybird 的代码链路更短更容易从入口追踪到出口这是它作为学习对象最突出的价值。1.2 从 SerenityOS 中独立出来的 LadybirdBrowser/ladybirdLadybird 最初来自 SerenityOS 这个类 Unix 操作系统项目作者是 Andreas Kling。它早期更像是 SerenityOS 桌面环境里的“演示级浏览器”用来展示系统自绘 UI 的能力。随着项目推进团队希望把它变成真正能在日常系统上运行的多平台浏览器于是仓库从 SerenityOS 中拆分出来形成了独立的 LadybirdBrowser/ladybird。现在你在 GitHub 上看到的组织名是 LadybirdBrowser核心仓库也叫 ladybird。虽然代码风格还保留着一些 SerenityOS 的影响比如大量Lib*前缀、类命名和 CMake 结构但它已经是一个独立的多平台浏览器项目不再依赖 SerenityOS。理解这个历史很重要因为你会发现源码里有些命名习惯和 SerenityOS 一脉相承比如dbgln这类调试输出方式。如果只看 GitHub 上的文件列表可能会误以为它还是某个操作系统的副产物实际上它已经是一个独立活跃的开源项目。1.3 项目当前定位适合学习不适合日常浏览器Ladybird 目前处于早期但非常活跃的阶段。它已经能打开很多现代网页JavaScript 核心能力和渲染能力也在持续增加但离成为日常浏览器还有明显距离。这里需要建立正确预期如果把它当成主力浏览器你会遇到大量页面显示不完整、网站兼容性报错、没有完整扩展生态等问题。如果你把它当成学习浏览器引擎和参与开源项目的入口它的代码量、模块划分和社区活跃度在同类项目中是相对友好的。判断一个开源项目值不值得投入不能只看它能打开多少网页还要看架构是否清晰、提交是否活跃、新人是否能找到切入点。从这几个维度看Ladybird 属于有很高学习价值的项目。即使你不打算贡献代码只把它作为浏览器原理的对照实现来阅读也会很有收获。2. 先看清技术地图仓库结构与核心组件2.1 仓库顶层目录为什么以 Libraries 为主打开 LadybirdBrowser/ladybird 仓库后第一眼会看到大量Lib*目录。这种命名习惯来自 SerenityOS它在项目里表示“可复用的库模块”。Ladybird 把浏览器按职责拆成不同库正是为了让你在阅读时不用一口气读完整个项目而是可以按模块切入。仓库结构会随版本迭代调整但常见形态包括这样几块LadybirdBrowser/ladybird ├── Meta/ # 构建脚本、辅助工具、CI 配置 ├── Libraries/ │ ├── LibWeb/ # HTML、CSS、DOM、布局和渲染 │ ├── LibJS/ # JavaScript 解析和执行 │ ├── LibGC/ # JavaScript 与 DOM 对象的垃圾回收基础组件 │ ├── LibUnicode/ # 字符处理、Unicode 国际化能力 │ └── ... ├── UI/ # 浏览器主程序与平台适配 ├── Tests/ # 自动化测试 └── CMakeLists.txt之所以把功能拆成多个Lib而不是写在一个大目录里是因为浏览器引擎不同部分有清晰的依赖边界。JavaScript 引擎可以不依赖渲染层独立测试HTML 解析器可以不依赖 JavaScript 运行。模块化之后开发者可以单独编译和测试某个模块新贡献者也能更快定位问题。这里要注意目录名和归属会随版本调整阅读之前最好先看仓库根目录的 README 和Meta/CMake相关配置不要拿旧文章里的目录结构去套新代码。2.2 从 LibJS 到 LibWeb两个核心库的分工LibJS 是 JavaScript 引擎负责解析和执行 ECMAScript 代码。它提供了 JavaScript 的标准对象、函数和运行时机制。LibWeb 是浏览器引擎中的渲染核心负责 HTML 解析、CSS 解析、DOM 管理、布局计算和绘制协调。两者是分离的。分离的原因很直接JavaScript 不需要 DOM 也能运行LibWeb 需要调用 LibJS 来执行页面脚本同时还要把 DOM 对象暴露给 JavaScript 环境。这里有一个浏览器引擎经典难点JavaScript 对象和 C 对象如何维持生命周期。在浏览器环境里JavaScript 引用了一个 DOM 节点这个节点就不能被随意析构反过来C 持有的 JavaScript 回调函数也不能出现悬空引用。为了处理这类问题项目里会有类似 LibGC 的基础设施来统一管理对象生命周期。阅读 Ladybird 源码时不要只盯着具体某个 CSS 属性或 HTML 标签先搞清楚对象是在哪里创建的、谁持有引用、什么时候释放比记住某个类名要重要得多。2.3 自研引擎不是否定现有引擎而是控制与学习一个常见疑问是为什么不直接用 WebKit 或 Blink 修修补补Ladybird 选择自研原因可以从三个角度看。第一是代码控制力。所有关键模块都放在自己的仓库里没有历史包袱可以按新标准自由改造。第二是研究价值。完整实现一个现代浏览器引擎是大型复杂项目自研过程中能验证 Web 标准的可实现性也能发现标准文本中不清晰的地方。第三是社区协作。自研代码可以从一开始就保持清晰的模块边界让新贡献者更容易理解。这不意味着现有引擎不好而是不同目标下的不同选择。阅读 Ladybird 时不需要带“推翻 Chrome”的心态它更接近一个开放研究项目加多平台浏览器的中间形态。你读它是因为想理解浏览器的本质而不是因为它已经比成熟的商业引擎强。3. 从源码构建 Ladybird 的完整流程3.1 构建前的环境准备和版本取舍在开始构建之前先确认环境满足项目要求。不同平台的依赖会有差异最权威的说明是仓库根目录的 README 或CONTRIBUTING.md。下面给出的是一般开源浏览器项目常见的依赖项目。工具用途常见版本建议Git拉取源码2.x 以上CMake生成构建系统尽量使用项目要求的版本Ninja真正的编译调度器1.10 以上C 编译器编译 C 源码Clang 或 GCC具体看项目支持Python 3运行辅助脚本3.8 以上平台编译工具链链接和平台依赖Linux 为 build-essentialmacOS 为 Xcode CLT桌面浏览器还需要窗口系统和图形相关依赖Linux 上通常是 libgl、libxkbcommon 等macOS 使用系统自带的 GUI 框架Windows 使用 MSVC 或 MinGW 环境。如果缺少某个依赖CMake 配置阶段通常会直接报错错误信息里会提示找不到某个头文件或库。注意不要拿到一份依赖列表就无条件安装。Ladybird 的项目脚本和 CI 配置里会明确测试过的最小版本如果你本地版本过低构建可能在很晚的阶段才失败。稳妥的做法是先看.github/workflows/和CMakeLists.txt里的版本声明。3.2 拉取源码并初始化子模块拉取代码时先看仓库是否使用 Git 子模块。Ladybird 的一些测试资源或第三方库可能会以子模块方式引入如果子模块没有拉全编译时会出现“找不到文件”的奇怪错误。基本命令git clone https://github.com/LadybirdBrowser/ladybird.git cd ladybird git submodule update --init --recursive如果你的仓库已经克隆完成但子模块缺失可以使用git submodule status检查状态。输出里每一行开头是-表示子模块未初始化是表示子模块提交记录与预期不一致需要更新。这条命令的正确理解是git clone只负责下载主仓库内容子模块是独立的 Git 仓库。浏览器项目里大量测试数据既占空间又经常更新选择子模块方式可以避免把数据直接塞进主仓库。如果你看到仓库根目录有.gitmodules文件那么上面两条命令几乎是必需的。3.3 使用项目自带脚本编译以 Meta/ladybird.sh 为例Ladybird 从 SerenityOS 继承了元工具脚本的习惯。常用的构建脚本位于Meta/ladybird.sh它的作用是把“创建构建目录、配置 CMake、调用 Ninja”这些步骤封装起来避免手动输入一长串命令。常见用法./Meta/ladybird.sh build执行过程中会看到大量 CMake 输出和编译日志。第一次编译需要较长时间因为需要把 LibJS、LibWeb 和平台层全部编译一遍。如果想要更多脚本选项可以执行./Meta/ladybird.sh help脚本的输出位置可能随构建配置变化。常见生成物在Build/目录下可能是Build/ladybird、Build/bin/ladybird或Build/bin/Ladybird。如果找不到可执行文件可以用 find 命令辅助定位find Build -type f -executable -name *ladybird* -o -name *Ladybird*这里的关键点是不要在一个大型项目里凭记忆猜二进制路径构建完成后先看脚本文档然后用 find 或ls -la Build/*确认实际位置。运行浏览器时可以打开一个任意网站./Build/ladybird https://example.com如果你是新手建议先打开本地 HTML 文件避免测试网站本身有兼容性问题干扰判断。Ladybird 支持file://协议可以直接传入本地路径。3.4 使用 CMake 直接构建时的注意事项如果项目自带脚本在你的平台上不适用或者你想精细控制构建参数可以绕过脚本直接使用 CMake。基本命令如下cmake -S . -B Build -GNinja -DCMAKE_BUILD_TYPEDebug ninja -C Build ladybird这里每个参数都有含义-S .指定源码目录是当前目录。-B Build指定构建目录是Build所有中间产物都放在里面。-GNinja选择 Ninja 作为构建系统生成器比默认的 Unix Makefiles 更快。-DCMAKE_BUILD_TYPEDebug表示编译调试版本带符号信息方便后续跟踪源码。如果只追求运行速度和更小的体积可以改成Release但建议学习和参与开发时使用 Debug。直接使用 CMake 意味着你要自己承担“清理构建目录”的责任。碰到源码切换分支、CMake 版本升级、依赖安装变化等问题时先清理Build目录再重新构建通常比在残留缓存上反复尝试更有效rm -rf Build cmake -S . -B Build -GNinja -DCMAKE_BUILD_TYPEDebug ninja -C Build ladybird注意Ninja 默认会根据 CPU 核心数并行编译但整个项目非常大内存不足的机器容易在链接阶段被系统杀掉。如果遇到内存不足限制并行数ninja -C Build -j 2 ladybird。4. 页面从 URL 到像素LibWeb 的渲染链路4.1 把一条 URL 拆成五个阶段在 Ladybird 里从输入 URL 到屏幕出现页面可以粗略分成五个阶段。第一阶段是网络请求获取 HTML 内容第二阶段是解析 HTML生成 DOM 树第三阶段是解析 CSS生成样式规则第四阶段是布局计算每个元素的位置和尺寸第五阶段是绘制把布局结果画到窗口上。这个拆分不是 Ladybird 独有的几乎所有浏览器引擎都遵循类似链路。Ladybird 的价值在于每条路径都有明确对应的库和数据结构。你在 DevTools 里看到的“Network、Parsing、Layout、Paint”不是抽象概念它们对应的是源码里真实存在的函数调用链。理解这条链路时最关键的一点是不要把它理解成线性流水线。HTML 解析过程中可能遇到 JavaScriptJavaScript 可能修改 DOM导致布局和绘制重新执行。CSS 样式变化也可能触发重新布局。真正的浏览器引擎是事件驱动、增量计算的系统而不是一个一次性读取文件的转换器。4.2 每个阶段对应的库和数据结构在不同的 Ladybird 版本里数据结构名称会有差异但可以按下面这个表建立整体印象阶段主要工作对应组件输入与输出网络获取 HTML、CSS、JS 资源网络层与资源加载URL 转为字节流解析HTML 解析、CSS 解析LibWeb 解析器字节流转为 DOM、CSS 规则样式计算元素最终样式样式系统DOM CSS 转为样式属性集合布局计算几何信息布局系统样式属性转为尺寸和坐标绘制把几何信息画到屏幕绘制与平台层布局树转为光栅化内容对应到代码里你会频繁看到DOM::Document、CSS::StyleProperties、Layout::*这类类名。它们分别是对文档、样式和布局节点的抽象。阅读时不要被类名吓到可以先从一个输入输出明确的小模块开始。比如看某个 HTML 标签的解析逻辑只需要关注“输入一段 HTML 字节流输出对应的 DOM 节点”。4.3 如何验证页面加载和调试渲染问题Ladybird 的开发者日常会使用大量测试用例验证行为。作为新手可以用一个最简单的最小页面来验证“HTML 解析、CSS 样式、JavaScript 执行”这一条主线是否正常。创建test.html!DOCTYPE html html head titleLadybird test/title /head body p idresultloading/p script document.getElementById(result).innerText hello from LibJS; /script /body /html然后运行./Build/ladybird file:///path/to/test.html如果页面最终显示hello from LibJS说明 HTML 解析、DOM 操作和 JavaScript 执行的核心链路是通的。如果页面保持loading不变说明脚本执行或 DOM API 实现还有问题。这个最小用例之所以好用是因为它把问题边界压缩得很小不涉及网络、不涉及复杂布局只测试最基本的 DOM API。项目通常会在Tests/目录里提供大量自动化用例。构建完成后可以尝试运行与某个模块相关的测试命令。具体命令要查看Meta/ladybird.sh help或仓库文档不要臆造。我的建议是先不要急着跑全部测试先找到Tests/LibWeb或Tests/LibJS这一类小范围测试理解一个测试文件的输入和断言再逐步扩大范围。5. 常见构建与运行问题排查5.1 使用统一排查顺序先环境再配置在开源大型项目里构建失败的原因通常不是一个而是一类。Ladybird 涉及的模块很多如果一上来就重编整个项目可能浪费大量时间。建议按这个顺序排查确认代码和子模块完整用git status查看干净状态用git submodule status检查子模块。确认 CMake、Ninja、编译器版本符合项目要求。确认平台依赖已安装尤其是图形和窗口库。清理构建目录后重新配置排除缓存干扰。查看第一条真正的错误日志不要只看最后的“fail”字样。如果编译过程中系统被杀死先检查内存和磁盘空间。这个顺序的核心逻辑是先排除“输入不完整”再排除“工具不匹配”最后才怀疑“代码本身有 bug”。大型项目里很大比例的失败都是环境问题而不是项目源码问题。5.2 构建失败、运行失败、页面白屏三类问题速查下面这张表总结了 Ladybird 构建和使用过程中容易出现的现象、原因和处理方式问题现象可能原因检查方式处理建议CMake 报找不到头文件缺少系统依赖查看错误日志中的头文件名安装对应开发包并重新配置子模块路径为空未执行submodule update查看.gitmodules和git submodule status执行初始化命令Ninja 找不到构建目标目标名或目录结构变化查看ninja -C Build help或 README使用项目脚本或确认正确 target编译中途被系统杀死内存不足查看系统日志或终端输出降低-j并行数扩大交换分区运行提示找不到动态库链接库路径未配置检查ldd和安装路径设置库路径或重新安装依赖打开页面白屏页面使用未实现的标准能力用本地简单 HTML 做对照测试缩减用例确认是未实现而非渲染故障这些问题的共同点是不要只看表面现象要看“第一次出现异常”的位置。CMake的错误信息通常已经指出了缺什么Ninja 的错误信息则会告诉你目标名运行时错误则要检查库和环境变量。5.3 一个容易误判的问题不是 bug而是标准还没实现很多刚接触 Ladybird 的人打开真实网站发现页面错乱第一反应是“浏览器有 bug”。在成熟浏览器里这通常确实算 bug但在 Ladybird 这样仍处于早期实现阶段的项目里更可能是某个 Web 标准特性还没有实现或者实现不完整。判断方式很简单用开发者工具查看这个网站使用了什么技术然后把页面简化直到只剩下一个导致问题的特性。比如某个网站的布局错乱可能只是因为它使用了某个新的 CSS 单位Ladybird 还没有完整支持。这时不应该怪浏览器不稳定而应该在项目 issues 里搜索该特性是否已经被跟踪或者自己尝试实现一个最小用例。如果确认是未实现的标准你可以提交一个 issue主要写清楚四件事使用的浏览器版本和构建时间。复现页面的最小 HTML/CSS/JS。预期行为是什么实际行为是什么。相关 Web 标准文档链接。这种问题报告是开源项目最欢迎的贡献形式之一它不需要你立刻理解全部源码但能帮助维护者快速定位问题。6. 参与 Ladybird 开发从读代码到提交 PR6.1 先读代码规范避免犯低级问题Ladybird 是一个 C 项目代码风格和 SerenityOS 一脉相承。参与之前先看仓库里的CONTRIBUTING.md和相关文档。不要急着提交先把代码格式工具配置好。项目通常会要求使用clang-format格式化代码。遵守命名约定类名用大写开头函数名和变量名用驼峰私有成员可能有m_前缀。提交信息简洁清晰描述“为什么”和“做了什么”。一个 PR 只解决一个问题避免把重构、修 bug、加文档混在一起。这些规范不是为了制造门槛而是为了让大量贡献者可以并行协作。Ladybird 这种项目每天都有大量提交如果每个人代码风格都不一样diff 会变得难以阅读审查效率也会大幅下降。6.2 找一个“边界清晰”的任务切入第一次参与不要直接挑战“重构 LibWeb”这种大任务。边界清晰的任务更适合新人比如实现一个 CSS 属性的解析和属性名注册。修正一个 HTML 元素在特殊场景下的行为。补一个测试用例。改进错误日志信息。在 GitHub issues 里搜索good first issue或help wanted标签通常能找到这类任务。选定任务后不要马上动手写代码先在 issue 下面说明你想尝试并问清楚维护者的预期范围。这样可以避免理解偏差也能让维护者提前知道你的计划。6.3 提交 PR 前要检查哪些内容提交 PR 前建议按下面这个清单逐项检查当前分支是否基于最新的主分支。是否只修改了和本次任务相关的文件。是否补充了对应的测试用例。是否运行了本地的相关测试。是否执行过clang-format。提交信息是否清晰是否说明了原因。是否检查过 diff 中不必要的变化比如自动生成的临时文件。这个清单就是一份可复用 checklist。以后参与任何开源项目都可以用它来检查自己的提交。它不复杂但能避免最常见的返工情况。6.4 贡献者真正在审查什么代码审查关注点往往不是“能不能跑”而是“将来好不好维护”。在 Ladybird 这种浏览器引擎项目里审查者会重点关注是否正确遵守了 Web 标准而不是只针对某个页面 fix。是否破坏了其他模块的边界。对象生命周期是否安全是否存在泄漏或悬空引用。是否引入了不必要的复杂度或性能回退。是否有足够的测试覆盖。如果你在 PR 描述里能写清楚“参考了标准文档的哪一节、实现思路是什么、测试结果是什么”审查者给你的帮助会大得多。开源项目的本质不是“我交了代码就完成”而是通过讨论让代码越变越好。7. 学习 Ladybird 的路径建议7.1 谁适合读这个项目Ladybird 最适合三类开发者。第一类是对浏览器原理感兴趣的前端工程师。平时写页面只看到浏览器的行为但在 Ladybird 里能看到这些行为背后的代码路径。第二类是 C 开发者。项目里包含了解析器、编译器、虚拟机和图形渲染等多个复杂领域是一个很好的大型 C 代码库。第三类是刚接触开源贡献的开发者。相比直接去改 Linux 内核或 LLVMLadybird 的模块边界更清晰任务颗粒度也更适合起步。如果你对 JavaScript 引擎感兴趣可以重点看 LibJS如果你对 HTML/CSS 渲染感兴趣可以重点看 LibWeb如果你对 GUI 和跨平台适配感兴趣可以看 UI 和平台相关代码。7.2 从浏览器原理到具体代码的学习顺序直接读代码效率很低建议按下面的顺序建立整体认知先了解浏览器基本工作流程URL 到页面显示经历了哪些阶段。再看 Ladybird 的目录结构和模块边界找到入口文件。从一个具体的页面元素入手比如p标签或button标签跟踪它在 HTML 解析时如何生成 DOM 节点。再看一个 CSS 属性比如color或display看它如何在解析、样式计算和布局阶段传递。最后再看 JavaScript 与 DOM 的绑定理解两种语言的边界在哪。这个顺序的核心是把“理解”建立在具体对象上而不是试图读完所有代码。每一步都可以用本地编译出来的浏览器验证行为。7.3 可以尝试的三个练习任务如果你是第一次接触这个项目可以试着完成下面三个小练习用本地构建的 Ladybird 打开自己的本地 HTML 文件运行最基本的 DOM API 脚本。修改一段日志代码让程序在打开页面时输出某个 DOM 节点的标签名然后重新编译运行。在源码里找一个简单的 CSS 属性理解它的浏览器默认值定义在哪里以及引擎如何把它应用到元素上。这三个练习不会直接让你成为贡献者但它们能帮你建立起“代码改动到行为变化”的闭环。你会慢慢发现浏览器不是黑盒它里面的每个标准行为都可以在源码里找到对应位置。7.4 对日常使用和生产环境的预期最后回到预期管理。Ladybird 是一个早期项目可以用来学习、研究和参与社区但不要把它当成日常主力浏览器也不要让业务系统直接依赖它作为生产级渲染方案。生产环境里对稳定性、性能、安全性要求很高成熟引擎经过大量兼容性测试这些积累不是短期内能赶上的。如果你因为学习这个项目而获得了对浏览器机制的更深入理解那最大的收益不是“我编译了一个浏览器”而是以后遇到线上页面渲染问题、JavaScript 性能问题、内存泄漏问题时你能比之前更有方向感。这种方向感才是 Ladybird 项目最值得投入时间的地方。
返回列表