WebKit網頁佈局實現之主要架構篇

horky發表於2012-08-06

轉自: http://www.fanzehua.com/blog/15.html

在有了對CSS網頁佈局標準及相關概念的認識之後,我們可以更加深入的理解WebKit究竟是如何實現其網頁佈局,同時實現對CSS佈局標準的支援。

畢竟標準歸標準,要高效的實現這些標準,不同的實現肯定有其不同的實現方式,就像不同的Web伺服器對HTTP協議標準的實現有所不同一樣,當然不同的實現也會增加一些自身特有的屬性。

下面我們從資料結構的角度來了解WebKit中為實現網頁佈局所設計的主要類結構及其主要方法。

一、Render樹的構成

在我們編寫網頁及使用JS的時候,大概都知道DOM樹及其主要構成,瞭解到DOM樹的構建其實質是對一個html或xml檔案的內容採取樹結構的方式來組織及描述,不同的標籤及其在文件中的位置決定了其在整顆DOM樹的地位及屬性,針對具體DOM樹的構成及不同樹節點的描述,可以參考有關DOM的相關標準等,以後有機會我們也會單獨來了解。


也許對於Render樹大家就不那麼瞭解了,簡單的說來,它是對DOM樹更進一步的描述,其描述的內容主要與佈局渲染等CSS相關屬性如left、top、width、height、color、font等有關,因為不同的DOM樹結點可能會有不同的佈局渲染屬性,甚至佈局時會按照標準動態生成一些匿名節點,所以為了更加方便的描述佈局及渲染,WebKit核心又生成一顆Render樹來描述DOM樹的佈局渲染等特性,當然DOM樹與Render樹不是一一對應,但可以相互關聯,下面分別描述其主要節點:

1、基類RenderObject

RenderObject作為所有Render樹節點的基類,完全類似與DOM樹中的Node基類,它是構成Render樹的基礎,作用非比尋常,其中包含了構成Render樹所可能涉及到的一些基本屬性及方法,內容相當多,其主要資料成員及方法分別如下:

RenderObject主要資料成員

 

圖一

其中成員m_parent、m_previous、m_next為構建Render樹設定好關聯基礎;
m_Node則為DOM樹中對應的節點;
m_style成員則描述該節點對應的各種CSS基本屬性資料,下面會單獨介紹;
至於其他的諸如m_positioned、m_isText、m_inline、m_floating、m_replaced等則描述其特性,就像CSS標準對不同元素的屬性分類定義一樣,從字面上我們就可以上一節WebKit網頁佈局實現之基本概念及標準篇中可以找到它們這麼定義的蹤影。

成員m_needsPositionedMovementLayout、m_normalChildNeedsLayout、m_posChildNeedsLayout、m_needsLayout等主要用來描述該RenderObject是否確實需要重新佈局;
當一個新的RenderObject物件插入到Render樹的時候,它會設定其m_needsLayout屬性為true,同時會根據該RenderObject物件在祖先RenderObject看來是一個positioned(擁有positiong:absolute或fixed屬性)狀態的孩子,如是則將相應祖先RenderObject物件的屬性m_posChildNeedsLayout設定為true;

如果是一個in-flow(positon:static或relative)狀態的孩子,則將相應祖先RenderObject物件的屬性m_normalChildNeedsLayout設定為true;

主要方法:
//與是否需要layout相關
bool needsLayout() const { return m_needsLayout || m_normalChildNeedsLayout ||m_posChildNeedsLayout; }
bool selfNeedsLayout() const { return m_needsLayout; }
bool posChildNeedsLayout() const { return m_posChildNeedsLayout; }
bool normalChildNeedsLayout() const { return m_normalChildNeedsLayout; }

//與基本屬性相關
bool isFloating() const { return m_floating; }
bool isPositioned() const { return m_positioned; } // absolute or fixed positioning
bool isRelPositioned() const { return m_relPositioned; } // relative positioning
bool isText() const { return m_isText; }
bool isInline() const { return m_inline; } // inline object
bool isCompact() const { return style()->display() == COMPACT; } // compact object
bool isRunIn() const { return style()->display() == RUN_IN; } // run-in object
bool isDragging() const { return m_isDragging; }
bool isReplaced() const { return m_replaced; } // a “replaced” element (see CSS)

//與外部DOM關聯相關
RenderView* view() const;
// don`t even think about making this method virtual!
Node* element() const { return m_isAnonymous ? 0 : m_node; }
Document* document() const { return m_node->document(); }
void setNode(Node* node) { m_node = node; }
Node* node() const { return m_node; }

// RenderObject tree manipulation
//////////////////////////////////////////
virtual bool canHaveChildren() const;
virtual bool isChildAllowed(RenderObject*, RenderStyle*) const { return true; }
virtual void addChild(RenderObject* newChild, RenderObject* beforeChild = 0);
virtual void removeChild(RenderObject*);
virtual bool createsAnonymousWrapper() const { return false; }

// raw tree manipulation
virtual RenderObject* removeChildNode(RenderObject*, bool fullRemove = true);
virtual void appendChildNode(RenderObject*, bool fullAppend = true);
virtual void insertChildNode(RenderObject* child, RenderObject* before, bool fullInsert = true);
// Designed for speed. Don`t waste time doing a bunch of work like layer updating and repainting when we know that our
// change in parentage is not going to affect anything.
virtual void moveChildNode(RenderObject*);

virtual void paint(PaintInfo&, int tx, int ty);

/*
* This function should cause the Element to calculate its
* width and height and the layout of its content
*
* when the Element calls setNeedsLayout(false), layout() is no
* longer called during relayouts, as long as there is no
* style sheet change. When that occurs, m_needsLayout will be
* set to true and the Element receives layout() calls
* again.
*/
virtual void layout() = 0;

其中很多方法如paint()、layout()等是虛擬的,不同的子類可以過載它;

其中方法container() 、containingBlock()、paint()、layout()很值得大家深入研究;

總的說來RenderObject基類定義一些通用屬性、方法,以便維護、佈局、渲染Render樹。

2、子類RenderBox
RenderBox代表描述CSS標準中的Box Model,它繼承自RenderObject;

RenderBox主要資料成員
圖二

其主要過載了部分繼承而來的方法。

3、子類RenderContainer
RenderContainer類用來描述可以擁有子RenderObject成員的容器類,它繼承自RenderBox;

RenderContainer主要資料成員
圖三

其主要過載了RenderObject提供的維護Render樹新增、刪除樹節點等方面的方法。

4、子類RenderFlow
RenderFlow主要用來描述CSS標準中提到的能進行inline-flow、block-flow相關處理的Render樹結點,它繼承自RenderContainer;

RenderFlow主要資料成員
圖四

其主要方法包括在flow的過程中建立、關聯匿名物件等;

5、子類RenderBlock
RenderBlock代表CSS標準中的block-level元素,它繼承自RenderFlow;

RenderBlock主要資料成員
圖五

它維護了一組由它定位的positioned樹節點,以及有關overflow方面的設定;
其主要過載了RenderObject繼承下來的layout、paint等方法;

因為html中的body、div、p等標籤對應RenderBlock類物件,其在Render樹具有非常重要的地位,其layout、paint等方法的實現,往往是WebKit整個佈局、渲染處理的發起中心,內容比較多並且複雜,以後有機會詳解。

6、子類RenderInline
RenderInline代表inline-level元素,其繼承自RenderFlow,主要過載了RenderObject關於inline-flow方面處理的方法,提供了splitFlow、splitInlines等處理自動換行的方法。

7、子類RenderText
RenderText代表對html中Text node對應的Render樹節點,它直接繼承自RenderObject;

RenderText主要資料成員
圖六

它提供關於處理文字方面如顯示文字、行高計算、整個Text node對應的寬度等;它沒有過載layout方法,因為它自身的定位往往由RenderBlock、RenderInline父物件來處理;

