Cómo intervenir en document.write()

¿Viste hace poco una advertencia como la siguiente en Play Console en Chrome y te preguntaste qué 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.

La composición es uno de los grandes poderes de la Web, ya que nos permite integrarnos fácilmente con servicios creados por terceros para crear nuevos productos increíbles. Una de las desventajas de este elemento es que implica una responsabilidad compartida sobre la experiencia del usuario. Si la integración no es óptima, la experiencia del usuario se verá afectada.

Una causa conocida del rendimiento bajo es el uso de document.write() dentro de páginas, en particular los usos que insertan secuencias de comandos. Por inocua que parezca, puede causar problemas reales a los usuarios.

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

Antes de que el navegador pueda representar una página, debe compilar el árbol del DOM analizando el lenguaje de marcado HTML. Cada vez que el analizador encuentra una secuencia de comandos, tiene que detenerla y ejecutarla para poder continuar analizando el código HTML. Si la secuencia de comandos inserta otra secuencia de comandos de forma dinámica, el analizador debe esperar aún más tiempo para que se descargue el recurso, lo que puede generar uno o más recorridos de red y demorar el tiempo necesario para procesar la página por primera vez.

Para los usuarios con conexiones lentas, como 2G, las secuencias de comandos externas que se incorporan de forma dinámica a través de document.write() pueden retrasar la visualización del contenido de la página principal durante decenas de segundos, o hacer que las páginas no se carguen o tarden tanto que el usuario se da por vencido. En función de la instrumentación de Chrome, aprendimos que las páginas que incluyen secuencias de comandos de terceros insertadas a través de document.write() suelen tardar el doble en cargarse que otras páginas en 2G.

Recolectamos datos de una prueba de campo de 28 días sobre el 1% de los usuarios estables de Chrome, restringida a los usuarios con conexiones 2G. Observamos que el 7.6% de todas las cargas de páginas en 2G incluían al menos una secuencia de comandos de bloqueo del analizador entre sitios que se insertó a través de document.write() en el documento de nivel superior. Como resultado del bloqueo de la carga de estas secuencias de comandos, vimos las siguientes mejoras en esas cargas:

  • 10% más de cargas de páginas que llegan al primer procesamiento de imagen con contenido (una confirmación visual para el usuario que indica que la página se está cargando de manera efectiva), un 25% más de cargas de páginas hasta alcanzar el estado completamente analizado y un 10% menos de recargas, lo que sugiere una disminución en la frustración del usuario.
  • Disminución del 21% del tiempo promedio (más de un segundo más rápido) hasta el primer procesamiento de imagen con contenido
  • Una reducción del 38% del tiempo promedio que se tarda en analizar una página, lo que representa una mejora de casi seis segundos y reduce considerablemente el tiempo que se tarda en mostrar lo que es importante para el usuario.

Con estos datos en mente, Chrome, a partir de la versión 55, interviene en nombre de todos los usuarios cuando detectamos este patrón malicioso conocido mediante el cambio de la forma en que se maneja document.write() en Chrome (consulta Estado de Chrome). Específicamente, Chrome no ejecutará los elementos <script> insertados a través de document.write() cuando se cumplan todas las siguientes condiciones:

  1. El usuario tiene una conexión lenta, en especial cuando utiliza 2G. (En el futuro, es posible que el cambio se extienda a otros usuarios con conexiones lentas, como conexiones 3G o Wi-Fi lentas).
  2. El document.write() está en un documento de nivel superior. La intervención no se aplica a las secuencias de comandos document.writes dentro de iframes, ya que no bloquean la renderización de la página principal.
  3. La secuencia de comandos de document.write() bloquea el analizador. Las secuencias de comandos con los atributos "async" o "defer" seguirán ejecutándose.
  4. La secuencia de comandos no está alojada en el mismo sitio. En otras palabras, Chrome no intervendrá en las secuencias de comandos con un eTLD+1 coincidente (p.ej., una secuencia de comandos alojada en js.example.org que se inserte en www.example.org).
  5. La secuencia de comandos no está en la caché HTTP del navegador. Las secuencias de comandos almacenadas en caché no generarán un retraso de red y se seguirán ejecutando.
  6. La solicitud de la página no se vuelve a cargar. Chrome no interviene si el usuario activó una nueva carga y ejecutará la página como de costumbre.

