浏览器渲染的那些事(二)


接上一篇浏览器渲染的那些事(一)继续说。

构建呈现树 Render Tree/Frame Tree

渲染的流程:

在这部分我们来讲一下构建Render Tree的过程。
呈现树主要是负责布局并将自身及其子元素绘制出来。
Webkits RenderObject类是所有呈现器的基类。定义如下:

class RenderObject{
    virtual void layout();
    virtual void paint(PaintInfo);
    virtual void rect repeatRect();
    Node* node; //DOM node
    RenderStyle* style; //the computed style
    RenderLayer* containgLayer; //the 
}

每个呈现器都代表了一个矩形区域,一般对应于相关节点的css框,包含宽度、高度、位置等几何信息。
不过对于一些具有复杂结构的元素,就对应了几个可见对象,这就不是一个矩形能表现出来的了。例如select元素【对应一个显示区域,一个下拉列表以及一个按钮】。
不规范的html也会产生多个渲染对象。【css规范中,一个行内元素只能仅包含行内元素或仅包含块状元素,在存在混合内容时,将会创建匿名的块状渲染对象包裹住行内元素。】

呈现树与DOM树关系

呈现树与DOM元素相对应,但不是一一对应。
非可视化的DOM元素就不会插入呈现树,例如head元素。
display为”none”的元素也不会显示在呈现树中【visibility:hidden的元素仍会显示】

除此之外,一些渲染对象和其对应的DOM节点不是在树上的位置也有所不同。
例如浮动元素和绝对定位元素在文本流之外,那么呈现树会标识出真实的结构,并用一个占位结构标识出它们原先的位置。

浏览器渲染的那些事(二)

webkit代码中说明了如何根据display属性决定某个节点创建什么对象的渲染对象。

RenderObject* RenderObject::createObject(Node* node, RenderStyle* style)
{
    Document* doc = node->document();
    RenderArena* arena = doc->renderArena();
    ...
    RederObject* o = 0;
    
    switch(style->display()){
        case NONE:
            break;
        case INLINE:
            o = new (arena) RenderInline(node);
            break;
        case BLOCK:
            o = new (arena) RenderBlock(node);
            break;
        ...
    }    
    return o;
}

样式处理

构建呈现树的时候,需要计算每一个呈现对象的可视化属性,也就是计算每个元素的样式属性来完成。这部分比较繁琐。有的部分是我自己翻译理解的【如果有误请指出,谢谢】

  1. 样式计算的复杂性
    样式计算是个复杂的工程,首先存储了无数的样式属性,可能会造成内存问题;如果没有进行优化,那么为每个元素查找匹配的规则都需要遍历一遍规则列表,而且选择器的结构可能会造成走错匹配路径。除此之外,应用规则也有复杂的层叠规则。【!important需要创建一个额外的规则对象 CSSImportantRule】

  2. webkit内核浏览器的处理方案
    webkit有样式对象,直接把这个style对象存在对应的DOM结点上。因此会对匹配的声明遍历4次,首先应用非重要高优先级的属性,其次是高优先级重要规则,然后是普通优先级非重要规则,最后是普通优先级重要规则。多次出现的属性会根据正确的层叠顺序进行解析,最后出现的最终生效。

  3. firefox的处理方案
    firefox采用了规则树样式上下文树来简化样式计算。

浏览器渲染的那些事(二)

  • 规则树包含了所有已经知道规则匹配的路径。

  • 在计算某个特定元素的样式上下文时,先计算规则树中的对应路径,或使用现有路径,从路径中最高优先级的底层节点开始,向上遍历规则树,直到在新的样式上下文中结构填充完成。也就是css优先级。(在之前写的css选择器中有讲到。)【规则树只有当某个节点样式需要计算时,才会添加新的计算路径。不会在开始时候就为所有节点进行计算】。

  • 如果没有满足这个元素的样式,对于inherit的属性就会使用initial value【例如’font-size’,’color’】,对于reset类型的属性就使用默认值【例如’border’,’background’】

  • css rule processor处理方式

    • css rule processor会将所有规则按照级联顺序排序,然后放入RuleHash。哈希表的选择器各不相同,包括ID,class,标记名称等。【例如,如果选择器是ID,就把规则放入ID的哈希表中】还有一种通用哈希表,适合不属于上述类别的规则。匹配样式时,就在RuleHash’s table查找,然后合并已经保存的样式列表,然后SelectorMatchesTree 会去找真正匹配的那个选择器。

    • 【查找id和class的速度比属性选择器快得多!】

    • 对于伪元素,是保存在一个元素hash内,所以查找伪元素只需要查找一个哈希表。
      有点难理解,看个栗子【来自MDN】。

    //HTML代码
    <doc>

       <title>A few quotes</title>
       <para class="emph">
           Franklin said that 
           <quote>"A penny saved is a penny earned."</quote>
       </para>
       <para>
           FDR said 
           <quote>"We have nothing tofear but 
               <span class="emph">fear itself.</span>"
           </quote>
       </para>

    </doc>
    //css样式如下
    / rule 1 / doc { display: block; text-indent: 1em; }

    /* rule 2 */ title { display: block; font-size: 3em; }
    /* rule 3 */ para { display: block; }
    /* rule 4 */ [class="emph"] { font-style: italic; }
           

    Rule tree规则树如下:

浏览器渲染的那些事(二)

Style context tree样式上下文树如下:

浏览器渲染的那些事(二)

4.匹配规则顺序
前面有说到样式对象的属性,如果定义有多个,那么就需要通过层叠顺序来决定最终的显示效果。

  • 层叠顺序优先级由高到低:
    用户重要声明>作者重要声明>作者普通声明>用户普通声明>浏览器声明

  • 选择器的优先级呢,可以看我之前写的css选择器总结

  • 规则排序
    匹配段规则会根据级联顺序进行排序。webkit对于较小列表使用冒泡排序,对于较大的列表使用归并排序。

参考文献:

  1. 浏览器内部工作原理

  2. 浏览器的渲染原理简介

  3. How browsers work

  4. 有关网页渲染,每个前端开发者都该知道的那点事

  5. 前端文摘:深入解析浏览器的幕后工作原理

  6. MDN:Style System Overview