转载请注明原文地址:http://blog.csdn.net/milado_nju/article/details/7685517
# CSS在WebKit和Chromium中的实现
## 概述
前面章节介绍了CSS的三种基本要素,大概可以分成选择器,各种基本样式和CSS3引入的变形、变换和动画等。本章在此基础上,着重介绍CSS是如何在WebKit和Chromium得到支持的。首先介绍的是CSS解析器,而后分别阐述上面三种基本要素如何在WebKit和Chromium中实现的。
接前面章节,这里仍然以之前的CSS例子为基础来介绍本章的内容。为方便起见,依旧包含该例子,如下图所示。
## CSS解析器
CSS的词法其实并不复杂,其分析工作由bison完成,具体实现可以在文件CSSGrammar.y中找到,详细过程这里不在赘述。其主要工作是解析词法并调用CSSParser的回调函数来生成结果,例如上例中的第4行到第17行,第4行首先是识别选择器,后面是各个属性的识别,最后,这些全部构成了基本的StyleRule类型(关于类型会在后面介绍),所以调用CSSParser的createStyleRule函数,该函数将选择器和属性列表的中间表示最后处理生成其内部表示保存在一个规则StyleRule中。对于其他的类型,作类似的处理。
解析器对上层的接口是CSSParser,所有任务均有其处理。那么上面例子中有哪些场景会需要创建CSSParser的实例呢?显而易见地有两处地方,第一个是第4行到第17行,当DOM建立好之后在创建RenderObject的时候会调用CSSParser;第二个是第29行,在该段JavaScript代码被执行时,JavaScript引擎会间接调用CSSParser为元素的属性style解析。但其实还有一个地方会使用到CSSParser。
实际上WebKit(其他渲染引擎应该也类似吧K)会为每个网页设置一个缺省的样式,这决定了你所没有设置的元素及其它们的属性缺省值和将要显示的效果。通常来讲,不同的WebKit的移植(port)会有不同的缺省样式。
解析后的结果应该是一系列的样式规则,下面让我们来看看这些规则在WebKit中的内部表示。
## 样式的内部表示
被解析后的CSS样式其实就是一组样式规则,每一个规则包含一组选择器和一组样式属性。如前面例子中第4行到第17行所示。这些数据在WebKit中都有相应的内部表示,为了便于理解,下图给出了CSS的表示和内部结果表示的对应图。
值得一提的是选择器部分,上图是一个选择器列表,这在现实中是比较常见的。举个例子, a[class=abc]其实是两个选择器,第一个是’a’,它一个标签选择器;第二个是’[class=abc]’,它是一个属性选择器。
这里例子中的样式规则是一种基本的样式类型。CSS的标准包含了多种规则类型:
Style:这个是基本类型,一般大多数规则属于这个类型;
Import:是WebKit中为方便引入的,其对应的是一个导入CSS文件的Style元素
Media: 对应于CSS标准中的@media类型
Fontface: CSS3新引入的自定义字体的规则类型
Page: 对于CSS标准中的@page类型
Keyframes: WebKit的@-webkit-key-frames类型,可以用来指定特定帧的样式属性信息
Region:对CSS标准正在进行中的Regions的支持,这方便了开发者对页面进行分区域来排版。详见参考文献2.
下图给出了这些类型在WebKit中的定义及其关系。
这里有必要解释一下StyleRuleImport类,这个一个伪类型,CSS中并没有该类型的定义,只是WebKit处理CSS文件方便引入的。该类需要两个类来辅助完成,一个是StyleSheetContents类,就是CSS文件中各个规则的处理和内部表示,这些规则被称为子规则,构成一个列表;另外一个是CSSStyleSheet,该类是个包装类,代表CSS文件,包含一个StyleSheetContents对象。
## 选择器(CSSSelector类)
CSS选择器的实现并不复杂,其实现由类CSSSelector来完成。CSSSelector的作用是储存从解析器生成的结果信息以被匹配使用。这里匹配指的是当需要为每个DOM中的节点计算样式适合,WebKit需要根据当前的节点信息来从规则列表中找到能够符合调节的规则,并把规则中的属性列表提取出来生成节点的样式信息。
在CSS介绍那一章中,我们知道有多达42种选择器类型,例如。在WebKit的CSSSelector类中,它们被称为伪类型(PseudoType),其含义是表示CSS标准中定义的类型,而主要用于匹配算法的类型定义为Match,它包含Id, Class, Extract, Set, List, Hyphen,PseudoClass,PseudoElement, Contain, Begin, End, PagePseudoClass。这些类型是通过抽象标准中的类型而得来,例如:first-of-type,:last-of-type,:only-of-typed等等都属于PseudoClass。具体的匹配算法参考代码(文件ElementRuleCollector.cpp),这里不在赘述。
### StyleResolver和RenderStyle
在样式规则解析完成之后,剩下的问题是如何把这样规则应用到具体的元素上。这就涉及到两个主要的类:StyleResolver和RenderStyle。
StyleResolver是管理类,其负责根据样式规则为每一个Document中的元素匹配响应的样式属性,它和Document 节点是一一对应关系,也就是说WebKit为每个Document创建一个StyleResolver对象,为所有该Document中的节点计算样式,并将其结果保存到RenderStyle对象中。
RenderStyle是元素所有样式属性的内部表示。由于其包含了所有样式属性,为了节约空间,WebKit将属性分为两种类型:常用属性和非常用属性。非常用属性会进行分组合并,并且仅在需要时创建,这相对有效地节约了内存。该对象在被StyleResolver创建后由该元素所对应的RenderObject所拥有。
那么StyleResolver是如何为一个DOM元素生成RenderStyle对象的呢?大致地有如下几个主要步骤:
1. 首先创建一个新的RenderStyle对象
2. 从它的父亲那里继承它的一切可以继承的属性
3. 如果是link类元素,设置link属性
4. 而后是样式规则的匹配,从已知规则中找到匹配到的属性
5. 将匹配到的属性应用到RenderStyle对象中
6. 为该DOM元素的RenderStyle做一些修正工作
7. 清理StyleResolver,为下次匹配请求做准备
## RenderStyle 和RenderObject
元素在匹配生成其样式属性值之后,RenderStyle对象被RenderObject所获得,这个触发一个重新绘制的动作,WebKit此时可以根据样式属性值来计算它的布局和显示,这将在下一章作详细介绍。
##JavaScript设置样式
JavaScript有能力设置任何元素的样式值,如例子中第29行所示,其原则是覆盖样式中同一属性的值。大致的过程是,JavaScript引擎调用设置属性值的公共处理函数,然后该函数调用属性值解析函数,在这个例子中则是CSS的JS绑定函数,而后将解析后的信息设置到元素的style属性的样式webkitTransform中,然后设置该元素需要重新计算style和invalidate它的style属性,如下图所示。
在这之后,重新绘制请求被处理时,WebKit先会重新计算布局,而后在渲染相应的区域。
## CSS3 变形(transform)、变换(transition)和动画(animation)
在WebKit渲染基础中,我们介绍了DOM数,Render树和RenderLayer树,根据WebKit的设计原则,可以知道上面HTML例子的三种树结构如下图所示。也就是说,元素P的内容会被包含在中间的RenderLayer中,Div元素的内容会被会包含单独的最下面的RenderLayer中,因为Div元素的需要做CSS的3D变形。
我们知道RenderLayer会有一个后端存储空间,Chromium中是一个FrameBuffer,该层会转成一个Texture,那些CSS3的变形,变换和动画效果将作用于该Texture上,而后绘制在网页的framebuffer中。
变形比较简单,WebKit和Chromium内部有一个Matrix来表示平移,旋转,缩放和扭曲等变形,因为OpenGL有直接对变形的支持,所以只需创建响应的OpenGL变换操作即可。
变换和动画的实现大致相同,前面解析的过程跟其他属性没有区别,其结果会保存于Animation对象中。在chromium的实现中,CC(chromium compositor, 前面介绍过)中有两个主要类对其提供支持,一个是CCLayerAnimationController,一个是CCActiveAnimation。前者是控制动画的行为,后者则是包含动画相关参数和状态。这一部分其实很复杂,有兴趣的读者可以自行阅读代码。
## 源文件目录
third_party/WebKit/Source/WebCore/css/
与CSS解析,内部表示生成等一系列相关的类
third_party/WebKit/Source/WebCore/rendering/style
渲染所需要的样式的支持类,其依赖于CSS解析器及其结果