您的位置:首页 > Web前端 > CSS

webkit 源码分析系列--css样式解析

2013-03-20 14:55 357 查看
http://hi.baidu.com/imxiangpeng/item/c30a463161739528b3c0c51d
http://www.cnblogs.com/yc_sunniwell/archive/2010/06/29/1767399.html1、css解析和执行过程
(1).浏览器自带属性解析;

(2).网页上css属性解析,在 此之前有部分浏览器自带属性被执行;

(3).更新css样式:丢弃无效的属性,使执行时取到的值为解析过程中设置的值;

(4).匹配样式 规则;

(5).浏览器自带属性执行;

(6).网页上style之间的css属性执行。

2、解析

css 属性说明: width:20px;

其中,“width”为属性名,在webkit/WebCore/generated /CSSPropertyNmaes.h中有属性名对应的id号;“20px”为属性值,在webkit/WebCore/generated /CSSValueKeywords.h中有属性值对应的id号.当没有找到id号时,返回0作为其id号。解析与执行,都是通过id号来进行相应的操
作。

对于一条属性,在CSSGrammar.cpp中,首先通过static int cssPropertyID(const UChar* propertyName, unsigned length)取属性名ID号,然后通过int cssValueKeywordID(const
CSSParserString& string)取属性值ID号。然后才是解析与执行。

对于每条属性的值,在CSSGrammar.y 中,都会创建至少一个CSSparserValueList对象,此对象的addValue()方法将属性的值传进此对象的 CSSParserValue,将其添加到这个对象的m_value。CSSParserValueList是一个类,CSSParserValue是一
个结构体,其定义如下:

struct CSSParserValue {

int id;

bool isInt;

union {

double fValue;

int iValue;

CSSParserString string;

CSSParserFunction* function;

};

enum {

Operator = 0×100000,

Function = 0×100001,

Q_EMS = 0×100002

};

int unit;

bool isVariable() const;

PassRefPtr createCSSValue();

};

struct CSSParserFunction {

CSSParserString name; //函数名

CSSParserValueList* args; //函数中的值

~CSSParserFunction() { delete args; }

};

当属性有多个值,通过m_valueList->current()来访问当前的 CSSParserValue值,m_valueList->next()访问下一个CSSParserValue值;如果属性值为函数,则 m_valueList->current()>function>args->current()访问当前的
CSSParserValue值,m_valueList->current()>function>args->next() 访问下一CSSParserValue值。CSSParserValue结构中几个重要成员如下:

CSSParserValue结构:

(1)id 为generated/CSSValueKeywords.h中的各种属性值对应的ID号,如果在属性值表中查找不到对应的id号,则其id号被置为0。 比如宽度width:20px ,属性值“20px”的id为0;

(2)fValue存储长度值,如前面的“20px”;

(3)function 是存储函数的相关值,包括函数名。如:clip:rect(10px 20px 30px 5px); 这里会创建两个CSSparserValueList对象,第一个对象用于添加rect函数里面的10px 20px 30px 5px这四个值,第二个对象用于添加“rect函数名”。也就是说如果写成clip:rect(10px
20px rect(1px 2px 3px 4px) 5px);会创建三个CSSparserValueList对象,第一个对象用于添加rect函数里面的10px 20px 3 rect(1px 2px 3px 4px) 5px这四个值,第二个对象用于添加1px 2px 3px 4px 这四个值,第三个对象用于添加“rect函数名”;

当属性有多个值,通过m_valueList->current()来访问当前的 值,m_valueList->next()访问下一个值;如果属性值为函数,则 m_valueList->current()>function>args->current()访问当前的 值,m_valueList->current()>function>args->next()访问下一值;

(4)unit 为长度单位对应的整数值,在CSSPrimitiveValue类里定义:

