:Firefox 源码全景地图)
文章简介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 Nightlymozilla-central的源码 revision264d49b61bdb9a078bdd4be90996d9f8deccbfc1在 MozillaBuild 终端中可以使用curl断点续传下载源码压缩包cd/fcurl-C--L-ofirefox-source.ziphttps://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 对应源码 ZIPNightly 归档按日期和构建时间排目录同一个主版本可能出现好几次。打开 Nightly 归档 后进入对应日期的mozilla-central目录再用buildhub.json或mozinfo.json核对 revision。只看151.0a1这个版本号就猜提交容易把“表兄弟”当成“本人”。如果使用 Mercurial 完整仓库文章要对应某个 Nightly 构建应先从该构建目录中的buildhub.json或mozinfo.json读取源码 revision再固定到该 revisioncd/f/mchgidentify-i-n-bhgupdate-rrevision对于本文的 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-applicationbrowser 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 浏览器 UIbrowser/和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源码 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在文件中找到hboxidnav-bar-customization-targetflex1这个hbox才是导航栏真正的可定制区域。按钮如果只放在外层的toolbar idnav-bar中可能会被加载却不会出现在定制工具栏的项目列表里。2. 备份原文件cpbrowser/base/content/navigator-toolbox.inc.xhtml\\browser/base/content/navigator-toolbox.inc.xhtml.backup先留一份备份XML 标签不小心少一个时可以快速恢复。3. 插入按钮代码把下面代码放在nav-bar-customization-target开始标签之后toolbarbuttonidduoshi-experiment-buttonclasstoolbarbutton-1 chromeclass-toolbar-additionallabel打开实验标签页aria-label打开实验标签页tooltiptext打开一个新的实验标签页removabletruestylebackground-color:#4b2e83;color:white;border-radius:4px;commandcmd_newNavigatorTab/这里的几个属性各有分工id是唯一标识label和aria-label提供可见或辅助阅读的名称tooltiptext提供悬停提示removabletrue让它能出现在定制工具栏列表style给没有图标的实验按钮一个稳定的紫色背景command则直接复用 Firefox 已有的新建标签页命令。4. 检查按钮位置保存后执行grep-n-A10-B5duoshi-experiment-button\\browser/base/content/navigator-toolbox.inc.xhtml确认它位于以下结构内部hboxidnav-bar-customization-targetflex1toolbarbuttonidduoshi-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 如何协作。