您的位置:首页 > Web前端 > CSS

【浏览器渲染原理】渲染树构建之样式计算

2014-07-23 14:17 447 查看
构建渲染树时,需要计算每一个渲染对象的可视化属性。通过计算每个元素的样式属性来完成。

样式包括来自各种来源的样式表、inline样式元素和HTML中的可视化属性。

样式表的来源包括浏览器的默认样式表、在网页中加载的样式表、用户在浏览器上设置的用户样式表。

样式计算存在以下难点:

1 样式数据是一个超大的结构,存储了无数的样式属性,可能会引发内存问题。

2 为每一个元素查找匹配的规则可能会造成性能问题,所以需要进行优化。要为每一个元素遍历整个规则列表来寻找匹配规则,是一项浩大的工程。选择器会具有很复杂的结构,这就会导致某个匹配过程一开始看起来可能是正确的,但最终发现是不行的,必须尝试其他匹配路径。

例如:div div div div{….}

这个规则适用于作为3个div元素的子孙的div。如果您要检查规则是否适用于某个指定的div元素,应选择树上的一条向上路径进行检查。

3 应用规则涉及到相当复杂的层叠规则(用于定义这些规则的层次)。

共享样式数据

Webkit节点引用了样式对象(RenderStyle)。这些对象在某些情况下可以由不同节点共享。这些节点是同级关系,同时:

1这些元素必须处于相同的鼠标状态(例如,不允许其中一个是“:hover”状态,而另一个不是)

2 任何元素都没有ID

3 标记名称应匹配

4 类属性应匹配

5 映射属性的集合必须是完全相同的

6 链接状态必须匹配

7 焦点状态必须匹配

8 任何元素都不应受属性选择器的影响,这里所说的“影响”是指在选择器中的任何位置有任何使用了属性选择器的选择器匹配

9 元素中不能有任何inline样式属性

10 不能使用任何同级选择器。同级选择器包括 +选择器 以及:first-child和:last-child等选择器。

Firefox规则树

Firefox通过两个额外的树来降低样式计算的难度——规则树和样式上下文树。Webkit也有样式对象,但是这些样式对象没有存储在类似样式内容树的树种,只是一些指向相关样式的DOM节点。



样式上下文包含了端值。端值是通过将所有的匹配规则应用到正确的顺序上,并将这些规则从逻辑值转换成具体的值。例如,屏幕的百分比是一个逻辑值,最后会被转换成具体的单位。规则树可以让那个节点间共享这些端值,避免了重复计算,节省了空间。

所有匹配的规则都存储在一个颗树中。路径中最底部的节点具有更高的优先级。规则树包含了能找到的规则匹配的所有路径。存储规则是懒加载。不是在一开始就计算每个节点的端值,而是当一个节点样式需要计算时,才会向规则树添加计算的路径。

例子:将规则树路径当作词典中的单词。如果已经计算出如下的规则树:



假设我们需要为内容树中的另一个元素匹配规则,并且找到匹配路径是 B-E-I。由于我们在树中已经计算了路径A-B-E-I-L,因此就已经有了此路径,这就减少了现在所需的工作量。

结构划分

样式上下文可分割成多个结构。这些结构体包含了特定类别(如border或color)的样式信息。结构中的属性都是继承的或非继承的。继承属性如果未由元素定义,则继承自其父代。非继承属性如果未进行定义,则使用默认值。

规则树通过缓存整个结构(包含计算出的端值)为我们提供帮助。这一想法假定底层节点没有提供结构的定义,则可使用上层节点中的缓存结构。

使用规则树计算样式上下文

在计算某个特定元素的样式上下文时,我们首先计算规则树中的对应路径,或者使用现有的路径。然后我们沿此路径应用规则,在新的样式上下文中填充结构。从路径中拥有最高优先级的底层节点开始,并向上遍历规则树,直到结构填充完毕。如果该规则节点对于此结构没有任何规范,那么可以实现更好的优化:寻找路径更上层的节点,找到后制定完整的规范并指向相关节点即可。可以减少端值的计算量并节约内存。

如果找到了部分定义,就会向上遍历规则树,直到结构填充完毕。

如果找不到结构的任何定义,那么如果该结构是“继承”类型,在上下文树中指向父代的结构,也可以共享结构。如果是reset类型的结构,则会使用默认值。

如果最特殊的节点确实添加了值,那么需要将这些值转化成实际值。然后将结果缓存在树节点中,供子代使用。

如果某个元素与其同级元素都指向了同一个树节点,那么它们就可以共享整个样式上下文。

例:

<html>
<body>
<div class="err" id="div1">
<p>         this is a <span class="big"> big error </span>         this is also a
<span class="big"> very  big  error</span> error
</p>
</div>
<div class="err" id="div2">another error</div>
</body>
</html>


规则如下:

1.	div {margin:5px;color:black}
2.	.err {color:red}
3.	.big {margin-top:3px}
4.	div span {margin-bottom:4px}
5.	#div1 {color:blue}
6.	#div2 {color:green}


