Suporte à camada superior no Chrome DevTools

O Chrome DevTools está adicionando suporte a elementos da camada superior, facilitando a depuração do código que usa esses elementos.

Este artigo descreve o que são os elementos da camada superior, como o DevTools ajuda a visualizar o conteúdo da camada superior para entender e depurar a estrutura do DOM que contém elementos da camada superior e como o suporte à camada superior do DevTools é implementado.

O que são a camada superior e os elementos da camada superior?

O que exatamente acontece internamente quando você abre um <dialog> como modal? 🤔

Ele é colocado em uma camada superior. O conteúdo da camada superior é renderizado sobre todos os outros conteúdos. Por exemplo, uma caixa de diálogo modal precisa aparecer acima de todo o outro conteúdo do DOM. Assim, o navegador renderiza automaticamente esse elemento em uma "camada superior", em vez de forçar os autores a lutar manualmente com o z-index. Um elemento da camada de cima aparece sobre um elemento, mesmo com o z-index mais alto.

A camada superior pode ser descrita como "a camada de empilhamento mais alta". Cada documento tem uma única viewport associada e, portanto, também uma única camada superior. Vários elementos podem estar dentro da camada superior ao mesmo tempo. Quando isso acontece, elas são empilhadas, com a última no topo. Em outras palavras, todos os elementos da camada superior são colocados em uma pilha last in, first out (LIFO) na camada superior.

O elemento <dialog> não é o único elemento renderizado pelo navegador em uma camada superior. Atualmente, os elementos da camada superior são: popovers, caixas de diálogo modal e elementos em um modo de tela cheia.

Examine a implementação da caixa de diálogo a seguir:

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

Confira uma demonstração com algumas caixas de diálogo que têm estilos aplicados aos planos de fundo (planos de fundo descritos abaixo):

O que é um plano de fundo?

Felizmente, há uma maneira de personalizar o conteúdo abaixo do elemento da camada superior.

Todos os elementos na camada de cima têm um pseudoelemento CSS chamado fundo.

O plano de fundo é uma caixa do tamanho da janela de visualização que é renderizada imediatamente abaixo de qualquer elemento da camada superior. O pseudoelemento ::backdrop permite ocultar, estilizar ou ocultar completamente tudo o que está abaixo do elemento quando ele é o primeiro na camada de cima.

Quando você cria vários elementos modais, o navegador desenha o plano de fundo imediatamente abaixo do elemento em primeiro plano e em cima de outros elementos em tela cheia.

Confira como estilizar um plano de fundo:

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

Como mostrar apenas o primeiro plano de fundo?

Cada elemento da camada superior tem um plano de fundo que pertence a uma pilha de camadas superior. Esses planos de fundo são projetados para se sobrepor. Portanto, se a opacidade de um plano de fundo não for de 100%, os planos de fundo abaixo dele vão ficar visíveis.

Se apenas o primeiro plano de fundo na pilha de camadas superior precisa estar visível, você pode fazer isso mantendo o controle dos identificadores de item na pilha de camadas superior.

Se o elemento adicionado não for o primeiro na camada superior, a função chamada quando o elemento for colocado na camada superior vai aplicar uma classe hiddenBackdrop ao ::backdrop. Essa classe é removida quando o elemento é removido da camada superior.

Confira o código neste exemplo de demonstração:

Design de suporte à camada superior no DevTools

O suporte das DevTools para a camada superior ajuda os desenvolvedores a entender o conceito da camada superior e a visualizar como o conteúdo dela muda. Esses recursos ajudam os desenvolvedores a identificar o seguinte:

  • Os elementos na camada de cima a qualquer momento e a ordem deles.
  • O elemento na parte de cima da pilha a qualquer momento.

Além disso, o suporte à camada superior do DevTools ajuda a visualizar a posição do pseudoelemento de plano de fundo na pilha da camada superior. Embora não seja um elemento de árvore, ele desempenha um papel importante no funcionamento da camada superior e pode ser útil para os desenvolvedores.

