跳到内容

LFC 显示树

布局格式化上下文显示树及相关功能(也称为“LFC 显示”)。

基本概念

显示树旨在成为一个完全解析的、独立的树,适用于绘制和命中测试。

完全解析意味着所有样式值都已解析为用于绘制的值。例如,显示树中的颜色是应用访问链接规则和应用 -apple-color-filter 后的结果。

独立意味着显示树是一个独立的数据结构,可以在不参考与布局共享的数据结构的情况下进行绘制。例如,显示树一旦构建完成,就不再使用 RenderStyle,而是在树构建时构建自己的显示样式。

显示树中的所有几何结构都已处于绘制坐标中:像素对齐在树构建时发生。

总的来说,该树的设计目标是将尽可能多的复杂性推到树构建阶段,以便快速绘制。

树对象本身是特意设计为轻量级的,主要为纯数据对象。复杂的树遍历代码存在于知道如何执行某些操作(如绘制盒子树)的外部对象中。

显示树中的盒子旨在是相当通用的,不具有非常特定于 CSS 或 SVG 的属性。希望 SVG 和 CSS 渲染能共享许多相同的盒子类。

Layout::BoxDisplay::Box 之间不一定存在一对一的关系。显示树可能会省略不绘制任何内容的盒子,或者为滚动等功能创建额外的盒子。

相关类

Display::View

表示显示树的呈现。是命中测试的入口点。

目前 FrameView 拥有一个 Display::View

Display::Tree

通过 Display::StackingItem 对象拥有 Display::Box 对象的层级结构,以及相关的树数据结构。Display::Tree 应该能够独立于 Display::View 存在。

Display::TreeBuilder

Display::TreeBuilder 是一个生命周期短暂的类,它知道如何从 Layout::Box 树、LayoutState 和样式构建 Display::StackingItem/Display::Box 树。在构建显示树时,它会在栈中维护各种状态位,以跟踪包含块等。Display::TreeBuilder 了解层级结构,而不了解几何结构。

Display::BoxFactory

Display::BoxFactory 实际创建 Display::Box 对象及其相关对象,如 Display::BoxGeometry。它的存在是为了将样式和几何代码与 Display::TreeBuilder 分离。它一次只处理一个盒子。

Display::Box

Display::Box 是显示树的基本构建块;它表示某个矩形区域,通常带有绘制的内容。有用于文本、图像等的子类。Display::Box 具有兄弟指针和父指针。Display::ContainerBox 用于带有子级的盒子,因此具有指向其第一个子级的指针。

Display::Box 对象的层级结构形成一棵树,但它不是一棵完整的树,因为每个 Display::StackingItem 拥有树的一部分,并且这些子树中的盒子对象并未连接。

Display::StackingItem

此类的存在是为了表示盒子树中按照 CSS 2.2 附录 E 规定原子性绘制的部分。根部总会有一个 Display::StackingItemDisplay::Tree 通过它拥有实际的盒子树),以及导致 CSS 堆叠上下文的 CSS 定位和其他样式会触发 Display::StackingItem 的创建。表示 CSS 堆叠上下文的 Display::StackingItem 对象具有包含指向后代 Display::StackingItem 对象的拥有引用的正 z 序列表负 z 序列表。这些列表在树构建时构建和排序。

Display::Style

Display::Style 是显示树中与 RenderStyle 等价的类,但有一些重要的区别。每个 Display::Box 都有一个 Display::Style,因此它应该相对较小。它必须只包含独立于盒子几何结构的数据;即,它不能包含任何 Length 值(这些值可能隐藏像 calc() 这样必须已解析的值)。它应该可以在具有相同外观但可能尺寸不同的盒子之间共享。所有颜色都应是最终使用的值,即在解析 visitedDependentColor() 并应用 -apple-color-filter 之后的值。

Display::BoxRareGeometry / Display::BoxDecorationData

这些类存储特定于单个 Display::Box 的依赖于几何结构的数据,并且只为具有相关样式(如可见边框或边框半径)的盒子分配。

Display::BoxDecorationData 存储有关背景和边框的信息。

Display::BoxRareGeometry 存储边框半径(此处是因为即使没有边框,具有 overflow:hidden 的盒子也会根据其边框半径进行裁剪),以及变换。

Display::CSSPainter

Display::CSSPainter 知道如何遍历 Display::StackingItem 及其 Display::Box 对象的树,以实现 CSS 绘制。它知道如何应用裁剪、不透明度和滤镜。

为了绘制单个盒子,Display::CSSPainter 利用 Display::BoxDecorationPainter,后者知道如何绘制 CSS 边框和背景。

Display::CSSPainter 对象仅在绘制期间存在于栈上;它们不是长期存在的。

预计 SVG 将在某个时候由 Display::SVGPainter 类绘制。

命中测试与绘制非常相似,因此该代码也存在于 Display::CSSPainter 中。也许它需要一个不同的名称。

绘制

绘制显示树某个子集的入口点是 GraphicsLayerClientpaintContents() 绘制回调。最终,显示树将通过 GraphicsLayers 的层级结构呈现;目前,根部只有一个 GraphicsLayer。

为了绘制树,调用静态函数 CSSPainter::paintTree(),然后 Display::CSSPainter 遍历 Display::StackingItem 层级并绘制其 Display::Box 对象,同时对带有盒子装饰的盒子使用 Display::BoxDecorationPainter

在 CSS/SVG 边界处,将在栈上创建一个新的绘制器对象来绘制内部内容。

目标 GraphicsContext 作为参数传递给绘制函数,并封装在一个 PaintingContext 结构体中,该结构体还包含 deviceScaleFactor,并且可能包含一个脏矩形。

几何

Note: this is subject to change. The code needs to make it hard to use the absolute rects in the wrong way.

显示盒子将其位置和大小存储为绝对(即文档相对)FloatRect(已进行像素对齐)。CSS 盒模型盒子(具有边框和边框半径等)也存储其内边距和内容盒的矩形。因此在绘制期间无需跟踪任何绘制偏移量。

一些盒子充当坐标边界;即那些具有变换的盒子,以及受滚动影响的盒子。"绝对"矩形是在考虑滚动和变换之前计算的,因此需要将盒子的矩形映射到视图坐标的代码需要考虑这些因素。

裁剪

裁剪是显示树中的复杂性来源,因为 CSS 裁剪不会触发 CSS 堆叠上下文;裁剪树遵循包含块树,这与绘制顺序不同。对于绘制正常的流内内容,我们可以在绘制时直接应用裁剪,但绘制流外内容时,我们必须能够从祖先计算裁剪。此类流外内容始终由 Display::StackingItem 的根盒子表示。

在树构建时,Display::TreeBuilder 会跟踪包含块,因此对于启动乱序绘制的盒子(即作为 Display::StackingItem 根盒子的那些),Display::BoxFactory 可以要求包含块提供应应用的裁剪。

裁剪存储在 Display::BoxClip 中,它跟踪一个交集矩形,并且在必要时,跟踪一组用于应用来自祖先的边框半径裁剪的圆角矩形。Display::BoxClip 对象在共享相同裁剪(即没有自身溢出)的父/子 Display::BoxModelObject 对象之间共享。

滚动

Note: this is preliminary

具有 overflow:autooverflow:scrollLayout::Box 以三层显示盒子层级表示

container box
    scrolling container box
        scrolled contents box

(可能还有用于滚动条和滚动角的额外盒子)。

容器盒子用于在滚动元素上绘制盒子装饰。“滚动容器”盒子具有容器内容盒子的几何结构,并用于表示滚动偏移量。“滚动内容”盒子绘制滚动内容,并具有布局溢出的尺寸。

显示树的目标是滚动不应使子树上的任何几何结构失效(从而避免滚动后遍历树以修复几何结构,这种遍历已知是渲染代码中性能问题的来源)。

Note: explain how scrolling interacts with positioned elements.

合成

Note: write me