阅读浏览器工作原理(三)

555 阅读10分钟

这是我参与更文挑战的第6天,活动详情查看: 更文挑战

阅读浏览器工作原理(三)

呈现树

作用:可视化元素按照显示顺序组成的树,可以辅助按照正确顺序挥之内容。

呈现树中的元素被称为呈现器,每个呈现器代表一个矩形区域,对应相关节点的css,包含宽度、高度、位置等信息。呈现器的类型可能受display属性的影响,比如:

switch(style->display){
    case NONE:  
        break; //不展示
    case INLINE:
        o = new RenderInline(node)
        break;
    case BLOCK:
        o = new RenderBlock(node)
        break;
        ...
}

呈现树和DOM树的关系

呈现树和DOM树相对应,但是有些例外:

  1. 非可视化的元素(head、display:none)不会插入到呈现树中。(visibility:hidden的还是会被放在呈现树中)
  2. 有复杂结构的元素往往对应多个呈现器,如select对应三个呈现器:一个用于显示区域,一个用于下拉列表框,还有一个用于按钮。如果宽度不够,文本需要分多行显示,新的行也会作为新呈现器添加。
  3. 格式无效的HTML,RenderBlock元素的子元素本应全是block或全是inline元素,如果又有block又有inline,会创建新的block呈现器来包裹inline元素,使得元素中只包含block元素。
  4. 节点相对应但是位置不同。例如浮动元素和绝对定位元素,放在原处的只是占位框。

在webkit中,创建呈现器的过程被称为附加:每个dom节点都有一个attach方法,当一个节点插入dom树的时候都需要调用attach方法去创建呈现树的新节点。其中,处理html和body标记的时候会创建呈现树的根节点,也就是视口,浏览器窗口显示区域。

RenderObject是呈现树的节点基础类,提供了一组公共的接口。下图示例的是RenderObject对象被创建的函数调用过程:

2014_05_04_03.gif

RenderObject它有很多的子类,这些子类可能对应一些DOM树中的节点(如RenderText,直接对应一个DOM节点)有些则是容器类(如RenderBlock,作为容器存放一些节点)。下是一些常用的类的继承关系图。

2014_05_04_04.gif

举例,DOM和render类的对应关系:

2014_05_21_02.png

样式计算

样式包括样式表、行内样式和html中可视化属性(如bgcolor)中定义的样式。

样式来源:浏览器默认样式,网页作者提供的样式、浏览器用户定义的样式。

样式计算存在的难点:

  1. 样式数据结构大,可能造成内存问题
  2. 为每一个元素查找匹配规则可能会造成性能问题
  3. 应用规则有复杂的优先级关系

共享样式数据

满足以下条件的节点会被共享样式:

  1. 同级关系

  2. 处于相同的鼠标状态(如hover)、焦点状态(是否被选中)、链接状态(是否active)

  3. tag相同、类相同、id相同(因为id是唯一的,不同id对应的样式可能不同,所以实际上应该都没有id)

    不受到属性选择器的影响(不会被任何属性选择器匹配到)

  4. 不能有行内样式属性

    而且匹配到的属性集是完全相同的

  5. 不能用同级选择器

firefox规则树

为了方便样式计算,firefox还创建了上下文树和规则树。webkit也有样式对象,但是只是由DOM节点指向相关样式。

流程概述:

<html>
    <body>
        <div class="err" id="div1">
            <p>
                p1
                <span class="big">span1</span>
                p1
                <span class="big">span2</span>
                p1
            </p>
        </div>
        <div class="err" id="div2">
            div2
        </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: bule
}
/* 6 */
#div2{
    color: green
}

规则树的作用:当某个节点样式需要计算的时候(需要将逻辑值转化为具体的值),会向规则树中添加计算的路径。如果现在要为某个元素匹配对应的规则,得到规则路径是a-b-c,规则树中已经存在这个规则路径a-b-c,那么就可以直接获取到路径上的所有规则。规则树包含了所有已知规则的路径。

根据上面代码形成的规则树:(节点名: 指向的css序号)

首先匹配到的是div,发现有div选择器,而且div节点上有.err,还有#id1,于是形成了一条路径div->.err->#id1

当匹配到div中的span,发现有div span选择器,而且span节点上有.big,于是形成了一条路径div span->.big

再次匹配到div,且节点上有.err,还有#id2,由于已有路径div->.err,于是添加一个节点#id2,形成路径div->.err->#id2

image-20210625225326190.png

形成规则树后,形成上下文树(节点名: 指向的规则)。

样式上下文包含了多种结构,这些结构包含某一类的样式(如border、color),结构中的属性都是继承或者非继承的。继承属性如果子元素没有定义就沿用,非继承属性如果没有定义就用默认值。

  1. 当计算某个元素的样式上下文的时候,首先计算规则树中对应的路径,或者现有的路径。

    假设解析到第二个div,现在需要给他创建上下文,填充结构。我们发现他的匹配路径是div->.err->#id2,规则树节点顺序为B->C->F,样式顺序1->2->6。

  2. 如果能在规则树中找到对应的路径,就从路径中最底层的节点开始向上遍历规则树,填充到元素样式上下文的对应结构中。

    找到匹配路径之后,现在需要创建节点放在上下文树中,该节点指向规则F。

    先填充margin结构,可以向上找到margin:5px。然后发现已经定义了color,所以直接使用color:green。

    将数据经过计算后(如将color转化为rgb),把计算过的结构缓存在节点上。

  3. 如果找不到

    1. 如果是继承类型结构,就指向父代结构
    2. 如果是非继承的结构,就用默认值。

