红心地瓜(tomorrow.cyz@gmail.com)
摘要:本文分析WebKit中html的解析过程,DOM节点树的建立。
关键词:WebKit,html解析,html tree construction,WebCore,
DOM节点树,dlmu2001
1. HTML解析模型
图1HTML解析模型图
上图是HTML解析模型图,HTML解析分成Tokeniser和Tree Construction两个步骤,在”WebKit中的html词法分析”
(http://blog.csdn.net/dlmu2001/archive/2010/11/09/5998130.aspx)一文中,我们已经对Tokeniser这一步进行了分析,本文的目标是Tree Construction这一步。
Tree Construction输入是token流,输出是DOM节点树。
2. DOM树
HTML DOM定义了一套标准来将html文档结构化,它定义了表示和修改文档所需的对象、这些对象的行为和属性以及对象之间的关系,可以把它理解为页面上数据和结构的一个树形表示。
Node是DOM模型中的基础类,它可以分成13类(见NodeType),在HTML解析中,最常见的是Document,Element,Text三类。
l Document是文档树的根节点,在HTML文档中,他派生为HTMLDocument。
l 在文档中,所有的标签转化为Element类,一般它有标签名,并根据标签名继承为特定的子类。
l Element之间的原始文本转化成Text类。
以一个简单的html页面为例:
<html>
<head>
<title>test</title>
</head>
<body>
<h1>hl1</h1>
<h2>hl2</h2>
<h3>hl3</h3>
</body>
</html>
经过解析后的节点树如下(忽略换行符):
图2 HTML DOM节点树示例
如果没有忽略换行符,则每个换行符就是一个Value为”\n”的Text节点。
3. Tree Construction原理
将图二中的节点树以WebKit中的类具体化(同样忽略换行符)。
图3 Webkit HTML DOM节点树示例
看到这里,你是不是觉得仿佛看到了一个呼之欲出的Tree Construction轮廓?是的,最简化的情况就是这样,根据输入的token,创建出相应的Element派生类,然后添加到DOM树中合适的位置,这就是Tree Construction干的事情。当然,添加到合适的位置,这个需要一系列复杂的规则,另外,WebKit将Render树的创建也放到了Tree Construction阶段中来,再加上CSS,Javascript,所以,这就是你看到的复杂的代码。
放出两个函数原型,热热身,培养培养感情。
- PassRefPtr<Element> HTMLConstructionSite::createHTMLElement(AtomicHTMLToken& token);
- void HTMLConstructionSite::insertHTMLElement(AtomicHTMLToken& token);<span style="color:#595959;FONT-SIZE: 12pt"> </span>
Tree Construction流程由一个状态“Insertion Mode”进行控制,它影响token的处理以及是否支持CDATA部分,HTML5中给出了详细的规则(http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#the-insertion-mode)。它也控制了在特定状态下能够处理的token,比如在head里面,再出现head标签,显然是不应该处理的。
4. 开放元素堆栈
为了维护即将解析的标签同已解析的标签之间的关系(此时即将解析的标签还没有加入到DOM树中),引入了开放元素堆栈m_openElements,初始状态下,这个堆栈是空的,它是向下增长的,所以最上面的节点是最早加入到堆栈中的,在html文档中,最上面的节点就是html元素,最底部的节点就是最新加入到堆栈中的。Tree Builder的时候,每碰到一个StartTag的token,就会往m_opnElements中压栈,碰到EndTag的token,则出栈。像Character这样的token,则不需要进行压栈出栈的动作,只有可以包含子节点的tag,才做压栈出栈的动作。Html5的文档中对开放元素堆栈也有说明,http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#the-stack-of-open-elements。
对于正在解析的token,除了根节点html,它必然是堆栈底部元素(m_openElements.top())的子节点,所以在形成DOM树的时候,就可以通过ContainerNode::parserAddChild这样的接口加入到DOM节点树中。
除了正常的堆栈和压栈,对于html,head,body元素,栈结构(HTMLElementStack)中有专门的成员m_htmlElement,m_headElement,m_bodyElement记录,主要是用于检错纠错处理。
在本文的html范例中,当解析到<h2>hl2</h2>的hl2这个character的token的时候,它的开放元素堆栈如下,HTMLHeadingElement是堆栈的top,所以它是hl2这个Text节点的parent。
图4 开放元素堆栈示例
此时的DOM节点树如下:
图5 Webkit DOM节点数示例
5. 元素的创建
HTMLElementFactory类提供了元素的创建方法createHTMLElement。传入为对应的标签名,所属的document,所属的form(如果属于form),在parser的时候,最后一个参数为true。
- PassRefPtr<HTMLElement> HTMLElementFactory::createHTMLElement(const QualifiedName& qName, Document* document, HTMLFormElement* formElement, bool createdByParser);
在HTMLElementFactory中,通过一个Hash Map将tag name和对应的元素构造函数对应起来(gFunctionMap)。tag一般对应一个派生于HTMLElement的类。如下是HTMLHeadingElement的类层次结构图。
图6 HTMLHeadingElement类层次图
6. 其它
HTMLConstructionSite::attach中的attach一词,地瓜理解主要是attach到DOM节点数上,当然,它同时调用了Element::attach,Element类的attach主要是attach到Render树上,它会创建对应该Element的RendrObject。
除了m_openElements,HTMLConstructionSite同时维护了Format元素列表m_activeFormattingElements,Formating元素就是那些格式化标签,包括a,b,big,code,em,font,I,fot,I,nobr,s,small,strike,strong,tt,u。为了处理这些Formatting元素的嵌套关系(此时它们可能不是父子关系,而是平级,不加入到m_openElements),HTML5引入了这个列表(http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#list-of-active-formatting-elements)。
顾名思义,FrameLoader是一个Frame的loader,它的作用就是为客户提供一个下载一个Frame的一系列接口。这里的客户指的是类的客户,比如Frame类,间接客户是上层应用,比如qwebframe。
从它的定义看,最容易想到的是一个load接口,用来将一个frame load下来。任何一个页面至少都需要一个mainframe,因此一个页面的下载一般就是从load一个mainframe开始。
在load frame的过程中,通过FrameLoaderClient接口将load过程的不同阶段告知客户。
FrameLoader通过setDocumentLoader相当于把load的工作委托给了DocumentLoader类。
FrameLoader同DocumentLoader是has-a的关系。一般在load的时候创建DocumentLoader。Frame调用DocumentLoader的startLoadingMainResource开始load frame。
1)Frame和FrameLoader是contain-a的关系,在Frame的构造函数中调用FrameLoader的构造函数,调用时传入参数Frame指针和FrameLoaderClient指针。
2)Frame有可能有子Frame,所以维护SubFrameLoader对象m_subframeLoader来管理子Frame的load。Frame可以对应xml document,也可对应html document,等等。跟Document相关的子resource的load不在FrameLoader的职责范围内。
3)包含一个DocumentWriter类对象m_writer,当Frame的数据load finish的时候,将数据传给DocumentWriter类,进行下一步的处理(比如解码)
4)FrameLoader维护了三个DocumentLoader对象,分别对应于不同的阶段,m_policyDocumentLoader对应于收到用户load调用,进行policy check阶段,m_provisionalDocumentLoader对应于policy check通过以后,Frame数据还没有到来之前,它会负责startLoadingMainResource的调用。m_documentLoader则是Frame第一个数据到来以后使用的DocumentLoader,这个时候,前一个主Frame的DocumentLoader已经不能再用(user agent开始白屏,刷掉前一个页面的显示)。
5)包含一个HistoryController对象,用于操作历史记录相关的接口,保存或者恢复Document和View相关的状态,维护前进后退队列,以实现前进后退功能,前进后退本质上是同Page对象关联的,FrameLoader通过HistoryController操作m_backFowardController对象
6)包含一个ResourceLoadNotifier对象,主要用于同ResourceLoader及FrameLoaderClient打交道,可以理解为ResourceLoader有事件变化或者发生的时候,通知FrameLoader的一个手段
7)包含一个SubframeLoader对象,当FrameLoader下载的Document有子帧需要请求的时候(比如HTMLDocument中解析到iframe元素),用来处理子帧请求
8)将FrameLoader的状态封装到FrameLoaderStateMachine中,这个状态同FrameState不同,FrameState倾向于判断Frame涉及的Document的下载状态,是出于发起状态(Provisional),还是出于已经收到响应但不全(CommittedPage),还是响应收全的状态,倾向于同http相关。而FramLoaderStateMachine倾向于同DocumentLoader相关,用来描述FrameLoader处理DocumentLoader的节点,是处于已经创建,还是显示的状态。
9)PolicyChecker主要用来对FrameLoader进行一些校验。包括三种校验:NewWindow,Navigation和Content。NewWindow对应于浏览器需要新开一个tab页或窗口的时候,Navigation对应于一个页面请求发起的时候,Content校验对应于收到数据以后(判断Mime type等),PolicyChecker通过提供对应的接口,由FrameLoaderClient来对这些请求进行校验,以确定是否允许继续,或者需要其它的动作。
FrameTree对象用来协助管理父帧和子帧的关系,常见的比如main frame之中有iframe元素,就会调用FrameLoaderClientQt::createFrame来产生子帧,产生的子帧会通过appendChild添加到主帧的树状结构中。Frame通过FrameTree对象,可以方便地访问它的父帧,子帧,兄弟帧。
2) 维护FrameLoader对象用来完成frame的加载,FrameLoader是一个非常重要的类,后续进行进一步的分析。
3) 维护NavigationScheduler对象用来管理页面跳转调度(比如重定向,meta refresh等)。
4) DOMWindow用来管理同DOM相关的事件、属性和消息。
5) FrameViwe类用于Frame的排版。
6) Frame文档解析后,对每一个tag或者attr,会有对应的dom节点关联,Document类用来管理这些dom节点。不同的文档类型继承出不同的子类,比如HTML文档对应子类HTMLDocument,XML文档对应于XMLDocument。
7) SciptController对象,脚本控制器,用来管理脚本的执行和操作。
8) Editor对象用来处理页面的编辑相关的操作,比如拷贝,粘贴,输入等,Editor对象,它同Page类的EditorClient对象紧密合作。和EditorClient的关系就如同Page同Frame的关系。
9) SelectionController用来管理Frame中的选取操作。
10) AnimationControlle,动画控制,控制动画的播放,暂停,继续(同HTML video标签是否有关系?)
11) EventHandler,事件处理对象,这里的对象主要是同上层应用也就是用户参与的事件,比如鼠标事件,按键事件(快捷键等),滚动事件,resize事件等。这是一个浏览器外壳经常需要打交道的类。
在WebKit渲染一个页面之前,它需要从网络上(其实也可以从本地文件或者内存加载)加载页面以及和它相关的所有派生资源。同加载资源相关的层有很多,在本文中,我将聚焦于解释WebCore,这一WebKit的主要渲染模块,如何参与到加载过程中的。
WebKit有两条加载路线,一条是加载documents到frames里面,另一条是加载派生资源(比如图片和脚本)。下图总结出了这两条路线涉及到的主要对象。
加载Frames
FrameLoader类负责将documents加载到Frames。当你点击一个链接的时候,FrameLoader创建一个新的处于”policy”状态的DocumentLoader对象,等待WebKit客户端决定是否处理这个加载。通常WebKit客户端会指示FrameLoader将这个加载视为一个导航(navigation),而不是阻止加载等。
一旦客户端指示FrameLoader将本次加载视为一个导航,FrameLoader就推动DocumentLoader进入”provisional”状态,在该状态,DocumentLoader会发起一个网络请求,并等待以确定网络请求将发起一个下载还是一个新的document。
接下去,DocumentLoader会创建一个MainResourceLoader对象,这个对象主要用来通过ResourceHandle接口同平台网络库进行交互。将MainResourceLoader和DocumentLoader分开来主要有两个目的:(1)MainResourceLoader让DocumentLoader从处理ResourceHandle回调的细节中抽身出来(2)降低MainResourceLoader的生命周期和DocumentLoader的生命周期(同Document绑定)的耦合度。
一旦加载系统接收到足够的信息可以确定资源确实代表了document,FrameLoader就将DocumentLoader推向”committed”状态,在该状态中,frame将显示document。
加载派生资源
显示一个Web页面,不只是需要组成document的HTML,还需要加载document引用到的图片,脚本及其它派生资源。DocLoader类负责加载这些派生资源。(注意DocumentLoader和DocLoader有非常相似的名字,但是他们的作用是完全不同的)。
以加载一个图片为例,加载图片时,DocLoader首先询问Cache,在内存中是否有该图片的拷贝(即CachedImage对象)。如果图片已经在Cache中,DocLoader可以以该Cache中的图片立即响应。更有效率更高者,Cache在video内存保留了解码过的图片,这样webkit连解码图片的过程都不需要了。
如果图片不再Cache中,Cache就会创建一个新的CachedImage对象来代表image。然后CachedImage对象要求Loader对象发起一个网络请求,Loader对象创建SubresourceLoader。SubresourceLoader在派生资源加载路线上的作用同MainResourceLoader在主资源加载路线上的作用一样,它也是用来同ResourceHandle直接打交道。
从Webkit CSS的实现可以看到,即使你不指定任何样式表,实际上当CSS模块运作起来的时候,它都会载入几张默认的样式表,要知道,在 CSSStyleSelector的构造函数中,总是会调用loadDefaultStyle()这个函数,其作用就是载入默认的样式表。
这些默认的样式表包含了一些HTML元素的最基本的样式信息。相信在使用css的用户中,大多数人都不会在对<div>指定样式的时候 会为其添加一条display:block吧,是啊,几乎所有使用css html的人都知道div是一个块级元素,所以没人会多此一举,但是通过了解其CSS模块的具体实现,我们可以知道,这些个默认的样式表其实就已经为我们 指定了一系列我们认为的想当然的规则。
这四个默认样式表是
- html4UserAgentStyleSheet
- quirksUserAgentStyleSheet
- svgUserAgentStyleSheet
- sourceUserAgentStyleSheet
额,从名字上大致也能够了解1, 2了吧,它们不是以文件形式存储,而是在CSS中以字符数组的形式出现,也就是说作为数据编到代码里面去了,应该是考虑到每次都要使用默认样式表而为了减少I/O造成的性能损失。
CSS使用的时候,只需要将按照其语法规范,书写一个规则集合,然后保存为一个.css文件,在html中引用即可,当然这里使用的是外部样式表的方式,只是使用CSS的一种方式,在这里我不打算讨论CSS的几种使用方式,所以都按外部的来。
那么这种按照语法规则书写的CSS样式表式如何转换为Webkit内部的CSS模型的呢,这自然需要通过词法语法分析。在这里,Webkit使用了 自动代码生成工具生成了相应的代码,也就是说词法分析和语法分析这部分代码是自动生成的,但它们不够完整,然后我们需要自己写一些配合性的代码才能让真个 CSS模块工作起来,说的再白一些,就是需要我们自己是写一些函数让那些个自动生成的代码来Call Back,用过其他各类解析器的朋友们应该很熟悉这个吧。如果谁对这部分代码有兴趣,可以研究一下。我倒是曾经为找一个跨平台的bug调过这部分代码,结 构还是蛮简单的,代码看起来稍多了些。入口是yylex和yyparse,有兴趣可以自己看看。
那么Webkit中实现的这些个Call Back们在哪里呢?就在CSSParser中了,显然,刨去生成的代码不说,需要手工完成的CSS解析代码部分就是这个了。CSS的一些解析功能的入口 也在此处,它们会调用lex,parse等生成代码。相对的,生成代码中需要的Call Back也需要在这里实现。
举例来说,现在可以来看一个较大单位的回调函数的实现,createStyleRule(),该函数将在一般性的规则需要被建立的时候调用。
1 CSSRule * CSSParser::createStyleRule(CSSSelector * selector) 2 { 3 CSSStyleRule * rule = 0 ; 4 if (selector) { 5 rule = new CSSStyleRule(styleElement); 6 m_parsedStyleObjects.append(rule); 7 rule -> setSelector(sinkFloatingSelector(selector)); 8 rule -> setDeclaration( new CSSMutableStyleDeclaration(rule, parsedProperties, numParsedProperties)); 9 } 10 clearProperties(); 11 return rule; 12 }
从该函数的实现可以很清楚的看到,解析器达到某条件需要创建一个CSSStyleRule的时候将调用该函数,该函数的功能是创建一个 CSSStyleRule,并将其添加已解析的样式对象列表m_parsedStyleObjects中去,这里的对象就是指的Rule。那么如此一来, 经过这样一番解析后,作为输入的样式表中的所有Style Rule将被转化为Webkit 的内部模型对象CSSStyleRule对象,存储在m_parsedStyleObjects中,它是一个Vector。
像这样的函数还有createCharsetRule,createImportRule,createMediaRule等等,它们的作用大体上和createStyleRule类似,都是为创建Rule而准备的,只不过是不同类型的Rule。
了解了上面这些,大体上能够就能够了解CSS解析式怎么运作的。但是我们解析所要的结果是什么?通过调用CSSStyleSheet的 parseString函数,上CSS解析过程将启动,解析完一遍后,所有的Rule都将存储在对应的CSSStyleSheet对象中。但是这个时候的 规则依然是不易于处理的,需要将之转换为CSSRuleSet,CSSRuleSet提供了一个addRulesFromSheet方法,能将 CSSStyleSheet中的rule转换为CSSRuleSet中的rule,这样所有的纯样式规则都会放存储在对应的集合当中,这种集合的抽象就是 CSSRuleSet。以后就可以基于这些个CSSRuleSet来决定每个页面中的元素的样式了,后面会有介绍。
(...)
CSS如何作用于Render Tree
所谓的作用于Render Tree其实是指基于上面的解析成果来为相应的Render Object来指定特定的样式,这个样式的抽象就是RenderStyle(关于Render Tree可参见我的其他文章)。
一、Render树的构成
在我们编写网页及使用JS的时候,大概都知道DOM树及其主要构成,了解到DOM树的构建其实质是对一个html或xml文件的内容采取树结构的方式来组织及描述,不同的标签及其在文档中的位置决定了其在整颗DOM树的地位及属性,针对具体DOM树的构成及不同树节点的描述,可以参考有关DOM的相关标准等,以后有机会我们也会单独来了解。
也许对于Render树大家就不那么了解了,简单的说来,它是对DOM树更进一步的描述,其描述的内容主要与布局渲染等CSS相关属性如left、top、width、height、color、font等有关,因为不同的DOM树结点可能会有不同的布局渲染属性,甚至布局时会按照标准动态生成一些匿名节点,所以为了更加方便的描述布局及渲染,WebKit内核又生成一颗Render树来描述DOM树的布局渲染等特性,当然DOM树与Render树不是一一对应,但可以相互关联,下面分别描述其主要节点:
1、基类RenderObject
RenderObject作为所有Render树节点的基类,完全类似与DOM树中的Node基类,它是构成Render树的基础,作用非比寻常,其中包含了构成Render树所可能涉及到的一些基本属性及方法,内容相当多,其主要数据成员及方法分别如下:
m_style成员则描述该节点对应的各种CSS基本属性数据,下面会单独介绍;
至于其他的诸如m_positioned、m_isText、m_inline、m_floating、m_replaced等则描述其特性,就像CSS标准对不同元素的属性分类定义一样,从字面上我们就可以从上一节WebKit网页布局实现之基本概念及标准篇中可以找到它们这么定义的踪影。
成员m_needsPositionedMovementLayout、m_normalChildNeedsLayout、m_posChildNeedsLayout、m_needsLayout等主要用来描述该RenderObject是否确实需要重新布局;
当一个新的RenderObject对象插入到Render树的时候,它会设置其m_needsLayout属性为true,同时会根据该RenderObject对象在祖先RenderObject看来是一个positioned(拥有positiong:absolute或fixed属性)状态的孩子,如是则将相应祖先RenderObject对象的属性m_posChildNeedsLayout设置为true;
如果是一个in-flow(positon:static或relative)状态的孩子,则将相应祖先RenderObject对象的属性m_normalChildNeedsLayout设置为true;
主要方法:
//与是否需要layout相关
bool needsLayout() const { return m_needsLayout || m_normalChildNeedsLayout ||m_posChildNeedsLayout; }
bool selfNeedsLayout() const { return m_needsLayout; }
bool posChildNeedsLayout() const { return m_posChildNeedsLayout; }
bool normalChildNeedsLayout() const { return m_normalChildNeedsLayout; }
//与基本属性相关
bool isFloating() const { return m_floating; }
bool isPositioned() const { return m_positioned; } // absolute or fixed positioning
bool isRelPositioned() const { return m_relPositioned; } // relative positioning
bool isText() const { return m_isText; }
bool isInline() const { return m_inline; } // inline object
bool isCompact() const { return style()->display() == COMPACT; } // compact object
bool isRunIn() const { return style()->display() == RUN_IN; } // run-in object
bool isDragging() const { return m_isDragging; }
bool isReplaced() const { return m_replaced; } // a "replaced" element (see CSS)
//与外部DOM关联相关
RenderView* view() const;
// don't even think about making this method virtual!
Node* element() const { return m_isAnonymous ? 0 : m_node; }
Document* document() const { return m_node->document(); }
void setNode(Node* node) { m_node = node; }
Node* node() const { return m_node; }
// RenderObject tree manipulation
//
virtual bool canHaveChildren() const;
virtual bool isChildAllowed(RenderObject*, RenderStyle*) const { return true; }
virtual void addChild(RenderObject* newChild, RenderObject* beforeChild = 0);
virtual void removeChild(RenderObject*);
virtual bool createsAnonymousWrapper() const { return false; }
// raw tree manipulation
virtual RenderObject* removeChildNode(RenderObject*, bool fullRemove = true);
virtual void appendChildNode(RenderObject*, bool fullAppend = true);
virtual void insertChildNode(RenderObject* child, RenderObject* before, bool fullInsert = true);
// Designed for speed. Don't waste time doing a bunch of work like layer updating and repainting when we know that our
// change in parentage is not going to affect anything.
virtual void moveChildNode(RenderObject*);
virtual void paint(PaintInfo&, int tx, int ty);
/*
* This function should cause the Element to calculate its
* width and height and the layout of its content
*
* when the Element calls setNeedsLayout(false), layout() is no
* longer called during relayouts, as long as there is no
* style sheet change. When that occurs, m_needsLayout will be
* set to true and the Element receives layout() calls
* again.
*/
virtual void layout() = 0;
其中很多方法如paint()、layout()等是虚拟的,不同的子类可以重载它;
其中方法container() 、containingBlock()、paint()、layout()很值得大家深入研究;
总的说来RenderObject基类定义一些通用属性、方法,以便维护、布局、渲染Render树。
2、子类RenderBox
RenderBox代表描述CSS标准中的Box Model,它继承自RenderObject;
4、子类RenderFlow
RenderFlow主要用来描述CSS标准中提到的能进行inline-flow、block-flow相关处理的Render树结点,它继承自RenderContainer;
5、子类RenderBlock
RenderBlock代表CSS标准中的block-level元素,它继承自RenderFlow;
RenderInline代表inline-level元素,其继承自RenderFlow,主要重载了RenderObject关于inline-flow方面处理的方法,提供了splitFlow、splitInlines等处理自动换行的方法。
7、子类RenderText
RenderText代表对html中Text node对应的Render树节点,它直接继承自RenderObject;
整个Render树中涉及的树节点类型,还有很多如RenderButton、RenderTable、RenderMedia等;并且各个类的方法及数据成员非常多,这里只是初步列出主要的类及其主要方法,特别是可能涉及到布局、渲染方方面的方法,以便我们能从中大致WebKit布局、渲染所涉及的基本内容及方法。
二、CSS属性的描述
1、RenderStyle类
RenderObject对象的m_style成员为RenderStyle类对象,它往往用来描述一个RenderObject所可能涉及的CSS属性数据(如left、top、align、color、font等等),其数据成员往往对应于CSS中定义的所有属性项,内容非常的庞杂,简单的说来就是将CSS标准中的所有属性按照一定分类定义到一个数据结构中。
2、RenderStyle类主要方法
为了获取、设置CSS属性所对应的值,RenderStyle类提供了所有的获取、设置CSS属性的方法如:
void setDisplay(EDisplay v) { noninherited_flags._effectiveDisplay = v; }
void setOriginalDisplay(EDisplay v) { noninherited_flags._originalDisplay = v; }
void setPosition(EPosition v) { noninherited_flags._position = v; }
void setFloating(EFloat v) { noninherited_flags._floating = v; }
void setLeft(Length v) { SET_VAR(surround,offset.left,v) }
void setRight(Length v) { SET_VAR(surround,offset.right,v) }
void setTop(Length v) { SET_VAR(surround,offset.top,v) }
void setBottom(Length v){ SET_VAR(surround,offset.bottom,v) }
void setWidth(Length v) { SET_VAR(box,width,v) }
void setHeight(Length v) { SET_VAR(box,height,v) }
等等。。。。
三、RenderObject及子类对象的生成
1、CSSParser
CSSParser类顾名思义,主要用来解析文本中各种CSS属性,并且有效的组织在一个RenderStyle对象中。
其主要方法parseValue、applyProperty的部分代码示例如下:
bool CSSParser::parseValue(int propId, bool important)
{
.....................................................
case CSSPropertyFloat:
// left | right | none | inherit + center for buggy CSS
if (id == CSSValueLeft || id == CSSValueRight ||
id == CSSValueNone || id == CSSValueCenter)
valid_primitive = true;
break;
case CSSPropertyClear: // none | left | right | both | inherit
if (id == CSSValueNone || id == CSSValueLeft ||
id == CSSValueRight|| id == CSSValueBoth)
valid_primitive = true;
break;
case CSSPropertyWebkitBoxAlign:
if (id == CSSValueStretch || id == CSSValueStart || id == CSSValueEnd ||
id == CSSValueCenter || id == CSSValueBaseline)
valid_primitive = true;
break;
.....................................................
case CSSPropertyWebkitBoxPack:
if (id == CSSValueStart || id == CSSValueEnd ||
id == CSSValueCenter || id == CSSValueJustify)
valid_primitive = true;
break;
....................................................
}
void CSSStyleSelector::applyProperty(int id, CSSValue *value)
{
case CSSPropertyOpacity:
HANDLE_INHERIT_AND_INITIAL(opacity, Opacity)
if (!primitiveValue || primitiveValue->primitiveType() != CSSPrimitiveValue::CSS_NUMBER)
return; // Error case.
// Clamp opacity to the range 0-1
m_style->setOpacity(min(1.0f, max(0.0f, primitiveValue->getFloatValue())));
return;
case CSSPropertyWebkitBoxAlign:
{
HANDLE_INHERIT_AND_INITIAL(boxAlign, BoxAlign)
if (!primitiveValue)
return;
EBoxAlignment boxAlignment = *primitiveValue;
if (boxAlignment != BJUSTIFY)
m_style->setBoxAlign(boxAlignment);
return;
}
...................................................
}
2、CSSStyleSelector类
CSSStyleSelector类其作用是基于所有用户的stylesheets集合为一个给定的DOM Element创建出其对应的RenderStyle对象。其主要功能由方法RenderStyle* styleForElement(Element*, RenderStyle* parentstyle=0, bool allowSharing = true, bool resolveForRootDefault = false);来实现。
3、构建Render树
在构建DOM树的过程中,Dom Element对象创建完后,往往通过attach方法来创建RenderObject对象,进而构建Render树。
其基本实现流程如下:void Element::attach()=>createRendererIfNeeded()=>createRenderer;
RenderObject* Element::createRenderer(RenderArena* arena, RenderStyle* style)
{
if (document()->documentElement() == this && style->display() == NONE) {
// Ignore display: none on root elements. Force a display of block in that case.
RenderBlock* result = new (arena) RenderBlock(this);
if (result)
result->setAnimatableStyle(style);
return result;
}
return RenderObject::createObject(this, style);
}
RenderObject* RenderObject::createObject(Node* node, RenderStyle* style)
{
Document* doc = node->document();
RenderArena* arena = doc->renderArena();
const ContentData* contentData = style->contentData();
if (contentData && !contentData->m_next && contentData->m_type == CONTENT_OBJECT && doc != node) {
RenderImageGeneratedContent* image = new (arena) RenderImageGeneratedContent(node);
image->setStyle(style);
if (StyleImage* styleImage = contentData->m_content.m_image)
image->setStyleImage(styleImage);
return image;
}
RenderObject* o = 0;
switch (style->display()) {//往往在CSSStyleSelector::styleForElement或CSSStyleSelector::adjustRenderStyle时//调用setDisplay()以确定其display属性。
case NONE:
break;
case INLINE:
o = new (arena) RenderInline(node);
break;
case BLOCK:
o = new (arena) RenderBlock(node);
break;
case INLINE_BLOCK:
o = new (arena) RenderBlock(node);
break;
case LIST_ITEM:
o = new (arena) RenderListItem(node);
break;
case RUN_IN:
case COMPACT:
o = new (arena) RenderBlock(node);
break;
case TABLE:
case INLINE_TABLE:
o = new (arena) RenderTable(node);
break;
case TABLE_ROW_GROUP:
case TABLE_HEADER_GROUP:
case TABLE_FOOTER_GROUP:
o = new (arena) RenderTableSection(node);
break;
case TABLE_ROW:
o = new (arena) RenderTableRow(node);
break;
case TABLE_COLUMN_GROUP:
case TABLE_COLUMN:
o = new (arena) RenderTableCol(node);
break;
case TABLE_CELL:
o = new (arena) RenderTableCell(node);
break;
case TABLE_CAPTION:
o = new (arena) RenderBlock(node);
break;
case BOX:
case INLINE_BOX:
o = new (arena) RenderFlexibleBox(node);
break;
}
return o;
}
这样就不同的DOM树节点结合不同的显示属性,创建出不同的RenderObject子类对象,进而形成一个Render树。
作为一个广受好评的浏览器引擎,其网页布局的质量(包括速度、效率、符合标准度等)往往是其关键,那么WebKit究竟是如何布局网页上的所有元素(包括滚动条、文字、图片、按钮、下拉框等)呢?其主要数据结构及流程都包括哪些呢?其布局的基本概念及标准都有哪些呢?下面分别介绍WebKit对其实现及运用。我们首先从关于布局的基本概念及标准的认识开始。
一、CSS布局相关标准介绍
其实我们对要素的布局都有不同程度的了解如我们使用Office时经常使用对一段文字的居中、靠左等操作,复杂一点有设置编号及文字与图片的环绕对应关系等,其实布局的关键在于确定页面元素的显示位置及大小,而页面中主要包括有文字、图片、按钮等页面元素,为了有效的组织布局这些页面元素,一些专家学者经过多年的摸索,总结并设计了布局这些元素所涉及的一些规则及标准,这就是CSS标准。
其中Visual formatting model details对其主要规则进行过具体描述,通过下面相关总结和汇总希望能对其主要要点有一定的认识与理解。
二、布局页面的基本概念
要在一块指定的画布(或窗口)上布局一些要素,往往需要按从上到下或从左到右(或从右到左)的规则来布局这些元素,而有些元素则可以包含其他元素,当作布局容器来使用。其中浏览网页的原生窗口就可看作一个布局容器的根。
由于页面内容的大小可能超过原生窗口提供的显示区域的大小,CSS中称页面上当前显示出来的区域为ViewPort,这个ViewPort相对页面的原始位置可通过滚动条来调整;
CSS标准中定义了html中的一些标签所对应的元素可当成容器使用的,以建立坐标定位所包含的元素如p、div,CSS中称这样的元素为block-level元素,相邻的block-level元素往往从上到下垂直排列;
而 其他象i、a、b、span等标签及text node对应的元素则缺省为inline-level元素,inline-level元素不能用来定位其他元素,但可以包含其他同为inline- level元素,相邻的inline-level元素,往往按照从左到右或从右到左的水平方向排列;
block-level元素所包含的元素往往要么全为block-level元素要么全为inline-level元素,在一定条件下布局时可能会产生匿名block-level元素;
而页面上的每一个元素必须对应一个布局容器称之为Containing Block,只有block-level元素可以成为Containing Block;
一个Containing Block元素究竟包含哪些子元素或者某一元素的Containing Block元素究竟是谁,由其自身position属性及其在文档层次结构中所处的位置所确定,下一节会描述相关内容;
而 每一个元素至少包含一个Box模型即由margin、border、padding、content width/height等属性所能描述的矩形区域;而这块区域相对于布局容器的坐标top、left,往往由布局容器按照block-flow、 inline-flow等规则布局该元素时确定;
CSS中将布局block-level元素的过程称为block-flow;将布局inline-level相关元素的过程称为line-flow;
而 CSS对html中诸如标签frame、image、object、embed、form等对应的元素称为replaced元素,它表示这些元素的内部布局不由Css来定义,而由浏览器来实现,而这些元素从外部来看相当于block-level元素,但可通过设置display:inline将其从外部看设为inline-level元素;
不同的html标签元素可以通过display:inline、display:block、display:inline-block等方式来调整其缺省block-level或inline-level属性;
三、如何确定页面元素显示位置
一个html标签元素的position属性可以设置为static、relative、fixed、absolute、inherit等,所有元素缺省为 static,其Containing Block布局容器元素为最近的祖先block-level元素,其属性left、top、right、bottom不起作用;
position属性为relative的元素同static属性元素一样,但其left、top等属性可以有效,其坐标相对于布局容器而言;
position属性为absolute的元素的布局容器元素是最近的除了其属性不为static的祖先block-level元素;
position属性为fiexed的元素的布局容器元素是往往是根布局容器,但其定位坐标需要根据ViewPort的位置作相应调整;
一 旦确定了其Containing Block布局容器,同时结合其自身的block-level或inline-level特性,布局时根据block flow和inline flow规则就可确定其起始位置,其中inline-level元素可在其布局容器提供的区域内自动换行;而block-level元素可在其布局容器提供的区域内自动换一个段落。
另外float属性为left或right元素较为特殊,则不遵守上面的规则,该元素让在其高度范围内的其他元素始终在其左边或右边。
四、如何确定页面元素大小
对 于有定义其宽高的页面元素,则按照其定义的宽高来确定其大小,而对于象text node这样的inline-level则需要结合其字体大小及文字的多少等来确定其对应的宽高;如果页面元素所确定的宽高超过了布局容器 Containing Block所能提供的宽高,同时其overflow属性为visible或auto,则会提供滚动条来保证可以显示其所有内容。
除非定义了页面元素的宽高,一般说来页面元素的宽高是在布局的时候通过相关计算得出来的。
五、如何理解z-index的使用
页面元素z-index属性的出现,引入了页面元素三维布局的思路,提出分层的概念,具有同一z-index属性的所有元素按照上面提到的二维布局方式(确定其位置及大小)来布局,而不同z-index所代表的层的元素有可能被其他层的元素所覆盖。每一个页面元素只能处在一个z-index所对应的层中,所有元素缺省z-index为0。
六、总结
CSS 布局标准的内容相当多,有的还相当复杂,这里只是初步的了解其基本原则及要素,也未必在各种条件下都成立,希望能为我们能从WebKit代码去了解 WebKit究竟是如何布局页面元素作一定准备而已,如果要想对CSS标准有更深入的具体理解,只有不断的练习及阅读理解CSS布局标准文档。
一、什么是css盒模型?
W3C组织就建议把所有网页上的对象都放在一个盒(box)中,设计师可以通过创建定义来控制这个盒的属性,这些对像包括段落、列表、标题、图片以及层。盒模型主要定义四个区域:内容(content)、边框距(padding)、边界(border)和边距(margin)。margin,background-color,background-image,padding,content,border之间的层次、关系和相互影响。盒模型的示意图。
这些属性我们可以把它转移到我们日常生活中的盒子(箱子)上来理解,日常生活中所见的盒子也具有这些属性,所以叫它盒子模式。那么内容(content)就是盒子里装的东西;而填充(padding)就是怕盒子里装的东西(贵重的)损坏而添加的泡沫或者其它抗震的辅料;边框(border)就是盒子本身了;至于边界(margin)则说明盒子摆放的时候的不能全部堆在一起,要留一定空隙保持通风,同时也为了方便取出嘛。在网页设计上,内容常指文字、图片等元素,但是也可以是小盒子(DIV嵌套),与现实生活中盒子不同的是,现实生活中的东西一般不能大于盒子,否则盒子会被撑坏的,而CSS盒子具有弹性,里面的东西大过盒子本身最多把它撑大,但它不会损坏的。填充和边界只有宽度属性,可以理解为生活中盒子里的抗震辅料厚度,而边框有大小和颜色之分,可以对每一条边框定义不同的样式。我们又可以理解为生活中所见盒子的厚度以及这个盒子是用什么颜色材料做成的,边界就是该盒子与其它东西要保留多大距离。
需要注意到是:width和height定义的是Content部分的宽度和高度而不是整个盒子的高度,padding border margin的宽度依次加在外面。背景会填充padding和content部分。但是由于浏览器设计上的问题,不同浏览器显示效果会有些不同。左右Margin加倍的问题当box为float时,IE6中box左右的margin会加倍。
W3C定义的平面盒模式如下:
二、webkit元素绘制
1.RenderBoxModelObject
(1)在RenderBoxModelObject::styleDidChange()函数里,会根据requiresLayer()函数的返回值,来决定是否
创建一个RenderLayer。requiresLayer()函数的定义为:
virtual bool requiresLayer() const { return isRoot() || isPositioned() || isRelPositioned() || isTransparent() || hasOverflowClip() ||
hasTransform() || hasMask() || hasReflection(); }
在其定义中:
isRoot()判断是否为根节点;
isPositioned()判断是否为absolute定位方式,或者fixed定位方式;
isRelPositioned()判断是否为relative定位方式;
isTransparent()对应于css属性的opacity(透明度),只有当opacity小于1.0时,返回值才为真。
后面几个条件为内部的条件,与css属性无关。
如果要创建一个RenderLayer,就需要上面的requiresLayer()返回为真,所以能够触发创建一个RenderLayer的css属性为:
position:absolute,relative,fixed(static不能,它为无特殊定位,对象遵循HTML定位规则);opacity:小于1 (大于1,isTransparent()
函数返回假,不会创建RenderLayer;小于0的时候,该函数也返回真,会创建RenderLayer)。
创建RenderLayer代码:m_layer = new (renderArena()) RenderLayer(this);
(2)在RenderBoxModelObject::styleDidChange()函数里,创建RenderLayer之后,必须调用setHasLayer(true)函数,否则该RenderLayer
不会被渲染,即被视为没有RenderLayer。
(3)在RenderBoxModelObject::styleDidChange()函数里将新创建的RenderLayer插入RenderLayer树中去。
代码: m_layer->insertOnlyThisLayer();
2.RenderLayer
(1)在RenderLayer::updateZOrderLists()函数里,通过一个for循环,把RenderBoxModelObject::styleDidChange()函数里,插入进来的
所有RenderLayer加入到m_posZOrderList。
m_posZOrderList的定义为:Vector<RenderLayer*>* m_posZOrderList;
这个函数的调用过程如下:
#0 0x42a61f40 in kill () from /lib/libc.so.0
#1 0x42052f14 in pthread_kill () from /lib/libpthread.so.0
#2 0x420534c8 in raise () from /lib/libpthread.so.0
#3 0x4167d438 in QWSSignalHandler::handleSignal () from /opt/lib/libQtGui.so.4
#4 <signal handler called>
#5 0x40e1fc78 in WebCore::RenderLayer::updateZOrderLists () from /opt/lib/libQtWebKit.so.4
#6 0x40e1fe7c in WebCore::RenderLayer::updateLayerListsIfNeeded () from /opt/lib/libQtWebKit.so.4
#7 0x40e1ff3c in WebCore::RenderLayer::hitTestLayer () from /opt/lib/libQtWebKit.so.4
#8 0x40e2043c in WebCore::RenderLayer::hitTestLayer () from /opt/lib/libQtWebKit.so.4
#9 0x40e2115c in WebCore::RenderLayer::hitTest () from /opt/lib/libQtWebKit.so.4
#10 0x408437c0 in WebCore::Document::prepareMouseEvent () from /opt/lib/libQtWebKit.so.4
#11 0x40c8b3e0 in WebCore::EventHandler::prepareMouseEvent () from /opt/lib/libQtWebKit.so.4
#12 0x40c96580 in WebCore::EventHandler::handleMouseMoveEvent () from /opt/lib/libQtWebKit.so.4
#13 0x40c96c0c in WebCore::EventHandler::mouseMoved () from /opt/lib/libQtWebKit.so.4
#14 0x40f4cf38 in WebCore::FrameLoaderClientQt::postProgressFinishedNotification () from /opt/lib/libQtWebKit.so.4
#15 0x40c16808 in WebCore::ProgressTracker::finalProgressComplete () from /opt/lib/libQtWebKit.so.4
#16 0x40c16954 in WebCore::ProgressTracker::progressCompleted () from /opt/lib/libQtWebKit.so.4
#17 0x40bb3518 in WebCore::FrameLoader::checkLoadCompleteForThisFrame () from /opt/lib/libQtWebKit.so.4
#18 0x40bbb008 in WebCore::FrameLoader::recursiveCheckLoadComplete () from /opt/lib/libQtWebKit.so.4
#19 0x40b9f57c in WebCore::DocumentLoader::removeSubresourceLoader () from /opt/lib/libQtWebKit.so.4
#20 0x40c2d51c in WebCore::SubresourceLoader::didFinishLoading () from /opt/lib/libQtWebKit.so.4
#21 0x40c23498 in WebCore::ResourceLoader::didFinishLoading () from /opt/lib/libQtWebKit.so.4
#22 0x40f0bb4c in WebCore::QNetworkReplyHandler::finish () from /opt/lib/libQtWebKit.so.4
#23 0x40f0c7f0 in WebCore::QNetworkReplyHandler::qt_metacall () from /opt/lib/libQtWebKit.so.4
#24 0x41f03374 in QMetaCallEvent::placeMetaCall () from /opt/lib/libQtCore.so.4
#25 0x41f061ac in QObject::event () from /opt/lib/libQtCore.so.4
#26 0x41697c30 in QApplicationPrivate::notify_helper () from /opt/lib/libQtGui.so.4
#27 0x41698b8c in QApplication::notify () from /opt/lib/libQtGui.so.4
#28 0x41eef1b4 in QCoreApplication::notifyInternal () from /opt/lib/libQtCore.so.4
#29 0x41ef42d0 in QCoreApplicationPrivate::sendPostedEvents () from /opt/lib/libQtCore.so.4
#30 0x00000000 in ?? ()
(2)在RenderLayer::paintLayer()函数里,会通过判断m_posZOrderList是否为空,来决定是否继续进行渲染。
代码:
if (m_posZOrderList)
for (Vector<RenderLayer*>::iterator it = m_posZOrderList->begin(); it != m_posZOrderList->end(); ++it)
it[0]->paintLayer(rootLayer, p, paintDirtyRect, paintRestriction, paintingRoot, overlapTestRequests, localPaintFlags);
通过这段代码,将m_posZOrderList里面所有的RenderLayer都渲染,它递归地回调到了paintLayer()函数。
(3)对于每个RenderLayer,会通过下面代码,进入到RenderObject里面,进行具体的渲染。
if (!selectionOnly) {
paintInfo.phase = PaintPhaseFloat;
renderer()->paint(paintInfo, tx, ty);
paintInfo.phase = PaintPhaseForeground;
paintInfo.overlapTestRequests = overlapTestRequests;
renderer()->paint(paintInfo, tx, ty); //为浏览器内容绘制的入口,很重要
paintInfo.phase = PaintPhaseChildOutlines;
renderer()->paint(paintInfo, tx, ty);
}
3.RenderBlock
(1)在RenderBlock::paint()函数里,调用RenderBlock::paintObject()函数,进入RenderObject的绘制。
(2)在RenderBlock::paintObject()函数里,会完成各个阶段的绘制,先从背景,再到内容,浮动,边框等。
(3)在绘制内容时,调用RenderBlock::paintContents()函数,它又会调用RenderBlock::paintChildren()来绘制其子元素。在绘制
子元素函数里面,通过child->paint()回调到RenderBlock::paint()函数。这样就形成了递归调用,直到把所有需要绘制的元素都绘制
完。从这里也可以看出,是从下层往上层绘制,因为paintContents()是在RenderBlock::paintObject()的第二阶段,只有它完成了,
才会绘制其他部分。
函数调用关系:
#0 0x42a60f40 in kill () from /lib/libc.so.0
#1 0x42051f14 in pthread_kill () from /lib/libpthread.so.0
#2 0x420524c8 in raise () from /lib/libpthread.so.0
#3 0x4167c438 in QWSSignalHandler::handleSignal () from /opt/lib/libQtGui.so.4
#4 <signal handler called>
#5 0x40db3c98 in WebCore::RenderBlock::paint () from /opt/lib/libQtWebKit.so.4
#6 0x40db3fdc in WebCore::RenderBlock::paintChildren () from /opt/lib/libQtWebKit.so.4
#7 0x40dc8b2c in WebCore::RenderBlock::paintObject () from /opt/lib/libQtWebKit.so.4
#8 0x40db3c04 in WebCore::RenderBlock::paint () from /opt/lib/libQtWebKit.so.4 重要点,从8到5递归调用。绘制需要重新绘制的元素。
#9 0x40e22ec0 in WebCore::RenderLayer::paintLayer () from /opt/lib/libQtWebKit.so.4
#10 0x40e22760 in WebCore::RenderLayer::paintLayer () from /opt/lib/libQtWebKit.so.4
#11 0x40e23430 in WebCore::RenderLayer::paint () from /opt/lib/libQtWebKit.so.4
#12 0x40cb3fa0 in WebCore::FrameView::paintContents () from /opt/lib/libQtWebKit.so.4
#13 0x40f60000 in QWebFramePrivate::renderPrivate () from /opt/lib/libQtWebKit.so.4
#14 0x40f90360 in QWebView::paintEvent () from /opt/lib/libQtWebKit.so.4
#15 0x416da7fc in QWidget::event () from /opt/lib/libQtGui.so.4
#16 0x40f8ff78 in QWebView::event () from /opt/lib/libQtWebKit.so.4
#17 0x41696c30 in QApplicationPrivate::notify_helper () from /opt/lib/libQtGui.so.4
#18 0x41697b8c in QApplication::notify () from /opt/lib/libQtGui.so.4
#19 0x41eee1b4 in QCoreApplication::notifyInternal () from /opt/lib/libQtCore.so.4
#20 0x416d83b0 in QWidgetPrivate::drawWidget () from /opt/lib/libQtGui.so.4
#21 0x41850394 in QWidgetBackingStore::sync () from /opt/lib/libQtGui.so.4
#22 0xbef9c790 in ?? ()
4.表单输入控件(input)
每个表单输入控件会在RenderBoxModelObject::styleDidChange()函数里,创建一个RenderLayer (因为我们在css默认样式里面,对input使用了opacity属性)。
(1).对于表单输入控件,其绘制不是通过RenderBlock::paintObject ()函数里面第二阶段(2. paint contents)的paintContents(paintInfo, tx, ty)
函数调用来绘制的,而是通过第一阶段(1. paint background, borders etc)的paintBoxDecorations(paintInfo, tx, ty)函数调用来绘制。
(2).在RenderBox::paintBoxDecorations ()函数里,通过 bool themePainted = style()->hasAppearance() && !theme()->paint(this, paintInfo, IntRect(tx, ty, w, h));
语句,在theme()->paint(this, paintInfo, IntRect(tx, ty, w, h)中,进入RenderTheme::paint () 。
(3).在RenderTheme::paint () 函数里面,绘制各种表单输入控件。往下面就是调用QT的图像绘制函数。
表单输入控件。这里是<input type=radio value="">的函数调用:
#0 0x42a59f40 in kill () from /lib/libc.so.0
#1 0x4204af14 in pthread_kill () from /lib/libpthread.so.0
#2 0x4204b4c8 in raise () from /lib/libpthread.so.0
#3 0x41675438 in QWSSignalHandler::handleSignal () from /opt/lib/libQtGui.so.4
#4 <signal handler called>
#5 0x40e2ca74 in WebCore::RenderObject::isBody () from /opt/lib/libQtWebKit.so.4
#6 0x40f2b2c8 in WebCore::RenderThemeQt::paintButton () from /opt/lib/libQtWebKit.so.4
#7 0x40f299a4 in WebCore::RenderThemeQt::paintCheckbox () from /opt/lib/libQtWebKit.so.4
#8 0x40e6feb4 in WebCore::RenderTheme::paint () from /opt/lib/libQtWebKit.so.4
#9 0x40dd88a4 in WebCore::RenderBox::paintBoxDecorations () from /opt/lib/libQtWebKit.so.4
#10 0x40dbdb10 in WebCore::RenderBlock::paintObject () from /opt/lib/libQtWebKit.so.4
#11 0x40dac69c in WebCore::RenderBlock::paint () from /opt/lib/libQtWebKit.so.4
#12 0x40e1b28c in WebCore::RenderLayer::paintLayer () from /opt/lib/libQtWebKit.so.4
#13 0x40e1b524 in WebCore::RenderLayer::paintLayer () from /opt/lib/libQtWebKit.so.4
#14 0x40e1b524 in WebCore::RenderLayer::paintLayer () from /opt/lib/libQtWebKit.so.4
#15 0x40e1c2c0 in WebCore::RenderLayer::paint () from /opt/lib/libQtWebKit.so.4
#16 0x40caca60 in WebCore::FrameView::paintContents () from /opt/lib/libQtWebKit.so.4
#17 0x40f58ee0 in QWebFramePrivate::renderPrivate () from /opt/lib/libQtWebKit.so.4
#18 0x40f89098 in QWebView::paintEvent () from /opt/lib/libQtWebKit.so.4
#19 0x416d37fc in QWidget::event () from /opt/lib/libQtGui.so.4
#20 0x40f88ca4 in QWebView::event () from /opt/lib/libQtWebKit.so.4
#21 0x4168fc30 in QApplicationPrivate::notify_helper () from /opt/lib/libQtGui.so.4
#22 0x41690b8c in QApplication::notify () from /opt/lib/libQtGui.so.4
#23 0x41ee71b4 in QCoreApplication::notifyInternal () from /opt/lib/libQtCore.so.4
#24 0x416d13b0 in QWidgetPrivate::drawWidget () from /opt/lib/libQtGui.so.4
#25 0x41849394 in QWidgetBackingStore::sync () from /opt/lib/libQtGui.so.4
#26 0xbedb1790 in ?? ()
(4).对于password,text两种input输入控件,会在WebCore/rendering/TextControlInnerElements.cpp文件里,创建一个HTMLDivElement。
代码:
TextControlInnerElement::TextControlInnerElement(Document* doc, Node* shadowParent)
: HTMLDivElement(HTMLNames::divTag, doc)
, m_shadowParent(shadowParent)
{
}
它被绑定在对应的input上,它们之间是兄弟关系。这里创建的div,在后面会调用setHasOverflowClip()函数来设置其m_hasOverflowClip。即在RenderBoxModelObject::styleDidChange()函数里,在“if (requiresLayer())”条件里,requiresLayer()函数的第五个条件hasOverflowClip()为真,
则就会创建此div对应的RenderLayer。
创建div对应的RenderObject函数调用:
#0 0x42a5bf40 in kill () from /lib/libc.so.0
#1 0x4204cf14 in pthread_kill () from /lib/libpthread.so.0
#2 0x4204d4c8 in raise () from /lib/libpthread.so.0
#3 0x41677438 in QWSSignalHandler::handleSignal () from /opt/lib/libQtGui.so.4
#4 <signal handler called>
#5 0x40e34c98 in WebCore::RenderObject::RenderObject () from /opt/lib/libQtWebKit.so.4
#6 0x40de06a8 in WebCore::RenderBoxModelObject::RenderBoxModelObject () from /opt/lib/libQtWebKit.so.4
#7 0x40dde648 in WebCore::RenderBox::RenderBox () from /opt/lib/libQtWebKit.so.4
#8 0x40daba90 in WebCore::RenderBlock::RenderBlock () from /opt/lib/libQtWebKit.so.4
#9 0x40e840b0 in WebCore::TextControlInnerTextElement::createRenderer () from /opt/lib/libQtWebKit.so.4 ( 创建div对应的RenderObject)
#10 0x40e849c0 in WebCore::TextControlInnerElement::attachInnerElement () from /opt/lib/libQtWebKit.so.4
#11 0x40e617e8 in WebCore::RenderTextControl::createSubtreeIfNeeded () from /opt/lib/libQtWebKit.so.4 (重要点)
#12 0x40e6a958 in WebCore::RenderTextControlSingleLine::createSubtreeIfNeeded () from /opt/lib/libQtWebKit.so.4
#13 0x40e6ac40 in WebCore::RenderTextControlSingleLine::updateFromElement () from /opt/lib/libQtWebKit.so.4
#14 0x40aa3430 in WebCore::HTMLFormControlElement::attach () from /opt/lib/libQtWebKit.so.4
#15 0x40ab7974 in WebCore::HTMLInputElement::attach () from /opt/lib/libQtWebKit.so.4
#16 0x40adeb94 in WebCore::HTMLParser::insertNode () from /opt/lib/libQtWebKit.so.4
#17 0x40adfa20 in WebCore::HTMLParser::parseToken () from /opt/lib/libQtWebKit.so.4
#18 0x40b01064 in WebCore::HTMLTokenizer::processToken () from /opt/lib/libQtWebKit.so.4
#19 0x40b15bec in WebCore::HTMLTokenizer::parseTag () from /opt/lib/libQtWebKit.so.4
#20 0x40b17c0c in WebCore::HTMLTokenizer::write () from /opt/lib/libQtWebKit.so.4
#21 0x40baf6b8 in WebCore::FrameLoader::write () from /opt/lib/libQtWebKit.so.4
#22 0x40f45fb4 in WebCore::FrameLoaderClientQt::committedLoad () from /opt/lib/libQtWebKit.so.4
#23 0x40ba4b8c in WebCore::FrameLoader::committedLoad () from /opt/lib/libQtWebKit.so.4
#24 0x40b8d904 in WebCore::DocumentLoader::commitLoad () from /opt/lib/libQtWebKit.so.4
#25 0x40c1ce1c in WebCore::ResourceLoader::didReceiveData () from /opt/lib/libQtWebKit.so.4
#26 0x40bf9cb8 in WebCore::MainResourceLoader::didReceiveData () from /opt/lib/libQtWebKit.so.4
#27 0x40c1c6b4 in WebCore::ResourceLoader::didReceiveData () from /opt/lib/libQtWebKit.so.4
#28 0x40f0561c in WebCore::QNetworkReplyHandler::forwardData () from /opt/lib/libQtWebKit.so.4
#29 0x40f067f0 in WebCore::QNetworkReplyHandler::qt_metacall () from /opt/lib/libQtWebKit.so.4
#30 0x41efd374 in QMetaCallEvent::placeMetaCall () from /opt/lib/libQtCore.so.4
#31 0x41f001ac in QObject::event () from /opt/lib/libQtCore.so.4
#32 0x41691c30 in QApplicationPrivate::notify_helper () from /opt/lib/libQtGui.so.4
#33 0x41692b8c in QApplication::notify () from /opt/lib/libQtGui.so.4
#34 0x41ee91b4 in QCoreApplication::notifyInternal () from /opt/lib/libQtCore.so.4
#35 0x41eee2d0 in QCoreApplicationPrivate::sendPostedEvents () from /opt/lib/libQtCore.so.4
#36 0x000a9f60 in ?? ()
创建HTMLDivElement函数调用:
#0 0x42a5af40 in kill () from /lib/libc.so.0
#1 0x4204bf14 in pthread_kill () from /lib/libpthread.so.0
#2 0x4204c4c8 in raise () from /lib/libpthread.so.0
#3 0x41676438 in QWSSignalHandler::handleSignal () from /opt/lib/libQtGui.so.4
#4 <signal handler called>
#5 0x40e836d8 in WebCore::TextControlInnerElement::TextControlInnerElement () from /opt/lib/libQtWebKit.so.4
#6 0x40e83974 in WebCore::TextControlInnerTextElement::TextControlInnerTextElement () from /opt/lib/libQtWebKit.so.4 (构造函数里创建HTMLDivElement)
#7 0x40e60f84 in WebCore::RenderTextControl::createSubtreeIfNeeded () from /opt/lib/libQtWebKit.so.4 (重要点)
#8 0x40e6a1a0 in WebCore::RenderTextControlSingleLine::createSubtreeIfNeeded () from /opt/lib/libQtWebKit.so.4
#9 0x40e6a488 in WebCore::RenderTextControlSingleLine::updateFromElement () from /opt/lib/libQtWebKit.so.4
#10 0x40aa31e8 in WebCore::HTMLFormControlElement::attach () from /opt/lib/libQtWebKit.so.4
#11 0x40ab772c in WebCore::HTMLInputElement::attach () from /opt/lib/libQtWebKit.so.4
#12 0x40ade94c in WebCore::HTMLParser::insertNode () from /opt/lib/libQtWebKit.so.4
#13 0x40adf7d8 in WebCore::HTMLParser::parseToken () from /opt/lib/libQtWebKit.so.4
#14 0x40b00e1c in WebCore::HTMLTokenizer::processToken () from /opt/lib/libQtWebKit.so.4
#15 0x40b159a4 in WebCore::HTMLTokenizer::parseTag () from /opt/lib/libQtWebKit.so.4
#16 0x40b179c4 in WebCore::HTMLTokenizer::write () from /opt/lib/libQtWebKit.so.4
#17 0x40baf470 in WebCore::FrameLoader::write () from /opt/lib/libQtWebKit.so.4
#18 0x40f459d4 in WebCore::FrameLoaderClientQt::committedLoad () from /opt/lib/libQtWebKit.so.4
#19 0x40ba4944 in WebCore::FrameLoader::committedLoad () from /opt/lib/libQtWebKit.so.4
#20 0x40b8d6bc in WebCore::DocumentLoader::commitLoad () from /opt/lib/libQtWebKit.so.4
#21 0x40c1cbd4 in WebCore::ResourceLoader::didReceiveData () from /opt/lib/libQtWebKit.so.4
#22 0x40bf9a70 in WebCore::MainResourceLoader::didReceiveData () from /opt/lib/libQtWebKit.so.4
#23 0x40c1c46c in WebCore::ResourceLoader::didReceiveData () from /opt/lib/libQtWebKit.so.4
#24 0x40f0503c in WebCore::QNetworkReplyHandler::forwardData () from /opt/lib/libQtWebKit.so.4
#25 0x40f06210 in WebCore::QNetworkReplyHandler::qt_metacall () from /opt/lib/libQtWebKit.so.4
#26 0x41efc374 in QMetaCallEvent::placeMetaCall () from /opt/lib/libQtCore.so.4
#27 0x41eff1ac in QObject::event () from /opt/lib/libQtCore.so.4
#28 0x41690c30 in QApplicationPrivate::notify_helper () from /opt/lib/libQtGui.so.4
#29 0x41691b8c in QApplication::notify () from /opt/lib/libQtGui.so.4
#30 0x41ee81b4 in QCoreApplication::notifyInternal () from /opt/lib/libQtCore.so.4
#31 0x41eed2d0 in QCoreApplicationPrivate::sendPostedEvents () from /opt/lib/libQtCore.so.4
#32 0x000a9f60 in ?? ()
5.元素具体绘制
图片,文字,表单输入控件的绘制,都是通过从RenderBlock::paint () 开始,在RenderBlock::paintObject ()中,调用RenderBlock::paintContents ()函数,
来进行递归地绘制。在RenderBlock::paintContents ()函数里,有下面代码:
if (childrenInline())
m_lineBoxes.paint(this, paintInfo, tx, ty);
else
paintChildren(paintInfo, tx, ty);
如果是inline,则进入具体网页内容的绘制。网页上面的图片和文字,都是以inline的方式呈现。即图片和文字都必须通过”m_lineBoxes.paint(this, paintInfo, tx, ty);“
语句,才能够显示在浏览器上面,它决定了网页上面是否能够正常显示图片和文字。
注意:网页上的文字或空格都会创建一个“#text”节点。
文本绘制的函数调用:
#3 0x40d0ecb4 in WebCore::GraphicsContext::drawText () from /opt/lib/libQtWebKit.so.4 (开始绘制文字)
#4 0x40da44ac in WebCore::paintTextWithShadows () from /opt/lib/libQtWebKit.so.4
#5 0x40da7a24 in WebCore::InlineTextBox::paint ()
#6 0x40d9ea28 in WebCore::InlineBox::paint () from /opt/lib/libQtWebKit.so.4
#7 0x40da3c1c in WebCore::InlineFlowBox::paint () from /opt/lib/libQtWebKit.so.4
#8 0x40e82184 in WebCore::RootInlineBox::paint () from /opt/lib/libQtWebKit.so.4
#9 0x40e1db34 in WebCore::RenderLineBoxList::paint () from /opt/lib/libQtWebKit.so.4
#10 0x40daccec in WebCore::RenderBlock::paintContents () from /opt/lib/libQtWebKit.so.4
#11 0x40dc1698 in WebCore::RenderBlock::paintObject () from /opt/lib/libQtWebKit.so.4
#12 0x40dac78c in WebCore::RenderBlock::paint () from /opt/lib/libQtWebKit.so.4
图片绘制的函数调用:
#5 0x40d11784 in WebCore::GraphicsContext::drawImage () from /opt/lib/libQtWebKit.so.4
#6 0x40e03570 in WebCore::RenderImage::paintReplaced () from /opt/lib/libQtWebKit.so.4
#7 0x40e3fb54 in WebCore::RenderReplaced::paint () from /opt/lib/libQtWebKit.so.4
#8 0x40d9eb4c in WebCore::InlineBox::paint () from /opt/lib/libQtWebKit.so.4
#9 0x40da3c24 in WebCore::InlineFlowBox::paint () from /opt/lib/libQtWebKit.so.4
#10 0x40e82198 in WebCore::RootInlineBox::paint () from /opt/lib/libQtWebKit.so.4
#11 0x40e1db48 in WebCore::RenderLineBoxList::paint () from /opt/lib/libQtWebKit.so.4
#12 0x40daccb4 in WebCore::RenderBlock::paintContents () from /opt/lib/libQtWebKit.so.4
#13 0x40dc1660 in WebCore::RenderBlock::paintObject () from /opt/lib/libQtWebKit.so.4
#14 0x40dac754 in WebCore::RenderBlock::paint () from /opt/lib/libQtWebKit.so.4
表单输入控件绘制的函数调用:
#6 0x40f2b2c8 in WebCore::RenderThemeQt::paintButton () from /opt/lib/libQtWebKit.so.4
#7 0x40f299a4 in WebCore::RenderThemeQt::paintCheckbox () from /opt/lib/libQtWebKit.so.4
#8 0x40e6feb4 in WebCore::RenderTheme::paint () from /opt/lib/libQtWebKit.so.4 (这里判断表单输入控件的类型,然后调用该类型的绘制函数)
#9 0x40dd88a4 in WebCore::RenderBox::paintBoxDecorations () from /opt/lib/libQtWebKit.so.4
#10 0x40dbdb10 in WebCore::RenderBlock::paintObject () from /opt/lib/libQtWebKit.so.4
#11 0x40dac69c in WebCore::RenderBlock::paint () from /opt/lib/libQtWebKit.so.4
总结
元素的绘制,是由默认配置(html4.css)中的display,-webkit-appearance这些css属性来控制其具体的绘制流程。即这些css属性变化,他们的绘制也会发生相应的变化。即我们对一些浏览器问题的处理,是基于某一种css默认配置的处理。所以不要轻易改动css默认配置,尤其是与上面的两条属性有关的样式。
DOM树
web页面解析后形成节点树,称作文档对象模型(简称DOM),树上所有节点的基类是Node。
Node.h
节点分为几类,与渲染代码相关的节点类型有:
Document - 树的根节点总是Document,WebCore中有三个文档类: Document, HTMLDocument和SVGDocument。Document用于除了SVG文档之外的所有XML文档。HTMLDocument继承自Document,仅适用于HTML文档。SVGDocument也是继承自Document,适用于SVG文档。 Document.h
HTMLDocument.h
Element - HTML和XML源代码中出现的所有标记都是元素(elements)。从渲染的角度看,元素就是一个节点,它具有标记名并可以转化(cast)成一个能查询渲染所需数据的子类。 Element.h
Text - 元素之间出现的原始文本(raw text)就是文本节点。Text节点存储原始文本(raw text),渲染树能够查询节点,获得字符串数据。 Text.h
渲染树
渲染的核心是渲染树。渲染树非常类似于DOM,树中的每个对象都对应着文档、元素或文本节点。渲染树也可以包含没有对应DOM节点的对象。
所有渲染树节点的基类是RenderObject。
RenderObject.h
DOM节点对应的RenderObject可以使用Node类的Renderer方法获得:
RenderObject* renderer() const
下列方法通常用来遍历渲染树:
RenderObject* firstChild() const;
RenderObject* lastChild() const;
RenderObject* previousSibling() const;
RenderObject* nextSibling() const;
RenderObject* firstChild() const;
RenderObject* lastChild() const;
RenderObject* previousSibling() const;
RenderObject* nextSibling() const;
这里的示例展示了如何遍历渲染对象的子节点,这是渲染代码中最常见的遍历方式:
for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
...
}
for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
...
}
创建渲染树
渲染对象(renderer)通过DOM创建,这一过程称为附着(attachment)。当文档解析,DOM节点添加时,DOM节点的attach方法被调用来创建渲染对象(renderer)。
void attach()
attach方法获得DOM节点的样式信息,如果元素的display CSS属性为none或者节点是具有display:none集合的元素的后代,将不创建任何渲染对象(renderer)。节点的子类和CSS display属性值一起决定为该节点创建什么样的渲染对象(renderer)。
附着(attach)是一个自顶向下的递归操作,父亲节点总是在子孙节点之前创建他们对应的渲染对象(renderer)。
销毁渲染树
当DOM节点从文档移除时,或者在文档关闭时(比如标签页/窗口关闭时),渲染对象(renderer)被销毁。DOM节点的detach方法被调用来断开并销毁渲染对象(renderer)。
void detach()
分离(detachment)是一个自底向上的递归操作。子孙节点总是在父亲节点之前销毁对应的渲染对象(renderer)。
存取样式信息
附着(attachment)过程中,DOM查询CSS获得元素的样式信息,结果存放在RenderStyle对象中。
RenderStyle.h
webkit所支持的每个单独CSS属性都可以通过该对象查询到。RenderStyle是一个引用计数对象。如果DOM创建了一个渲染对象(renderer),它通过渲染对象(renderer)的setStyle方法关联样式信息到渲染对象(renderer)。
void setStyle(RenderStyle*)
渲染对象(renderer)将在Style上增加一个引用,并一直维持着直到得到新的样式或者被销毁。
RenderStyle可以使用style()方法从渲染对象(renderer)获得。
RenderStyle* style() const
CSS盒子模型(The CSS Box Model)
RenderObject的基本子类之一就是RenderBox,该类表示遵从CSS盒子模型的对象,它们包括具有边框、填充(padding)、边距、宽度和高度的任何对象。现在有些对象并没有符合CSS盒子模型(比如SVG对象)但仍然从RenderBox派生,这实际上是一个错误,将在以后进行渲染树重构时修复。
CSS 2.1规格的示意图说明了CSS盒子的各部分,下列方法可用来获得边框/边距/填充的宽度。除非是查看原始的样式信息,否则RenderStyle不应该使用,因为最终RenderObject得到的计算值会有很大不同(特别是表格,可以覆盖单元格的填充,单元格之间也可以有展开的边框)
int marginTop() const;
int marginBottom() const;
int marginLeft() const;
int marginRight() const;
int paddingTop() const;
int paddingBottom() const;
int paddingLeft() const;
int paddingRight() const;
int borderTop() const;
int borderBottom() const;
int borderLeft() const;
int borderRight() const;
int marginTop() const;
int marginBottom() const;
int marginLeft() const;
int marginRight() const;
int paddingTop() const;
int paddingBottom() const;
int paddingLeft() const;
int paddingRight() const;
int borderTop() const;
int borderBottom() const;
int borderLeft() const;
int borderRight() const;
width()和height()方法获得包括边界在内的盒子宽度和高度。
int width() const;
int height() const;
int width() const;
int height() const;
客户盒子(client box)是盒子(box)除边框和滚动条之外但包括填充在内的区域。
int clientLeft() const { return borderLeft(); }
int clientTop() const { return borderTop(); }
int clientWidth() const;
int clientHeight() const;
int clientLeft() const { return borderLeft(); }
int clientTop() const { return borderTop(); }
int clientWidth() const;
int clientHeight() const;
内容盒子(content box)用来表示CSS盒子除边框和填充之外的区域。
IntRect contentBox() const;
int contentWidth() const { return clientWidth() - paddingLeft() - paddingRight(); }
int contentHeight() const { return clientHeight() - paddingTop() - paddingBottom(); }
IntRect contentBox() const;
int contentWidth() const { return clientWidth() - paddingLeft() - paddingRight(); }
int contentHeight() const { return clientHeight() - paddingTop() - paddingBottom(); }
当盒子有垂直或这水平滚动条时,它们放在填充和边框之间,滚动条的宽度和高度包括在client宽度和client高度中。滚动条不是内容盒子(content box)的组成部分。可滚动区域的尺寸和当前滚动的位置都可以从RenderObject获得。我将在关于滚动的另一章节详细阐述。
int scrollLeft() const;
int scrollTop() const;
int scrollWidth() const;
int scrollHeight() const;
int scrollLeft() const;
int scrollTop() const;
int scrollWidth() const;
int scrollHeight() const;
盒子(box)还有x和y位置,这些位置都是相对于祖先节点的,它们负责决定盒子(box)放在何处。但存在一些例外,这也是渲染树让人难以理解的领域之一。