2008年12月26日星期五

WebKit网页布局实现之布局篇

在我们对WebKit网页布局实现所涉及的主要概念及数据结构有了一定的理解之后,让我们再来看看其布局过程的具体实现。

一、FrameView::layout方法
FrameView作为与View相关的类,其主要涉及与显示相关的内容,而其中对页面元素的布局至关重要,这也是浏览器的核心处理部分。

我们都知道浏览器从Web服务器获得数据后,经解析会构建DOM树、Render树,然后进行布局处理,进而为渲染页面作好准备,其中的布局处理往往由FrameView::layout方法发起,让我们来具体看看其实现,一窥其主要实现过程。
void FrameView::layout(bool allowSubtree)
{
if (d->m_midLayout)
return;
// Always ensure our style info is up-to-date. This can happen in situations where
// the layout beats any sort of style recalc update that needs to occur.
//进行CSSStyleSelector的更新处理,因为一旦CSS发生变化,布局的结果也可能发生相关变化,所以在开始布局之前,需要检查CSS是否发生变化,如果有则需要作相应调整,进而可能影响Render树等。
bool subtree = d->m_layoutRoot;
.......................................................................
RenderObject* root = subtree ? d->m_layoutRoot : document->renderer();
if (!root) {
// FIXME: Do we need to set m_size here?
d->m_layoutSchedulingEnabled = true;
return;
}
//布局的处理可能相互嵌套,这与发起布局处理的时机相关。
d->m_nestedLayoutCount++;
ScrollbarMode hMode = d->m_hmode;
ScrollbarMode vMode = d->m_vmode;

d->m_doFullRepaint = !subtree && (d->m_firstLayout || static_cast(root)->printing());

if (!subtree) {
// Now set our scrollbar state for the layout.
ScrollbarMode currentHMode = hScrollbarMode();
ScrollbarMode currentVMode = vScrollbarMode();
//对于初次布局,则需要设置FrameView的滚动条信息等
if (d->m_firstLayout || (hMode != currentHMode || vMode != currentVMode)) {
suppressScrollbars(true);
if (d->m_firstLayout) {
d->m_firstLayout = false;
d->m_firstLayoutCallbackPending = true;
d->m_lastLayoutSize = IntSize(width(), height());
d->m_lastZoomFactor = root->style()->zoom();

// Set the initial vMode to AlwaysOn if we're auto.
if (vMode == ScrollbarAuto)
ScrollView::setVScrollbarMode(ScrollbarAlwaysOn); // This causes a vertical scrollbar to appear.
// Set the initial hMode to AlwaysOff if we're auto.
if (hMode == ScrollbarAuto)
ScrollView::setHScrollbarMode(ScrollbarAlwaysOff); // This causes a horizontal scrollbar to disappear.
}

if (hMode == vMode)
ScrollView::setScrollbarsMode(hMode);
else {
ScrollView::setHScrollbarMode(hMode);
ScrollView::setVScrollbarMode(vMode);
}

suppressScrollbars(false, true);
}

IntSize oldSize = m_size;

m_size = IntSize(visibleWidth(), visibleHeight());

if (oldSize != m_size)
d->m_doFullRepaint = true;
}
//root往往为RenderView对象
RenderLayer* layer = root->enclosingLayer();
......................................
d->m_midLayout = true;
beginDeferredRepaints();
root->layout();
endDeferredRepaints();
d->m_midLayout = false;
.......................................
d->m_layoutSchedulingEnabled = true;

if (!subtree && !static_cast(root)->printing())
adjustViewSize();

// Now update the positions of all layers.
//对当前Render树布局完后,设置RenderLayer树的布局信息,其中d->m_doFullRepaint描述是否需要发起渲染处理。
beginDeferredRepaints();
layer->updateLayerPositions(d->m_doFullRepaint);///it's very important for RenderLayer to set m_x/m_y/m_width/m_height
endDeferredRepaints();
//因为在布局的过程中,可能进一步获得网页数据,则需要继续布局处理。
if (needsLayout()) {
// Post-layout widget updates or an event handler made us need layout again.
// Lay out again, but this time defer widget updates and event dispatch until after
// we return.
........................
layout();
}
}

FrameView::layout方法,简单的说来就是发起对Render树中的每一个节点按照从父节点到子节点的方式进行x、y、width、height计算,当每一个树节点的位置及大小确定之后就可以进行后面的渲染。

FrameView::layout往往会调用Render树根的layout方法即RenderView::layout。

二、RenderView::layout方法
void RenderView::layout()
{
if (printing())
m_minPrefWidth = m_maxPrefWidth = m_width;

// Use calcWidth/Height to get the new width/height, since this will take the full page zoom factor into account.
bool relayoutChildren = !printing() && (!m_frameView || m_width != viewWidth() || m_height != viewHeight());
if (relayoutChildren)
setChildNeedsLayout(true, false);

ASSERT(!m_layoutState);
LayoutState state;
// FIXME: May be better to push a clip and avoid issuing offscreen repaints.
state.m_clipped = false;
m_layoutState = &state;

if (needsLayout())
RenderBlock::layout();//类继承的好处,直接调用父类的layout

// Ensure that docWidth() >= width() and docHeight() >= height().
setOverflowWidth(m_width);
setOverflowHeight(m_height);

setOverflowWidth(docWidth());
setOverflowHeight(docHeight());

ASSERT(m_layoutStateDisableCount == 0);
ASSERT(m_layoutState == &state);
m_layoutState = 0;
setNeedsLayout(false);
}

void RenderBlock::layout()
{
// Update our first letter info now.
updateFirstLetter();
// Table cells call layoutBlock directly, so don't add any logic here. Put code into
// layoutBlock().
layoutBlock(false);

// It's safe to check for control clip here, since controls can never be table cells.
if (hasControlClip()) {
// Because of the lightweight clip, there can never be any overflow from children.
m_overflowWidth = m_width;
m_overflowHeight = m_height;
m_overflowLeft = 0;
m_overflowTop = 0;
}
}

三、RenderBlock::layoutBlock方法
void RenderBlock::layoutBlock(bool relayoutChildren)
{
......................................................
calcWidth();//先计算宽度
calcColumnWidth();

m_overflowWidth = m_width;
m_overflowLeft = 0;
if (oldWidth != m_width || oldColumnWidth != desiredColumnWidth())
relayoutChildren = true;
clearFloats();

int previousHeight = m_height;
m_height = 0;
m_overflowHeight = 0;
..................................................
//这就是在布局基本概念中提到的Block-level元素的子节点要么是Block-level元素要么为Inline-level元素。
if (childrenInline())
layoutInlineChildren(relayoutChildren, repaintTop, repaintBottom);
else
layoutBlockChildren(relayoutChildren, maxFloatBottom);

// Expand our intrinsic height to encompass floats.
int toAdd = borderBottom() + paddingBottom() + horizontalScrollbarHeight();
if (floatBottom() > (m_height - toAdd) && (isInlineBlockOrInlineTable() || isFloatingOrPositioned() || hasOverflowClip() ||
(parent() && parent()->isFlexibleBox() || m_hasColumns)))
m_height = floatBottom() + toAdd;

// Now lay out our columns within this intrinsic height, since they can slightly affect the intrinsic height as
// we adjust for clean column breaks.
int singleColumnBottom = layoutColumns();

// Calculate our new height.//布局完子节点后确定父节点高度
int oldHeight = m_height;
calcHeight();
....................................................
if (previousHeight != m_height)
relayoutChildren = true;
...................................................
if ((isCell || isInline() || isFloatingOrPositioned() || isRoot()) && !hasOverflowClip() && !hasControlClip())
addVisualOverflow(floatRect());
//另外布局属性为Fixed和absolute的元素
layoutPositionedObjects(relayoutChildren || isRoot());

// Always ensure our overflow width/height are at least as large as our width/height.
m_overflowWidth = max(m_overflowWidth, m_width);
m_overflowHeight = max(m_overflowHeight, m_height);

......................................................

// Update our scroll information if we're overflow:auto/scroll/hidden now that we know if
// we overflow or not.
if (hasOverflowClip())
m_layer->updateScrollInfoAfterLayout();//also is important..

// Repaint with our new bounds if they are different from our old bounds.
bool didFullRepaint = false;
//布局后根据条件确定是否发起渲染处理
if (checkForRepaint)
didFullRepaint = repaintAfterLayoutIfNeeded(oldBounds, oldOutlineBox);
..........................................................
// Make sure the rect is still non-empty after intersecting for overflow above
if (!repaintRect.isEmpty()) {
repaintRectangle(repaintRect); // We need to do a partial repaint of our content.
if (hasReflection())
layer()->reflection()->repaintRectangle(repaintRect);
}
}
setNeedsLayout(false);
}

