翻译2

WebCore Rendering

本文是一系列博文,旨在帮助人们理解 WebCore 的渲染系统。当我完成这些文章我将发布这些文章,它们也可以通过网站的文档区获得。

DOM Tree

一个页面将被解析成一个含有多个节点的树,其被称为文档对象模型(DOM).所有节点的基类为Node

Node可以被细分为如下几类,这些与渲染代码相关的节点类型包括

  • Document:树的根节点总是 document 。有三个 document 类,Document,HTMLDocumentSVGDocument。第一个是用于所有非 SVG 的 XML document 。第二个用于所有继承自Document的 HTML document 。第三个用于所有继承自Document的 SVG document 。
  • Elements:所有出现在HTML和XML源代码中的标签(tag)都会转化为 element 。从渲染的角度来看, element 是一个带有 tag 的节点,其可以被转化(cast)为一个特定的子类,以便于在渲染时查询必要的信息。
  • Text: element 中间出现的原始文本可以被转化为 text 节点。这些 text 节点存储了原始文本,渲染树可以查询这些节点以获得其文本信息

Render Tree

渲染的核心在于 render tree , render tree 非常类似于DOM,因为其也是一个对象组成的树,其中每个对象都对应于 document , elements 或者 text 节点。这个渲染树可以包含与 DOM 无对应的额外的对象。

render tree 的基类是RenderObject, DOM 节点的RenderObject可以通过Noderenderer()方法获得。

以下是遍历render tree时常用的方法

RenderObject *firstChild() const;
RenderObject *lastChild() const;
RenderObject *previsousSibling() const;
RenderObject *nextSibling() const;

下面是一个用于遍历所有孩子节点的循环。这是 render tree 代码中最常用的遍历方法

for(RenderObject *child=firstChild();child;child=child->nextSibling()){
  ...
}

Creating the Render Tree

Render 树的生成是通过一个在 DOM 树上的 attachment 的操作完成的。当一个 document 被解析和添加 DOM 节点时, DOM 节点上一个叫做attach的方法被调用了

void attach()

attach方法计算了DOM节点的样式信息。如果CSS的display属性为none或者其为一个display属性为none的后代,那么并不会生成renderer。节点的子类和CSS的display属性一起来决定改为节点生成什么样的renderer。

attach是一个自顶向下的递归操作。一个父节点总是在其后代生成renderer之前生成renderer。

Destroying Render Tree

当DOM节点从document中移除或document被移除(比如当tab/window 被关闭了)时renderers就销毁了。DOM节点上一个叫做detach的方法被调用以销毁renderer。

detach是一个自底向上的递归操作。后代节点的销毁总是在于父节点之前。

Accessing Style Information

attach过程中,DOM查询CSS以获得元素的样式信息。获得的样式信息存储在一个叫做RenderStyle的对象中。

每一个WebKit支持的CSS属性都可以通过该对象查询。RenderStyle是一个引用计数对象。如果一个DOM节点生成了一个renderer,其将通过setStyle方法设置该renderer的样式信息

void setStyle(RenderStyle* )

这些renderer添加了对RenderStyle的一个引用,这样就能维持样式信息直到其重新获得一个样式或者被销毁。

RenderStyle可以通过RenderObjectstyle方法来获取

RenderStyle*  style() const

The CSS Box Model

RenderObject的一个主要子类就是RenderBox。该子类代表了符合CSS盒子模型的对象。这包括含有 border,padding,margins,width,height 的任何对象。现在一些没有符合 CSS 盒子模型的对象(比如 SVG 对象)仍然是RenderBox的子类。这是一个错误,将在以后对 render tree 的代码重构中进行修复。

该图来自于CSS2.1标准,描述了CSS盒子。以下方法可以用来获取 border/margin/padding/ 的值。除非为了获得原始的样式信息这里不应该调用RenderStyle,因为RenderObject实际的计算值可能与其大有不同(特别是对于 tables ,其可以覆盖 cells 的 padding 和重叠 cells 的 borders)。

int marginTop() const;
int marginBottom() const;
int marginLeft() const;
int marginRight() const;

int paddingTop() const;
int paddingBottom() const;
int paddingLeft() const;
int paddingRight() const;

int borderTop() const;
int borderBottom() const;
int borderLeft() const;
int borderRight() const;

widthheight方法给出了盒子中包含border的宽度和高度信息

int width() const;
int height() const;

client box是盒子中除了borders和scrollbars的区域。其包含了padding。

int clientLeft() const { return borderLeft();}
int clientTop() const { return borderTop();}
int clientWidth() const;
int clientHeight() const;

术语 content box 是用来描述 CSS 盒子中除去 borders 和 padding 的区域。

IntRect contentBox() const;
int contentWidth() const { return clientWidth() - paddingLeft() - paddingRigth();}
int contentHeight() const { return clientHeight() - paddingTop() - paddingBottom(); }

当一个盒子拥有一个水平或垂直的滚动条,其被放在border和padding之间。一个滚动条的尺寸被从client width 和 client height 中除去了。滚动条不是content box 的一部分。滚动条区域的尺寸和当前滚动的位置都可以从RenderObject获得。我将在单独的章节讲述滚动。

int scrollLeft() const;
int scrollTop() const;
int scrollWidth() const;
int scrollHeight() const;

盒子也具有 x 和 y 坐标。这些位置是相对于其祖先,其用来决定盒子应该在哪摆放。这条规则有很多特殊情况,而且这也是 render tree 中最令人迷惑的地方。

int xPos() const;
int yPos() const;

Blocks and Inlines

上节我们讲述了 CSS 盒子的基本结构。本节将讨论RenderBox的各个子类和 block 和 inline 的概念。

block flow 是一个用于包含行(例如段落)或者包含其他块的框(box),其垂直排列。HTML 中的常见block flow 包括 pv

inline flow 是一个对象,其用于作为行的一部分。HTML中常见的flow element 包括 a,b,ispan

在 WebCore 中,有三个renderer类,其包含了block和inline flows。分别是RenderBlockRenderInline 和它们的父类RenderFlow

一个 inline flow 通过 display 属性可以转化为 block flow(反之亦然)。

div { display: inline }
span { display: block}

除了 block 和 inline flow,还有一种元素可以作为 block 或者 inline : 替换元素。 一个替换元素是一个元素,其并未在 CSS 中指明该如何渲染。这个对象内容该如何渲染交由元素自身决定。常见的替换元素包括images,form,iframes,plugins和applets。

一个替换元素可以作为block-level或者inline-level。当一个替换元素作为一个block,其垂直的进行排列就像其代表了自己的段落。当一个替换元素作为inline,其被视为一个段落中的一行的一部分。替换元素默认是inline。

表单控制(form control)是一个特例。它们作为替换元素,但是因为它们由引擎实现,表单实际上是作为RenderBlock的子类。因此,替换这个概念并不限制在一个单一的子类,并用RenderObject的一个比特网代表。isReplaced方法可以用来查询一个对象是否为替换元素。

bool isReplaced() const

Images,plugins,frames,applets都继承自一个子类,其实现了替换元素的行为。该子类为RenderReplaced

The Inline Block

CSS 中国最令人奇怪的是 inline-block 。Inline block 是block flow但是其做落于一行中(注:block flow强调其内部的block垂直排列,并没有强调其自身如何排列)。事实上从外部来看它们就像替换元素一样,但是从内部来看它们是block flow。CSS 的 display 属性可以用来生成 inline block。如果询问 Inline block 元素是否为替换元素,其会返回true(即调用该对象的isReplaced方法,会返回 true)。

div { display : inline-block}

Tables

HTML中的Tables默认为block-level。但是其可以利用 CSS 的 display 属性转化为 inlines。

table { display : inline-table }

从外面来看 inline-table 就像替换元素(事实上其isReplaced也返回true)。但是从内部来看其仍然为table。

在WebCore中RenderTable类代表了一个table。其继承自RenderBlock,原因将在position该节讲述。

Text

原始文本用RenderText类表示。Text 在WebCore中被视为inline,因为其被置于行内。

Getting Block and Inline Information

获取block vs inline 状态最基本的方法是`isInline。该方法询问一个对象是否被作为行的一部分。其不关心该元素内部是什么样(如image,inline flow,inline block,inline-table)。

bool isInline() const

一个最常见的错误是当考虑render tree时总是假设一个对象是认为 isInline 意味着一个对象是inline flow,text,或者为 inline replaced 元素。但是因为inline-block 和 inline-table,该方法对于这些对象也会返回true。

查询一个对象是否为block或者inline flow,应该采用如下方法。

bool isInlineFlow() const
bool isBlockFlow() const

这些方法实际上询问的是对象的内部结构。一个inline-block实际上是block flow而非 inline flow。其外部是inline而内部是block flow。

我们可以通过如下方法查询blocks 和 inlines的具体的类类型。

bool isRenderBlock() const
bool isRenderInline() const

isRenderBlock 方法在定位时很有用,因为 block flow 和 table 都作为定位对象的容器。

查询一个元素是否为inline block 或者 inline table。可以用如下方法。

bool isInlineBlockInlineTable() const

Children of Block Flows

Block flow 有一个关于其孩子节点的简单的不变式,其render tree必须遵守。该规则总结如下。

Block flow 的所有 in flow 的孩子必须都是block或者都是inline

换句话说就是,一旦你排除了浮动和定位元素,render tree 中所有block flow 的孩子调用isInline时必须同时返回true,或者同时返回false。render tree 为了维护不变量有时会需要修改其结构。

childrenInline方法被用来查询block flow的孩子是inline还是block

bool childrenInline() const

Children of Inline Flows

inline flow的所有孩子的有一个更为简单的不变式。

inline flow 的所有 in flow 孩子必须都是 inline

Anoymous Blocs

为了维护block flow 孩子的不变式(只有inline 孩子或者只有 block 孩子)。render tree 会构造一个匿名块(anoymous blocks)。考虑如下代码

<div>
Some text
<div>
Some more text
</div>
</div>

上例中,外层的div含有两个孩子,一些text 和一个div。第一个孩子是inline的。但是第二个孩子是block。因为这样的组合违反了不变式,render tree会构造一个匿名块去包含这个文本。该render tree因此成为如下这样:

<div>
<anonymous block>
Some text
</anoymous block>
<div>
Some more text
</div>
</div>

isAnoymousBlock方法可以用来查询一个renderer是否为anymous block flow。

bool isAnonymousBlock() const

当一个block有inline 孩子和一个block 对象其就会尝试生成匿名块去包裹所有的inlines。连续的inlines可以共用一个公共的匿名块。因此匿名块的数目尽可能小。RenderBlock有一个makeChildrenNonInline方法去完成上述操作。

void makeChildrenNonInline(RenderObject* insertionPoint)

Blocks inside Inline Flows

HTML中你能见到最难以忍受的就是将一个block放在inline flow中。如下所示:

<i>Italic only <b> italic and bold
<div>
Wow,a block!
</div>
<div>
Wow another block
</div>
More italic and bold text</b>
More italic text</i>

上述的两个div违反了所有的bold元素的孩子都是inline这个不变量。render tree必须执行一个非常复杂的步骤去修复自身。需要构造三个匿名块。第一个块包含所有的div之前的inlines。第二个匿名块包裹divs,第三个匿名块包含剩下的div之后的inlines

<anonymous pre block>
<i>Italic only <b>italic and bold</b</i>
</anonymous pre block>
<anonymous middle block>
<div>
Wow, a block!
</div>
<div>
Wow, another block!
</div>
</anonymous middle block>
<anonymous post block>
<i><b>More italic and bold text</b> More italic text</i>
</anonymous post block>

注意到bold和italic renderers必须拆分为两个render对象,因为他们在匿名块之前和之后。对于bold元素,其孩子先在block之前,然后在block内,接着在Block之后。render tree通过一个continuation链连接这些对象。

RenderFlow* continuation() const
bool isInlineContinuation() const

在block之前的bold renderer可以通过b元素的renderer()方法获得,该renderer将中间的匿名块作为其continuation(),中间匿名块将第二个bold renderer作为其continuation。这样代码就可以检查代表DOM元素的renderers的操作就比较简单了。

执行递归切割inline flow和生成continuation链的方法是splitFlow

Layout Basics

当renderes被生成并加入到树中时,其并不包含position或者size信息。决定盒子的位置和大小的过程叫做layout。所有的renderer多有一个layout方法。

void layout()

Layout 是一个递归的操作。一个叫做FrameView的类代表了document中的视图信息,其也含有一个layout方法。frameview负责管理renderer tree的布局。

FrameView可以执行两种布局操作。第一种(也是最常见的)就是整个render tree的布局。此时render tree的根节点调用其layout方法,然后整个render tree 都得到更新。第二种layout类型是render tree的局部更新。第二种用在部分子树的更新不影响全局的情况下。如今subtree layout仅被用于text fields(但是可能在将来被用于 带有overflow:auto属性的block或者类似结构)。

Dirty Bits

Layout有一个dirty bit 系统去决定一个对象是否需要layout。当有新的renderers插入树里时,它们dirty自己和其祖先链中的相关连接。有三个单独的bits在render tree 中使用。

bool needsLayout() const{
  return m_needsLayout || m_normalChildNeedsLayout ||
  m_postChildNeedsLayout;
}
bool selfNeedsLayout() const{
  return m_needsLaytout;
}
bool postChildNeedsLayout() const{
  return m_possChildNeedsLayout;
}
bool normalChildNeedsLayout() const { return m_normalChildNeedsLayout; }

当renderer本身是dirty时使用第一位,可以通过selfNeedsLayout进行查询,当该bit被置true,相关的祖先renderers设置对应的bits表示它们有个dirty孩子。置为的bit类型取决于之前变脏的link的定位状态(#todo),posChildNeedsLayout用来表明一个定位的孩子dirted,normalChildNeedsLayout被用来指示一个in flow孩子变脏了。通过区别这两种孩子layout可以在只有定位元素移动的情况下进行优化。

The Containing Block

”相关联的祖先链(the relevant ancestor)“指的是什么?当一个对象被标记为需要进行layout,被dirted的祖先链基于CSS的包含块概念(containing block)。包含块也被用来为孩子建立坐标。Renderers有xPosyPos坐标,这些都是相对于包含块。那么到底什么事包含块

这就是包含块的定义

从WebCore render tree角度来讲,我所理解的包含块如下

一个renderer的包含块是该renderer的祖先块,该块负责决定renderer的位置。

换句话来说,当render tree 发生layout,该render负责定位所有以它作为包含块的renderers。

render tree的根元素为RenderView,该类根据CSS2.1规范对应于初始包含快(initial containing block)。这也是调用Document的render()返回的render。

初始包含块总是与视口尺寸大小相同。在桌面浏览器里,这就是浏览器窗口的可见区域。其也总是被置于坐标(0,0)处。下图展现了包含块在document的定位。黑色带有边框的盒子代表了RenderView,灰色的盒子代表了整个document。

​ ![屏幕快照 2016-03-02 下午3.52.46](/Users/yj/Desktop/屏幕快照 2016-03-02 下午3.52.46.png)

如果滚动document,那么初始包含快将会随着移动。其总是在document的上部且与视口大小相同。人们总觉得疑惑的地方是他们以为初始包含块应该脱离文档并为视口的一部分。

这是CSS2.1中初始包含块的相关规范

这些规则可以总结如下:

  • renderer的root元素(如元素)总是把RenderView作为其包含块。

  • 如果renderer的CSS position为relative或者static,那么其包含块为最近的block-level祖先。

  • 如果renderer 的 CSS position为fixed,那么其包含块为RenderView,技术上来说RenderView并不作为视口,因此RenderView必须调整fixed 定位的元素来应对document的滚动位置。这种情况下可以简单的把RenderView当作视口的包含块。

  • 如果一个renderer的position为absolute,那么是最近的非static定位的block-level祖先。如果不存在这样的祖先,那么包含块为RenderView

    render tree有两个方便的方法用于查询一个对象的position是否为absolute或者fixed或relative。

    bool isPositioned() const;
    bool isRelPositioned() const;
    

    代码中术语positioned指代absolute和fixed对象。术语relPositioned指代relative 定位对象。

    render tree 有一个方法来获取renderer的包含块。

    RenderBlock* containingBlock() const
    

    当一个对象被标注为需要layout,其遍container chain,设置normalChildNeedsLayoutbit或者posChildNeedsLayoutbit。链中的之前连接的isPositioned状态决定了该设置哪个bit位。container chain 大致对应于containing block chain,尽管中间的inlines 依旧dirted。因为该差别,一个方法叫做container替代containBlock

    RenderObject* container() const
    

    所有的layout方法以setNeedsLayout(false)结尾,这样做的原因在于在离开 layout方法前清空renderer 的dirty bit,因此未来layout调用不会误以为该对象仍然dirty。

Anatomy of a Layout Method

layout 方法类似于下面

void layout()
{
  ASSERT(needsLayout());
  //Determin the width and horizontal margins of this object
  ...

  for(RenderObject* child = firstChild();child;child=child-		  >nextSibling()){
  //Determine if the child needs to get a relayout despite the bit not being set.
  // place the child
  child->layoutIfNeeded()
}
// now the intrinsic height of the object is known because the children are placed 
// Determine the final height
...
setNeedsLayout(false);
}

我们将在下节详细阐述layout方法。

Absolute/Fixed and Relative Positioning

CSS的position属性用来相对于对象的包含块进行定位。其含有四个值:'static','absolute','fixed','relative'。static 定位是默认的定位方式,意味着该对象该对象利用常规的block和line layout规则进行定位。

Relative Positionning

relative定位和static定位十分类似除了其CSS的left,top,right,bottom属性可以用来移动对象。isRelPositioned方法可以用来查询一个renderer是否为relative定位

bool isRelPositioned() const

其偏移属性也可以通过下列方法获得

int relativePositionOffsetX() const;
int relativePositionOffsetY() const;

relative 定位只不过是paint-time translation。就layou而言,该对象仍然处于原来的地方。下例利用relative偏移了行的一部分。如你所视,这些行就像那个对象仍然处在原位置。

<div style="border:5px solid black;padding:20px;width:300px">
Here is a line of text.
<span style="position:relative:top:-10px;background-color:#eeeeee">
This part is shifted<br> up a bit.
</span>
but the rest of the line is in its original position.
</div>

Absolute and Fixed Positioning

fixed定位对象的定位相对于视口。absolute定位对象相对于包含块,即最近的position非static的祖先块。如果不存在这样的祖先,那么使用初始包含块(the RenderView)

isPositioned可以用来判断一个renderer是absolute或者为fixed定位。

bool isPositioned() const

当一个对象是absolute/fixed定位,其变为block-level。即使其CSS display属性设置为inline或者inline-block/table,一旦一个对象被定位后,其display就变为block-level。isInline对于定位元素总是返回false。

RenderStyle可以得出display值,有时候当需要原始的display属性时,可以调用如下属性获得其display属性

EDisplay() display() const;
EDisplay() originalDisplay() const;

The Positioned Object List

每个块都有一个定位对象列表,其包含了以他作为包含块的所有的absolute/fixed定位的renderers。该块负责定位所有这些定位元素。下列方法可以用来管理定位对象列表。

void insertPositionedObject(RenderObject*)
void removePositionedObject(RenderObject*)

layoutOnlyPositionedObjects方法被用来定位positioned对象。如果只有定位对象发生改变,那么该方法返回true。这表示layout方法不必layout所有的常规孩子,可以早一点返回。

bool layoutOnlyPositionedObjects

layoutPositionedObejcts方法负责定位所有的定位对象,其含有一个boolean参数来表示是否需要对所有对象进行relayout。在某些情况下relayout是必须的。

bool layoutPositionedObjects(bool relayoutChildren)

Coordinates of Positioned Objects

定位元素的坐标是相对于包含块的padding边缘的。例如下例指明了绝对定位对象的left和top坐标为(0,0)会导致对象被放在包含块的左上角。

<div style="position:relative;border:5px solid black;width:300px;height:300px;">
<div style="position:absolute;width:200px;height:200px;background-color:purple"></div>
</div>
		 ![屏幕快照 2016-03-02 下午5.20.05](/Users/yj/Desktop/屏幕快照 2016-03-02 下午5.20.05.png) 

在WebCore中,坐标位置总是相对于border边缘,所以上例中对象的坐标为(5,5)。

当在CSS中忽略某坐标,WebCore必须为定位对象决定一个合适的位置。CSS对此有一系列复杂的规则,我们将在未来详细讨论。

Inline Relative Positioned Containers

一个relative 定位的inline是可能为其后代定位元素充当包含块的。这是另一个极端复杂的例子。暂时我们只需知道可能存在这种情况,因此代码必须处理好这种情况。

Float

float是一个意图移动到一个段落的左侧或右侧的render。段落里的内容都会避让float对象。

HTML中有对应结构表明float行为。例如image的align属性可以用来浮动image

<img align = left src="...">

一个浮动元素可以占据多行。本例中,即使浮动元素在一个段落中声明,其也可能突出到段落到下一个段落。

因为floats可以影响到多个blocks,WebCore利用block flows的一个float对象列表来追踪所有的入侵到该block的浮动的renderers。一个浮动元素因此可以在多个block flow的浮动列表里。Line布局必须要考虑到float的位置,以便能收缩自身来避让float元素。通过浮动对象列表,block可以了解其需要避让哪些floats。

一个浮动对象列表包含了如下数据结构:

struct FloatingObject{
  enum Type{
  	FloatLeft,
    FloatRight,
	};
  FloatingObject(Type type)
    :node(0),
  startY(0),
  endY(0),
  left(0),
  width(0),
  m_type(type),
  ,noPaint(false)
  {
​```}
Type type() { 
return static_cast<type>(m_type)
}
  RenderObject* node;
  int startY;
  int endY;
  int left;
  int width;
  unsigned m_type:1;
  bool noPaint:1;
}

如你所视,该结构包含了一个矩形框(top,bottom,left,width)。每个列表项都含有其位置和尺寸(其独立于float对象的位置和尺寸)的原因在于这些坐标都是相对于持有对象列表的对象的。这样使得便于查询每个block都可以轻易的查询基于自己坐标空间float的位置,而不用做一系列转换。

另外float的margins也被存储在float列表项里,因为行框不仅需要避让box的border也需要避让其margin。这对于float对象很重要,这使得其可以在与行框之间留有空隙。

以下方法作用于floating对象列表上:

void insertFloatingObject(RenderObject*);
void removeFloatingObject(RenderObject*);
void clearFloats();
void positionNewFloats();

前两个函数自不必说,它们用于在列表中插入或删除特定的float对象。clearFloats将会清空float对象列表。

当一个对象插入到列表里时,其是未定位的(unpositioned)。其垂直坐标被设置为-1。positionNewFloats方法被用来摆放所有的floats。CSS有一系列关于floats该如何摆放的规则。该方法保证floats按照上述规则摆放。

floats有许多帮助函数。当我讲到block 和line 布局时我会进一步阐释它们。

Clearance

CSS提供了一个方法来指明衣蛾对象应该处于所有的浮动对象之下,或者所有的左浮动对象之下,或所有的右浮动对象之下。clear属性指明了这些行为,其值可以为none,left,right或both。

本段落处于上面的float对象之下,是因为使用了clear:left。clearance在block和line布局时被计算得到。clear属性也可以用于float对象,以确保其处于所有之前的浮动对象之下(当然这里只left,right或者both)。

posted @ 2016-03-12 16:58  zjuyang  阅读(272)  评论(0)    收藏  举报