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

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

2012-05-28 10:50 495 查看
1、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() 函数的实现。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: