从输入 URL 到页面展示,这中间经历了什么

我们需要在地址栏中输入一个查询关键字时,地址栏会先判断输入的关键字是搜索内容,还是请求的 URL。

如果是搜索内容字,地址栏会使用浏览器默认的搜索引擎,来合成新的搜索关键字的 URL。

如果是符合 URL 规则,那么浏览器会根据规则,加上协议,合并成为完整的 URL。

csdn.net 合并为     https://www.csdn.net

并且在发起真正的 csdn.net 请求之前,浏览器会给当前(即将跳转的页面)一次执行 beforeunload 事件的机会,允许当前页面执行一些清理性的操作,有的也会进行一次询问用户是否要跳转页面的弹框,可以用来取消导航。

接下来浏览器需要提交文档,在这个阶段浏览器是处于一个即将跳转页面的页面刷新。

提交文档阶段

此时此刻,你在浏览器地址栏输入了百度的网址:

https://www.baidu.com/

网络请求

1. 浏览器会构件请求行:

// 请求方法是 GET , 请求路径为根路径下的index.html,HTTP协议版本为 1.1

GET /index.html HTTP/1.1

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

2. 查找强缓存

网络进程在发起请求资源前,先检查强缓存,如果命中直接使用,否则就进入下一步。

3. DNS 解析

由于我们输入的是域名,而数据包是通过 *IP 地址* 传给对方的。因此我们需要得到域名对应的 IP 地址 。这个过程需要依赖一个服务系统,这个系统将域名和 IP 一一映射,我们将这个系统叫做 DNS (域名系统)。

浏览器提供了 **DNS数据缓存功能**。如果一个域名已经解析过了,那么会把解析的结果缓存起来,瑕疵处理直接走缓存,不需要经过 <u>DNS解析</u>。

4. 建立 TCP 连接。

Chrome 在同一个域名下要求同时最多只能有 6 个 TCP 连接,超过 6 个的话剩下的请求就得等待。

假设现在是小于 6 个连接的,我们进入 TCP 连接的建立阶段。

`TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。`

TCP 通过三次握手,数据包校验、四次挥手 来保证数据传输的可靠性。

5. 发送 HTTP 请求。

现在的话,以及建立好了 TCP 连接,浏览器可以和服务器进行通信,发送 HTTP 请求。浏览器会向服务器发送请求行。

// 请求方法是 GET , 请求路径为根路径下的index.html,HTTP协议版本为 1.1

GET /index.html HTTP/1.1

同时加上请求头,比如我们常见的就是关于缓存的请求头数据 Cache-Control、If-Modified-Since 都可以被放入请求头作为缓存标识。还有一些其他的属性。

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3

Accept-Encoding: gzip, deflate, br

Accept-Language: zh-CN,zh;q=0.9

Cache-Control: no-cache

Connection: keep-alive

Cookie: /* 省略cookie信息 */

Host: www.baidu.com

Pragma: no-cache

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1

最后是请求体,请求体只有在POST方法下存在,常见的场景是表单提交。

6. HTTP 请求加上 TCP 头部,包括原端口号、目的程序端口号和用与校验完整性的序号,向下传输。

7. 底层通过物理网络传输给目标服务器主机。

8. 目标服务器主机网络层接收到数据包,解析出IP头部,识别出数据部分,将解开的数据包向上传输到传输层.

9. 目的服务器主机传输层获取到数据包,解析出TCP头部,识别端口,将解开的数据包向上传输到应用层。

网络响应

HTTP 请求到达服务器,服务器进行对应的处理。最后要把数据传给浏览器,也就是返回网络响应。

跟请求部分类似,网络响应具有三个部分:响应行、响应头和响应体。

响应行类似下面这样:

HTTP协议版本、状态码和状态描述

HTTP/1.1 200 OK

响应头包含了服务器及其返回数据的一些信息, 服务器生成数据的时间、返回的数据类型以及对即将写入的Cookie信息。

Cache-Control: no-cache

Connection: keep-alive

Content-Encoding: gzip

Content-Type: text/html;charset=utf-8

Date: Wed, 04 Dec 2019 12:29:13 GMT

Server: apache

Set-Cookie: rsv_i=f9a0SIItKqzv7kqgAAgphbGyRts3RwTg%2FLyU3Y5Eh5LwyfOOrAsvdezbay0QqkDqFZ0DfQXby4wXKT8Au8O7ZT9UuMsBq2k; path=/; domain=.baidu.com

1. 如果需要重定向,HTTP 直接返回 HTTP 响应数据的状态 301 或者 302 同时在请求头的 Location 字段中附上重定向地址,浏览器会根据 `状态码` 和 `Location` 进行重定向操作,然后按照重定向地址按照上面步骤重新发起请求。

