转载:
原文链接:https://blog.csdn.net/zhchaoo/java/article/details/9050517
第一章
1 概述
本文主要以CSS加载,解析和匹配计算的完整流程为主线, 描述了Webkit内核中样式处理相关的各个处理模块. 主要包括: 1) CSS的解析流程; 2) 样式表的收集处理过程; 3) RenderStyle的样式匹配的计算流程; 4) 匹配样式的应用。
1.1 类型
mappedElement:一些可以影响CSS ComputedStyle的html元素。举例:HTML的<font>元素。
mappedAttribute:一些可以影响CSS ComputedStyle的html属性。举例:<p align="middle">paragraph</p>,那么属性align="middle"就叫做mappedAttribute。一般每个Element有个CSSComputedStyleDeclaration,实际上还有个隐含的Declaration叫CSSMappedAttributeDeclaration。它的优先级比用户定义的CSS样式要高,但是比网页作者定义的样式要低。
RenderStyle:这就是ComputedStyle在webkit中的表示。
BloomFilter:一种二进制向量数据结构,它具有很好的空间和时间效率,被用来检测一个元素是不是集合中的一个成员;缺点是有一定的错误率,会将不是集合中的成员误认为是集合中的成员,但是不会将是集合中成员误认为不是集合中的成员,所以常用来构造过滤器。
1.2 样式表的级联顺序
一个样式属性可能在几个样式表中出现,或是在一个样式表中出现多次,因此,应用规则的顺序至关重要,这个顺序就是级联顺序。根据css2的规范,级联顺序为(从低到高):
1.浏览器本身的CSS表
2.如果是quirks模式,有浏览器本身的quicks模式CSS表
3.如果是viewSource模式,有相应的CSS表
4.用户在浏览器中定义的CSS表
5.元素中的属性(mapped的attributes)
6.网页头部中通过link连接的外部CSS表或直接在头部style定义的CSS样式
7.在元素中内嵌的style定义的CSS样式。
浏览器默认样式是最不重要的,用户样式只有被标记为important时才会覆盖网页作者定义的样式。具有同等级别的样式将根据specifity以及它们被定义时的顺序进行排序。Html可视化属性将被转换为匹配的css规则,它们被视为最低优先级的作者规则。
1.3 WebKit样式处理概述
WebKit对样式表的处理和为某个元素匹配样式的过程可以用下图概述:
图1.1 样式处理和匹配的流程
第二章
2 CSS加载解析流程
图2.1 解析相关的类图
2.1 CSS样式解析的入口
CSS样式表解析的入口是从HTML解析到相应的样式元素开始的,如<style>和<link>元素。
html的解析流程主要分为词法解析和语法解析,解析过程主要由HTMLDocumentParser::pumpTokenizer函数推动,它会去调用HTMLTokenizer::nextToken函数来进行词法解析,然后将词法解析获取的结果token交给语法解析函数HTMLTreeBuilder::processToken来处理。HTMLTreeBuilder::processToken会根据不同的元素类型(如DOCTYPE,StartTag,EndTag,Comment等)调用不同的处理函数。如果传递进来的token为一个EndTag,则会调用processEndTag函数来处理。该函数会弹出之前压栈的StartTag标签,同时调用finishParsingChildren函数来完成该元素的处理。finishParsingChildren在Element中被定义为一个虚函数,不同类型的元素(HTMLXXXXElement)会对该函数有不同的实现。
2.2 HTML解析中对样式元素的处理
对于<style>元素,HTMLStyleElement::finishParsingChildren函数最终会去调用StyleElement::process函数。该函数会将<style>元素中定义的样式文本保存在sheetText中,然后传递给createSheet函数来创建样式表。该函数会创建一个空样式表m_sheet并调用parseStringAtLine函数解析出<style>中定义的样式文本并保存至m_sheet中。
对于 <link> 元素, HTMLLinkElement会在CSS数据加载完毕后调用HTMLLinkElement::setCSSStyleSheet函数来创建m_sheet并调用parseStringAtLine函数解析。
parseStringAtLine函数会去调用parseSheet函数来解析CSS文本。解析过程借助了 lex和yyac语法分析工具,Tokenizer.cpp被包含在CSSParser.cpp的最后。解析CSS的yacc语法定义在 CSSGrammar.y文件中,该文件包含在CSSGrammar.cpp中。而CSSParser负责相关的回调函数。CSSStyleRule代表一条CSS规则。最后的内容都在m_parsedRuleLists中。
2.3 样式表的解析流程
CSS数据的解析主要由CSSParser::parseSheet函数来完成,它主要是借助yacc与flex自动化的词法分析工具来完成,利用这些工具,只需要写好需要解析的文法定义和回调函数。具体的流程如下:
setupParser(const char* prefix, const String& string, const char* suffix),setupParser先分配用于存储CSS文本数据的内存,然后将prefix, string, suffix的内容复制到这个数据缓冲区中。
cssyyparse(void* parser),调用由yacc/flex这样的文法解析器生成工具所生成的解析函数,进行CSS的解析。具体来说,yacc/flex根据CSSGrammar.y定义的CSS文法描述文件,生成yacc/flex结构的文法解析器代码。从算法上看,这样的文法解析器是基于表驱动的方式来解析元素和执行相应的操作。简单来说是,按照文法规则生成的状态切换表和当前的输入数据,在不同的解析状态和处理逻辑之间进行跳转,在某些特定的状态下解析出具体的表示CSS信息的数据对象。
词法分析和语法分析这部分代码是自动生成的,但它们不够完整,WebKit还需要自己写一些回调函数才能让整个 CSS模块工作起来。Webkit中实现的这些回调函数在CSSParser中了,显然,刨去生成的代码不说,需要手工完成的CSS解析代码部分就是这个了。CSS的一些解析功能的入口 也在此处,它们会调用lex,parse等生成代码。相对的,生成代码中需要的回调也需要在这里实现。举例来说,回调函数createStyleRule()该函数将在一般性的规则需要被建立的时候调用,该函数主要作用是创建一个CSSStyleRule,并将其添加到已解析的样式对象列表m_parsedRules中去。像这样的函数还有createCharsetRule,createImportRule,createMediaRule等等,它们的作用大体上和createStyleRule类似,都是为创建Rule而准备的,只不过是不同类型的Rule。
通过调用CSSStyleSheet的 parseString函数,上面的CSS解析过程将启动,解析完一遍后,所有的Rule都被转化为Webkit 的内部模型对象CSSStyleRule对象,存储在对应的CSSStyleSheet对象的m_children中。但是这个时候的规则是不易于处理的,需要将之转换为RuleSet,提供了一个addRulesFromSheet方法,能将 CSSStyleSheet中的rule转换为RuleSet中的rule,这样所有的纯样式规则都会放存储在对应的集合当中,这种集合的抽象就是 RuleSet。以后就可以基于这些个RuleSet来决定每个页面中的元素的样式了,后面会有介绍。
样式声明在CSSSyleRule中的组织结构如下图所示:
图2.2 样式声明在CSSSyleRule中的组织结构
2.4 样式加载状态对排版和渲染的影响
本章主要分析Document和样式加载状态对排版和渲染的影响。
2.4.1 Document类中的样式加载状态相关
相关变量
1.int m_pendingStylesheets;
该变量主要记录当前正在加载中的样式表的数目,用@import语法加载的样式表不被计数。该变量用来检测什么时候可以attach元素以及什么时候可以安全地执行脚本。
2.bool m_ignorePendingStylesheets;
有时候JS程序需要忽略正在加载(pending)中的样式表而强制立刻进行排版操作。该变量在需要强制排版时设置为true,排版之后会设置为原来的值。
3.enum PendingSheetLayout { NoLayoutWithPendingSheets, DidLayoutWithPendingSheets, IgnoreLayoutWithPendingSheets };
PendingSheetLayout m_pendingSheetLayout;
如果我们忽略正在加载(pending)中的样式表,我们需要一个布尔值来跟踪这种情况以便样式表最终加载完毕时可以执行一次完全的repaint。
该变量的状态转移图如下所示:
图5.1 PendingSheetLayout变量的状态转换图
从状态图可以看出该变量在updateLayoutIgnorePendingStylesheets函数时进行强制排版时会记录进行过强制刷新,在之后的styleSelectorChanged中会检测样式表是否已经加载完毕,如果已经加载完毕则会调用repaint函数进行重绘。然后该变量便不会再起作用。
4.m_pendingStyleRecalcShouldForce
强制重新计算样式信息。
相关函数
1.void Document::addPendingSheet() { m_pendingStylesheets++; }
添加正在加载状态中的样式表的计数。
2.void Document::removePendingSheet()
减少正在加载状态中的样式表计数,如果还有正在加载的样式表,则直接返回,否则调用styleSelectorChanged(RecalcStyleImmediately)函数立即重新计算样式(将会导致页面的重排版)。执行等待样式加载的脚本(如果有),执行滚动操作(如果有)。
3.void Document::updateLayoutIgnorePendingStylesheets()
忽略正在加载的CSS,立即进行排版,调用过程是Document::updateLayoutIgnorePendingStylesheets()->Document::updateLayout()->FrameView::layout(),这样就直接去layout了,定时器都不用起了。这种时机一般有:1.JS执行时需要获取或设置元素的一些属性时就需要走这个流程,如当执行Element::setScrollTop()、Element::scrollWidth()等等类似方法时就会调用到document()->updateLayoutIgnorePendingStylesheets()直接去排版而不管新CSS是否加载完; 2.和焦点相关的处理时,如将Element::focus()或EventHandler::dispatchMouseEvent()等,都会马上更新排版,以得到最新排版后信息,来判断当前元素是否可以成为焦点。
4.bool haveStylesheetsLoaded() const
{
return m_pendingStylesheets <= 0 || m_ignorePendingStylesheets;
}
判断样式表是否加载完毕或者是否需要强制排版。
5.bool Document::shouldScheduleLayout()
{
// This function will only be called when FrameView thinks a layout is needed.
// This enforces a couple extra rules.
//
// (a) Only schedule a layout once the stylesheets are loaded.
// (b) Only schedule layout once we have a body element.
return (haveStylesheetsLoaded() && body())
|| (documentElement() && !documentElement()->hasTagName(htmlTag));
}
判断是否需要排版操作。
2.4.2 样式加载过程对排版的影响
样式表的加载主要有三种方式,分别为:
1.CSS中的@import语法制定的外部样式文件;
2.HTML元素<style>中直接定义的样式;
3.HTML元素<link>中引用的外部样式文件;
其中:
第一种方式不会直接影响加载状态相关变量;但是,在创建样式表之后,解析样式表过程中如果发现@import属性,则会去请求外部的样式文件,同时标记m_loading状态为ture,在样式文件加载完毕后会去递归调用父节点的checkLoaded方法。
第二种方式<style>中直接定义的样式,WebKit在解析到<style>的闭标签之后会调到StyleElement::createSheet()函数来创建和解析样式表,在创建之前调用Document::addPendingSheet()函数增加计数状态,在解析之后会调到StyleElement::sheetLoaded函数,其中首先会检查是否有@import类型的子样式正在加载,如果没有会调用Document::removePendingSheet()函数减少计数状态,如果其中不含有@import引用的外部样式,可以认为<style>定义的样式不影响加载状态的相关变量;
下面主要分析下第三种方式的处理过程。
HTML元素<link>中引用的外部样式文件的处理过程主要在HTMLLinkElement类中,该类中也有和加载状态相关的变量,如下所示。
相关变量
1.enum PendingSheetType { None, NonBlocking, Blocking };
PendingSheetType m_pendingSheetType;
该变量主要用于标记当前<link>定义的外部样式文件的加载是否会阻塞样式计算和排版的执行。
相关函数
1.void HTMLLinkElement::addPendingSheet()
该函数会根据当前样式是否会阻塞样式计算和排版来设置m_pendingSheetType的值,如果非阻塞则设置之后直接返回,否则继续调用Document::addPendingSheet。
2.void HTMLLinkElement::removePendingSheet()
该函数在样式加载完毕之后会去调用,根据m_pendingSheetType的值,如果NonBlocking则调用styleSelectorChanged(RecalcStyleImmediately)函数立即重新计算样式(将会导致页面的重排版),否则调用Document::removePendingSheet()处理。
处理流程
<link>元素定义的外部样式的加载处理主要在HTMLLinkElement::process函数中进行,在该函数中会根据<link>元素中的rel属性来判断该样式是否是阻塞的,rel="alternate stylesheet"则采用非阻塞的方式加载,否则会阻塞样式计算和排版但是在发送加载样式表请求时非阻塞会有较低的优先级。
当样式表加载完毕后HTMLLinkElement::setCSSStyleSheet函数会继续后续处理,包括创建和解析样式表,之后便会去调用HTMLLinkElement::removePendingSheet函数。
第三章
http://trac.webkit.org/changeset/115097
3 样式表的收集和处理
图3.2 样式表的收集等处理相关的类图
3.1 样式表的收集
在m_sheet生成完毕之后,都会调用checkLoaded方法,这个过程会通知拥有m_sheet的节点(即HTMLLinkElement或者HTMLStyleElement)表单读取完成sheetLoaded。之后相关的节点会通知Document移除正在读取的sheet,即removePendingSheet方法。如果所有样式都加载完毕则会在removePendingSheet方法内使用styleSelectorChanged方法,styleSelectorChanged方法又会去调用recalcStyleSelector方法收集生成好的CSSStyleSheet对象(包括之前生成好的)将它们保存在StyleSheetList变量m_styleSheets中,然后会去调用recalcStyle(Force)更新Style,最后会决定是否需要重排版。
相关函数
1.void Document::styleSelectorChanged(StyleSelectorUpdateFlag updateFlag)
在样式加载完毕,忽略加载样式排版,清除页面样式时调用,它会去判断是否需要重绘,调用样式收集函数,样式计算函数和排版函数。
2.void Document::recalcStyleSelector(StyleChange change)
收集之前解析好的HTMLStyleElement和HTMLLinkElement中的样式表,清除之前的CSSStyleSelector。
3.2 样式表的处理
Document::recalcStyle函数会调用CSSStyleSelector::styleForDocument为整个文档生成一个RenderStyle然后将其设置给render树的根节点,然后会去调用它的每一个子节点Element的recalcStyle函数,Element::recalcStyle函数会调用styleForRenderer(最终会调到CSSStyleSelector::styleForElement)来创建一个新的RenderStyle,然后调用setRenderStyle函数将新创建的RenderStyle设置给该节点对应的Render节点。Element::recalcStyle还会继续调用它的每一个子节点的Element的recalcStyle函数,最终会以广度优先遍历的顺序更新render树中每个节点的RenderStyle。
前面提到样式解析过程获取的结构CSSStyleSheet并不适合样式计算,我们需要根据样式规则中选择器的匹配类型将样式声明重新散列到便于处理的RuleSet结构中,这个过程是由CSSStyleSelector的构造函数来完成。
每个Document有一个CSSStyleSelector,通常会在第一次需要使用CSSStyleSelector时创建,在collectActiveStylesheets方法中会重新收集生成好的CSSStyleSheet,之后会清除CSSStyleSelector对象。在CSSStyleSelector构造函数中首先会对Document中的m_pageGroupUserSheets,m_userSheets规则集中的每一条规则调用RuleSet::addRulesFromSheet的方法添加到RuleSet的对象m_userStyle或者m_authorStyle中。然后会将Document中的m_styleSheets规则集中的每一条规则都添加到m_authorStyle中。在添加到RuleSet集合时会根据每条样式规则的选择器组中最右侧一个选择器的匹配类型将规则添加到RuleSet中对应的HashMap或者向量表中。
相关函数
1.void Document::recalcStyle(StyleChange change)
调用CSSStyleSelector::styleForDocument为整个文档生成一个RenderStyle然后将其设置给render树的根节点,然后会去调用它的每一个子节点Element的recalcStyle函数,最终计算出整棵树的RenderStyle。
2.void Element::recalcStyle(StyleChange change)
调用CSSStyleSelector::styleForElement计算每一个元素子节点的样式信息。
3.void RuleSet::addRulesFromSheet(CSSStyleSheet* sheet, const MediaQueryEvaluator& medium, CSSStyleSelector* styleSelector)
该方法主要是根据样式表StyleSheet中的每一个CSSRule的类型调用不同的处理函数进行处理,对普通的style类型,会调用addStyleRule做进一步处理,对@import类型会对其中保存的styleSheet()继续调用addRulesFromSheet方法,对@page类型会调用addPageRule将其添加到向量表m_pageRules中,对@media类型对其中的所有子StyleSheet做如上处理。
4.void RuleSet::addStyleRule(CSSStyleRule* rule)
该方法对CSSStyleRule中的selectorList()的每一个CSSSelector调用addRule方法。
5.void RuleSet::addRule(CSSStyleRule* rule, CSSSelector* sel)
该方法会根据CSSSelector中的匹配类型,如果是id,class,unknownpseudo,tag则将以CSSSelector中的字符串为key,将CSSStyleRule封装成RuleData分别保存到RuleSet的四个HashMap变量中m_idRules,m_classRules,m_shadowPseudoElementRules,m_tagRules;如果是伪类类型则保存到m_linkPseudoClassRules或者m_focusPseudoClassRules变量中。譬如:
#id1{color:red;} -->存放在m_idRules中。
.class1{color:red;} -->存放在m_classRules中。
div .class1{color:red;} -->同上。
p{color:red;} -->在m_tagRules中。
*{color:red;} -->在m_universalRules中。
RuleSet和RuleData的组织结构如下图所示:
图3.2 RuleSet的组织结构
CSSStyleSheet和RuleSet之间的联系如下图所示:
图3.3 CSSStyleSheet和RuleSet之间的联系
注意:
1.一条样式声明以RuleData的形式,根据其选择器组中最右侧的选择器的匹配方式,被散列到RuleSet中的一个hashmap或者向量表中,不会同时被散列到其中两个之中,也即是说,一个声明对应的RuleData不会即存在于m_idRules中又存在于m_tagRules中。
2.以逗号隔开的选择器列表在RuleSet中会为其创建多个RuleData,并分别散列到逗号分隔的每一个选择器组对应的hashmap或者向量表中。
第四章
http://trac.webkit.org/changeset/115097
4 RenderStyle的计算
图4.1 样式计算相关的类图
4.1 样式计算的总体流程
RenderStyle的计算是在CSSStyleSelector::styleForElement()中完成的。
一个页面可以包含着多个样式表,而对于页面上的某一个可绘制的元素来说,由于它所处的文档层次不同,即使是同一个标签都可能具有不同的显示样式。
当文档中的某一个元素需要获取其显示样式时,它最终会调用CSSStyleSelector的接口方法styleForElement()来获取最终的RenderStyle。
一个HTML页面元素的最终样式计算流程如下:
调用CSSStyleSelector::initElement()函数和CSSStyleSelector::initForStyleResolve(),初始化CSSStyleSelector当前的处理节点。CSSStyleSelector::initElement会将m_element和m_styledElement都设置为当前节点。initForStyleResolve会清空样式计算相关的数据结构,如m_style,m_matchedDels,m_pendingImageProperties,m_ruleList等。
如果当前处理节点有父RenderStyle,则当前处理的节点的RenderStyle继承父RenderStyle。设置RenderStyle是否为链接元素等。
调用CSSStyleSelector::ensureDefaultStyleSheetsForElement()设置默认的RenderStyle。如果元素的默认RenderStyle已经设置了,则不重新设置。
然后如果resolveForRootDefault则调用CSSStyleSelector::matchUARules()匹配默认的样式,否则调用CSSStyleSelector::matchAllRules()函数,该函数首先会调用matchUARules()匹配默认样式,然后搜索用户定义的样式、Mapped的属性样式、作者的样式、最后搜索元素内嵌的样式。将匹配的样式按顺序存储在m_matchedDecls中。
对于浏览器默认样式,用户定义的样式(m_userStyle),作者样式(m_authorStyle)的搜索过程,是通过调用CSSStyleSelector::matchRules方法来完成的。具体过程是首先清空m_matchedRules。依次根据元素的ID,元素定义的class,元素的伪类,元素的Tag名字,通用样式依次搜索匹配的样式。每一个搜索步骤是通过CSSStyleSelector::matchRulesForList方法来获取所匹配的CSSStyle集合。将匹配的结果缓存在m_matchedRules中,然后对m_matchedRules中的规则进行排序,并将排好序的属性申明加入到m_matchedDecls中。
5.1. CSSStyleSelector::matchRulesForList的处理流程是从对应的向量表Vector<RuleData>中遍历,如果它的某一个样式对应的选择器组能够匹配当前元素的特征(通过checkSelector方法进行判别),那么将这条规则添加到已匹配的规则列表中(即m_matchedRules中)。
调用CSSStyleSelector::applyMatchedDeclarations 方法,而该方法又会调用applyDeclarations方法,从已匹配的规则列表中提取出一个个CSSMutableStyleDeclaration对象,将CSSMutableStyleDeclaration中符合显示相关的CSSProperty来构建最终的RenderStyle,并返回。提取CSSMutableStyleDeclaration对象的顺序是:提取不重要的浏览器定义的不重要的,提取用户定义的不重要的,提取作者定义的不重要的,提取作者定义的重要的,提取用户定义的重要的,提取浏览器定义的重要的。通过这个提取顺序,实现了CSS的优先级关系。
最后调用CSSStyleSelector::initElement(0)清楚设置的元素信息等,为下一次样式计算做准备。
4.2 样式的匹配
样式匹配过程的主流程是由CSSStyleSelector::mathAllRules函数来完成,它会调用CSSStyleSelector::matchRules函数按照样式表的级联顺序对defaultStyle,m_userRules,m_authorRules三个RuleSet集合中的样式进行匹配以及检查HTML标签中的属性和内联样式;在具体匹配某一个RuleSet是通过调用CSSStyleSelector::matchRulesForList匹配RuleSet中存放样式规则的部分子集合(这里会根据元素的特性,如id,class和是否链接/焦点等来决定匹配RuleSet中的哪一部分子集),在判断一条样式规则是否和当前的元素匹配时是通过CSSStyleSelector::checkSelector函数检查选择器是否和当前元素匹配来完成。
下图是样式匹配的概述图:
图4.2 样式匹配概述图
相关函数
1.void CSSStyleSelector::matchAllRules(MatchResult& result)
该函数按如下顺序检查之前整理好的样式集和mappedAttribute:首先匹配用户定义的样式集m_userStyle,检查HTML标签中的mapped attributes,检查附加的additional mapped declarations(表格和表格单元中的附加属性),匹配作者样式集m_authorStyle,最后检查内联样式属性。样式集中的属性通过matchRules匹配添加,其它属性直接通过addMatchedDeclaration函数添加。
2.void CSSStyleSelector::matchRules(RuleSet* rules, int& firstRuleIndex, int& lastRuleIndex, bool includeEmptyRules)
该函数首先清空m_matchedRules变量为本次匹配做准备。然后按顺序以元素的id,class,shadowPseudoId为key在RuleSet的m_idRules,m_classRules,m_shadowPseudoElementRules变量中搜索对应的RuleData向量表并调用matchRulesForList函数检查向量表中的RuleData是否匹配(如果元素有id,class或者shadowPseudoId);接着同样用matchRulesForList函数处理m_linkPseudoClassRules,m_focusPseudoClassRules,m_tagRules和m_universalRules中的RuleData。这时,本次匹配的规则都在m_matchedRules变量中,对其中的规则排序,最后调用addMatchedDeclaration函数将匹配结果保存在m_matchedDels变量中。
3.void CSSStyleSelector::matchRulesForList(const Vector<RuleData>* rules, int& firstRuleIndex, int& lastRuleIndex, bool includeEmptyRules)
该函数会去进一步检查matchRule初步匹配出来的RuleData向量表中的每一个RuleData是否是该元素的匹配样式,对每一个RuleData首先调用CSSStyleSelector::checkSelector函数检查选择器是否匹配,如果匹配则进一步检查,然后将该RuleData添加到m_matchedRules中。
4.inline bool CSSStyleSelector::checkSelector(const RuleData& ruleData)
该函数会根据CSSSelector的m_relation类型走不同的匹配流程,如对于CSSSelector::Descendant后代类型会从当前元素开始沿着它到根结点的路径查找是否和当前选择器匹配,如果匹配,查询CSSSelector是否是TagHistory中的最后一个,如果是则整个过程匹配成功,如果不是,继续取选择器组中下一个CSSSelector重复上述匹配过程。匹配到根节点而选择器组中还有选择器则认为匹配失败。它的时间复杂度可以认为是O(n)=m+n;其中m是选择器组中选择器的个数,n是当前匹配的节点的深度。
4.2.1 匹配规则的排序
前面介绍了样式表的级联顺序,这里介绍同等级别的声明的排序,WebKit将根据specifity以及它们被定义时的顺序进行排序。Html可视化属性将被转换为匹配的css声明,它们被视为最低优先级的作者规则。
Specifity
Css2规范中定义的选择符specifity如下:
² 如果声明来自style属性,而不是一个选择器的规则,则计1,否则计0(=a)
² 计算选择器中id属性的数量(=b)
² 计算选择器中class及伪类的数量(=c)
² 计算选择器中元素名及伪元素的数量(=d)
连接a-b-c-d四个数量(用一个大基数的计算系统)将得到specifity。这里使用的基数由分类中最高的基数定义。例如,如果a为14,可以使用16进制。不同情况下,a为17时,则需要使用阿拉伯数字17作为基数,这种情况可能在这个选择符时发生html body div div …(选择符中有17个标签,一般不太可能)。
排序规则
WebKit对同级别的声明采用std::sort来排序,排序的标准由compareRules函数定义:
static inline bool compareRules(const RuleData* r1, const RuleData* r2)
{
unsigned specificity1 = r1->specificity();
unsigned specificity2 = r2->specificity();
return (specificity1 == specificity2) ? r1->position() < r2->position() : specificity1 < specificity2;
}
根据matchAllRules函数对级联样式的处理顺序,样式匹配最终保存在m_matchedDecls中的结果是按浏览器默认样式,用户定义的样式,网页作者定义的样式的顺序存放匹配的样式规则,同级样式又是按specifity和position来排序,MatchResult中保存各级样式在m_matchedDecls中的起始和结束为止(-1表示不存在该级样式)。
4.3 样式的比较
Element::recalcStyle函数在调用CSSStyleSelector::styleForElement计算获取了元素的新样式RenderStyle之后会去和元素当前的样式进行比较,比较的结果是以下几种之一:NoChange, NoInherit,Inherit,Detach,Force。针对不同的比较结果会去采取不同的操作,例如可能会去reattach当前节点。
4.4 样式的应用
在完成了样式匹配和排序之后, 就需要将排好序的匹配样式m_matchedDecls应用到RenderStyle中, 这个步骤主要通过applyDeclarations函数来完成。
在应用样式时需要分四次进行,WebKit定义了一部分高优先级的属性, 它们是会被其他样式属性依赖的样式属性,它们的属性ID被定义为小于‘line-height’的属性ID, 这样的属性包括‘line-height’目前有20个,除了‘line-height’主要是字体和缩放。应用顺序如下:
1. 高优先级的非important属性
2. 高优先级的important属性
3. 普通优先级的非important属性
4. 普通优先级的important属性
在具体应用每一条属性时是通过CSSStyleSelector::applyProperty函数来进行。 它根据不同的属性ID对属性值作相应的处理并最终设置到RenderStyle中。
在最后样式应用完毕之后还需要调用CSSStyleSelector::adjustRenderStyle 函数对得到的RenderStyle样式进行一些调整, 主要是按照标准对样式进行修订。例如,在quirk模式下,table标签必须是display:table的样式,如果样式表中制定了其它样式,那么在CSSStyleSelector::adjustRenderStyle函数中就会对它进行调整,将其设置为table样式。