阅读 372

浏览器系列 | 人气面试题 : 从输入URL到页面展示发生了什么?

前置知识

浏览器的多进程架构

最新的 Chrome 浏览器包括:1 个浏览器(Browser)主进程、1 个 GPU 进程、1 个网络(NetWork)进程、多个渲染进程和多个插件进程。

进程 作用
浏览器(Browser)进程 主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
渲染(Render)进程 默认采用 Process-per-site-instance 模式。核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页, 排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中。出于安全考虑,渲染进程都是运行在沙箱模式下。
插件(Plugin)进程 主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保 证插件进程崩溃不会对浏览器和页面造成影响。
GPU 进程 其实,Chrome 刚开始发布的时候是没有 GPU 进程的。而 GPU 的使用初衷 是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。最后,Chrome 在其多进程架构上也引入了 GPU 进程。
网络(NetWork)进程 主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面 的,直至最近才独立出来,成为一个单独的进程。

【 Process-per-site-instance 】多个页面满足 1. 一级域名 + 二级域名相同(例如: z.baidu.com 和 b.baidu.com) 2. 用户通过 < a target="_blank" > 这种方式点击打开的新页面 或者 JavaScript code 打开的新页面(比如 window.open) 这两个条件,即共享同一个 renderer 进程

【 沙箱模式 】将网页的运行限制在一个特定的环境中,也就是一个沙箱中,使它只能访问有限的功能。那么,即使网页工作的渲染引擎被攻击,它也不能够获取渲染引擎工作的主机系统中的任何权限,这一思想就是沙箱模型。 【 参考 】浏览器沙箱模型

开头几张图

盗来的图

URL

原创的图

从输入URL到页面展示流程图

正文开始 —— 以进程为维度展开

1. 浏览器进程:用户输入

当用户在地址栏中输入一个查询关键字时,地址栏会判断输入的关键字是搜索内容,还是请求的URL

  • 如果是搜索内容,地址栏会使用浏览器默认的搜索引擎,来合成新的带搜索关键字的 URL。
  • 如果判断输入内容符合 URL 规则,比如输入的是 time.geekbang.org,那么地址栏会根据规则,把这段内容加上协议,合成为完整的 URL,如 time.geekbang.org

2. 网络进程:发起 URL 请求

浏览器进程会通过进程间通信(IPC)把 URL 请求发送至网络进程,网络进程接收到 URL 请求后,会在这里发起真正的 URL 请求流程。

主要分为以下几个步骤:

  • 检查强缓存
  • DNS 解析
  • 建立 TCP 连接
  • 发起 URL 请求

2.1 检查强缓存

通过相应的字段进行检查,检查如果命中则直接使用,否则进入下一步。

  • HTTP/1.0 Expires — 即过期时间,存在于服务端返回的响应头中,告诉浏览器在这个过期时间之前可以直接从缓存里面获取数据。【 缺点:服务器的时间和浏览器的时间可能并不一致,那服务器返回的这个过期时间可能就是不准确的。因此这种方式很快在后来的HTTP1.1版本中被抛弃了。】
  • HTTP/1.1 Cache-Control max-age 属性 — 采用过期时长来控制缓存,而不是具体的过期时间点

2.2 DNS 解析

DNS(Domain Name Server 域名服务器)查询。DNS 的作用就是通过域名查询到具体的IP。DNS 查询顺序如下,若其中一步查询成功就到进入建立TCP连接的部分。

  • 浏览器缓存( chrome 对每个域名会默认缓存60s)
  • 系统缓存( hosts 文件)
  • 客户机请求 LDNS(本地域名服务器),进行递归查询
  • LDNS 请求 DNS 根域名服务器,进行迭代查询

【 迭代查询 】DNS 服务器会向客户机提供其他能够解析查询请求的 DNS 服务器地址。当客户机发送查询请求时,DNS 服务器并不直接回复查询结果,而是告诉客户机另一台 DNS 服务器地址,客户机再向这台 DNS 服务器提交请求,依次循环直到返回查询的结果为止。

【 递归查询 】一种 DNS 服务器的查询模式,在该模式下 DNS 服务器接收到客户机请求,必须使用一个准确的查询结果回复客户机。如果 DNS 服务器本地没有存储查询 DNS 信息,那么该服务器会询问其他服务器,并将返回的查询结果提交给客户机。

