像素的一生—浏览器渲染流水线简述

文章来自于
https://mp.weixin.qq.com/s/9lX8VgMmtXRjFdf7zkOFMQ,@Big前端

提到浏览器不得不说Chrome,Chrome是Google发行的商业产品,而Chromium是一个开源版本的Chrome,两者很像但是不完全一样。

这里尝试将自己的理解结合下方PPT用最直白的语言记录最近了解到的浏览器的渲染原理知识,方便后续查阅。因为涉及到的知识点非常多且繁杂,如果有表述不到位的地方敬请谅解,错别字/错误理解之类的欢迎联系我修改。

PPT内容整体来自Chromium开发者,Steve Kobes的演讲,PPT地址

https://docs.google.com/presentation/d/1boPxbgNrTU0ddsc144rcXayGA_WF53k96imRH8Mp34Y/edit#slide=id.ga884fe665f_64_1691

浏览器分层架构

简单的说浏览器作为应用,底层分别有content,Blink,V8,Skia等等,一层一层像套娃一样一层引用一层。对比普通应用的项目来说就是不断用第三方库和组件来拼凑应用,Chrome也不例外

  • content可以理解为就是除了浏览器主进程下的书签导航之外, 页内容这一部分,会随着 页不同而变化的部分
  • Blink渲染引擎,应该都听过就是 页的排版引擎,现存的Chrome/Edge都在用,作为开源项目维护,是在渲染进程里
  • Blink又嵌套了V8 JavaScript engine来执行JS代码
  • 渲染

    页的渲染可以表示为Content经过rendering最后呈现的过程,即Code -> 可交互的页面

    content是什么?

    可以看到content就是WebContents对象,C++代码的一个类。其代表的区域其实是标签页页打开的部分(上图红色部分)。而浏览器主进程还包含有地址栏、导航按钮、菜单、扩展,安全提示的小弹窗等等。

    渲染进程render process是一个沙盒,基于安全考虑单独渲染进程render process挂了不会引起整个浏览器挂

    渲染进程render process包含Blink渲染排版引擎和Chromium compositor(上图中绿色的CC简写)

    content还表示 页内容代码,有HTML,CSS,JS,图片等,还有video,canvas,WebAssembly,WebGL等都可能在content区域显示或者运行

    综上,content就是 页代码最后运行的结果,浏览器开发者工具可以看到最后是一个经过处理后的HTML的结构。「而这个HTML在渲染流水线里是一个输入」

    像素是如何呈现的?

    写过C/C++代码的同学知道,我们必须使用操作系统提供的底层API去画图,操作系统底层又去调用驱动程序,驱动程序驱动硬件。

    今天大多数平台上都提供了“OpenGL”的标准化API。在Windows上有一个额外的DirectX转换。这些库提供诸如“纹理”和“着色器”之类的低级图形基元,并允许执行类似“在这些坐标处绘制一个三角形到虚拟像素缓冲区”之类的底层操作。未来计划用Vulkan替代Skia来做底层图形化调用。

    所以渲染流水线的整个过程就是将输入的HTML、CSS、JS转化为OpenGL调用,最后在屏幕上呈现像素

    浏览器渲染的目标

    1. 初次渲染,将 页内容转化为底层OpenGL调用去显示页面
    2. 更新渲染,在JS运行,用户输入,异步请求或者滑动等交互介入后,再次渲染页面起到交互的目的,而且这里再次渲染需要高效执行,你会想到缓存对吗?是的每个阶段的结果为了提高渲染效率而被缓存下来。还有JS API会查询一些渲染数据如某个DOM节点的信息

    拆分渲染阶段

    把渲染管道分成多个阶段的话,可以看出来原来的content内容会被各个阶段stage处理为中间数据,最后才呈现为画面呈现出来。

    DOM

    解析为DOM

    HTML嵌套解析,解析时候解析为数据对象映射反应这里的嵌套模型

    DOM(Document Object Model)是一棵树,树有父子,邻居的关系,而且这棵树是暴露API给JS调用,JS可以查询和修改这棵树。JS引擎V8通过bindings的系统将DOM包装为DOM API供给Web开发者调用

    文档可能包含多个DOM树

    如上图的示例,自定义元素custom element有shadow tree。ShadowRoot的子元素其实被嵌入到slot元素里了,这跟各大前端框架的slot其实很像。

    其实最后是在遍历树后合成视图,也就是两棵树合并为一棵树

    Style

    style步骤依赖前置的DOM树解析结果,选择器是选择DOM节点集合决定最后应用范围,最后样式生效是多个选择器共同作用的结果,而且样式间可能互相冲突导致没有按照预期运行,关于选择器的优先级感兴趣的同学自行查阅

    CSS解析器样式表StyleSheet构建样式规则。样式表可能位于<style>元素、单独加载的资源的css文件中,也可能由浏览器默认提供。样式规则以各种方式编制索引以实现高效查找。
    属性类在构建时由Python脚本自动生成,以声明方式定义了所有样式属性,如上图右上侧css_properties.json经过py脚本转化为.cc文件。

    ComputedStyle

    样式的重新计算(recalc)从活动样式表中获取所有解析的样式规则,并计算每个DOM元素的每个样式属性的最终值。这些内容存储在一个名为ComputedStyle的对象中,该对象只是样式属性到值的映射。可以看到其实每一个DOM节点都对应有一个ComputedStyle对象

    在Chrome浏览器里的话,就是对应开发者工具的Computed样式属性这一栏。或者是通过getComputedStyle的JSAPI去获取。

    Layout

    在DOM和Style计算好后开始进入布局Layout阶段,比如将DIV解析为一个块级的LayoutReat区域,用x+y+width+height来表示,布局就是为了计算x,y,width,height这些数据

    默认情况下文档按照顺序排列下去形成了文档流

    文字和内联元素则是左右浮动的,而且内联元素会被行尾打断(自动换行)。当然也有从右到左的语言,比如阿拉伯语和希伯来语

    布局也包括字体的排列,因为布局需要考虑文本在那里进行换行,Layout使用名为HarfBuzz的开源文本库来计算每个字形的大小和位置,这决定了文本的总宽度。字体成型必须考虑到排版特征,如字距调整letter-spacing和连字。

    布局可以计算单个元素的多种边界矩形。例如,当存在溢出时,Layout将同时计算边界框和布局溢出。如果节点的溢出是可滚动的,Layout还会计算滚动边界并为滚动条预留空间。最常见的可滚动DOM节点是文档本身

    表格元素或display:table的样式需要更复杂的布局,这些元素或样式指定将内容分成多列,或浮动对象漂浮在一边,内容在其周围流动,或者东亚语言的文本垂直排列,而不是水平排列。请注意DOM结构和ComputedStyle值(如“Float:Left”)是布局算法的输入。「渲染流水线的每个阶段都会使用到前面阶段的结果」

    通过遍历DOM树创建渲染树LayoutTree,节点一一对应。布局树中的节点实现布局算法。根据所需的布局行为,LayoutObject有不同的子类。比如LayoutBlockFlow就是块级Flow的文档节点。样式更新阶段也构建布局树。

    在样式解析最后结束时需要构建布局树LayoutTree,布局阶段遍历布局树,对布局树每个节点LayoutObject执行布局,计算几何数据、换行符,滚动条等。

    DOM节点跟Layout节点不一定是一一对应

    一般情况下一个DOM节点会有一个LayoutObject,但是有时候LayoutObject是没有DOM节点与之对应的。
    比如上图,
    span标签外部没有section标签嵌套,但是LayoutTree会自动创建LayoutBlock的匿名节点与之对应,再比如样式有display:none的样式,那么也不会创建对应的LayoutTree

    最后,如果是shadowTree的话,其LayoutObject节点可能会在不同的TreeScope

    布局引擎正在重写

    如上图所示,「LayoutNG」代表下一代的布局引擎,2020年布局引擎还在过渡阶段,所以有中间形态,如上图包含了LayoutObjectLayoutNGMixin混合节点。未来所有节点都会变成LayoutNGlayout object

    NG节点的更新主要是因为之前的节点包含了输入、输出还有布局算法的信息,也就是说单个节点可以看到整棵树的状态(节点有可能需要获取父节点的宽高数据,但是父节点正在递归子节点布局中,实际上还没确定最后的布局)。

    而新的NG节点对输入和输出做了明显的区分,而且输出是immutable的,可以缓存结果

    布局结果指向描述物理几何的片段树,如图一个NGLayoutResult对应几个NGPhysicalFragment,对应右上角的几个矩形图形,如果NGLayoutResult没变化则对应整块都不会变化。

    举一个布局例子

    上图的HTML代码,会渲染如右下角的例子,对应的DOM树如左侧所示

    DOM树跟Layout树很像,节点几乎是一一对应的,但是注意这里anonymous匿名节点被创建出来,它只有一个块级子元素。一个布局节点只能拥有块级元素或者内联元素其中之一

    声明:本站部分文章内容及图片转载于互联 、内容不代表本站观点,如有内容涉及侵权,请您立即联系本站处理,非常感谢!

    (0)
    上一篇 2022年2月6日
    下一篇 2022年2月6日

    相关推荐