本系列为极客时间上的《浏览器工作原理与实践》专栏的学习笔记
浏览器架构变迁
起初浏览器为单进程架构,页面、插件、网络等其他模块都在一个进程下面工作(多个线程),只要有一个线程出现问题,就会导致整个浏览器崩溃。这种架构不稳定、不安全、不流畅。如下图所示:

后面架构升级为多进程架构,分为渲染进程、插件进程、网络进程、GPU 进程和浏览器主进程。每个页面和插件都有自己单独的进程,保证了独立性,互不影响,单个页面或者插件出问题之后,不会影响到整个浏览器,将其关闭就好。而且渲染进程被隔离在沙箱内,保证了系统的安全性。如下图所示:

未来浏览器的架构方向是面向服务的架构,也就是将浏览器的底层能力划分为一个个基础服务,每个服务在独立的进程内运行,互相通过 IPC 通信。构建一个更内聚、低耦合、易于扩展和维护的系统。如下图所示:

浏览器数据包传输
互联网的数据是通过数据包来传输的。如果发送的的数据过大,会拆分成一个个小的数据包进行传输,浏览器的网络传输是基于 TCP/IP 协议簇进行。
IP 协议位于网络层,主要的职责是将数据从一个主机地址传输到另一个主机地址,这里的主机地址就是指的 IP。传输方式是在基础数据包的基础上加上 IP 头信息,里面包含了源 IP 地址、目标 IP 地址等信息。
UDP (用户数据包协议),负责将应用程序通过端口号与网络数据包连接起来。IP 接收到数据包之后,UDP 通过端口号将数据包分发给对应的应用程序。UDP 的特点是:传输速度快,但是不保证传输可靠性(会丢包)。
TCP 协议是一个可靠、稳定、面向连接的传输层协议,它解决了 UDP 的缺点,特点如下:
提供重传机制,解决丢包的问题
大的数据包会拆分成若干个小数据包,接收端收到之后再按照序号拼接
一个完整的 TCP 生命周期包括:建立连接(三次握手)、数据传输、断开连接(四次挥手)
一个完整的 HTTP 请求过程
- 构建请求
- 构建请求行信息
- 查找缓存
- 页面数据本地缓存
- 缓存对应头字段:Cache-Control,不同的值带来不同的缓存生命周期
- 准备 IP 地址和端口
- IP 地址通过 DNS 查询获取,并且会优先查找本地的 DNS 缓存
- 等待 TCP 队列
- Chrome 对于每个域名只能分配 6 个 TCP 连接
- 建立 TCP 连接
- 发送 HTTP 请求
- 请求行、请求头、请求体
- 如有对应的 Cookie ,会携带,用于持久化登录态
- 等到服务端 HTTP 响应
- 响应行、响应头、响应体
- 断开连接/重定向
- 如果 HTTP 状态码为 301 或 302 ,浏览器需要重定向到另一个地址访问
从输入 URL 到页面展示完整
导航阶段
流程示意图:

用户输入请求信息
网络进程发起 URL 请求
服务器响应之后,浏览器进程开始准备渲染进程
- 如果 HTTP 响应码是 301 或者是 302 ,浏览器会有一个重定向的过程
- 响应头中的 Content-Type 决定浏览器如何处理响应内容,影响着后续流程,如果是 HTML 页面,那么 Content-Type 的值应该是 text/html
- 每打开一个页面,浏览器都会准备一个新的进程,有一种情况例外,就是新打开的页面和之前的页面属于同一站点时,这时候会复用前一个进程
渲染进程准备好之后,就会通知浏览器进程已经做好展示页面状态的准备,这个过程称为“提交文档”
浏览器进程接收到提交文档的消息后,便会移除旧的文档,同时通知渲染进程文档已提交,渲染进程进入解析页面阶段
这个过程就叫做 “导航”
渲染流程

按照渲染的时间顺序,渲染流水线可以分为如下几个子阶段:
构建 DOM 树 => 样式计算 => 布局阶段 => 分层 => 绘制 => 分块 => 光栅化 => 合成
每个子阶段都可以看成是一个函数,接收输入参数,进行本阶段的处理,然后输出相应的处理结果给下一个子阶段。多个子阶段构成一条渲染流水线。
1. 构建 DOM 树
将简单的 HTML 代码,经由解析器解析,最终转换为一个树状结构,包含节点和内容,方便接下来流程的处理。
2. 样式计算
为了将 CSS 文件转换成浏览器能够理解的结构,先要进行一步转换的操作,解析 CSS 文件,输出 styleSheets
接下来是将CSS 的属性值标准化
- 然后根据 CSS 继承和层叠的规则,计算每个 DOM 节点的样式
3. 布局阶段
计算 DOM 树中可见节点的几何位置的过程,就叫做布局。
- 创建布局树,构建一棵只包含可见元素的布局树
- 布局计算,计算 DOM 树节点的坐标位置
4. 分层
为了完成页面很多复杂的效果,渲染引擎会为一些特定的节点生成对应的图层,最终形成一棵图层树。满足以下两点中的任意一点都会形成一个对应的图层:
- 拥有层叠上下文属性(定位属性、透明度定义、CSS 滤镜)
- 需要裁剪的地方(内容溢出容器,就会发生裁剪)
5. 图层绘制
图层的绘制会被分解成一个个绘制指令,形成绘制列表
6. 栅格化(raster)操作
当图层的绘制列表准备好之后,渲染线程的主线程会将绘制列表提交(commit)给合成线程。
合成线程将图层划分为图块,视口附近的图块会被优先转化为位图,生成位图的操作是由栅格化来进行的。栅格化,就是将图块转换为位图。
渲染引擎维护了一个栅格化的线程池,所有的图块栅格化都是在线程池中进行的。
一般栅格化线程都会由 GPU 来加速进行,生成的位图会保存在 GPU 的内存中。

7. 合成和显示
所有的图块光栅化之后,合成线程会发出一个绘制图块的命令-“DrawQuad”,提交给浏览器进程。
浏览器进程里有一个叫 viz 的组件,用来接收 DrawQuad 命令,根据这个命令,将页面内容绘制到内存中,最后再显示在屏幕上。
至此,用户就可以看见最终的页面了。