Funcionamento interno de um processo de renderizador
Esta é a terceira parte de uma série de quatro blogs sobre como os navegadores funcionam. Anteriormente, abordamos arquitetura de vários processos e fluxo de navegação. Nesta postagem, vamos analisar o que acontece dentro do processo de renderização.
O processo do renderizador afeta muitos aspectos do desempenho da Web. Como há muitas coisas acontecendo dentro do processo do renderizador, esta postagem é apenas uma visão geral. Se você quiser se aprofundar, a seção Performance dos Fundamentos da Web tem muitos outros recursos.
Os processos do renderizador processam o conteúdo da Web
O processo de renderização é responsável por tudo o que acontece em uma guia. Em um processo de renderizador, a linha de execução principal processa a maior parte do código enviado ao usuário. Às vezes, partes do JavaScript são processadas por linhas de execução de worker se você usa um worker da Web ou um service worker. As linhas de execução do compositor e do raster também são executadas dentro de um processo de renderizador para renderizar uma página de maneira eficiente e suave.
A principal função do processo de renderização é transformar HTML, CSS e JavaScript em uma página da Web com a qual o usuário pode 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 transformar em um Documento Object Model (DOM).
O DOM é uma representação interna da página de um navegador, bem como a estrutura de dados e a API com que o desenvolvedor da Web pode interagir usando JavaScript.
A análise de um documento HTML em um DOM é definida pelo
padrão HTML. Você pode ter notado que o envio de HTML para um navegador
nunca gera um erro. Por exemplo, a tag </p>
de fechamento ausente é 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 escrito
Hi! <b>I'm <i>Chrome</i></b><i>!</i>
. Isso ocorre porque a especificação HTML foi projetada para
processar esses erros com facilidade. Se você tem curiosidade sobre como essas coisas são feitas, leia a seção
"Uma introdução ao processamento de erros e casos estranhos no analisador"
da especificação do HTML.
Carregamento de subrecurso
Um site geralmente usa recursos externos, como imagens, CSS e JavaScript. Esses arquivos precisam ser
carregados da rede ou do cache. A linha de execução principal pode solicitá-los um por um à medida que os encontra
durante a análise para criar um DOM. No entanto, para acelerar, o "scanner de pré-carregamento" é executado simultaneamente.
Se houver elementos como <img>
ou <link>
no documento HTML, o scanner de pré-carregamento vai conferir os tokens
gerados pelo analisador de HTML e enviar solicitações para a linha de execução de rede no processo do navegador.

O 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 quê? Porque o JavaScript pode mudar a forma do
documento usando elementos como document.write()
, que muda toda a estrutura do DOM. A visão geral do modelo de análise
na especificação do HTML tem um diagrama bacana. É por isso que o analisador HTML precisa esperar a execução do JavaScript
antes de retomar a análise do documento HTML. Se você tem curiosidade sobre o que acontece na
execução do JavaScript, a equipe do V8 tem palestras e postagens no blog sobre o assunto.
Indicar ao navegador como você quer carregar recursos
Há muitas maneiras de os desenvolvedores da Web enviarem dicas ao navegador para carregar recursos corretamente.
Se o JavaScript não usar 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. Também é possível usar o módulo JavaScript, se for adequado. <link rel="preload">
é uma forma de informar ao navegador que o recurso é definitivamente necessário para a navegação atual e que você quer fazer o download o mais rápido possível. Saiba mais em Priorização de recursos: como usar o navegador para ajudar você.
Cálculo do estilo
Ter um DOM não é suficiente para saber como a página vai ficar, porque podemos estilizar os elementos da página
no CSS. A linha de execução principal analisa o CSS e determina o estilo computado para cada nó do DOM. Essas são
informações sobre o tipo de estilo aplicado a cada elemento com base em seletores de CSS. Você pode conferir
essas informações na seção computed
do DevTools.

Mesmo que você não forneça nenhum CSS, cada nó do DOM terá um estilo computado. A tag <h1>
é exibida
maior que a tag <h2>
, e as margens são definidas para cada elemento. Isso ocorre porque o navegador tem uma
folha de estilo padrão. Se você quiser saber como é o CSS padrão do Chrome,
confira o código-fonte aqui.
Layout
Agora o processo de renderização conhece 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 seu amigo por telefone. "Há um círculo vermelho grande e um quadrado azul pequeno" 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 computados e cria a árvore de layout, que tem informações como coordenadas x y e tamanhos
de caixa de limite. A árvore de layout pode ter uma estrutura semelhante à árvore DOM, mas contém apenas informações
relacionadas ao que está 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. Até mesmo o layout de página mais simples, como um fluxo de bloco de cima para baixo, precisa considerar o tamanho da fonte e onde fazer quebras de linha, porque elas afetam o tamanho e a forma de um parágrafo, o que afeta onde o parágrafo seguinte precisa estar.
O CSS pode fazer com que o elemento flutue para um lado, mascarar o item de overflow e mudar as direções de escrita. Como você pode imaginar, essa etapa de layout tem uma tarefa poderosa. No Chrome, uma equipe inteira de engenheiros trabalha no layout. Se você quiser saber mais sobre o trabalho deles, confira algumas palestras da BlinkOn Conference, que foram gravadas e são muito interessantes.
Tinta

Ter um DOM, estilo e layout ainda não é suficiente para renderizar uma página. Digamos que você está tentando reproduzir uma pintura. Você sabe o tamanho, a forma e a localização dos elementos, mas ainda precisa decidir em que ordem pintá-los.
Por exemplo, z-index
pode ser definido para determinados elementos. Nesse caso, a pintura na ordem dos
elementos gravados no HTML resultará em 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 observação do processo de pintura, como "primeiro o plano de fundo, depois o texto e depois o retângulo". Se você já desenhou no
elemento <canvas>
usando JavaScript, esse processo pode ser familiar para você.

Atualizar o pipeline de renderização é caro
A coisa mais importante a entender no pipeline de renderização é que, em cada etapa, o resultado da operação anterior é usado para criar novos dados. Por exemplo, se algo mudar na árvore de layout, a ordem de pintura precisará ser regenerada para as partes afetadas do documento.
Se você estiver animando elementos, o navegador terá que executar essas operações entre cada frame. A maioria das telas atualiza 60 vezes por segundo (60 qps). A animação vai parecer suave para os olhos humanos quando você mover coisas pela tela em cada frame. No entanto, se a animação perder os frames no meio, a página vai parecer "desajeitada".

Mesmo que suas operações de renderização estejam acompanhando a atualização da tela, esses cálculos estão sendo executados na linha de execução principal, o que significa que ela pode ser bloqueada quando o aplicativo estiver executando JavaScript.

É possível dividir a operação do JavaScript em pequenos pedaços e programar para executar em cada frame usando
requestAnimationFrame()
. Para mais informações sobre esse assunto, consulte
Otimizar a execução do JavaScript. Você também pode executar o JavaScript em workers da Web
para evitar o bloqueio da linha de execução principal.

Composição
Como você desenharia uma página?
Agora que o navegador conhece 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 conversão dessas informações em pixels na tela é chamada de rasterização.
Talvez uma maneira ingênua de lidar com isso seja rasterizar partes dentro da área de visualização. Se um usuário rolar a página, mova o frame rasterizado e preencha as partes ausentes com mais rasterização. É assim que o Chrome lidava com a rasterização quando foi lançado. No entanto, o navegador moderno executa um processo mais sofisticado chamado composição.
O que é composição
A composição é uma técnica para separar partes de uma página em camadas, rasterizar separadamente e compor como uma página em uma linha de execução separada chamada de linha de execução do compositor. Se o rolagem acontecer, já que as camadas já estão rasterizadas, tudo o que ela precisa fazer é compor um novo frame. A animação pode ser feita da mesma forma, movendo camadas e combiná-las em um novo frame.
Você pode conferir como o site é dividido em camadas no DevTools usando o painel de camadas.
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 criar a árvore de camadas. Essa parte é chamada de "Atualizar árvore de camadas" no painel de desempenho
do DevTools. Se determinadas partes de uma página que precisam ser uma camada separada (como o menu lateral
deslizante) não estiverem recebendo uma, você poderá sugerir ao navegador usando o atributo will-change
no CSS.

Você pode ter vontade de adicionar camadas a todos os elementos, mas a composição em um número excessivo de camadas pode resultar em uma operação mais lenta do que rasterizar pequenas partes de uma página a cada frame. Portanto, é essencial medir o desempenho de renderização do seu aplicativo. Para saber mais sobre o assunto, consulte Usar apenas propriedades do compositor e gerenciar a contagem de camadas.
Raster 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. A linha de execução do compositor rasteriza cada camada. Uma camada pode ser grande como o comprimento de uma página inteira. Por isso, a linha de execução do compositor as divide em blocos e envia cada bloco para linhas de execução de raster. Os threads rasterizam 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 raster para que as coisas dentro da viewport ou nas proximidades possam ser rasterizadas primeiro. Uma camada também tem vários blocos para diferentes resoluções para processar ações como o zoom.
Depois que os blocos são rasterizados, a linha de execução do compositor coleta informações de blocos chamadas draw quads para criar um frame do compositor.
Desenhar quadriláteros | Contém informações como o local do bloco na memória e onde na página renderizar o bloco, considerando a composição da página. |
Frame do compositor | Uma coleção de quads de desenho que representa um frame de uma página. |
Um frame do compositor é enviado ao processo do navegador por IPC. Nesse ponto, outro frame do compositor pode ser adicionado da linha de execução da 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 serem mostrados em uma tela. Se um evento de rolagem for recebido, a linha de execução do compositor vai criar outro frame do compositor para ser enviado à GPU.

O benefício da composição é que ela é feita sem envolver a linha de execução principal. A linha de execução do compositor não precisa esperar pelo cálculo de estilo ou pela execução do JavaScript. É por isso que compor apenas animações é considerado o melhor para um desempenho suave. Se o layout ou a pintura precisar ser calculado novamente, a linha de execução principal precisa estar envolvida.
Conclusão
Nesta postagem, analisamos o pipeline de renderização, da análise à composição. Agora você já pode ler mais sobre a otimização de desempenho de um site.
Na próxima e última postagem desta série, vamos analisar a linha de execução do compositor com mais detalhes e
ver o que acontece quando a 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, entre em contato comigo na seção de comentários abaixo ou pelo @kosamari no Twitter.