Preface
This is an introductory article that gives an overview of the internal operations of WebKit and Gecko and is the result of extensive research by Israeli developers Tally Garchill. Over the past few years, she has looked at all publicly published data about the internal mechanism of the browser (see Resources) and spent a lot of time poring over the source code of the Web browser. She wrote:
In the era of IE occupying 90% market share, we can do nothing except to regard the browser as a "black box". But now that open source browsers have more than half the market, it's time to uncover the mysteries of the Web browser. Well, there are only a few millions of lines of C + + code ...
Tully published her research on her website, but we think it's worth getting more people to understand, so we'll rearrange and publish it here.
As a web developer, learning the inner workings of a browser will help you make smarter decisions and understand the reasons for the best development practices . Although this is a fairly lengthy document, we recommend that you take some time to read it carefully, and that you will feel the cost of it after reading it. Paul Eli (Paul Irish), Chrome Browser developer Affairs
Brief introduction
Web browsers are likely to be the most widely used software. In this introductory article, I'll explain how they work behind the scenes. We'll see what happens when you enter google.com in the address bar until you see the Google homepage on the browser screen.
The model of the network is synchronized. The Web page author wants the parser to parse and execute the script immediately when it encounters a tag. The resolution of the document will stop until the script finishes executing. If the script is external, the parsing process stops until the resource is completed from the network synchronization and then continues. This model has been in use for many years and is also specified in the HTML4 and HTML5 specifications. The author can also mark the script as "defer" so that it does not stop parsing the document, but does not execute until the parse is finished. HTML5 adds an option to mark the script as asynchronous so that it can be parsed and executed by another thread.
The browser we want to discuss
Graph: Rendering the tree and its corresponding DOM tree (3.1). The initial container block is "viewport" and the "Renderview" object is in WebKit.
Building the process of rendering a tree
In Firefox, the system registers the presentation layer for a DOM update as a listener. The presentation layer delegates the framework creation work to frameconstructor, which resolves the style (see style calculations) and creates the framework.
In WebKit, the process of parsing a style and creating a renderer is called "Attach." Each DOM node has a "attach" method. The addition is synchronous, and inserting a node into the DOM tree requires invoking the new node "Attach" method.
Processing the HTML and body tags will build the render root node. This root node renders the object corresponding to the container block as described in the CSS specification, which is the top-level block, which contains all the other blocks. Its size is the viewport, that is, the size of the browser window display area. Firefox is called Viewportframe, and WebKit called Renderview. This is the rendering object that the document points to. The remainder of the rendering tree is constructed in the form of a DOM tree node insert.
See the CSS2 specification for the processing model.
Style calculation
When you build a rendering tree, you need to compute the visual properties of each rendering object. This is done by calculating the style attributes for each element.
Styles include style sheets from various sources, inline style elements, and visual properties in HTML (such as the "bgcolor" attribute). The latter will be transformed to match the CSS style attributes.
The source of the style sheet includes the default style sheet for the browser, the style sheet provided by the author of the Web page, and the user style sheet provided by the browser user (the browser allows you to define the style you like.) In Firefox, for example, users can put their favorite style sheets under the Firefox profile folder.
The following are the difficulties with style calculations:
Style data is an oversized structure that stores countless style attributes, which can cause memory problems.
If you do not optimize, finding a matching rule for each element can cause performance problems. It's a huge project to go through the entire list of rules for each element to find a matching rule. The selector has a very complex structure, which causes a matching process to appear to be correct at first, but in the end it turns out to be futile and must try another matching path.
For example, the following combination selector:
Div Div div div{...}
This means that the rule applies to a descendant of a 3 div element.
。 If you want to check whether the rule applies to a specified
element, you should select an upward path on the tree to check. You may need to traverse the node tree up, only to find two div, and the rule does not apply. Then, you must try another path in the tree.
Applying rules involves fairly complex cascading rules that define the levels of these rules.
Let's take a look at how browsers handle these issues:
Share style data
The WebKit node refers to the style object (RenderStyle). These objects can, in some cases, be shared by different nodes. These nodes are sibling relationships, and:
These elements must be in the same mouse state (for example, one is not allowed to be a ": hover" state, and the other is not)
No element has an ID
Tag names should match
Class properties should match
The collection of mapped attributes must be exactly the same
Link State must match
Focus State must match
Any element should not be affected by the property selector, where the term "impact" refers to any selector match that uses the property selector anywhere in the selector
Cannot have any inline style properties in an element
You cannot use any sibling selectors. WebCore when any sibling selector is encountered, only one global switch is raised and the entire document's style share is deactivated (if present). This includes the + selector as well as: First-child and: Last-child and other selectors.
Firefox Rule Tree
To simplify style computing, Firefox also uses two other kinds of trees: the rule tree and the style context tree. WebKit also have style objects, but they are not in a tree structure such as a style context tree, but only by the associated styles of the DOM nodes pointing to such objects.
Figure: Firefox Style context tree (2.2)
The style context contains the end value. To calculate these values, all matching rules should be applied in the correct order and translated from logical values to specific values. For example, if the logical value is a percentage of the screen size, you need to convert it to an absolute unit. The rule tree idea is really ingenious, it allows the nodes to share these values, to avoid duplication of computation, but also to save space.
All matching rules are stored in the tree. The underlying node in the path has a higher priority. The rule tree contains the paths that all known rules match. The storage of the rule is deferred. The rule tree does not compute for all nodes at the beginning, but instead adds the calculated path to the rule tree only when a node style needs to be evaluated.
The idea is to treat the rule tree path as a word in the dictionary. If we have calculated the following rule tree:
Suppose we need to match the rules for another element in the content tree, and find the matching path to be b-e-I (in this order). Since we have already calculated the path a-b-e-i-L in the tree, we already have this path, which reduces the amount of work that is needed now.
Let's see how the rule tree helps us reduce our work.
Structure Division
Style contexts can be split into multiple structures. These structures contain style information for a particular category, such as border or color. Properties in structs are either inherited or not inherited. Inherited properties are inherited from their parent if they are not defined by the element. Non-inherited properties (also known as "reset" properties) Use default values if they are not defined.
The rule tree helps us by caching the entire structure (including computed end values). This idea assumes that the underlying node does not provide a definition of the structure, you can use the caching structure in the upper node.
To compute a style context using the rule tree
When evaluating the style context of a particular element, we first compute the corresponding path in the rule tree, or use an existing path. We then apply the rule along this path to populate the structure in the new style context. We start with the underlying node (usually the most special selector) with the highest priority in the path, and traverse the rule tree up until the structure is populated. If the rule node does not have any specification for this structure, then we can achieve better optimization: Find the node with higher path, and then specify the complete specification and point to the relevant node. This is the best way to optimize, because the entire structure can be shared. This reduces the amount of calculation for the end value and saves memory.
If we find a partial definition, we traverse the rule tree up until the structure is populated.
If we cannot find any definition of the struct, then if the struct is an "inherited" type, we will point to the structure of the parent in the context tree , so that we can share the structure. If it is a struct of type reset, the default value is used.
If the most specific node does add a value, then we need to do some additional calculations to convert the values to actual values. We then cache the results in a tree node for use by the descendant.
If an element points to the same tree node as its sibling elements, they can share the entire style context .
Let's take a look at an example, assuming we have the following HTML code:
This is Abig errorthis are also Avery Big Errorerror
Another error
There are also the following rules:
Div{margin:5px;color:black}
. err{color:red}
. big{margin-top:3px}
Div span{margin-bottom:4px}
#div1 {Color:blue}
#div2 {Color:green}
For simplicity, we only need to populate two structures: color structure and margin structure. The color structure contains only one member (that is, "color"), while the margin structure contains four edges.
The resulting rule tree is shown in the following illustration (the node is labeled as "node Name: point to Rule Sequence number"):
Figure: Rule Tree
The context tree is shown in the following illustration (section name: point to the rule node):
Picture: Context Tree
Let's say we encountered a second one when parsing HTML
tag, we need to create a style context for this node and populate its style structure.
After a rule match, we found that the
The matching rules are 1th, 2, and 6. This means that there is already a path in the rule tree for our elements to use, and we just need to add one more node to match the 6th rule (the F node in the rule tree).
We'll create the style context and put it in the context tree. The new style context will point to the F node in the rule tree.
Now we need to populate the style structure. The first thing to fill is the margin structure. Since the last Rule node (F) is not added to the margin structure, we need to go back to the rule tree until we find the cached structure that was computed in the previous node insert, and then use the structure. We will find the structure on the topmost node (that is, the B-node) of the specified margin rule.
We already have the definition of a color structure, so we can't use the cached structure. Since color has an attribute, we do not need to trace the rule tree to populate the other attributes. We will compute the end value (convert the string to RGB, etc.) and cache the computed structure on this node.
The second element is simpler to handle. We'll match the rule and finally find it pointing to rule G like the previous span. As we find a sibling to the same node, we can share the entire style context, just pointing to the previous span.
For structures that contain rules inherited from the parent, the cache is made in the context tree (in fact, the color property is inherited, but Firefox treats it as a reset property and is cached on the rule tree).
For example, if we add a font rule to a paragraph:
P{font-family:verdana;font Size:10px;font-weight:bold}
The paragraph element, then, as a descendant of a div in the context tree, shares the same font structure as its parent (provided that the paragraph does not specify a font rule).
There is no rule tree in the WebKit, so the matching declaration is traversed 4 times. First, the attributes that are not important high priority are applied (attributes that should be applied first, such as display) as a basis for other properties, followed by high-priority important rules, then ordinary precedence rules and, finally, general priority rules. This means that the properties that appear multiple times are parsed according to the correct cascading order. Finally come into effect.
As a general rule, shared style objects (the entire object or part of the object) can be used to resolve issues 1 and 3. The Firefox rule tree also helps to apply attributes in the correct order.
Process rules to simplify matching
Style rules have some sources:
CSS rules in an external style sheet or STYLE element
P{color:blue}
Inline style properties and similar content
HTML Visual properties (mapped to related style rules)
The latter two are easy to match with the element because the element has style attributes and the HTML attribute can be mapped using the element as the key value.
As we mentioned earlier in question 2nd, CSS rule matching can be tricky. To address this dilemma, you can do some processing of CSS rules for access.
When the stylesheet resolves, the system adds CSS rules to a hash table based on the selector. The selectors for these hash tables are different, including IDs, class names, tag names, and so on, and a generic hash table for rules that are not part of the above categories. If the selector is an ID, the rule is added to the ID table, and if the selector is a class, the rule is added to the class table, and so on.
This process can greatly simplify rule matching. We don't need to look at every statement, just extract the relevant rules for the elements from the hash table. This optimization method eliminates more than 95% rules, so the rules are not considered at all in the matching process (4.1).
We take the following style rule as an example:
P.error{color:red} #messageDiv {height:50px}div{margin:5px}
The first rule inserts the Class table, the second inserts the ID table, and the third inserts the tag table.
For the following HTML code snippet:
An error occurredthis was a message
We will first look for a matching rule for the P element. There is an "error" key in the class table, where you can find the "p.error" rule. div elements have related rules in the ID table (the key is ID) and the tag table. The rest of the work is figuring out which rules to extract from the key are really matched.
For example, if the div's corresponding rule is as follows:
Table div{margin:5px}
This rule will still be extracted from the tag table because the key is the rightmost selector, but this rule does not match our div element because the DIV does not have a table ancestor.
WebKit and Firefox have all done this process.
Apply rules in the correct stacking order
A Style object has properties that correspond to each of the visual property one by one (both CSS properties but more general). If a property is not defined by any matching rule, then some properties can be inherited by the parent element style object. Other properties have default values.
If more than one definition is defined, the problem arises and needs to be resolved in a cascade order.
Style surface Stacking order
A declaration of a style property may appear in more than one style sheet or multiple times in the same style sheet. This means that the order in which rules are applied is extremely important. This is referred to as the "cascade" order. According to the CSS2 specification, the stacking order is (priority is from low to high):
Browser declaration
User General statement
Author's general statement
Author important statement
User Important statement
The browser declaration is the least important, and the user can only replace the page author's statement by marking the declaration as "important." Declarations of the same order are sorted according to specificity and then in the order in which they are specified. HTML visual properties are converted to matching CSS declarations. They are considered to be low priority page author rules.
Specificity
The specificity of the selector is defined by the CSS2 specification as follows:
If the declaration comes from a "style" attribute, rather than a rule with a selector, it is 1, or 0 (= a)
Number of ID attributes in selector (= b)
Number of other attributes and pseudo classes in the selector (= c)
The number of element names and pseudo elements in the selector (= d)
The four digits are connected by a-b-c-d (in a digital system of large numbers), which constitutes specificity.
The system you use depends on the highest count in the category above.
For example, if a=14, you can use hexadecimal. If a=17, then you need to use 17; Of course it is unlikely to happen unless the following selector exists: HTML body div div p ... (17 tags appear in the selector, which is extremely low probability).
Some examples:
*{}/* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */li{}/* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */li:first-line{}/* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */ul li{}/* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */ul ol+li{}/* a=0 b= 0 c=0 d=3 -> specificity = 0,0,0,3 */h1+*[rel=up]{}/* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */ul ol li.red{}/* a= 0 b=0 c=1 d=3-> specificity = 0,0,1,3 */li.red.level{}/* a=0 b=0-c=2 d=1-> specificity = 0,0,2,1/#x34y {} /* a=0 b=1 c=0 d=0-> specificity = 0,1,0,0 */style= ""/* a=1 b=0 c=0-d=0-> specificity = 1,0,0,0 * *
Sorting rules
After matching rules are found, they should be sorted according to their cascading order. WebKit uses a bubble sort for smaller lists, but for larger lists, it uses a merge sort. For the following rules, WebKit implements sorting by replacing the ">" Operator:
Staticbooloperator> (CSSRULEDATA&R1,CSSRULEDATA&R2) {intspec1=r1.selector ()->specificity (); Intspec2=r2.selector ()->specificity (); return (SPEC1==SPEC2): R1.position () >r2.position (): SPEC1>SPEC2;}
Incremental processing
WebKit uses a tag to indicate whether all top-level style sheets, including @imports, have been loaded. If the style has not been fully loaded during the attach process, the placeholder is used, annotated in the document, and then recalculated when the style sheet is loaded.
Layout
The renderer does not contain location and size information when it is created and added to the rendering tree. The process of calculating these values is called layout or rearrangement.