四、RenderBlock::layoutBlockChildren方法
void RenderBlock::layoutBlockChildren(bool relayoutChildren, int& maxFloatBottom)
{
int top = borderTop() + paddingTop();
int bottom = borderBottom() + paddingBottom() + horizontalScrollbarHeight();

m_height = m_overflowHeight = top;
//遍历子节点
RenderObject* child = firstChild();
while (child) {
if (legend == child) {
child = child->nextSibling();
continue; // Skip the legend, since it has already been positioned up in the fieldset's border.
}
.........................................
// Handle the four types of special elements first. These include positioned content, floating content, compacts and
// run-ins. When we encounter these four types of objects, we don't actually lay them out as normal flow blocks.
bool handled = false;
RenderObject* next = handleSpecialChild(child, marginInfo, compactInfo, handled);
if (handled) { child = next; continue; }

// The child is a normal flow object. Compute its vertical margins now.
child->calcVerticalMargins();
// Do not allow a collapse if the margin top collapse style is set to SEPARATE.

// Try to guess our correct y position. In most cases this guess will
// be correct. Only if we're wrong (when we compute the real y position)
// will we have to potentially relayout.
int yPosEstimate = estimateVerticalPosition(child, marginInfo);

// Cache our old rect so that we can dirty the proper repaint rects if the child moves.
IntRect oldRect(child->xPos(), child->yPos() , child->width(), child->height());

// Go ahead and position the child as though it didn't collapse with the top.
view()->addLayoutDelta(IntSize(0, child->yPos() - yPosEstimate));
//先确定x、y。
child->setPos(child->xPos(), yPosEstimate);
..........................................
bool childNeededLayout = child->needsLayout();
if (childNeededLayout)
child->layout();//子节点进行布局处理
...................................................
// Now place the child in the correct horizontal position
determineHorizontalPosition(child);
// Update our height now that the child has been placed in the correct position.
m_height += child->height();
if (child->style()->marginBottomCollapse() == MSEPARATE) {
m_height += child->marginBottom();
marginInfo.clearMargin();
}
.........................................................
// Update our overflow in case the child spills out the block.
..........................................................
child = child->nextSibling();
}
// Now do the handling of the bottom of the block, adding in our bottom border/padding and
// determining the correct collapsed bottom margin information.
handleBottomOfBlock(top, bottom, marginInfo);
}

五、RenderBlock::layoutInlineChildren方法
这个方法相当复杂,其作用就是布局文字、图像等,对文字行高确定、断行等处理,同时还包括 文字从左到右或从右到左的布局处理。具体可以参考bidi.cpp中的源码实现。

六、调用FrameView::layout方法的时机
由于从Web服务器获取的网页数据不可能一次性完成,往往需要边获取数据,边布局,然后渲染,这样才可能获得良好的用户感受。

所以一旦获得主要数据如css数据及body等标签后,就可以开始布局,布局完后会根据当前条件决定是否将布局的数据渲染出来,或者继续布局处理后来获取的数据,这样也增加了布局处理过程的复杂度。

而调用layout方法的时机也至关重要,因为layout本身就可能需要花费大量的时间如layoutBlockChildren、layoutInlineChildren等处理,其往往与网页的内容有关,而网页的内容却由网页开发者来确定,对浏览器来讲是千变万化的,这就对layout方法的实现及调用时机提出更高的要求,同时确定了其复杂性。

调用layout的时机主要有获得一定DOM文档数据后调用Document::updateLayout()、需要重新使用CSS数据时调用Document::recalcStyle()、改变窗口大小后调用Frame::forceLayout()等来实现。。。

七、总结
其实WebKit涉及网页布局方面的layout方法蛮复杂的,如其他RenderObject子类也会根据自身情况重载实现layout,还有对float、fixed、absolute、inline元素等的处理,但其主要逻辑就象上面所提,这里只是汇总一下主要流程及概念,针对每一个具体标签或RenderObject的布局实现则需要更深一步的了解,希望大家能对了解WebKit的网页布局过程有一个清晰而准确的认识。。

