WebKit 布局 (Layout)

优质
小牛编辑
128浏览
2023-12-01

一个网页从文本信息到最后的渲染结果,要经过很多复杂的过程,前面介绍过DOM树、Render树的创建,也阐述了页面如果被渲染的,其实,这两者中间还有一个非常重要的步骤――布局计算,这是因为在渲染每个元素之前,渲染引擎必须知道它的位置大小等布局信息,我们把计算这些信息的过程称之为布局。

布局根据其计算的范围大致可以分为两类,第一类是对整个Render树进行的计算,第二类是对Render树中某个子树的计算,常见于文本元素或者是overflow:auto块的计算,这种情况一般是其子树布局的改变不会影响其周围元素的布局,因而不需要重新计算更大范围内的布局。

按照惯例,为了便于表述,首先我们来看一个能够帮助理解问题的例子。这个例子不复杂,其重点是包含两个div元素,其中id为adiv的元素是类型为aclass元素的父亲。CSS样式设置了类型aclass的样式属性值。

enter image description here

箱子(box)模型

先介绍箱子模型。CSS布局计算是基于箱子模型来进行的,其基本构成是一个矩形区域,包含了外边距(margin),内边距(padding),边框(border)和内容(content),也就是说每个元素的布局都是按照箱子模型来排布的,通过设置这些属性,达到特定的布局效果。如下图所示,最外层的虚线以内就是一个box模型的实例,这个实例表示的就是一个元素布局表示。

这个图相信大家都看过,这里简单介绍一下。箱子最边缘部分分别是四个方向上的外边距(TM, RM, BM, LM),可以设置不同的大小,其次往内是四个方向上的边框(TB, RB, BB, LB),再次是四个方向上的内边距(TP, RP, BP, LB),最后是中间的内容。下面结合实例来解释,给人以直观印象,便于深入了解。下图就是本章开始时候介绍的示例在浏览器中的显示结果,最外边的矩形框就是ID为‘adiv’的div元素的显示区域,其内部就是对于属于’.aclass’的类型的div元素的box模型实例。旁边的标注表明了box模型的各个属性值,读者可以跟上面的箱子模型进行对照理解。

enter image description here

例子中,文字被设置为‘A B C… T’,其表示的是内容区域,之所以设置成这样,是为了让布局随时可以换行,便于读者更清晰地看到内边距和内容区域。同样地,建议读者修改本章例子的各个参数,亲身体验这些设置带来的布局变化。 介绍了箱子模型后,箱子内部的位置和大小可以被确定了,那么箱子本身的位置和大小是如何确定的?这就需要“包含块”的帮助了。

Containing block(包含块)

当需要计算元素的箱子的位置和大小的时候,需要计算它和另外一个矩形区域的相对位置,这个矩形区域称为该元素的包含块,箱子模型就是在包含块内计算和确定其一系列的属性值的,包含块的具体定义如下:

1) 根元素的包含块称为初始包含块,通常它的大小就是可视区域(viewport)的大小。 2) 对于其他位置属性设置为’static’或者’relative’元素,它的包含块就是的最近的祖先的箱子模型中的内容区域(content)。 3) 如果元素的位置属性为’fixed’,其脱离文档,固定在可视区域的某个特定位置。 4) 如果元素的位置属性为’absolute’,其包含块由最近的包含属性’absolute’, ‘relative’或者’fixed’的祖先决定,具体规则如下: a) 如果其是一个inline元素,那么元素的包含块是包含该祖先的第一个和最后一个inline盒子的内边距的区域。 b) 否则,则是由该祖先的内边距所包围的区域。

这个复杂的定义读起来比较让人头痛,webkit中简单理解起来就是:

Render节点的包含块是该节点的负责决定该节点的祖先节点对应的块区域。根节点RenderView表示的就是”初始包含块”,其初始大小始终是可视区域的大小。

结合实例来讲,类型为’aclass’的div元素的包含块就是父亲的内容区域,其箱子模型就是在该内容区域上进行计算生成得来的。

布局计算

布局计算的相关信息都保存在RenderStyle对象中,如之前介绍,该对象属于Render节点。 RenderStyle没有什么特别之处,就是包含各个样式的属性值。同时,Render节点也包含一个位数组,该数组会保存一些用来表示是否需要重新计算布局等信息。

下面看看如果计算布局的。其主要由RenderObject中的layout方法来完成: 首先,layout函数会判断Render节点是否需要重新计算,通常这通过检查位数组中的相应标记位,子女是否需要计算布局等来确定。一般来说,初始显示,可视区域变化,样式值变化(例如动画,JavaScript操作样式值)等都会需要重新计算布局。

其次,它会遍历其每一个子女节点,依次计算它们的布局。

再次,对于每一个元素来说,它会实现自己的layout方法,根据特定的算法来计算该类型元素的布局。如果页面元素定义了其自己的宽高,那么webkit按照其定义的宽高来确定其大小,而对于象文本节点这样的Inline元素则需要结合其字体大小及文字的多少等来确定其对应的宽高。如果页面元素所确定的宽高超过了布局容器包含块所能提供的宽高,同时其overflow 属性为visible 或auto,则会提供滚动条来保证可以显示其所有内容。除非定义了页面元素的宽高,一般说来页面元素的宽高是在布局的时候通过相关计算得出来的。

布局测试(layouttests)

渲染引擎要处理各式各样、越来越复杂的网页,这需要Layout测试来保证它的质量。 Layout测试可以说是WebKit中最重要并且最著名的测试了。其基本测试工作方式是,预先准备很多很多的简单网页和期望的渲染结果,然后根据WebKit编译出来的DumpRenderTree(DRT)来测试网页,把得到的结果和期望的结果进行对比,以检查WebKit引擎的对网页排版布局等的正确性。每个WebKit的移植都会提供一个DumpRenderTree,通查由于移植的差异性,它们的期望结果也不一样,所以通常每个移植都有自己特殊的期望结果。

每个测试都会有一个或者多个期望结果,一般地,期望结果是一些文本结果,但是,对一些复杂的测试,单纯的文本不能够满足需求,因为测试渲染结果可能需要比较布局,字体,图片等等,所以这时候期望结果其实是一幅图片(还有其他类型),这个图片其实就是网页应该渲染的结果。不幸的是,由于字体,平台的样式等差异性(例如Qt,GTK等就不一样),相同的网页渲染出的结果可能不一样,所以,你可以看到布局测试对不同的移植会有不同的期望结果。

一般来讲,当开发者提交新的补丁时候,需要先进行布局测试,只有当该测试通过,才有可能被WebKit所接受。如果你提交的是解决了一个新问题,那么,建议你提交一个新的测试用例来保证代码的正确性。

源文件目录

third_party/WebKit/Source/WebCore/rendering/style
  渲染所需要的样式的支持类,其依赖于CSS解析器及其结果 

参考文献

  1. http://www.webkit.org/projects/layout/index.html
  2. http://www.w3.org/TR/CSS21/box.html#box-dimensions
  3. http://www.webkit.org/blog/1452/layout-tests-theory/
  4. http://www.webkit.org/blog/1456/layout-tests-practice/