enum UnitTypes {

CSS_UNKNOWN = 0,

CSS_NUMBER = 1,

CSS_PERCENTAGE = 2,

CSS_EMS = 3,

CSS_EXS = 4,

CSS_PX = 5,

CSS_CM = 6,

CSS_MM = 7,

CSS_IN = 8,

CSS_PT = 9,

CSS_PC = 10,

CSS_DEG = 11,

CSS_RAD = 12,

CSS_GRAD = 13,

CSS_MS = 14,

CSS_S = 15,

CSS_HZ = 16,

};

CSSParserFunction 结构:

(5)name属性值为函数时的函数名;

(6)args 属性值为函数时函数中的各个值列表;

在 CSSGrammar.cpp中设置一条属性的m_valueList的值,在CSSParser::parseValue(int propId, bool important)中,会对m_valueList进行属性对应的处理。

在对属性解析过程中,会有一个valid标记,只要不是规定 的属性值,它就会被置为false,通过它的值来判断是否添加属性值,即是否执行addProperty()。

在每一条属性解析后,只要它合法 (valid为真),即为能够被浏览器识别的有效属性,就会创建一个CSSPrimitiveValue对象 (如:CSSPrimitiveValue::create(rect.release())),然后会对该属性通过addProperty()函数保存
当前的CSSValue到m_parsedProperties[m_numParsedProperties++] ,m_parsedProperties的定义为:“CSSProperty** m_parsedProperties;”。不合法的属性被丢弃,不会执行addProperty()。

浏览器解析网页上的css样式,在之间的 每一个选择器中的每条属性都有一个m_numParsedProperties与之对应,它从0开始,每次加1,当一个选择器中的属性解析完后,会通过 CSSGrammar.cpp即cssyyparse ()调用CSSParser::createStyleRule
()将m_parsedProperties保存到Vector > m_parsedStyleObjects中。

3、 更新css样式

当解析完成后,在Document::updateStyleSelector()调用 Document::recalcStyleSelector ()函数,在recalcStyleSelector ()这个函数里更新CSS样式表,以及创建CSSStyleSelector
对象:m_styleSelector = new CSSStyleSelector(this, userStyleSheet(), m_styleSheets.get(), m_mappedElementSheet.get(), !inCompatMode(), matchAuthorAndUserStyles);

4、 匹配样式规则

主要完成对解析后的值的获取。将其存放到CSSStyleSelector对象的m_matchedDecls, 它的定义为:Vector m_matchedDecls;

5、执行

当匹配完成后,调用 CSSStyleSelector::applyDeclarations ()将m_matchedDecls的值转换成CSSProperty,代码如下:

CSSMutableStyleDeclaration* decl = m_matchedDecls[i];

CSSMutableStyleDeclaration::const_iterator end = decl->end();

