type
status
date
slug
summary
tags
category
icon
password
 

什么是 RenderObject?

样式匹配结束后,我们就得到了一颗带有完整 CSS 样式内容的 Dom 树,但此时 Dom 树上的内容,并不能作为数据依据直接在屏幕上绘制出东西,因此下一步,我们就需要利用 Dom 树作为基本,生成一颗新的树 —— RenderObject 树。

Dom 与 RenderObject 的对应关系

与 Dom 树相比,RenderObject 树上的每个节点,会将 Dom 的 Style 中的属性计算成可以直接绘制的到屏幕上的内容,和每个 Dom 都有自己的 Element 类不同的是,RenderObject 的类型并不会有很多,大部分情况下我们只会看到:
  • RenderInline
  • RenderBlockFlow
  • RenderFlexibleBox
  • RenderText
  • RenderImage
从 Dom 生成到这些 RenderObject 的规则如下:
Dom 类型
RenderObject 类型
Dom 中的纯文本
RenderText
display 为 flex 的元素
RenderFlexibleBox
display 为 block、inline-block 的元素
RenderBlockFlow
display 为 inline 的元素
RenderInline
<img> 元素
RenderImage

匿名 Box 的产生规则

如果没有意外情况,一个 Dom 会和 RenderObject 一一对应,但是在实际实现中,由于 Web 规范中定义的一些特殊行为,导致这个过程不能这样简单。
在如下情况下,RenderObject 和 Dom 不会是一一对应的关系:
  • Block、Flex 容器元素直接子元素为 Text
  • Block、Inline 元素中既包含 inline-level 又包含 block-level 的元素
    • 特殊的,Inline 元素中含有 block-level 的元素时,inline 元素会对应生成两个甚至更多的 RenderObject
简单的总结一下:所有会产生匿名 Box 的场景下,RenderObject 和 Dom 都不会一一对应。
接下来我们展开描述一下这些会产生匿名 Box 的情况

Block、Flex 容器元素直接子元素为 Text

这种情况下匿名 Box 的策略比较简单,直接给 Text 外部包裹一个 Block Box 即可:
notion image
不过相对 Flex 容器元素和 Block 容器还有所区别,Flex 容器元素不会有直接的 Text 子元素,而 Block 可以:
notion image

Block、Inline 元素中既包含 inline-level 又包含 block-level 的元素

对于 Block 元素来说,事情比较简单,如果自己的直接子元素中既有 inline-level 又有 block-level 的,那么直接给所有的 inline-level 的子元素包裹一个匿名 Block Box 即可:
notion image
而对于 Inline 元素,事情就比较复杂了。
首先 Inline 元素会找到自己的父元素中的 Block Box(也就是 Block Container),将自己分成多个 “分身”,然后再将多个 “分身” 与 inilne-level 子元素的外部包裹一个匿名 Block Box 中;
同时子元素中的 block-level 的外部也会包裹一个匿名 block box,然后作为 Block Container 的直接子元素存在(也就是 “上位”):
notion image
如果有 n 个 block-level 的子元素,Inline 元素就会有 n+1 个 “分身”:
notion image

生成 RenderObject 这个模块的职责

  • 根据 Dom 生成对应的 RenderObject
  • 创建匿名 RenderObject

匿名 Box 在浏览器中是怎么生成的?

以上步骤在浏览器中,发生在将新生成的 RenderObject 挂载到其他 RenderObject 的过程中。
WebCore 中,这一部分的代码逻辑在 RenderTreeBuilder::attachInternal() ;Blink 中则是在 LayoutTreeBuilderForElement::CreateLayoutObject() 中(更准确的说,是在 LayoutBlockFlow / LayoutInline / LayoutBlock 的 AddChild 方法中);
这部分的代码逻辑可以简单描述为:
  1. 当前元素为 inline
    1. 当 “子元素均为 block-level”
      1. 添加了 inline-level 子元素:将该子元素添加到最后一个 “分身” 下
      2. 添加了 block-level 子元素:过程同 1.b.II
    2. 当 “子元素均为 inline-level” 或 “没有子元素”
      1. 添加了 inline-level 子元素:直接添加
      2. 添加了 block-level 子元素:找到该 block 所在的 block container(注意,可能不是该 block 的直接父元素,可能会跨好几层),将该 block 之前的所有的 inline-level box(包括父元素的,同时中间不能再有 block box)包裹在一个匿名 block box(这里称其为匿名 block box 1)中,插入到 block container 的上一个 block 后面;同时当前 block box 包裹在一个匿名 block box (这里称其为匿名 block box 2)中,将匿名 block box 2 插入到匿名 block box 1 的后面,同属于 block container 的子元素
  1. 当前元素为 block
    1. 当 “子元素均为 inline-level”:
      1. 添加了 inline-level 子元素:直接添加
      2. 添加了 block-level 子元素:将之前的 inline box 都包裹在一个 block box 中 (Blink 代码见 MakeChildrenNonInline
    2. 当 “子元素均为 block” 或 “没有子元素”:
      1. 添加了 inline-level 子元素:在 inline 的外部包裹一个匿名 block box;如果之前已经有一个用于包裹 inline 的匿名 block,则添加到这个匿名 block 中
        1. 如果该 inline 之前的元素是纯文本,将文本包裹在一个匿名的 inline block 中
      2. 添加了 block-level 子元素:直接添加
  1. 当前元素为 flex
    1. 添加了非纯文本子元素:直接添加(flex 容器的子元素的 display 如果是 inline、inline-block,都会被改成 block)
    2. 添加了纯文本元素:文本外部包裹一个 block box
如果想验证一个 Dom 会生成什么样的 RenderObject 树,可以使用 WebKit 中的 DumpRenderTree 这个工具来验证:
例如,上面的 Dom 结构:
会打印出如下结果:
notion image
WebKit 源码调试指南浏览器是怎么做 CSS 测试的?