Surfin' Safari

WebCore Rendering II – Blocks and Inlines

Posted by Dave Hyatt on Thursday, August 9th, 2007 at 2:59 pm

In the previous entry I talked about the basic structure of a CSS box. In this article I’m going to talk about subclasses of RenderBox and about the concepts of block and inline.

A block flow is a box designed either to contain lines (e.g., a paragraph) or to contain other blocks that it stacks vertically. Example block flow elements in HTML are p and div.

An inline flow is an object that is designed to be part of a line. Example inline flow elements in HTML are a, b, i and span.

In WebCore, there are three renderer classes that cover block and inline flows. RenderBlock, RenderInline and their common superclass RenderFlow.

RenderFlow.h
RenderBlock.h
RenderInline.h

An inline flow can be changed to a block flow (and vice versa) using the CSS display property.

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

In addition to block and inline flows, there is another kind of element that can act as a block or inline: the replaced element. A replaced element is an element whose rendering is unspecified by CSS. How the contents of the object render is left up to the element itself. Examples of replaced elements are images, form controls, iframes, plugins and applets.

A replaced element can also be either block-level or inline-level. When a replaced element acts as a block, it will get stacked vertically as though it represents its own paragraph. When a replaced element acts as an inline, it will be part of a line inside a paragraph. Replaced elements are inline by default.

Form controls are actually a strange special case. They are still replaced elements, but because they are implemented by the engine, controls actually ultimately subclass from RenderBlock. As a result, the concept of being replaced can’t really be confined to a single common subclass, and is therefore represented as a bit on RenderObject instead. The isReplaced method can be used to ask if an object is a replaced element.

bool isReplaced() const

Images, plugins, frames and applets all inherit from a common subclass that implements replaced element behavior. This class is RenderReplaced.

RenderReplaced.h

The Inline Block

One of the most confusingly named objects in CSS is the inline-block. Inline blocks are block flows that are designed to sit on a line. In effect they are like inline replaced elements on the outside, but on the inside they are block flows. The display property in CSS can be used to create inline blocks. Inline blocks will report true if asked if they are replaced.

div { display: inline-block }

Tables

Tables in HTML are block-level by default. However they can also be made into inlines using the CSS display property with a value of inline-table.

table { display: inline-table }

Again, from the outside an inline-table is like an inline replaced element (and will return true from isReplaced), but on the inside the object is still just a table.

In WebCore the RenderTable class represents a table. It inherits from RenderBlock for reasons that will be covered in the positioning section later.

RenderTable.h

Text

Raw text is represented using the RenderText class. Text is always considered inline by WebCore, since it is always placed on lines.

RenderText.h

Getting Block and Inline Information

The most basic method for obtaining block vs. inline status is the isInline function. This method asks if an object is designed to be part of a line. It does not care what the interior of the element is (e.g., text, image, an inline flow, an inline-block or an inline-table).

bool isInline() const

One of the common mistakes people make when working with the render tree is assuming that isInline means an object is always an inline flow, text or an inline replaced element. However because of inline-blocks and inline-tables, this method can return true even for these objects.

To ask if an object is actually a block or inline flow, the following methods should be used.

bool isInlineFlow() const
bool isBlockFlow() const

These methods are essentially asking questions about the interior of the object. An inline-block for example is still a block flow and not an inline flow. It is inline on the outside, but on the inside it is a block flow.

The exact class type can be queried for blocks and inlines using the following methods.

bool isRenderBlock() const
bool isRenderInline() const

The isRenderBlock method is useful in the context of positioning, since both block flows and tables act as positioned object containers.

To ask if an object is specifically an inline block or inline table, the isInlineBlockOrInlineTable method can be used.

bool isInlineBlockOrInlineTable() const

Children of Block Flows

Block flows have a simple invariant regarding their children that the render tree always obeys. That rule can be summarized as follows:

All in-flow children of a block flow must be blocks, or all in-flow children of a block flow must be inlines.

In other words, once you exclude floating and positioned elements, all of the children of a block flow in the render tree must return true from isInline or they must all return false from isInline. The render tree will change its structure as needed to preserve this invariant.

The childrenInline method is used to ask whether the children of a block flow are inlines or blocks.

bool childrenInline() const

Children of Inline Flows

Children of inline flows have an even simpler invariant that must be maintained.

All in-flow children of an inline flow must be inlines.

Anonymous Blocks

In order to preserve the block flow child invariant (only inline children or only block children), the render tree will construct objects called anonymous blocks. Consider the following example:

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

In the above example, the outer div has two children: some text and another div. The first child is an inline, but the second child is a block. Because this combination of children violates the all-inline or all-block child rule, the render tree will construct an anonymous block flow to wrap the text. The render tree therefore becomes:

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

The isAnonymousBlock method can be used to ask if a renderer is an anonymous block flow.

bool isAnonymousBlock() const

Whenever a block flow has inline children and a block object suddenly tries to insert itself as a child, anonymous blocks will be created as needed to wrap all of the inlines. Contiguous inlines will share a single common anonymous block, so the number of anonymous blocks can be kept to a minimum. The makeChildrenNonInline method in RenderBlock is the function that performs this adjustment.

void makeChildrenNonInline(RenderObject *insertionPoint)

Blocks inside Inline Flows

One of the nastiest constructs you will see in HTML is when a block is placed inside an inline flow. Here is an example:

<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>

The two divs violate the invariant that all of the children of the bold element must be inlines. The render tree has to perform a rather complicated series of steps in order to fix up the tree. Three anonymous blocks are constructed. The first block holds all of the inlines that come before the divs. The second anonymous block holds the divs. The third anonymous block holds all of the inlines that come after the divs.

<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>

Notice that the bold and italic renderers had to split into two render objects, since they are in both the anonymous pre block and the anonymous post block. In the case of the bold DOM element, its children are in the pre block, but then continue into the middle block and then finally continue into the post block. The render tree connects these objects via a continuation chain.

RenderFlow* continuation() const
bool isInlineContinuation() const

The first bold renderer in the pre block is the one that can be obtained from the b DOM element using the element’s renderer() method. This renderer has as its continuation() the middle anonymous block. The middle anonymous block has as its continuation() the second bold renderer. In this way code that needs to examine the renderers that represent the children of the DOM element can still do so relatively easily.

In the above example the i DOM element also split. Its children are all in the pre and post blocks, and therefore only one connection is needed. The italic renderer in the pre block sets its continuation() to the italic renderer in the post block.

The function that performs the recursive splitting of inline flows and that creates the continuation chain connections is called splitFlow and is in RenderInline.cpp.

3 Responses to “WebCore Rendering II – Blocks and Inlines”

  1. Chris Griego Says:

    If the problem is having a block-level element within an inline element, then why is the anonymous middle block necessary?

  2. hyatt Says:

    Strictly speaking it isn’t necessary, although it simplifies a lot of the code that deals with continuations. It’s basically there for convenience.

  3. hyatt Says:

    Longer term, I’d like to explore eliminating continuations completely and wrapping the offending blocks in an anonymous inline-block instead. The difficult problem with using an anonymous inline-block, however, is that when the pre and post sections end up having no content, you have to margin collapse the offending blocks with surrounding content. This would necessitate having a sort of hybrid line/block layout that could easily slide into and out of both layout modes.