浏览器探究——webkit部分——Button

来源:互联网 发布:python迭代器是什么 编辑:程序博客网 时间:2024/06/05 11:20


测试页面

<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的分析暂时到此。

0 0