WebCore 渲染 I – 基础

这是旨在帮助那些对 WebCore 渲染系统感兴趣的人们系列文章的第一篇。我将在完成这些文章后把它们发布到本博客上,它们也将发布到网站的文档部分。

DOM 树

网页被解析成一个节点树,称为文档对象模型(Document Object Model,简称 DOM)。树中所有节点的基础类是 Node

Node.h

节点分为几类。与渲染代码相关的节点类型有

  • Document – 树的根始终是文档。有三个文档类:DocumentHTMLDocumentSVGDocument。第一个用于除 SVG 文档之外的所有 XML 文档。第二个仅适用于 HTML 文档,并继承自 Document
    第三个适用于 SVG 文档,也继承自 Document

    Document.h
    HTMLDocument.h

  • Elements – HTML 或 XML 源代码中出现的所有标签都变成元素。从渲染角度来看,元素是具有标签名称的节点,可以用来转换为特定的子类,以便查询渲染器所需的数据。

    Element.h

  • Text – 元素之间出现的原始文本会变成文本节点。文本节点存储这些原始文本,渲染树可以查询节点以获取其字符数据。

    Text.h

渲染树

渲染的核心是渲染树。渲染树与 DOM 非常相似,它是一个对象树,其中每个对象可以对应于文档、元素或文本节点。渲染树还可以包含没有相应 DOM 节点的附加对象。

所有渲染树节点的基础类是 RenderObject

RenderObject.h

DOM 节点的 RenderObject 可以使用 Node 上的 renderer() 方法获取。

RenderObject* renderer() const

以下方法是遍历渲染树最常用的方法。

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

这里是一个遍历渲染器直接子节点的循环示例。这是渲染树代码中最常见的遍历方式。

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

创建渲染树

渲染器通过 DOM 上的一个称为附加的过程创建。当文档被解析并添加 DOM 节点时,一个称为attach的方法会在 DOM 节点上被调用以创建渲染器。

void attach()

attach 方法计算 DOM 节点的样式信息。如果元素的 display CSS 属性设置为 none,或者该节点是设置为 display: none 的元素的后代,则不会创建渲染器。节点的子类和 CSS display 属性值一起用于确定为该节点创建哪种类型的渲染器。

Attach 是一个自上而下的递归操作。父节点总是在其任何后代创建渲染器之前创建其渲染器。

销毁渲染树

当 DOM 节点从文档中移除或文档被卸载时(例如,因为它所在的标签页/窗口被关闭),渲染器会被销毁。一个称为detach的方法会在 DOM 节点上被调用以断开并销毁渲染器。

void detach()

Detachment 是一个自下而上的递归操作。后代节点总是在父节点销毁其渲染器之前销毁其渲染器。

访问样式信息

在附加过程中,DOM 查询 CSS 以获取元素的样式信息。结果信息存储在一个称为RenderStyle.

RenderStyle.h

WebKit 支持的每一个 CSS 属性都可以通过此对象查询。RenderStyle 是引用计数对象。如果一个 DOM 节点创建一个渲染器,那么它会使用setStyle方法将样式信息连接到该渲染器。

void setStyle(RenderStyle*)

渲染器会增加对该样式的引用,并一直维护它,直到获取新的样式或被销毁。

可以从 RenderObject 使用 style() 方法访问 RenderStyle

RenderStyle* style() const

CSS 盒模型

RenderObject 的主要工作子类之一是 RenderBox。此子类代表遵循 CSS 盒模型的对象。这些包括具有边框、内边距、外边距、宽度和高度的任何对象。目前,一些不遵循 CSS 盒模型的对象(例如,SVG 对象)仍然是 RenderBox 的子类。这实际上是一个错误,将来会通过重构渲染树来修复。

来自 CSS2.1 规范的此图说明了 CSS 盒的各个部分。以下方法可用于获取边框/外边距/内边距的宽度。不应使用 RenderStyle,除非目的是查看原始的原始样式信息,因为为 RenderObject 实际计算的值可能非常不同(特别是对于表格,表格可以覆盖单元格内边距并在单元格之间具有折叠边框)。

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;

width()height() 方法给出盒的宽度和高度,包括其边框。

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

client box 是排除边框和滚动条的盒区域。内边距包含在内。

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

术语 content box 用于描述排除边框和内边距的 CSS 盒区域。

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

当盒具有水平或垂直滚动条时,它位于边框和内边距之间。滚动条的大小包含在 client width 和 client height 中。滚动条不是 content box 的一部分。可滚动区域的大小和当前滚动位置都可以从 RenderObject 中获取。我将在关于滚动的单独部分中更详细地介绍这一点。

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

盒也具有 x 和 y 位置。这些位置相对于负责决定该盒应放置在何处的祖先。然而,这条规则有很多例外情况,这也是渲染树中最令人困惑的领域之一。

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