Funcionamento interno de um processo de renderizador
Esta é a parte 3 de 4 da série do blog sobre como os navegadores funcionam. Anteriormente, abordamos a arquitetura de vários processos e o fluxo de navegação. Nesta postagem, veremos o que acontece dentro do processo do renderizador.
O processo do renderizador afeta muitos aspectos do desempenho da Web. Como há muita coisa acontecendo dentro do processo do renderizador, esta postagem é apenas uma visão geral. Se você quiser se aprofundar mais, a seção "Desempenho" dos Fundamentos da Web tem muitos outros recursos.
Os processos do renderizador processam o conteúdo da Web
O processo do renderizador é responsável por tudo o que acontece dentro de uma guia. Em um processo de renderizador, a linha de execução principal lida com a maior parte do código enviado ao usuário. Às vezes, partes do JavaScript são processadas pelas linhas de execução do worker se você usar um Web worker ou um service worker. As linhas de execução de composição e varredura também são executadas em processos de renderizador para renderizar uma página de forma eficiente e sem problemas.
A função principal 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 transformar em um Document object Model (DOM).
O DOM é a representação interna da página pelo navegador, além da estrutura de dados e da API com que o desenvolvedor da Web pode interagir via JavaScript.
A análise de um documento HTML em um DOM é definida pelo padrão HTML. Você deve ter notado que fornecer HTML a um navegador nunca gera um erro. Por exemplo, a falta 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 escrito
Hi! <b>I'm <i>Chrome</i></b><i>!</i>
. Isso ocorre porque a especificação HTML foi projetada para lidar com esses erros corretamente. Se você tiver curiosidade de como isso é feito, leia a seção
"Uma introdução ao tratamento de erros e casos estranhos no analisador"
da especificação HTML.
Carregamento de recursos secundários
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 poderia solicitá-las uma a uma, já que elas são encontradas
durante a análise para criar um DOM, mas, para acelerar, o "verificador de pré-carregamento" é executado de maneira simultânea.
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 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ê? Como o JavaScript pode mudar a forma do documento usando recursos 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 esperar a execução do JavaScript
antes de retomar a análise do documento HTML. Se você tiver curiosidade sobre o que acontece na
execução do JavaScript, a equipe do V8 tem palestras e postagens do blog sobre isso (link em inglês).
Dica para navegar como você quer carregar recursos
Há muitas maneiras de os desenvolvedores da Web enviarem dicas ao navegador para carregar os recursos adequadamente.
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. Você também pode usar o módulo JavaScript, se isso for adequado. <link rel="preload">
é uma maneira 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 saber como seria a aparência da página, porque podemos definir o estilo dos elementos dela no CSS. A linha de execução principal analisa o CSS e determina o estilo calculado para cada nó DOM. Essas informações
são sobre o tipo de estilo aplicado a cada elemento com base nos seletores de CSS. Consulte
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 calculado. A tag <h1>
é exibida
maior do 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, consulte o código-fonte aqui.
Layout
Agora, o processo do renderizador conhece a estrutura de um documento e os estilos para 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 caixa delimitadora. A árvore de layout pode ser semelhante à árvore do 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 uma pseudoclasse com conteúdo como p::before{content:"Hi!"}
for aplicada, ela será incluída na
árvore de layout, mesmo que isso 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 quebrá-la, porque eles afetam o tamanho e a forma de um parágrafo, o que afeta onde o parágrafo seguinte precisa estar.
O CSS pode fazer o elemento flutuar para um lado, mascarar item flutuante e alterar as direções de escrita. Imagine que essa fase de layout tem uma tarefa poderosa. No Chrome, uma equipe inteira de engenheiros trabalha no layout. Se você quiser conferir detalhes do trabalho deles, 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 o local dos elementos, mas ainda precisa julgar a ordem em que eles serão pintados.
Por exemplo, z-index
pode ser definido para determinados elementos. Nesse caso, a pintura na ordem dos
elementos escritos no HTML resulta em uma renderização incorreta.
Nessa 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 “segundo plano primeiro, 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 é caro
O 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 gerada novamente para as partes afetadas do documento.
Se você estiver animando elementos, o navegador precisará executar estas operações entre cada quadro. A maioria das telas atualiza a tela 60 vezes por segundo (60 fps). A animação é exibida suave para os olhos humanos quando você move elementos na tela em cada frame. No entanto, se a animação não tiver os frames intermediários, a página vai parecer "instável".
Mesmo que as operações de renderização estejam acompanhando a atualização da tela, esses cálculos serão executados na linha de execução principal, o que significa que ela poderá ser bloqueada quando o aplicativo estiver executando JavaScript.
É possível dividir a operação JavaScript em pequenos blocos e programar a execução em cada frame usando
requestAnimationFrame()
. Para mais informações sobre esse tópico, consulte Otimizar a execução do JavaScript. Você também pode executar seu 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 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? Transformar essas informações em pixels na tela é chamado de varredura.
Talvez uma maneira simples de lidar com isso seja varredura de partes dentro da janela de visualização. Se um usuário rolar a página, mova o frame rasterizado e preencha as partes ausentes fazendo a varredura. Foi assim que o Chrome lidou com a varredura quando ela foi lançada. Porém, os navegadores mais recentes executam um processo mais sofisticado chamado composição.
O que é a 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 linha de execução de composição. Se a rolagem acontecer, já que as camadas já foram rasterizadas, basta criar um novo frame. A animação pode ser feita da mesma maneira ao mover camadas e compor um novo frame.
Para conferir como o site é dividido em camadas no DevTools, use 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 criar a árvore de camadas. Essa parte é chamada de "Update Layer Tree" 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.
Você pode ficar tentado a dar camadas a cada elemento, mas a composição em um número excessivo de camadas pode resultar em uma operação mais lenta do que a varredura de pequenas partes de uma página a cada frame. Portanto, é fundamental que você meça o desempenho da renderização do aplicativo. Para saber mais sobre esse assunto, consulte Usar apenas propriedades do compositor 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 para a linha de execução do compositor. Em seguida, o encadeamento do compositor faz a varredura de cada camada. Uma camada pode ser grande como a extensão de uma página. Por isso, a linha de execução do compositor divide em blocos e envia cada bloco para linhas de execução de varredura. As linhas de execução de varredura 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 dentro da janela de visualização (ou próximos) possam ser rasterizados primeiro. Uma camada também tem vários blocos para resoluções diferentes para processar 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 bloco chamadas draw quads para criar um frame do compositor.
Desenhar quadriciclos | Contém informações como o local do bloco na memória e onde ele será desenhado na página, considerando a composição da página. |
Frame de composição | Uma coleção de quads de desenho que representa um frame de uma página. |
Um frame de compositor é então enviado ao processo do navegador via IPC. Nesse ponto, outro frame de compositor pode ser adicionado a partir da linha de execução de IU para a mudança da IU do navegador ou de outros processos do renderizador para extensões. Esses frames do compositor são enviados à GPU para que eles sejam exibidos em uma tela. Se um evento de rolagem chegar, 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 compostor 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 uma performance suave. Se o layout ou a pintura precisar ser calculado novamente, a linha de execução principal precisará estar envolvida.
conclusão
Nesta postagem, falamos sobre a renderização do pipeline da análise para a composição. Esperamos que agora você consiga ler mais sobre a otimização da performance de um site.
Na próxima e na última postagem desta série, analisaremos 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
, entra.
Você gostou da postagem? Se você tiver dúvidas ou sugestões para a próxima postagem, queremos saber sua opinião na seção de comentários abaixo ou @kosamari no Twitter.