八、参考资源
The WebKit Open Source Project

2008年12月18日星期四

WebKit网页布局实现之主要数据结构篇之二

上一篇WebKit网页布局实现之主要数据结构篇中,我们对WebKit网页布局所涉及的主要数据结构有了初步的了解,认识到Render树的构成及CSS属性的描述RenderStyle等,对主要 RenderObject基类及子类有了一定的了解,但在构成Render树时所涉及的一些关键方法还未具体描述,同时还有其他一些为布局及渲染所准备的数据结构也需要有更深入的理解,下面将进一步的了解相关数据结构及相关方法。

一、继续Render树的构成
1、子类RenderButton
RenderButton代表html中input标签type为button时对应的Render树节点,它直接继承自RenderFlexibleBox;
RenderFlexibleBox代表能按居中、左对齐、右对齐等水平或垂直方向布局子节点的树节点;
RenderButton主要数据成员

图一

其中m_buttonText为button上的文字对应的树节点,而m_inner为添加m_buttonText时创建的匿名对象,以便于居中等处理等。这些成员的创建来自于方法updateFromElement;
void RenderButton::updateFromElement()
{
// If we're an input element, we may need to change our button text.
if (element()->hasTagName(inputTag)) {
HTMLInputElement* input = static_cast(element());
String value = input->valueWithDefault();
setText(value);
}
}
void RenderButton::setText(const String& str)
{
..........................
m_buttonText = new (renderArena()) RenderTextFragment(document(), str.impl());
m_buttonText->setStyle(style());
addChild(m_buttonText);
......................
}
void RenderButton::addChild(RenderObject* newChild, RenderObject* beforeChild)
{
if (!m_inner) {
// Create an anonymous block.
m_inner = createAnonymousBlock();
m_inner->style()->setBoxFlex(1.0f);
RenderFlexibleBox::addChild(m_inner);
}
m_inner->addChild(newChild, beforeChild);
}
在缺省的html.css中对应button的css属性如下:
input[type="button"] {
-webkit-appearance: push-button;
white-space: pre
}
input[type="button"]{
-webkit-box-align: center;
text-align: center;
cursor: default;
color: ButtonText;
padding: 2px 6px 3px 6px;
border: 2px outset ButtonFace;
background-color: ButtonFace;
-webkit-box-sizing: border-box
}
这 些css属性通过CSSStyleSelector::applyProperty方法来设定其成员m_RenderStyle对应的值,其中包含 m_style->setAppearance(PushButtonAppearance);尤其值得关注,其初步决定了button是如何画出 来的。。

2、子类RenderTextControl

RenderTextControl代表html中input标签type为text或textarea标签对应的Render树节点,它直接继承自RenderBlock;
RenderTextControl主要数据成员

