这个问题面试中经常出现,每次我都能说个大概,但是总有面试官会追问我“渲染流程”的细节。这篇文章是我学习《浏览器工作原理与实践》的笔记,仅用作学习交流。
浏览器的多进程结构
Chrome 浏览器是多进程结构,由五个进程组成,分别是:浏览器进程、渲染进程、网络进程、GPU进程、插件进程。
- 浏览器进程。主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
- 渲染进程。核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中。
- 网络进程。主要负责页面的网络资源加载。
- 插件进程。主要是负责插件的运行。
流程概述
- 浏览器进程接收到用户输入的 URL 请求,浏览器进程便将该 URL 转发给网络进程。
- 网络进程接收到了响应头数据,便解析响应头数据,并将数据转发给浏览器进程。
- 浏览器进程接收到网络进程的响应头数据之后,发送“提交导航”消息到渲染进程;渲染进程接收到“提交导航”的消息之后,直接和网络进程建立数据管道,准备接收HTML数据。
- 渲染进程向浏览器进程“确认提交”;浏览器进程接收到渲染进程“提交文档”的消息之后,执行更新页面等操作。
具体流程
-
当用户在地址栏中输入一个查询关键字时,地址栏会判断输入的关键字是搜索内容,还是请求的 URL。
- 如果是搜索内容,地址栏会使用浏览器默认的搜索引擎,来合成新的带搜索关键字的 URL。
- 如果判断输入内容符合 URL 规则,比如输入的是
www.danmoits.com,那么地址栏会根据规则,把这段内容加上协议,合成为完整的 URL
-
beforeunload 事件允许页面在退出之前执行一些数据清理操作,还可以询问用户是否要离开当前页面。
-
浏览器进程接收到用户输入的 URL 请求,浏览器进程便将该 URL 转发给网络进程。然后,在网络进程中发起真正的 URL 请求。标签的图标开始旋转。
-
DNS解析、建立TCP连接、建立TLS连接、发送HTTP请求。网络进程解析响应头,并将数据转发给浏览器进程。
- 处理重定向:如果发现返回的状态码是 301 或者 302,网络进程会从响应头的 Location 字段里面读取重定向的地址,然后再发起新的 HTTP 或者 HTTPS 请求。
- 浏览器会根据 Content-Type 的值来决定如何显示响应体的内容。如果类型是
text/html,接下来需要准备渲染进程。
-
准备渲染进程:
- 默认情况下每个页面分配一个渲染进程。
- 如果从一个页面打开了另一个新页面,而新页面和当前页面属于同一站点的话,那么新页面会复用父页面的渲染进程。这个默认策略叫 process-per-site-instance。
-
提交文档,最后渲染进程接收到了 HTML 数据,向浏览器进程“确认提交”,标志着这一阶段的结束。浏览器进程会更新浏览器界面状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新 Web 页面。
-
完成页面渲染,包括若干步骤。一旦页面生成完成,渲染进程会发送一个消息给浏览器进程,浏览器接收到消息后,会停止标签图标上的加载动画。
渲染流程
渲染进程有多个线程,前面的几步在主线程完成,而后面的步骤由其他线程完成。例如 tiles 是合成线程,raster 是栅格化线程...
主线程
-
DOM:构建 DOM 树。在控制台输入“document”可以查看 DOM 树的结构。
-
Style:样式计算,目的是计算出 DOM 节点中每个元素的具体样式。
- 把 CSS 转化成浏览器可以理解的 stylesheets,
document.stylesheets可以看到其结构: - 使样式表的属性标准化,比如把长度单位 em 转换成 px,把颜色“red”转换成 rgb。
- 运用 CSS 的继承规则和层叠规则,计算 DOM 结点的样式属性,这个阶段最终输出的内容是每个 DOM 节点的样式,并被保存在 ComputedStyle 的结构内。
- 把 CSS 转化成浏览器可以理解的 stylesheets,
-
Layout:布局阶段,目的是计算出 DOM 树中可见元素的几何位置。
- 创建布局树(Layout Tree):遍历 DOM 树中的所有可见节点,并把这些节点加到布局树中,而不可见的节点会被布局树忽略掉,如 head 标签下面的全部内容,或属性包含 dispaly:none 的结点。
- 计算布局树节点的坐标位置。
-
Layer:分层阶段。因为页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)。
并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。
拥有层叠上下文属性的元素,以及需要裁剪的地方,会被提升为单独的一层。
-
Paint:图层绘制,把一个图层的绘制拆分成很多小的绘制指令,生成待绘制列表。可以在浏览器控制台的 Layers 看到绘制列表,还可以看到实时绘制的样子。
非主线程
当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程。
- tiles:把图层划分成图块。因为有的图层很大,但是用户只能看见视口(viewport)那么大的区域,所以没必要同时绘制所有图层内容,因此合成线程会将图层划分为图块(tile),每个图块大小为 256 * 256 或 512 * 512。在后续的绘制过程中,各位置图块的优先级不同。
-
raster:合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的。
栅格化过程都会使用 GPU 来加速生成,如果栅格化操作使用了 GPU,那么最终生成位图的操作是在 GPU 中完成的,会涉及到渲染进程和GPU进程的进程间通信。最后位图保存在 GPU 的内存中。
- draw quad + display:一旦所有图块都被栅格化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。浏览器进程里面有一个叫 viz 的组件,它会收到 DrawQuad 命令,然后通知GPU进程绘制页面。
渲染全过程图:
渲染流水线相关的概念
重排
重绘
个人推测,如果进行了影响层叠上下文的修改,例如开启 flex,添加定位,或者是修改 z-index,将不会跳过 Layer 阶段。