1.2 CSS 解释器和规则匹配
1.2.1 样式的 WebKit 表示类
对于 CSS 样式表,不管是内嵌还是外部文档,WebKit 都使用 CSSStyleSheet 类来表示。图 6-5 描述了 WebKit 内部是如何表示 CSS 文档的。
一切的起源都是从 DOM 的 Document 类开始。
- DoucmentStyleSheetCollection 类,该类包含了所在 CSS 样式表
- WebKit的内部表示类 CSSStyleSheet,它包含 CSS 的 href 、类型、内容等信息。
- CSS 的内容就是样式信息 StyleSheetContent,包含了一个样式规则
(StyleRuleBase)列表。样式规则被 用在 CSS 的解释器的工作过程中。
下面部分 WebKit 主要是将解释之后的规则组织起来,用于为 DOM 中的元素匹配相应的规则,从而应用规则中的属性值序列。这一过程的主要负责者是 StyleSheetResolver 类,它属于 Document 类,并包含了一个 DocumentRuleSets 类用来表示多个规则集合(RuleSet)。每个规则集合就是将之前解释之后的结果合并起来,并进行分类,例如 id 类规则,标签类规则等。至于为什么是多个规则集合,是因为这些规则集合可能源自于默认的规则集合,或者网页自定义的规则集合等。
1.2.2 解释过程
CSS 解释过程是指从 CSS 字符串经过 CSS 解释器处理后变成渲染引擎内部规则的表示过程。
在 WebKit 中,过程如 6-8
这一过程是基本思想是由 CSSParser 类负责。CSSParser 类其实也是桥接类,实际的解释工作是由 CSSGrammer.y.in 来完成。CSSGrammer.y.in 是Bison 的输入文件,Bioson 是一个生成解释器的工具。Bison 根据 CSSGrammer.y.in 生成 CSS 解释器——CSSGrammer 类。当然 CSSGrammer 类需要调用 CSSParser类来处理解释结果,例如需要使用 CSSParser 类创建选择器对象、属性、规则等。
在解释网页中自定义的 CSS 样式之前,实际上 WebKit 渲染引擎会为每个网页设置一个默认的样式,这决定了网页所没有设置的元素属性及其属性默认值和将要显示的效果。
一般来讲,不同的 WebKit 移植可以设置不同的默认样式。下面是 Chrome 浏览器使用的默认样式,这些样式决定了默认的网页显示效果。
1.2.4 样式规则匹配
样式规则建立完成之后,WebKit 保存规则结果在 DocumentRuleSets 对象类中。当 DOM 的节点建立之后,WebKit 会为其中的一些节点(只限于可视节点)选择合适的样式信息。这些工作都是由 StyleResolver 来负责。当然,实际的匹配工作还是在 DocumentRuleSets 类中完成的。
图 6-9 描述了参与样式规则匹配的 WebKit 主要相关类。基本思路是使用 StyleResolver 类来为 DOM 的元素节点匹配样式。StyleResolver 类根据元素的信息,例如标签名、类别等,从样式规则中查找最匹配的规则,然后将样式信息保存到新建的 RenderStyle 对象中。最后,最后这些 RenderStyle 对象被 RenderObject 类所管理和使用。
规则的匹配则是由 ElementRuleCollector 类来计算并获得,它根据元素的属性等,并从 DocumentRuleSets 类中获取规则集合,依次按照 ID、类别、标签等选择器信息逐次匹配获得元素的样式。
首先,当 WebKit 需要为 HTML 元素创建 RenderObject 类的时候,首先 StyleResolver 类负责获取样式信息,并返回 RenderStyle 对象,RenderStyle 对象包含了匹配完的结果样式结果。
其次,根据实际需求,每个元素可能需要匹配不同来源的规则,依次是用户代理(浏览器)规则集合、用户规则集合和 HTML 网页中包含的自定义规则集合。这三个规则的匹配方式是类似的。这里是以自定义规则匹配为例加以说明的。
再次,对于自定义规则集合,它先查找 ID 规则,检查有无匹配的规则,之后依次检查类型规则,标签规则等,如果某个规则匹配上该元素,WebKit 把这些规则保存到匹配结果中。
最后,WebKit 对这些规则进行排序。对于该元素需要的样式属性,WebKit 选择从高优先级规则中选取,并将样式属性值返回。
1.2.5 JavaScript 设置样式
CSSDOM 定义了 JavaScript 访问样式的能力和方式。使用 CSSDOM 接口来更改属性值的过程,在 WebKit 中,这需要 JavaScript 引擎和渲染引擎协同完成。
大致的过程是,JavaScript 引擎调用设置属性值的公共处理函数,然后该函数调用属性值解析函数,在这个例子中则是 CSS 的 JavaScript 绑定函数。而后 WebKit 将解释后的信息设置到元素的 “style” 属性的样式 “webkitTransform” 中,然后设置标记表明该元素需要重新计算样式,并触发重新计算布局。最后是 WebKit 的重新绘图,图 6-12 描述了其中的主要过程。
1.3 WebKit 布局
1.3.1 基础
当 WebKit 创建 RenderObject 对象之后,每个对象是不知道自己的位置、大小等信息的,WebKit 根据框模型来计算它们的位置,大小等信息的过程称为布局计算。
第五章描述过 Frame 类,用于表示网页的框结构,每个框都有一个 FrameView 类,用于表示框的视图结构。
FrameView 类主要负责视图方面的任务,例如网页视图大小,滚动、布局计算、绘图等,它是一个总入口类。 “layout” 和 “needsLayout” ,它们用来布局计算和决定是否需要布局计算,实际的布局计算则是在 RenderObject 类中。
布局计算根据其计算的范围大致可以分为两类:第一类是对整个 RenderObject 树进行的计算;第二类是对 RenderObject 树中某个子树的计算,常见于文本元素或者是 overflow:auto 块的计算,这种情况一般是其子树布局的改变不会影响其周围元素的布局,因而不需要重新计算更大范围内的布局。
1.3.2 布局计算
布局计算是一个递归的过程,因为一个节点的大小通常需要先计算它的子女节点的位置,大小等信息。
图 6-14 描述了 RenderObject 节点计算布局的主要过程,中间省略了很多判断和步骤,主要逻辑都是由 RenderObject 类的 “layout” 函数来完成。
首先,该函数会判断 RenderObject 节点是否需要重新计算,通常这需要通过检查位数组中的相应标记位、子女是否需要计算布局等来确定。
其次,该函数会确定网页的宽度和垂直方向上的外边距,这是因为网页通常是垂直方向上滚动,而水平方向尽量不需要滚动。
再次,该函数会遍历其每一个子女节点,依次计算它们的布局。每一个元素会实现自己的 “layout” 函数,根据特定的算法来计算该类型元素的布局。如果页面元素定义了自身的宽高,那么 WebKit 按照定义的宽高来确定元素的大小,而对于像文本节点这样的内联元素则需要结合其字号大小及文字的多少等来确定其对应的宽高。如果页面元素所确定的宽高超过了布局容器包含块所能提供的宽高,同时其 overflow 的属性为 visible 或 auto , WebKit 则会提供滚动条来保证可以显示其所有内容。除非网页定义了页面元素的宽高,一般来说页面元素的宽高是在布局的时候通过相关计算得出来的。如果元素它有子女,则 WebKit 需要递归这一过程。
最后,节点根据它的子女们的大小计算得出自己的高度,整个过程结束。
重新布局的情况:
首先,当网页首次被打开的时候,浏览器设置网页的可视区域(viewport),并调用计算布局的方法。这其实也描述了一种常见的情景,就是当可视区域发生变化的时候,WebKit 都需要重新计算布局,这是因为网页的包含块的大小发生了改变。
其次,网页的动画会触发布局计算。当网页显示结束后,动画可能改变样式属性,那么 WebKit 就需要重新计算。
然后,JavaScript 代码通过 CSSDOM 等直接修改样式信息,它们也会触发 WebKit 重新计算布局。
最后,用户的交互也会触发布局计算,例如翻滚网页,这会角触发新区域布局计算。
CSS 的布局计算是以包含块和框模型为基础的,这表示这些元素的布局计算都依赖于块,例如 “div” 通常就是一个块,如前面所述它们通常是在垂直方向上展开
。
但是,CSS 标准也规定了行布局形式,这就是内联元素。内联元素表现的是行布局形式,就是说这些元素以行进行显示。
以 “div” 元素为例,如果设置属性 “style” 为 “displa: inline” 时,则该元素是内联元素,那么它可能与前面的元素在同一行。如果该元素没有设置这个属性时,则是块元素,那么在新的行里显示。这显然会增加处理的复杂性,为此,WebKit 的处理方式是 ——对于一个块元素对应的 RenderObject 对象,它的子女要么都是块元素的 RenderObject 对象,要么都是非内联元素对应的 RenderObject 对象,这可以通过建立匿名块(Anonymous Block)对象来实现。
布局计算相对也是比较耗时间的,更糟糕的是,一旦布局发生变化,WebKit 就需要后面的重新绘制操作。另一方面,减少样式的变动而依赖现在 HTML5 的新功能可以有效地提高网页的渲染效率。
总结
- 匹配算法结合 CSS 规则来设置样式
- 选择器就是选中某个元素的
- 框模型就是常说的盒子模型,包含 margin、border、padding、content
- CSSOM 称为 CSS 对象模型,JavaScript 可以获取和操作 CSS 属性。
- CSS 解释过程是指从 CSS 字符串经过 CSS 解释器处理后变成渲染引擎内部规则的表示过程。
- 当 WebKit 创建 RenderObject 对象之后,每个对象是不知道自己的位置、大小等信息的,WebKit 根据框模型(Frame 类的 FrameView)来计算它们的位置,大小等信息的过程称为布局计算
- 布局计算是一个递归的过程,而且还会发生重新布局。