从输入URL到页面展示,这中间发生了什么?

165 阅读6分钟

极客时间:浏览器工作原理与实践

基本流程

  • 0、浏览器进程获取用户输入

  • 1、查找本地是否缓存了资源

    • 1.1、如果缓存了,查看缓存是否过期
      • 1.1.1、没有过期,那么直接返回缓存的资源
      • 1.1.2、如果过期了,那么向服务器重新发送资源请求
    • 1.2、如果没有缓存,则发起请求
  • 2、网络请求

    • 2.1、DNS解析,浏览器缓存--〉本地缓存(host文件)--〉DNS请求
    • 2.2、建立TCP连接
      • 2.2.1、如果是https协议,还需要建立TLS连接
    • 2.3、请求资源
      • 2.3.1、如果是返回的状态码是301,重定向,则根据返回的地址重新发送请求(重复一遍【2、网络请求】的步骤)
    • 2.4、根据服务端返回的【Content-Type】决定如何处理返回的信息
      • 2.4.1、如果是字节流类型的Content-Type : application/octet-stream,则按照文件处理,此次导航结束
      • 2.4.2、如果是html类型的Content-Type : text/html,则准备进行下一步渲染
  • 3、准备渲染进程

    • 3.1、默认情况下,一个页面一个渲染进程,但是当一个页面从另一个页面打开,并且两个页面属于同一站点时,也会共用一个渲染进程。

        同一站点:相同的协议和根域名
        同源策略中的同源:协议/主机/端口元组
      
  • 4、提交文档

    • 4.1、浏览器进程拿到用户输入的URL地址之后,就让网络进程发送请求,服务端返回响应头之后,网络进程读取,然后通知浏览器进程“响应头读取完了”,于是浏览器进程通知渲染进程:“提交文档”。
    • 4.2、渲染进程收到浏览器进程发过来的“提交文档”的消息之后,就开始和网络进程建立连接,两个进程之间创建一个“管道”传输数据。
    • 4.3、数据传输完毕之后,渲染进程向浏览器进程发送消息“确认提交”。
    • 4.4、浏览器进程收到消息之后,就开始更新浏览器界面一系列数据
      • 4.4.1、前进后退的历史
      • 4.4.2、页面的安全状态(连接是安全的)
      • 4.4.3、地址栏URL
      • 4.4.4、web界面的显示

至此,【导航】结束。

        导航:从浏览器进程提交url给网络进程时的【2、网络请求】到【4、提交文档】结束的过程叫做导航
        浏览器的导航过程涵盖了从用户发起请求到提交文档给渲染进程的中间所有阶段。            
  • 5、渲染阶段
    • 5.1、网络进程读取响应头之后,也会读取响应体,读取响应体中带有的资源和数据,资源和数据提交到渲染进程,让渲染进程进行【页面解析和子资源加载】,这一步结束之后,渲染进程就会通知浏览器进程:“页面加载完成”

最后一步的渲染如何做到的

渲染是一个比较复杂的步骤,为了简单,我们将这些复杂的步骤拆分成一个个的子阶段,输入的html经过这些子阶段,最后产出成像素,我们把这样的一个过程叫做渲染流水线

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

构建DOM树

我们可以在控制台输入document查看构建完成的DOM树。

样式计算

首先将html文档中各个来源的css样式提取出来,然后转换成计算机可以理解的结构styleSheets,这个结构可以在控制台通过输入document.styleSheets查看。构建成功的styleSheets具备了查询修改的功能。

然后浏览器根据转化成功的数据结构,进行样式的标准化:即根据窗口大小等条件,将样式中各种emvh%等转化成px之类的。

再之后,就是根据各种继承规则、优先级顺序等,来计算具体到每一个节点的样式、并显示出来。

布局阶段

将文档中各个可见节点提取出来,创建一个布局树
那什么是不可见的节点呢?比如说head、比如说display:none等等。

布局树创建出来了,按照上面【DOM树-->样式计算】的步骤的话,布局树后面也要跟上一个布局树的样式计算,计算布局节点的具体坐标。

分层

因为一些z-index3D渲染overflow:hidden等等,都会出现一些图层上的差别。这些让一个二维的展示拥有了三维的概念。

所以我们要将其分层,构建图层树

绘制

分层结束之后,就是绘制。

渲染引擎实现图层的绘制的时候,会将每一层的渲染拆分成各个小的 绘制指令

这些绘制指令组成的列表称为绘制列表。这一步仅仅是将指令保存,而非完成绘制这个操作。

光栅化(栅格化(raster)操作

上面这些渲染的操作都是在渲染进程中的主线程内完成的。

而到了这一步,主线程要将绘制列表提交到渲染进程中的合成线程

所谓栅格化,在百度百科中是这样解释的:在图像编辑中,栅格——像素,意思是将图像中的指令转换为像素。栅格化——指将指令转化为像素的过程。

这里我们类推一下,在浏览器渲染中的栅格化,即使不是“像素化”,也是将一个个的图块一个个的“位图化”。
图块,即栅格化中操作的最小单位。也就是说,可以把一个个图块栅格化为更为细小的位图。

合成线程会将图层划分为图块。并且优先加载视口(页面可视窗口)附近的内容。

切分成图块之后,合成线程就会把这些图块送到栅格化线程池

什么是栅格化线程池呢 我们上面提到了渲染进程中有主线程、合成线程。除此之外,渲染进程中也维护有一个栅格化线程池,里面有一个个的栅格化线程。所有图块的栅格化都是在这个线程池里面执行的。

通常,栅格化都会用GPU来加速,GPU操作是运行在GPU进程中的。
如果栅格化过程中用到了GPU,那么就不是在栅格化线程池中进行栅格化了,而是在 GPU进程中执行栅格化操作。并且最后生成的位图保存在GPU内存中。
这其中涉及到了跨进程操作,不做细说(因为我也不知道怎么回事呢讲实话)。

合成

当所有的图块都栅格化之后,合成线程就会向浏览器进程提交一个绘制图块的命令: DrawQuad
浏览器进程中有一个viz组件,接收到命令之后就开始辛勤工作了,最终就会呈现出一个绘制完成的漂亮的界面了。