8、子類RenderImage
RenderImage代表html中img標籤對應的樹節點,它繼承自RenderBox;

RenderImage繼承關係及主要資料成員
圖七

其主要提供關於圖片顯示、大小設定等方面的處理,其中paintReplaced方法將其圖片顯示出來;

9、子類RenderView
RenderView對應整個html文件物件的樹節點,可看成是Render樹的根,它繼承自RenderBlock;

RenderView主要資料成員
圖八

其中m_frameview成員對應整個文件對應的FrameView,而m_widgets則包括了該文件可能包含的plugin外掛等對應的Render樹節點;

RenderView物件作為Render樹的根,它往往隨著Document物件的建立而建立,它的layout、paint方法的發起往往是整顆Render樹佈局、渲染處理的開始;其中也包含了對選擇處理。

10、其他
整個Render樹中涉及的樹節點型別,還有很多如RenderButton、RenderTable、RenderMedia等;並且各個類的方法及資料成員非常多,這裡只是初步列出主要的類及其主要方法,特別是可能涉及到佈局、渲染方方面的方法,以便我們能從中大致WebKit佈局、渲染所涉及的基本內容及方法。

二、CSS屬性的描述
1、RenderStyle類
RenderObject物件的m_style成員為RenderStyle類物件,它往往用來描述一個RenderObject所可能涉及的CSS屬性資料(如left、top、align、color、font等等),其資料成員往往對應於CSS中定義的所有屬性項,內容非常的龐雜,簡單的說來就是將CSS標準中的所有屬性按照一定分類定義到一個資料結構中。

2、RenderStyle類主要方法
為了獲取、設定CSS屬性所對應的值,RenderStyle類提供了所有的獲取、設定CSS屬性的方法如:

 

void setDisplay(EDisplay v) { noninherited_flags._effectiveDisplay = v; }
void setOriginalDisplay(EDisplay v) { noninherited_flags._originalDisplay = v; }
void setPosition(EPosition v) { noninherited_flags._position = v; }
void setFloating(EFloat v) { noninherited_flags._floating = v; }

void setLeft(Length v) { SET_VAR(surround,offset.left,v) }
void setRight(Length v) { SET_VAR(surround,offset.right,v) }
void setTop(Length v) { SET_VAR(surround,offset.top,v) }
void setBottom(Length v){ SET_VAR(surround,offset.bottom,v) }

void setWidth(Length v) { SET_VAR(box,width,v) }
void setHeight(Length v) { SET_VAR(box,height,v) }

等等。。。。

三、RenderObject及子類物件的生成
1、CSSParser
CSSParser類顧名思義,主要用來解析文字中各種CSS屬性,並且有效的組織在一個RenderStyle物件中。
其主要方法parseValue、applyProperty的部分程式碼示例如下:

bool CSSParser::parseValue(int propId, bool important)
{
.....................................................
case CSSPropertyFloat: 
// left | right | none | inherit + center for buggy CSS
if (id == CSSValueLeft || id == CSSValueRight ||
id == CSSValueNone || id == CSSValueCenter)
valid_primitive = true;
break;

case CSSPropertyClear: // none | left | right | both | inherit
if (id == CSSValueNone || id == CSSValueLeft ||
id == CSSValueRight|| id == CSSValueBoth)
valid_primitive = true;
break;

case CSSPropertyWebkitBoxAlign:
if (id == CSSValueStretch || id == CSSValueStart || id == CSSValueEnd ||
id == CSSValueCenter || id == CSSValueBaseline)
valid_primitive = true;
break;
.....................................................
case CSSPropertyWebkitBoxPack:
if (id == CSSValueStart || id == CSSValueEnd ||
id == CSSValueCenter || id == CSSValueJustify)
valid_primitive = true;
break; 
....................................................
}

void CSSStyleSelector::applyProperty(int id, CSSValue *value)
{
case CSSPropertyOpacity:
HANDLE_INHERIT_AND_INITIAL(opacity, Opacity)
if (!primitiveValue || primitiveValue->primitiveType() != CSSPrimitiveValue::CSS_NUMBER)
return; // Error case.
// Clamp opacity to the range 0-1
m_style->setOpacity(min(1.0f, max(0.0f, primitiveValue->getFloatValue())));
return;
case CSSPropertyWebkitBoxAlign:
{
HANDLE_INHERIT_AND_INITIAL(boxAlign, BoxAlign)
if (!primitiveValue)
return;
EBoxAlignment boxAlignment = *primitiveValue;
if (boxAlignment != BJUSTIFY)
m_style->setBoxAlign(boxAlignment);
return;
}
...................................................
}

2、CSSStyleSelector類
CSSStyleSelector類其作用是基於所有使用者的stylesheets集合為一個給定的DOM Element建立出其對應的RenderStyle物件。其主要功能由方法RenderStyle* styleForElement(Element*, RenderStyle* parentStyle = 0, bool allowSharing = true, bool resolveForRootDefault = false);來實現。

3、構建Render樹
在構建DOM樹的過程中,Dom Element物件建立完後,往往通過attach方法來建立RenderObject物件,進而構建Render樹。
其基本實現流程如下:

 

void Element::attach()=>createRendererIfNeeded()=>createRenderer;

RenderObject* Element::createRenderer(RenderArena* arena, RenderStyle* style)
{
if (document()->documentElement() == this && style->display() == NONE) {
// Ignore display: none on root elements. Force a display of block in that case.
RenderBlock* result = new (arena) RenderBlock(this);
if (result)
result->setAnimatableStyle(style);
return result;
}
return RenderObject::createObject(this, style);
}

RenderObject* RenderObject::createObject(Node* node, RenderStyle* style)
{
Document* doc = node->document();
RenderArena* arena = doc->renderArena();

const ContentData* contentData = style->contentData();
if (contentData && !contentData->m_next && contentData->m_type == CONTENT_OBJECT && doc != node) {
RenderImageGeneratedContent* image = new (arena) RenderImageGeneratedContent(node);
image->setStyle(style);
if (StyleImage* styleImage = contentData->m_content.m_image)
image->setStyleImage(styleImage);
return image;
}

RenderObject* o = 0;

switch (style->display()) {//往往在CSSStyleSelector::styleForElement或CSSStyleSelector::adjustRenderStyle時
//呼叫setDisplay()以確定其display屬性。
case NONE:
break;
case INLINE:
o = new (arena) RenderInline(node);
break;
case BLOCK:
o = new (arena) RenderBlock(node);
break;
case INLINE_BLOCK:
o = new (arena) RenderBlock(node);
break;
case LIST_ITEM:
o = new (arena) RenderListItem(node);
break;
case RUN_IN:
case COMPACT:
o = new (arena) RenderBlock(node);
break;
case TABLE:
case INLINE_TABLE:
o = new (arena) RenderTable(node);
break;
case TABLE_ROW_GROUP:
case TABLE_HEADER_GROUP:
case TABLE_FOOTER_GROUP:
o = new (arena) RenderTableSection(node);
break;
case TABLE_ROW:
o = new (arena) RenderTableRow(node);
break;
case TABLE_COLUMN_GROUP:
case TABLE_COLUMN:
o = new (arena) RenderTableCol(node);
break;
case TABLE_CELL:
o = new (arena) RenderTableCell(node);
break;
case TABLE_CAPTION:
o = new (arena) RenderBlock(node);
break;
case BOX:
case INLINE_BOX:
o = new (arena) RenderFlexibleBox(node);
break;
}
return o;
}

這樣就不同的DOM樹節點結合不同的顯示屬性,建立出不同的RenderObject子類物件,進而形成一個Render樹。

四、總結
其實WebKit涉及網頁佈局方面的資料結構遠不止這些,其中有的也比較複雜,這裡只是列出自己認為較為重要的一小部分,希望能對了解WebKit的網頁佈局渲染有一定的基礎性作用。。

五、參考資源
The
WebKit Open Source Project


相關文章