2 CSS解释器和规则匹配
在了解了CSS的基本概念之后,下面来理解WebKit如何来解释CSS代码并选择相应的规则。通过介绍WebKit的主要设施帮助理解WebKit的内部工作原理和机制。
2.1 样式的WebKit表示类
在DOM树中,CSS样式可以包含在“style”元素中或者使用“link”来引用一个CSS文档。对于CSS样式表,不管是内嵌还是外部文档,WebKit都使用CSSStyleSheet类来表示。图描述了WebKit内部是如何表示CSS文档的。
图CSS的内部结构主要类和关系
一切的起源都是从DOM中的Document类开始。先看Document类之外的左上部分:包括一个DocumentStyleSheetCollection类,该类包含了所有CSS样式表;还包括WebKit的内部表示类CSSStyleSheet,它包含CSS的href、类型、内容等信息。CSS的内容就是样式信息StyleSheetContents,包含了一个样式规则(StyleRuleBase)列表。样式规则被用在CSS的解释器的工作过程中。
下面的部分WebKit主要是将解释之后的规则组织起来,用于为DOM中的元素匹配相应的规则,从而应用规则中的属性值序列。这一过程的主要负责者是StyleSheetResolver类,它属于Document类,并包含了一个DocumentRuleSets类用来表示多个规则集合(RuleSet)。每个规则集合都是将之前解释之后的结果合并起来,并进行分类,例如id类规则、标签类规则等。至于为什么是多个规则集合,是因为这些规则集合可能源自于默认的规则集合(前面提到过WebKit使用默认的CSS样式信息),或者网页自定义的规则集合等。
下面让我们更进一步重点介绍样式规则。样式规则是解释器的输出结构,是样式匹配的输入数据。样式规则有很多类型,图描述了这些类和继承关系。
图StyleRuleBase和它的子类们
- Style: 这个是基本类型,大多数规则属于这个类型。
- lmport: 是WebKit中为了方便而引入的,其对应的是一个导入CSS文件的Style元素。
- Media: 对应于CSS标准中的@media类型。
- Fontface: CSS3新引入的自定义字体的规则类型。
- Page: 对应于CSS标准中的@page类型。
- Keyframes: 对应于WebKit中的@-webkit-key-frames类型,可以用来定制特定帧的样式属性信息。
- Region: 对CSS标准正在进行中的Regions的支持,这方便了开发者对页面进行分区域排版。
这些类基本上跟CSS的标准相对应,当然也有特例,那就是StyleRuleImport,它是WebKit引入的一个新的类型,主要对应的是导入CSS文件的元素。
接下来剖析规则的内部是如何组成和表示的。图描述的是一个CSS规则(从示例代码中获取)和WebKit的内部结构表示类。
图CSS样式规则和WebKit的内部结构表示类
首先最外层的是样式规则,通常一个样式表可能包括一个或者多个样式规则,这里描述的样式规则对应于图中的StyleRule类。
然后是选择器部分,图的上半部分是一个选择器列表,这在现实中是比较常见的,因为选择的条件可能有多个。举个例子,“a[class=abc]”其实是两个选择器合起来的,第一个是“a”,它是一个标签选择器;第二个是“[class=abc]”,它是一个属性选择器。这两个选择器合起来的含义就是匹配元素是“a”的标签并且它的类别为“abc”。选择器在WebKit的内部表示是CSSSelector类。在规则中,CSSSelector类使用一个对象列表来表示它们。
最后是这个规则对应的属性集合CSSPropertySet。WebKit使用了一些类来分别表示属性名字CSSPropertyID (1) 、属性值CSSValue。图6-7清晰地描述了它们的结构。
2.2 解释过程
CSS解释过程是指从CSS字符串经过CSS解释器处理后变成渲染引擎的内部规则表示的过程。在WebKit中,这一过程如图所示。
图CSS解释器的工作过程
这一过程并不复杂,基本的思想是由CSSParser类负责。CSSParser类其实也是桥接类,实际的解释工作是由CSSGrammer.y.in来完成。CSSGrammer.y.in是Bison的输入文件,Bison是一个生成解释器的工具。Bison根据CSSGrammer.y.in生成CSS解释器——CSSGrammer类。当然CSSGrammer类需要调用CSSParser类来处理解释结果,例如需要使用CSSParser类创建选择器对象、属性、规则等。
图描述的正是这一过程。当WebKit需要解释CSS内容的时候,它调用CSSParser对象来设置CSSGrammer对象等,解释过程中需要的回调函数由CSSParser来负责处理,最后WebKit将创建好的结果直接设置到StyleSheetContents对象中,这一过程显得直接而且简单。
在解释网页中自定义的CSS样式之前,实际上WebKit渲染引擎会为每个网页设置一个默认的样式,这决定了网页所没有设置的元素属性及其属性默认值和将要显示的效果。一般来讲,不同的WebKit移植可以设置不同的默认样式。下面是Chrome浏览器使用的默认样式,这些样式决定了默认的网页显示效果。
"html,body,div{display:block}head{display:none}body{margin:8px}div:focus, span:focus{outline:auto 5px-webkit-focus-ring-color}a:-webkit-any-link{color:-webkit-link;text-decoration:underline}a:-webkit-any-link:active{color:-webkit-activelink}"
2.3 样式规则匹配
样式规则建立完成之后,WebKit保存规则结果在DocumentRuleSets对象类中。当DOM的节点建立之后,WebKit会为其中的一些节点(只限于可视节点,在第7章中介绍)选择合适的样式信息。根据前面的描述,这些工作都是由StyleResolver来负责的。当然,实际的匹配工作还是在DocumentRuleSets类中完成的。
图描述了参与样式规则匹配的WebKit主要相关类。基本的思路是使用StyleResolver类来为DOM的元素节点匹配样式。StyleResolver类根据元素的信息,例如标签名、类别等,从样式规则中查找最匹配的规则,然后将样式信息保存到新建的RenderStyle对象中。最后,这些RenderStyle对象被RenderObject类所管理和使用。
图CSS样式规则匹配使用的主要WebKit类
规则的匹配则是由ElementRuleCollector类来计算并获得,它根据元素的属性等信息,并从DocumentRuleSets类中获取规则集合,依次按照ID、类别、标签等选择器信息逐次匹配获得元素的样式。那么具体的过程如何呢?图为我们描述了WebKit如何为HTML元素获取样式并从规则集合中匹配的过程。
图为HTML元素获取样式和WebKit的样式规则匹配过程
首先,当WebKit需要为HTML元素创建RenderObject类的时候,首先StyleResolver类负责获取样式信息,并返回RenderStyle对象,RenderStyle对象包含了匹配完的结果样式信息。
其次,根据实际需求,每个元素可能需要匹配不同来源的规则,依次是用户代理(浏览器)规则集合、用户规则集合和HTML网页中包含的自定义规则集合。这三个规则的匹配方式是类似的,这里是以自定义规则的匹配为例加以说明的。
再次,对于自定义规则集合,它先查找ID规则,检查有无匹配的规则,之后依次检查类型规则、标签规则等。如果某个规则匹配上该元素,WebKit把这些规则保存到匹配结果中。
最后,WebKit对这些规则进行排序。对于该元素需要的样式属性,WebKit选择从高优先级规则中选取,并将样式属性值返回。
2.4 实践:样式匹配
为了理解样式的实际匹配过程和结果,以网页“http://lab.hakim.se/reveal-js/”为例,详细的步骤如下。
首先,打开Chrome浏览器输入上述网页地址,并打开开发者工具,单击“Elements”按钮。
其次,在开发者工具中单击“打开网页的源代码”按钮,选择“body”元素,在开发者工具的右侧,读者会看到如图6-11左边部分所示经过计算的该元素的匹配结果样式(Computed Style)。用户甚至可以直接在开发者工具中修改属性值,看看它们是如何影响布局的。
图网页的样式计算和规则匹配
最后,查看一下“styles”中的“Matched CSS Rules”,这些规则能够匹配上该节点的规则列表,但是不同的属性可能来源于不同的规则。在本例中,读者可以看到图中右边的部分就是当前匹配上的各个样式规则,它们来源于不同的CSS样式表中不同的规则。前面四个规则来源于网页自定义的样式表,最后一个“user agent stylesheet”来源于浏览器默认样式表。这些规则中有些对属性的设置有冲突,当然规则按照CSS标准定义的优先级来选择。图中被线划掉的表示该属性没有对当前元素起作用,这包含了两种情形:第一是属性设置错误;第二是被更高优先级的规则属性覆盖。开发者通过元素的具体匹配规则可以调试和修改自己的样式规则。
2.5 JavaScript设置样式
CSSOM定义了JavaScript访问样式的能力和方式。示例代码6-1中的第29行所示的是使用CSSOM接口来更改属性值的。在WebKit中,这需要JavaScript引擎和渲染引擎协同完成。为了描述这一过程,可能会涉及到一些JavaScript引擎的调用,目前比较难以理解,所以读者只需要有一个大致的印象即可,在第9章的JavaScript引擎中会有更详细和系统的介绍。
大致的过程是,JavaScript引擎调用设置属性值的公共处理函数,然后该函数调用属性值解析函数,在这个例子中则是CSS的JavaScript绑定函数。而后WebKit将解析后的信息设置到元素的“style”属性的样式“webkitTransform”中,然后设置标记表明该元素需要重新计算样式,并触发重新计算布局。最后就是WebKit的重新绘图,图6-12描述了其中的主要过程。
图WebKit引擎和JavaScript引擎设置样式