Inside look at modern web browser (part 3)
原文地址: https:// developers.google.com/w eb/updates/2018/09/inside-browser-part3
渲染进程的内部工作
这是一个由四篇文章组成的 Chrome 简介系列的第三篇,介绍了浏览器的工作方式。之前,我们介绍了 多进程体系架构 和 导航流程。在本文中,我们将研究渲染进程如何工作。
渲染进程涉及 Web 性能的许多方面。由于渲染进程内部做了很多事情,因此本文仅是概述。如果您想更深入地研究,the Performance section of Web Fundamentals 提供了更多资料。
渲染进程处理 Web 内容
渲染进程负责 Tab 页内的所有事情。在渲染进程中,主线程负责执行绝大部分发送给用户的代码。如果您使用 WebWorker 或者 ServiceWorker,那么这部分 JavaScript 代码是在 Worker 线程中执行。合成线程和光栅线程也运行在渲染进程内,以高效、流畅地渲染页面。
渲染进程的核心工作是将 HTML,CSS 和 JavaScript 转换为用户可与之交互的网页。
![f51d95cf52e072d1db4087636a7e5952.png](https://i-blog.csdnimg.cn/blog_migrate/1512b6c2bb1c986d8f7c253f63ea1ece.jpeg)
解析(Parsing)
构造 DOM
当渲染进程接收导航提交消息并开始接收 HTML 数据时,主线程就开始解析文本串,将 HTML 转换成一个 Document Object Model(DOM)。
DOM 是浏览器对 Web 开发人员与页面交互的数据和 API 的抽象。
HTML 标准 定义了浏览器应该如何将 HTML 解析成 DOM。您可能已经注意到,将 HTML 发送到浏览器永远不会报错。例如,缺少结束</p>
标记是有效的 HTML。比 Hi! <b>I'm <i>Chrome</b>!</i>
(b
标签在 i
标签之前关闭)这样的错误代码会被转成 Hi! <b>I'm <i>Chrome</i></b><i>!</i>
。这是因为 HTML 规范旨在优雅地处理这些错误。如果您对如何完成这些操作感到好奇,可以阅读 HTML 规范的“解析器中的错误处理和奇怪情况简介”部分。
资源加载
一个网站通常会使用一些外部资源,如图片、JavaScript 脚本、CSS 样式文件。这些外部资源需要通过网络或者缓存加载。主线程可以在解析并构建 DOM 的时候找到这些外部资源并一个接着一个的请求相应资源,但为了加快速度,“预加载扫描器”会并行运行。如果在 HTML 文档中出现类似 <img>
或者 <link>
标签,预加载扫描器会查看 HTML 解析器生成的分词结果,并把相应资源请求发送给游览器进程中的网络线程。
![d6152cf7083ce897455b0a66b96e8536.png](https://i-blog.csdnimg.cn/blog_migrate/89aa0b3c4105611baaf5a817bac67de6.jpeg)
JavaScript 会阻塞解析
HTML 解析器遇到 <script>
标签时,它会暂停 HTML 文档的解析工作,不得不加载、解析并执行 JavaScript 代码。为什么呢?因为 JavaScript 可能会使用诸如 document.write()
修改文档,这可能改变整个 DOM 结构(解析模型概述 中有很好的图解)。
![3118d214144be2b97b8da700e99941c1.png](https://i-blog.csdnimg.cn/blog_migrate/30cc1fb0f82b335b2e1902074ed190d9.jpeg)
这就是为什么 HTML 解析器必须等待 JavaScript 执行才能继续解析 HTML 文档的原因。如果你对于 JavaScript 执行时发生了什么感兴趣,V8 团队有相关的演讲和博文(译文点这)。
提示浏览器您想如何加载资源
Web 开发人员有很多方法可以提示浏览器如何优雅地加载资源。如果你的 JavaScript 没有使用 document.write()
之类的代码,您可以在 script 标签上添加 async
或 defer
标志。这样,浏览器就会异步加载、运行 JavaScript 代码,而不会阻塞 HTML 解析过程。条件允许的话,您也可以使用 JavaScript module(module 默认 defer
)。此外,<link rel="preload">
可以提示浏览器某资源是本次导航必要资源,您想尽可能快的下载这些资源。您可以通过阅读 资源优先级 – 让浏览器帮您 了解更多。
计算样式
仅仅有 DOM 还不足以让浏览器知道页面看上去应该怎样,因为我们可以通过 CSS 来装饰我们的页面元素。主线程为解析 CSS 并对每一个 DOM 节点计算出样式结果。您可以通过 DevTools 的计算结果面板,查看有关基于 CSS 选择器每个元素使用了哪些样式。
![ee47de1ecc68569f990f3233006b302c.png](https://i-blog.csdnimg.cn/blog_migrate/c02cf98586991d18a4d9d23dbbc2da10.jpeg)
即使您不提供任何 CSS,每个 DOM 节点也具有计算后的样式。<h1>
标签比<h2>
标签显示的内容大,并且每个元素都定义了外边距。这是因为浏览器有默认样式表。如果您想知道 Chrome 的默认 CSS 是什么样的, 可以在此处查看源代码。
布局
现在,渲染进程知道了文档中每个节点的结构和样式,但这不足以渲染页面。想象一下,您正在尝试通过电话向您的朋友描述一幅画。“有一个大的红色圆圈和一个小小的蓝色方块”不足以让您的朋友知道这幅画的模样。
![4fc7e8d82e226fe71a52f3c3f95e717d.png](https://i-blog.csdnimg.cn/blog_migrate/65b2e0f450a5601057cd0a8336b00577.jpeg)
布局是查找元素几何形状的过程。主线程遍历 DOM 和计算后的样式,并创建布局树,该树具有诸如 x、y 坐标和边界大小之类的信息。布局树的结构可能与 DOM 树类似,但它仅包含页面上可见内容有关的信息。如果 display: none
应用,则该元素不属于布局树(但是,具有 visibility: hidden
的元素,还是会在布局树中)。类似的,如果应用了具有类似内容的伪类p::before { content: "Hi!" }
,则即使它不在 DOM 树中,它也将包含在布局树中。
![351dee179e84a4b5de4fe35b5a9522d4.png](https://i-blog.csdnimg.cn/blog_migrate/40ad7511a8539706ffb9d4500e21164b.jpeg)
确定页面的布局是一项艰巨的任务。即使是最简单的页面布局(例如:从上到下的布局的块级元素)也必须考虑字体的大小以确定何处换行,因为这会影响段落的大小和形状,并影响下一段的位置。
知乎视频www.zhihu.comCSS 可以使元素浮动到一侧、隐藏溢出项并更改书写方向。您可以想象布局是多么艰巨的任务。在 Chrome 中,有一整组工程师团队在负责布局工作。如果你想了解他们详细的工作,BlinkOn Conference 记录了一下非常有趣的演讲。
绘制
拥有 DOM、样式和布局仍然不足以呈现页面。假设您正在尝试复制一幅画,您知道元素的大小、形状和位置,但是仍然需要判断以什么顺序绘制它们。
例如,某些元素可能设置了z-index
,在这种情况下,按 HTML 中编写的元素顺序进行绘画就会导致错误的呈现。
![de4b035d1a65d66d043a6fcfeebd57fd.png](https://i-blog.csdnimg.cn/blog_migrate/1c8f942fd93a8d1c39d8cfc4d10109ce.png)
![fa21fa343f5e054eefd1ae6e25526d78.png](https://i-blog.csdnimg.cn/blog_migrate/94d99e2a1cdf562a4a17205e9c5972b7.jpeg)
在此绘制过程中,主线程遍历布局树以创建绘制顺序记录,例如“先画背景,然后是文本,然后是矩形”。如果您使用过 JavaScript 在 <canvas>
上绘制元素,您可能会熟悉此过程。
![9de026772eded80a3266a86173d092fb.png](https://i-blog.csdnimg.cn/blog_migrate/9104073a302ee53198d1acd9b2128ffe.jpeg)
更新渲染管线的成本很高
在渲染管线中要掌握的最重要的事情是,在每个步骤中,先前操作的结果都用于创建新数据。比如,布局树中发生某些更改,则需要为文档的受影响部分重新生成绘制顺序记录。
知乎视频www.zhihu.com如果要给元素添加动画效果,浏览器必须在每帧之间运行这些操作。大多数显示器每秒刷新屏幕 60 次(60 fps)。当您在每一帧中在屏幕上移动物体时,动画将显得平滑。但是,如果动画在帧之间没有运行,则页面将产生“抖动(janky)”。
![9620801543350269f9b3886f4d83bd8f.png](https://i-blog.csdnimg.cn/blog_migrate/dd1f84f72f7af8827602f39a1267fd4e.png)
即使渲染操作与屏幕刷新保持同步,由于这些计算仍在主线程上运行,所以当您的应用程序运行 JavaScript 代码时,渲染操作会被阻塞。
![539128a095f6b41cbd09a2f3d7432caf.png](https://i-blog.csdnimg.cn/blog_migrate/6058b6165d6773e445c58d4f5953c3f5.png)
您可以将 JavaScript 的操作切分成小块,然后使用 requestAnimationFrame()
在每一帧中运行一部分。关于这个话题跟多的内容,请看优化 JavaScript 执行。您也可以在 Worker 线程中运行 JavaScript 来避免阻塞主线程。
![195f85f616844eb41e855ceb5ce91973.png](https://i-blog.csdnimg.cn/blog_migrate/2f2223726ec6e9bb4859fc751542b187.png)
合成
如何绘制页面
现在,浏览器知道了文档的结构、每个元素的样式、页面的几何形状和绘制顺序,接下来如何绘制页面?将此信息转换为屏幕上的像素点称为栅格化。
处理这一问题的一种天真的方法可能是在栅格化视口部分。如果用户滚动页面,则移动栅格化过的帧,并栅格化更多内容来填充空白部分。这就是 Chrome 刚发布时处理栅格化的方式。但是,现代的浏览器使用一种更复杂的栅格化过程,称为合成。
知乎视频www.zhihu.com什么是合成
合成是一种将页面的各个部分分成若干层,分别对其进行栅格化并在称为合成器线程的单独线程中作为页面进行合成的技术。在发生滚动时,由于图层已经被栅格化过,所以要做的只是合成一个新的帧。动画也可以通过这样移动图层、合成新的帧来实现。
您可以在 DevTools 的 “层” 面板查看您的网站如何被划分图层。
知乎视频www.zhihu.com划分图层
为了找出哪些元素需要位于哪个图层中,主线程需要遍历布局树来创建图层树(在 DevTools 性能面板中,此部分称为“更新图层树”)。如果页面的某些部分应该是单独的层(如滑入式侧边菜单)却没有显示为单独的图层,则可以使用 CSS 属性 will-change
提示浏览器为元素生成单独图层。
![2b2b84606e96a066a9cf66f413ac5199.png](https://i-blog.csdnimg.cn/blog_migrate/c2e5c45ad84816fc203bfeb1fe55a814.jpeg)
您可能很想为每个元素提供图层,但是对于每帧,进行合并时存在过多的图层会比对页面的一小部分进行栅格化更慢,因此,测量应用程序的渲染性能至关重要。有关此主题的更多信息,请参见 只使用合成器属性及控制层数。
脱离主线程的栅格化和合成
当创建了图层树并确定了绘制顺序,主线程便将这些信息提交给合成线程。然后,合成线程将每个图层栅格化。一个图层可能像页面的整个长度一样大,因此合成线程将它们划分为小的图块,并将每个图块发送给栅格线程。栅格线程栅格化每个图块并将其存储在显存中。
![01d0490ac5f80ac49049115b1028c6e8.png](https://i-blog.csdnimg.cn/blog_migrate/c5701a5fdcf99a2e59d69d2727e3958b.jpeg)
合成线程可以对不同的栅格线程给予不同的优先级,以便优先对视口(或附近)的图层进行栅格化。图层还具有不同分辨率的多个图块,以处理诸如缩放之类的操作。
栅格化后,合成线程会收集用来创建“合成帧(compositor frame)”的图块信息,我们称之为“绘制四边形(draw quads)”信息。
- 绘制四边形:包含诸如图块在内存中的位置,以及在页面合成情况下,在页面的什么位置绘制图块
- 合成帧:描述页面一帧的绘制四边形的集合
然后,合成帧通过 IPC 提交给浏览器进程。此时,另一个合成帧(来自浏览器进程的 UI 变更或者扩展插件的渲染进程)就可以接着添加。这些合成帧被发送到 GPU,以将其显示在屏幕上。如果发生滚动事件,则合成线程会创建另一个合成帧发送到 GPU。
![9d33c96a2f419d4ce3830e6144e40e8b.png](https://i-blog.csdnimg.cn/blog_migrate/b3d49c084087f40f75bf88c641b0e590.jpeg)
合成的好处是不需要主线程参与。合成线程无需等待样式计算或 JavaScript 执行。这就是为什么合成动画被认为是获得最佳平滑效果的原因。如果需要重新布局或绘制,则必须占用主线程。
下面的网站例举了 CSS 属性会涉及哪些渲染步骤:
https://csstriggers.com/csstriggers.com总结
在本文中,我们研究了从解析到合成的渲染管线具体发生了什么。希望您现在更容易地理解有关网站性能优化的知识。
在本系列的下一篇文章中,我们将更详细地研究合成线程,并了解用户输入时(如,移动鼠标、点击鼠标)发生了什么。
Pegasus:[翻译] 瞧一瞧现代游览器如何工作?Part 1zhuanlan.zhihu.com![4e3c04156295479ea6dce85fc77e8d93.png](https://i-blog.csdnimg.cn/blog_migrate/7e7225d126c6152ab000678466583c49.jpeg)
![1b5e3d3e050af4b892cad5e00bf18d49.png](https://i-blog.csdnimg.cn/blog_migrate/5adf8c105ff97408b51f88dd4ddc5daf.jpeg)
![897db04f00fed580804962083dc22e24.png](https://i-blog.csdnimg.cn/blog_migrate/749972088fb97673c691a05e136289d6.jpeg)