Cómo intervenir en document.write()

¿Alguna vez viste una advertencia como la siguiente en tu Developer 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 capacidad de componibilidad es uno de los grandes beneficios de la Web, ya que nos permite realizar una integración sencilla con servicios creados por terceros para crear excelentes productos nuevos. 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 rendimiento deficiente es el uso de document.write() en las páginas, específicamente aquellos usos que insertan 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 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, debe detenerla y ejecutarla para poder continuar analizando el código HTML. Si la secuencia de comandos inserta otra de forma dinámica, el analizador se verá obligado a esperar aún más tiempo para que se descargue el recurso, lo que puede generar uno o más recorridos de la red y demorar el tiempo de renderización de la página por primera vez.

En el caso de los usuarios con conexiones lentas, como 2G, las secuencias de comandos externas que se insertan 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 rinda. Según la instrumentación en Chrome, aprendimos que las páginas que incluyen secuencias de comandos de terceros insertadas a través de document.write() suelen tardar el doble de tiempo en cargarse que otras páginas en 2G.

Recopilamos datos de una prueba de campo de 28 días sobre el 1% de los usuarios estables de Chrome, restringido 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 del bloqueo de 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.
  • Una 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 interesa 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 está en 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() se encuentra 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 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á 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 se encuentra en la caché HTTP del navegador. Las secuencias de comandos almacenadas en caché no incurrirán en una demora en la red y aún se ejecutarán.
  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 normalmente.

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 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?

Esta 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 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 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 para informarnos cómo se verán afectadas.

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

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

Hay una gran cantidad de criterios que determinan si la restricción se aplica de manera forzosa, por lo tanto, ¿cómo sabes si te afecta?

Cómo detectar cuando un usuario está en 2G

Para comprender el impacto potencial de este cambio, primero debes comprender cuántos de tus usuarios estarán en la red 2G. Puedes detectar el tipo de red y la velocidad actuales del usuario con la API de Network Information que está disponible en Chrome y, luego, enviar un aviso a tus sistemas analíticos 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 Herramientas para desarrolladores de Chrome

A partir de Chrome 53, las Herramientas para desarrolladores emiten advertencias sobre declaraciones 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 bloquea 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 la 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 Play Console en Chrome 53. (La versión beta se realizó en julio de 2016. Esperamos que el canal 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 manera provisoria 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, intentaremos intervenir cuando los usuarios tengan una conexión lenta (es decir, 3G o Wi-Fi lentas). Sigue esta entrada sobre el estado de Chrome.

¿Quieres obtener más información?

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