图二
其中成员m_multiLine以描述是textarea或text input;m_innerText为其中包括的文字对应的树节点;当作搜索按钮时
m_cancelButton/m_resultsButton为对应的树节点;这些成员的创建来自于方法updateFromElement;
void RenderTextControl::updateFromElement()
{
HTMLFormControlElement* element = static_cast(node());

createSubtreeIfNeeded();

...................................

m_innerText->renderer()->style()->setUserModify(element->isReadOnlyControl() || element->disabled() ? READ_ONLY : READ_WRITE_PLAINTEXT_ONLY);

if ((!element->valueMatchesRenderer() || m_multiLine) && !m_placeholderVisible) {
String value;
if (m_multiLine)
value = static_cast(element)->value();
else
value = static_cast(element)->value();
if (value.isNull())
value = "";
else
value = value.replace('\\', backslashAsCurrencySymbol());
if (value != text() || !m_innerText->hasChildNodes()) {
if (value != text()) {
if (Frame* frame = document()->frame())
frame->editor()->clearUndoRedoOperations();
}
ExceptionCode ec = 0;
m_innerText->setInnerText(value, ec);
if (value.endsWith("\n") || value.endsWith("\r"))
m_innerText->appendChild(new HTMLBRElement(document()), ec);
m_dirty = false;
m_userEdited = false;
}
....................................
}
....................................
}
void RenderTextControl::createSubtreeIfNeeded()
{
............................................
if (!m_innerText) {
m_innerText = new HTMLTextFieldInnerTextElement(document(), m_innerBlock ? 0 : node());
RenderTextControlInnerBlock* textBlockRenderer = new (renderArena()) RenderTextControlInnerBlock(m_innerText.get());
m_innerText->setRenderer(textBlockRenderer);
m_innerText->setAttached();
m_innerText->setInDocument(true);

RenderStyle* parentStyle = style();
if (m_innerBlock)
parentStyle = m_innerBlock->renderer()->style();
RenderStyle* textBlockStyle = createInnerTextStyle(parentStyle);
textBlockRenderer->setStyle(textBlockStyle);

// Add text block renderer to Render tree
if (m_innerBlock) {
m_innerBlock->renderer()->addChild(textBlockRenderer);
ExceptionCode ec = 0;
// Add text block to the DOM
m_innerBlock->appendChild(m_innerText, ec);
} else
RenderBlock::addChild(textBlockRenderer);
}
.................................
}
在缺省的html.css中对应标签的css属性如下:
textarea {
-webkit-appearance: textarea;
background-color: white;
border: 1px solid;
-webkit-rtl-ordering: logical;
-webkit-user-select: text;
-webkit-box-orient: vertical;
resize: auto;
cursor: auto;
}
input, input[type="password"], input[type="search"], isindex {
-webkit-appearance: textfield;
padding: 1px;
background-color: white;
border: 2px inset;
-webkit-rtl-ordering: logical;
-webkit-user-select: text;
cursor: auto;
}
其中-webkit-appearance属性分别为textarea、textfield;

3、子类RenderListBox
RenderListBox代表html中select标签对应的Render树节点,它直接继承自RenderBlock;
RenderListBox主要数据成员

图三
其相关成员同样通过方法updateFromElement来设置初值;
在缺省的html.css中对应标签的css属性如下:
select {
-webkit-appearance: menulist;
-webkit-box-sizing: border-box;
-webkit-box-align: center;
border: 1px solid;
..............................................................
}

4、子类RenderTheme
RenderTheme在html标签中没有对应的页面元素,其作用主要用于如何渲染按钮、输入框、列表框等,其实现往往有一定平台相关性。
RenderTheme主要数据成员及方法

