Chrome DevTools 中的顶层支持

Alina Varkki
Alina Varkki

Chrome 开发者工具添加了对顶层元素的支持,以便开发者更轻松地调试使用顶层元素的代码。

本文介绍了什么是顶层元素,开发者工具如何帮助直观呈现顶层内容,以了解和调试包含顶层元素的 DOM 结构,以及如何实现开发者工具顶层支持。

顶层和顶层元素是什么?

当您以模态形式打开 <dialog> 时,内部到底发生了什么?🤔

置于顶层。顶层内容会呈现在所有其他内容之上。例如,模态对话框需要显示在所有其他 DOM 内容之上,因此浏览器会自动在“顶层”渲染此元素,而不是强制作者手动解决 Z-index 问题。即使 Z-index 值最高,顶层元素也会显示在元素之上。

顶层可以描述为“最高堆叠层”。每个文档都有一个关联的视口,因此也有一个顶层。 顶层可以同时有多个元素。当发生这种情况时,它们会堆叠在一起,而最后一个元素则位于顶部。换句话说,所有顶层元素都放置在顶层的后进先出 (LIFO) 堆栈中。

<dialog> 元素不是浏览器渲染到顶层的唯一元素。目前,顶层元素包括:弹出式窗口模态对话框全屏模式下的元素。

检查以下对话框实现:

<main>
  <button onclick="window.dialog.showModal();">Open Dialog</button>
</main>
<dialog id="dialog"></dialog>

以下演示包含几个将样式应用于背景幕(背景幕)的对话框:

什么是背景幕?

幸运的是,您可以自定义顶层元素下的内容。

顶层中的每个元素都有一个称为“背景幕”的 CSS 伪元素

背景幕是一个与视口大小相同的方框,该方框紧挨着任何顶层元素下方渲染。借助 ::backdrop 伪元素,当元素下方是顶层顶层时,可以对其进行遮盖、设置样式或完全隐藏。

当您将多个元素设为模态时,浏览器会将背景幕直接绘制到此类元素最前面的下方和其他全屏元素的上层。

您可以按以下步骤为背景设置样式:

/* The browser displays the backdrop only when the dialog.showModal() function opens the dialog.*/
dialog::backdrop {
    background: rgba(255,0,0,.25);
}

如何仅显示第一个背景?

每个顶层元素都有一个属于顶层堆栈的背景幕。这些背景被设计为彼此重叠,因此如果背景幕的不透明度不是 100%,则其下的背景幕将可见。

如果只需显示顶层堆栈中的第一个背景幕,您可以通过跟踪顶层堆栈中的项标识符来实现这一点。

如果添加的元素不是顶层中的第一个元素,则将该元素放入顶层时调用的函数会将 hiddenBackdrop 类应用于 ::backdrop。从顶层移除元素时,也会移除此类。

查看此示例演示中的代码:

开发者工具中的顶层支持设计

开发者工具对顶层的支持有助于开发者了解顶层的概念并直观呈现顶层内容的变化。这些功能可帮助开发者识别以下内容:

  • 顶层中的元素在任何时间及其顺序。
  • 位于任意点堆栈顶部的元素。

此外,开发者工具顶层支持有助于直观呈现背景幕伪元素在顶层堆栈中的位置。虽然它不是树元素,但它在顶层的工作原理中发挥着重要作用,并且对开发者非常有用。

借助顶层支持功能,您可以:

  1. 随时观察顶层堆栈中有哪些元素。顶层表示堆栈会随着在顶层中添加或移除元素而动态变化。
  2. 查看元素在顶层堆栈中的位置。
  3. 从树中的顶层元素或背景幕伪元素跳到顶层表示容器中的元素或背景伪元素,然后再跳回。

我们来看看如何使用这些功能!

顶层容器

为了帮助直观呈现顶层元素,开发者工具向元素树添加了顶层容器。它位于 </html> 结束标记之后。

借助此容器,您可以随时观察顶层堆栈中的元素。顶层容器是一个列表,包含指向顶层元素及其背景的链接。顶层表示堆栈会随着在顶层中添加或移除元素而动态变化。

若要在元素树或顶层容器中查找顶层元素,请点击从顶层容器中的顶层元素表示形式指向元素树中同一元素的链接,然后再返回该元素。

要从顶层容器元素跳转到顶层树元素,请点击顶层容器中相应元素旁边的显示按钮。

从顶层容器链接跳转到相应元素。

要从顶层树元素跳转到顶层容器中的链接,请点击相应元素旁边的顶层标志。

从某个元素跳转到顶层容器链接。

您可以关闭任何标志,包括顶层标志。要停用徽章,请右键点击任何徽章,选择徽章设置,然后清除要隐藏的徽章旁边的对勾。

正在关闭徽章。

顶层堆栈中的元素顺序

顶层容器会按元素在堆栈中的显示顺序显示这些元素,但顺序要颠倒。堆栈元素的顶部是顶层容器元素列表中的最后一个元素。这意味着,顶层容器列表中的最后一个元素是您当前可以在文档中与之交互的元素。

树元素旁边的标记表示这些元素是否属于顶层以及元素在堆栈中的位置编号。

