浏览器内核这门“老手艺”,这几年几乎没有新玩家入场。主流产品要么基于 Chromium,要么基于 WebKit,真正从零编写、不借用现成浏览器引擎的项目屈指可数。而 Ladybird 的出现,打破了这种局面。
很多人第一次看到 LadybirdBrowser / ladybird 这个仓库时,第一反应是“又一个换皮浏览器”。但仔细观察源码结构会发现,它从 HTML 解析器、CSS 布局引擎、JavaScript 解释器,到网络协议栈、图形渲染层,全部是独立实现的。它不是给 Chromium 套了一层 UI,也不是 WebKit 的派生分支。
这篇文章会围绕 Ladybird 项目展开,重点讲清楚三件事:Ladybird 到底是什么、它的核心架构有哪些值得学习的设计、以及如何从源码开始把它跑起来。同时会穿插介绍构建过程中常见的坑和源码阅读建议,希望对浏览器内核感兴趣的读者有帮助。
1. 背景与核心概念
1.1 Ladybird 是什么,它从哪里来
Ladybird 是一个跨平台 Web 浏览器项目,最早诞生于 SerenityOS 操作系统项目内部。SerenityOS 是一个从零构建的类 Unix 操作系统,由 Andreas Kling 发起,Ladybird 最初只是该系统自带的浏览器。
2024 年年中,Ladybird 正式从 SerenityOS 独立出来,成为一个跨平台浏览器项目。独立之后的 Ladybird 不再绑定 SerenityOS,而是把目标平台扩展到了 Linux、macOS 等主流操作系统。项目采用 C++ 编写,遵循 C++23 标准,使用 CMake 和 Ninja 构建。
从技术路线上看,Ladybird 最大的特点是“独立实现”。它没有使用 Chromium 的 Blink 引擎,没有使用 Firefox 的 Gecko 引擎,也没有像许多小型浏览器那样直接套用 WebKit。它的渲染引擎叫 LibWeb,JavaScript 引擎叫 LibJS,这两者是与 WebKit、V8 完全不同的独立代码库。
1.2 Ladybird 要解决什么问题
浏览器内核开发是软件工程中复杂度极高的领域。目前在消费级市场,实际存活的独立渲染引擎只有 Blink、Gecko 和 WebKit 三家。Ladybird 项目希望通过重新实现一套完整的浏览器技术栈,达到几个目标:
- 降低浏览器内核的理解门槛。现有三大引擎代码量巨大,历史包袱多,新研究者往往不知道该从哪里入手。Ladybird 起步较晚,代码结构更加清晰,模块边界更明确。
- 打破浏览器内核的技术垄断。如果未来所有浏览器都基于 Chromium,整个 Web 生态的演进方向将由单一厂商主导。独立实现是一种生态保险。
- 探索性能与安全的新路径。在实现过程中,团队可以重新思考 JIT 编译、进程隔离、渲染调度等核心问题,而不必受旧架构约束。
1.3 谁适合关注这个项目
从读者角度来说,我认为以下三类人最值得关注 Ladybird:
- C/C++ 开发者,尤其是对编译器、解释器、渲染引擎感兴趣的开发者。
- 前端工程师,想深入了解浏览器如何把 HTML/CSS/JavaScript 变成页面的开发者。
- 操作系统和底层软件爱好者,关心系统组件如何协作、进程如何通信的开发者。
如果只是日常使用浏览器,目前 Ladybird 还不适合作为主力浏览器,它更适合作为学习对象和二次开发基座。
2. 技术架构与核心组件拆解
2.1 整体架构概览
Ladybird 采用多进程架构。简单来说,浏览器主界面运行在一个进程中,每个标签页的网页内容运行在独立的 WebContent 进程中。这样设计的好处是:某个标签页崩溃或卡死时,不会拖垮整个浏览器窗口。
+-------------------+ | Browser 主进程 | 负责窗口管理、地址栏、书签、设置 +-------------------+ | | IPC v +-------------------+ | WebContent 进程 1 | 负责标签页 1 的渲染与脚本执行 +-------------------+ | | IPC v +-------------------+ | WebContent 进程 2 | 负责标签页 2 的渲染与脚本执行 +-------------------+进程之间通过 Ladybird 自定义的 IPC 机制通信。主进程发送“加载这个 URL”“执行点击事件”等指令,WebContent 进程返回“页面绘制完成”“网络请求失败”等状态。
2.2 LibWeb:HTML 与 CSS 的解析渲染引擎
LibWeb 是 Ladybird 中最核心的组件,负责 Web 标准的解析与渲染。它的工作流程可以简化成下面几步:
- 通过 LibURL 解析地址,使用网络组件获取 HTML 文本。
- 通过 HTML 解析器把字符流转换为 DOM 树。
- 通过 CSS 解析器解析样式表,计算每个 DOM 节点的最终样式。
- 通过布局引擎计算每个元素的尺寸和位置。
- 通过绘制模块把布局结果输出为像素。
对应到源码目录,核心部分集中在Userland/Libraries/LibWeb目录下。这个目录以子目录形式区分功能模块,例如HTML目录存放 HTML 解析与 DOM 实现,CSS目录存放样式解析,Layout目录存放布局算法,Painting目录存放绘制逻辑。
2.3 LibJS:JavaScript 引擎
JavaScript 引擎是浏览器性能的关键,LibJS 是 Ladybird 的独立 JavaScript 引擎。它包含完整的解释器、字节码虚拟机、垃圾回收器和 JIT 编译管线。
与 V8 这类高度优化的生产级引擎相比,LibJS 目前的主要优势是可读性。它没有过多历史的兼容包袱,代码组织方式更符合现代 C++ 的习惯。对于想了解“ JavaScript 引擎如何工作”的开发者来说,LibJS 比 V8 容易读得多。
LibJS 的源码集中在Userland/Libraries/LibJS目录。该引擎还会额外提供 LibWasm,也就是 WebAssembly 的运行时支持。
2.4 LibGC:自动内存管理
写过 C++ 的朋友都清楚,C++ 本身没有垃圾回收机制。但浏览器引擎的 JavaScript 对象生命周期非常复杂,如果完全依赖手工管理内存,很容易出现悬垂指针或内存泄漏。为此,Ladybird 实现了一个专属的垃圾回收器 LibGC。
LibGC 采用追踪式垃圾回收策略,配合 C++ 的GCPtr指针模板使用。所有需要被 GC 管理的对象都继承自Cell,通过Heap分配和跟踪。这种设计让 JavaScript 对象可以安全地在多个组件之间传递,同时避免引入过重的引用计数开销。
2.5 LibGfx 与图形绘制
浏览器最终要把渲染结果输出到屏幕上,这依赖图形库。Ladybird 的图形模块叫 LibGfx,负责处理颜色、位图、字体渲染、图片解码以及 2D 绘制操作。在独立后的新版本中,绘制后端还支持接入 Skia,以便在某些平台获得更好的渲染性能。
LibGfx 的实现贴近图形学基础,适合阅读,里面包含大量位图操作、路径填充、抗锯齿算法的实际代码。
3. 环境准备与版本说明
3.1 支持的操作系统
Ladybird 官方优先支持 Linux 和 macOS。Windows 平台目前没有官方支持,但用户可以通过 WSL 构建运行。本文以 Ubuntu 22.04/24.04 为例,如果你使用其他发行版,包管理命令需要对应调整。
3.2 依赖项清单
构建 Ladybird 不是一个“单命令搞定”的过程,它依赖许多系统库。以下是 Ubuntu 上常见的依赖安装命令。版本需要根据你的系统实际情况调整,这里以软链到较新版本的方式保持可复现性。
sudo apt update sudo apt install -y \ build-essential \ cmake \ ninja-build \ ccache \ python3 \ python3-pip \ libgl1-mesa-dev \ libegl1-mesa-dev \ libssl-dev \ libcurl4-openssl-dev \ libavcodec-dev \ libavformat-dev \ libswscale-dev \ libgtk-3-dev \ libgdk-pixbuf-2.0-dev \ libxkbcommon-dev \ libxrandr-dev \ libxinerama-dev \ libxcursor-dev \ libxi-dev \ libasound2-dev \ libpulse-dev \ fonts-liberation \ libfontconfig1-dev \ libharfbuzz-dev说明几点:
- LibreSSL 与 OpenSSL 的 API 有差异,如果使用系统自带的 OpenSSL 版本较老,可能需要指定路径。
- 图像解码方面,项目会用到 FFmpeg 相关库来支持视频编解码,因此
libavcodec-dev、libavformat-dev、libswscale-dev是必要的。 - 输入事件与窗口管理依赖 X11/XKB 相关库,如果缺少这些库,编译可以在完成之后,运行时会无法创建窗口。
3.3 编译器要求
Ladybird 使用 C++23 特性,因此编译器版本不能太老。推荐使用 GCC 13 或 Clang 16 及更高版本。在 Ubuntu 上,如果默认 GCC 版本偏低,可以通过gcc-13包升级。
sudo apt install -y gcc-13 g++-13 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 100 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 1003.4 磁盘与内存建议
Ladybird 的代码量虽然不如 Chromium 庞大,但完整编译仍然需要时间。建议至少准备 20 GB 空闲磁盘空间和 8 GB 内存。如果机器内存较小,可以通过调低编译并行度来避免内存耗尽,这一点在后续构建部分会详细说明。
4. 源码获取与完整构建过程
4.1 拉取代码
使用 Git 从 GitHub 拉取 Ladybird 仓库。为了减少下载体积,建议只克隆当前分支的最近一次提交。
git clone --depth=1 https://github.com/LadybirdBrowser/ladybird.git cd ladybird拉取完成后,可以通过下面的命令查看源码目录结构:
ls -la主要目录说明:
Userland/存放用户态代码,浏览器组件大多在这里。Meta/存放项目维护脚本、构建辅助脚本。Tests/存放测试用例。CMakeLists.txt是构建系统的总入口。
4.2 使用官方构建脚本
Ladybird 提供了一个便捷构建脚本Meta/ladybird.sh。脚本内部会完成依赖检测、CMake 配置和构建调用。直接运行:
./Meta/ladybird.sh run这个命令会先构建,构建成功后再启动 Ladybird。
如果只想构建、不立即运行,可以使用:
./Meta/ladybird.sh build如果你是第一次构建,这个过程需要一段时间,具体时长取决于 CPU 核心数和内存。使用 8 核 16G 的机器,完整构建大约在 15 到 30 分钟之间。
4.3 手动 CMake 构建
如果你希望自定义构建参数,建议直接使用 CMake 和 Ninja。
cmake -B Build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo ninja -C Build ladybird参数解析:
-B Build表示构建目录为Build。-GNinja使用 Ninja 作为构建后端,Ninja 的并行构建能力和增量编译速度比 Make 好很多。-DCMAKE_BUILD_TYPE=RelWithDebInfo生成带调试信息的优化版本,对后续调试更友好。
如果系统内存不够,可以限制并行任务数:
ninja -C Build ladybird -j 44.4 运行 Ladybird
构建完成后,可执行文件会生成在Build/bin/ladybird路径下。运行:
./Build/bin/ladybird如果你想启动后直接打开某个页面,可以传入 URL 参数:
./Build/bin/ladybird https://example.com/如果一切正常,屏幕上会出现 Ladybird 窗口,并加载对应页面。如果出现闪退或空白窗口,请参考第 6 节常见问题排查。
4.5 如何确认构建成功
在构建结束时,Ninja 会输出类似下面的提示:
[1234/2345] Linking CXX executable bin/ladybird这说明ladybird可执行文件已经生成。另一个验证方式是直接检查文件是否存在:
file Build/bin/ladybird正常输出中会出现ELF 64-bit等描述,说明这是一个可执行的二进制文件。
5. 核心模块源码导读
5.1 浏览器主入口
Ladybird 的入口在Userland/Applications/Ladybird目录下。以当前源码版本为例,入口函数最终会创建主窗口并启动事件循环。理解入口时,可以关注两个点:
- 应用如何初始化多进程环境。
- 事件循环如何注册和分发系统事件。
5.2 HTML 解析流程入口
LibWeb 的 HTML 解析器位于Userland/Libraries/LibWeb/HTML/Parser/。解析器实现了 HTML Standard 的 tokenization 和 tree construction 算法。你可以从HTMLParser类开始阅读,观察它如何处理标签、属性和文本节点。
理解 HTML 解析器的关键是先掌握状态机思想。HTML 解析不是简单的正则匹配,而是一个复杂的状态机:每当读入一个字符,解析器会根据当前状态决定下一个状态,并可能产生 token。
5.3 CSS 样式计算
CSS 相关代码位于Userland/Libraries/LibWeb/CSS/。样式计算的入口通常从StyleComputer类开始,它会遍历 DOM 树,为每个节点匹配选择器并计算最终样式。
阅读这一段源码时,建议重点关注级联顺序(cascade)和继承机制。浏览器中的“最终样式”并不等于开发者写的 CSS,而是经过默认样式、用户样式、作者样式、内联样式、!important规则加权之后的综合结果。
5.4 JavaScript 引擎入口
LibJS 的入口在Userland/Libraries/LibJS/Runtime/。最常见的入口是Interpreter类和VM类。要了解一段 JavaScript 是如何被执行的,可以按这条链路阅读:
- 通过 Parser 将 JavaScript 源代码解析为 AST。
- 通过 Bytecode Generator 将 AST 编译为字节码。
- 通过 Interpreter 或 JIT 执行字节码。
5.5 IPC 通信机制
进程间通信是 Ladybird 架构中重要的一环。IPC 消息定义通常分布在各个Endpoint相关目录中。搜索IPC::Endpoint的注册代码,可以看到主进程与 WebContent 进程之间支持哪些消息。
阅读 IPC 代码时,可以从前端和后端两个角度看。前端是“消息如何发送”,后端是“消息如何被接收并分发到具体业务逻辑”。
6. 项目特色与 Web 标准支持
6.1 完全独立的渲染与脚本实现
Ladybird 不像市面上大多数浏览器那样依赖 Blink、Gecko 或 WebKit,它的渲染引擎和 JavaScript 引擎都是独立开发的。这就意味着,Ladybird 每多支持一项 Web 标准,都是“从零实现”的成果。
这种独立性带来的价值不只是“自己写代码”,更在于整个实现过程可以被学习、被审查、被改进。对于 Web 标准的研究者来说,Ladybird 是一个非常难得的实验场。
6.2 对 Web 标准测试的推进
Ladybird 团队积极参与 Web Platform Tests(WPT)测试套件。WPT 是 Web 标准社区共享的一套跨浏览器测试用例,覆盖 HTML、CSS、JavaScript、Web API 等方方面面。
在项目的 GitHub 仓库中,Tests目录下包含大量 WPT 相关用例。每次提交代码后,CI 会跑一部分测试,以衡量新特性的支持率达到什么水平。
6.3 安全架构的考虑
Ladybird 在多进程基础上,正在逐步加强沙箱能力。WebContent 进程运行的是不可信的网页代码,因此需要通过系统层能力隔离,限制它能访问的文件和系统调用。
在阅读源码时,可以关注沙箱相关代码,比如 seccomp 过滤规则的配置。它展示了在 Linux 环境下,浏览器如何尽可能限制恶意页面的权限。
6.4 与主流浏览器引擎的对比
| 维度 | Chromium | WebKit | Ladybird |
|---|---|---|---|
| 渲染引擎 | Blink | WebKit | LibWeb |
| JavaScript 引擎 | V8 | JavaScriptCore | LibJS |
| 起步时间 | 2008 年 | 2001 年 | 2022 年 |
| 代码可读性 | 较低 | 中等 | 较高 |
| 生产可用度 | 极高 | 极高 | 发展中 |
| 主要语言 | C++ | C/C++ | C++23 |
从表格可以看出,Ladybird 在代码规模和复杂度上远小于两位“老前辈”,但对新人学习来说,这反而是一个优势。
7. 常见问题与排查思路
7.1 编译失败:找不到libavcodec相关头文件
- 问题现象:CMake 配置或编译时报错,提示找不到
libavcodec/avcodec.h。 - 常见原因:系统中没有安装 FFmpeg 开发库。
- 解决思路:安装
libavcodec-dev、libavformat-dev、libswscale-dev。
sudo apt install -y libavcodec-dev libavformat-dev libswscale-dev安装后需要重新运行 CMake:
rm -rf Build cmake -B Build -GNinja7.2 编译失败:C++23 特性无法使用
- 问题现象:编译时出现
error: 'std::expected' has not been declared之类的错误。 - 常见原因:编译器版本过老,不支持 C++23。
- 解决思路:升级到 GCC 13 或 Clang 16 以上版本,并确认 CMake 使用的是新版本编译器。
cmake -B Build -GNinja -DCMAKE_CXX_COMPILER=g++-137.3 运行后窗口空白
- 问题现象:Ladybird 窗口能打开,但页面完全空白。
- 常见原因:
- 网络请求失败,页面资源没有加载。
- WebContent 子进程异常退出。
- 系统缺少字体。
- 解决思路:先在终端里运行
./Build/bin/ladybird https://example.com/,观察终端输出有没有错误日志。然后检查网络是否可达目标地址。如果确认是字体问题,安装fonts-liberation后再试。
7.4 中文显示为方块
- 问题现象:页面中文无法正常渲染,显示为方块或乱码。
- 常见原因:系统中缺少合适的中文字体。
- 解决思路:安装中文字体,例如文泉驿或者 Noto Sans CJK。
sudo apt install -y fonts-noto-cjk然后在 Ladybird 设置中重新选择字体,或者直接重新启动浏览器。
7.5 构建过程中电脑卡死
- 问题现象:使用 Ninja 并行编译时,内存占用过高,系统响应变慢。
- 常见原因:
-j并行数过高。 - 解决思路:减少并行任务数,例如只使用 4 个任务。
ninja -C Build ladybird -j 47.6 总结排查清单
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| CMake 找不到依赖 | 系统库未安装完整 | 对照依赖列表逐一安装 |
| 编译报错 C++23 语法不支持 | 编译器版本老 | 升级 GCC/Clang |
| 运行直接闪退 | WebContent 进程崩溃 | 查看终端日志,检查网络 |
| 页面中文乱码 | 缺少中文字体 | 安装 Noto CJK 字体 |
| 构建太慢/卡死 | 并行任务过高 | 限制-j参数 |
8. 最佳实践与学习建议
8.1 从一个小模块开始阅读源码
Ladybird 的源码组织方式比较清晰,但直接读LibWeb的完整实现仍然很困难。建议从一个具体且小的模块开始,比如 URL 解析、文本编码转换或某一个小型 HTML 标签的实现。先把单个功能读透,再逐步扩大范围。
8.2 以 WPT 用例驱动理解
只看源码容易陷入细节,配合 WPT 用例理解更好。每挑一个 Web 平台特性,先找到对应的测试文件,再去看 Ladybird 的实现代码,这样能很快建立“标准要求什么”和“代码做了什么”之间的联系。
8.3 保持跟进主分支
Ladybird 处于快速发展阶段,代码变化非常快。如果以学习为目的,建议定期拉取主分支更新,观察最近 7 天的提交记录,能直观看到项目在哪些模块推进最多。
git pull origin main8.4 为项目做贡献的正确方式
如果你打算参与社区贡献,有以下几点建议:
- 先从
good first issue标签找任务。 - 明确你的改动涉及哪个模块,在对应目录内修改。
- 提交前运行测试,确保不影响现有功能。
- 阅读项目的
CONTRIBUTING文档,遵循提交信息规范。
8.5 学习浏览器内核的成长路线
如果把 Ladybird 当作学习教材,建议按这个顺序进阶:
- 掌握 C++ 基础,尤其是智能指针、移动语义和模板。
- 阅读 LibWeb 的 DOM 与 HTML 解析部分。
- 阅读 LibJS 的解释器与 GC 实现。
- 最后再研究 JIT 编译和渲染优化。
- 配合 Web 平台规范文档,理解标准背后的设计动机。
8.6 不要忽视测试
浏览器引擎最容易出现“改一个功能,坏一片页面”的问题。Ladybird 的测试体系包括单元测试、WPT 集成测试和快照测试。修改代码后,建议至少运行相关模块的单元测试。
运行测试:
ninja -C Build test9. 总结
Ladybird 是一个少见的、从零开始构建的跨平台浏览器项目。它没有依赖 Chromium、Gecko 或 WebKit,而是独立实现了 LibWeb 渲染引擎和 LibJS JavaScript 引擎。对想要理解浏览器内核原理的开发者来说,这几乎是最好的学习样本。
文章中详细介绍了 Ladybird 的架构组成、环境准备、源码构建流程、核心模块导读以及常见问题排查。如果你按照步骤操作,应该可以在 Linux 环境下编译并运行自己的 Ladybird 浏览器。编译过程本身也是一次很有价值的技术训练,它能帮助你快速建立对大型 C++ 项目的整体认知。
浏览器的 Web 标准支持是一个长期工程。Ladybird 目前还不能替代主流浏览器完成日常高频使用,但它的进展速度很快,代码质量也在持续提升。如果感兴趣,建议把它放到虚拟机或备用电脑上体验,同时关注它的 WPT 通过率和每周的版本更新。理解了浏览器内核的工作方式之后,你再看前端性能和兼容性问题,思路会完全不一样。