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%,则可以看到下面的背景。

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

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

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

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

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

  • 顶层中的元素随时可见及其顺序。
  • 位于堆栈顶部的元素。

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

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

  1. 随时观察哪些元素位于顶层堆栈中。顶层表示堆栈会随着元素的增减而动态变化。
  2. 查看元素在顶层堆栈中的位置。
  3. 从顶层元素或顶层元素跳转到其他元素将树中的背景幕伪元素映射到顶层表示容器中的元素或背景幕伪元素。

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

顶层容器

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

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

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

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

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

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

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

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

正在关闭徽章。

顶层堆栈中的元素顺序

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

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

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

元素在堆栈中的顺序。

顶层容器中的背景幕

如上所述,每个顶层元素都有一个称为背景幕的 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 命令来获取已更新元素的列表。如果我们在每次事件触发时发送导致更改的元素列表或特定元素,就可以避免调用命令的一个步骤。 但是,在这种情况下,前端将无法控制推送哪些元素。

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