从输入 URL 到页面展示完整流程

2024-09 1

下图是从输入 URL 到页面展示完整流程示意图

92d73c75308e50d5c06ad44612bcb45d.png

这里可以分为两个部分,一个是导航流程,一个是渲染流程,下面先介绍导航流程。

导航流程

处理输入信息

  1. 用户回车后,浏览器会执行当前页面的beforeunload事件,用户可以通过 beforeunload 事件来取消导航,让浏览器不再执行任何后续工作。
  2. 若是继续后续流程的话,浏览器进程接收到用户输入的内容,判断是URL还是搜索内容,如果是URL,浏览器进程便将该 URL 转发给网络进程;否则将使用默认的搜索引擎,将搜索内容合成新的URL。

开始导航

  1. 浏览器进程处理完用户输入信息后,便进入开始导航阶段,此阶段开始标志是“浏览器tab图标会变成加载状态”,但页面显示的依然是之前打开的页面内容

发起URL请求

  1. 开始导航之后,浏览器进程将URL发给网络进程,在网络进程中发起真正的请求。
  2. 网络进程首先查找本地缓存,有缓存的话便返回304,结束请求。
  3. 没有缓存则开始进入网络请求,第一步是进行DNS解析,查找域名对应的IP地址。
  4. 利用IP地址和服务器建立TCP连接,如果是HTTPS,则还要建立TLS连接。
  5. 建立好连接后,利用Cookie等构建好请求头信息,向服务器发起请求。

读取响应头

  1. 网络进程发完请求后,等待服务器返回数据,然后解析响应头信息。
  2. 响应头如果是301或者302重定向的话,则取响应头中Location字段,重新发起网络请求。
  3. 否则继续解析响应头数据类型字段Content-Type,如果是application/octet-stream字节流类型,则开启下载进程。如果是text/html,则需要通知浏览器进程开始准备渲染进程

准备渲染进程

  1. 浏览器进程接收到网络进程的准备渲染进程的消息后,会先判断返回的页面数据是同一站点数据。
  2. 如果是同一站点的数据,则复用之前的渲染进程,否则将创建一个新的渲染进程。
  3. 接着浏览器进程便发送“提交文档”消息到渲染进程;

提交文档

  1. 渲染进程接收到“提交文档”的消息后,会和网络进程建立传输数据的“管道”;
  2. 等文档数据传输完成之后,渲染进程会返回“确认提交”的消息给浏览器进程;

确认提交

  1. 浏览器进程收到渲染进程发回来的“确认提交”信息后,便开始移除之前旧的文档,更新浏览器界面状态,包括了安全状态、地址栏的 URL、前进后退的历史状态。
  2. 此时页面会进入白屏阶段,渲染进程开始工作。

解析页面和子资源的加载

  1. 渲染进程将“确认提交”的消息发回给浏览器进程后,便开始解析页面,同时加载子资源了。加载完毕后会向浏览器进程发送消息,告知浏览器进程新的页面已经加载完毕。

页面加载完成

  1. 浏览器进程收到页面加载完毕的消息后,便停止tab栏图标的加载状态,同时显示新的页面。
渲染流程

渲染流程较为复杂,总共分为构建DOM树、样式计算、布局、

构建DOM树

输入: HTML

解析过程

<html>
<body>
    <div>1</div>
    <div>test</div>
</body>
</html>

以上述代码为例:通过分词器先将从网络进程传过来的字节流转换为一个个 Token,分为 Tag Token 和文本 Token,其中Tag Token 又分 StartTag 和 EndTag。然后通过Token 栈,一步一步构建DOM树。

image-20240508202643016.png

  1. 如果压入到栈中的是 StartTag Token,HTML 解析器会为该 Token 创建一个 DOM 节点,然后将该节点加入到 DOM 树中,它的父节点就是栈中相邻的那个元素生成的节点。

image-20240508203055859.png

  1. 如果分词器解析出来是文本 Token,那么会生成一个文本节点,然后将该节点加入到 DOM 树中,文本 Token 是不需要压入到栈中,它的父节点就是当前栈顶 Token 所对应的 DOM 节点。

    如果是 EndTag 标签,比如是 EndTag div,HTML 解析器会查看 Token 栈顶的元素是否是 StarTag div,如果是,就将 StartTag div 从栈中弹出,表示该 div 元素解析完成。

image-20240508203142462.png

  1. 按照同样的规则,一路解析,最终结果如下图所示:

aabf14cde38b058c5203195db82ec22e.png

输出:DOM树

125849ec56a3ea98d4b476c66c754f79.png
具体的过程如下:

样式计算

输入:CSS、DOM

过程:将CSS解析styleSheets,并将样式表中的值标准化,最后计算出DOM树种每个节点的具体样式。

输出:每个节点的具体样式ComputedStyle

fe9a0ea868dc02a3c4a59f6080aa80b4.png

注意,在构建CSS树的时候,每个节点里的CSS属性都是按照的书写来加载的。后续CSS树和DOM树构建成布局树的时候,CSS的顺序就很重要,详情可以参考这篇文章

构建布局树

输入:DOM树、每个节点的样式

过程:结合DOM树和每个节点的具体样式,创建布局树,并根据布局树,计算计算每个元素的几何坐标位置,保存在布局树种。

输出:布局树。

8e48b77dd48bdc509958e73b9935710e.png

创建图层树

输入:布局树

过程:为布局树中特定节点生成专用的图层。生成专用图层的条件为:拥有层叠上下文属性的元素、或者需要裁剪的地方。

输出:图层树。

e8a7e60a2a08e05239456284d2aa4061.png

图层绘制

输入:图层树

过程:将图层树种每个图层拆分层很多小的绘制指令,再把这些指令按照顺序组成一个待绘制列表。

输出:待绘制列表。

栅格化操作

输入:待绘制列表

过程:渲染主线程完成待绘制列表后,将待绘制列表提交给合成线程,合成线程先将图层分块,按照视口附近先后的顺序,将图块传给栅格化线程池,然后栅格化线程池将图块转换成像素位图。在转换成位图的过程中,通常会用到GPU加速。

输出:位图。

a8d954cd8e4722ee03d14afaa14c3987.png

合成和显示

输入:图块转换后的位图。

过程:一旦所有图块都转换成位图后,合成线程会通知浏览器进程,浏览器进程会接受合成线程的位图,并在内存中合成完整的页面,最后将页面显示在屏幕上。

输出:屏幕上的页面。

image-20240506095507951.png

重排

重排是指更新了元素的几何位置,例如改变元素的宽度、高度等,那么浏览器会触发该元素的样式计算,导致重新执行布局、解析之后的一系列子阶段,这个过程就叫重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。

b3ed565230fe4f5c1886304a8ff754e5.png

重绘

重绘是指更新了元素的背景颜色,该操作不会影响布局,因为没有引起几何位置的变换,故而会直接进去绘制阶段,然后执行之后的一系列自操作。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。

3c1b7310648cccbf6aa4a42ad0202b03.png

直接合成

更改一个既不要布局也不要绘制的属性,渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做合成。

024bf6c83b8146d267f476555d953a2c.png

例如使用了 CSS 的 transform 来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。由于避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。