渲染流水线中的动态变化处理

从脚本中更改 DOM 的能力是 Web 平台的一项主要功能。网页作者依赖于这个概念(尽管有一些例外,例如动画),即从脚本更改 DOM 会导致与从该 DOM 树开始相同的渲染结果。他们还依赖于这些更改的性能特征:对 DOM 进行的微小更改,如果产生的影响很小,则其处理时间也应该成比例地小。这意味着 Gecko 需要有效地将更改从内容树传播到样式、框架树、框架树的几何形状以及屏幕。

然而,对于许多类型的更改,无论更改多么小,处理更改都会产生大量的开销。例如,无论更改多小,重排都必须从框架树的顶部传播到脏的框架。解决此问题的一种非常常见的方法是批量处理更改。我们以多种方式批量处理更改,例如

  • 内容接收器在通知监听器已添加节点之前,将多个节点添加到 DOM 树中。这允许一次通知祖先而不是每个后代,或者一次通知一组后代,从而加快这些通知的处理速度。

  • 我们批量处理需要重新解析样式的节点(重新计算选择器匹配并处理由此产生的样式更改)。此批量处理是基于树的,因此它不仅合并了对同一元素的多个通知,还合并了对祖先的通知与其后代的通知(因为某些这些通知暗示需要对所有后代重新解析样式)。

  • 我们等待重建需要重建的框架(在急切地销毁框架之后)。这与基于树的样式重新解析批量处理类似,避免了重复,无论是在同一元素的通知还是祖先-后代通知中,即使它实际上并没有进行任何基于树的缓存。

  • 我们推迟执行重排,直到需要为止。与样式重新解析一样,这维护了基于树的脏位(请参阅重排下 NS_FRAME_IS_DIRTY 和 NS_FRAME_HAS_DIRTY_CHILDREN 的描述)。

  • 我们允许操作系统在重新绘制之前排队多个失效操作(尽管我们可能会切换到自己控制)。这导致对某些像素集进行单次重新绘制,否则可能会进行多次重新绘制(尽管如果将多个矩形合并为一个,也可能导致更多像素被重新绘制)。

但是,将更改缓冲起来意味着各种信息(布局、样式等)可能不是最新的。有些事情需要最新信息:例如,我们不想将我们的缓冲细节暴露给网页脚本,因为网页脚本的编程模型假设 DOM 更改“立即”生效,即脚本不应该能够检测到任何缓冲。许多网页都依赖于此。

因此,我们有方法来刷新这些不同类型的缓冲区。有一些名为 FlushPendingNotifications 的方法,位于 nsIDocument 和 nsIPresShell 上,它们接受一个参数来指定要刷新的内容

  • Flush_Content:从解析器中缓冲的数据创建所有内容节点

  • Flush_ContentAndNotify:以上操作,以及通知文档观察器已创建所有迄今为止创建的节点

  • Flush_Style:以上操作,以及确保样式数据是最新的

  • Flush_Frames:以上操作,以及确保所有框架构造都已完成(目前与 Flush_Style 相同)

  • Flush_InterruptibleLayout:以上操作,以及执行布局(重排),但允许在布局花费时间过长时中断

  • Flush_Layout:以上操作,以及确保布局(重排)运行至完成

  • Flush_Display(不应该使用):以上操作,以及确保重新绘制发生

更改通知从内容代码传播到布局和代码的其他区域的主要方式是通过 nsIDocumentObserver 和 nsIMutationObserver 接口。类可以实现此接口来侦听整个文档或内容树子树的更改通知。

待编写:… 布局文档观察器实现

待办:样式系统如何优化避免重新运行选择器匹配

待办:样式更改和 nsChangeHint