DNS 查询

递归查询和迭代查询的区别:核心区别就是 "查询递交者" 是否改变。迭代查询每次都是从 LDNS 向下一个域名服务重新发起请求,"查询递交者" 永远都是 LDNS ,没有改变。递归查询,"查询递交者"由客户端更替为 LDNS ,LDNS 代为查询并将查询结果返回。

参考文章

2.3 建立 TCP 连接

备注:左边为客户端 — Client ,右边为服务端 — Server

TCP

三次握手🤝

双端开始都处于 CLOSED 状态,然后服务端开始监听某个端口,进入了 LISTEN 状态

  • 第一次握手:Client 主动向 Server 发送握手信息。发送 SYN = 1,seq = x (随机值),随后变成 SYN_SENT 状态。
  • 第二次握手:Server 接收到来自 Client 的握手信息并准备发送 确认信息 + 自己的握手信息。发送 ACK = 1, ack = x + 1, SYN = 1, seq = y (随机值),随后变成 SYN_RCVD 状态。
  • 第三次握手:Client 接收到 来自 Server 的握手信息并准备发送 确认信息。发送 ACK = 1, ack = y + 1, seq = x + 1,随后变成 ESTABLISHED 状态。Server 收到确认信息后,也变成 ESTABLISHED 状态。

关于 TCP 更详细的知识请移步:

2.4 发送 URL 请求

  • TCP 连接建立之后,浏览器端会构建请求行、 请求头等信息,并把和该域名相关的 Cookie 等数据附加到请求头中,然后向服务器发送构建的请求信息。

  • 服务器接收到请求信息后,会根据请求信息生成响应数据(包括响应行、响应头和响应体等信息),并发给网络进程。等网络进程接收了响应行和响应头之后,就开始解析响应头的内容了。

关于 HTTP 更详细的知识请移步:

特殊响应场景处理

  1. 重定向:在导航过程中,如果服务器响应行的状态码包含了 301、302 一类的跳转信息,浏览器会跳转到新的地址继续发起请求;如果响应行是 200,那么表示浏览器可以继续处理该请求。
  2. 响应数据类型处理:Content-Type 是 HTTP 头中一个非常重要的字段, 它告诉浏览器服务器返回的响应体数据是什么类型,然后浏览器会根据 Content-Type 的值来决定如何显示响应体的内容。

举个🌰,如果值为 text/html,则继续导航,往下进行浏览器解析和渲染页面;若值为 text/octet-stream,显示数据是字节流类型的,通常情况下,浏览器会按照下载类型来处理该请求。那么该请求会被提交给浏览器的下载管理器,同时该 URL 请求的导航流程就此结束。

3. 浏览器进程:准备渲染进程

默认情况下,Chrome 会为每个页面分配一个渲染进程,也就是说,每打开一个新页面就 会配套创建一个新的渲染进程。但是,也有一些例外,在某些情况下,浏览器会让多个页面 直接运行在同一个渲染进程中。【 参见上述的 Process-per-site-instance 模式 】

渲染进程准备好之后,还不能立即进入文档解析状态,因为此时的文档数据还在网络进程中,并没有提交给渲染进程,所以下一步就进入了提交文档阶段。

4. 渲染进程:提交文档

首先要明确一点,这里的“文档”是指 URL 请求的响应体数据。

  • 浏览器进程发出 "提交文档" 指令后,三个进程之间的交互

三个进程之间的交互

5. 渲染进程:解析与合成阶段 —— 以线程为维度展开

5.1 主线程的工作

主要分为以下几个步骤:

  • 创建 DOM 树
  • 样式计算
  • 创建布局树(LayoutTree)
  • 创建图层树(LayerTree)
  • 生成绘制列表

5.1.1 构建 DOM 树🌲

什么是 DOM 树呢?

DOM树本质上是一个以 document 为根节点的多叉树。

为什么要构建 DOM 树呢?

这是因为浏览器无法直接理解和使用 HTML,所以需要将 HTML 转换为浏览器能够理解的结构——DOM 树。

怎样构建 DOM 树呢?

  • 标记化(tokenization) —— 词法分析

  • 建树 —— 语法分析

【 词法分析 】(英语:lexical analysis)是计算机科学中将字符序列转换为单词(Token)序列的过程。 词法分析的第一阶段即词法分析器(扫描器),通常基于有限状态自动机。