如果不是重定向,首先服务器会根据请求头中的` If-None-Match` 的值来判断请求的资源是否被更新,如果没有更新,就返回 `304` 状态码,相当于告诉浏览器之前的缓存还可以使用,就不返回新数据了;否则,返回新数据,`200` 的状态码,并且如果想要浏览器缓存数据的话,就在相应头中加入字段:`Cache-Control:Max-age=2000`。

2. 响应数据又顺着应用层——传输层——网络层——网络层——传输层——应用层的顺序返回到网络进程。

响应完成之后怎么办?TCP 连接就断开了吗?

1. 这时候要判断 `Connection` 字段, 如果请求头或响应头中包含 `Connection: Keep-Alive` ,表示建立了持久连接,这样TCP连接会一直保持,之后请求统一站点的资源会复用这个连接。

否则断开TCP连接, 请求-响应流程结束。

2. 网络进程将获取到的数据包进行解析,根据响应头中的 `Content-type` 来判断响应数据的类型,如果是字节流类型,就将该请求交给下载管理器,该导航流程结束,不再进行;如果是 `text/html` 类型,就通知浏览器进程获取到文档准备渲染进程。

3. 默认情况下,Chrome 会为每个页面分配一个渲染进程,也就是说,每打开一个新页面就会配套创建一个新的渲染进程。但是,也有一些例外,`在同一站点下`,浏览器会让多个页面直接运行在同一个渲染进程中。

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

  • 首先当浏览器进程接收到网络进程的响应头数据之后,便向渲染进程发起“ `提交文档` ”的消息;
  • 渲染进程接收到“提交文档”的消息后,会和网络进程建立传输数据的“ `管道` ”。
  • 等文档数据传输完成之后,渲染进程会返回“ `确认提交` ”的消息给浏览器进程;
  • 浏览器进程在收到“确认提交”的消息后,会 `更新浏览器界面` 状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新 Web 页面。

完成了网络请求和响应,如果响应头中 Content-Type 的值是 text/html ,接下来就是浏览器的 `解析` 和 `渲染` 工作量。

渲染阶段: 

目的:左边输入的是 HTML、CSS、JavaScript 数据,这些数据经过中间渲染模块的处理,最终输出为屏幕上的像素。

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

1. 构建 DOM 树

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

DOM 树的构建过程: 

构建 DOM 树的输入内容是一个非常简单的 HTML 文件,然后经由 HTML 解析器解析,最终输出树状结构的 DOM。DOM 和 HTML 内容几乎是一样的,但是和 HTML 不同的是,DOM 是保存在内存中树状结构,可以通过 JavaScript 来查询或修改其内容。 

2. 样式计算(Recalculate Style)

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

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

并且该结构同时具备了查询和修改功能,这会为后面的样式操作提供基础。

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

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

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

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

这个阶段最终输出的内容是每个 DOM 节点的样式,并被保存在 ComputedStyle 的结构内。

3. 布局计算

1. 创建布局树

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

2. 布局计算

现在我们有了一棵完整的布局树。那么接下来,就要计算布局树节点的坐标位置了。会把布局运算的结果重新写回布局树中,所以布局树既是输入内容也是输出内容。 

4. 分层

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

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

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

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

第一点,拥有层叠上下文属性的元素会被提升为单独的一层。明确定位属性的元素、定义透明属性的元素、使用 CSS 滤镜的元素等,都拥有层叠上下文属性。

第二点,需要剪裁(clip)的地方也会被创建为图层。overflow: auto;

5. 图层绘制

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

渲染引擎实现图层的绘制与之类似,会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表,如下图所示:

6. 栅格化(raster)操作

绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。你可以结合下图来看下渲染主线程和合成线程之间的关系:

当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程,那么接下来合成线程是怎么工作的呢?

 合成线程会将图层划分为图块,这些图块的大小通常是 256x256 或者 512x512,如下图所示。然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的,运行方式如下图所示:

 栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。

GPU 操作是运行在 GPU 进程中,如果栅格化操作使用了 GPU,那么最终生成位图的操作是在 GPU 中完成的,这就涉及到了跨进程操作。

从图中可以看出,渲染进程把生成图块的指令发送给 GPU,然后在 GPU 中执行生成图块的位图,并保存在 GPU 的内存中。 

5. 合成和显示

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

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

到这里,经过这一系列的阶段,编写好的 HTML、CSS、JavaScript 等文件,经过浏览器就会显示出漂亮的页面了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值