color结构包含color,margin结构包含margin-top,margin-left,margin-right,margin-bottom。

形成的规则树如下图所示(节点名:指向的规则序号)。



上下文树(节点名:指向的规则节点)



假设我们解析HTML时遇到了第二个<div>标记,要为这个节点创建样式上下文,并填充样式结构。

经过规则匹配,这个<div>的匹配规则是第1、2、6条。这意味着规则树中已有一条路径可供我们的元素使用,我们只需要再为其添加一个节点已匹配第6条规则(节点F)。

将创建样式上下文并将其放入上下文树中。新的样式上下文指向规则树中的F节点。

现在需要填充样式结构。首先填充的是margin结构。由于最后的规则节点F并没有添加到margin结构,上溯规则树,直到找到在先前节点插入中计算过的缓存结构,然后使用该结构。在指定margin规则的最上层节点(节点B)找到该结构。

Color结构的定义已经有了,因此不能使用缓存的结构。由于color有一个属性,无需上溯规则树。计算端值(将字符串转化成rgb)并在此节点上缓存经过计算的结构。

第二个span元素处理起来更加简单。我们将匹配规则,最终发现和之前的span一样指向规则G。由于我们找到了指向同一节点的同级,就可以共享整个样式上下文,只需指向之前span的上下文即可。

对于包含了继承自父代的规则的结构,缓存是在上下文树中进行的。

在Webkit中没有规则树,因此会对匹配的声明遍历4次。首先应用非重要高优先级的属性(例如,display),接着是高优先级重要规则,然后是普通优先级非重要规则,最后是普通优先级重要规则。多次出现的属性会根据正确的层叠顺序进行解析,最后出现的最终生效。

对规则进行处理以简化匹配

样式规则来源:

(1) 外部样式表或样式元素中的CSS规则 p {color:blue}

(2) inline样式属性及类似内容 <p style="color:blue" />

(3) HTML可视化属性 <pbgcolor="blue" />

样式表解析完毕后,系统会根据选择器将CSS规则添加到对应的哈希表中。这些哈希表包括:ID哈希表、类名称哈希表、标记名哈希表等,还有一种通用哈希表,适合不属于上述类别的规则。如果选择器是ID,规则就会添加到ID表中;如果选择器是类,规则就会添加到类表中。

以如下的样式规则为例:

p.error {color:red}
#messageDiv {height:50px}
div {margin:5px}


第一条规则将插入类表,第二条将插入ID表,第三条插入标记名表。
对于下面的HTML代码段:
<p class="error">an error occurred </p>
<div id=" messageDiv">this is a message</div>


首先会为p元素寻找匹配的规则。类表中有一个error键,在哈希表中找到p.error规则。Div元素在ID表和标记表中有相关的规则。剩下的就是找出哪些根据键提取的规则是真正匹配的了。
例如,table div{margin:5px}
因为div是最右边的选择器,这条规则会从标记表中提取出来。但这条规则并不匹配我们的div元素,因为div没有table祖先。

以正确的层叠顺序应用规则
样式对象具有每个可视化属性。如果某个属性未由任何匹配规则所定义,那么部分属性就可由父代元素样式对象继承。其他属性具有默认值。
如果定义不止一个,就会出现问题,需要通过层叠顺序来解决。

样式表层叠顺序
某个样式属性的声明可能会出现在多个样式表中,也可能在同一个样式表中出现多次,称为“层叠”顺序。根据CSS2规范,层叠的顺序为(优先级从低到高):
1. 浏览器声明
2. 用户普通声明
3. 作者普通声明
4. 作者重要声明
5. 用户重要声明
浏览器声明是重要程度最低的,而用户只有将该声明标记为“重要”才可以替换网页作者的声明。同样顺序的声明会根据特异性进行排序,然后再指定顺序。HTML可视化属性会转换成匹配的CSS声明,被视为低优先级的网页作者规则。

特异性
CSS2规范对于特异性的定义:
1. 如果声明来自于“style”属性,而不是带有选择器的规则,则记为1,否则记为0(=a)
2. 选择器中ID属性的个数(=b)
3. 选择器中其他属性和伪类的个数(=c)
4. 选择器中元素名称和伪元素的个数(=d)
将4个数字按a-b-c-d连接起来,构成特异性。
使用的进制取决于4个类别中的最高计数。
例如,如果a=14,可以使用16进制。如果a=17,则需要使用17进制。
举个栗子:
*             {}  /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */
li            {}  /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */
li:first-line {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
ul li         {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
ul ol+li      {}  /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */
h1 + *[rel=up]{}  /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */
ul ol li.red  {}  /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */
li.red.level  {}  /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */
#x34y         {}  /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */
style=""          /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */


规则排序
找到匹配的规则之后,应根据级联顺序将其排序。Webkit对于较小的列表会使用冒泡排序,而对较大的列表则使用归并排序。

[参考资料]

浏览器的工作原理
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: