Como intervir em document.write()

Você já recebeu um aviso como este no console do desenvolvedor no Chrome e ficou se perguntando o que ele significava?

(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 composabilidade é um dos grandes poderes da Web, permitindo que nos integremos facilmente a serviços criados por terceiros para criar novos produtos. Uma das desvantagens da composabilidade é que ela implica 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 prejudicada.

Uma causa conhecida de baixo desempenho é o uso de document.write() nas páginas, especificamente aqueles usos que injetam scripts. Por mais inócuo que pareça, o exemplo a seguir 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 parar e executá-lo antes de continuar analisando o HTML. Se o script injetar outro script dinamicamente, o analisador será forçado a esperar ainda mais para que o recurso seja transferido, o que pode gerar uma ou mais viagens de ida e volta pela rede e atrasar o tempo para 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 com conexões 2G. Descobrimos que 7, 6% de todos os carregamentos de página em 2G incluíam pelo menos um script de bloqueio de parser entre sites que foi inserido por document.write() no documento de nível superior. Como resultado do bloqueio do carregamento desses scripts, observamos as seguintes melhorias:

  • 10% mais carregamentos de página alcançando First Contentful Paint (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 recarregares sugerindo 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
  • Redução de 38% no 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 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, alterando a forma como o document.write() é processado no Chrome. Consulte o Status do Chrome. Especificamente, o Chrome não vai executar os elementos <script> injetados por document.write() quando todas as condições a seguir 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.written em iframes, porque 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 intervém 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 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 vai intervir se o usuário acionar uma recarga e vai executar a página normalmente.

Os snippets de terceiros às vezes 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 na página.

Como resolvo esse problema?

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

Se o seu provedor não estiver na lista e oferecer suporte ao 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 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 document.write(), será possível adicionar um atributo async ao elemento de script ou adicionar os elementos de script com APIs DOM, como document.appendChild() ou parentNode.insertBefore().

Como detectar quando o site é afetado

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

Detectar quando um usuário está em 2G

Para entender o possível impacto dessa mudança, primeiro você precisa saber quantos dos seus usuários vão usar a rede 2G. É possível detectar o tipo de rede atual do usuário e a velocidade usando a API Network Information disponível no Chrome e enviar um aviso para seus sistemas de RUM ou de análise.

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á parecido com este:

Aviso de gravação de documento.

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

Verificar os cabeçalhos HTTP no recurso de script

Quando um script inserido por document.write é bloqueado, o Chrome envia 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 do script (de forma assíncrona em caso de uma intervenção real).

O que o futuro reserva?

O plano inicial é realizar essa intervenção quando detectarmos que os critérios estão sendo atendidos. Começamos mostrando apenas um aviso no console do desenvolvedor no Chrome 53. (A versão Beta foi lançada em julho de 2016. A versão estável deve estar disponível para todos os usuários em setembro de 2016.)

Vamos bloquear os scripts injetados para usuários de 2G, provisoriamente, a partir do Chrome 54, que deve ser lançado 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 do Status do Chrome.

Quer saber mais?

Para saber mais, consulte estes recursos extras: