Chrome DevTools 中的顶层支持

Chrome DevTools 将添加对顶层元素的支持,让开发者更轻松地调试使用顶层元素的代码。

本文介绍了什么是顶层元素、DevTools 如何帮助可视化顶层内容以了解和调试包含顶层元素的 DOM 结构,以及如何实现 DevTools 顶层支持。

什么是顶层和顶层元素?

当您以模态窗口打开 <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。当元素从顶层移除时,系统会移除此类。

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

DevTools 中的顶层支持设计

对顶层的 DevTools 支持有助于开发者了解顶层的概念,并直观地了解顶层内容的变化方式。这些功能可帮助开发者确定以下问题:

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

此外,DevTools 顶层支持有助于直观呈现背景虚化伪元素在顶层堆叠中的显示位置。虽然它不是树形元素,但在顶层的运作方式中发挥着重要作用,对开发者来说也非常有用。

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

  1. 观察顶层堆栈中随时存在的元素。随着元素从顶层添加或移除,顶层表示堆栈会动态变化。
  2. 查看元素在顶层堆叠中的显示位置。
  3. 从树中的顶层元素或元素的背景幕伪元素跳转到顶层表示容器中的元素或背景幕伪元素,反之亦然。

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

顶层容器

为了帮助直观呈现顶层元素,DevTools 会向元素树添加顶层容器。它位于闭合 </html> 标记之后。

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

如需在元素树或顶层容器中查找顶层元素,请点击顶层容器中顶层元素表示法与元素树中同一元素之间的链接,然后再点击相反方向的链接。

如需从顶层容器元素跳转到顶层树元素,请点击顶层容器中相应元素旁边的展开按钮。

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

如需从顶层树元素跳转到顶层容器中的链接,请点击该元素旁边的顶层标记。

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

您可以关闭任何徽章,包括顶层徽章。如需停用徽章,请右键点击任意徽章,选择徽章设置,然后取消选中要隐藏的徽章旁边的对勾。

关闭徽章。

顶层堆栈中的元素顺序

顶层容器会显示堆叠中的元素,但顺序相反。堆叠元素的顶部是顶层容器的元素列表中的最后一个元素。这意味着,顶层容器列表中的最后一个元素是您当前可以在文档中与之互动的元素。

树元素旁边的标记表示元素是否属于顶层,并包含元素在堆叠中的编号。

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

堆栈中元素的顺序。

顶层容器中的背景

如上所述,每个顶层元素都有一个名为 backdrop 的 CSS 伪元素。您可以为此元素设置样式,因此检查该元素并查看其表示形式会很有用。

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

背景堆叠位置。

DOM 树的更改

ElementsTreeElement 是负责在 DevTools 中创建和管理各个 DOM 树元素的类,但不足以实现顶层容器。

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

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

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

为了实现所需的放置,用于呈现元素的类会将 TopLayerContainer 附加为 <html> 标记的下一个同级兄弟。

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

初始设计

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

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

初始设计:

初始设计。

Chrome DevTools Protocol (CDP) 变更

如需实现顶层支持,必须对 Chrome 开发者工具协议 (CDP) 进行更改。CDP 是 DevTools 和 Chromium 之间的通信协议。

我们需要添加以下内容:

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

CDP:DOM.getTopLayerElements 命令

如需显示当前的顶层元素,我们需要一个新的实验性 CDP 命令,该命令会返回顶层元素的节点 ID 列表。每当打开 DevTools 或顶层元素发生变化时,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 命令来获取更新的元素列表。如果我们在每次触发事件时发送导致更改的元素列表或特定元素,则可以避免调用命令这一步骤。不过,在这种情况下,前端将无法控制要推送哪些元素。

之所以这样实现,是因为我们认为由前端决定何时请求顶层节点更好。例如,如果界面中顶层处于收起状态,或者用户使用的 DevTools 面板不包含元素树,则无需获取可能位于树中更深层次的额外节点。