for (CSSMutableStyleDeclaration::const_iterator it = decl->begin(); it != end; ++it) {

const CSSProperty& current = *it;

再通过applyProperty(current.id(), current.value())传进属性名id号,和属性值对应的CSSValue。

在CSSStyleSelector::applyProperty(int id, CSSValue *value)函数中:执行一条属性,会传进一个CSSValue对象。判断是否为CSSPrimitiveValue,如果是则将其通过强制转换成 CSSPrimitiveValue对象;如果不是,则不转换,下面直接使用CSSValue。代码如下:

if (value->isPrimitiveValue())

primitiveValue = static_cast<CSSPrimitiveValue*>(value);

在primitiveValue中,有一个私有成 员m_type,它保存长度单位对应的整数值;另外还有一个重要的私有成员m_value ,其定义如下:

union {

int ident;

double num;

StringImpl* string;

Counter* counter;

Rect* rect;

unsigned rgbcolor;

Pair* pair;

DashboardRegion* region;

} m_value;

其中重要数据成员,

(1)ident为generated/CSSValueKeywords.h中的各种属性值对应 的ID号。可以通过primitiveValue->getIdent()获取它的值;

(2)num 存放数字的值,如:100px 20s 30mm等。可以通过primitiveValue-> getDoubleValue()来获取它的值;

(3)string 字符串;可以通过primitiveValue-> getStringValue()来获取它的值;

(4)counter 计数器。可以通过primitiveValue-> getCounterValue()来获取它的值;

(5)rect矩形;可以通过 primitiveValue->getRectValue()来获取它的值;

(6)rgbcolor颜色 。可以通过primitiveValue-> getRGBColorValue()来获取它的值;

在 CSSStyleSelector.cpp中的applyProperty()函数中,会对属性id对应的属性值进行调用,以此为执行的入口。执行属性是 发生在所有属性都被“case 199:”C传递之后,它与属性在网页中被调用的顺序有关。(第一次调用的选择器会被执行两次,开始一次,结束一次,中间调用的选择器只执行一次)。

6、 三种样式的解析

(1)Internal Style Sheet

网页上的之间的css属性,会按每条属性的先后顺序, 通过CSSGrammar.cpp中的“case 199:”依次把属性 id号传进CSSParser.cpp中的parseValue()函数进行属性解析,而不是通过网页中对各种选择器的调用先后顺序传id号。这里通过以 parseValue()为入口对每条属性进行解析,如果属性名和属性值都合法,就将其保存,并更新样式表,不会执行属性。“case
199:”中会初始化所有的css属性,而不管属性是否在网页中被调用。在进入parseValue之前,浏览器会在CSSGrammar.cpp中调用 ssPropertyID (),cssValueKeywordID (),来分别判断属性名和属性值是否合法。

对于内部样式,必需将css属 性放在选择器里,即在{ }之间,否则该属性将失去作用,并且它后面的所有选择器中的css样式也都将失去作用。

(2)Inline Styles

标签内部的样式:如<a style =”color:red” ..>,它们在<style>..</style>内的所有属性解析完成之后,不会解析他们。当网页上标签中有 style=”..”时,执行到相应的标签时,才进行解析,解析不会经过CSSGrammar.cpp。它们的优先最高。也就是说,标签中的样式是在标签
被执行时才进行初始化,并且初始化方式<style>..</style>内的所有属性不一样。对于标签内部的样式,不要加{ },即不要写成style =”{color:red}”,这样会样式会失去作用。

(3)External Style Sheet

外部 样式按照(1)中内部样式处理。

对于外部样式,也必需要将css属性放在选择器里,即在{ }之间,否则该属性将失去作用,以及紧跟的一个选择器的样式失去作用,不会导致它后面的所有选择器中的css样式失去作用。

注意:内 部样式放在网页的任何位置都会产生效果,为了规范最好都放在与之间。优先级:(3)>(2)>(1)

7、 长度处理

对于各种长度,浏览器在执行时,会把相应长度单位进行转换,全都转换成像素。在css/ CSSPrimitiveValue.cpp中414行把各种长度单位进行了处理,其中cssPixelsPerInch定义为:const float cssPixelsPerInch = 96.0f;
它在css/CSSHelper.h中被定义。按一英寸等于96像素处理。

switch (type) {

case CSS_EMS:

applyZoomMultiplier = false;

factor = computingFontSize ? style->fontDescription().specifiedSize() : style->fontDescription().computedSize();

break;

case CSS_EXS:

applyZoomMultiplier = false;

factor = style->font().xHeight();

break;

case CSS_PX:

break;

case CSS_CM:

factor = cssPixelsPerInch / 2.54; // (一英寸等于2.54厘米)

break;

case CSS_MM:

factor = cssPixelsPerInch / 25.4;

break;

case CSS_IN:

factor = cssPixelsPerInch;

break;

case CSS_PT:

factor = cssPixelsPerInch / 72.0;

break;

case CSS_PC:

// 1 pc == 12 pt

factor = cssPixelsPerInch * 12.0 / 72.0;

break;

default:

return -1.0;

}

double result = getDoubleValue() * factor;

if (!applyZoomMultiplier || multiplier == 1.0)

return result;

8、 一个标签的css样式执行顺序

例:

(1).首先执行一个标签内部的标签属性。如“width=”320””等。如果标 签里的style中也有width属性,或者css样式定义了width, 标签内部的标签属性将被覆盖,style中的优先级最高。

(2).执 行标签在 css里的样式属性。如css样式里对应的img;

(3).执行class或id里的样式属性。如a1里面的属性;

(4).执 行标签内部的style中的样式属性,即使前面执行了width=”320”,这里也会再次执行style=”width:20px”.

9、 添加一条css属性

(1).通过在css/CSSPropertyNames.in文件中增加一个属性名的关键字;

(2). 使用perl命令,生成对应的.cpp和.h文件,这两个文件包含了属性名和属性ID,以及属性名的查找规则;

(3).将(2)中生成的两个文件 拷贝到generated/ 目录下;

(4)在CSSStyleSelector.cpp, CSSParser.cpp两个文件中提供了属性ID对应的接口。

执行:CSSStyleSelector.cpp: void CSSStyleSelector::applyProperty(int id, CSSValue *value)

解 析:CSSParser.cpp : bool CSSParser::parseValue(int propId, bool important)

10、 几个重要的文件

(1).webkit/WebCore/generated/CSSPropertyNmaes.cpp

与 属性名对应的id和查找有关。

(2). webkit/WebCore/css/CSSParser.cpp

与css解析有关。

(3). webkit/WebCore/ generated /CSSValueKeywords.cpp

与属性值对应的id和查找有关。

(4). webkit/WebCore/css/CSSStyleSelector.cpp

与属性执行有关。

(5).webkit/WebKit/qt/Api/qwebview.cpp

swCSSInit() 函数负责初始化浏览器自带的css样式,它主要是从mnt/css目录下读取各个css文件。

(6). webkit/WebCore/rendering/RendBox.cpp

paintBoxDecoration()函数中,绘图有关的操作。

11、 css宽度和高度

css指定的width 和height属性,他们只对Content区域有效。

其 中,border,padding会受背景颜色影响。使用css的”border”属性从padding外面开始画线,占用空间;使用css 的”outline”属性从border外面开始画线,不会占用空间。即给不同的元素增加border属性会出现移位的情况,而outline不会。

在 我们的浏览器中,焦点是从margin外面开始画先,而css的”outline”属性是从border外面开始画线,如果margin为0,它们都从 border外面开始画线,于是对可以聚焦的元素一般不要用outline属性,可以用border属性。

小结:这里我们主要讨论了属性的解析以 及执行的过程,对于具体的属性实现,它涉及具体的操作,根据属性的不同而不同。

12、颜色的处理

对于 颜色,即可以为“#”开头的16进制数(如:#0×00FF00),也可以为对应英文单词(如:white)。在css样式中它们都能够被浏览器识别。其 实,浏览器处理是按照“#”开头的16进制数来传递给QPainter进行处理的。在CSS中颜色对象有两个重要的成员:m_valid(bool
m_valid;)为1则传进来的值是一个颜色值;m_color (RGBA32 m_color;)存放颜色的值。

