WebKit中CSS处理流程(二)

WebKit中CSS处理流程(二)

2  CSS加载解析流程

    

2.1 解析相关的类图

2.1  CSS样式解析的入口

    CSS样式表解析的入口是从HTML解析到相应的样式元素开始的,如<style><link>元素。

    html的解析流程主要分为词法解析和语法解析,解析过程主要由HTMLDocumentParser::pumpTokenizer函数推动,它会去调用HTMLTokenizer::nextToken函数来进行词法解析,然后将词法解析获取的结果token交给语法解析函数HTMLTreeBuilder::processToken来处理。HTMLTreeBuilder::processToken会根据不同的元素类型(DOCTYPEStartTagEndTagComment)调用不同的处理函数。如果传递进来的token为一个EndTag,则会调用processEndTag函数来处理。该函数会弹出之前压栈的StartTagfinishParsingChildren函数来完成该元素的处理。finishParsingChildrenElement中被定义为一个虚函数,不同类型的元素(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文本。解析过程借助了 lexyyac语法分析工具,Tokenizer.cpp被包含在CSSParser.cpp的最后。解析CSSyacc语法定义在 CSSGrammar.y文件中,该文件包含在CSSGrammar.cpp中。而CSSParser负责相关的回调函数。CSSStyleRule代表一条CSS规则。最后的内容都在m_parsedRuleLists中。

2.3  样式表的解析流程

    CSS数据的解析主要由CSSParser::parseSheet函数来完成,它主要是借助yaccflex自动化的词法分析工具来完成,利用这些工具,只需要写好需要解析的文法定义和回调函数。具体的流程如下:

1. setupParser(const char* prefix, const String& string, const char* suffix)setupParser先分配用于存储CSS文本数据的内存,然后将prefix, string, suffix的内容复制到这个数据缓冲区中。

2. cssyyparse(void* parser),调用由yacc/flex这样的文法解析器生成工具所生成的解析函数,进行CSS的解析。具体来说,yacc/flex根据CSSGrammar.y定义的CSS文法描述文件,生成yacc/flex结构的文法解析器代码。从算法上看,这样的文法解析器是基于表驱动的方式来解析元素和执行相应的操作。简单来说是,按照文法规则生成的状态切换表和当前的输入数据,在不同的解析状态和处理逻辑之间进行跳转,在某些特定的状态下解析出具体的表示CSS信息的数据对象。

3. 词法分析和语法分析这部分代码是自动生成的,但它们不够完整,WebKit还需要自己写一些回调函数才能让整个 CSS模块工作起来。Webkit中实现的这些回调函数在CSSParser中了,显然,刨去生成的代码不说,需要手工完成的CSS解析代码部分就是这个了。CSS的一些解析功能的入口 也在此处,它们会调用lex,parse等生成代码。相对的,生成代码中需要的回调也需要在这里实现。举例来说,回调函数createStyleRule()该函数将在一般性的规则需要被建立的时候调用,该函数主要作用是创建一个CSSStyleRule,并将其添加到已解析的样式对象列表m_parsedRules中去。像这样的函数还有createCharsetRulecreateImportRulecreateMediaRule等等,它们的作用大体上和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函数。



WebKit中CSS处理流程(二)

相关文章:

你感兴趣的文章:

标签云: