Como intervir em document.write()

Você já viu um aviso como o seguinte no Play Console 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, que nos permite integrar facilmente a serviços criados por terceiros para criar novos produtos excelentes. Uma das desvantagens da composição é que ela implica em uma responsabilidade compartilhada sobre a experiência do usuário. Se a integração não for ideal, a experiência do usuário vai ser afetada negativamente.

Uma causa conhecida de desempenho ruim é o uso de document.write() nas páginas, especificamente nos usos que injetam scripts. Por mais inofensivo que pareça ser o exemplo a seguir, ele pode causar problemas reais para os usuários.

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

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

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 carreguem ou demorem tanto que o usuário desiste. Com base na instrumentação no Chrome, aprendemos que as páginas com scripts de terceiros inseridos via document.write() costumam ter o carregamento duas vezes mais lento do que as páginas 2G.

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

  • 10% mais carregamentos de página alcançando a primeira exibição de conteúdo (uma confirmação visual para o usuário de que a página está sendo carregada), 25% mais carregamentos de página alcançando o estado totalmente analisado e 10% menos recarregamentos, sugerindo uma diminuição na frustração do usuário.
  • Redução de 21% do tempo médio (mais de um segundo mais rápido) até a primeira exibição de conteúdo.
  • 38% de redução do tempo médio necessário para analisar uma página, o que representa uma melhoria de quase seis segundos, reduzindo drasticamente o tempo necessário para exibir o que importa para o usuário.

Com esses dados em mente, o Chrome, a partir da versão 55, intervende em nome de todos os usuários quando detectamos esse padrão inválido, mudando a forma como o document.write() é processado no Chrome (consulte Status do Chrome). Mais especificamente, o Chrome não vai executar os elementos <script> injetados pelo document.write() quando todas as condições abaixo forem atendidas:

  1. O usuário está em uma conexão lenta, especificamente quando está em 2G. No futuro, a mudança poderá ser estendida a outros usuários em conexões lentas, como 3G 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.writers em iframes, já que eles não bloqueiam a renderização da página principal.
  3. O script em 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 interfere nos 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 HTTP do navegador. Os scripts no cache não geram atraso na rede e ainda são executados.
  6. A solicitação da página não é uma atualização. O Chrome não intervirá se o usuário acionar uma atualização e executará a página normalmente.

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

Como resolvo esse problema?

Essa resposta simples é não injetar scripts usando document.write(). Temos um conjunto de serviços conhecidos para suporte a carregador assíncrono que recomendamos que você continue verificando.

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

Se o provedor não for compatível com a capacidade de carregar scripts de forma assíncrona na sua página, recomendamos que você entre em contato com ele e informe-nos como eles serão afetados.

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

Como detectar quando seu site foi afetado

Há um grande número de critérios que determinam se a restrição será aplicada. Então, 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 entender quantos dos seus usuários estarão no 2G. É possível detectar o tipo e a velocidade atuais da rede do usuário usando a API Network Information, disponível no Chrome, e enviar um alerta para seus sistemas de análise ou de métricas do usuário real (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 de 2 a 5 (o Chrome ignorará os critérios de conexão ao enviar esse aviso), o aviso será parecido com este:

Aviso de gravação de documento.

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

Verifique os cabeçalhos HTTP no recurso de script

Quando um script inserido por document.write é bloqueado, o Chrome envia o seguinte cabeçalho ao recurso solicitado:

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

Quando um script inserido por 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 da 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 que os critérios estão sendo atendidos. Começamos mostrando apenas um aviso no Play Console no Chrome 53. A versão Beta foi lançada em julho de 2016. Esperamos que o Stable esteja disponível para todos os usuários em setembro de 2016.

Interviremos no bloqueio de scripts injetados para usuários de 2G temporariamente a partir do Chrome 54, que deve 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, queremos intervir quando algum usuário tem uma conexão lenta (por exemplo, 3G ou Wi-Fi lento). Siga esta entrada de status do Chrome.

Quer saber mais?

Para saber mais, consulte estes recursos adicionais: