与浏览器进程如何配合
首先我们需要知道浏览器进程的作用和负责的功能;
- 浏览器进程主要负责 用户交互和管理子进程 和存储功能;
- 网络进程主要面向浏览器进程和渲染进程提供网络下载功能;
- 渲染进程的主要职责是把从网络下载的 HTML、JavaScript、CSS、图片等资源解析为可以显示和交互的页面,渲染进程通过网络获取的内容不一定都是安全的,所以Chrome 的渲染进程是运行在沙箱内的,为了保证安全;
后续其他的文章 会专门介绍进程之间的交互和功能
用户输入URL 到页面解析出来 经历哪些步骤:
DNS 解析 http缓存讲到了DNS解析
TCP 三次握手
发送请求
服务端处理请求返回HTTP报文
浏览器解析渲染页面
断开连接:TCP四次挥手
用户输入URL
- 判断输入的URL 是否符合URL 规范,如果符合地址栏根据输入的内容加上协议,组成完整的URL,比如:baidu.com> https://baidu.com
- 用户回车或者开始搜索的时候,表示当前页面将要被替换;标签页面的图标进入的加载状态,但是页面还是旧的页面,需要等待提交文档阶段;
URL请求过程
- 输入URL,开始请求之后,进入页面资源请求的过程;浏览器进程通过进程之间的通信把URL -> 网络进程
- 网络进程接收到URL;会先检查本地缓存是否缓存了该资源;
1、如果有缓存,直接返回给浏览器进程;
2、如果没有缓存,重新进行网络请求;
1、DNS解析:
- 1、浏览器会首先查看本地硬盘的 hosts 文件,看看其中有没有和这个域名对应的规则,如果有的话就直接使用 hosts 文件里面的 ip 地址和服务器建立连接。
- 2、如果没有,浏览器会发出一个 DNS请求到本地DNS服务器 。
- 3、本地DNS服务器会首先查询它的缓存记录,如果缓存中有此条记录,就可以直接返回结果,此过程是递归的方式进行查询。如果没有,本地DNS服务器还要向DNS根服务器进行查询
- 4、根DNS服务器没有记录具体的域名和IP地址的对应关系,而是告诉本地DNS服务器,你可以到域服务器上去继续查询,并给出域服务器的地址。这种过程是迭代的过程。
- 5、本地DNS服务器继续向域服务器发出请求,
- 6、本地DNS服务器向域名的解析服务器发出请求,这时就能收到一个域名和IP地址对应关系,
本地DNS服务器不仅要把IP地址返回给用户电脑,
还要把这个对应关系保存在缓存中,以备下次别的用户查询时
2、TCP连接:
3、发送HTTP请求:
一个HTTP请求报文由请求行(request line)、请求头部(header)、空行和请求实体4个部分组成。
浏览器会构建请求头请求行,并且把和域名相关的cookie 等数据加在请求头上,向服务器发送构建请求;
method:
headers:本质来说,就是一些名值对。首部分以下几类:通用首部、请求首部、响应首部、实体首部、扩展首部(规范中没有的自定义首部)
4、服务器处理请求:
客户端不是直接通过HTTP协议访问某网站应用服务器,而是先请求到Nginx,Nginx再请求应用服务器,然后将结果返回给客户端,这里Nginx的作用是反向代理服务器。同时也带来了一个好处,其中一台服务器万一挂了,只要还有其他服务器正常运行,就不会影响用户使用。
5、返回响应结果:
- 服务器接收到请求信息之后生成响应数据,服务器把响应信息返回给网路进程
- 网络进程接收到响应信息之后,开始解析:
- 如果网络进程解析发现返回的状态码301或者302,说明浏览器需要重新定
向到新的url上(从响应头Location中读取);然后重新发起新的请求。 - 如果返回的是200,则浏览器可以继续处理这个请求;
- 如果网络进程解析发现返回的状态码301或者302,说明浏览器需要重新定
提到响应体,我们可能要提到浏览器是怎么知道URL 请求的类型是什么?
- Content-Type 的值决定了如何显示响应体的内容
- 如果Content-Type 的值是 application/octet-stream 显示字节流类型,一般浏览器会按照下载类型处理该请求;
- Content-Type 不同后续的处理也是不同的;
渲染进程准备
- 服务器返回响应体,并且Content-Type 类型不是下zai,这时候要进入渲染进程;
- 每个页面都会有个渲染进程,但是如果几个页面是同一个站点一个根源(一个根域名),如果从一个页面打开了另一个新页面,而新页面和当前页面属于同一站点的话,那么新页面会复用父页面的渲染进程
- 渲染进程准备好之后,还不能立即进入文档解析状态
- 因为此时的文档数据还在网络进程中,并没有提交给渲染进程
提交文档
- 浏览器进程让 网络进程 将 收到的 HTML 数据提交给渲染进程具体步骤如下:
1、当浏览器进程接收到网络进程的响应头数据之后,便向渲染进程发起“提交文档”的消息;
2、渲染进程接收到“提交文档”的消息后,会和网络进程建立传输数据的“管道”;
3、等文档数据传输完成之后,渲染进程会返回“确认提交”的消息给浏览器进程;
4、浏览器进程收到消息之后,开始更新页面 的状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新 Web 页面
判断文件类型,然后创建一个渲染进程,渲染进程准备好之后,网络进程和渲染进程之间建立一个共享数据的管道,渲染进程在另一端不断的读取数据,然后给到HTML 解析器
渲染阶段:
这个页面很重要,主要是渲染页面,熟悉了相关流程:
1、就可以知道如何解决优化页面卡顿问题,优化动画流程,优化样式。
2、我们编好的HTML JS CSS 又是如何转化成我们看到的页面的呢?
带着问题下面看下 渲染阶段都是怎么渲染页面的:
1、构建DOM 树
1、通过分词器将字节流转换成Token
2、Token 解析为DOM 节点,并将DOM 节点添加到DOM树中
如果遇到 script 解析器会暂停 DOM 解析 ,因为js 可能要修改当前的DOM 结构。 JS 文件下载的过程中会阻塞DOM 解析
async 和 defer 虽然都是异步的,
1、使用 async 标志的脚本文件一旦加载完成,会立即执行;
2、使用了 defer 标记的脚本文件,需要所有的元素解析完成之后,在 DOMContentLoaded 事件之前执行。
如果代码引用了外部css 文件,那么执行js 之前还需要等待外部 css 文件下载完成,并解析成CSSDOM 之后才能执行js。 所以js 阻塞DOM 生成,样式又会阻塞js 的执行。
- 因为浏览器无法直接理解和使用HTML,所以需要将浏览器转化成能够理解的结构 DOM 树
- 构建 DOM 树的输入内容是一个非常简单的 HTML 文件,然后经由 HTML 解析器解析,最终输出树状结构的 DOM
HTML 解析器并不是等整个文档加载完成之后在解析的,而是网络进程加载了多少数据,HTML 解析器便解析多少数据
2、样式计算
- 把 CSS 转换为浏览器能够理解的结构;当渲染引擎接收到 CSS 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构——styleSheets。
- 转换样式表中的属性值,使其标准化
- 计算出 DOM 树中每个节点的具体样式
样式计算阶段的目的是为了计算出 DOM 节点中每个元素的具体样式,在计算过程中需要遵守 CSS 的继承和层叠两个规则。这个阶段最终输出的内容是每个 DOM 节点的样式,并被保存在 ComputedStyle 的结构内
css 如何影响 首次加载的白屏时间:
- 当渲染进程接收 HTML 文件字节流时,会先开启一个预解析线程,如果遇到 JavaScript 文件或者 CSS 文件,那么预解析线程会提前下载这些数据
- 当遇到 css 文件,去发起css 下载,这里有个空闲时间,就是
DOM 构建结束之后,css 还未下载 , 渲染流水线无事可做,因为下一步是合成布局树,而合成布局树需要 CSSOM 和 DOM,所以这里需要等待 CSS 加载结束并解析成 CSSOM
<html>
<head>
<link href="theme.css" rel="stylesheet">
</head>
<body>
<div>geekbang com</div>
<script src='foo.js'></script>
<div>geekbang com</div>
</body>
</html>
- 上面代码中 有css 外部引用和js 的外部文件,HTML 预解析器识别出来了有 CSS 文件和 JavaScript 文件需要下载,然后就同时发起这两个文件的下载请求,需要注意的是,
这两个文件的下载过程是重叠的,所以下载时间按照最久的那个文件来算
- 不管 CSS 文件和 JavaScript 文件谁先到达,都要先等到 CSS 文件下载完成并生成 CSSOM,然后再执行 JavaScript 脚本,最后再继续构建 DOM,构建布局树,绘制页面。
优化策略:
缩短白屏时间:
1、通过内联 JavaScript、内联 CSS 来移除这两种类型的文件下载,这样获取到 HTML 文件之后就可以直接开始渲染流程了
2、但并不是所有的场合都适合内联,那么还可以尽量减少文件大小,比如通过 webpack 等工具移除一些不必要的注释,并压缩 JavaScript 文件
3、可以将一些不需要在解析 HTML 阶段使用的 JavaScript 标记上 sync 或者 defer
3、布局阶段
我们有 DOM 树和 DOM 树中元素的样式,但这还不足以显示页面,因为我们还不知道 DOM 元素的几何位置信息
- 在显示之前,我们还要额外地构建一棵只包含可见元素布局树。
- 遍历DOM 的可见节点,并把这些节点加到布局树中;
- 不可见的节点会被布局树忽略掉,如head标签或者display:none 的标签
- 布局计算:
我们有了一棵完整的布局树。那么接下来,就要计算布局树节点的坐标位置了
总结:在 HTML 页面内容被提交给渲染引擎之后,渲染引擎首先将 HTML 解析为浏览器可以理解的 DOM;然后根据 CSS 样式表,计算出 DOM 树所有节点的样式;接着又计算每个元素的几何坐标位置,并将这些信息保存在布局树中
4、分层
- 因为页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树
- 浏览器的页面实际上被分成了很多图层,这些图层叠加后合成了最终的页面
- 并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层
图层绘制
- 一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表
栅格化(raster)操作
- 绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的
- 当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程
- 合成线程会按照视口附近的图块来优先生成位图,所谓栅格化,是指将图块转换为位图
- 栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中
- 渲染进程把生成图块的指令发送给 GPU,然后在 GPU 中执行生成图块的位图,并保存在 GPU 的内存中
5、 合成和显示
- 一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程
- 浏览器进程中的viz 组件 ,将其页面内容绘制到内存中,最后再将内存显示在屏幕上
总结下渲染流程:
1、渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构
2、渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式
3、创建布局树,并计算元素的布局信息。
4、对布局树进行分层,并生成分层树。
5、为每个图层生成绘制列表,并将其提交到合成线程。
6、合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
7、合成线程发送绘制图块命令 DrawQuad 给浏览器进程
8、浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。