This article is found when I clear my Evernote in the afternoon, it’s a fantastic story to get known the flow of how browser render a page, it’s also very help to understand font-side developing in some view. Share this article to blog, hope to find some time to translate it to Chinese.
The browser main functionality is to present the web resource you choose, by requesting it from the server and displaying it on the browser window. The resource is usually an HTML document, but may also be a PDF, image, or other type. The location of the resource is specified by the user using a URI (Uniform resource Identifier).
For years browsers conformed to only a part of the specifications and developed their own extensions. That caused serious compatibility issues for web authors. Today most of the browsers more or less conform to the specifications.
Strangely enough, the browser’s user interface is not specified in any formal specification, it just comes from good practices shaped over years of experience and by browsers imitating each other. The HTML5 specification doesn’t define UI elements a browser must have, but lists some common elements. Among those are the address bar, status bar and tool bar. There are, of course, features unique to a specific browser like Firefox’s downloads manager.
The user interface – this includes the address bar, back/forward button, bookmarking menu etc. Every part of the browser display except the main window where you see the requested page.
The browser engine – marshalls the actions between the UI and the rendering engine.
The rendering engine – responsible for displaying the requested content. For example if the requested content is HTML, it is responsible for parsing the HTML and CSS and displaying the parsed content on the screen.
Networking – used for network calls, like HTTP requests. It has platform independent interface and underneath implementations for each platform.
UI backend – used for drawing basic widgets like combo boxes and windows. It exposes a generic interface that is not platform specific. Underneath it uses the operating system user interface methods.
Data storage. This is a persistence layer. The browser needs to save all sorts of data on the hard disk, for examples, cookies. The new HTML specification (HTML5) defines ‘web database’ which is a complete (although light) database in the browser.
Figure : Browser main components.
It is important to note that Chrome, unlike most browsers, holds multiple instances of the rendering engine – one for each tab. Each tab is a separate process.
The responsibility of the rendering engine is well… Rendering, that is display of the requested contents on the browser screen.
By default the rendering engine can display HTML and XML documents and images. It can display other types through a plug-in (or browser extension); for example, displaying PDF using a PDF viewer plug-in. However, in this chapter we will focus on the main use case: displaying HTML and images that are formatted using CSS.
Our reference browsers – Firefox, Chrome and Safari are built upon two rendering engines. Firefox uses Gecko – a “home made” Mozilla rendering engine. Both Safari and Chrome use Webkit.
Webkit is an open source rendering engine which started as an engine for the Linux platform and was modified by Apple to support Mac and Windows. See webkit.org for more details.
The rendering engine will start getting the contents of the requested document from the networking layer. This will usually be done in 8K chunks.
Figure : Rendering engine basic flow.
The rendering engine will start parsing the HTML document and turn the tags to DOMnodes in a tree called the “content tree”. It will parse the style data, both in external CSS files and in style elements. The styling information together with visual instructions in the HTML will be used to create another tree – the render tree.
The render tree contains rectangles with visual attributes like color and dimensions. The rectangles are in the right order to be displayed on the screen.
After the construction of the render tree it goes through a “layout” process. This means giving each node the exact coordinates where it should appear on the screen. The next stage is painting – the render tree will be traversed and each node will be painted using the UI backend layer.
It’s important to understand that this is a gradual process. For better user experience, the rendering engine will try to display contents on the screen as soon as possible. It will not wait until all HTML is parsed before starting to build and layout the render tree. Parts of the content will be parsed and displayed, while the process continues with the rest of the contents that keeps coming from the network.
From figures 3 and 4 you can see that although Webkit and Gecko use slightly different terminology, the flow is basically the same.
Since parsing is a very significant process within the rendering engine, we will go into it a little more deeply. Let’s begin with a little introduction about parsing.
Parsing a document means translating it to some structure that makes sense – something the code can understand and use. The result of parsing is usually a tree of nodes that represent the structure of the document. It is called a parse tree or a syntax tree.
Parsing is based on the syntax rules the document obeys – the language or format it was written in. Every format you can parse must have deterministic grammar consisting of vocabulary and syntax rules. It is called a context free grammar. Human languages are not such languages and therefore cannot be parsed with conventional parsing techniques.
Parsing can be separated into two sub processes – lexical analysis and syntax analysis.
Lexical analysis is the process of breaking the input into tokens. Tokens are the language vocabulary – the collection of valid building blocks. In human language it will consist of all the words that appear in the dictionary for that language.
Syntax analysis is the applying of the language syntax rules.
Parsers usually divide the work between two components – the lexer (sometimes called tokenizer) that is responsible for breaking the input into valid tokens, and the parser that is responsible for constructing the parse tree by analyzing the document structure according to the language syntax rules. The lexer knows how to strip irrelevant characters like white spaces and line breaks.
The parsing process is iterative. The parser will usually ask the lexer for a new token and try to match the token with one of the syntax rules. If a rule is matched, a node corresponding to the token will be added to the parse tree and the parser will ask for another token.
If no rule matches, the parser will store the token internally, and keep asking for tokens until a rule matching all the internally stored tokens is found. If no rule is found then the parser will raise an exception. This means the document was not valid and contained syntax errors.
Many times the parse tree is not the final product. Parsing is often used in translation – transforming the input document to another format. An example is compilation. The compiler that compiles a source code into machine code first parses it into a parse tree and then translates the tree into a machine code document.
In figure 5 we built a parse tree from a mathematical expression. Let’s try to define a simple mathematical language and see the parse process.
Vocabulary: Our language can include integers, plus signs and minus signs.
The language syntax building blocks are expressions, terms and operations.
Our language can include any number of expressions.
2 + 3 – 1.
2 + 3this matches the third rule – a term followed by an operation followed by another term. The next match will only be hit at the end of the input.
2+3is a term so we have a term followed by an operation followed by another term.
2 + + will not match any rule and therefore is an invalid input.
Vocabulary is usually expressed by regular expressions.
As you see, integers are defined by a regular expression.
There are two basic types of parsers – top down parsers and bottom up parsers. An intuitive explanation is that top down parsers see the high level structure of the syntax and try to match one of them. Bottom up parsers start with the input and gradually transform it into the syntax rules, starting from the low level rules until high level rules are met.
2 + 3 – 1as an expression (the process of identifying the expression evolves matching the other rules, but the start point is the highest level rule).
The bottom up parser will scan the input until a rule is matched it will then replace the matching input with the rule. This will go on until the end of the input. The partly matched expression is placed on the parsers stack.
This type of bottom up parser is called a shift-reduce parser, because the input is shifted to the right (imagine a pointer pointing first at the input start and moving to the right) and is gradually reduced to syntax rules.
There are tools that can generate a parser for you. They are called parser generators. You feed them with the grammar of your language—its vocabulary and syntax rules—and they generate a working parser. Creating a parser requires a deep understanding of parsing and its not easy to create an optimized parser by hand, so parser generators can be very useful.
Webkit uses two well known parser generators – Flex for creating a lexer and Bison for creating a parser (you might run into them with the names Lex and Yacc). Flex input is a file containing regular expression definitions of the tokens. Bison’s input is the language syntax rules in BNF format.
The job of the HTML parser is to parse the HTML markup into a parse tree.
The vocabulary and syntax of HTML are defined in specifications created by the W3C organization. The current version is HTML4 and work on HTML5 is in progress.
As we have seen in the parsing introduction, grammar syntax can be defined formally using formats like BNF.
There is a formal format for defining HTML — DTD (Document Type Definition) — but it is not a context free grammar.
This appears strange at first sight; HTML is rather close to XML. There are lots of available XML parsers. There is an XML variation of HTML — XHTML — so what’s the big difference?
The difference is that HTML approach is more “forgiving”, it lets you omit certain tags which are added implicitly, sometimes omit the start or end of tags etc. On the whole it’s a “soft” syntax, as opposed to XML’s stiff and demanding syntax.
Apparently this seemingly small difference makes a world of a difference. On one hand this is the main reason why HTML is so popular – it forgives your mistakes and makes life easy for the web author. On the other hand, it makes it difficult to write a formal grammar. So to summarize – HTML cannot be parsed easily, not by conventional parsers since its grammar is not a context free grammar, and not by XML parsers.
HTML definition is in a DTD format. This format is used to define languages of the SGMLfamily. The format contains definitions for all allowed elements, their attributes and hierarchy. As we saw earlier, the HTML DTD doesn’t form a context free grammar.
The root of the tree is the “Document” object.
Like HTML, DOM is specified by the W3C organization. See www.w3.org/DOM/DOMTR. It is a generic specification for manipulating documents. A specific module describes HTML specific elements. The HTML definitions can be found here: www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.html.
When I say the tree contains DOM nodes, I mean the tree is constructed of elements that implement one of the DOM interfaces. Browsers use concrete implementations that have other attributes used by the browser internally.
As we saw in the previous sections, HTML cannot be parsed using the regular top down or bottom up parsers.
can add extra tokens, so the parsing process actually modifies the input.
Unable to use the regular parsing techniques, browsers create custom parsers for parsing HTML.
The parsing algorithm is described in detail by the HTML5 specification. The algorithm consists of two stages – tokenization and tree construction.
Tokenization is the lexical analysis, parsing the input into tokens. Among HTML tokens are start tags, end tags, attribute names and attribute values.
The tokenizer recognizes the token, gives it to the tree constructor, and consumes the next character for recognizing the next token, and so on until the end of the input.
The algorithm’s output is an HTML token. The algorithm is expressed as a state machine. Each state consumes one or more characters of the input stream and updates the next state according to those characters. The decision is influenced by the current tokenization state and by the tree construction state. This means the same consumed character will yield different results for the correct next state, depending on the current state. The algorithm is too complex to describe fully, so let’s see a simple example that will help us understand the principle.
input will be treated like the previous case.
When the parser is created the Document object is created. During the tree construction stage the DOM tree with the Document in its root will be modified and elements will be added to it. Each node emitted by the tokenizer will be processed by the tree constructor. For each token the specification defines which DOM element is relevant to it and will be created for this token. Except of adding the element to the DOM tree it is also added to a stack of open elements. This stack is used to correct nesting mismatches and unclosed tags. The algorithm is also described as a state machine. The states are called “insertion modes”.
The input to the tree construction stage is a sequence of tokens from the tokenization stage The first mode is the “initial mode”. Receiving the html token will cause a move to the“before html” mode and a reprocessing of the token in that mode. This will cause a creation of the HTMLHtmlElement element and it will be appended to the root Document object.
The state will be changed to “before head”. We receive the “body” token. An HTMLHeadElement will be created implicitly although we don’t have a “head” token and it will be added to the tree.
We now move to the “in head” mode and then to “after head”. The body token is reprocessed, an HTMLBodyElement is created and inserted and the mode is transferred to“in body”.
The character tokens of the “Hello world” string are now received. The first one will cause creation and insertion of a “Text” node and the other characters will be appended to that node.
The receiving of the body end token will cause a transfer to “after body” mode. We will now receive the html end tag which will move us to “after after body” mode. Receiving the end of file token will end the parsing.
At this stage the browser will mark the document as interactive and start parsing scripts that are in “deferred” mode – those who should be executed after the document is parsed. The document state will be then set to “complete” and a “load” event will be fired.
You never get an “Invalid Syntax” error on an HTML page. Browsers fix any invalid content and go on.
I must have violated about a million rules (“mytag” is not a standard tag, wrong nesting of the “p” and “div” elements and more) but the browser still shows it correctly and doesn’t complain. So a lot of the parser code is fixing the HTML author mistakes.
The error handling is quite consistent in browsers but amazingly enough it’s not part of HTML current specification. Like bookmarking and back/forward buttons it’s just something that developed in browsers over the years. There are known invalid HTML constructs that repeat themselves in many sites and the browsers try to fix them in a conformant way with other browsers.
The parser parses tokenized input into the document, building up the document tree. If the document is well-formed, parsing it is straightforward.
Unfortunately, we have to handle many HTML documents that are not well-formed, so the parser has to be tolerant about errors.
The element being added is explicitly forbidden inside some outer tag. In this case we should close all tags up to the one, which forbids the element, and add it afterwards.
We are not allowed to add the element directly. It could be that the person writing the document forgot some tag in between (or that the tag in between is optional). This could be the case with the following tags: HTML HEAD BODY TBODY TR TD LI (did I forget any?).
We want to add a block element inside to an inline element. Close all inline elements up to the next higher block element.
If this doesn’t help, close elements until we are allowed to add the element or ignore the tag.
Some sites use </br> instead of <br>. In order to be compatible with IE and Firefox, Webkit treats this like <br>.
Note – the error handling is internal – it won’t be presented to the user.
A stray table is a table inside another table contents but not inside a table cell.
Webkit uses a stack for the current element contents – it will pop the inner table out of the outer table stack. The tables will now be siblings.
In case the user puts a form inside another form, the second form is ignored.
The comment speaks for itself.
www.liceo.edu.mx is an example of a site that achieves a level of nesting of about 1500 tags, all from a bunch of <b>s. We will only allow at most 20 nested tags of the same type before just ignoring them all together.
Again – the comment speaks for itself.
Support for really broken html. We never close the body tag, since some stupid web pages close it before the actual end of the doc. Let’s rely on the end() call to close things.
So web authors beware – unless you want to appear as an example in a Webkit error tolerance code snippet – write well formed HTML.
Remember the parsing concepts in the introduction? Well, unlike HTML, CSS is a context free grammar and can be parsed using the types of parsers described in the introduction. In fact the CSS specification defines CSS lexical and syntax grammar.
The syntax grammar is described in BNF.
This means a ruleset is a selector or optionally number of selectors separated by a coma and spaces (S stands for white space). A ruleset contains curly braces and inside them a declaration or optionally a number of declarations separated by a semicolon. “declaration” and “selector” will be defined in the following BNF definitions.
Webkit uses Flex and Bison parser generators to create parsers automatically from the CSS grammar files. As you recall from the parser introduction, Bison creates a bottom up shift-reduce parser. Firefox uses a top down parser written manually. In both cases each CSS file is parsed into a StyleSheet object, each object contains CSS rules. The CSS rule objects contain selector and declaration objects and other object corresponding to CSS grammar.
The model of the web is synchronous. Authors expect scripts to be parsed and executed immediately when the parser reaches a <script> tag. The parsing of the document halts until the script was executed. If the script is external then the resource must be first fetched from the network – this is also done synchronously, the parsing halts until the resource is fetched. This was the model for many years and is also specified in HTML 4 and 5 specifications. Authors could mark the script as “defer” and thus it will not halt the document parsing and will execute after it is parsed. HTML5 adds an option to mark the script as asynchronous so it will be parsed and executed by a different thread.
Both Webkit and Firefox do this optimization. While executing scripts, another thread parses the rest of the document and finds out what other resources need to be loaded from the network and loads them. This way resources can be loaded on parallel connections and the overall speed is better. Note – the speculative parser doesn’t modify the DOM tree and leaves that to the main parser, it only parses references to external resources like external scripts, style sheets and images.
Style sheets on the other hand have a different model. Conceptually it seems that since style sheets don’t change the DOM tree, there is no reason to wait for them and stop the document parsing. There is an issue, though, of scripts asking for style information during the document parsing stage. If the style is not loaded and parsed yet, the script will get wrong answers and apparently this caused lots of problems. It seems to be an edge case but is quite common. Firefox blocks all scripts when there is a style sheet that is still being loaded and parsed. Webkit blocks scripts only when they try to access certain style properties that may be effected by unloaded style sheets.
While the DOM tree is being constructed, the browser constructs another tree, the render tree. This tree is of visual elements in the order in which they will be displayed. It is the visual representation of the document. The purpose of this tree is to enable painting the contents in their correct order.
Firefox calls the elements in the render tree “frames”. Webkit uses the term renderer or render object.
A renderer knows how to layout and paint itself and its children.
Each renderer represents a rectangular area usually corresponding to the node’s CSS box, as described by the CSS2 spec. It contains geometric information like width, height and position.
The box type is affected by the “display” style attribute that is relevant for the node (see thestyle computation section). Here is Webkit code for deciding what type of renderer should be created for a DOM node, according to the display attribute.
The element type is also considered, for example form controls and tables have special frames.
method. The renderers points to style objects that contains the non geometric information.
The renderers correspond to the DOM elements, but the relation is not one to one. Non visual DOM elements will not be inserted in the render tree. An example is the “head” element. Also elements whose display attribute was assigned to “none” will not appear in the tree (elements with “hidden” visibility attribute will appear in the tree).
There are DOM elements which correspond to several visual objects. These are usually elements with complex structure that cannot be described by a single rectangle. For example, the “select” element has 3 renderers – one for the display area, one for the drop down list box and one for the button. Also when text is broken into multiple lines because the width is not sufficient for one line, the new lines will be added as extra renderers.
Another example of several renderers is broken HTML. According to CSS spec an inline element must contain either only block element or only inline elements. In case of mixed content, anonymous block renderers will be created to wrap the inline elements.
Some render objects correspond to a DOM node but not in the same place in the tree. Floats and absolutely positioned elements are out of flow, placed in a different place in the tree, and mapped to the real frame. A placeholder frame is where they should have been.
Figure : The render tree and the corresponding DOM tree (3.1). The “Viewport” is the initial containing block. In Webkit it will be the “RenderView” object.
and the constructor resolves style (seestyle computation) and creates a frame.
In Webkit the process of resolving the style and creating a renderer is called “attachment”. Every DOM node has an “attach” method. Attachment is synchronous, node insertion to the DOM tree calls the new node “attach” method.
. This is the render object that the document points to. The rest of the tree is constructed as a DOM nodes insertion.
See the CSS2 spec on the processing model.
Building the render tree requires calculating the visual properties of each render object. This is done by calculating the style properties of each element.
The style includes style sheets of various origins, inline style elements and visual properties in the HTML (like the “bgcolor” property).The later is translated to matching CSS style properties.
The origins of style sheets are the browser’s default style sheets, the style sheets provided by the page author and user style sheets – these are style sheets provided by the browser user (browsers let you define your favorite style. In Firefox, for instance, this is done by placing a style sheet in the “Firefox Profile” folder).
Style data is a very large construct, holding the numerous style properties, this can cause memory problems.
element. You choose a certain path up the tree for checking. You may need to traverse the node tree up just to find out there are only two divs and the rule does not apply. You then need to try other paths in the tree.
Applying the rules involves quite complex cascade rules that define the hierarchy of the rules.
Firefox has two extra trees for easier style computation – the rule tree and style context tree. Webkit also has style objects but they are not stored in a tree like the style context tree, only the DOM node points to its relevant style.
The style contexts contain end values. The values are computed by applying all the matching rules in the correct order and performing manipulations that transform them from logical to concrete values. For example – if the logical value is percentage of the screen it will be calculated and transformed to absolute units. The rule tree idea is really clever. It enables sharing these values between nodes to avoid computing them again. This also saves space.
All the matched rules are stored in a tree. The bottom nodes in a path have higher priority. The tree contains all the paths for rule matches that were found. Storing the rules is done lazily. The tree isn’t calculated at the beginning for every node, but whenever a node style needs to be computed the computed paths are added to the tree.
Suppose we need to match rules for another element in the content tree, and find out the matched rules (in the correct order) are B – E – I. We already have this path in the tree because we already computed path A – B – E – I – L. We will now have less work to do.
Let’s see how the tree saves us work.
The style contexts are divided into structs. Those structs contain style information for a certain category like border or color. All the properties in a struct are either inherited or non inherited. Inherited properties are properties that unless defined by the element, are inherited from its parent. Non inherited properties (called “reset” properties) use default values if not defined.
The tree helps us by caching entire structs (containing the computed end values) in the tree. The idea is that if the bottom node didn’t supply a definition for a struct, a cached struct in an upper node can be used.
When computing the style context for a certain element, we first compute a path in the rule tree or use an existing one. We then begin to apply the rules in the path to fill the structs in our new style context. We start at the bottom node of the path – the one with the highest precedence (usually the most specific selector) and traverse the tree up until our struct is full. If there is no specification for the struct in that rule node, then we can greatly optimize – we go up the tree until we find a node that specifies it fully and simply point to it – that’s the best optimization – the entire struct is shared. This saves computation of end values and memory.
If we find partial definitions we go up the tree until the struct is filled.
If we didn’t find any definitions for our struct, then in case the struct is an “inherited” type – we point to the struct of our parent in the context tree, in this case we also succeeded in sharing structs. If its a reset struct then default values will be used.
If the most specific node does add values then we need to do some extra calculations for transforming it to actual values. We then cache the result in the tree node so it can be used by children.
In case an element has a sibling or a brother that points to the same tree node then theentire style context can be shared between them.
To simplify things let’s say we need to fill out only two structs – the color struct and the margin struct. The color struct contains only one member – the color The margin struct contains the four sides.
Suppose we parse the HTML and get to the second <div> tag. We need to create a style context for this node and fill its style structs.
We will match the rules and discover that the matching rules for the <div> are 1 ,2 and 6. This means there is already an existing path in the tree that our element can use and we just need to add another node to it for rule 6 (node F in the rule tree).
We will create a style context and put it in the context tree. The new style context will point to node F in the rule tree.
We now need to fill the style structs. We will begin by filling out the margin struct. Since the last rule node(F) doesn’t add to the margin struct, we can go up the tree until we find a cached struct computed in a previous node insertion and use it. We will find it on node B, which is the uppermost node that specified margin rules.
We do have a definition for the color struct, so we can’t use a cached struct. Since color has one attribute we don’t need to go up the tree to fill other attributes. We will compute the end value (convert string to RGB etc) and cache the computed struct on this node.
The work on the second <span> element is even easier. We will match the rules and come to the conclusion that it points to rule G, like the previous span. Since we have siblings that point to the same node, we can share the entire style context and just point to the context of the previous span.
For structs that contain rules that are inherited from the parent, caching is done on the context tree (the color property is actually inherited, but Firefox treats it as reset and caches it on the rule tree).
Then the paragraph element, which is a child of the div in the context tree, could have shared the same font struct as his parent. This is if no font rules where specified for the paragraph.
In Webkit, who does not have a rule tree, the matched declarations are traversed 4 times. First non important high priority properties (properties that should be applied first because others depend on them – like display) are applied, than high priority important, then normal priority non important, then normal priority important rules. This means that properties that appear multiple times will be resolved according to the correct cascade order. The last wins.
So to summarize – sharing the style objects (entirely or some of the structs inside them) solves issues 1 and 3. Firefox rule tree also helps in applying the properties in the correct order.
CSS rules, either in external style sheets or in style elements.
The last two are easily matched to the element since he owns the style attributes and HTML attributes can be mapped using the element as the key.
As noted previously in issue #2, the CSS rule matching can be trickier. To solve the difficulty, the rules are manipulated for easier access.
After parsing the style sheet, the rules are added to one of several hash maps, according to the selector. There are maps by id, by class name, by tag name and a general map for anything that doesn’t fit into those categories. If the selector is an id, the rule will be added to the id map, if it’s a class it will be added to the class map etc.
This manipulation makes it much easier to match rules. There is no need to look in every declaration – we can extract the relevant rules for an element from the maps. This optimization eliminates 95+% of the rules, so that they need not even be considered during the matching process(4.1).
The first rule will be inserted into the class map. The second into the id map and the third into the tag map.
We will first try to find rules for the p element. The class map will contain an “error” key under which the rule for “p.error” is found. The div element will have relevant rules in the id map (the key is the id) and the tag map. So the only work left is finding out which of the rules that were extracted by the keys really match.
it will still be extracted from the tag map, because the key is the rightmost selector, but it would not match our div element, who does not have a table ancestor.
Both Webkit and Firefox do this manipulation.
The style object has properties corresponding to every visual attribute (all css attributes but more generic). If the property is not defined by any of the matched rules – then some properties can be inherited by the parent element style object. Other properties have default values.
The problem begins when there is more than one definition – here comes the cascade order to solve the issue.
The browser declarations are least important and the user overrides the author only if the declaration was marked as important. Declarations with the same order will be sorted byspecificity and then the order they are specified. The HTML visual attributes are translated to matching CSS declarations . They are treated as author rules with low priority.
The number base you need to use is defined by the highest count you have in one of the categories.
For example, if a=14 you can use hexadecimal base. In the unlikely case where a=17 you will need a 17 digits number base. The later situation can happen with a selector like this: html body div div p … (17 tags in your selector.. not very likely).
Webkit uses a flag that marks if all top level style sheets (including @imports) have been loaded. If the style is not fully loaded when attaching – place holders are used and it is marked in the document, and they will be recalculated once the style sheets were loaded.
When the renderer is created and added to the tree, it does not have a position and size. Calculating these values is called layout or reflow.
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 – for example, HTML tables may require more than one pass (3.5).
The coordinate system is relative to the root frame. Top and left coordinates are used.
element of the HTML document. Layout continues recursively through some or all of the frame hierarchy, computing geometric information for each renderer that requires it.
The position of the root renderer is 0,0 and its dimensions are the viewport – the visible part of the browser window.
All renderers have a “layout” or “reflow” method, each renderer invokes the layout method of its children that need layout.
In order not to do a full layout for every small change, browsers use a “dirty bit” system. A renderer that is changed or added marks itself and its children as “dirty” – needing layout.
There are two flags – “dirty” and “children are dirty”. Children are dirty means that although the renderer itself may be ok, it has at least one child that needs a layout.
A global style change that affects all renderers, like a font size change.
Layout can be incremental, only the dirty renderers will be layed out (this can cause some damage which will require extra layouts).
Incremental layout is triggered (asynchronously) when renderers are dirty. For example when new renderers are appended to the render tree after extra content came from the network and was added to the DOM tree.
Figure : Incremental layout – only dirty renderers and their children are layed out (3.6).
Incremental layout is done asynchronously. Firefox queues “reflow commands” for incremental layouts and a scheduler triggers batch execution of these commands. Webkit also has a timer that executes an incremental layout – the tree is traversed and “dirty” renderers are layout out.
Scripts asking for style information, like “offsetHeight” can trigger incremental layout synchronously.
Global layout will usually be triggered synchronously.
Sometimes layout is triggered as a callback after an initial layout because some attributes , like the scrolling position changed.
When a layout is triggered by a “resize” or a change in the renderer position(and not size), the renders sizes are taken from a cache and not recalculated..
In some cases – only a sub tree is modified and layout does not start from the root. This can happen in cases where the change is local and does not affect its surroundings – like text inserted into text fields (otherwise every keystroke would have triggered a layout starting from the root).
Parent renderer determines its own width.
Place the child renderer (sets its x and y).
Calls child layout if needed(they are dirty or we are in a global layout or some other reason) – this calculates the child’s height.
Parent uses children accumulative heights and the heights of the margins and paddings to set it’s own height – this will be used by the parent renderer’s parent.
Sets its dirty bit to false.
Firefox uses a “state” object(nsHTMLReflowState) as a parameter to layout (termed “reflow”). Among others the state includes the parents width.
The output of the Firefox layout is a “metrics” object(nsHTMLReflowMetrics). It will contain the renderer computed height.
The renderer’s width is calculated using the container block’s width, the renderer’s style “width” property, the margins and borders.
clientWidth and clientHeight represent the interior of an object excluding border and scrollbar.
The elements width is the “width” style attribute. It will be calculated as an absolute value by computing the percentage of the container width.
The horizontal borders and paddings are now added.
So far this was the calculation of the “preferred width”. Now the minimum and maximum widths will be calculated.
If the preferred width is higher then the maximum width – the maximum width is used. If it is lower then the minimum width (the smallest unbreakable unit) then the minimum width is used.
The values are cached, in case a layout is needed but the width does not change.
When a renderer in the middle of layout decides it needs to break. It stops and propagates to its parent it needs to be broken. The parent will create the extra renderers and calls layout on them.
In the painting stage, the render tree is traversed and the renderers “paint” method is called to display their content on the screen. Painting uses the UI infrastructure component.
Like layout, painting can also be global – the entire tree is painted – or incremental. In incremental painting, some of the renderers change in a way that does not affect the entire tree. The changed renderer invalidates it’s rectangle on the screen. This causes the OS to see it as a “dirty region” and generate a “paint” event. The OS does it cleverly and coalesces several regions into one. In Chrome it is more complicated because the renderer is in a different process then the main process. Chrome simulates the OS behavior to some extent. The presentation listens to these events and delegates the message to the render root. The tree is traversed until the relevant renderer is reached. It will repaint itself (and usually its children).
Firefox goes over the render tree and builds a display list for the painted rectangular. It contains the renderers relevant for the rectangular, in the right painting order (backgrounds of the renderers, then borders etc). That way the tree needs to be traversed only once for a repaint instead of several times – painting all backgrounds, then all images , then all borders etc.
Firefox optimizes the process by not adding elements that will be hidden, like elements completely beneath other opaque elements.
Before repainting, webkit saves the old rectangle as a bitmap. It then paints only the delta between the new and old rectangles.
The browsers try to do the minimal possible actions in response to a change. So changes to an elements color will cause only repaint of the element. Changes to the element position will cause layout and repaint of the element, its children and possibly siblings. Adding a DOM node will cause layout and repaint of the node. Major changes, like increasing font size of the “html” element, will cause invalidation of caches, relayout and repaint of the entire tree.
The rendering engine is single threaded. Almost everything, except network operations, happens in a single thread. In Firefox and safari this is the main thread of the browser. In chrome it’s the tab process main thread.
Network operations can be performed by several parallel threads. The number of parallel connections is limited (usually 2 – 6 connections. Firefox 3, for example, uses 6).
According to CSS2 specification, the term canvas describes “the space where the formatting structure is rendered.” – where the browser paints the content. The canvas is infinite for each dimension of the space but browsers choose an initial width based on the dimensions of the viewport.
According to www.w3.org/TR/CSS2/zindex.html, the canvas is transparent if contained within another, and given a browser defined color if it is not.
Each box has a content area (e.g., text, an image, etc.) and optional surrounding padding, border, and margin areas.
Each node generates 0..n such boxes.
The default is inline but the browser style sheet set other defaults. For example – the default display for “div” element is block.
The positioning scheme is set by the “position” property and the “float” attribute.
In static positioning no position is defined and the default positioning is used. In the other schemes, the author specifies the position – top,bottom,left,right.
Block box: forms a block – have their own rectangle on the browser window.
Inline box: does not have its own block, but is inside a containing block.
Blocks are formatted vertically one after the other. Inlines are formatted horizontally.
Inline boxes are put inside lines or “line boxes”. The lines are at least as tall as the tallest box but can be taller, when the boxes are aligned “baseline” – meaning the bottom part of an element is aligned at a point of another box other then the bottom. In case the container width is not enough, the inlines will be put in several lines. This is usually what happens in a paragraph.
Relative positioning – positioned like usual and then moved by the required delta.
The layout is defined exactly regardless of the normal flow. The element does not participate in the normal flow. The dimensions are relative to the container. In fixed – the container is the view port.
Note – the fixed box will not move even when the document is scrolled!
It is specified by the z-index CSS property. It represents the 3rd dimension of the box, its position along the “z axis”.
The boxes are divided to stacks (called stacking contexts). In each stack the back elements will be painted first and the forward elements on top, closer to the user. In case of overlap the foremost element will hide the former element.
The stacks are ordered according to the z-index property. Boxes with “z-index” property form a local stack. The viewport has the outer stack.
Although the red div precedes the green one in the markup, and would have been painted before in the regular flow, the z-index property is higher, so it is more forward in the stack held by the root box.