对于字符串,浏览器的处理 如下:

在webkit/WebCore/platform/Color.cpp中160行,以Color::Color(const char* name)构造一个颜色对象。在这个函数里面,用了一个if语句判断name的第一个字符是否为“#”.

(1) 如果第一个字符是 “#” ,就设置m_valid,在parseHexColor()函数里,通过各种操作,给m_color赋值。

(2) 如果第一个字符不是 “#”,通过findColor()函数在webkit/WebCore/ generated/ColorData.cpp文件中进行查找,它返回一个NamedColor 结构体,该结构体的定义为:“struct NamedColor { const
char *name; int RGBValue; };” ;当查找完成后设置m_color和m_valid的值

Color::Color(const char* name) {

if (name0 == ’#’)

m_valid = parseHexColor(&name1, m_color);

else {

const NamedColor* foundColor = findColor(name, strlen(name));

m_color = foundColor ? foundColor->RGBValue : 0;

m_color |= 0xFF000000;

m_valid = foundColor;

}

}

即css操作的颜色值为m_valid。

13、背景颜色

(1). 设置网页背景颜色

在FrameView::setBaseBackgroundColor(Color bc)中:

925 行:“d->m_baseBackgroundColor = bc;”设置背景颜色为传进来的bc。

(2).获取设置的背景颜色,传递给 下面进行绘图

在RenderBox::paintFillLayerExtended()中:

(a).840行:“Color baseColor =view()->frameView()->baseBackgroundColor();”,调用 baseBackgroundColor()函数取出在(1)中的setBaseBackgroundColor ()函数设置的背景颜色值;

(b).846 行:“context->fillRect(rect, baseColor);” ,调用fillRect()函数,将背景矩形,和背景颜色传进GraphicsContext::fillRect ();

(3).通过 QPainter对象绘制背景颜色

在GraphicsContext::fillRect ()中;

(c).629 行:“m_data->solidColor.setColor(QColor?); ”,solidColor为一个QBrush(即:QBrush solidColor;),这里设置画刷的颜色为12中设置的背景颜色;

(d).630 行:“ m_data->p()->fillRect(rect, m_data->solidColor);” ,p()返回一个QPainter,fillRect(rect, m_data->solidColor)为QPainte的方法,其原型为:void
fillRect ( const QRect & rectangle, const QBrush & brush )。

14、宽度

在QWebView::paintEvent (QPaintEvent *ev)中,通过传进来的ev对象的region()方法,即ev->region() 得到一个QRegion对象,它为Qpainter指定了一个剪切区域。它里面就包含了屏幕高度,宽度,起始点坐标等信息。比如可以通过下面两种方法打印
这四个值:

printf("%d,%d,%d,%d\n<a href="%d,%d,%d,%d\n">,ev->rect().x(), ev->rect().y(), ev->rect().width(), ev->rect().height());

printf(”,

ev->region().rects().at(0).x(),ev->region().rects().at(0).y(),ev->region().rects().at(0).width(), ev->region().rects().at(0).height());

这里涉及的几个QT对象:

(1)QPaintEvent 对象中包含绘图事件的一些事件信息。它有两种初始化,分别为:

QPaintEvent ( const QRegion & paintRegion )

QPaintEvent ( const QRect & paintRect )

对应的方法:(它 们取出初始化传入的值)

const QRect & rect () const

const QRegion & region () const

(2) QRegion对象为Qpainter指定了一个区域。它不可以直接获取值,需要先转化为QRect对象,通过QRect的方法获取值。

15、 添加一条属性

添加一条属性,它能够绘制一个矩形框。它可以指定矩形边的颜色,边的宽度,边的样式,以及矩形区域。如:

picl:blue solid 2px rect(100px 300px 300px 100px);

picl为属性名称,blue为边的颜色,solid为边 的样式,2px为边的宽度,rect(100px 300px 200px 50px)为矩形区域。

步骤:

(1).通过在 css/CSSPropertyNames.in文件中增加一个属性名的关键字;

(2).使用perl命令(#perl makeprop.pl),生成对应的.cpp和.h文件,这两个文件包含了属性名和属性ID,以及属性名的查找规则;

(3).将(2)中生成 的两个文件拷贝到generated/ 目录下;

(4).在CSSStyleSelector.cpp, CSSParser.cpp两个文件中提供了属性ID对应的接口:(中间一些具体处理不详细阐述,这里只说明总体步骤)

a.在 CSSParser.cpp中的“bool CSSParser::parseValue(int propId, bool important)”函数中增加“case CSSPropertyPicl: ”以及对应的处理代码,它是网页上css样式中我们自定义属性“picl”的解析的入口;

说明:以此函数为起点,将picl中各种值进行解析,判 断值是否合法。如果合法,将其进行保存,保存后会更新css样式。我们所关心的是如何取出picl属性的各个值,以及怎样进行保存,我们不必去修改与样式 更新有关的代码,当解析完后,浏览器会更新css样式,丢弃无效的样式,只保留合法(有效)的样式。由于picl属性是复合属性,添加本条属性只需调用这
个文件中的“bool CSSParser::parseShorthand(int propId, const int *properties, int numProperties, bool important)”函数,它就会完成它里面对应的各种单一属性的解析。

b.如果属性是单一 属性,必须在CSSStyleSelector.cpp中的“void CSSStyleSelector::applyProperty(int id, CSSValue *value)”函数中添加对应的case语句,它是css样式执行的入口。由于我们添加的属性是一条复合属性,它在执行时会通过它里面的单一属性进行执
行。因此,在CSSStyleSelector.cpp中的“void CSSStyleSelector::applyProperty(int id, CSSValue *value)”函数中不必添加本条属性对应的case语句。

说明:我借助了border属性的方法来保存“边的颜 色,边的宽度,边的样式”,我们主要需要做的是对矩形值的处理,以及设置一个执行我们的“picl”属性的标志。我添加了一个单一属性:hic,它主要保 存矩形的四个值。在CSSStyleSelector.cpp中,“边的颜色,边的宽度,边的样式”这三个值由类似border属性处理方式进行了传递,
矩形的值通过“ case CSSPropertyHic: ”来传递,当“picl”属性的标志被激活时,则取出picl属性的各个值,做如下处理:

paintInfo.context->setStrokeColor(); 设置边的颜色;

paintInfo.context->setStrokeThickness(); 设置边的宽度;

paintInfo.context->setStrokeStyle(); 设置边边的样式;

paintInfo.context->drawLine() 画矩形的一条边,需要画四次。

修改的文件:

(1).webkit/WebCore/css/CSSPropertyNames.in 中,在文件末尾增加如下两行:

hic;

picl;

(2).webkit/WebCore/css/CSSParser.cpp 中,CSSParser::parseValue()函数中增加“case CSSPropertyPicl: ”以及对应的处理代码。如下:

case CSSPropertyPicl: {

const int properties4 = { CSSPropertyBorderWidth, CSSPropertyBorderStyle,

CSSPropertyBorderColor, CSSPropertyHic};

return parseShorthand(propId, properties, 4, important);

}

(3).webkit/WebCore /css/CSSStyleSelector.cpp中,CSSStyleSelector::applyProperty()函数中添加对应的 “case CSSPropertyHic: ”,这里取出矩形的四条边的值,并传送到下面。

(4).webkit/WebCore /rendering/RenderObject.h中添加hasHic(),getHicRect()两个函数的定义。

(5).webkit/WebCore /rendering/style/RenderStyle.h中添加如下几个函数:

Length hicLeft() const { return visual->hic.left(); }

Length hicRight() const { return visual->hic.right(); }

Length hicTop() const { return visual->hic.top(); }

Length hicBottom() const { return visual->hic.bottom(); }

LengthBox hic() const { return visual->hic; }

bool hasHic() const { return visual->hasHic; }

void setHasHic(bool b =true) { SET_VAR(visual, hasHic, b) }

void setHic(Length top, Length right, Length bottom, Length left);

(6).webkit/WebCore /rendering/style/RenderStyle.cpp中添加setHic()函数的实现;

(7).webkit/WebCore /rendering/style/StyleVisualData.h中添加:

LengthBox hic;

bool hasHic : 1; //hasHic

(8).webkit/WebCore/rendering/RendBox.h中添加:

virtual IntRect getHicRect(int tx, int ty);

(9).webkit/WebCore/rendering /RendBox.cpp中,RenderBox::paintBoxDecorations()函数中添加画矩形的实现,以及getHicRect() 函数的实现。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: