WebCore 渲染 I – 基础
这是旨在帮助那些对 WebCore 渲染系统感兴趣的人们系列文章的第一篇。我将在完成这些文章后把它们发布到本博客上,它们也将发布到网站的文档部分。
DOM 树
网页被解析成一个节点树,称为文档对象模型(Document Object Model,简称 DOM)。树中所有节点的基础类是 Node
。
节点分为几类。与渲染代码相关的节点类型有
- Document – 树的根始终是文档。有三个文档类:
Document
、HTMLDocument
和SVGDocument
。第一个用于除 SVG 文档之外的所有 XML 文档。第二个仅适用于 HTML 文档,并继承自Document
。
第三个适用于 SVG 文档,也继承自Document
。 - Elements – HTML 或 XML 源代码中出现的所有标签都变成元素。从渲染角度来看,元素是具有标签名称的节点,可以用来转换为特定的子类,以便查询渲染器所需的数据。
- Text – 元素之间出现的原始文本会变成文本节点。文本节点存储这些原始文本,渲染树可以查询节点以获取其字符数据。
渲染树
渲染的核心是渲染树。渲染树与 DOM 非常相似,它是一个对象树,其中每个对象可以对应于文档、元素或文本节点。渲染树还可以包含没有相应 DOM 节点的附加对象。
所有渲染树节点的基础类是 RenderObject
。
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.
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;