瀏覽器探究——webkit部分——Button



測試頁面

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章