在这样的树下,如果两个元素指向同一个树节点,那么他们就可以共享到整个样式上下文。

例如第二个span元素,我们发现他的规则路径为div span->.big,和第一个span一样指向规则G,于是这两个span可以共享样式上下文,只需要指向之前span的上下文即可。

image-20210625231607183.png

对于webkit,没有规则树,所以会先匹配高优先级非重要规则(display),然后是高优先级重要规则,然后是普通优先级非重要规则,最后是普通优先级重要规则。

处理规则,简化匹配

样式表解析完后,系统会根据选择器将css规则添加到哈希表中。匹配示例如下:

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

第一条规则插入类hash表,第二条插入idhash表,第三条插入tag表。

对于下面的代码:

<p class="error">
    error
</p>
<div id="messageDiv">
    message
</div>
  1. 找p元素匹配的规则

    首先我们知道要在类表中查找,找到一个error键,里面有p.error的规则。于是规则匹配。

  2. 找div元素匹配的规则

    在tag表中查找div键,如果在里面找到一个table div {margin: 5px}的规则,由于div上层没有tabel所以不会应用该规则。

声明层叠顺序、应用规则

从各个地方获取到的样式表,根据css2规范,样式的层叠顺序应该为:

  1. 浏览器声明的顺序
  2. 用户普通声明
  3. 作者普通声明
  4. 作者重要声明
  5. 用户重要声明

在webkit上,节点会先采用父节点上继承的样式,再到浏览器指定样式,再到网页作者指定样式。

style->inheritFrom(*state.parentStyle())
matchUARules(collector);
matchAuthorRules(*state.element(), collector);

目前暂未看到chrome存在设置用户自定义样式的入口,唯一可以设置的用户样式为浏览器默认字体Sans-serif等,是否采用以及优先级也由网页作者在font-family中控制。

选择器的优先级:

在webkit中,如果有样式规则和元素相匹配,就会放到当前元素的m_matchedRules中计算优先级并记录。计算规则为:从右到左取每个selector的优先级,得到总和。

根据参考文章,在webkit上,每个selector的优先级如下:

 switch (m_match) {
    case Id: //id选择器
      return 0x010000; //65536,十六进制的10000
    case PseudoClass: //伪类选择器
      return 0x000100; //256,十六进制的100
    case Class: //类选择器
    case PseudoElement: //伪元素选择器
    case AttributeExact:
    case AttributeSet:
    case AttributeList:
    case AttributeHyphen:
    case AttributeContain:
    case AttributeBegin:
    case AttributeEnd:
      return 0x000100; //256,十六进制的100
    case Tag: //标签选择器
      return 0x000001; //1,十六进制的1
    case Unknown:
      return 0;
  }
  return 0;

这里有一个疑问,根据规定,标签选择器应该和伪元素选择器处于同一优先级,为什么这边的伪元素选择器优先级却高于标签选择器?

本来想比较各浏览器下伪元素选择器和标签选择器的优先级,尝试的过程中遇到一些问题:

前提:想比较伪元素选择器、标签选择器的优先级,首先要想办法将两个选择器作用的对象变为同一个

现在:如果我给一个标签设置伪元素,样式是作用于隐式生成的那个伪元素上的,我怎么通过别的css选择器获取到伪元素呢??..所以伪元素的优先级是不是没啥用?...感觉一直都是最高的,因为他似乎是最底层的子元素,汗,后面再详细出一篇文章讲。

根据css2规范中选择器特异性应该如下定义:

  1. 如果声明来自于“style”属性,而不是带有选择器的规则,则记为 1,否则记为 0 (= a)
  2. 记为选择器中 ID 属性的个数 (= b)
  3. 记为选择器中其他属性和伪类的个数 (= c)
  4. 记为选择器中元素名称和伪元素的个数 (= d)

将四个数字按 a-b-c-d 这样连接起来(位于大数进制的数字系统中),构成特异性。例子可以在上面的规范链接中查看,这里就不重复举例了。

在webkit中,排序的规则是:如果两者的优先级一样,则根据书写样式的位置进行排序,后面写的会覆盖前面的,排序完成后按照从小到大的顺序保存在容器里。

当这些选择器的样式处理完了,开始处理inline style样式,然后push进去。因为他放在后面所以优先级比之前的更高。

前面的样式处理完了,接下来开始按照优先级计算css,先设置完正常的规则,最后设置important的规则来覆盖前面所有规则。

最后计算出来的style规则被分为几类:

v2-a900e5c2e71bc08f4ef372c4c2d9962c_720w.png

  1. box:主要是长宽
  2. backround:背景相关
  3. surround:主要是盒子周围的一些参数,如边框,边距等
  4. noneInheritedData:不可继承的数据
  5. styleInheritedData:可以继承的数据
  6. svgstyle:svg相关

比较少用到的属性会放在rareData结构中,避免实例化属性占用太多空间。

所有色值会被转换为16进制整数,em等单位也会换算成px。

最后在对style进行一些调整,主要是一些容错设置:

  1. 把absolute/fixed定位、float的元素设置成block

    w3c的规定

  2. 如果有:first-letter选择器时,会把对应伪元素display和position做调整

    除非是float,否则display必须设置为inline

    css2规定伪元素不能设置position

渐进式处理

在webkit中,如果attach(构建呈现树)的时候还没加载完样式,就会先在文档中用占位符标注,等到样式表加载完了再重新计算。

www.nowamagic.net/academy/det…

www.nowamagic.net/academy/det…

zhuanlan.zhihu.com/p/25380611