Com os recursos de suporte da camada superior, é possível:

  1. Observe quais elementos estão na pilha da camada superior a qualquer momento. A pilha de representação da camada superior muda dinamicamente à medida que os elementos são adicionados ou removidos da camada superior.
  2. Confira a posição do elemento na pilha da camada superior.
  3. Saltar do elemento da camada superior ou do pseudoelemento de pano de fundo dos elementos na árvore para o elemento ou o pseudoelemento de pano de fundo no contêiner de representação da camada superior e vice-versa.

Vamos conferir como usar esses recursos.

Contêiner da camada superior

Para ajudar a visualizar os elementos da camada superior, o DevTools adiciona um contêiner da camada superior à árvore de elementos. Ele fica depois da tag </html> de fechamento.

Esse contêiner permite observar os elementos na pilha da camada superior a qualquer momento. O contêiner da camada superior é uma lista de links para os elementos da camada superior e os planos de fundo deles. A pilha de representação da camada superior muda dinamicamente à medida que os elementos são adicionados ou removidos da camada superior.

Para encontrar elementos da camada superior na árvore de elementos ou no contêiner da camada superior, clique nos links da representação do elemento da camada superior no contêiner da camada superior para o mesmo elemento na árvore de elementos e vice-versa.

Para pular do elemento do contêiner da camada superior para o elemento da árvore da camada superior, clique no botão revelar ao lado do elemento no contêiner da camada superior.

Saltar do link do contêiner da camada superior para o elemento.

Para pular do elemento da árvore da camada superior para o link no contêiner da camada superior, clique no ícone camada superior ao lado do elemento.

Saltar de um elemento para o link do contêiner da camada superior.

É possível desativar qualquer selo, incluindo o da camada superior. Para desativar os selos, clique com o botão direito do mouse em qualquer selo, escolha Configurações do selo e remova as marcas de seleção ao lado dos selos que você quer ocultar.

Desativando o selo.

Ordem dos elementos na pilha da camada superior

O contêiner da camada superior mostra os elementos como aparecem na pilha, mas na ordem inversa. O elemento da pilha é o último na lista de elementos do contêiner da camada de cima. Isso significa que o último elemento na lista de contêineres da camada superior é o elemento com que você pode interagir no documento.

Os selos ao lado dos elementos da árvore indicam se os elementos pertencem à camada superior e contêm o número de posição de um elemento na pilha.

Nesta captura de tela, a pilha da camada superior consiste em dois elementos, com o segundo elemento na parte de cima da pilha. Se você remover o segundo elemento, o primeiro vai ser movido para o topo.

A ordem dos elementos na pilha.

Planos de fundo no contêiner da camada superior

Como mencionado acima, cada elemento da camada superior tem um pseudoelemento CSS chamado "backdrop". Você pode estilizar esse elemento. Por isso, é útil inspecionar e conferir a representação dele.

Na árvore de elementos, um elemento de pano de fundo fica antes da tag de fechamento do elemento a que pertence. No entanto, no contêiner da camada de cima, um link de plano de fundo é listado logo acima do elemento da camada de cima a que ele pertence.

Posição da pilha de planos de fundo.

Mudanças na árvore do DOM

ElementsTreeElement, a classe responsável por criar e gerenciar elementos individuais da árvore DOM no DevTools, não era suficiente para implementar um contêiner de camada superior.

Para mostrar o contêiner da camada superior como um nó na árvore, adicionamos uma nova classe que cria nós de elementos de árvore das Ferramentas do desenvolvedor. Anteriormente, a classe responsável por criar a árvore de elementos do DevTools inicializava cada TreeElement com um DOMNode, que é uma classe com um backendNodeId e outras propriedades relacionadas ao back-end. O backendNodeId, por sua vez, é atribuído no back-end.

O nó de contêiner da camada superior, que tem uma lista de links para elementos da camada superior, precisava se comportar como um nó de elemento de árvore normal. No entanto, esse nó não é um nó DOM "real", e o back-end não precisa criar o nó de contêiner da camada superior.

Para criar um nó de front-end que represente a camada superior, adicionamos um novo tipo de nó de front-end que é criado sem um DOMNode. Esse elemento de contêiner da camada superior é o primeiro nó de front-end que não tem DOMNode, ou seja, ele existe apenas no front-end, e o back-end não "sabe" sobre ele. Para ter o mesmo comportamento de outros nós, criamos uma nova classe TopLayerContainer que estende a classe UI.TreeOutline.TreeElement, responsável pelo comportamento dos nós da interface.

Para alcançar o posicionamento desejado, a classe que renderiza um elemento anexa TopLayerContainer como o próximo irmão da tag <html>.

Um novo selo da camada superior indica que o elemento está na camada superior e serve como um link para o atalho dele no elemento TopLayerContainer.

Design inicial

No início, o plano era duplicar os elementos da camada superior no contêiner da camada superior em vez de criar uma lista de links para os elementos. Não implementamos essa solução devido à forma como a busca de elementos filhos funciona no DevTools. Cada elemento tem um ponteiro pai usado para buscar filhos, e é impossível ter vários ponteiros. Portanto, não podemos ter um nó que se expanda e contenha todas as crianças em vários lugares na árvore. Em geral, o sistema não foi criado pensando em subtrees duplicados.

A solução que encontramos foi criar links para os nós DOM do front-end em vez de duplicá-los. A classe responsável por criar links para elementos no DevTools é ShortcutTreeElement, que estende o UI.TreeOutline.TreeElement. O ShortcutTreeElement tem o mesmo comportamento que outros elementos da árvore DOM do DevTools, mas não tem um nó correspondente no back-end e tem um botão que faz link para um ElementsTreeElement. Cada ShortcutTreeElement para o nó da camada superior tem um ShortcutTreeElement filho que vincula à representação de um pseudoelemento ::backdrop na árvore DOM das ferramentas do desenvolvedor.

Design inicial:

Design inicial.

Mudanças no protocolo do Chrome DevTools (CDP, na sigla em inglês)

Para implementar o suporte à camada superior, são necessárias mudanças no protocolo do Chrome DevTools (CDP, na sigla em inglês). O CDP serve como um protocolo de comunicação entre o DevTools e o Chromium.

Precisamos adicionar o seguinte:

  • Um comando para chamar do front-end a qualquer momento.
  • Um evento acionado no front-end pelo back-end.

CDP: comando DOM.getTopLayerElements

Para mostrar os elementos da camada superior atual, precisamos de um novo comando experimental do CDP que retorne uma lista de IDs de nó dos elementos que estão na camada superior. O DevTools chama esse comando sempre que é aberto ou quando os elementos da camada superior mudam. O comando se parece com isto:

  # 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: evento DOM.topLayerElementsUpdated

Para receber a lista atualizada dos elementos da camada superior, precisamos que cada mudança desses elementos acione um evento experimental do CDP. Esse evento informa o front-end sobre a mudança, que chama o comando DOM.getTopLayerElements e recebe a lista de novos elementos.

O evento é semelhante ao seguinte:

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

Considerações sobre o CDP

Havia várias opções de como o suporte do CDP para a camada superior poderia ser implementado. Outra opção que consideramos foi criar um evento que retornasse a lista dos elementos da camada superior, em vez de apenas informar o front-end sobre a adição ou remoção de um elemento da camada superior.

Como alternativa, podemos criar dois eventos em vez do comando: topLayerElementAdded e topLayerElementRemoved. Nesse caso, receberíamos um elemento e precisaríamos gerenciar a matriz dos elementos da camada superior no front-end.

Atualmente, um evento de front-end chama o comando getTopLayerElements para receber uma lista de elementos atualizados. Se enviássemos uma lista de elementos ou um elemento específico que causou a mudança toda vez que um evento é acionado, poderíamos evitar uma etapa de chamada do comando. No entanto, nesse caso, o front-end perderia o controle sobre quais elementos são enviados.

Implementamos dessa forma porque, na nossa opinião, é melhor se o front-end decidir quando solicitar os nós da camada superior. Por exemplo, se a camada superior estiver recolhida na interface ou se o usuário estiver usando um painel do DevTools que não tem a árvore de elementos, não será necessário buscar os nós extras que podem estar mais profundos na árvore.