大致过程
- 首先,浏览器进程接收到用户输入的 URL 请求,浏览器进程便将该 URL 转发给网络进程。
- 然后,在网络进程中发起真正的 URL 请求。
- 接着网络进程接收到响应头数据,便解析响应头数据,并将数据转发给浏览器进程。
- 浏览器进程接收到网络进程的响应头数据之后,发送 “提交导航( CommitNavigation )” 消息到渲染进程。
- 渲染进程接收到 “提交导航” 的消息之后,便开始准备接收 HTML 数据,接收数据的方式是直接和网络进程建立数据管道。
- 最后渲染进程会向浏览器进程 “确认提交” ,这是告诉浏览器进程:“已经准备好接受和解析页面数据了” 。
- 浏览器进程接收到渲染进程 “提交文档” 的消息之后,便开始移除之前旧的文档,然后更新浏览器进程中的页面状态。
具体过程
用户输入
当用户在地址栏中输入一个查询关键字时,地址栏会判断输入的关键字是搜索内容,还是请求的 URL。
- 如果是搜索内容,地址栏会使用浏览器默认的搜索引擎,来合成新的带关键字的 URL。
- 如果输入内容符合 URL 规则,那么地址栏会根据规则加上协议,合成完整的 URL。
随后当用户键入回车之后,浏览器即将开始加载一个地址,不过在此之前会有一次执行 beforeunload 事件的机会,可以通过此事件来取消导航,如果没有监听这个事件或者同意了继续后续流程,浏览器便开始加载一个地址。
开始加载后,标签页上的图表便进入了加载状态,此时页面显示的还依然是之前打开的内容,这是因为需要等待提交文档阶段,页面内容才会被替换。
URL 请求过程
到了页面资源请求过程。这时浏览器进程会通过(IPC)把 URL 请求发送至网络进程,在网络进程中发起真正的 URL 请求。
首先,网络进程会查找本地缓存,查到就直接返回资源给浏览器进程,没查到那么直接进入网络请求流程。
1.DNS
进行 DNS 解析,以获取服务器 IP 地址。
DNS 查询顺序:
- 浏览器的 DNS 缓存
- 操作系统 DNS 缓存
- hosts 文件
- DNS 服务器
2.建立连接
利用 IP 地址和服务器建立 TCP 连接,如果是 HTTPS 协议那么在 TCP 连接之后还要进行 TLS 连接。
3.发送请求
连接建立之后,浏览器端会构建请求行、请求头等信息,并把和该域名相关的 Cookie 等数据附加到请求头中,然后向服务器发送构建的请求信息。
4.接收响应
服务器接收请求信息后,会根据请求信息生成响应数据(包括响应行、响应头、响应体等信息),并发送给网络进程。网络进程接收了响应信息后就开始解析响应头的内容。
如果响应状态码包含了 301 或者 302 一类的跳转信息,那么网络进程会从响应头的 Location 字段里读取重定向的地址,然后重投开始一遍。
浏览器会根据响应头中的 Content-Type 来选择如何处理该响应。如果是 HTML 那么继续进行导航流程。由于 Chrome 的页面渲染是运行在渲染进程中的,接下来就需要准备渲染进程了。
准备渲染进程
默认情况下 Chrome 会为每个页面分配一个渲染进程,但也有一些例外,在某些情况下,浏览器会让多个页面直接运行在同一个渲染进程。
打开一个新页面采用的渲染进程策略:
- 通常情况下,打开的新的页面都会使用单独的渲染进程。
- 如果从 A 页面打开 B 页面,且 A 和 B 都属于同一站点的话,那么 B 页面复用 A 页面的渲染进程
渲染进程准备好之后,还不能立即进入文档解析状态,因为此时的文档数据还在网络进程中,并没有提交给渲染进程,所以下一步就进入了提交文档阶段。
提交文档
提交文档,就是指浏览器进程将网络进程接收到的 HTML 数据提交给渲染进程
- 浏览器进程向渲染进程发起 “提交文档” 的消息
- 渲染进程收到 "提交文档" 的消息后,会和网络进程建立传输数据的 “管道”
- 文档数据传输完成之后,渲染进程会返回 “确认提交” 的消息给浏览器进程
- 浏览器进程收到 “确认提交” 的消息后,会更新浏览器界面状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新 Web 页面
渲染阶段
一但文档被提交,渲染进程便开始页面解析和子资源加载了,一旦页面生成完成,渲染进程会发送一个消息给浏览器进程,浏览器接收到消息后,会停止标签图标上的加载动画。
渲染流程
由于渲染机制过于复杂,所以渲染模块在执行过程中会被划分为很多子阶段,输入的 HTML 经过这些子阶段,最后输出像素。这个过程叫做渲染流水线。
按照渲染的时间顺序,流水线可分为如下几个子阶段:构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成。
每个子阶段都会有以下三部分内容:
- 开始每个子阶段都有其输入内容。
- 然后每个子阶段都有其处理过程。
- 最终每个子阶段都会产生输出内容。
构建 DMO 树
浏览器无法直接理解和使用 HTML,所以需要将 HTML 转换为浏览器能够理解的结构——DOM 树
构建 DOM 树的输入内容是一个 HTML 文件,然后经由 HTML 解析器解析,最终输出树状结构的 DOM。
document 结构就是 DOM 结构,与 HTML 不同的是,DOM 是保存在内存中的树状结构,可以通过 JavaScript 来查询或修改其内容。
样式计算
计算出 DOM 节点中每个元素的具体样式,这个阶段大致可分为三步
1.把 CSS 转换为浏览器能够理解的内容
CSS 的来源主要有三种:
-
通过 link 引用的外部 CSS 文件
- <style> 标签内的 CSS
-
元素 style 属性的内联样式
渲染引擎也会将 CSS 文本转换为浏览器可以理解的结构——styleSheets。
2.转换样式表中的属性值,使其标准化
属性标准化是指:例如 2em、blue、bold,这些类型数值会被转换为渲染引擎可以理解的、标准化的计算值
3.计算出 DOM 树中每个节点的具体样式
计算过程需要遵循 CSS 的继承和层叠两个规则
层叠是 CSS 的一个基本特征,它是一个定义了如何合并来自多个源的属性值的算法,它在 CSS 处于核心地位,CSS 的全称 ”层叠样式表“ 正是强调了这一点。
这个阶段最终输出的内容是每个 DOM 节点的样式,并被保存在 ComputedStyle 的结构内。
布局阶段
计算出 DOM 树种可见元素的几何位置
1.创建布局树
构建一颗只包含可见元素布局树
- 遍历 DOM 树中所有可见节点,并把这些节点加到布局树种
- 不可见的节点会被布局树忽略掉,如 head 标签下面的全部内容、display:none
2.布局计算
有了一颗完整的布局树,接下来会计算布局树节点的坐标位置(后续补充计算过程)。
在执行布局操作时,会把布局运算的结果重新写回到布局树种,所以布局树既是输入内容也是输出内容,这是布局阶段一个不合理的地方,因为在布局阶段没有清晰的将输入内容和输出内容区分开来。针对这个问题,Chrome 团队在重构布局代码,下一代布局系统叫 LayoutNG,试图更清晰地分离输入和输出,从而让新设计的布局算法更加简单。
分层
页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-index 做 z 轴排序等,为了更加方便的实现这些效果,渲染引擎还需要为特定的节点产生专用的图层,并生成一颗对应的图层树(LayerTree)。渲染引擎给页面分了很多图层,这些图层按照一定的顺序叠加在一起,就形成了最终的页面。
通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。但不管怎样,最终每一个节点都会直接或者间接的从属于一个层。
渲染引擎为特定节点创建新的图层,通常满足下面两点中任意一点:
-
拥有层叠上下文属性的元素会被提升为单独的一层。
页面是个二维平面,但是层叠上下文能过够让 HTML 元素具有三维概念,这些 HTML 元素按照自身属性的优先级分布在垂直于这个二位平面的 z 轴上。
明确定位属性的元素、定义透明属性的元素、使用 CSS 滤镜的元素等,都拥有层叠上下文属性。
-
需要裁剪(clip)的地方也会被创建为图层。
当元素内部的元素内容超出元素区域时就产生了裁剪,渲染引擎会把裁剪内容的一部分用于显示在元素区域,出现这种裁剪情况的时候,渲染引擎会为超出元素单独创建一个层,如果出现滚动条,滚动条也会被提升为单独的层。
图层绘制
渲染引擎会将一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表。绘制列表中的指令非常简单,就是让其执行一个简单的绘制操作,比如绘制粉色矩形或者黑色的线等。而绘制一个元素通常需要好几条绘制指令,因为每个元素的背景、前景、边框都需要单独的指令去绘制。在图层绘制阶段,输出的内容就是这些待绘制列表。
栅格化(raster)操作
绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。当涂层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程。
合成线程会把图层划分为图块(tile),这些土块的大小通常是 256 * 256 或者 512 * 512。然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的。
通常,栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,生成的位图被保存在 GPU 内存中。
GPU 操作是运行在 GPU 进程中,如果栅格化操作使用了 GPU,那么最终生成位图的操作是在 GPU 中完成的,这就涉及到了跨进程操作。渲染进程把生成图块的指令发送给 GPU,然后再 GPU 中执行生成图块的位图,并保存在 GPU 的内存中。
合成和显示
一旦所有的图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容会知道内存中,最后再将内存显示在屏幕上。
到这里,经过这一系列的阶段,编写好的 HTML、CSS、JavaScript 等文件,经过浏览器就会显示出页面了。