Análise detalhada de um navegador da Web moderno (parte 4)

Mariko Kosaka

A entrada está chegando ao compositor

Esta é a última da série de blogs em quatro partes sobre o Chrome. Vamos investigar como ela lida com o código para exibir um site. Na postagem anterior, analisamos o processo de renderização e aprendemos sobre o compositor. Nesta postagem, vamos analisar como o compositor permite uma interação suave quando a entrada do usuário é recebida.

Eventos de entrada do ponto de vista do navegador

Quando você ouve "eventos de entrada", pode pensar apenas em digitar em uma caixa de texto ou clicar com o mouse, mas, do ponto de vista do navegador, entrada significa qualquer gesto do usuário. A rolagem da roda do mouse é um evento de entrada, e o toque ou o passar do mouse também é um evento de entrada.

Quando um gesto do usuário, como tocar na tela, ocorre, o processo do navegador é o que recebe o gesto primeiro. No entanto, o processo do navegador só sabe onde esse gesto ocorreu, já que o conteúdo dentro de uma guia é processado pelo processo do renderizador. Assim, o processo do navegador envia o tipo de evento (como touchstart) e as coordenadas para o processo do renderizador. O processo do renderizador processa o evento adequadamente, encontrando o destino dele e executando os listeners do evento anexados.

evento de entrada
Figura 1: evento de entrada roteado pelo processo do navegador para o processo do renderizador

O criador recebe eventos de entrada

Figura 2: janela de visualização passando sobre as camadas de página

Na postagem anterior, mostramos como o compositor poderia processar a rolagem de forma suave com a composição de camadas rasterizadas. Se nenhum listener de evento de entrada estiver anexado à página, a linha de execução do compositor poderá criar um novo frame composto completamente independente da linha de execução principal. Mas e se alguns listeners de eventos fossem anexados à página? Como a linha de execução do compositor descobriria se o evento precisa ser processado?

Noções básicas sobre a região de rolagem não rápida

Como a execução do JavaScript é o trabalho da linha de execução principal, quando uma página é composta, a linha de execução do compositor marca uma região da página que tem manipuladores de eventos anexados como "Região não rolável rápida". Com essas informações, a linha de execução do compositor pode enviar o evento de entrada para a linha de execução principal se o evento ocorrer nessa região. Se o evento de entrada vier de fora dessa região, a linha de execução do compositor vai continuar compondo um novo frame sem esperar pela linha de execução principal.

Região limitada não rolável
Figura 3: diagrama da entrada descrita para a região não rolável rápida

Tenha cuidado ao escrever manipuladores de eventos

Um padrão de manipulação de eventos comum no desenvolvimento da Web é a delegação de eventos. Como os eventos são formados no balão, você pode anexar um manipulador de eventos ao elemento de nível superior e delegar tarefas com base no destino do evento. Você pode ter visto ou escrito um código como o abaixo.

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault();
    }
});

Como você só precisa escrever um gerenciador de eventos para todos os elementos, a ergonomia desse padrão de delegação de eventos é atraente. No entanto, se você analisar esse código do ponto de vista do navegador, a página inteira será marcada como uma região de rolagem não rápida. Isso significa que, mesmo que o aplicativo não se importe com a entrada de determinadas partes da página, a linha de execução do compositor precisa se comunicar com a linha de execução principal e esperar por ela sempre que um evento de entrada chegar. Assim, a capacidade de rolagem suave do compositor é anulada.

região sem rolagem rápida na página inteira
Figura 4: diagrama da entrada descrita para a região de rolagem não rápida cobrindo uma página inteira

Para evitar que isso aconteça, transmita as opções passive: true no listener de eventos. Isso indica ao navegador que você ainda quer ouvir o evento na linha de execução principal, mas o compositor pode continuar e compor um novo frame também.

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault()
    }
 }, {passive: true});

Verificar se o evento pode ser cancelado

rolagem de página
Figura 5: uma página da Web com parte dela fixada na rolagem horizontal

Imagine que você tem uma caixa em uma página que quer limitar a direção de rolagem apenas para rolagem horizontal.

Usar a opção passive: true no evento do ponteiro significa que a rolagem da página pode ser suave, mas a rolagem vertical pode ter começado no momento em que você quer preventDefault para limitar a direção da rolagem. Para conferir isso, use o método event.cancelable.

document.body.addEventListener('pointermove', event => {
    if (event.cancelable) {
        event.preventDefault(); // block the native scroll
        /*
        *  do what you want the application to do here
        */
    }
}, {passive: true});

Como alternativa, use uma regra CSS como touch-action para eliminar completamente o manipulador de eventos.

#area {
  touch-action: pan-x;
}

Como encontrar o destino do evento

teste de hit
Figura 6: linha de execução principal analisando os registros de pintura perguntando o que está desenhado no ponto x.y

