Como intervir em document.write()

Você viu recentemente um aviso como este no seu console do desenvolvedor no Chrome e se perguntou o que era?

(index):34 A Parser-blocking, cross-origin script,
https://paul.kinlan.me/ad-inject.js, is invoked via document.write().
This may be blocked by the browser if the device has poor network connectivity.

A composição é um dos grandes poderes da Web, o que nos permite fazer uma integração facilmente com serviços criados por terceiros para criar ótimos produtos novos. Uma das desvantagens da composição é que ela implica uma responsabilidade compartilhada sobre a experiência do usuário. Se a integração não for o ideal, a experiência do usuário vai ser prejudicada.

Uma causa conhecida de baixa performance é o uso de document.write() dentro de páginas, especificamente aqueles que injetam scripts. Por mais inofensivo que pareça, ele pode causar problemas reais para os usuários.

document.write('<script src="https://example.com/ad-inject.js"></script>');

Antes que o navegador possa renderizar uma página, ele precisa criar a árvore do DOM analisando a marcação HTML. Sempre que o analisador encontra um script, ele precisa interrompê-lo e executá-lo antes de continuar a analisar o HTML. Se o script injetar dinamicamente outro script, o analisador será forçado a esperar ainda mais tempo pelo download do recurso, o que pode gerar uma ou mais idas e voltas da rede e atrasar a primeira renderização da página.

Para usuários em conexões lentas, como 2G, os scripts externos injetados dinamicamente via document.write() podem atrasar a exibição do conteúdo da página principal por dezenas de segundos ou fazer com que as páginas não sejam carregadas ou demorem tanto que o usuário desista. Com base na instrumentação do Chrome, aprendemos que páginas com scripts de terceiros inseridos por document.write() normalmente têm o carregamento duas vezes mais lento do que outras páginas em 2G.

Coletamos dados de um teste de campo de 28 dias com 1% dos usuários estáveis do Chrome, restritos a usuários em conexões 2G. Notamos que 7, 6% de todos os carregamentos de página em 2G incluíram pelo menos um script de bloqueio de analisador entre sites que foi inserido por document.write() no documento de nível superior. Como resultado do bloqueio desses scripts, notamos as seguintes melhorias nesses carregamentos:

  • 10% mais carregamentos de página alcançando a First Contentful Paint (uma confirmação visual para o usuário de que a página está sendo carregada efetivamente), 25% mais carregamentos de página que alcançam o estado totalmente analisado e 10% menos recarregamentos, o que sugere uma diminuição na frustração do usuário.
  • 21% de redução do tempo médio (mais de um segundo mais rápido) até a First Contentful Paint
  • 38% de redução do tempo médio necessário para analisar uma página, representando uma melhoria de quase seis segundos, reduzindo drasticamente o tempo necessário para mostrar o que é importante para o usuário.

Com esses dados em mente, o Chrome, a partir da versão 55, intervém em nome de todos os usuários quando detectamos esse padrão conhecido como ruim, mudando a forma como o document.write() é processado no Chrome. Consulte Status do Chrome. Especificamente, o Chrome não executará os elementos <script> injetados via document.write() quando todas as condições abaixo forem atendidas:

  1. O usuário está com uma conexão lenta, especificamente quando está em 2G. No futuro, a mudança pode ser estendida para outros usuários com conexões lentas, como 3G lento ou Wi-Fi lento.
  2. O document.write() está em um documento de nível superior. A intervenção não se aplica a scripts document.write dentro de iframes, porque eles não bloqueiam a renderização da página principal.
  3. O script no document.write() bloqueia o analisador. Os scripts com os atributos "async" ou "defer" ainda serão executados.
  4. O script não está hospedado no mesmo site. Em outras palavras, o Chrome não intervirá em scripts com um eTLD+1 correspondente (por exemplo, um script hospedado em js.example.org inserido em www.example.org).
  5. O script ainda não está no cache de HTTP do navegador. Os scripts no cache não incorrerão em atraso de rede e ainda serão executados.
  6. A solicitação da página não é uma atualização. O Chrome não intervém se o usuário aciona uma atualização e executa a página normalmente.

Às vezes, os snippets de terceiros usam document.write() para carregar scripts. Felizmente, a maioria dos terceiros oferece alternativas de carregamento assíncrono, que permitem que scripts de terceiros sejam carregados sem bloquear a exibição do restante do conteúdo da página.

Como resolvo esse problema?

A resposta simples é não injetar scripts usando document.write(). Mantemos um conjunto de serviços conhecidos que oferecem suporte a carregador assíncrono (link em inglês) que recomendamos que você verifique regularmente.

Caso seu provedor não esteja na lista e seja compatível com o carregamento de script assíncrono, entre em contato para que possamos atualizar a página e ajudar todos os usuários.

Caso seu provedor não seja compatível com o recurso de carregamento assíncrono de scripts na sua página, recomendamos que você entre em contato com ele e informe-nos sobre como isso será afetado.

Se o provedor fornecer um snippet que inclua o document.write(), talvez seja possível adicionar um atributo async ao elemento de script ou os elementos de script com APIs DOM, como document.appendChild() ou parentNode.insertBefore().

Como detectar quando seu site é afetado

Há um grande número de critérios que determinam se a restrição foi aplicada. Portanto, como saber se você foi afetado?

Como detectar quando um usuário está usando 2G

Para entender o possível impacto dessa mudança, primeiro é preciso saber quantos usuários utilizarão a conexão 2G. Para detectar o tipo e a velocidade atuais da rede do usuário, use a API Network Information, disponível no Chrome, e envie um aviso para os seus sistemas analíticos ou de métricas de usuários reais (RUM, na sigla em inglês).

if(navigator.connection &&
    navigator.connection.type === 'cellular' &&
    navigator.connection.downlinkMax <= 0.115) {
    // Notify your service to indicate that you might be affected by this restriction.
}

Detectar avisos no Chrome DevTools

Desde o Chrome 53, o DevTools emite avisos para instruções document.write() problemáticas. Especificamente, se uma solicitação document.write() atender aos critérios 2 a 5 (o Chrome ignora os critérios de conexão ao enviar esse aviso), o aviso será semelhante a este:

Aviso de gravação de documento.

Receber avisos no Chrome DevTools é ótimo, mas como detectar isso em grande escala? É possível verificar se há cabeçalhos HTTP que são enviados ao seu servidor quando a intervenção acontece.

Verifique seus cabeçalhos HTTP no recurso de script

Quando um script inserido por document.write for bloqueado, o Chrome enviará o seguinte cabeçalho para o recurso solicitado:

Intervention: <https://shorturl/relevant/spec>;

Quando um script inserido via document.write é encontrado e pode ser bloqueado em diferentes circunstâncias, o Chrome pode enviar:

Intervention: <https://shorturl/relevant/spec>; level="warning"

O cabeçalho de intervenção será enviado como parte da solicitação GET para o script (de forma assíncrona, no caso de uma intervenção real).

O que o futuro reserva?

O plano inicial é executar essa intervenção quando detectarmos os critérios que estão sendo atendidos. Começamos mostrando apenas um aviso no Play Console no Chrome 53. A versão Beta foi em julho de 2016. Esperamos que o Stable esteja disponível para todos os usuários em setembro de 2016.

Interviremos para bloquear provisoriamente scripts injetados para usuários 2G a partir do Chrome 54, que deverá estar em uma versão estável para todos os usuários em meados de outubro de 2016. Confira a entrada de status do Chrome para mais atualizações.

Com o tempo, tentamos intervir quando algum usuário tem uma conexão lenta (ou seja, 3G ou Wi-Fi lentos). Siga esta entrada de status do Chrome.

Quer saber mais?

Para saber mais, consulte estes recursos extras: