测试页面
<input type="submit" value="GOOD" style="height:30px; width: 70px;">
DOM Tree
*#document 0xcfbbf8
HTML 0xc7dba8
HEAD 0xcef368
BODY 0xcd4aa8
INPUT 0x9100b8 STYLE=height:30px; width: 70px;
Render Tree
layer at (0,0) size 980x1250
RenderView at (0,0) size 980x1250
layer at (0,0) size 980x1250
RenderBlock {HTML} at (0,0) size 980x1250
RenderBody {BODY} at (8,8) size 964x1234
RenderButton {INPUT} at (0,0) size 70x30 [bgcolor=#C0C0C0] [border: (2px outset #C0C0C0)]
RenderBlock (anonymous) at (14,10) size 42x19
RenderText at (0,0) size 44x19
text run at (0,0) width 44: "GOOD"
RenderText {#text} at (0,0) size 0x0
HTMLInputElement
创建
在解析html时,对于input标签,会创建HTMLInputElement节点用于构建DOM树。
创建HTMLInputElement时会调用HTMLInputElement::create接口。该函数new了一个HTMLInputElement的对象。
看下HTMLInputElement的继承体系
Node
ContainerNode
Element
StyledElement
HTMLElement FormAssociatedElement
HTMLFormControlElement
HTMLFormControlElementWithState
HTMLTextFormControlElement InputElement
HTMLInputElement
属性
属性的设置通过HTMLInputElement::parserMappedAttribute(Attribute*attr)
首先属性在解析后会保存在Element的成员mutable RefPtr<NamedNodeMap> m_attributeMap;中,而Element是HTMLInputElement的基类,以后HTMLInputElement可以通过其基类的成员m_attributeMap找到所有属性的信息。
Attribute中保存了该属性的名字和值的信息。该函数会被循环调用,每次把一组属性传入。
首先被传入的属性是type="submit" ,对type属性的处理会调用HTMLInputElement::updateType()。HTMLInputElement中有成员OwnPtr<InputType>m_inputType;用于记录类型,构造时该类型被创建为TextInputType类型。在HTMLInputElement::updateType()中会根据type的值创建对应的InputType的子类,然后赋值给m_inputType。这里的值是submit,对应创建的SubmitInputType类型。
这里看下SubmitInputType的继承体系
InputType
BaseButtonInputType
SubmitInputType
第二个属性是value="GOOD",但是注意,在HTMLInputElement::parserMappedAttribute中并没有处理value的值。即GOOD信息并没有被转存。
CSS属性
第三个属性是style="height:30px; width: 70px;"。在HTMLInputElement::parserMappedAttribute中没有找到对style属性的处理,那么在函数的结尾处会调用其父类的parserMappedAttribute,其父类的处理逻辑一样,先查看自己是否能够处理该属性,如果不能继续调用父类的parserMappedAttribute。调用栈如下
#0WebCore::HTMLElement::parseMappedAttribute
#1WebCore::HTMLFormControlElement:: parserMappedAttribute
#2WebCore::HTMLTextFormControlElement:: parserMappedAttribute
#3 WebCore::HTMLInputElement::parserMappedAttribute
在HTMLElement:: parserMappedAttribute中判断了属性是styleAttr后,调用了StyledElement:: parserMappedAttribute,注意StyledElement是HTMLElement的基类。
StyledElement:: parserMappedAttribute终于处理该属性了,在StyledElement中有成员RefPtr<CSSMutableStyleDeclaration> m_inlineStyleDecl;这个记录了style类型的属性情况。看下CSSMutableStyleDeclaration的继承体系。
StyleBase
CSSStyleDeclaration
CSSMutableStyleDeclaration
StyleBase是对应CSS的,代码中注释说明为//Base class for most CSS DOM objects.由于style属性描述的是css内容,所以这里解析走到了css相关的处理类中。
在StyledElement::parserMappedAttribute中首先判断m_inlineStyleDecl为空,则创建一个CSSMutableStyleDeclaration赋值给m_inlineStyleDecl,之后调用CSSMutableStyleDeclaration:: parseDeclaration来处理刚才的attribute的值,也就是"height:30px; width: 70px;"
在CSSMutableStyleDeclaration中有成员vector<CSSProperty, 4> m_properties;
在CSSMutableStyleDeclaration:: parseDeclaration中创建了一个CSSParser,通过CSSParser::parseDeclaration来解析这个css的属性值,解析后的属性会存在CSSMutableStyleDeclaration的m_properties中。其中每个CSS属性是用一个CSSProperty来标识的,该CSS属性的类型用CSSProperty中的成员int m_id : 15;来标识,属性的值用CSSProperty中的成员RefPtr<CSSValue> m_value;来标识。
那么经过CSSParser::parseDeclaration处理后CSSMutableStyleDeclaration:: m_properties已经存储了解析后的CSS的属性。回顾一下CSSMutableStyleDeclaration是StyledElement中的成员m_inlineStyleDecl,而HTMLElement继承自StyledElement,HTMLInputElement又继承自HTMLElement。则HTMLInputElement可以通过基类的成员m_inlineStyleDecl找到它的CSS的属性了。
RenderButton的创建
在为HTMLInputElement添加了属性后,通过调用它的HTMLInputElement::attach方法来创建对应的RenderObject。
HTMLInputElement::attach在层层调用父类的attach后,在Element::attach中终于开始创建RenderObject了,看下调用栈:
#0 WebCore::HTMLInputElement::createRenderer
#1 WebCore::Node::createRendererAndStyle
#2 WebCore::Node::createRendererIfNeeded
#3 WebCore::Element::attach
#4 WebCore::HTMLFormControlElement::attach
#5 WebCore::HTMLInputElement::attach
HTMLInputElement::createRenderer又通过其成员m_inputType这个标识它类型的成员,调用了该成员的createRenderer来创建具体的RenderObject。根据之前的分析,此时的m_inputType是SubmitInputType类型的。所以此处调用的SubmitInputType:: createRenderer, SubmitInputType本身没有实现createRenderer则这里调用其父类BaseButtonInputType的createRenderer。
在BaseButtonInputType::createRenderer中创建的是RenderButton类的对象。源码中注释说明为
// RenderButtonsare just like normal flexboxes except that they will generate an anonymousblock child.
// For inputs,they will also generate an anonymous RenderText and keep its style and contentup
// to date as thebutton changes.
看下RenderButton的继承体系。
RenderObject
RenderBoxModelObject
RenderBox
RenderBlock
RenderFlexibleBox
RenderButton
在创建了RenderButton之后,把RenderButton与HTMLInputElement相互关联。
在HTMLFormControlElement::attach执行完HTMLElement::attach后,会调用其对应的RenderObject的updateFromElement。看名字是从Element更新一些东西给RenderObject。这里调用的是RenderButton::updateFromElement。
RenderTextFragment的创建
RenderButton::updateFromElement从它对应的HTMLInputElement中通过valueWithDefault取出要显示的字符串的值,然后通过RenderButton::setText设置给RenderButton的成员RenderTextFragment*m_buttonText;
该成员也是一个RenderObject,这里首先需要创建RenderTextFragment的对象,设置字符串的值,还要把RenderButton的RefPtr<RenderStyle> m_style;成员赋值给RenderTextFragment的RefPtr<RenderStyle> m_style;并把RenderTextFragment加入为RenderButton的孩子。这里也就是说RenderObject树的孩子会继承父亲的RenderStyle信息。但是这里还有一点要注意RenderTextFragment对应的Node是Document节点。
这里先看下字符串的值从哪里获取的,之前将到在处理属性时,value="GOOD"中的GOOD信息并没有被转存到HTMLInputElement的某个成员中,那么它的信息只能从Element::m_attributeMap中获取。在HTMLInputElement::valueWithDefault函数会获取保存的字符串信息,它首先从m_inputType中获取,如果没有,则从其成员InputElementDatam_data中获取,如果仍然没有,则从其属性中获取,这个属性就是指m_attributeMap中获取,通过Element::fastGetAttribute(const QualifiedName& name)找到某个属性名对应的属性值,这里就是“value”属性名,找到了值“GOOD”。如果再找不到则会调用m_inputType的fallbackValue函数来获取。
获取到“GOOD”字符串后,会利用该值创建RenderTextFragment。看下RenderTextFragment的继承体系:
RenderObject
RenderText
RenderTextFragment
在把RenderTextFragment设置给RenderButton时,调用的是RenderButton的addChild,该函数会先通过RenderBlock::createAnonymousBlock创建一个匿名的RenderBlock,把这个匿名的RenderBlock设置给RenderButton的成员RenderBlock* m_inner;然后把这个m_inner设置为RenderButton的孩子,之后再把刚刚创建的RenderTextFragment设置为这个匿名RenderBlock m_inner的孩子。
这个过程也就是RenderButton内部有一个匿名的RenderBlock m_inner。所以新添加到RenderButton的孩子实际上是添加为m_inner的孩子。而m_inner本身又是RenderButton的孩子。也就是匿名的RenderBlock m_inner成为了一个中间RenderObject。
RenderButton-> 匿名RenderBlock m_inner-> RenderTextFragment
经过上述的过程,HTMLInputElement 有了与其对应的RenderButton,而RenderButton也有了一个专门针对其字符串信息的RenderTextFragment孩子,并且该RenderTextFragment中存有了HTMLInputElement的第二个属性value="GOOD"的信息。
结果以上的内容就完成了解析过程中对input标签的Node和RenderObject的创建,并将其插入到DOM树和Render树中。
Layout
在完成解析后,会执行layout的操作,在FrameLoader::finishedParsing()中会逐步调用到整个页面的layout。看下调用栈:
#0 WebCore::RenderView::layout
#1 WebCore::FrameView::layout
#2 WebCore::Document::implicitClose
#3 WebCore::FrameLoader::checkCallImplicitClose
#4 WebCore::FrameLoader::checkCompleted
#5 WebCore::FrameLoader::finishedParsing
由调用栈可知,调用了RenderView的layout。RenderView是Render树的根,跟DOM树的Document对应,它的继承体系如下:
RenderObject
RenderBoxModelObject
RenderBox
RenderBlock
RenderView
接下来进入了比较复杂的layout逻辑。首先看下RenderObject提供的跟layout相关函数。
virtual voidlayout();// Recursive function that computes the size and position of thisobject and all its descendants.
voidlayoutIfNeeded();/* This function performs a layout only if one is needed. */
接下来要回顾一下当前页面的RenderTree,根据RenderTree对比着进行layout的分析。
layer at (0,0) size 980x1250
RenderView at (0,0) size 980x1250
layer at (0,0) size 980x1250
RenderBlock {HTML} at (0,0) size 980x1250
RenderBody {BODY} at (8,8) size 964x1234
RenderButton {INPUT} at (0,0) size 70x30 [bgcolor=#C0C0C0] [border: (2px outset #C0C0C0)]
RenderBlock (anonymous) at (14,10) size 42x19
RenderText at (0,0) size 44x19
text run at (0,0) width 44: "GOOD"
RenderText {#text} at (0,0) size 0x0
首先看第一个出现的RenderObject,也就是RenderView,刚刚说过,调用了RenderView::layout。
RenderView的layout
RenderView::layout会进一步调用其基类的RenderBlock ::layout。这里我们稍微看一下RenderBlock的layout过程,因为很多RenderObject都继承自RenderBlock。
RenderBlock ::layout会调用RenderBlock::layoutBlock。
RenderBlock::layoutBlock中会对其孩子进行layout的调用,其中其孩子分为Inline类型的和Block类型的,这里会做一个判断,判断如果children是Inline类型的,则调用RenderBlock::layoutInlineChildren,否则调用RenderBlock:: layoutBlockChildren。
看RenderTree可知,RenderView的孩子是RenderBlock{HTML}所以这里调用的是RenderBlock:: layoutBlockChildren。
RenderBlock:: layoutBlockChildren会对其所有的孩子分别处理,会调用RenderBlock:: layoutBlockChild.
RenderBlock:: layoutBlockChild会找到具体的child,如果需要,则调用该child的layout。这样把layout的操作传递了下去。
看下上述所说内容的调用栈:
#0RenderBlock::layoutBlockChild
#1RenderBlock::layoutBlockChildren
#2RenderBlock::layoutBlock
#3RenderBlock ::layout
#4RenderView::layout
以上调用栈完成了RenderTree中的RenderView at (0,0) size 980x1250
接下来看RenderBlock {HTML} at (0,0) size 980x1250,这里是RenderBlock,那么调用过程就跟上述讲的几乎一致。那么会执行如下的调用栈
#0RenderBlock::layoutBlockChild
#1RenderBlock::layoutBlockChildren
#2RenderBlock::layoutBlock
#3RenderBlock ::layout
#4RenderBlock::layoutBlockChild
#5RenderBlock::layoutBlockChildren
#6RenderBlock::layoutBlock
#7RenderBlock ::layout
#8RenderView::layout
再接下来看RenderBody {BODY} at (8,8) size 964x1234
这个的处理有一点不同,它的孩子被认为是Inline类型的,这里我不知道为什么。
它的调用栈如下:
#0RenderBlock::layoutInlineChildren
#1RenderBlock::layoutBlock
#2RenderBlock::layout
RenderButton 的layout
终于到了我们的RenderButton {INPUT} at (0,0) size 70x30 [bgcolor=#C0C0C0] [border: (2px outset #C0C0C0)]行了。
在之前的RenderBlock::layoutInlineChildren中会调用RenderButton的layoutIfNeeded(),该函数判断如果需要,则会调用layout操作。
那么这里也就会调用到RenderButton的layout了,由于RenderButton没有重新实现layout,其基类RenderFlexibleBox也没有重新实现layout。这里调用的是其基类RenderBlock::layout。
接着按之前的逻辑会调用layoutBlock,这时候RenderFlexibleBox实现了layoutBlock接口,所以这里会调用到RenderFlexibleBox::layoutBlock。
在RenderFlexibleBox::layoutBlock会判断下孩子是水平布局还是垂直布局的,根据情况会分别调用layoutHorizontalBox和layoutVerticalBox。当前是调用的RenderFlexibleBox:: layoutHorizontalBox
RenderFlexibleBox::layoutHorizontalBox中又会调用到孩子的layoutIfNeeded。
上述是RenderButton的layout调用过程,看下调用栈:
#0 RenderFlexibleBox::layoutHorizontalBox
#1 RenderFlexibleBox::layoutBlock
#2RenderBlock::layout
#3RenderBlock::layoutIfNeeded
接下来是RenderBlock (anonymous) at (14,10) size 42x19这个匿名RenderBlock。(Layout部分比较复杂,本人没研究过,以下只是简单的看看大概情况,这些内容还是得多看看CSS相关的规范)
在RenderText中有方法InlineTextBox* RenderText::createInlineTextBox(),并且有成员InlineTextBox* m_firstTextBox; InlineTextBox* m_lastTextBox;这两个成员组成了一个链表结构。
看下InlineTextBox的继承体系:
InlineBox
InlineTextBox
在匿名的Block中会调用RenderText::createInlineTextBox()。这里注意下在InlineBox和InlineTextBox中有paint()方法。注意InlineTextBox和InlineBox并没有layout方法。
看下匿名RenderBlock调用到RenderText::createInlineTextBox()时的调用栈
#0 WebCore::RenderText::createInlineTextBox
#1 createInlineBoxForRendere (static)
#2 WebCore::RenderBlock::constructLine
#3 WebCore::RenderBlock::layoutInlineChildren
#4 WebCore::RenderBlock::layoutBlock
#5 WebCore::RenderBlock::layout
对于layout大致就先看到这里,有个大致的印象先。
Paint
这里直接看对RenderButton开始的paint操作。
RenderObject提供了virtual void paint(PaintInfo&,int tx, int ty);方法。绘制时都是调用该paint虚函数。其中PaintInfo相当于一个绘制的上下文信息,(tx, ty)是该RenderObject的绘制的起始位置。
RenderButton极其父类RenderFlexibleBox都没有实现paint,所以当调用RenderButton的paint时,调用的是其基类RenderBlock的paint。
RenderBlock::paint
RenderBlock::paint中首先通过参数获取了它的绘制的起始位置,也就是左上角的坐标,该坐标需要加上一个内部的相对偏移坐标,即如下代码:
tx += x();
ty += y(); //tx,ty:参数传入的绝对的起始坐标,x(),y()计算的相对于父节点的起始坐标.
参数PaintInfo中的成员phase用于记录当前绘制的哪个阶段,看下它的枚举值
enumPaintPhase {
PaintPhaseBlockBackground,
PaintPhaseChildBlockBackground,
PaintPhaseChildBlockBackgrounds,
PaintPhaseFloat,
PaintPhaseForeground,
PaintPhaseOutline,
PaintPhaseChildOutlines,
PaintPhaseSelfOutline,
PaintPhaseSelection,
PaintPhaseCollapsedTableBorders,
PaintPhaseTextClip,
PaintPhaseMask
};
RenderBlock::paint中会调用RenderBlock::paintObject,该函数会根据传入的PaintInfo::phase值来选择进入对应的绘制流程中去。
那么是在哪里控制着PaintInfo::phase值的设置呢?是在该RenderBlock::paint之前的调用中。之前的调用是InlineBox::paint,该函数是从哪里调用过来的暂时先不看了。这里只关注下该函数的实现。
InlineBox::paint
InlineBox::paint中有如下的代码
PaintInfo info(paintInfo);
info.phase = preservePhase ?paintInfo.phase : PaintPhaseBlockBackground;
renderer()->paint(info, childPoint.x(),childPoint.y());
if (!preservePhase) {
info.phase =PaintPhaseChildBlockBackgrounds;
renderer()->paint(info,childPoint.x(), childPoint.y());
info.phase = PaintPhaseFloat;
renderer()->paint(info, childPoint.x(),childPoint.y());
info.phase = PaintPhaseForeground;
renderer()->paint(info,childPoint.x(), childPoint.y());
info.phase = PaintPhaseOutline;
renderer()->paint(info,childPoint.x(), childPoint.y());
}
这里的renderer()就是RenderButton。该段代码比较清晰的展现了绘制控制的流程:
创建PaintInfo。通过它设置他的phase成员,来多次调用renderer()的paint函数。
绘制背景。
绘制Float。
绘制前景。
绘制输出线。
这里控制了绘制的顺序,之后在RenderBlock::paintObject中就会根据这里设置的PaintInfo::phase的值,做相应的绘制的处理。
回到RenderBlock::paint中,前面讲的RenderBlock::paint会调用RenderBlock::paintObject。
RenderBlock::paintObject
voidRenderBlock::paintObject(PaintInfo& paintInfo, int tx, int ty),该函数中此时传入的tx和ty已经是经过RenderBlock::paint调整后的坐标了。
该函数通过判断PaintInfo::phase来进入不同的绘制,这里只看phase值为PaintPhaseForeground的情况,即绘制前景的情况。
这里首先会进入RenderBlock::paintContents函数,根据函数名可知,该函数是绘制具体内容用的。
RenderBlock::paintContents
该函数中主要做了一个判断,如果孩子是Inline类型的,则调用其成员
RenderLineBoxListm_lineBoxes; // All of the root lineboxes created for this block flow. Forexample, <div>Hello<br>world.</div> will have two total linesfor the <div>.
的paint操作。否则调用RenderBlock::paintChildren。这里其孩子是匿名RenderBlock所以这里调用的是RenderBlock::paintChildren。该函数中会遍历的调用每一个孩子的paint操作。
那么经过RenderBlock::paintChildren后,paint操作由RenderButton传递到了匿名RenderBlock。看下到这的调用栈:
#0 WebCore::RenderBlock::paintChildren
#1 WebCore::RenderBlock::paintContents
#2 WebCore::RenderBlock::paintObject
#3 WebCore::RenderBlock::paint
#4 WebCore::InlineBox::paint
这里PaintInfo::phase仍然是PaintPhaseForeground。对于匿名RenderBlock,它的调用与RenderButton非常类似,不同之处是它的孩子RenderText是Inline类型的,所以在RenderBlock::paintContents中调用的是上面提到的RenderLineBoxList m_lineBoxes的paint函数。看下到匿名RenderBlock的调用栈:
#0 WebCore::RenderBlock::paintContents
#1 WebCore::RenderBlock::paintObject
#2 WebCore::RenderBlock::paint
#3 WebCore::RenderBlock::paintChildren
#4 WebCore::RenderBlock::paintContents
#5 WebCore::RenderBlock::paintObject
#6 WebCore::RenderBlock::paint
#7 WebCore::InlineBox::paint
RenderLineBoxList::paint
RenderLineBoxList有成员如下:
// For block flows, each box representsthe root inline box for a line in the
// paragraph.
// For inline flows, each box represents aportion of that inline.
InlineFlowBox* m_firstLineBox;
InlineFlowBox* m_lastLineBox;
该成员组件了一个链表,在RenderLineBoxList::paint中会遍历这个链表,分别调用每个元素的paint操作,即InlineFlowBox::paint。这里首先调用的是RootInlineBox::paint,RootInlineBox是InlineFlowBox的子类。在RootInlineBox::paint中会调用其基类InlineFlowBox::paint。
看下RootInlineBox的继承体系:
InlineBox
InlineFlowBox
RootInlineBox
在InlineFlowBox::paint中会绘制其孩子,这里找到了InlineTextBox,调用它的paint。
看下调用栈如下:
#0 WebCore::InlineTextBox::paint
#1 WebCore::InlineFlowBox::paint
#2 WebCore::RootInlineBox::paint
#3 WebCore::RenderLineBoxList::paint
经过上述的调用过程,到达了InlineTextBox::paint,这个InlineTextBox对应的就是之前RenderText创建的那个InlineTextBox.
InlineTextBox的paint就会对文字进行真正的绘制了。InlineTextBox的父类InlineBox中有成员RenderObject* m_renderer;该成员记录了InlineBox对应的哪个RenderObject,这里即对应之前的RenderTextFragment了。InlineTextBox有方法textRenderer()可以找到它对应的RenderText,而RenderTextFragment继承自RenderText。所以这个textRenderer()找到的也就是RenderTextFragment了。这样找到了RenderTextFragment也就可以找到要显示的字符串内容了。
另外通过renderer()找到成员m_renderer,通过RenderObject的style()方法可以找到与之对应的RenderStyle,这样可以找到对应的CSS信息。通过paint传入的阐述PaintInfo能够找到绘制上下文的信息,如PaintInfo::context就是GraphicsContext类型的。通过RenderStyle::font()又能找到Font信息。
经过多种信息的获取和计算等操作就可以执行相应的绘制操作了。
看下从进入RenderButton的paint开始到当前的调用栈:
#0 WebCore::InlineTextBox::paint
#1 WebCore::InlineFlowBox::paint
#2 WebCore::RootInlineBox::paint
#3 WebCore::RenderLineBoxList::paint
#4 WebCore::RenderBlock::paintContents
#5 WebCore::RenderBlock::paintObject
#6 WebCore::RenderBlock::paint
#7 WebCore::RenderBlock::paintChildren
#8 WebCore::RenderBlock::paintContents
#9 WebCore::RenderBlock::paintObject
#10 WebCore::RenderBlock::paint
#11 WebCore::InlineBox::paint
那么RenderButton的按钮背景是哪里绘制的?是在之前RenderBlock::paintObject中,传入的PaintInfo::phase为PaintPhaseBlockBackground时。RenderBlock::paintObject会调用RenderBox::paintBoxDecorations,该函数又会进一步调用voidRenderBox::paintBoxDecorationsWithSize(PaintInfo&, int tx, int ty, intwidth, int height);通过参数可见,这里已经计算了位置和大小了。其中此时的width和height就是我们测试页面CSS给出的值style="height:30px; width: 70px;"具体的绘制先不研究了
该调用栈为:
#0 WebCore::RenderBox::paintBoxDecorationsWithSize
#1 WebCore::RenderBox::paintBoxDecorations
#2 WebCore::RenderBlock::paintObject
#3 WebCore::RenderBlock::paint
#4 WebCore::InlineBox::paint
对Button的分析暂时到此。