从输入 URL 到页面展示完整流程
2024-09 1
下图是从输入 URL 到页面展示完整流程示意图
这里可以分为两个部分,一个是导航流程,一个是渲染流程,下面先介绍导航流程。
导航流程处理输入信息
- 用户回车后,浏览器会执行当前页面的
beforeunload
事件,用户可以通过beforeunload
事件来取消导航,让浏览器不再执行任何后续工作。 - 若是继续后续流程的话,浏览器进程接收到用户输入的内容,判断是URL还是搜索内容,如果是URL,浏览器进程便将该 URL 转发给网络进程;否则将使用默认的搜索引擎,将搜索内容合成新的URL。
开始导航
- 浏览器进程处理完用户输入信息后,便进入开始导航阶段,此阶段开始标志是“浏览器tab图标会变成加载状态”,但页面显示的依然是之前打开的页面内容
发起URL请求
- 开始导航之后,浏览器进程将URL发给网络进程,在网络进程中发起真正的请求。
- 网络进程首先查找本地缓存,有缓存的话便返回304,结束请求。
- 没有缓存则开始进入网络请求,第一步是进行DNS解析,查找域名对应的IP地址。
- 利用IP地址和服务器建立TCP连接,如果是HTTPS,则还要建立TLS连接。
- 建立好连接后,利用
Cookie
等构建好请求头信息,向服务器发起请求。
读取响应头
- 网络进程发完请求后,等待服务器返回数据,然后解析响应头信息。
- 响应头如果是301或者302重定向的话,则取响应头中
Location
字段,重新发起网络请求。 - 否则继续解析响应头数据类型字段
Content-Type
,如果是application/octet-stream
字节流类型,则开启下载进程。如果是text/html
,则需要通知浏览器进程开始准备渲染进程
准备渲染进程
- 浏览器进程接收到网络进程的准备渲染进程的消息后,会先判断返回的页面数据是同一站点数据。
- 如果是同一站点的数据,则复用之前的渲染进程,否则将创建一个新的渲染进程。
- 接着浏览器进程便发送“提交文档”消息到渲染进程;
提交文档
- 渲染进程接收到“提交文档”的消息后,会和网络进程建立传输数据的“管道”;
- 等文档数据传输完成之后,渲染进程会返回“确认提交”的消息给浏览器进程;
确认提交
- 浏览器进程收到渲染进程发回来的“确认提交”信息后,便开始移除之前旧的文档,更新浏览器界面状态,包括了安全状态、地址栏的 URL、前进后退的历史状态。
- 此时页面会进入白屏阶段,渲染进程开始工作。
解析页面和子资源的加载
- 渲染进程将“确认提交”的消息发回给浏览器进程后,便开始解析页面,同时加载子资源了。加载完毕后会向浏览器进程发送消息,告知浏览器进程新的页面已经加载完毕。
页面加载完成
- 浏览器进程收到页面加载完毕的消息后,便停止tab栏图标的加载状态,同时显示新的页面。
渲染流程较为复杂,总共分为构建DOM树、样式计算、布局、
构建DOM树
输入: HTML
解析过程:
<html>
<body>
<div>1</div>
<div>test</div>
</body>
</html>
以上述代码为例:通过分词器先将从网络进程传过来的字节流转换为一个个 Token,分为 Tag Token 和文本 Token,其中Tag Token 又分 StartTag 和 EndTag。然后通过Token 栈,一步一步构建DOM树。
- 如果压入到栈中的是 StartTag Token,HTML 解析器会为该 Token 创建一个 DOM 节点,然后将该节点加入到 DOM 树中,它的父节点就是栈中相邻的那个元素生成的节点。
-
如果分词器解析出来是文本 Token,那么会生成一个文本节点,然后将该节点加入到 DOM 树中,文本 Token 是不需要压入到栈中,它的父节点就是当前栈顶 Token 所对应的 DOM 节点。
如果是 EndTag 标签,比如是 EndTag div,HTML 解析器会查看 Token 栈顶的元素是否是 StarTag div,如果是,就将 StartTag div 从栈中弹出,表示该 div 元素解析完成。
- 按照同样的规则,一路解析,最终结果如下图所示:
输出:DOM树
具体的过程如下:
样式计算
输入:CSS、DOM
过程:将CSS解析styleSheets,并将样式表中的值标准化,最后计算出DOM树种每个节点的具体样式。
输出:每个节点的具体样式ComputedStyle
注意,在构建CSS树的时候,每个节点里的CSS属性都是按照的书写来加载的。后续CSS树和DOM树构建成布局树的时候,CSS的顺序就很重要,详情可以参考这篇文章
构建布局树
输入:DOM树、每个节点的样式
过程:结合DOM树和每个节点的具体样式,创建布局树,并根据布局树,计算计算每个元素的几何坐标位置,保存在布局树种。
输出:布局树。
创建图层树
输入:布局树
过程:为布局树中特定节点生成专用的图层。生成专用图层的条件为:拥有层叠上下文属性的元素、或者需要裁剪的地方。
输出:图层树。
图层绘制
输入:图层树
过程:将图层树种每个图层拆分层很多小的绘制指令,再把这些指令按照顺序组成一个待绘制列表。
输出:待绘制列表。
栅格化操作
输入:待绘制列表
过程:渲染主线程完成待绘制列表后,将待绘制列表提交给合成线程,合成线程先将图层分块,按照视口附近先后的顺序,将图块传给栅格化线程池,然后栅格化线程池将图块转换成像素位图。在转换成位图的过程中,通常会用到GPU加速。
输出:位图。
合成和显示
输入:图块转换后的位图。
过程:一旦所有图块都转换成位图后,合成线程会通知浏览器进程,浏览器进程会接受合成线程的位图,并在内存中合成完整的页面,最后将页面显示在屏幕上。
输出:屏幕上的页面。
重排
重排是指更新了元素的几何位置,例如改变元素的宽度、高度等,那么浏览器会触发该元素的样式计算,导致重新执行布局、解析之后的一系列子阶段,这个过程就叫重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。
重绘
重绘是指更新了元素的背景颜色,该操作不会影响布局,因为没有引起几何位置的变换,故而会直接进去绘制阶段,然后执行之后的一系列自操作。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。
直接合成
更改一个既不要布局也不要绘制的属性,渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做合成。
例如使用了 CSS 的 transform 来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。由于避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。
- 无目录