引子
作为一名前端工程师,我们必须时刻保持高度的好奇心与求知欲,提出问题并去探索其原因或机制。
于是,我抛出一个问题,浏览器是如何将“没有趣味”的代码变成“五彩缤纷”的网页的?
如下图,我将介绍,这个 rendering 的过程。
参考:docs.google.com/presentatio…
CONTENT
首先,我们要明确一个概念,我们我指的网页内容是什么,content负责的是哪个区域,从架构角度出发,Chromium c++代码库中的 “content”的命名空间所负责的是图片中红框里面的所有内容。即,标签栏,地址栏,导航按钮,菜单这些都不属于网页的内容。
content是Chromium中的通用术语,指的是网页内或者web应用程序前端的所有代码。基本的构建单位是
- 其他,当然还有很多其他渲染内容,比如: canvas,video,webGL...
我们打开网页,按下F12,开启新的世界,我们会发现啥?没错,其实一个真正的网页也只是数千行的html,css,javascipt。而这个源代码其实就是渲染器的输入,不需要什么繁琐的流程,又编译又打包的,我们只需要给它源代码就ok,这种简单性也是网络早期成功的关键。
chrome安全模型中,渲染的过程是在沙盒中进行的。Blink是渲染进程代码的子集,在网页的内容层之下。Blink实现了web平台api和web规范的语义。
于此同时,浏览器进程同时运行了一个被称为“compositor(cc)”的组件,裆盐,这个cc在后面我也会介绍。
ps:前面提到了一个名词,Chromium是个啥东西呢,相比大家作为开发者,chrome肯定是熟悉的不行,毕竟chrome天下无敌也是经常挂在嘴边,我就简单介绍一下Chromium,它是一个由Google主导开发的网页浏览器。Chromium的开发可能早自2006年即开始,设计思想基于简单、高速、稳定、安全等理念,在架构上使用了Apple发展出来的WebKit引擎、Safari的部份源代码与Firefox的成果,并采用Google独家开发出的V8引擎,以提升解译JavaScript的效率(在Chrome中,只有htm渲染采用了webkit的代码,而js上自己搭建了一个牛逼的V8引擎,webkit+v8强强联手,当然这其中也一定有许多的“爱恨纠葛”,我就不在此多言了)。Chromium是Google为发展自家的浏览器Google Chrome(以下简称Chrome)而开启的计划,所以Chromium相当于Chrome的工程版或称实验版(尽管Chrome自身也有β版阶段),新功能会率先在Chromium上实现,待验证后才会应用在Chrome上,故Chrome的功能会相对落后、稳定。Chromium的更新速度很快。
Pixels
在整个渲染管道的另一头,我们必须要使用底层操作系统提供的图形库来将像素放到屏幕上。在今天的大多数平台上,那是一个被称为“OpenGL”的标准化API。在Windows上,有一个额外的转换到DirectX。将来,我们可能会支持新的api,比如Vulkan(最早由柯纳斯组织提出,在2015年游戏开发者大会上发表,最开始把Vulkan称为:下一代OpenGL行动,或者glNext,但是正式宣布之后这些名字就没有再用过了,Vulkan计划提供高性能和低CPU管理负担。。。)。 这些库提供了底层的图形原语,如“纹理”和“着色器”,并让你做到类似于“在这些坐标上绘制一个三角形到虚拟像素缓冲区”这样的事情。
Goals
于是经过上面的话,我们明白了,整个渲染的过程的目标就是将 html/css/js 转换到 正确的opengl调用来调整像素的样式。不仅于此,我们还有另外一个目标,就是我们也需要一个正确的中间数据结构,以便于我们在绘制完成后去有效的去进行更新。
触发更新的原因也有很多:
- js脚本触发更新
- 用户的输入
- 异步加载
- 动画
- 滚动条滚动
- ...
我们将管道分为很多个“生命周期阶段”,生成中输出,我先会去表述工作管道的每个阶段,之后再回到高效更新的概念。
DOM
html文档中加入了一个语义上有意义的层次结构。 比如一个div可以包含两个段落,每个段落都有文本。于是,第一步就是去解析这些标签,来建立一个对象模型,去映射这个结构。
<div> <p> hello </p> <p> world </p> </div>

比如上面这个代码,HTML标签可以嵌套,div中包含了两个p标签,p标签中又包含了文本。于是最后会解析出这样的树形结构,当然,我也建议所有开发者去学习一下 《编译原理》,即使是前端开发。
ok,看上图,有一点计算机基础的人就会发现这是一个树形结构。他的名字是 Document Object Model 文档对象模型(很重要,要考的,嘿嘿),DOM是一个树形结构。
DOM有双重功能:
- 页面的内部表示
- 也是暴露给脚本用于在渲染过程中查询和修改的API接口
javascript引擎(V8)通过一个被称为“binding”的系统,将DOM web api作为实际DOM树的瘦身后的包装器暴露了出来,一个文档里面可能有多个dom树,自定义元素有一个阴影树。主树中的阴影树的孩子会被分配到阴影树的槽中。
FlatTreeTraversal算法会将其转化:(从host到 shadow的根节点,从slot到assigned节点)
STYLE
在构建DOM树之后下一步就是处理css的样式,css选择器的属性声明会 委派到 他所选择的dom元素中去
样式属性是网页作者用来影响dom元素呈现的手段,有数百种样式属性。
此外,确定样式规则选择哪些元素并不简单,有些元素可能被多个规则选择,对于特定的样式属性的声明也有可能存在冲突,比如下面的代码。(复杂性)
div:not(.foo) > p:nth-of-type(2n) { color: red !important; } p { color: blue; }

CSS解析器从每个活跃的的样式表构建样式规则的模型。样式表可以位于< Style >元素中,也可以是一个单独加载的资源(styles.css),或者由浏览器提供。
样式解析器从活跃的样式表中提取所有已解析的样式规则,并为每个DOM元素计算每个样式属性的最终值。这些存储在一个名为ComputedStyle的对象中,该对象是从一个样式属性到值的巨大映射。
如上图,Chrome开发者工具会显示任何DOM元素的“计算样式”。这也会暴露给javascript。这些是基于Blink的计算属性对象的。(但是一些属性只是布局数据的增强)