Los fragmentos de terceros a veces usan document.write() para cargar secuencias de comandos. Afortunadamente, la mayoría de los terceros proporcionan alternativas de carga asíncrona, que permiten que las secuencias de comandos de terceros se carguen sin bloquear la visualización del resto del contenido en la página.

¿Cómo puedo solucionarlo?

La respuesta simple es no insertar secuencias de comandos con document.write(). Mantenemos un conjunto de servicios conocidos para la compatibilidad con cargadores asíncronos que te recomendamos que sigas revisando.

Si tu proveedor no está en la lista y admite la carga asíncrona de secuencias de comandos, comunícate con nosotros para que podamos actualizar la página a fin de ayudar a todos los usuarios.

Si tu proveedor no admite la capacidad de cargar secuencias de comandos de forma asíncrona en tu página, te recomendamos que te comuniques con él y que nos informes sobre ellos cómo se verán afectados.

Si tu proveedor te proporciona un fragmento que incluye el document.write(), tal vez puedas agregar un atributo async al elemento de la secuencia de comandos o que tú agregues los elementos de secuencia de comandos con API de DOM, como document.appendChild() o parentNode.insertBefore().

Cómo detectar cuándo se ve afectado tu sitio

Hay una gran cantidad de criterios que determinan si se aplica la restricción. Entonces, ¿cómo puedes saber si te afecta?

Cómo detectar si un usuario utiliza 2G

Para entender el impacto potencial de este cambio, primero debes comprender cuántos de tus usuarios estarán en 2G. Puedes detectar la velocidad y el tipo de red actual del usuario con la API de Network Information, que está disponible en Chrome, y luego enviar un aviso a tus sistemas de estadísticas o de métricas de usuarios reales (RUM).

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.
}

Detecta advertencias en las Herramientas para desarrolladores de Chrome

A partir de Chrome 53, Herramientas para desarrolladores emite advertencias para las declaraciones document.write() problemáticas. Específicamente, si una solicitud document.write() cumple con los criterios del 2 al 5 (Chrome ignora los criterios de conexión cuando envía esta advertencia), la advertencia se verá de la siguiente manera:

Advertencia de escritura de documentos.

Ver advertencias en las Herramientas para desarrolladores de Chrome es genial, pero ¿cómo se detecta a gran escala? Puedes verificar los encabezados HTTP que se envían a tu servidor cuando ocurre la intervención.

Verifica tus encabezados HTTP en el recurso de la secuencia de comandos

Cuando se bloquee una secuencia de comandos insertada a través de document.write, Chrome enviará el siguiente encabezado al recurso solicitado:

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

Cuando se encuentra una secuencia de comandos insertada a través de document.write y se podría bloquear en diferentes circunstancias, Chrome puede enviar lo siguiente:

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

El encabezado de intervención se enviará como parte de la solicitud GET de la secuencia de comandos (de forma asíncrona en caso de una intervención real).

¿Qué nos depara el futuro?

El plan inicial es ejecutar esta intervención cuando detectemos los criterios que se cumplen. En Chrome 53, comenzamos a mostrar solo una advertencia en la consola para desarrolladores. (La versión beta fue en julio de 2016. Esperamos que el canal estable esté disponible para todos los usuarios en septiembre de 2016).

Interveniremos de forma provisional para bloquear las secuencias de comandos insertadas para los usuarios de 2G a partir de Chrome 54, que se estima que estará en una versión estable para todos los usuarios a mediados de octubre de 2016. Consulta la entrada de estado de Chrome para obtener más actualizaciones.

Con el tiempo, queremos intervenir cuando un usuario tenga una conexión lenta (es decir, 3G o Wi-Fi lenta). Sigue esta entrada del estado de Chrome.

¿Quieres más información?

Para obtener más información, consulta estos recursos adicionales: