How WebCore Handles HTML Parsing and Layout in Safari
This article provides a technical overview of how the WebCore component within the WebKit engine processes web pages for the Safari browser. It covers the essential stages of preparing a webpage for display, specifically detailing how WebCore parses raw HTML into a Document Object Model (DOM), combines it with CSS styles, and executes the layout phase to determine the exact size and position of elements on the screen.
HTML Parsing and DOM Construction
The rendering process in WebCore begins the moment raw bytes of an
HTML document arrive from the network. WebCore converts these bytes into
characters based on the file's specified encoding and passes them to the
HTMLDocumentParser.
Parsing is divided into two main sub-processes: tokenization and tree construction.
- Tokenization: The
HTMLTokenizerbreaks the character stream into distinct tokens, such as start tags, end tags, attribute names, and text content. It uses a state machine to handle the complexities and forgiving nature of HTML syntax, ensuring even broken HTML is parsed predictably. - Tree Construction: The
HTMLTreeBuildertakes the tokens generated by the tokenizer and constructs the Document Object Model (DOM) tree. Each token is converted into a specificNodeorElementC++ object in memory. This tree represents the hierarchical structure of the web page and serves as the foundation for both rendering and JavaScript manipulation.
If the parser encounters a blocking resource, such as a synchronous
<script> tag, the HTMLDocumentParser
halts tree construction until the script is fetched and executed. To
prevent network bottlenecks during this pause, WebCore uses a
lightweight HTMLPreloadScanner to look ahead in the
unparsed HTML and speculatively fetch external resources like images and
stylesheets.
Style Resolution
Before WebCore can calculate the layout of a page, it must know what
visual rules apply to each element. As the DOM is being built, WebCore
simultaneously parses CSS files and <style> blocks
into internal style sheets.
Once the DOM and CSS rules are processed, WebCore performs style
resolution. The StyleResolver matches CSS selectors against
DOM nodes to compute the final set of CSS properties for every element.
The result of this computation is stored in an object called
RenderStyle, which holds all the visual and geometric
properties—such as margins, display types, and dimensions—for a given
DOM node.
Constructing the Render Tree
With the DOM tree and RenderStyle objects in place,
WebCore builds the Render Tree. While the DOM tree represents the
document's structure and content, the Render Tree represents what will
actually be drawn on the screen.
The Render Tree is composed of RenderObject nodes. For
most visible DOM nodes, WebCore creates a corresponding
RenderObject and attaches the computed
RenderStyle to it.
The DOM tree and the Render Tree do not have a one-to-one mapping:
- Non-visual DOM elements, such as
<head>,<script>, or elements with the CSS propertydisplay: none, are omitted from the Render Tree entirely. - Pseudo-elements, like
::beforeand::after, do not exist in the DOM, but they are inserted as physical nodes in the Render Tree. - Elements with
visibility: hiddenare included in the Render Tree because they still take up physical space in the layout, even though they are not visibly painted.
The Layout Phase
Once the Render Tree is constructed, WebCore moves to the layout
phase. At this point, the RenderObject nodes exist, but
they do not have calculated positions or dimensions. The goal of the
layout phase is to compute the exact coordinates and bounding boxes for
every node in the Render Tree based on the browser's viewport size.
Layout is a recursive process that begins at the root of the Render
Tree, known as the RenderView, which corresponds to the
Safari window's visible viewport.
- Parent-to-Child Traversal: The
RenderViewdetermines the available width based on the browser window and passes this constraint down to its child renderers. - Box Model Calculation: Each
RenderBox(a subclass ofRenderObjectfor elements that adhere to the CSS box model) calculates its own width, height, margins, padding, and borders. Generally, an element's width is constrained by its parent, while its height is determined by the cumulative height of its children. - Formatting Contexts: WebCore uses different layout
algorithms depending on the CSS display properties in use. It contains
specific logic for standard block and inline flows, tables, Flexbox
(
RenderFlexibleBox), and CSS Grid (RenderGrid). WebKit also utilizes a modern architecture called Layout Formatting Contexts (LFC), which calculates inline and block layout geometry using an immutable tree structure to improve speed and reliability.
Incremental Layout and Dirty Bits
Recalculating the layout of an entire webpage is computationally expensive. If a user resizes a single element or JavaScript injects new text, WebCore avoids laying out the whole page from scratch by using a "dirty bit" system.
When a change occurs that affects geometry, WebCore marks that
specific RenderObject and its ancestor chain as "dirty"
(needing layout). During the next layout pass, WebCore traverses the
Render Tree from the root, ignoring branches that are not marked as
dirty, and only recalculates the geometry for the affected subtrees.
Once the layout phase completes and all RenderObject
nodes have valid coordinates and dimensions, WebCore passes the updated
tree to the painting and compositing phases, which finally draw the
pixels into the Safari window.