Skip to content

浏览器页面加载过程

准备动作

输入后,先做 url 匹配,从书签,历史记录等地方,找到已经输入的字符串可能匹配的 URL,然后显示出来。让你做选择。

开始加载

当浏览器刚开始加载一个地址之后,标签页上的图标便进入了加载状态。但此时图中页面显示的依然是之前打开的页面内容。

URL 请求过程

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

  • 网络进程会查找本地缓存是否缓存了该资源。如果有缓存资源,那么直接返回资源给 浏览器进程;如果在缓存中没有查找到资源,那么直接进入网络请求流程。这请求前的第一步是要进行 DNS 解析,以获取请求域名的服务器 IP 地址。如果请求协议是 HTTPS,那么还需要建立 TLS 连接。

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

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

重定向

在接收到服务器返回的响应头后,网络进程开始解析响应头,如果发现返回的状态码是 301 或者 302,那么说明服务器需要浏览器重定向到其他 URL。这时网络进程会从响应头 的 Location 字段里面读取重定向的地址,然后再发起新的 HTTP 或者 HTTPS 请求,一切又重头开始了。

响应数据类型处理

  • Content-Type:它告诉浏览器服务器返回的响应体数据是什么类型,然后浏览器会根据 Content-Type 的值来决定如何显示响应体的内容。

  • 响应头中的 Content-type 字段的值是 text/html,这就是告诉浏览器,服务器返回的数据是 HTML 格式。

  • Content-Type 的值是 application/octet-stream,显示数据是字节流类型的,通常情况下,浏览器会按照下载类型来处理该请求。

准备渲染进程

Chrome 会为每个页面分配一个渲染进程,也就是说,每打开一个新页面就 会配套创建一个新的渲染进程。

通常情况下,打开新的页面都会使用单独的渲染进程; 如果从 A 页面打开 B 页面,且 A 和 B 都属于同一站点的话,那么 B 页面复用 A 页面的渲染进程;如果是其他情况,浏览器进程则会为 B 创建一个新的渲染进程。

同一站点:同一站点”定义为根域名(xxxx.org)加上协议(例如,https:// 或者 http://),还包含了该根域名下的所有子域名和不同的端口

提交文档

“提交文档”的消息是由浏览器进程发出的,渲染进程接收到“提交文档”的消息后,会和网络进程建立传输数据的“管道”。

等文档数据传输完成之后,渲染进程会返回“确认提交”的消息给浏览器进程。

浏览器进程在收到“确认提交”的消息后,会更新浏览器界面状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新 Web 页面。

渲染阶段

渲染机制过于复杂,所以渲染模块在执行过程中会被划分为很多子阶段,输入的 HTML 经过这些子阶段,最后输出像素。我们把这样的一个处理流程叫做渲染流水线。

按照渲染的时间顺序,流水线可分为如下几个子阶段: 构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成。

构建 DOM 树

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

解析是将一个元素通过一定的方式转换成另一种形式。 比如 html 的解析。首先要明确,html 下载到浏览器的表现形式就是 包含字符串的文件。浏览器将 html 文件里面的字符串读取到内存中,按照 html 规则,对字符串进行取词编译,将字符串转化成另一种易于表达的数据结构。

样式计算

把 CSS 转换为浏览器能够理解的结构

CSS 样式来源主要有三种:

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

当渲染引擎接 收到 CSS 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构—— StyleSheets

转换样式表中的属性值,使其标准化

现在我们已经把现有的 CSS 文本转化为浏览器可以理解的结构了,那么接下来就要对其进行属性值的标准化操作。

CSS 文本中有很多属性值,如 2em、blue、bold,这些类型数值不容易 被渲染引擎理解。所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值。

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

CSS 继承就是每个 DOM 节点都包含有父节点的样式。所有子节点都继承了父节点样式。

这里需要特别提下 UserAgent 样式, 它是浏览器提供的一组默认样式,如果你不提供任何样式,默认使用的就是 UserAgent 样式。

布局阶段

创建布局树

为了构建布局树,浏览器大体上完成了下面这些工作:

  • 遍历 DOM 树中的所有可见节点,并把这些节点加到布局中;
  • 而不可见的节点会被布局树忽略掉,如 head 标签下面的全部内容,再比如 body.p.span 这个元素,因为它的属性包含 display:none,所以这个元素也没有被包进布局树。

布局计算

在执行布局操作的时候,会把布局运算的结果重新写回布局树中,所以布局树既是输入内容也是输出内容,这是布局阶段一个不合理的地方,因为在布局阶段并没有清晰地将输入内容和输出内容区分开来。

分层

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

浏览器的页面实际上被分成了很多图层,这些图层叠加后合成了最终的页面。

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

满足下面两点中任意一点的元素就可以被提升为单独的一个图层。

  • 第一点,拥有层叠上下文属性的元素会被提升为单独的一层。
  • 第二点,需要剪裁( clip )的地方也会被创建为图层。

图层绘制

在完成图层树的构建之后,渲染引擎会对图层树中的每个图层进行绘制,那么接下来我们看看渲染引擎是怎么实现图层绘制的?

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

当图层的绘制列表准备好之后,主线程会把该绘制列表提交( commit )给合成线程。

完整的渲染流程

  1. 渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。
  2. 渲染引擎将 CSS 样式表转化为浏览器可以理解的 StyleSheets,计算出 DOM 节点的样式。
  3. 创建布局树,并计算元素的布局信息。
  4. 对布局树进行分层,并生成分层树。
  5. 为每个图层生成绘制列表,并将其提交到合成线程。
  6. 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
  7. 合成线程发送绘制图块命令 DrawQuad 给浏览器进程。
  8. 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。

重排,重绘,合成

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

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

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

接下来,我们再来看看重绘,比如通过 JavaScript 更改某些元素的背景颜色,渲染流水线会怎样调整呢?你可以参考下图:

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

直接合成阶段

那如果你更改一个既不要布局也不要绘制的属性,会发生什么变化呢?渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做合成。

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

浏览器的网络请求

浏览器端发起 HTTP 请求流程

  • 构建请求
shell
GET /index.html HTTP1.1
  • 查找缓存

在真正发起网络请求之前,浏览器会先在浏览器缓存中查询是否有要请求的文件其中,浏览器缓存是一种在本地保存资源副本,以供下次请求时直接使用的技术。命中缓存,返回该资源的副本,并直接结束请求。否则继续请求。

  • 准备 IP 地址和端口

第一步浏览器会请求 DNS 返回域名对应的 IP。当然浏览器还提供了 DNS 数据缓存服务,如果某个域名已经解析过了,那么浏览器会缓存解析的结果,以供下次查询时直接使用,这样也会减少一次网络请求。拿到 IP 之后,接下来就需要获取端口号了。通常情况下,如果URL没有特别指明端口号,那么 HTTP 协议默认是 80 端口。

  • 等待 TCP 队列

Chrome 有个机制,同一个域名同时最多只能建立 6TCP 连接, 如果在同一个域名下同时有 10 个请求发生,其中 4 个请求会进入排队等待状态,直到进行中的请求完成。

  • 建立 TCP 连接

排队等待结束后,浏览器通过 TCP 与服务器连接

  • 发送 HTTP 请求

首先浏览器会向服务器发送请求行,包括了请求方法,请求 URI,HTTP 协议。发送请求行就是告诉服务器需要什么资源。 发送请求体,把信息告诉服务器,发送请求行命令之后,还要以请求头形式发送其他一些信息,把浏览器的一些基础 信息告诉服务器。

  • 服务端处理 HTTP 请求

服务器会返回响应行,包括协议版本和状态码,发送响应体

  • 断开连接

一旦服务器向客户端返回了请求数据,它就要关闭 TCP 连接,当配置了 Connection:Keep-Alive 属性, TCP 连接会被保持打开状态,这样浏览器可以通过同一个 TCP 连接发送请求。保持 TCP 连接可以省去下次请求时需要建立连接的时间,提升资源加载速度。

缓存问题

第二次打开页面时,速度会变快,主要原因是第一次加载页面过程中,缓存了一些耗时操作。

DNS缓存页面资源 会被浏览器缓存下来。dns缓存主要就是在浏览器本地把对应 ip 和域名关联起来。

页面资源缓存过程,分以下情况:

  • 本地无缓存,继续请求 web 服务器,web 服务器无缓存,返回数据,数据缓存本地。
  • 本地缓存在生存期 Max-age 里,无需请求,直接返回本地缓存.
  • 本地缓存过期,请求 web 服务器,服务器返回 304,缓存内容没有改动,刷新缓存新鲜度。

当服务器返回 HTTP 响应头给浏览器时,浏览器是通过响 应头中的 Cache-Control 字段来设置是否缓存该资源,通常,我们还需要为这个资源设置一个缓存过期时长。而这个时长是通过 Cache-Control 中的 Max-age 参数来设置的,比 如上图设置的缓存过期时间是 2000 秒。

这也就意味着,在该缓存资源还未过期的情况下, 如果再次请求该资源,会直接返回缓存中 的资源给浏览器。但如果缓存过期了,浏览器则会继续发起网络请求,并且在 HTTP 请求头中带上:

服务器收到请求头后,会根据 If-None-Match 的值来判断请求的资源是否有更新,如果没有更新,就返回 304 状态码。如果资源有更新,服务器就直接返回最新资源给浏览器

加载时间统计

总时间

排队时间 + 网络传输时间 + 页面解析时间

网络传输层

  • DNS 时间

通过域名解析服务(DNS),将指定的域名解析成IP地址的消耗时间,例如将 www.tingyun.com 解析成 42.62.56.35。发生一次域名解析后,WINDOWS 操作系统会缓存此操作的结果,当监测节点再次解析相同的域名时,WINDOWS 会将此缓存的结果返回给监测节点,对应消耗时间值可能为0。

  • DNS 解析总时间

浏览一个页面过程中,包含页面中的元素,发生DNS解析时的总花费时间。

  • 建立连接时间

浏览器和WEB服务器建立 TCP/IP 连接的消耗时间。当元素下载完成后,浏览器可能会根据服务器返回的结果保持此连接,而不是完全关闭此连接。当监测节点再次和相同的服务器建立连接时,会复用此连接,对应消耗时间可能为 0。注:此指标即为 TCP/IP 连接三次握手的前二次握手的时间(从 IE 发送 TCPSYN 到收到服务器返回的 TCPSYN ACK 的时间),第三次握手时间(从 IE 发送 TCPACK 到服务器接收此 TCP 包的时间)不计算在内。

  • SSL 握手时间

IE浏览器和WEB服务器建立安全套接层(SSL)连接的消耗时间。TCP/IP 连接建立之后,根据WEB服务器需要,可能建立安全套接层(SSL)连接,例如 使用 https 协议的网址。和 TCP/IP 连接一样,SSL 连接也可能被相同的服务器保持连接和复用连接,对应消耗时间可能为 0

  • 重定向时间

重定向技术可以分为两类,一类是客户端重定向,一类是服务器端重定向。此项指标不包括客户端重定向时间。当 IE 浏览器通过一个网址访问 WEB 服务器时,服务器可能通知IE浏览器此网址已经被其它网址所替代,服务器会返回IE浏览器新的网址,浏览器再根据这个新的网址继续访问,这就是服务器重定向。重定向时间是从收到 WEB 服务器重定向指令到请求WEB服务器的第一个元素之前的消耗时间。

  • 发出请求时间

IE 浏览器发送 HTTP 请求开始,到IE浏览器发送 HTTP 请求结束的消耗时间。注:HTTP 请求的最后一个数据包在网络上传送并到达WEB服务器的时间没有包括在内。当一个 HTTP 请求包含的数据量比较小时,例如仅发出一个元素下载(GET)请求,发出请求时间通常是比较短的。而当一个 HTTP 请求包含的数据量比较大时,例如上传文件(POST)请求,此请求会被 WINDOWS 网络层(winInet)分成多个数据包分多次传送到WEB服务器,每个数据包被 WEB 服务器接收之后才会通知 IE浏览器 继续传送下一个数据包,所以这时发出请求时间会相对长一些。

  • 首包时间

IE 浏览器发送 HTTP 请求结束开始,到收到 WEB 服务器返回的第一个数据包的消耗时间。此指标包含了发送 HTTP 请求时最后一个数据包在网络上的传输时间、服务器响应此请求的时间和服务器回应的第一个数据包在网络上面的传输时间。

  • 客户端时间

监测一个页面时,没有发生网络通讯的时间片断的总和。此项指标包括了浏览器内存初始分配,解析下载的数据包,页面显示等多个小段时间的总和。注:当IE浏览器解析数据包等操作和网络通讯同时进行时,此段时间并不包含在客户端时间之内。

  • 内容下载时间

监测一个页面时,从页面角度看,IE浏览器 接收 WEB服务器 返回的非第一个数据包的消耗时间。

页面总下载字节数

  • 页面总下载字节数

浏览一个页面过程中从 WEB 服务器返回的网络通讯字节总数。此指标包含了 HTTP 协议头的字节数,代表了实际发生的真实的网络流量。

  • 元素总下载字节数

下载页面内某一元素时,例如图像元素、css 元素,从 WEB 服务器返回的字节数。注:此指标未包含 HTTP 协议头的字节数,所以当服务器未启用 HTTP 压缩时,页面内全部元素总下载字节数的累计会比页面总下载字节数稍微小一些。而在 WEB 服务器返回的网络数据包是经过 HTTP 压缩的情况下,IE 浏览器会对原始网络数据包解压缩,此指标指的是解压缩后的元素的大小,页面内全部元素总下载字节数的累计会比页面总下载字节数大很多。

Released under the MIT License.