【 有限状态自动机 】拥有有限数量的状态。每个状态可以迁移到零个或多个状态,输入字串决定执行哪个状态的迁移。

【 语法分析 】的任务是在词法分析的基础上将单词序列组合成各类语法短语。

浏览器从网络中接受到 HTML 文件然后进行一系列的转换过程如下:

字节数据 => 字符串(我们所写的代码) => Token => Node => DOM

5.1.2 样式计算 (Recalculate Style)

CSS 样式的来源

  • 通过 link 引用的外部 CSS 文件 【外部】
  • < style >标记内的 CSS 【内联】
  • 元素的 style 属性内嵌的 CSS 【内嵌】

怎样进行样式计算呢?

  • 把 CSS 转换为浏览器能够理解的结构 —— styleSheets
  • 转换样式表中的属性值,使其标准化

标准化

  • 计算出 DOM 树中每个节点的具体样式

    在计算过程 中需要遵守 CSS 的继承和层叠两个规则。

    【 CSS 继承 】就是每个 DOM 节点都包含有父节点的样式。

    【 层叠 】是 CSS 的一个基本特征,它是一个定义了 如何合并来自多个源的属性值的算法。它在 CSS 处于核心地位,CSS 的全称“层叠样式表”正是强调了这一点。

5.1.3 创建布局树🌲(LayoutTree)

DOM 树还含有很多不可见的元素,比如 head 标签,还有使用了 display:none 属性的元素。所以在显示之前,我们还要额外地构建一棵只包含可见元素布局树。

构造布局树

5.1.4 创建图层树🌲(LayerTree)

为什么要创建图层树呢?

因为页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)。

布局树和图层树的关系?

图层树
通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。

需要满足什么条件,渲染引擎才会为特定的节点创建新的层呢?【显式合成】

  1. 拥有层叠上下文属性的元素会被提升为单独的一层。
    • 比如明确定位属性的元素、定义透明属性的元素、使用 CSS 滤镜的元素等
    • 更多关于层叠上下文的知识,请参考:MDN-层叠上下文
  2. 需要剪裁(clip)的地方也会被创建为图层。
    • 出现这种裁剪情况的时候,渲染引擎会为文字部分单独创建一个层,如果出现滚动条,滚动 条也会被提升为单独的层。

【隐式合成】 简单来说就是层叠等级低的节点被提升为单独的图层之后,那么所有层叠等级比它高的节点都会成为一个单独的图层。【风险】可能会导致层爆炸。

参考:

5.1.5 生成绘制列表

渲染引擎会把一个图层的绘制拆分成很多小的绘制指令,然后再 把这些指令按照绘制顺序组成一个待绘制列表。

绘制列表

5.2 合成线程的工作

主要分为以下几个步骤:

  • 划分图块并栅格化
  • 发送 DrawQuad 指令

5.2.1 划分图块并栅格化

为什么要划分图块呢?

在有些情况下,有的图层可以很大,比如有的页面你使用滚动条要滚动好久才能滚动到底部,但是通过视口,用户只能看到页面的很小一部分,所以在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。基于这个原因,合成线程会将图层划分为图块(tile),这些图块的大小通常是 256x256 或者 512x512。

划分图块之后的操作是什么呢?

进行栅格化,所谓栅格化,是指将图块转换为位图。 合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。

图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的

栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化, 或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。【拓展】究竟什么是位图?

栅格化

5.2.2 发送 DrawQuad 指令

一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。

6. 浏览器进程:生成页面并显示

浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。

完整的渲染流水线示意图

完整的渲染流水线示意图

彩蛋 — 重排、重绘和合成三胞胎

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

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

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

重绘

从上图来看,如果修改了元素的背景颜色,那么布局阶段( 构建布局树和构建图层树 )将不会被执行。因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后触发Paint( 生成绘制列表 )以及后面的一系列子阶段,这个过程就叫 重绘。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高 一些。

直接合成阶段(合成)

合成

从上图来看,我们使用了 CSS 的 transform 来实现动画效果,这可以避开重排和重绘阶段, 直接在非主线程上执行合成动画操作。浏览器触发Paint 后面一系列的子阶段,这个过程就叫 合成。相较于重排操作,合成省去了布局、分层和绘制阶段

参考资料: