Cómo intervenir en document.write()

¿Viste recientemente una advertencia como la siguiente en Play Console en Chrome y te preguntaste qué significaba?

(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 componibilidad es uno de los grandes poderes de la Web, lo que nos permite integrarnos fácilmente con servicios creados por terceros para crear productos nuevos y geniales. Una de las desventajas de la componibilidad 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 bajo rendimiento es el uso de document.write() dentro de las páginas, específicamente aquellos usos que inyectan secuencias de comandos. Por más inocua que se ve, puede causarles problemas reales a los usuarios.

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

Antes de que el navegador pueda renderizar una página, debe compilar el árbol del DOM analizando el marcado HTML. Cada vez que el analizador encuentra una secuencia de comandos, debe detenerla y ejecutarla antes de poder continuar analizando el HTML. Si la secuencia de comandos inserta otra secuencia de comandos de forma dinámica, el analizador se ve obligado a esperar aún más tiempo para que se descargue el recurso, lo que puede generar uno o más recorridos de red y retrasar el tiempo de la primera renderización de la página.

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

Recopilamos datos de una prueba de campo de 28 días en 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 que bloquea el analizador entre sitios y que se insertó con document.write() en el documento de nivel superior. Como resultado de bloquear la carga de estas secuencias de comandos, observamos las siguientes mejoras en esas cargas:

  • Un 10% más de cargas de página que alcanzan el primer procesamiento de imagen con contenido (una confirmación visual para el usuario de que la página se está cargando de manera efectiva), un 25% más de cargas de página que alcanzan 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% en el tiempo promedio que se tarda en analizar una página, lo que representa una mejora de casi seis segundos y reduce drásticamente el tiempo que se tarda en mostrar lo que le importa al usuario.

Con estos datos en mente, Chrome, a partir de la versión 55, interviene en nombre de todos los usuarios cuando detecta este patrón malicioso conocido; para ello, cambia la forma en que se maneja document.write() en Chrome (consulta el 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 usa 2G. (En el futuro, es posible que el cambio se extienda a otros usuarios con conexiones lentas, como redes 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.writers dentro de iframes, ya que no bloquean la renderización de la página principal.
  3. La secuencia de comandos en document.write() bloquea el analizador. Las secuencias de comandos con los atributos "async" o "defer" se ejecutarán de todos modos.
  4. La secuencia de comandos no se aloja 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 insertada en www.example.org).
  5. La secuencia de comandos aún no está en la caché HTTP del navegador. Las secuencias de comandos en la caché no incurrirán en una demora de red y se ejecutarán de todos modos.
  6. La solicitud de la página no es una recarga. Chrome no intervendrá si el usuario activó una recarga y ejecutará la página como de costumbre.

A veces, los fragmentos de terceros usan document.write() para cargar secuencias de comandos. Afortunadamente, la mayoría de los terceros proporcionan alternativas de carga asíncrona, que permiten que se carguen las secuencias de comandos de terceros 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 cargador asíncrono que te recomendamos que sigas revisando.

Si tu proveedor no aparece en la lista y admite la carga de secuencias de comandos asíncronas, infórmanos al respecto para que podamos actualizar la página y 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 nos informes a nosotros y a él cómo se verá afectado.

Si tu proveedor te proporciona un fragmento que incluye document.write(), es posible que puedas agregar un atributo async al elemento de secuencia de comandos o agregar los elementos de secuencia de comandos con APIs de DOM, como document.appendChild() o parentNode.insertBefore().

Cómo detectar si tu sitio se ve afectado

Existen una gran cantidad de criterios que determinan si se aplica la restricción, así que ¿cómo puedes saber si te afecta?

Cómo detectar cuándo un usuario está en 2G

Para comprender el posible impacto de este cambio, primero debes saber cuántos de tus usuarios usarán la red 2G. Puedes detectar el tipo y la velocidad de red actuales del usuario con la API de Network Information que está disponible en Chrome y, luego, enviar un aviso a tus sistemas de métricas de usuarios reales (RUM) o de análisis.

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, DevTools emite advertencias para las sentencias document.write() problemáticas. Específicamente, si una solicitud document.write() cumple con los criterios 2 a 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 documento.

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

Verifica los encabezados HTTP en el recurso de 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 esta se puede bloquear en diferentes circunstancias, es posible que Chrome envíe 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 que se cumplen los criterios. Comenzamos mostrando solo una advertencia en la Consola de Play en Chrome 53. (La versión beta se realizó en julio de 2016. Esperamos que la versión estable esté disponible para todos los usuarios en septiembre de 2016).

Interveniremos para bloquear las secuencias de comandos insertadas para los usuarios de 2G de forma provisional 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, esperamos intervenir cuando un usuario tenga una conexión lenta (es decir, 3G o Wi-Fi lentos). Sigue esta entrada de estado de Chrome.

¿Quieres obtener más información?

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