在此屏幕截图中,顶层堆栈由两个元素组成,第二个元素位于堆栈的顶部。如果您移除第二个元素,则第一个元素会移至顶部。

堆栈中元素的顺序。

顶层容器中的背景幕

如上所述,每个顶层元素都有一个称为 Backdrop 的 CSS 伪元素。您可设置此元素的样式,因此同时检查该元素和查看其表示形式也很有用。

在元素树中,背景幕元素位于其所属元素的结束标记之前。不过,在顶层容器中,背景幕链接会列在它所属的顶层元素的正上方。

背景幕堆叠位置。

对 DOM 树的更改

ElementsTreeElement(负责在开发者工具中创建和管理各个 DOM 树元素的类)不足以实现顶层容器。

为了将顶层容器显示为树中的节点,我们添加了一个新类,用于创建开发者工具树元素节点。以前,负责创建开发者工具元素树的类使用 DOMNode 初始化每个 TreeElement,后者是一个具有 backendNodeId 和其他后端相关属性的类。系统会依次在后端分配 backendNodeId

顶层容器节点包含指向顶层元素的链接列表,需要像常规树元素节点一样运行。不过,此节点不是“真正的”DOM 节点,并且后端不需要创建顶层容器节点。

为了创建代表顶层的前端节点,我们添加了一种无需 DOMNode 创建的新型前端节点。此顶层容器元素是第一个没有 DOMNode 的前端节点,这意味着它仅存在于前端,后端不知道它。为了具有与其他节点相同的行为,我们创建了一个新的 TopLayerContainer 类,用于扩展 UI.TreeOutline.TreeElement 类,该类负责前端节点的行为。

为了实现所需的展示位置,呈现元素的类会将 TopLayerContainer 添加为 <html> 标记的下一个同级。

新的顶层标记表示该元素位于顶层,并充当指向 TopLayerContainer 元素中此元素的快捷方式的链接。

初始设计

起初,我们计划将顶层元素复制到顶层容器中,而不是创建指向元素的链接的列表。我们未实现此解决方案,因为提取元素的子项在开发者工具中的运作方式。每个元素都有一个用于获取子元素的父指针,因此不可能有多个指针。因此,我们无法让一个节点在树中的多个位置正确扩展并包含所有子节点。一般情况下,在构建系统时不考虑重复的子树。

我们的折衷方法是创建指向前端 DOM 节点的链接,而不是复制这些节点。负责在开发者工具中创建元素链接的类是 ShortcutTreeElement,它会扩展 UI.TreeOutline.TreeElementShortcutTreeElement 的行为与其他开发者工具 DOM 树元素相同,但在后端没有对应的节点,并且包含可链接到 ElementsTreeElement 的按钮。 顶层节点的每个 ShortcutTreeElement 都有一个子 ShortcutTreeElement,链接到开发者工具 DOM 树中 ::backdrop 伪元素的表示形式。

初始设计:

初始设计。

Chrome 开发者工具协议 (CDP) 变更

要实现顶层支持,需要更改 Chrome 开发者工具协议 (CDP)。CDP 充当开发者工具和 Chromium 之间的通信协议。

我们需要添加以下内容:

  • 随时从前端调用的命令。
  • 要从后端在前端触发的事件。

CDP:DOM.getTopLayerElements 命令

为了显示当前的顶层元素,我们需要新的实验性 CDP 命令,该命令会返回顶层元素的节点 ID 列表。每当开发者工具打开或顶层元素发生变化时,DevTools 都会调用此命令。该命令如下所示:

  # Returns NodeIds of the current top layer elements.
  # Top layer renders closest to the user within a viewport, therefore, its elements always
  # appear on top of all other content.
  experimental command getTopLayerElements
    returns
      # NodeIds of the top layer elements.
      array of NodeId nodeIds

CDP:DOM.topLayerElementsUpdated 个事件

为了获取顶层元素的最新列表,我们需要对顶层元素进行每项更改来触发实验性 CDP 事件。此事件会通知前端,然后前端会调用 DOM.getTopLayerElements 命令并接收新元素列表。

该事件如下所示:

  # Called by the change of the top layer elements.
  experimental event topLayerElementsUpdated

CDP 注意事项

关于如何实现顶层 CDP 支持的方式有多种。我们考虑的另一种选择是创建一个会返回顶层元素列表的事件,而不是仅通知前端有关添加或移除顶层元素的情况。

或者,我们可以创建两个事件,而不是命令:topLayerElementAddedtopLayerElementRemoved。在本例中,我们将接收一个元素,并且需要在前端管理顶层元素的数组。

目前,前端事件会调用 getTopLayerElements 命令来获取已更新元素的列表。如果要在每次事件触发时发送导致更改的元素列表或特定元素,则可以避免执行一步调用命令。 但在这种情况下,前端无法控制推送哪些元素。

我们之所以采用这种方法实现它,是因为我们认为最好由前端决定何时请求顶层节点。例如,如果顶层在界面中收起,或者用户使用没有元素树的开发者工具面板,则无需获取可以更深层结构树的额外节点。