3 WebKit布局
3.1 基础
当WebKit创建RenderObject对象之后,每个对象是不知道自己的位置、大小等信息的,WebKit根据框模型来计算它们的位置、大小等信息的过程称为布局计算(或者称为排版)。
图描述了这一过程中涉及的主要WebKit类。第5章描述过Frame类,用于表示网页的框结构,每个框都有一个FrameView类,用于表示框的视图结构。
图布局计算中的主要WebKit类
FrameView类主要负责视图方面的任务,例如网页视图大小、滚动、布局计算、绘图等,它是一个总入口类。图中标注了两个跟布局计算密切相关的函数——“layout”和“needsLayout”,它们用来布局计算和决定是否需要布局计算,实际的布局计算则是在RenderObject类中。
布局计算根据其计算的范围大致可以分为两类:第一类是对整个RenderObject树进行的计算;第二类是对RenderObject树中某个子树的计算,常见于文本元素或者是overflow:auto块的计算,这种情况一般是其子树布局的改变不会影响其周围元素的布局,因而不需要重新计算更大范围内的布局。
3.2 布局计算
布局计算是一个递归的过程,这是因为一个节点的大小通常需要先计算它的子女节点的位置、大小等信息。
下图描述了RenderObject节点计算布局的主要过程,中间省略了很多判断和步骤,主要逻辑都是由RenderObject类的“layout”函数来完成。
图布局计算过程
首先,该函数会判断RenderObject节点是否需要重新计算,通常这需要通过检查位数组中的相应标记位、子女是否需要计算布局等来确定。
其次,该函数会确定网页的宽度和垂直方向上的外边距,这是因为网页通常是在垂直方向上滚动,而水平方向尽量不需要滚动。
再次,该函数会遍历其每一个子女节点,依次计算它们的布局。每一个元素会实现自己的“layout”函数,根据特定的算法来计算该类型元素的布局。如果页面元素定义了自身的宽高,那么WebKit按照定义的宽高来确定元素的大小,而对于像文本节点这样的内联元素则需要结合其字号大小及文字的多少等来确定其对应的宽高。如果页面元素所确定的宽高超过了布局容器包含块所能提供的宽高,同时其overflow的属性为visible或auto,WebKit则会提供滚动条来保证可以显示其所有内容。除非网页定义了页面元素的宽高,一般来说页面元素的宽高是在布局的时候通过相关计算得出来的。如果元素它有子女,则WebKit需要递归这一过程。
最后,节点根据它的子女们的大小计算得出自己的高度,整个过程结束。
哪些情况下需要重新计算布局呢?总体来讲,只要样式发生变化,WebKit都需要重新计算,但是实际场景中,有以下一些情况。
首先,当网页首次被打开的时候,浏览器设置网页的可视区域(viewport),并调用计算布局的方法。这其实也描述了一种常见的情景,就是当可视区域发生变化的时候,WebKit都需要重新计算布局,这是因为网页的包含块的大小发生了改变。
其次,网页的动画会触发布局计算。当网页显示结束后,动画可能改变样式属性,那么WebKit就需要重新计算。
然后,JavaScript代码通过CSSOM等直接修改样式信息,它们也会触发WebKit重新计算布局。
最后,用户的交互也会触发布局计算,例如翻滚网页,这会触发新区域布局的计算。
CSS的布局计算是以包含块和框模型为基础的,这表示这些元素的布局计算都依赖于块,例如“div”通常就是一个块,如前面所述它们通常是在垂直方向上展开。但是,CSS标准也规定了行布局形式,这就是内联元素。内联元素表现的是行布局形式,就是说这些元素以行进行显示。以“div”元素为例,如果设置属性“style”为“display:inline”时,则该元素是内联元素,那么它可能与前面的元素在同一行。如果该元素没有设置这个属性时,则是块元素,那么在新的行里显示。这显然会增加处理的复杂性,为此,WebKit的处理方式是——对于一个块元素对应的RenderObject对象,它的子女要么都是块元素的RenderObject对象,要么都是非内联元素对应的RenderObject对象,这可以通过建立匿名块(Anonymous Block)对象来实现,在下一章也会作介绍。 (2)
布局计算相对也是比较耗时间的,更糟糕的是,一旦布局发生变化,WebKit就需要后面的重新绘制操作。另一方面,减少样式的变动而依赖现在HTML5的新功能可以有效地提高网页的渲染效率,这些在后面介绍绘图的时候一并分析。
3.3 布局测试
在这里介绍布局测试(Layout Tests)貌似也有点文不对题,因为其实布局测试不仅测试布局,还包括渲染等综合渲染结果。本章主要介绍CSS的样式计算和布局计算,不过它们也或多或少存在联系。
布局测试可以说是WebKit中最重要并且最著名的测试了,用于测试网页的整个渲染结果,包括网页加载和渲染整个过程。渲染引擎要处理各式各样越来越复杂的网页,这需要布局测试来保证引擎的渲染结果的正确性。基本测试工作方式是:预先准备大量用于单元测试的网页和期望的渲染结果,然后使用WebKit编译出来的DumpRenderTree(DRT)来测试网页,把得到的结果和期望的结果进行对比,以检查WebKit引擎对网页排版布局等的正确性。每个WebKit的移植都会提供一个DumpRenderTree, (3) 通常由于移植的差异性,它们的期望结果也不一样,所以通常每个移植都有特殊的期望结果。
每个测试都会有一个或者多个期望结果,一般情况下,期望结果是一些文本结果。但是,对一些复杂的测试,单纯的文本不能够满足需求,因为测试渲染结果可能需要比较布局、字体、图片等,所以这时候期望结果其实是一幅图片(还有其他类型),这个图片其实才是网页应该渲染的结果。可惜的是,由于字体、平台的样式等差异性(如Qt、GTK等就不一样),相同的网页渲染出的结果可能不一样,所以,读者可以看到布局测试对不同的移植会有不同的期望结果。
一般来讲,当开发者提交新的代码补丁包时,需要先进行布局测试,只有当该测试通过并且没有造成其他的测试出现新错误的时候,才有可能被WebKit项目所接受。如果读者提交代码的目的是解决一个新问题,那么,强烈建议读者提交一个新的测试用例来保证代码的正确性。