Quando a linha de execução do compositor envia um evento de entrada para a linha de execução principal, a primeira coisa a ser executada é um teste de hit para encontrar o destino do evento. O teste de hit usa dados de registros de pintura gerados no processo de renderização para descobrir o que está abaixo das coordenadas do ponto em que o evento ocorreu.

Como minimizar os envios de eventos para a linha de execução principal

Na postagem anterior, discutimos como nossa tela de exibição típica atualiza a tela 60 vezes por segundo e como precisamos acompanhar a cadência para uma animação suave. Para entrada, um dispositivo touchscreen típico gera eventos de toque de 60 a 120 vezes por segundo, e um mouse típico gera eventos 100 vezes por segundo. O evento de entrada tem uma fidelidade maior do que a tela pode atualizar.

Se um evento contínuo, como touchmove, for enviado para a linha de execução principal 120 vezes por segundo, ele poderá acionar uma quantidade excessiva de testes de hit e execução do JavaScript em comparação com a velocidade de atualização da tela.

eventos não filtrados
Figura 7: eventos que inundam a linha do tempo do frame, causando instabilidade na página

Para minimizar chamadas excessivas na linha de execução principal, o Chrome combina eventos contínuos (como wheel, mousewheel, mousemove, pointermove e touchmove) e atrasa o envio até pouco antes do próximo requestAnimationFrame.

eventos agrupados
Figura 8: mesma linha do tempo anterior, mas o evento está sendo agrupado e atrasado

Qualquer evento discreto, como keydown, keyup, mouseup, mousedown, touchstart e touchend, é enviado imediatamente.

Use getCoalescedEvents para receber eventos intraframe

Para a maioria dos aplicativos da Web, os eventos agrupados são suficientes para proporcionar uma boa experiência do usuário. No entanto, se você estiver criando itens como um aplicativo de desenho e colocando um caminho com base em coordenadas touchmove, talvez perca as coordenadas entre elas para desenhar uma linha suave. Nesse caso, é possível usar o método getCoalescedEvents no evento do ponteiro para receber informações sobre esses eventos agrupados.

getCoalescedEvents
Figura 9: caminho do gesto de toque suave à esquerda e caminho limitado mesclado à direita
window.addEventListener('pointermove', event => {
    const events = event.getCoalescedEvents();
    for (let event of events) {
        const x = event.pageX;
        const y = event.pageY;
        // draw a line using x and y coordinates.
    }
});

Próximas etapas

Nesta série, abordamos o funcionamento interno de um navegador da Web. Se você nunca pensou em por que o DevTools recomenda adicionar {passive: true} ao seu manipulador de eventos ou por que você pode escrever o atributo async na tag de script, esperamos que esta série esclareça por que um navegador precisa dessas informações para oferecer uma experiência na Web mais rápida e suave.

Usar o Lighthouse

Se você quer que seu código seja bom para o navegador, mas não sabe por onde começar, o Lighthouse é uma ferramenta que realiza auditorias em qualquer site e gera um relatório sobre o que está sendo feito corretamente e o que precisa de melhorias. Ler a lista de auditorias também dá uma ideia do que é importante para um navegador.

Saiba como medir a performance

Os ajustes de performance podem variar para sites diferentes. Por isso, é fundamental medir a performance do seu site e decidir o que é melhor para ele. A equipe do Chrome DevTools tem alguns tutoriais sobre como medir o desempenho do seu site.

Adicionar uma política de recursos ao seu site

Se quiser ir além, a Política de recursos é um novo recurso da plataforma da Web que pode ser uma proteção ao criar seu projeto. Ativar a política de recursos garante o comportamento específico do app e evita erros. Por exemplo, se você quiser garantir que o app nunca bloqueie a análise, execute-o na política de scripts síncronos. Quando sync-script: 'none' está ativado, o JavaScript que bloqueia o analisador não pode ser executado. Isso impede que o código bloqueie o analisador, e o navegador não precisa se preocupar em pausar o analisador.

Conclusão

agradeço

Quando comecei a criar sites, eu só me preocupava em como escrever o código e o que me ajudaria a ser mais produtivo. Essas coisas são importantes, mas também precisamos pensar em como o navegador recebe o código que escrevemos. Os navegadores modernos têm investido e continuam investindo em maneiras de proporcionar uma experiência da Web melhor para os usuários. Ser simpático com o navegador organizando nosso código melhora a experiência do usuário. Espero que você se junte a mim na missão de ser gentil com os navegadores.

Agradecemos muito a todos que revisaram os primeiros rascunhos desta série, incluindo, mas não se limitando a: Alex Russell, Paul Ireland, Meggin Kearney, Eric Bidelman, Mathias Bynens, Addy Osmani e Kinuko Yaskoisuda.

Você gostou da série? 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.