系列第一篇,目的是建立第一张 Chromium 架构地图。 写给想开始读浏览器源码、但不知道从哪里下手的人,也写给未来复习的自己。
为什么先画一张地图?
最近在系统学 Chromium 源码。
Chromium 是 Chrome 浏览器的开源内核,也是大部分基于 Blink 的浏览器的基础。它的源码非常大,光 src/ 目录下就有几百个子目录,涉及的 C++ 类动辄几千个。
一开始我就犯了一个错误:从某个函数开始追,越追越深,最后忘了自己最开始要解决什么问题。
后来我意识到,读大型工程需要先有一张地图。
这张地图的核心问题只有一个:
用户输入一个 URL 后,Chromium 里大概有哪些模块参与,它们分别在做什么?
这篇文章就是我的第一张地图。不追求全面,只追求能复述。
Chromium 是一套协作系统,不是一个大程序
先给一个整体印象。
我目前把 Chromium 大致分成这几层:
| 层次 | 职责 | 关键目录 |
|---|---|---|
| 产品层 | UI、Tab、地址栏、平台接入 | chrome/、android_webview/ |
content/ |
页面、导航、frame、renderer process 调度 | content/ |
| Blink + V8 | HTML/CSS/DOM 解析,JavaScript 执行 | third_party/blink/、v8/ |
| Network | 资源加载 | services/network/、net/ |
| cc / Viz / GPU | 合成、聚合、屏幕显示 | cc/、components/viz/、gpu/ |
| Mojo | 跨进程 IPC 通信基础设施 | mojo/ |
base/ |
任务调度、回调、WeakPtr、日志等 | base/ |
但这些模块不是简单的上下级,而是围绕"页面加载和显示"这条运行时链路协作的。

多进程:理解 Chromium 的第一道门
看 Chromium 源码之前,最值得先弄清楚一件事:
Chromium 为什么要多进程?
答案不是"为了让架构更优雅",而是现实需要。
网页内容不可信。渲染引擎可能崩溃。如果所有网页都跑在同一个进程里:
text
一个页面崩溃
-> 渲染引擎崩
-> 整个浏览器崩
-> 所有 tab 一起没了
所以 Chromium 把浏览器拆开:
text
Browser Process <- 浏览器的"可信控制中心"
UI、tab、导航、权限、进程管理
↕ Mojo / IPC
Renderer Process <- 网页内容的"隔离执行环境"
Blink、V8、DOM、layout、paint
↕ Mojo / IPC
Network / GPU / Service Process
资源加载、图形输出、设备能力
这里有一个命名规律很有用:
text
带 Host 的对象,一般是 Browser 侧代表 renderer 里某个东西。
比如 RenderProcessHost 代表一个 renderer process,RenderFrameHost 代表 renderer 里某个 frame。Browser 侧通过这些 Host 对象和 Mojo / IPC 跟 renderer 通信,而不是直接拿对象指针。
明白了多进程模型,后面很多 Chromium 的设计就顺了。
URL 到首帧:八步
接下来是主线。以用户在地址栏输入 https://example.com 为例,Chromium 内部大概会经历这些阶段:
text
用户输入 URL
↓
[1] 产品层:整理打开意图
↓
[2] content:创建 NavigationRequest,组织导航
↓
[3] Network:加载主资源 HTML
↓
[4] CommitNavigation:Browser → Renderer 交接
↓
[5] Blink + V8:解析 HTML,执行 JS,加载子资源
↓
[6] Blink:style → layout → paint
↓
[7] cc:合成,生成 CompositorFrame
↓
[8] Viz / GPU:present 到屏幕
逐步看一下每一段。
1 产品层:表达"我要打开这个 URL"
用户输入 URL 时,最先接手的是产品层,不是 Blink 或 Network。
text
PC Chrome → chrome/
Android WebView → android_webview/
产品层负责地址栏、Tab、菜单、权限弹窗这些用户能直接看到的东西。它不解析 HTML,也不执行 JavaScript。
它的核心职责只是把用户操作翻译成一个意图交给 content:
text
请在这个 WebContents 里打开这个 URL。
地址栏的输入可能是 URL、搜索词,也可能是历史记录或自动补全结果,产品层需要先判断最终打开什么。
2 content:一次导航的调度中心
产品层把意图交给 content 后,真正的导航才开始。
我目前对 content 的一句话理解:
content把产品层的意图,变成浏览器内核里的页面运行流程。
它要负责这次导航的全局调度:
- 哪个
WebContents承载这个页面。 - 这是新导航、刷新,还是历史跳转。
- 是否需要新的 renderer process。
- 旧页面如何退出,新文档如何接管。
NavigationRequest 是这次导航的"任务单",记录 URL、目标 frame、发起来源、redirect 状态、响应信息、commit 状态等。
第一轮可以先记几个源码入口:
text
NavigationControllerImpl::LoadURLWithParams
NavigationRequest::Create
NavigationRequest::BeginNavigation
这里最重要的一个认知是:
Navigation 不是单纯的网络请求,而是一次页面跳转从发起到 commit 的生命周期管理。
它不只要"下载 HTML",还要处理安全策略检查、redirect、下载分流、错误页、目标 frame、renderer process 选择等等问题。
3 Network:拿回主资源
导航进入加载阶段后,需要拿到主资源,也就是主 HTML 文档。
主资源加载的大致路径:
text
NavigationRequest
→ NavigationURLLoader
→ ResourceRequest
→ URLLoaderFactory → URLLoader
→ Network Service
→ net(DNS、TCP、TLS、HTTP、缓存、代理...)
→ response headers + body stream
注意这里 Network 返回的不是一个字符串,而是两部分:
- response headers:让上层先判断响应类型,比如状态码、MIME type、是否 redirect、是否触发下载。
- body stream:后续 Blink 才真正消费的 HTML 字节流。
services/network 负责资源加载服务的入口和策略,net 负责更底层的网络能力。
子资源(CSS、JS、图片等)不是在这里加载的,而是 Blink 解析 HTML 时自己去发起,最终也走同一套 Network Pipeline。
4 CommitNavigation:Browser 和 Renderer 的交接点
拿到主资源响应之后,并不是直接把 HTML 交给 Blink 就行了。
content 还需要决定:
text
提交到哪个 frame?
使用哪个 RenderFrameHost?
是否需要换 renderer process?
旧页面如何退出?
CommitNavigation 是我第一轮重点记的分界线:
text
commit 前:Browser Process / content 主导导航、检查、加载、进程和 frame 选择。
commit 后:Renderer Process / Blink 开始创建 Document、解析 HTML、加载子资源和渲染。
网络响应回来 ≠ 新页面已经生效。只有 content 通过 Mojo IPC 把 commit 参数和 body stream 发给 renderer,Blink 才真正开始处理新 Document。
5 Blink + V8:解析 HTML,执行 JS,加载子资源
Renderer 收到 commit 后,Blink 创建 Document / DocumentLoader,开始消费 HTML body stream:
text
HTML bytes → HTML parser → tokens → DOM nodes → DOM tree
解析过程中,Blink 会发现 CSS、JS、图片等子资源,再次触发 Network Pipeline 去加载。
Blink 和 V8 的分工:
text
Blink:Web 文档结构、样式、布局、绘制语义、Web API。
V8:JavaScript 运行时,脚本编译和执行。
JavaScript 不是孤立地在 V8 里运行。网页里的 JS 通常通过 Blink 暴露的 DOM / Web API 影响页面。比如:
js
document.querySelector("h1").innerText = "Hello Chromium";
执行过程可以这样理解:
text
V8 执行 JS
→ Blink 提供 DOM / Web API
→ JS 修改 DOM
→ Blink 更新页面状态
→ 触发后续 style / layout / paint
6 Blink:style → layout → paint
当 DOM 和 CSS 规则都准备好了(可能已经被 JS 修改过),Blink 进入渲染计算:
style calculation:算出每个元素最终应用什么样式(computed style)。
layout:算出每个元素放在哪里、占多大(layout tree + geometry)。
paint:决定每个东西怎么画(背景、边框、文字、图片...),生成绘制指令列表。
这里要记一个点:
paint records 还不是像素,只是"应该怎么画"的记录。
真正产生像素要到合成和显示阶段。
7 cc:合成,生成 CompositorFrame
绘制记录接下来进入合成体系(cc):
text
paint records
→ layerization(哪些内容独立成层)
→ cc layer tree
→ raster / tiles(栅格化为纹理)
→ DrawQuads
→ CompositorFrame
几个值得注意的点:
- DOM tree ≠ layer tree。Chromium 会按需把内容提升为独立层,比如有动画的元素、video、canvas、position:fixed、滚动区域。
- 页面不会一次性光栅化成一张大图。Chromium 分 tile 并行处理,视口外的内容可以懒处理。
CompositorFrame是 renderer 提交给 Viz 的帧数据,但还不是最终屏幕画面。
8 Viz / GPU:present 到屏幕
最终屏幕画面可能来自多个来源:主页面、跨进程 iframe、视频 surface、浏览器 UI......
Viz 负责统一聚合这些 CompositorFrame:
text
CompositorFrame(来自多个 renderer)
→ Surface(每个来源对应一个 Surface)
→ SurfaceAggregator(聚合为最终帧)
→ AggregatedFrame
→ SkiaRenderer / GPU
→ SwapBuffers / present
→ 屏幕
到这一步,用户才真正看到页面首帧。
用数据形态串一遍
比起记模块名,我更喜欢追"数据在链路里的形态变化",这样不容易混乱:
text
用户输入文本
→ 最终 URL
→ LoadURLParams
→ NavigationRequest
→ ResourceRequest
→ response headers + body stream
→ commit params + body stream
→ Document / DocumentLoader
→ HTML tokens → DOM tree
→ CSS rules → computed style
→ V8 执行 JS / DOM mutation
→ layout geometry
→ paint records / PaintArtifact
→ composited layers → tiles / raster resources
→ DrawQuads → CompositorFrame
→ Surface → AggregatedFrame
→ GPU buffer → screen pixels
这条数据变化链路贯穿了产品层、content、Network、Blink、V8、cc、Viz、GPU。
我总结的 Chromium 源码阅读方法
写到这里,我觉得第一篇最重要的收获不是记住了哪些类,而是建立了一种读 Chromium 的方法。
以前我会问:
text
这个类是干什么的?
这个函数在哪里被调用?
这段代码具体怎么跑?
这些问题当然要问,但如果一开始就钻进去,很容易忘了自己在哪个链路里。
现在我会先问五个问题:
| 问题 | 作用 |
|---|---|
| 它接收什么输入? | 定位它在链路中的起点 |
| 它产出什么? | 定位它的终点和交接对象 |
| 它在哪个进程/线程? | 定位它的执行环境 |
| 它把事情交给谁? | 理解 handoff,而不只是 helper |
| 出问题怎么反查? | 从症状反推到模块,而不是从代码猜症状 |
这套方法的核心是:
先理解责任边界,再理解内部实现。 先能讲清主线,再深入状态机和分支。
对于 Chromium 这类大型工程,这比从头开始追调用栈更适合第一轮入门。
这篇先不展开什么
说清楚边界很重要。以下内容我目前都只是"知道有这个东西",还没有深入:
- BFCache / Prerender / FencedFrame
SiteInstance/RenderFrameHost生命周期状态机- Network 的 Cookie、Cache、CORS、HTTP/2、QUIC
- V8 Ignition / TurboFan / GC
- cc scheduler、raster priority
- Viz surface lifetime、overlay 细节
- FP / FCP / LCP 等性能指标的精确定义
第一轮目标只是:
能从 URL 输入讲到首帧显示,知道每个模块在链路里大概负责什么。
最后:一段话总结
用户输入 URL 后,产品层把意图传给
content;content创建NavigationRequest,完成导航调度和主资源加载;Network Service /net取回 HTML 响应;content通过CommitNavigation把响应交给 renderer;Blink 创建 Document、解析 HTML、加载 CSS / JS / 图片;V8 执行 JavaScript 并可能修改 DOM;Blink 计算 style / layout / paint;cc合成并生成CompositorFrame;Viz 聚合多个 Surface;GPU 最终 present 到屏幕。
Chromium 打开一个页面,不是简单的"下载 HTML 然后显示",而是一条跨多个进程、多个模块的运行时协作链路。
下一篇预告
下一篇我打算把这条链路里最值得深入的一段单独拆开看:
Navigation Pipeline:一次 URL 如何变成新页面?
重点会看:
NavigationRequest到底记录了什么。- Navigation 为什么不只是网络请求。
NavigationURLLoader如何进入资源加载栈。CommitNavigation为什么是 Browser 和 Renderer 的关键分界线。
这是一篇学习笔记,不是官方文档。第一轮建立主线,后续持续修正。如果有理解偏差欢迎指出 : ) 写给想开始读浏览器源码、但不知道从哪里下手的人,也写给未来复习的自己。