图四
RenderTheme 往往提供一个接口,不同的图形库对其中不同的方法如paintbutton、paintcheckbox、painttextfield等进行了实现;其中 paint方法则根据appearance属性的不同以分别调用不同的paintxxx方法,其示例代码如下:
bool RenderTheme::paint(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
{
........................................
if (paintInfo.context->paintingDisabled())
return false;
// Call the appropriate paint method based off the appearance value.
switch (o->style()->appearance()) {
case CheckboxAppearance:
return paintCheckbox(o, paintInfo, r);
case RadioAppearance:
return paintRadio(o, paintInfo, r);
case PushButtonAppearance:
case SquareButtonAppearance:
case DefaultButtonAppearance:
case ButtonAppearance:
return paintButton(o, paintInfo, r);
case MenulistAppearance:
return paintMenuList(o, paintInfo, r);
break;
....................................................
default:
break;
}
return true; // We don't support the appearance, so let the normal background/border paint.
}
其中的appearance就是上面RenderButton、RenderTextControl、RenderListBox中提到的属性,至于 html中涉及到的类似标签或属性如radio、checkbox等等,其相关代码基本类似,至于不同的平台如Qt、Gtk、Win、Mac等究竟是如何画按钮、下拉框、列表框、多选框、单选框等等,则需详细参考RenderThemeQt/RenderThemeGtk/RenderThemeWin /RenderThemeMac等中的实现。

通过上述的了解我们应该对html中form标签内的输入框、按钮、下拉框等实现有了一定的认识。

5、子类RenderTable、RenderTableRow、RenderTableCol、RenderTableCell
这一组子类主要对应与html中table标签相关的树节点;
Table标签相关类主要数据成员

图五
RenderTable主要通过addChild方法来维护对RenderTableCell、RenderTableCol、RenderTableRow等对象的管理及维护;
void RenderTableCell::updateFromElement()
{
Node* node = element();
if (node && (node->hasTagName(tdTag) || node->hasTagName(thTag))) {
HTMLTableCellElement* tc = static_cast(node);
int oldRSpan = m_rowSpan;
int oldCSpan = m_columnSpan;

m_columnSpan = tc->colSpan();
m_rowSpan = tc->rowSpan();
if ((oldRSpan != m_rowSpan || oldCSpan != m_columnSpan) && style() && parent()) {
setNeedsLayoutAndPrefWidthsRecalc();
if (section())
section()->setNeedsCellRecalc();
}
}
}

void RenderTableCol::updateFromElement()
{
int oldSpan = m_span;
Node* node = element();
if (node && (node->hasTagName(colTag) || node->hasTagName(colgroupTag))) {
HTMLTableColElement* tc = static_cast(node);
m_span = tc->span();
} else
m_span = !(style() && style()->display() == TABLE_COLUMN_GROUP);
if (m_span != oldSpan && style() && parent())
setNeedsLayoutAndPrefWidthsRecalc();
}
RenderTableRow通过方法layout和paint方法来布局管理RenderTableCell对象;
这一组子类主要实现人们熟知的表格布局,具体的实现可具体参考相关类实现;

6、子类RenderFrame
RenderFrame代表html中标签frame对应的Render树节点,其继承关系如下:
RenderFrame类继承关系


图六

其中属性m_widget、m_view代表frame对应的widget及frameview,通过其中setwidget方法来设置m_widget属性,m_view属性则在对象创建的时候设置为当前document对应的frameview。

其中html中的embed/object插件标签对应的Render树节点为RenderPartObject对象。

7、构建Render树
从上一篇中我们了解到构建Render树的基本实现流程如下:void Element::attach()=>createRendererIfNeeded()=>createRenderer;以前我们着重了解过createRenderer,现在我们回头再来看看createRendererIfNeeded(),以更深入的了解是如何构建Render树。
void Node::createRendererIfNeeded()
{
if (!document()->shouldCreateRenderers())
return;
Node *parent = parentNode();
RenderObject *parentRenderer = parent->renderer();
if (parentRenderer && parentRenderer->canHaveChildren()
#if ENABLE(SVG)
&& parent->childShouldCreateRenderer(this)
#endif
) {
RenderStyle* style = styleForRenderer(parentRenderer);
if (rendererIsNeeded(style)) {
if (RenderObject* r = createRenderer(document()->renderArena(), style)) {
if (!parentRenderer->isChildAllowed(r, style))
r->destroy();
else {
setRenderer(r);
renderer()->setAnimatableStyle(style);
parentRenderer->addChild(renderer(), nextRenderer());
}
}
}
style->deref(document()->renderArena());
}
}

RenderStyle* Element::styleForRenderer(RenderObject* parentRenderer)
{
return document()->styleSelector()->styleForElement(this);
}

void RenderObject::setAnimatableStyle(RenderStyle* style)
{
if (!isText() && m_style && style)
style = animation()->updateImplicitAnimations(this, style);

setStyle(style);
}

从createRendererIfNeeded中我们可以了解到创建完RenderObject子类对象后,会为其设置RenderStyle属性,然后在该DOM Node的父节点对应的RenderObject中添加刚新建的RenderObject,这样以构建Render树。

但是在setStyle的过程中可能会调用createAnonymousFlow或createAnonymousBlock来创建匿名对象,其中RenderBox的setStyle方法如下:
void RenderBox::setStyle(RenderStyle* newStyle)
{
bool wasFloating = isFloating();
bool hadOverflowClip = hasOverflowClip();
RenderStyle* oldStyle = style();
if (oldStyle)
oldStyle->ref();

RenderObject::setStyle(newStyle);
....................................................................
setInline(newStyle->isDisplayInlineType());

switch (newStyle->position()) {
case AbsolutePosition:
case FixedPosition:
setPositioned(true);
break;
default:
setPositioned(false);

if (newStyle->isFloating())
setFloating(true);

if (newStyle->position() == RelativePosition)
setRelPositioned(true);
}

// We also handle and , whose overflow applies to the viewport.
if (!isRoot() && (!isBody() || !document()->isHTMLDocument()) && (isRenderBlock() || isTableRow() || isTableSection())) {
// Check for overflow clip.
if (newStyle->overflowX() != OVISIBLE) {
if (!hadOverflowClip)
// Erase the overflow
repaint();
setHasOverflowClip();
}
}
..............................................
if (requiresLayer()) {
if (!m_layer) {
if (wasFloating && isFloating())
setChildNeedsLayout(true);
m_layer = new (renderArena()) RenderLayer(this);
setHasLayer(true);
m_layer->insertOnlyThisLayer();
if (parent() && !needsLayout() && containingBlock())
m_layer->updateLayerPositions();
}
} else if (m_layer && !isRoot() && !isRenderView()) {
.......................................................
}
..................................................................
}

bool RenderObject::requiresLayer()
{
return isRoot() || isPositioned() || isRelPositioned() || isTransparent() || hasOverflowClip() || hasTransform() || hasMask() || hasReflection();
}

bool
RenderObject::isRoot() const { return document()->documentElement() == node(); }
bool
RenderObject::isPositioned() const { return m_positioned; } // absolute or fixed positioning
bool
RenderObject::isRelPositioned() const { return m_relPositioned; } // relative positioning
bool
RenderObject::isTransparent() const { return style()->opacity() <>RenderObject::hasOverflowClip() const { return m_hasOverflowClip; }
bool
RenderObject::hasTransform() const { return m_hasTransform; }
bool
RenderObject::hasMask() const { return style() && style()->hasMask(); }
bool
RenderObject::hasReflection() const { return m_hasReflection; }

通过上面的了解我们知道在setStyle时符合一定条件的RenderObject会创建RenderLayer对象,那么究竟什么是RenderLayer类,其有什么作用,下面作初步的介绍。

二、RenderLayer树
1、类RenderLayer
RenderLayer类其实是一个非常复杂并且很重要的类,其主要数据成员如下:
RenderLayer类主要数据成员


图六
RenderLayer类主要与处理分层布局、渲染页面元素等相关如处理z-index、opacity等,只有符合一个条件的RenderObject才会创建RenderLayer对象,并且将这些RenderLayer对象组织成一颗树。

2、构建RenderLayer树
通过方法
insertOnlyThisLayer来组织这颗RenderLayer树。
void RenderLayer::insertOnlyThisLayer()
{
if (!m_parent && renderer()->parent()) {
// We need to connect ourselves when our renderer() has a parent.
// Find our enclosingLayer and add ourselves.
RenderLayer* parentLayer = renderer()->parent()->enclosingLayer();
RenderLayer* beforeChild = parentLayer->reflectionLayer() != this ? renderer()->parent()->findNextLayer(parentLayer, renderer()) : 0;
if (parentLayer)
parentLayer->addChild(this, beforeChild);
}

// Remove all descendant layers from the hierarchy and add them to the new position.
for (RenderObject* curr = renderer()->firstChild(); curr; curr = curr->nextSibling())
curr->moveLayers(m_parent, this);

// Clear out all the clip rects.
clearClipRects();
}

void RenderLayer::addChild(RenderLayer* child, RenderLayer* beforeChild)
{
RenderLayer* prevSibling = beforeChild ? beforeChild->previousSibling() : lastChild();
if (prevSibling) {
child->setPreviousSibling(prevSibling); prevSibling->setNextSibling(child);
} else
setFirstChild(child);

if (beforeChild) { beforeChild->setPreviousSibling(child); child->setNextSibling(beforeChild); } else setLastChild(child); child->setParent(this);

if (child->isOverflowOnly())
dirtyOverflowList();

if (!child->isOverflowOnly() || child->firstChild()) {
// Dirty the z-order list in which we are contained. The stackingContext() can be null in the
// case where we're building up generated content layers. This is ok, since the lists will start
// off dirty in that case anyway.
RenderLayer* stackingContext = child->stackingContext();
if (stackingContext)
stackingContext->dirtyZOrderLists();
}

child->updateVisibilityStatus();
if (child->m_hasVisibleContent || child->m_hasVisibleDescendant)
childVisibilityChanged(true);
}

static void addLayers(RenderObject* obj, RenderLayer* parentLayer, RenderObject*& newObject,
RenderLayer*& beforeChild)
{
if (obj->hasLayer()) {
if (!beforeChild && newObject) {
// We need to figure out the layer that follows newObject. We only do
// this the first time we find a child layer, and then we update the
// pointer values for newObject and beforeChild used by everyone else.
beforeChild = newObject->parent()->findNextLayer(parentLayer, newObject);
newObject = 0;
}
parentLayer->addChild(obj->layer(), beforeChild);
return;
}

for (RenderObject* curr = obj->firstChild(); curr; curr = curr->nextSibling())
addLayers(curr, parentLayer, newObject, beforeChild);
}

void RenderObject::addLayers(RenderLayer* parentLayer, RenderObject* newObject)
{
if (!parentLayer)
return;

RenderObject* object = newObject;
RenderLayer* beforeChild = 0;
WebCore::addLayers(this, parentLayer, object, beforeChild);
}

void RenderObject::removeLayers(RenderLayer* parentLayer)
{
if (!parentLayer)
return;

if (hasLayer()) {
parentLayer->removeChild(layer());
return;
}

for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling())
curr->removeLayers(parentLayer);
}

void RenderObject::moveLayers(RenderLayer* oldParent, RenderLayer* newParent)
{
if (!newParent)
return;

if (hasLayer()) {
if (oldParent)
oldParent->removeChild(layer());
newParent->addChild(layer());
return;
}

for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling())
curr->moveLayers(oldParent, newParent);
}

通过上面一组方法我们可以了解到拥有RenderLayer对象的RenderObject对象,按照Render树中最近的原则将含有的RenderLayer对象依Render树对应的父子关系组织RenderLayer树,RenderLayer对象的存在是依附于RenderObject对象而存在。

RenderView对象拥有对应的RenderLayer对象,同时其作为RenderLayer树根。

3、RenderLayer树与Render树的关系
通过RenderContainer::addChild方法回过头再来具体看看Render树自身的构成。
void RenderContainer::addChild(RenderObject* newChild, RenderObject* beforeChild)
{
bool needsTable = false;
//检查是否为Table的情况
if (needsTable) {
......................................................
} else {
// just add it...
insertChildNode(newChild, beforeChild);
}
.........................................................
}

void RenderContainer::insertChildNode(RenderObject* child, RenderObject* beforeChild, bool fullInsert)
{
if (!beforeChild) {
appendChildNode(child);
return;
}

while (beforeChild->parent() != this && beforeChild->parent()->isAnonymousBlock())
beforeChild = beforeChild->parent();

if (beforeChild == m_firstChild)
m_firstChild = child;

RenderObject* prev = beforeChild->previousSibling();
child->setNextSibling(beforeChild);
beforeChild->setPreviousSibling(child);
if(prev) prev->setNextSibling(child);
child->setPreviousSibling(prev);

child->setParent(this);

if (fullInsert) {
// Keep our layer hierarchy updated. Optimize for the common case where we don't have any children
// and don't have a layer attached to ourselves.
RenderLayer* layer = 0;
if (child->firstChild() || child->hasLayer()) {
layer = enclosingLayer(); child->addLayers(layer, child);
}

// if the new child is visible but this object was not, tell the layer it has some visible content
// that needs to be drawn and layer visibility optimization can't be used
if (style()->visibility() != VISIBLE && child->style()->visibility() == VISIBLE && !child->hasLayer()) {
if (!layer)
layer = enclosingLayer();
if (layer)
layer->setHasVisibleContent(true);
}
...........................................................
}

child->setNeedsLayoutAndPrefWidthsRecalc();
if (!normalChildNeedsLayout())
setChildNeedsLayout(true); // We may supply the static position for an absolute positioned child.
.................................................................
}
通过上面代码的了解我们知道通过addChild不仅维护Render树的构成,同时会将拥有的RenderLayer树构建起来。

4、RenderLayer树的作用
RenderLayer树的构建为渲染阶段处理z-index、opacity、overflow、scrollbar等打下一定的基础,在我们了解渲染的处理过程时我们再来深入的了解。

在这里我们初步的了解到在构建Render树的同时会维护一颗RenderLayer树,为分层布局、渲染作准备。


三、总结
其 实WebKit涉及网页布局方面的数据结构还有关于SVG方面的,但通过上面的理解,如果对SVG感兴趣的话,应该对理解SVG有一定的参考作用。 当然数据结构方面还有相当多的内容未提及,这里只是列出一些关键类或结构,以便有个整体的抽象认识,希望能对了解WebKit的网页布局渲染有一定的基础性作用。

四、参考资源
The WebKit Open Source Project