Поддержка верхнего уровня в Chrome DevTools

Алина Варкки
Alina Varkki

В Chrome DevTools добавлена ​​поддержка элементов верхнего уровня, что упрощает разработчикам отладку кода, использующего элементы верхнего уровня.

В этой статье описывается, что такое элементы верхнего уровня, как DevTools помогают визуализировать содержимое верхнего уровня для понимания и отладки структуры DOM, содержащей элементы верхнего уровня, а также как реализована поддержка верхнего уровня DevTools.

Что такое верхний слой и элементы верхнего слоя?

Что именно происходит внутри, когда вы открываете <dialog> в модальном режиме? 🤔

Его кладут в верхний слой. Содержимое верхнего слоя отображается поверх всего остального содержимого. Например, модальное диалоговое окно должно появляться поверх всего остального содержимого DOM, чтобы браузер автоматически отображал этот элемент на «верхнем уровне», а не заставлял авторов вручную бороться с z-index. Элемент верхнего слоя отображается поверх элемента даже с самым высоким индексом z.

Верхний слой можно охарактеризовать как «самый высокий уровень укладки». Каждый документ имеет одно связанное окно просмотра и, следовательно, один верхний слой. Внутри верхнего слоя может одновременно находиться несколько элементов. Когда это происходит, они складываются друг на друга, последний сверху. Другими словами, все элементы верхнего слоя помещаются в стек «последним пришел — первым ушел» (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> .

Этот контейнер позволяет вам наблюдать за элементами в стеке верхнего слоя в любое время. Контейнер верхнего слоя представляет собой список ссылок на элементы верхнего слоя и их фоны. Стек представления верхнего уровня изменяется динамически по мере добавления или удаления элементов из верхнего слоя.

Чтобы найти элементы верхнего слоя в дереве элементов или контейнере верхнего слоя, щелкните ссылки от представления элемента верхнего слоя в контейнере верхнего слоя к тому же элементу в дереве элементов и обратно.

Чтобы перейти от элемента контейнера верхнего слоя к элементу дерева верхнего слоя, нажмите кнопку раскрытия рядом с элементом в контейнере верхнего слоя.

Переход от ссылки контейнера верхнего уровня к элементу.

Чтобы перейти от элемента дерева верхнего слоя к ссылке в контейнере верхнего слоя, щелкните значок верхнего слоя рядом с элементом.

Переход от элемента к ссылке на контейнер верхнего уровня.

Вы можете отключить любой значок, в том числе верхний слой . Чтобы отключить значки, щелкните правой кнопкой мыши любой значок, выберите «Настройки значка» и снимите галочки рядом со значками, которые хотите скрыть.

Отключение значка.

Порядок элементов в стеке верхнего слоя

Контейнер верхнего слоя показывает элементы в том виде, в котором они появляются в стеке, но в обратном порядке. Верхний элемент стека является последним в списке элементов контейнера верхнего слоя. Это означает, что последний элемент в списке контейнеров верхнего уровня — это элемент, с которым вы в данный момент можете взаимодействовать в документе.

Значки рядом с элементами дерева указывают, принадлежат ли элементы верхнему слою, и содержат номер позиции элемента в стеке.

На этом снимке экрана стек верхнего слоя состоит из двух элементов, причем второй элемент находится наверху стека. Если вы удалите второй элемент, первый переместится наверх.

Порядок элементов в стеке.

Фоны в контейнере верхнего слоя

Как упоминалось выше, каждый элемент верхнего слоя имеет псевдоэлемент CSS, называемый фоном. Вы можете стилизовать этот элемент, поэтому полезно также проверить его и просмотреть его представление.

В дереве элементов элемент фона располагается перед закрывающим тегом элемента, которому он принадлежит. Однако в контейнере верхнего слоя ссылка на фон отображается прямо над элементом верхнего слоя, которому она принадлежит.

Положение стопки фонов.

Изменения в дереве DOM

ElementsTreeElement , класс, ответственный за создание и управление отдельными элементами дерева DOM в DevTools, был недостаточен для реализации контейнера верхнего уровня.

Чтобы отобразить контейнер верхнего уровня в качестве узла в дереве, мы добавили новый класс, который создает узлы элементов дерева DevTools. Ранее класс, ответственный за создание дерева элементов DevTools, инициализировал каждый TreeElement с помощью DOMNode , который представляет собой класс с backendNodeId и другими свойствами, связанными с серверной частью. backendNodeId , в свою очередь, назначается на бэкэнде.

Узел контейнера верхнего уровня, который имеет список ссылок на элементы верхнего уровня, должен вести себя как обычный узел элемента дерева. Однако этот узел не является «настоящим» узлом DOM, и серверной части не требуется создавать узел контейнера верхнего уровня.

Чтобы создать внешний узел, представляющий верхний уровень, мы добавили новый тип внешнего узла, который создается без DOMNode . Этот элемент контейнера верхнего уровня является первым узлом внешнего интерфейса, у которого нет DOMNode , то есть он существует только во внешнем интерфейсе, а серверная часть о нем не «знает». Чтобы поведение было таким же, как у других узлов, мы создали новый класс TopLayerContainer , который расширяет класс UI.TreeOutline.TreeElement , который отвечает за поведение узлов внешнего интерфейса.

Чтобы добиться желаемого размещения, класс, отображающий элемент, присоединяет TopLayerContainer в качестве следующего брата тега <html> .

Новый значок верхнего слоя указывает, что элемент находится в верхнем слое, и служит ссылкой на ярлык этого элемента в элементе TopLayerContainer .

Первоначальный дизайн

Сначала планировалось дублировать элементы верхнего слоя в контейнер верхнего слоя вместо создания списка ссылок на элементы. Мы не реализовали это решение из-за того, как в DevTools работает выборка дочерних элементов. Каждый элемент имеет родительский указатель, используемый для извлечения дочерних элементов, и невозможно иметь несколько указателей. Следовательно, у нас не может быть узла, который правильно расширяется и содержит всех дочерних узлов в нескольких местах дерева. В целом система не была построена с учетом дублирования поддеревьев.

Компромисс, к которому мы пришли, заключался в создании ссылок на узлы внешнего интерфейса DOM вместо дублирования этих узлов. Класс, отвечающий за создание ссылок на элементы в DevTools, — ShortcutTreeElement , который расширяет UI.TreeOutline.TreeElement . ShortcutTreeElement имеет такое же поведение, как и другие элементы дерева DOM DevTools, но не имеет соответствующего узла на внутренней стороне и имеет кнопку, которая ссылается на ElementsTreeElement . Каждый ShortcutTreeElement узла верхнего уровня имеет дочерний элемент ShortcutTreeElement , который ссылается на представление псевдоэлемента ::backdrop в дереве DOM DevTools.

Первоначальный дизайн:

Первоначальный дизайн.

Изменения протокола Chrome DevTools (CDP)

Для реализации поддержки верхнего уровня необходимы изменения в протоколе Chrome DevTools (CDP) . CDP служит протоколом связи между DevTools и Chromium.

Нам нужно добавить следующее:

  • Команда для вызова из фронтенда в любое время.
  • Событие, которое срабатывает во внешнем интерфейсе с внутренней стороны.

CDP: команда DOM.getTopLayerElements

Чтобы отобразить текущие элементы верхнего слоя, нам нужна новая экспериментальная команда CDP, которая возвращает список идентификаторов узлов элементов, находящихся в верхнем слое. 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 верхнего уровня. Другой вариант, который мы рассматривали, заключался в создании события, которое возвращало бы список элементов верхнего слоя, а не просто информировало интерфейсную часть о добавлении или удалении элемента верхнего слоя.

В качестве альтернативы мы могли бы создать вместо команды два события: topLayerElementAdded и topLayerElementRemoved . В этом случае мы получим элемент, и нам нужно будет управлять массивом элементов верхнего слоя во внешнем интерфейсе.

В настоящее время событие внешнего интерфейса вызывает команду getTopLayerElements , чтобы получить список обновленных элементов. Если бы мы отправляли список элементов или конкретный элемент, вызвавший изменение, каждый раз при запуске события, мы могли бы избежать одного шага вызова команды. Однако в этом случае интерфейс потеряет контроль над тем, какие элементы будут отправлены.

Мы реализовали это таким образом, потому что, по нашему мнению, будет лучше, если интерфейс будет решать, когда запрашивать узлы верхнего уровня. Например, если верхний уровень в пользовательском интерфейсе свернут или пользователь использует панель DevTools, в которой нет дерева элементов, нет необходимости получать дополнительные узлы, которые могут находиться глубже в дереве.