The browser engine is sometimes referred to as a rendering engine or a layout engine, but these are incorrect terms. The rendering engine and the layout engine are subcomponents of the browser engine. The layout engine is described in Build the Render Tree below. The rendering engine is described in Render the Document below.
1) Build the Content Tree
The content tree’s structure represents the structure of the document. The relationship between HTML tags and the HTML block in which they appear is represented by a child/parent relationship within the content tree. For example, the html node has two children, the head node and the body node. Another example: If a <div> section of the HTML contains five paragraphs, then the corresponding div node in the content tree has five p nodes dangling from it.
The HTML parser is very forgiving. When we violate HTML rules, it does its best to figure out what we meant and to present some output that might be what we’re looking for. Unfortunately, there is a performance penalty for that extra effort. For best performance, use well-formed XHTML instead of HTML.
Reduce the size of the content tree, especially its depth.
2) Build the Style Structure
I expect the style structure is not cascading, but rather something like a list of already-cascaded styles that apply to nodes in the content tree. It therefore contains fully-realized formatting rules (i.e., effective styles as opposed to cascading styles).
The style structure differs from browser engine to browser engine. There is no explicit or de facto standard, but that’s okay because there is no public API into it. Only the parser and the rendering engine need to understand it.
Minimize the number of CSS rules. Eliminate duplicated and unused rules.
3) Build the Render Tree
After the content tree and style structure are built, the layout engine uses that information to build the render tree. Now, that’s a bit of a lie, isn’t it? The content tree and the style structure need not be fully built. The layout engine can start determining positions and sizes as soon as the first little bit of information becomes available. True, it may need to duplicate some of its effort (e.g., an element that is relatively positioned above elements that have already been laid out will require those other elements to be laid out a second time), but delaying startup until the parser and layout engine are completely finished can take even longer.
After the page is completely loaded, it’s a different story. See the Reflow section in Part XIII for tips we can use after the page loads (i.e., in response to events).
Avoid HTML tables. Never use them for formatting; use <div>s instead. If you must use them, keep them as small as possible. Layout can almost always be calculated in a single pass, but HTML tables may need two passes.
There is no correspondence between the content tree’s structure and the render tree’s. The content tree’s structure reflects the document’s HTML structure. The render tree’s structure reflects the positioning of the frames seen by the end-user, left-to-right and top-to-bottom. These two structures can be quite different.
Some nodes found in the content tree are not found in the render tree. For example, the head node will not appear in the render tree because it requires no rendering. Another example: Content tree nodes that are not visible (e.g., display:none in CSS) are not included in the render tree.
Some nodes found in the render tree are not found in the content tree. For example, each line of text requires a separate node in the render tree, but they are lumped together into one text node in the content tree.
4) Render the Document
Did you notice how much repetition there is? Most frames are built and rebuilt several times. This is an example of the repetition mentioned in Build the Render Tree above. Each change can trigger any number of other changes.
Rendering does not really happen after the previous step. It can begin as soon as the layout engine puts something into the render tree. Then it runs concurrently with the parser and layout engine.
If the rendering engine responds too slowly to changes in the rendering tree, that is an obvious performance issue. However, responding too quickly can also become a performance issue. If a script or stylesheet changes previously-rendered nodes, the rendering engine can find itself rendering, re-rendering, re-re-rendering, and so on. [This is actually much more common than one might expect.]
Specify all formatting in the <head> and specify all content in the <body>. If a document provides formatting information after the content to which it applies, the rendering engine will use default formatting rules when it encounters the content. Later, when the layout engine sees the new formatting information, it will relayout the frame and all affected frames, then the rendering engine will have to do all its time-consuming work again.
If the layout engine were to wait a few milliseconds for the content tree and style structure to settle down into their final state, it can do the layout once rather than multiple times. This will trigger one change to the render tree instead of multiple changes, which will make the rendering engine do its job once instead of multiple times.
In fact, some browsers do improve performance by purposely delaying rendering, but in one way they are at the mercy of the programmer. If the script asks for current layout information, the layout engine has to immediately process all the queued frames before it can answer the question. By clearing the queue, answering the question eliminates the performance improvements that could have been. Programmers should avoid code that queries the size (height or width) or position (top or left) of any element.
5) When onLoad Fires
In the grand scheme of things, it’s not really onLoad that determines when a document is interactive, it’s the end-user. Some elements must be present and fully functional before the end-user will consider the page ready to go. Other elements aren’t quite so urgent. They can wait a few milliseconds, or perhaps even a few hundred milliseconds.
The developer must be aware of the end-user’s thought processes. He must know what the user needs immediately vs. what he needs in a second or two. Example: A typical user needs to see the top of the document sooner than the bottom of the document. The top is therefore needed-now and the bottom is therefore needed-soon. Other elements that may be needed-soon or needed-later: footers, advertising, dynamic content, images, etc.
Code that creates needed-later elements can be executed after onLoad fires. We note that this is a perceived performance improvement, not a real one. However, from the end-user’s perspective, it is very real. He can get on with what he wants to do sooner. He may not even notice that some elements have not been fully created yet.
If, however, most users do notice the delay in an element, then we have incorrectly classified the element as needed-later when it is really needed-now. Performance must always relate back to the end-user’s wait time.
Since the layout engine and the rendering engine execute concurrently, the layout engine can make changes to frames that have already been drawn. When this happens, the frames need to be drawn again. If this were only an occasional occurrence, it wouldn’t be a problem, but in real life it happens far too often.
Part XIII tells how post-loading, event-driven scripts affect performance. Watch for it on the Monitor.Us Blog.