浏览器工作流程『从输入 URL 到页面展示』

161 阅读5分钟

cooly_romantic_serene_blue_vast_luxurious_A_man_and_a_woman_emb_10cdd419-f675-4fa4-b049-a3d9edc27af2.png

先说点题外话,上面这种midjourney+ chatgpt生成的图效果还挺不错的。言归正传,由于目前基本没有系统介绍浏览器知识的文档,而且网上看到的很多相关文档都是比较早期的,很多内容都不太适合新版的浏览器了。 这里将浏览器知识和前端系统下结合起来讲一下这个经典的面试题。

整体流程

  1. 用户输入
  2. URL请求
  3. 准备渲染进程
  4. 提交文档
  5. 渲染流水线

用户输入

  1. 用户在地址栏按下回车,检查输入(关键字 or 符合 URL 规则),组装完整 URL;

  2. 回车前,当前页面执行 onbeforeunload 事件;

beforeunload 事件允许页面在退出之前执行一些数据清理操作,还可以询问用户是否要离开当前页面,比如当前页面可能有未提交完成的表单等情况,因此用户可以通过 beforeunload 事件来取消导航,让浏览器不再执行任何后续工作。

  1. 浏览器进入加载状态。

URL请求

  1. 浏览器进程通过 IPCURL 请求发送至网络进程;

  2. 查找资源缓存(有效期内);

  3. DNS 解析(查询 DNS 缓存);

  4. 进入 TCP 队列(单个域名 TCP 连接数量限制);

  • http1.1,同一个域名同时最多只能建立 6 个 TCP 连接,多了会进入等待状态。
  • http2.0,是可以并行请求资源的,浏览器只会为每个域名维护一个tcp连接。
  1. 创建 TCP 连接(三次握手);

  2. HTTPS 建立 TLS 连接(client hello, server hello, pre-master key 生成『对话密钥』);

  3. 发送 HTTP 请求(请求行[方法、URL、协议]、请求头 Cookie 等、请求体 POST);

  4. 接受请求(响应行[协议、状态码、状态消息]、响应头、响应体等); - 状态码 301 / 302,根据响应头中的 Location 重定向; - 状态码 200,根据响应头中的 Content-Type 决定如何响应(下载文件、加载资源、渲染 HTML)。

准备渲染进程

根据是否同一站点(相同的协议和根域名),决定是否复用渲染进程。

提交文档

  1. 浏览器进程接受到网路进程的响应头数据,向渲染进程发送『提交文档』消息;

  2. 渲染进程收到『提交文档』消息后,与网络进程建立传输数据『管道』;

  3. 传输完成后,渲染进程返回『确认提交』消息给浏览器进程;

  4. 浏览器接受『确认提交』消息后,移除旧文档、更新界面、地址栏,导航历史状态等;

  5. 此时标识浏览器加载状态的小圆圈,从此前 URL 网络请求时的逆时针选择,即将变成顺时针旋转(进入渲染阶 段)。

渲染流水线

  • 构建 DOM
  1. 输入:HTML 文档;

  2. 处理:HTML 解析器解析;

  3. 输出:DOM 数据解构。

  • 样式计算

bc93df7b8d03b2675f21e1d9e4e1407c.webp 从图中可以看出,CSS 样式来源主要有三种:通过 link 引用的外部 CSS 文件

  1. 输入:CSS 文本;

  2. 处理:属性值标准化,每个节点具体样式(继承、层叠);

  3. 输出:styleSheets(CSSOM)。

HTML 文件一样,浏览器也是无法直接理解这些纯文本的 CSS 样式,所以当渲染引擎接收到 CSS 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构——styleSheets

  • 布局(DOM 树中元素的计划位置)
  1. DOM & CSSOM 合并成渲染树;

  2. 布局树(DOM 树中的可见元素);

  3. 布局计算。

微信截图_20230320115722.png 上图就是渲染的一部分步骤了。

  • 分层
  1. 特定节点生成专用图层,生成一棵图层树(层叠上下文、Clip,类似 PS 里的图层);

  2. 拥有层叠上下文属性(明确定位属性、透明属性、CSS 滤镜、z-index 等)的元素会创建单独图层;

  3. 没有图层的 DOM 节点属于父节点图层;

  4. 需要剪裁的地方也会创建图层。

  • 绘制指令
  1. 输入:图层树;

  2. 渲染引擎对图层树中每个图层进行绘制;

  3. 拆分成绘制指令,生成绘制列表,提交到合成线程;

  4. 输出:绘制列表。

  • 分块
  1. 合成线程会将较大、较长的图层(一屏显示不完,大部分不在视口内)划分为图块(tile, 256256, 512512)。
  • 光栅化(栅格化)
  1. 在光栅化线程池中,将视口附近的图块优先生成位图(栅格化执行该操作);

  2. 快速栅格化:GPU 加速,生成位图(GPU 进程)。

  • 合成绘制
  1. 绘制图块命令——DrawQuad,提交给浏览器进程;

  2. 浏览器进程的 viz 组件,根据DrawQuad命令,绘制在屏幕上。

975fcbf7f83cc20d216f3d68a85d0f37.webp 上图就是完整的渲染流程了

相关概念

更新了元素的几何属性(重排)

b3ed565230fe4f5c1886304a8ff754e5.webp 从上图可以看出,如果你通过 JavaScript 或者 CSS 修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。

更新元素的绘制属性(重绘)

3c1b7310648cccbf6aa4a42ad0202b03.webp 从图中可以看出,如果修改了元素的背景颜色,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。

直接合成阶段

024bf6c83b8146d267f476555d953a2c.webp 在上图中,我们使用了 CSS 的 transform 来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。