WebCore 渲染 II – 块和行内元素

在上一篇文章中,我谈到了 CSS 盒子的基本结构。在本文中,我将讨论 RenderBox 的子类以及行内的概念。

一个块流是一个盒子,其设计目的是包含行(例如段落),或者包含其他垂直堆叠的块。HTML 中的块流元素示例有 pdiv

一个行内流是一个设计为行的一部分的对象。HTML 中的行内流元素示例有 abispan

在 WebCore 中,有三个渲染器类涵盖了块流和行内流:RenderBlockRenderInline 及其共同超类 RenderFlow

RenderFlow.h
RenderBlock.h
RenderInline.h

行内流可以使用 CSS 的 display 属性更改为块流(反之亦然)。

div { display: inline }
span { display: block }

除了块流和行内流之外,还有另一种可以充当块或行内元素的类型:替换元素。替换元素是指其渲染方式未由 CSS 指定的元素。对象内容的渲染方式取决于元素本身。替换元素的示例有图像、表单控件、iframe、插件和 applets。

替换元素也可以是块级别或行内级别。当替换元素充当块时,它会像代表自己的段落一样垂直堆叠。当替换元素充当行内元素时,它将成为段落内的一行的一部分。替换元素默认为行内。

表单控件实际上是一个奇怪的特殊情况。它们仍然是替换元素,但由于它们是由引擎实现的,控件实际上最终继承自 RenderBlock。因此,替换的概念无法真正局限于一个单一的共同子类,而是改在 RenderObject 上以一个位表示。isReplaced 方法可以用于询问对象是否是替换元素。

bool isReplaced() const

图像、插件、帧和 applets 都继承自一个实现替换元素行为的共同子类。这个类是 RenderReplaced

RenderReplaced.h

行内块

CSS 中命名最令人困惑的对象之一是inline-block。行内块是块流,其设计目的是放置在行上。实际上,它们在外部就像行内替换元素,但在内部它们是块流。CSS 中的 display 属性可用于创建行内块。如果被询问是否是替换元素,行内块会返回 true。

div { display: inline-block }

表格

HTML 中的表格默认为块级别。然而,它们也可以使用值为 inline-table 的 CSS display 属性变成行内元素。

table { display: inline-table }

再次强调,从外部看,行内表格就像一个行内替换元素(并且从 isReplaced 会返回 true),但在内部,该对象仍然只是一个表格。

在 WebCore 中,RenderTable 类表示一个表格。它继承自 RenderBlock,原因将在稍后的定位部分讨论。

RenderTable.h

文本

原始文本使用 RenderText 类表示。WebCore 始终将文本视为行内元素,因为它总是放在行上。

RenderText.h

获取块和行内信息

获取块与行内状态的最基本方法是 isInline 函数。该方法询问对象是否设计为行的一部分。它不关心元素的内部是什么(例如,文本、图像、行内流、行内块或行内表格)。

bool isInline() const

人们在使用渲染树时常犯的错误之一是假设 isInline 意味着对象始终是行内流、文本或行内替换元素。然而,由于行内块和行内表格的存在,这个方法甚至可以为这些对象返回 true。

要询问对象是否实际上是块流或行内流,应该使用以下方法。

bool isInlineFlow() const
bool isBlockFlow() const

这些方法本质上是询问对象的内部情况。例如,一个行内块仍然是块流而不是行内流。它在外部是行内元素,但在内部它是一个块流。

可以使用以下方法查询块和行内元素的精确类类型。

bool isRenderBlock() const
bool isRenderInline() const

isRenderBlock 方法在定位上下文中很有用,因为块流和表格充当定位对象的容器。

要询问对象是否具体是行内块或行内表格,可以使用 isInlineBlockOrInlineTable 方法。

bool isInlineBlockOrInlineTable() const

块流的子元素

块流对其子元素有一个简单的不变性,渲染树始终遵守该不变性。该规则可以总结如下:

一个块流的所有文档流中的子元素必须全部是块,或者全部是行内元素。

换句话说,一旦你排除浮动和定位元素,渲染树中块流的所有子元素必须全部从 isInline 返回 true,或者全部从 isInline 返回 false。渲染树会根据需要改变其结构以保持这种不变性。

childrenInline 方法用于询问块流的子元素是行内元素还是块。

bool childrenInline() const

行内流的子元素

行内流的子元素有一个必须保持的更简单的不变性。

一个行内流的所有文档流中的子元素必须全部是行内元素。

匿名块

为了保持块流子元素的不变性(只有行内子元素或只有块子元素),渲染树将构造称为匿名块的对象。考虑以下示例:

<div>
Some text
<div>
Some more text
</div>
</div>

在上面的示例中,外部的 div 有两个子元素:一些文本和另一个 div。第一个子元素是行内元素,但第二个子元素是块。因为这种子元素组合违反了全行内或全块子元素规则,渲染树将构造一个匿名块流来包裹文本。因此,渲染树变为:

<div>
<anonymous block>
Some text
</anonymous block>
<div>
Some more text
</div>
</div>

isAnonymousBlock 方法可以用于询问渲染器是否是匿名块流。

bool isAnonymousBlock() const

每当块流具有行内子元素且一个块对象突然试图作为子元素插入自身时,会根据需要创建匿名块来包裹所有行内元素。连续的行内元素将共享一个共同的匿名块,因此可以将匿名块的数量保持在最低限度。RenderBlock 中的 makeChildrenNonInline 方法是执行此调整的函数。

void makeChildrenNonInline(RenderObject *insertionPoint)

行内流内部的块

你会在 HTML 中看到的最麻烦的构造之一是当一个块被放置在行内流内部时。这里有一个示例:

<i>Italic only <b>italic and bold
<div>
Wow, a block!
</div>
<div>
Wow, another block!
</div>
More italic and bold text</b> More italic text</i>

这两个 div 违反了粗体元素的所有子元素必须是行内元素的不变性。渲染树必须执行一系列相当复杂的步骤来修复树结构。构造了三个匿名块。第一个块包含所有位于 div 之前的行内元素。第二个匿名块包含 div。第三个匿名块包含所有位于 div 之后的行内元素。

<anonymous pre block>
<i>Italic only <b>italic and bold</b></i>
</anonymous pre block>
<anonymous middle block>
<div>
Wow, a block!
</div>
<div>
Wow, another block!
</div>
</anonymous middle block>
<anonymous post block>
<i><b>More italic and bold text</b> More italic text</i>
</anonymous post block>

注意,粗体和斜体渲染器不得不分裂成两个渲染对象,因为它们同时位于匿名前块和匿名后块中。对于粗体 DOM 元素的情况,它的子元素位于前块中,然后继续到中间块,最后继续到后块。渲染树通过一个延续链连接这些对象。

RenderFlow* continuation() const
bool isInlineContinuation() const

前块中的第一个粗体渲染器是可以从 b DOM 元素使用元素的 renderer() 方法获取的那个。这个渲染器的 continuation() 是中间的匿名块。中间匿名块的 continuation() 是第二个粗体渲染器。通过这种方式,需要检查代表 DOM 元素子元素的渲染器的代码仍然可以相对容易地做到。

在上面的示例中,i DOM 元素也分裂了。它的子元素全部位于前块和后块中,因此只需要一个连接。前块中的斜体渲染器将其 continuation() 设置为后块中的斜体渲染器。

执行行内流递归分裂并创建延续链连接的函数称为 splitFlow,它位于 RenderInline.cpp 中。