Funcionamento interno de um processo de renderizador
Esta é a parte 3 de 4 da série de blogs sobre o funcionamento dos navegadores. Anteriormente, abordamos a arquitetura de vários processos e o fluxo de navegação. Nesta postagem, veremos o que acontece no processo do renderizador.
O processo do renderizador afeta muitos aspectos do desempenho na Web. Como muita coisa acontece dentro do processo do renderizador, esta postagem é apenas uma visão geral. Se quiser saber mais, a seção "Desempenho" dos Fundamentos da Web tem muitos outros recursos.
Os processos do renderizador lidam com conteúdo da Web
O processo do renderizador é responsável por tudo o que acontece dentro de uma guia. Em um processo do renderizador, a linha de execução principal lida com a maior parte do código enviado ao usuário. Às vezes, partes do seu JavaScript são processadas por linhas de execução de worker, se você usa um web worker ou um service worker. As linhas de execução de composição e rasterização também são executadas dentro de um renderizador para renderizar uma página de maneira eficiente e suave.
A principal função do processo do renderizador é transformar HTML, CSS e JavaScript em uma página da Web com a qual o usuário possa interagir.
Análise
Construção de um DOM
Quando o processo do renderizador recebe uma mensagem de confirmação para uma navegação e começa a receber dados HTML, a linha de execução principal começa a analisar a string de texto (HTML) e a transformá-la em um Model de Document Object (DOM).
O DOM é a representação interna da página do navegador, além da estrutura de dados e da API com que o desenvolvedor Web interage usando JavaScript.
A análise de um documento HTML em um DOM é definida pelo HTML padrão. Talvez você tenha notado que alimentar HTML
em um navegador nunca gera um erro. Por exemplo, a ausência da tag </p>
de fechamento é um HTML válido. Marcação incorreta,
como Hi! <b>I'm <i>Chrome</b>!</i>
(a tag b é fechada antes da tag "i") é tratada como se você tivesse programado
Hi! <b>I'm <i>Chrome</i></b><i>!</i>
. Isso ocorre porque a especificação HTML foi projetada para lidar com esses erros adequadamente. Se você quiser saber como essas coisas são feitas, leia a seção
Uma introdução ao tratamento de erros e casos estranhos no analisador (link em inglês)
da especificação HTML.
Carregamento de recursos secundários
Geralmente, um site usa recursos externos, como imagens, CSS e JavaScript. Esses arquivos precisam ser carregados
pela rede ou pelo cache. A linha de execução principal poderia solicitá-las uma a uma à medida que as encontrava durante a análise para criar um DOM, mas, para acelerar, o "verificação de pré-carregamento" é executado simultaneamente.
Se houver itens como <img>
ou <link>
no documento HTML, o scanner de pré-carregamento exibe os tokens
gerados pelo analisador HTML e envia solicitações para a linha de execução da rede no processo do navegador.
JavaScript pode bloquear a análise
Quando o analisador HTML encontra uma tag <script>
, ele pausa a análise do documento HTML e precisa
carregar, analisar e executar o código JavaScript. Por que o JavaScript pode mudar a forma do
documento usando itens como document.write()
, que muda toda a estrutura do DOM. A visão geral do modelo de análise
na especificação HTML tem um bom diagrama. É por isso que o analisador HTML precisa aguardar a execução do JavaScript
antes de retomar a análise do documento HTML. Se você quiser saber o que acontece na
execução do JavaScript, a equipe do V8 tem conversas e postagens do blog sobre isso (link em inglês).
Dica para o navegador sobre como você quer carregar recursos
Os desenvolvedores Web podem enviar dicas para o navegador de várias maneiras para que os recursos sejam carregados corretamente.
Se o JavaScript não usa document.write()
, adicione o atributo async
ou defer
à tag <script>
. Em seguida, o navegador carrega e executa o código JavaScript de forma assíncrona e não bloqueia a análise. Você também pode usar o módulo JavaScript, se isso for adequado. O <link rel="preload">
é uma forma de informar ao navegador que o recurso é realmente necessário para a navegação atual e que você quer fazer o download o mais rápido possível. Leia mais sobre isso em Priorização de recursos: como o navegador pode ajudar você.
Cálculo do estilo
Ter um DOM não é suficiente para determinar a aparência da página, porque podemos definir o estilo dos elementos no CSS. A linha de execução principal analisa o CSS e determina o estilo calculado para cada nó do DOM. São informações sobre que tipo de estilo é aplicado a cada elemento com base em seletores de CSS. Confira
essas informações na seção computed
do DevTools.
Mesmo que você não forneça nenhum CSS, cada nó do DOM tem um estilo computado. A tag <h1>
é exibida
acima da tag <h2>
e as margens são definidas para cada elemento. Isso porque o navegador tem
uma folha de estilo padrão. Se você quiser saber como é o CSS padrão do Chrome, consulte o código-fonte aqui.
Layout
Agora, o processo do renderizador sabe a estrutura de um documento e os estilos de cada nó, mas isso não é suficiente para renderizar uma página. Imagine que você está tentando descrever uma pintura para um amigo por telefone. "Há um grande círculo vermelho e um pequeno quadrado azul" não é informação suficiente para que seu amigo saiba exatamente como seria a pintura.
O layout é um processo para encontrar a geometria dos elementos. A linha de execução principal percorre o DOM e
os estilos calculados e cria a árvore de layout, que tem informações como coordenadas x y e tamanhos de
caixas delimitadoras. A árvore de layout pode ter uma estrutura semelhante à árvore do DOM, mas contém apenas informações
relacionadas ao que é visível na página. Se display: none
for aplicado, esse elemento não fará parte da
árvore de layout. No entanto, um elemento com visibility: hidden
estará na árvore de layout. Da mesma forma,
se um pseudoelemento com conteúdo como p::before{content:"Hi!"}
for aplicado, ele será incluído na
árvore de layout, mesmo que não esteja no DOM.
Determinar o layout de uma página é uma tarefa desafiadora. Mesmo o layout de página mais simples, como um fluxo de blocos de cima para baixo, precisa considerar o tamanho da fonte e onde quebrar a linha, porque isso afeta o tamanho e a forma de um parágrafo, o que afeta onde o parágrafo a seguir precisa estar.
O CSS pode fazer o elemento flutuar para um lado, mascarar o item flutuante e alterar as direções de escrita. Como você pode imaginar, esse estágio de layout tem uma tarefa poderosa. No Chrome, uma equipe inteira de engenheiros trabalha no layout. Se você quiser ver detalhes do trabalho, algumas palestras da BlinkOn Conference são gravadas e são bastante interessantes de assistir.
Tinta
Ter um DOM, um estilo e um layout ainda não é suficiente para renderizar uma página. Digamos que você esteja tentando reproduzir uma pintura. Você sabe o tamanho, a forma e a localização dos elementos, mas ainda precisa julgar a ordem em que os pinta.
Por exemplo, z-index
pode ser definido para determinados elementos. Nesse caso, a pintura na ordem dos
elementos gravados no HTML resultará em uma renderização incorreta.
Nesta etapa de pintura, a linha de execução principal percorre a árvore de layout para criar registros de pintura. O registro de pintura é
uma nota do processo de pintura, como "primeiro plano de fundo, depois texto e depois retângulo". Se você desenhou no elemento
<canvas>
usando JavaScript, esse processo pode ser familiar para você.
Atualizar o pipeline de renderização é dispendioso
O mais importante a entender no pipeline de renderização é que o resultado da operação anterior é usado em cada etapa para criar novos dados. Por exemplo, se algo mudar na árvore de layout, será necessário gerar novamente a ordem de pintura para as partes afetadas do documento.
Se você estiver animando elementos, o navegador terá que executar essas operações entre cada quadro. A maioria das nossas telas atualiza a tela 60 vezes por segundo (60 QPS). A animação aparecerá suave para os olhos humanos quando você mover os itens pela tela em cada frame. No entanto, se a animação não tiver os frames intermediários, a página aparecerá "instável".
Mesmo que as operações de renderização estejam acompanhando a atualização da tela, esses cálculos são executados na linha de execução principal, o que significa que eles podem ser bloqueados quando o aplicativo está executando o JavaScript.
É possível dividir a operação de JavaScript em pequenos blocos e programar a execução em cada frame usando
requestAnimationFrame()
. Para saber mais sobre esse tópico, consulte Otimizar a execução do JavaScript. Também é possível executar o JavaScript no Web Workers
para evitar o bloqueio da linha de execução principal.
Composição
Como você desenharia uma página?
Agora que o navegador sabe a estrutura do documento, o estilo de cada elemento, a geometria da página e a ordem de pintura, como ele desenha uma página? A transformação dessas informações em pixels na tela é chamada de rasterização.
Talvez uma maneira simples de lidar com isso seja rasterizar partes dentro da janela de visualização. Se um usuário rolar a página, mova o frame rasterizado e preencha as partes que faltam fazendo uma varredura mais. Foi assim que o Chrome lidava com a varredura quando foi lançado. No entanto, os navegadores mais recentes executam um processo mais sofisticado, chamado de composição.
O que é composição
A composição é uma técnica para separar partes de uma página em camadas, fazer a varredura separadamente e compor como uma página em uma linha de execução separada, chamada de linha de execução de composição. Se a rolagem acontecer, como as camadas já foram rasterizadas, basta compor um novo frame. A animação pode ser feita da mesma maneira, movendo camadas e compondo um novo frame.
É possível conferir como seu site é dividido em camadas no DevTools usando o painel Layers.
Divisão em camadas
Para descobrir quais elementos precisam estar em quais camadas, a linha de execução principal percorre a
árvore de layout para criá-la. Essa parte é chamada de "Atualizar a árvore de camadas" no painel
de desempenho do DevTools. Se determinadas partes de uma página que deveriam ser uma camada separada (como o menu lateral
deslizante) não estiverem recebendo uma, você pode indicar ao navegador usando o atributo will-change
no CSS.
Pode ser tentador fornecer camadas para cada elemento, mas a composição em um número excessivo de camadas pode resultar em uma operação mais lenta do que a rasterização de partes pequenas de uma página a cada frame. Por isso, é fundamental medir o desempenho da renderização do aplicativo. Para saber mais sobre o assunto, consulte Usar propriedades somente para compositores e gerenciar o número de camadas.
Varredura e composição fora da linha de execução principal
Depois que a árvore de camadas é criada e as ordens de pintura são determinadas, a linha de execução principal confirma essas informações na linha de execução do compositor. Em seguida, o thread do compositor faz a varredura de cada camada. Uma camada pode ser grande, como todo o tamanho de uma página. Portanto, a linha de execução do compositor os divide em blocos e envia cada bloco para linhas de execução rasterizadas. As linhas de execução rasterizadas fazem a varredura de cada bloco e os armazenam na memória da GPU.
A linha de execução do compositor pode priorizar diferentes linhas de execução de varredura para que os itens na janela de visualização (ou próximos) possam ser rasterizados primeiro. Uma camada também tem vários blocos com diferentes resoluções para lidar com ações como o aumento de zoom.
Depois que os blocos são rasterizados, a linha de execução do compositor coleta informações de blocos chamadas quadrados de desenho para criar um frame do compositor.
Desenhar quadriciclos | Contém informações como a localização do bloco na memória e onde ele deve ser desenhado considerando a criação da página. |
Frame do compositor | Uma coleção de quadriculados de desenho que representa o frame de uma página. |
Um frame de compositor é enviado para o processo de navegação via IPC. Nesse momento, outro frame de compositor pode ser adicionado a partir da linha de execução de interface para a mudança da interface do navegador ou de outros processos de renderizador para extensões. Esses frames do compositor são enviados à GPU para exibi-lo em uma tela. Se houver um evento de rolagem, a linha de execução do compositor criará outro frame para ser enviado à GPU.
A vantagem da composição é que ela é feita sem envolver a linha de execução principal. A linha de execução do criador não precisa esperar o cálculo do estilo ou a execução do JavaScript. É por isso que a composição apenas de animações é considerada a melhor para um desempenho fluido. Se o layout ou a pintura precisar ser calculado novamente, a linha de execução principal precisará estar envolvida.
Resumo
Nesta postagem, analisamos o pipeline de renderização desde a análise até a composição. Esperamos que agora você possa ler mais sobre a otimização de desempenho de um site.
Na próxima e na última postagem desta série, veremos a linha de execução do compositor em mais detalhes e
veremos o que acontece quando uma entrada do usuário, como mouse move
e click
, é recebida.
Você gostou da postagem? Se você tiver dúvidas ou sugestões para a próxima postagem, fique à vontade na seção de comentários abaixo ou com @kosamari no Twitter.