Notes on HTML Reflow
Reflow is the process by which the geometry of the layout engine's formatting objects are computed. The HTML formatting objects are called frames : a frame corresponds to the geometric information for (roughly) a single element in the content model; the frames are arranged into a hierarchy that parallels the containment hierarchy in the content model. A frame is rectangular, with width, height, and an offset from the parent frame that contains it.
More than one frame may be needed to represent a single element from the content model; for example, text that wraps is broken into several frames, one per wrapped line. In this case, the primary frame is the frame containing the first line of text, with continuing frames (or continuations ) created for subsequent lines.
HTML uses a flow based layout model, meaning that most of the time it is possible to compute the geometry in a single pass. Elements later "in the flow" typically do not affect the geometry of elements that are earlier "in the flow", so layout can proceed left-to-right, top-to-bottom through the document. There are exceptions to this rule: most notably, HTML tables may require more than one pass.
The XUL box layout model, on the other hand, is constraint based , meaning that geometric preferences and constraints of neighboring elements are taken into consideration before the elements' final geometry can be computed. The box is the geometric primitive for the XUL layout model.
All HTML reflow, including the
, begins at the
, which corresponds to the
<html> element of the HTML document. Reflow proceeds recursively through some or all of the frame hierarchy, computing geometric information for each frame object that requires it. Reflow may have the side-effect of creating new continuation frames, for example, for a text frame when the text must be wrapped.
Some reflows are
in response to user or script actions; for example, resizing the window or changing the document's default font. These are dispatched directly from the presentation shell (e.g.,
nsIPresShell::StyleChangeReflow), and affect the entire frame tree. Other reflows are
and are dealt with asynchronously; for example, when content streams in from the network. Incremental reflows are queued by the presentation shell for batched dispatch.
nsHTMLReflowState, is used to pass constraining information "down" from parent frames to child frames. For example, a
<div> with a constrained width (e.g., set via the CSS
width property) would note this in the reflow state object before flowing its children.
When reflow begins, the
root reflow state
is initialized with information about the top-level container for the document's presentation; e.g., the width and height of the application window. This is passed as an argument to the
Reflow method of the root frame in the frame hierarchy.
Each container frame constructs a new reflow state object (based on its container's reflow state object) in which the container will reflow its children. Most of the constraints in the new reflow state are computed when the state is created; for example, the available space in the new reflow state is computed by subtracting the container frame's border and padding from the parent reflow state's available space.
All reflows have a reason , which is maintained in the reflow state object (and may mutate, as described below). The reflow reason controls how a frame reacts during a reflow, and is one of the following:
Initial, for the very first time that the frame hierarchy is flowed. In this case, a frame knows that there is no residual state that can be used to simplify geometry computation.
Incremental, when something in the frame tree changes; for example, when more content is read from the network, or some script manipulates the DOM. An incremental reflow is targeted at a single frame in the frame hierarchy. During an incremental reflow, a frame can assume that none of the constraints computed "from above" (for example, available width) have changed; instead, something "within" the frame has changed, which may have bottom-up impact to the frame hierarchy.
Resize, when the containing boundary for the frame hierarchy changes. During a resize reflow, the frame can assume that none of the frame's internal state (e.g., a text frame's text) has changed; instead, a top-down change in the layout constraints has occurred.
StyleChange, when the entire frame hierarchy must be traversed to recover from stylistic change; for example, a change in the default font size.
Dirty, when a container frame has consolidated several individual
Incrementalreflows that have been targeted at its child frames.
Initial, incremental, resize, and style change reflows may each be performed as an immediate "global" reflow from the presentation shell:
- An initial reflow is performed when the presentation shell is initialized to flow the shell's initial frame hierarchy.
- Incremental reflows are dispatched en masse when the presentation shell's incremental reflow queue is asynchronously serviced.
- A resize reflow is performed when the presentation shell's dimensions change; e.g., because the user resized the shell's window.
- A style change reflow is performed when the presentation shell's global stylistic information is changed; e.g., addition or removal of a style sheet, a change to the shell's default font.
A dirty reflow is never performed directly from the presentation shell. Instead, a dirty reflow is detected when an incremental reflow reaches its target frame , described below.
nsHTMLReflowMetrics, is used to propagate information from child frames back to the parent. For example, the dimensions of each child frame of an unconstrained
<div> would be passed back to the
<div>'s frame via the
Although all of the reflow in Gecko attempts to re-use as much existing state as possible (and is in therefore some sense "incremental") an
Incremental reflow corresponds to a reflow that is specifically targeted at an individual frame in the frame hierarchy. A frame requests a
Incremental reflow (or one is requested on a frame's behalf) when something about the frame itself has changed.
Scheduling. To request (or
) an incremental reflow (e.g., in response to a change in the content model), a
object is created and passed to the presentation shell via the
nsIPresShell::AppendReflowCommand method. The presentation shell does not process the command immediately. Instead, it queues the command, and processes it asynchronously along with other queued reflow commands
Coalescing. As described below, the reflow command has a type and a target frame . Multiple reflow commands with the same type and target frame are coalesced : the presentation shell simply refuses to add subsequent commands of the same type for the same frame to the queue. A caller may also cancel a reflow command that is in the queue; e.g., if the target frame is destroyed.
Dispatch. The presentation shell processes the reflow queue by removing a single reflow command from the queue and
it to its target frame. (But cf. the reflow tree work, which will remove several commands from the queue at once.) A
is built from the target frame to the
and stored in the reflow command. A
object is created with a
Incremental, the reflow command is stored in the state, and the
Reflow method of the root frame is invoked.
Processing. The root frame notes the
Incremental reflow reason specified in the reflow state, and inspects the path contained within the reflow command object. Specifically, it extracts the
along the path from the reflow command object, creates its own reflow state, also with an
Incremental reason, and invokes the
Reflow method of the next frame.
The incremental reflow proceeds recursively through the frame hierarchy. Each frame along the incremental reflow path (as specified in the reflow command object) extracts the next frame and dispatches the reflow downward. In order to correctly dispatch the reflow to the child frame, the frame may need to perform some state recovery ; for example, a block frame will traverse its line list to recover the space occupied by floated frames.
At some point, the incremental reflow reaches the target frame , at which point the reflow command's type becomes significant.
ContentChangedindicates that the content corresponding to the target frame has changed somehow; for example, the text associated with a text frame has been modified. In reality, the only frame that responds to this sort of reflow is the block frame. The block frame treats this sort of change as a `full reflow' (i.e., as a resize). This makes me believe that we could probably eliminate this class altogether.
StyleChangedindicates that the stylistic information corresponding to the target frame has changed; for example, the font size has increased. This causes the frame to mutate the reflow state's reason to
StyleChange, which is propagated recursively to the entire subtree beneath the target frame.
ReflowDirtyindicates that container frame has decided to coalesce several incremental reflows targeted at its children into a single reflow. The container frame maintains the necessary state to determine which children must be reflowed.
UserDefined, for "special situations". Currently, this is only used by the viewport frame to schedule a reflow to reflow all of the viewport's fixed-position frames. We should probably try to eliminate it.
An incremental reflow may damage other parts of the frame hierarchy; for example, changing the size of the font used in a specific paragraph may cause the paragraph to grow or shrink. A container must therefore propagate any damage that the incremental reflow of the child frame caused, possibly reflowing other children as well.
If several incremental changes occur in the same part of the frame hierarchy, it is possible to have several
Incremental reflows targeted at nearby frames. In this case, it is likely that the individual
Incremental reflows will end up doing redundant work. For example, each keystroke typed into a text widget could generate a separate
Incremental reflow targeted at the text frame. Were we to process each individually, the text widget would be flowed once for each keystroke, which would be wasteful if the latency of an individual reflow exceeds the speed at which text is being typed. The purpose of the
Dirty reflow is to allow these individual reflows to be coalesced intelligently.
A frame that decides it needs a dirty reflow sets the
NS_FRAME_IS_DIRTY state bit on itself, and then calls the
ReflowDirtyChild method on its parent frame. In
ReflowDirtyChild, the parent frame sets the
NS_FRAME_HAS_DIRTY_CHILD state bit on itself, and does any bookkeeping necessary to remember which child is dirty (for example, the block frame marks the linebox dirty that contains the child frame). The parent frame can then either decide to schedule a
Incremental reflow targeted at itself, or to delegate that responsibility to
parent. If it decides to delegate, then it sets the
NS_FRAME_IS_DIRTY state bit on itself and recursively calls
Incremental reflow is dispatched, and arrives at the container frame that scheduled it. The target frame recovers its bookkeeping information (e.g., the block frame iterates through the dirty lineboxes), and reflows the dirty child frames.
Isn't information lost if a
Incremental reflow coalesces different kinds of incremental reflows (e.g., a
ContentChanged with a
StyleChanged)? No, because these kinds of reflows
coalesced; instead, they're directly enqueued to the presentation shell's reflow queue.
HTML and XUL Interaction
As mentioned above, HTML and XUL have fundamentally different layout models, the former being a flow-based model, and the latter being a constraint based model. These differences are mediated by two adapter classes:
nsBoxFrame is an HTML frame that "wraps" a XUL box. Its purpose is to convert HTML reflows their box analog.
nsBoxToBlockAdaptor is a XUL box that wraps an HTML block frame, used to convert changes in the box layout into HTML reflows.