文章简介:Firefox 源码像一座不断扩建的城市,第一次进去很容易在目录里绕晕。本文从下载、编译开始,带你先认认 DOM、布局、图形、网络、进程通信和浏览器 UI 这些“街区”,再做两个小实验,亲手确认 Firefox 界面是从哪里改起来的。不用试图一次看懂全部源码,先把地图拿在手里就赢了一半。
打开引擎盖,先看里面有哪些部件
你每天都在用浏览器:打开网页、播放视频、写代码、调试接口。但浏览器并不是一个单独的“窗口程序”,而是一组协同工作的引擎、服务和用户界面。
Firefox 的主开发仓库是mozilla-central,通常简称mc。它由 C++、Rust、JavaScript、Python、WebIDL 等多种语言组成,历史很长,目录和构建系统也会持续演进。把它想成一座不断扩建的城市比较合适:有负责 DOM 和布局的核心区域,有网络和安全基础设施,也有面向用户的桌面 UI。
这篇文章先不钻进某个类的几千行实现里,目标很朴素:把源码拿到手、成功构建一次、认出主要目录,再用两个小实验看看 Firefox UI 到底从哪里下手。
一、先让源码构建起来
1.1 获取源码
下面按 Windows 10/11、Visual Studio 2022 和 MozillaBuild 来走一遍。你如果用的是 Linux 或 macOS,安装依赖的方式会换一套,但源码目录和mach这位主角基本不变。
Firefox 官方开发流程以 Mercurial 为主,也存在 Git 镜像。选择哪一种取决于你的工作流和网络条件。
官方下载与开发文档:
- Firefox 源码获取与构建指南(Mozilla 官方)
- Firefox 源码目录结构(Mozilla 官方)
- 在线浏览 Firefox 源码(Searchfox)
本系列统一把源码放在F:\mc。这是一个看起来有点“朴素”的路径,但很实用:Firefox 里有 WebRTC 等目录很深的第三方代码,路径再多套几层,Windows 可能就开始抱怨“路径太长”。
# Mozilla 官方源码仓库hgclone https://hg.mozilla.org/mozilla-centralmc# Git 镜像gitclone https://github.com/mozilla/gecko-dev.gitmc完整仓库不算小,具体大小还会随历史和平台文件变化。开始前给硬盘留出几十 GB 的余量,完整编译还会再生成一大坨对象文件,别等磁盘红了才发现咖啡已经泡好了。
网络不太稳定时,可以下载某个 revision 的源码压缩包,适合阅读和做一次性实验;只是它没有完整的 Mercurial 历史。要经常更新、切换版本或提交补丁,再选择hg clone,让版本管理工具替你记账。
本系列当前固定使用Firefox 151.0a1 Nightly(mozilla-central)的源码 revision:
264d49b61bdb9a078bdd4be90996d9f8deccbfc1在 MozillaBuild 终端中,可以使用curl断点续传下载源码压缩包:
cd/fcurl-C--L-ofirefox-source.zip"https://hg.mozilla.org/mozilla-central/archive/264d49b61bdb9a078bdd4be90996d9f8deccbfc1.zip"下载完成后,把压缩包解压到F:\mc,然后看一眼:F:\mc\moz.build和F:\mc\mach应该就在眼前。要是变成了F:\mc\mozilla-central-264d49b\moz.build,说明压缩包又套了一层目录,把里面那层源码目录当成真正的工作目录即可。
该 revision 的源码页面和压缩包地址分别是:
- Mercurial revision 264d49b61bdb9a078bdd4be90996d9f8deccbfc1
- 下载 Firefox 151.0a1 对应源码 ZIP
Nightly 归档按日期和构建时间排目录,同一个主版本可能出现好几次。打开 Nightly 归档 后,进入对应日期的mozilla-central目录,再用buildhub.json或mozinfo.json核对 revision。只看151.0a1这个版本号就猜提交,容易把“表兄弟”当成“本人”。
如果使用 Mercurial 完整仓库,文章要对应某个 Nightly 构建,应先从该构建目录中的buildhub.json或mozinfo.json读取源码 revision,再固定到该 revision:
cd/f/mchgidentify-i-n-bhgupdate-r<revision>对于本文的 Firefox 151.0a1,可直接执行:
hgupdate-r264d49b61bdb9a078bdd4be90996d9f8deccbfc1mozilla-central每天都在往前走,Searchfox 通常只给当前主线建索引,不会为每个 Nightly 都留一间独立展厅。所以 Searchfox 很适合导航,真正要对号入座时,还是以本地固定 revision 为准。
1.2 Windows 编译环境
先把下面几样工具准备好,Firefox 才不会在编译半路突然“缺这缺那”:
- Visual Studio 2022:安装“使用 C++ 的桌面开发”工作负载,确保包含 MSVC 编译器、Windows SDK、链接器和 ATL 等组件。官方下载:Visual Studio Community。
- MozillaBuild:提供 Mozilla 使用的 MSYS2 shell、Mercurial、Make 和其他辅助工具。官方下载:MozillaBuild。默认安装到
C:\mozilla-build\。 - 足够的硬件资源:建议 16 GB 或更多内存、SSD 和至少 80 GB 可用空间。首次完整编译可能持续较长时间,具体取决于 CPU、磁盘、网络和源码版本。
后面的命令是在 MozillaBuild 终端里跑的,不是在普通 PowerShell 里混搭。安装好后打开C:\mozilla-build\start-mozbuild.bat,接下来的操作都在这个窗口里完成。
1.3 Bootstrap 初始化
源码根目录里的mach可以理解成 Firefox 的总管。第一次开工前,先在 MozillaBuild 终端进入源码目录,叫它把依赖和工具链准备好:
cd/f/mc ./mach bootstrap交互提示里选Firefox for Desktop。Artifact Mode 会拿预编译的 C++ 组件来加速,改网页 UI 时很香,但要改 C++ 或 Rust 就不够用了。Mercurial 配置向导按自己的习惯来;不打算向 Mozilla 提交代码的话,提交相关选项可以跳过,构建系统遥测也可以关闭。
Bootstrap 做完后如果提示重启 shell,照做就好:关掉当前窗口,再运行一次C:\mozilla-build\start-mozbuild.bat。这样新工具链和环境变量才会真正上岗。
1.4 创建mozconfig
在源码根目录放一个叫mozconfig的文件,告诉构建系统这次想编译什么、要不要优化、开几路并行。下面这份配置适合先把完整版本跑起来:
ac_add_options --enable-application=browser ac_add_options --enable-optimize ac_add_options --disable-crashreporter ac_add_options --disable-updater ac_add_options --disable-tests mk_add_optionsMOZ_MAKE_FLAGS="-j8"-j8就是同时开 8 个编译任务,可以按 CPU 核心数和内存上下调整。内存不大的机器别一口气把数字拧到最大,否则编译器们会一起抢内存。要认真下断点,再换成--enable-debug并关闭优化,只是它会更慢,也更占地方。
旧项目里的固定MOZ_BUILD_DATE、指纹参数或自定义启动参数先别急着整段搬过来。确实需要可重复时间戳或实验配置时再加,并顺手写清楚它是干什么的,免得半年后连自己都要考古。
1.5 开始编译
先确认当前目录能看到moz.build和mach,然后让编译正式开跑:
cd/f/mc ./mach build首次完整编译可能从几十分钟到数小时不等,电脑配置不同,咖啡消耗量也不同。成功后终端会给出完成提示,产物通常会落在类似下面的对象目录里:
F:\mc\obj-x86_64-pc-windows-msvc\dist\bin\对象目录名称会随平台、编译器和配置变化,脚本里别把它写成永远不变的名字。只改 JavaScript、CSS 或其他前端资源时,可以考虑更快的构建方式:
./mach build faster它适合不需要重新编译 C++/Rust 的小改动,不能拿来冒充完整构建。
1.6 运行和打包
编译成功后,启动刚刚亲手造出来的本地版本:
./mach run这会启动源码树里的 Firefox,不会误打开系统已经安装的那个版本。以后改了 UI 或源码,重新跑./mach build,再启动一次就行。
需要生成可分发目录或omni.ja等打包产物时执行:
./mach package打包结果通常位于obj-*\dist\下。omni.ja不一定在普通编译完成后立即出现,它属于打包流程的产物。
1.7 增量编译与清理
修改少量文件后继续执行:
./mach build构建系统会尽量复用已有产物。若修改了mozconfig、切换了重要构建选项,或遇到难以解释的配置错误,可以清理对象目录后重新配置:
./mach clobber ./mach buildclobber会清掉编译缓存,下一次从头来过。执行前确认没有要保留的本地产物,毕竟它不是“撤销”按钮。
1.8 常见问题
- **路径过长:**先看看源码是不是乖乖待在
F:\mc,别又在外面套了好几层文件夹。 - **工具找不到:**确认终端是
start-mozbuild.bat打开的,并且 bootstrap 后重新开过 shell。 - **误选 Artifact Mode:**要编译 C++/Rust 时改用完整 Desktop 构建,必要时清理
obj-*再来一遍。 - **网络下载失败:**先检查代理、证书和磁盘空间,错误日志留着,它通常比“删了重下”更有用。
- **IDE 出现大量 include 错误:**别被红线吓到,Firefox 的 include 路径由构建系统管理;IDE 抱怨不代表
mach build一定失败。 - **修改后行为没变化:**确认改的是当前源码树,重新构建,并且确实运行了
./mach run启动的本地版本。
二、核心目录地图
不同版本会搬家、合并目录,下面这张图先当作“地铁线路图”来看,够我们入门和找方向:
mc/ ├── dom/ DOM、Web API、WebIDL 绑定和相关实现 ├── layout/ 布局、Frame 树和部分绘制流程 ├── gfx/ 图形、合成和 WebRender 相关代码 ├── js/ SpiderMonkey JavaScript 引擎 ├── netwerk/ HTTP、DNS、缓存、Cookie 和 Socket 等网络代码 ├── security/ 安全库、证书和安全策略 ├── widget/ 窗口、输入和平台抽象 ├── ipc/ 进程间通信基础设施 ├── browser/ Firefox 桌面 UI 和浏览器功能 ├── toolkit/ 多个 Mozilla 应用共用的 UI 与平台组件 ├── devtools/ 开发者工具 ├── storage/ 存储相关组件 ├── memory/ 内存分配和管理 ├── python/ 构建与开发辅助脚本 ├── testing/ 测试框架与测试工具 └── tools/ 分析、格式化和其他工程工具2.1 DOM、布局与图形
dom/负责网页脚本接触到的大量对象和 API,例如Document、Element、事件、Fetch、Worker 和 WebIDL 绑定。WebIDL 文件描述脚本可见的接口,构建过程会生成连接 JavaScript 与底层实现的代码;真正的行为通常还要继续追踪到对应的 C++ 或 Rust 文件。
layout/把 DOM 和样式信息转化为具有尺寸和位置的布局结构。Flex、Grid、表格、表单和文本排版等问题,通常会在这里找到入口。
gfx/覆盖绘制、合成、图像和 GPU 相关路径。现代 Firefox 大量使用 WebRender,但具体模块、后端和启用条件会随平台和版本变化。
2.2 JavaScript 引擎与网络层
js/中的 SpiderMonkey 负责解析、执行和优化 JavaScript,也包含垃圾回收、JIT 和 WebAssembly 等组件。
netwerk/是 Necko 网络栈的主要代码区域,涉及协议处理、DNS、连接、缓存、Cookie 和底层 Socket。一次页面加载未必会经过所有层:缓存命中、连接复用、代理、Service Worker 或 HTTP/3 都可能改变实际路径。
2.3 浏览器 UI
browser/和toolkit/中可以看到地址栏、标签页、下载、设置和主题等代码。Firefox 桌面 UI 大量使用 HTML/XML、CSS 和 JavaScript,但它同时受自定义元素、平台代码、权限和构建流程约束,因此不能简单理解为“修改网页 HTML 就能修改一切”。
三、Searchfox:先找入口,再读实现
Firefox 源码大到不适合从第一行读到最后一行,真的这么做,咖啡会先退休。Searchfox 可以按目录搜索、跳到符号定义、查看引用和关联文件。比较省力的走法是:
- 先从用户行为或 API 名称出发,找到公开入口。
- 查看入口调用的关键对象和线程/进程边界。
- 再追踪数据结构、异步回调和错误处理。
- 用本地构建、日志或测试验证自己的理解。
文章里的文件路径和符号名,发布前最好按目标 Firefox 版本再核对一遍;能链到固定版本的 Searchfox,就别让读者玩“猜路径”。
四、实战:修改一个本地实验构建
实验 1:修改本地品牌文案
Firefox 的品牌资源通常藏在品牌目录和 locale 资源里。不同版本会有些小搬家,这次我们直接在当前源码树里定位文件,再对自己的实验构建下手。
1. 进入源码目录
打开 MozillaBuild 终端,切换到本系列统一使用的源码目录:
cd/f/mcpwdlsmoz.build mach终端显示/f/mc,并列出moz.build、mach,说明已经站在正确的源码根目录里。
2. 查看原始品牌配置
本次实验使用 unofficial branding,文件位置是:
browser/branding/unofficial/locales/en-US/brand.ftl执行:
catbrowser/branding/unofficial/locales/en-US/brand.ftl可以看到当前版本的品牌字段,例如:
-brand-shorter-name = Nightly -brand-short-name = Nightly -brand-shortcut-name = Nightly -brand-full-name = Nightly -brand-product-name = Firefox这里的brand-short-name通常用于短名称,brand-full-name用于完整产品名称。不同版本的字段可能略有差异,修改时保留现有字段名即可。
3. 先做一个备份
改源码之前先留一份原文件,实验翻车时可以快速回滚:
cpbrowser/branding/unofficial/locales/en-US/brand.ftl\\browser/branding/unofficial/locales/en-US/brand.ftl.backuplsbrowser/branding/unofficial/locales/en-US/如果目录里同时出现brand.ftl和brand.ftl.backup,备份就完成了。
4. 修改品牌名称
用记事本打开文件:
notepad browser/branding/unofficial/locales/en-US/brand.ftl本次实验把短名称改成duoshi,完整名称改成duoshi Experimental Browser。修改后的关键字段如下:
-brand-shorter-name = duoshi -brand-short-name = duoshi -brand-shortcut-name = duoshi -brand-full-name = duoshi Experimental Browser字段名不要改,行首的-也要保留;这里只替换等号右侧的文案。保存后重新查看文件:
catbrowser/branding/unofficial/locales/en-US/brand.ftl5. 重新编译
回到 MozillaBuild 终端执行完整构建:
./mach build这次构建日志显示生成了 Windows 安装相关文件,并在最后给出:
Your build was successful! To run your build, run |mach run|日志中出现第三方代码的 warning 不一定代表构建失败,关键要看最后是否明确显示构建成功。如果终端最后出现 error,应保留完整错误上下文,不要只截取最后一行。
6. 启动本地构建
构建成功后执行:
./mach run随后会打开源码树对应的 Firefox。进入“关于”页面,可以看到窗口标题已经变成About duoshi Experimental Browser,版本信息显示为151.0a1;页面主体中的大标题仍显示Nightly,说明品牌字段并不是所有界面共用同一个资源入口。
这其实是一个很有价值的结果:窗口标题已经验证了我们的修改进入了构建产物,但关于页面的大标题仍有自己的品牌来源。源码分析不能只看到一个字段就宣布“全局改名成功”,还要继续追踪具体 UI 使用了哪个 Fluent 字段或资源。
7. 实验结果记录
| 项目 | 本次结果 |
|---|---|
| 源码版本 | Firefox 151.0a1 |
| 源码分支 | mozilla-central |
| 源码 revision | 264d49b61bdb9a078bdd4be90996d9f8deccbfc1 |
| 修改文件 | browser/branding/unofficial/locales/en-US/brand.ftl |
| 修改前 | Nightly |
| 修改后 | duoshi/duoshi Experimental Browser |
| 构建命令 | ./mach build |
| 运行命令 | ./mach run |
| 验证结果 | 窗口标题显示About duoshi Experimental Browser,关于页面主体仍显示Nightly |
这个实验完成了两件事:第一,证明源码修改确实进入了本地构建;第二,展示了 Firefox 品牌文案可能由多个资源入口共同控制。下一步如果想让关于页面的大标题也改变,就需要继续搜索它实际引用的 Fluent 字段,而不是盲目修改更多品牌文件。
实验 2:在 UI 中增加一个实验入口
这一次我们给 Firefox 工具栏加一个真正能点击的实验按钮。它不做复杂功能,只复用 Firefox 已有的cmd_newNavigatorTab命令:点击后打开一个新标签页。小功能很简单,但能完整走通“改 XHTML → 重新编译 → 定制工具栏 → 点击验证”这条链路。
1. 找到正确的工具栏文件
当前 Firefox 151.0a1 源码中,导航工具栏定义在:
browser/base/content/navigator-toolbox.inc.xhtml打开文件:
cd/f/mc notepad browser/base/content/navigator-toolbox.inc.xhtml在文件中找到:
<hboxid="nav-bar-customization-target"flex="1">这个hbox才是导航栏真正的可定制区域。按钮如果只放在外层的<toolbar id="nav-bar">中,可能会被加载,却不会出现在定制工具栏的项目列表里。
2. 备份原文件
cpbrowser/base/content/navigator-toolbox.inc.xhtml\\browser/base/content/navigator-toolbox.inc.xhtml.backup先留一份备份,XML 标签不小心少一个时,可以快速恢复。
3. 插入按钮代码
把下面代码放在nav-bar-customization-target开始标签之后:
<toolbarbuttonid="duoshi-experiment-button"class="toolbarbutton-1 chromeclass-toolbar-additional"label="打开实验标签页"aria-label="打开实验标签页"tooltiptext="打开一个新的实验标签页"removable="true"style="background-color:#4b2e83;color:white;border-radius:4px;"command="cmd_newNavigatorTab"/>这里的几个属性各有分工:id是唯一标识;label和aria-label提供可见或辅助阅读的名称;tooltiptext提供悬停提示;removable="true"让它能出现在定制工具栏列表;style给没有图标的实验按钮一个稳定的紫色背景;command则直接复用 Firefox 已有的新建标签页命令。
4. 检查按钮位置
保存后执行:
grep-n-A10-B5"duoshi-experiment-button"\\browser/base/content/navigator-toolbox.inc.xhtml确认它位于以下结构内部:
<hboxid="nav-bar-customization-target"flex="1"><toolbarbuttonid="duoshi-experiment-button".../>...</hbox>如果按钮位于这个hbox外面,Firefox 可能不会把它识别为可拖拽的工具栏项目。
5. 重新编译
修改的是浏览器 UI 的 XHTML 文件,可以先执行:
./mach build faster如果界面没有变化,再执行完整构建:
./mach build成功后应看到:
Your build was successful! To run your build, run |mach run|6. 启动本地构建
./mach run这会启动F:\mc中刚刚编译的本地版本。之前已经打开的旧窗口不会自动加载新的 XHTML 文件。
7. 打开工具栏定制页面
当前版本直接输入about:customizing可能显示“Problem loading page”,这不是编译失败,而是该地址不是当前版本有效的定制入口。
正确操作是:点击右上角菜单 →更多工具→定制工具栏。进入定制页面后,可以在下方项目区看到紫色的“打开实验标签页”项目。
8. 拖到顶部工具栏
将“打开实验标签页”从定制面板拖到顶部导航栏,然后点击右下角“完成”。
拖动成功后,按钮会出现在地址栏右侧附近。当前工具栏使用图标模式,按钮文字不一定一直显示;本实验特意设置了紫色背景,让它在未悬停时也能被识别。
9. 点击按钮验证
回到普通浏览器窗口,观察地址栏右侧的紫色按钮。鼠标移上去时会显示:
打开一个新的实验标签页点击按钮后,Firefox 会打开一个新的标签页。
10. 实验结果记录
| 项目 | 本次结果 |
|---|---|
| 源码版本 | Firefox 151.0a1 |
| 源码目录 | F:\mc |
| 修改文件 | browser/base/content/navigator-toolbox.inc.xhtml |
| 按钮 ID | duoshi-experiment-button |
| 显示文字 | 打开实验标签页 |
| 使用命令 | cmd_newNavigatorTab |
| 按钮颜色 | #4b2e83 |
| 构建命令 | ./mach build faster/./mach build |
| 运行命令 | ./mach run |
| 验证结果 | 按钮出现在定制工具栏并可拖到顶部,点击后打开新标签页 |
这个实验还展示了一个 UI 细节:按钮“存在”不等于用户“看得见”。没有图标的按钮在图标模式下可能只剩空白方块,所以我们用背景色和aria-label给它补上识别度。正式产品通常还会制作图标、加入 Fluent 本地化文本,并把内联样式移到主题 CSS 中。
小结
这一篇不是目录背诵比赛,重点是建立一套找代码的方法:先按功能分层,再用符号和调用关系缩小范围,最后用构建或日志验证。下一篇我们就沿着这张地图,去看看多进程架构怎么分工。
**下一篇预告:**为什么打开几个标签页后,系统中会出现多个 Firefox 进程?我们将区分 Parent、Content、GPU、Socket、RDD 和 Utility 等常见角色,并观察站点隔离和 IPC 如何协作。