測試頁面
<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的分析暫時到此。