『问题探究』从输入URL到显示页面的过程

161 阅读7分钟

这个问题面试中经常出现,每次我都能说个大概,但是总有面试官会追问我“渲染流程”的细节。这篇文章是我学习《浏览器工作原理与实践》的笔记,仅用作学习交流。

浏览器的多进程结构

Chrome 浏览器是多进程结构,由五个进程组成,分别是:浏览器进程、渲染进程、网络进程、GPU进程、插件进程。

  • 浏览器进程。主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
  • 渲染进程。核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中。
  • 网络进程。主要负责页面的网络资源加载。
  • 插件进程。主要是负责插件的运行。

流程概述

  1. 浏览器进程接收到用户输入的 URL 请求,浏览器进程便将该 URL 转发给网络进程。
  2. 网络进程接收到了响应头数据,便解析响应头数据,并将数据转发给浏览器进程。
  3. 浏览器进程接收到网络进程的响应头数据之后,发送“提交导航”消息到渲染进程;渲染进程接收到“提交导航”的消息之后,直接和网络进程建立数据管道,准备接收HTML数据。
  4. 渲染进程向浏览器进程“确认提交”;浏览器进程接收到渲染进程“提交文档”的消息之后,执行更新页面等操作。

具体流程

  1. 当用户在地址栏中输入一个查询关键字时,地址栏会判断输入的关键字是搜索内容,还是请求的 URL。

    • 如果是搜索内容,地址栏会使用浏览器默认的搜索引擎,来合成新的带搜索关键字的 URL。
    • 如果判断输入内容符合 URL 规则,比如输入的是 www.danmoits.com,那么地址栏会根据规则,把这段内容加上协议,合成为完整的 URL
  2. beforeunload 事件允许页面在退出之前执行一些数据清理操作,还可以询问用户是否要离开当前页面。

  3. 浏览器进程接收到用户输入的 URL 请求,浏览器进程便将该 URL 转发给网络进程。然后,在网络进程中发起真正的 URL 请求。标签的图标开始旋转。

  4. DNS解析、建立TCP连接、建立TLS连接、发送HTTP请求。网络进程解析响应头,并将数据转发给浏览器进程。

    • 处理重定向:如果发现返回的状态码是 301 或者 302,网络进程会从响应头的 Location 字段里面读取重定向的地址,然后再发起新的 HTTP 或者 HTTPS 请求。
    • 浏览器会根据 Content-Type 的值来决定如何显示响应体的内容。如果类型是text/html,接下来需要准备渲染进程。
  5. 准备渲染进程:

    • 默认情况下每个页面分配一个渲染进程。
    • 如果从一个页面打开了另一个新页面,而新页面和当前页面属于同一站点的话,那么新页面会复用父页面的渲染进程。这个默认策略叫 process-per-site-instance。
  6. 提交文档,最后渲染进程接收到了 HTML 数据,向浏览器进程“确认提交”,标志着这一阶段的结束。浏览器进程会更新浏览器界面状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新 Web 页面。

  7. 完成页面渲染,包括若干步骤。一旦页面生成完成,渲染进程会发送一个消息给浏览器进程,浏览器接收到消息后,会停止标签图标上的加载动画。

渲染流程

渲染进程有多个线程,前面的几步在主线程完成,而后面的步骤由其他线程完成。例如 tiles 是合成线程,raster 是栅格化线程...

image.png

主线程

  1. DOM:构建 DOM 树。在控制台输入“document”可以查看 DOM 树的结构。

  2. Style:样式计算,目的是计算出 DOM 节点中每个元素的具体样式。

    • 把 CSS 转化成浏览器可以理解的 stylesheets,document.stylesheets可以看到其结构: image.png
    • 使样式表的属性标准化,比如把长度单位 em 转换成 px,把颜色“red”转换成 rgb。
    • 运用 CSS 的继承规则和层叠规则,计算 DOM 结点的样式属性,这个阶段最终输出的内容是每个 DOM 节点的样式,并被保存在 ComputedStyle 的结构内。
  3. Layout:布局阶段,目的是计算出 DOM 树中可见元素的几何位置。

    • 创建布局树(Layout Tree):遍历 DOM 树中的所有可见节点,并把这些节点加到布局树中,而不可见的节点会被布局树忽略掉,如 head 标签下面的全部内容,或属性包含 dispaly:none 的结点。
    • 计算布局树节点的坐标位置。
  4. Layer:分层阶段。因为页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)。

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

    拥有层叠上下文属性的元素,以及需要裁剪的地方,会被提升为单独的一层。

  5. Paint:图层绘制,把一个图层的绘制拆分成很多小的绘制指令,生成待绘制列表。可以在浏览器控制台的 Layers 看到绘制列表,还可以看到实时绘制的样子。

image.png

非主线程

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

image.png

  1. tiles:把图层划分成图块。因为有的图层很大,但是用户只能看见视口(viewport)那么大的区域,所以没必要同时绘制所有图层内容,因此合成线程会将图层划分为图块(tile),每个图块大小为 256 * 256 或 512 * 512。在后续的绘制过程中,各位置图块的优先级不同。

image.png

  1. raster:合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的。

    栅格化过程都会使用 GPU 来加速生成,如果栅格化操作使用了 GPU,那么最终生成位图的操作是在 GPU 中完成的,会涉及到渲染进程和GPU进程的进程间通信。最后位图保存在 GPU 的内存中。

image.png

  1. draw quad + display:一旦所有图块都被栅格化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。浏览器进程里面有一个叫 viz 的组件,它会收到 DrawQuad 命令,然后通知GPU进程绘制页面。

渲染全过程图: image.png

渲染流水线相关的概念

重排

image.png

重绘

image.png

个人推测,如果进行了影响层叠上下文的修改,例如开启 flex,添加定位,或者是修改 z-index,将不会跳过 Layer 阶段。

合成

image.png