☰
拆解火狐(一):Firefox 源码全景地图
2026/10/16 3:03:04 网站建设 项目流程

文章简介: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-r264d49b61bdb9a078bdd4be90996d9f8deccbfc1

mozilla-central每天都在往前走,Searchfox 通常只给当前主线建索引,不会为每个 Nightly 都留一间独立展厅。所以 Searchfox 很适合导航,真正要对号入座时,还是以本地固定 revision 为准。

1.2 Windows 编译环境

先把下面几样工具准备好,Firefox 才不会在编译半路突然“缺这缺那”:

  1. Visual Studio 2022:安装“使用 C++ 的桌面开发”工作负载,确保包含 MSVC 编译器、Windows SDK、链接器和 ATL 等组件。官方下载:Visual Studio Community。
  2. MozillaBuild:提供 Mozilla 使用的 MSYS2 shell、Mercurial、Make 和其他辅助工具。官方下载:MozillaBuild。默认安装到C:\mozilla-build\。
  3. 足够的硬件资源:建议 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 build

clobber会清掉编译缓存,下一次从头来过。执行前确认没有要保留的本地产物,毕竟它不是“撤销”按钮。

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 可以按目录搜索、跳到符号定义、查看引用和关联文件。比较省力的走法是:

  1. 先从用户行为或 API 名称出发,找到公开入口。
  2. 查看入口调用的关键对象和线程/进程边界。
  3. 再追踪数据结构、异步回调和错误处理。
  4. 用本地构建、日志或测试验证自己的理解。

文章里的文件路径和符号名,发布前最好按目标 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.ftl

5. 重新编译

回到 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
源码 revision264d49b61bdb9a078bdd4be90996d9f8deccbfc1
修改文件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
按钮 IDduoshi-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 如何协作。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询