What is RenderObject?
After style matching is complete, we get a DOM tree with complete CSS style content. However, the content on the DOM tree cannot be directly used as data to draw things on the screen. Therefore, in the next step, we need to use the DOM tree as a basis to generate a new tree — the RenderObject tree.
Correspondence between DOM and RenderObject
Compared to the DOM tree, each node in the RenderObject tree will compute the properties in the DOM’s Style into content that can be directly drawn to the screen. Unlike how each DOM has its own Element class, there aren’t many types of RenderObject. In most cases, we’ll only see:
- RenderInline
- RenderBlockFlow
- RenderFlexibleBox
- RenderText
- RenderImage
The rules for generating these RenderObjects from DOM are as follows:
DOM Type | RenderObject Type |
---|---|
Plain text in DOM | RenderText |
Elements with display: flex | RenderFlexibleBox |
Elements with display: block, inline-block | RenderBlockFlow |
Elements with display: inline | RenderInline |
element | RenderImage |
Rules for Anonymous Box Generation
If there are no exceptional cases, a DOM would correspond one-to-one with a RenderObject. However, in actual implementation, due to some special behaviors defined in the Web specification, this process cannot be this simple.
In the following cases, RenderObject and DOM will not have a one-to-one relationship:
- Block, Flex container elements with direct Text children
- Block, Inline elements containing both inline-level and block-level elements
- Specifically, when an Inline element contains block-level elements, the inline element will correspond to two or even more RenderObjects
To summarize simply: in all scenarios where anonymous boxes are generated, RenderObject and DOM will not correspond one-to-one.
Let’s expand on these situations that generate anonymous boxes
Block, Flex Container Elements with Direct Text Children
The anonymous box strategy in this case is relatively simple, just wrap a Block Box directly around the Text:
However, there is a difference between Flex container elements and Block containers. Flex container elements won’t have direct Text children, while Block can:
Block, Inline Elements Containing Both Inline-level and Block-level Elements
For Block elements, things are relatively simple. If its direct children contain both inline-level and block-level elements, simply wrap all inline-level children in an anonymous Block Box:
For Inline elements, things get more complicated.
First, the Inline element will find its parent element’s Block Box (i.e., Block Container), split itself into multiple “clones”, then wrap these “clones” and inline-level children in an anonymous Block Box;
At the same time, the block-level children will also be wrapped in an anonymous block box, which exists as a direct child of the Block Container (i.e., “promoted”):
If there are n block-level children, the Inline element will have n+1 “clones”:
Responsibilities of the RenderObject Generation Module
- Generate corresponding RenderObject based on DOM
- Create anonymous RenderObject
How are Anonymous Boxes Generated in Browsers?
In browsers, these steps occur during the process of mounting newly generated RenderObjects to other RenderObjects.
In WebCore, this part of the code logic is in RenderTreeBuilder::attachInternal(); in Blink, it’s in LayoutTreeBuilderForElement::CreateLayoutObject() (more specifically, in the AddChild methods of LayoutBlockFlow / LayoutInline / LayoutBlock).
This part of the code logic can be simply described as:
- Current element is inline
- When “all children are block-level”
- Added inline-level child: Add the child to the last “clone”
- Added block-level child: Process same as 1.b.II
- When “all children are inline-level” or “no children”
- Added inline-level child: Add directly
- Added block-level child: Find the block container of this block (Note: might not be the direct parent element of this block, could span several layers), wrap all inline-level boxes before this block (including parent elements’, and no block boxes in between) in an anonymous block box (let’s call it anonymous block box 1), insert it after the previous block of the block container; at the same time, wrap the current block box in an anonymous block box (let’s call it anonymous block box 2), insert anonymous block box 2 after anonymous block box 1, both as children of the block container
- When “all children are block-level”
- Current element is block
- When “all children are inline-level”:
- Added inline-level child: Add directly
- Added block-level child: Wrap all previous inline boxes in a block box (Blink code see MakeChildrenNonInline)
- When “all children are block” or “no children”:
- Added inline-level child: Wrap an anonymous block box around the inline; if there’s already an anonymous block for wrapping inline, add to that anonymous block
- If the element before this inline is plain text, wrap the text in an anonymous inline block
- Added block-level child: Add directly
- Added inline-level child: Wrap an anonymous block box around the inline; if there’s already an anonymous block for wrapping inline, add to that anonymous block
- When “all children are inline-level”:
- Current element is flex
- Added non-text child: Add directly (display of flex container’s children will be changed to block if it’s inline or inline-block)
- Added text element: Wrap a block box around the text
If you want to verify what kind of RenderObject tree a DOM will generate, you can use the DumpRenderTree tool in WebKit:
<path-to-webkit>/WebKitBuild/Debug/DumpRenderTree <webpage-url>
For example, the DOM structure above:
<div>
<span>
<span>inline level box</span>
<div>block level box</div>
<span>inline level box</span>
<div>block level box</div>
<span>inline level box</span>
<div>block level box</div>
<span>inline level box</span>
</span>
</div>
Will print the following result: