Haz que la activación del usuario sea coherente en todas las APIs

Mustaq Ahmed
Joe Medley
Joe Medley

Para evitar que las secuencias de comandos maliciosas abusen de las APIs sensibles, como las ventanas emergentes, la pantalla completa, etc., los navegadores controlan el acceso a esas APIs a través de la activación del usuario. La activación del usuario es el estado de una sesión de navegación con respecto a las acciones del usuario: un estado "activo" generalmente implica que el usuario está interactuando con la página o que completó una interacción desde que se cargó la página. Gesto del usuario es un término popular pero engañoso para la misma idea. Por ejemplo, un gesto de deslizar o girar el dedo por parte de un usuario no activa una página y, por lo tanto, desde el punto de vista de una secuencia de comandos, no es una activación del usuario.

En la actualidad, los principales navegadores muestran un comportamiento ampliamente divergente con respecto a la forma en que la activación del usuario controla las APIs restringidas por activación. En Chrome, la implementación se basó en un modelo basado en tokens que resultó ser demasiado complejo como para definir un comportamiento coherente en todas las APIs restringidas por activación. Por ejemplo, Chrome permitía el acceso incompleto a las APIs restringidas por activación a través de postMessage() y llamadas setTimeout(). Además, la activación del usuario no era compatible con las promesas, XHR, la interacción del control de mando, etc. Ten en cuenta que algunos de estos errores son populares, pero de larga data.

En la versión 72, Chrome envía User Activation v2, que completa la disponibilidad de la activación del usuario para todas las APIs con acceso restringido. Esto resuelve las inconsistencias mencionadas anteriormente (y algunas más, como MessageChannels), que creemos que facilitaría el desarrollo web en torno a la activación de usuarios. Además, la nueva implementación proporciona una implementación de referencia para una nueva especificación propuesta que tiene como objetivo reunir todos los navegadores juntos a largo plazo.

¿Cómo funciona User Activation v2?

La nueva API mantiene un estado de activación del usuario de dos bits en cada objeto window en la jerarquía de fotogramas: un bit persistente para el estado histórico de activación del usuario (si un fotograma alguna vez vio una activación del usuario) y un bit transitorio para el estado actual (si un fotograma vio una activación del usuario en aproximadamente un segundo). Una vez que se configura, la punta adhesiva nunca se restablece durante la vida útil del marco. El bit transitorio se configura en cada interacción del usuario y se restablece después de un intervalo de vencimiento (alrededor de un segundo) o mediante una llamada a una API que consume activación (p.ej., window.open()).

Ten en cuenta que las diferentes APIs con acceso restringido dependen de la activación del usuario de diferentes maneras. La nueva API no cambiará ninguno de estos comportamientos específicos de la API. P.ej., solo se permite una ventana emergente por activación del usuario porque window.open() consume la activación del usuario como solía ser, Navigator.prototype.vibrate() sigue siendo eficaz si un fotograma (o cualquiera de sus submarcos) vio alguna vez una acción del usuario, y así sucesivamente.

¿Cuáles son los cambios?

  • User Activation v2 formaliza la noción de la visibilidad de la activación del usuario más allá de los límites de los fotogramas: una interacción del usuario con un fotograma en particular ahora activará todos los fotogramas que los contengan (y solo aquellos) sin importar su origen. (En Chrome 72, tenemos una solución temporal para expandir la visibilidad a todos los fotogramas del mismo origen. Quitaremos esta solución alternativa una vez que tengamos una manera de pasar de manera explícita la activación del usuario a los submarcos.
  • Cuando se llama a una API con acceso restringido por activación desde un marco activado, pero desde fuera de un código de controlador de eventos, esta funcionará siempre que el estado de activación del usuario sea "activo" (p.ej., no haya vencido ni se haya consumido). Antes de la versión 2 de la activación del usuario, fallaba incondicionalmente.
  • Las múltiples interacciones del usuario sin usar dentro del intervalo de tiempo de vencimiento se fusionan en una sola activación que corresponde a la última interacción.

Ejemplos de coherencia en APIs con acceso de activación

A continuación, se incluyen dos ejemplos con ventanas emergentes (que se abren con window.open()) que muestran cómo User Activation v2 hace que el comportamiento de las APIs restringidas por activación sea coherente.

setTimeout() llamadas encadenadas

Este ejemplo es de nuestra demostración de setTimeout(). Si un controlador click intenta abrir una ventana emergente en un segundo, se espera que funcione sin importar cómo el código "componga" el retraso. User Activation v2 cumple con esta expectativa, por lo que cada uno de los siguientes controladores de eventos abre una ventana emergente en un click (con un retraso de 100 ms):

function popupAfter100ms() {
  setTimeout(callWindowOpen, 100);
}

function asyncPopupAfter100ms() {
  setTimeout(popupAfter100ms, 0);
}

someButton.addEventListener('click', popupAfter100ms);
someButton.addEventListener('click', asyncPopupAfter100ms);

Sin User Activation v2, el segundo controlador de eventos falla en todos los navegadores que probamos. (Incluso el primero falla en algunos casos).

Llamadas multidominio a postMessage()

Aquí tienes un ejemplo de nuestra demostración de postMessage(). Supongamos que un controlador click en un submarco de origen cruzado envía dos mensajes directamente al marco superior. El marco superior debería poder abrir una ventana emergente cuando reciba cualquiera de estos mensajes (pero no ambos):

// Parent frame code
window.addEventListener('message', e => {
  if (e.data === 'open_popup' && e.origin === child_origin)
    window.open('about:blank');
});

// Child frame code:
someButton.addEventListener('click', () => {
  parent.postMessage('hi_there', parent_origin);
  parent.postMessage('open_popup', parent_origin);
});

Sin User Activation v2, el marco superior no puede abrir una ventana emergente cuando recibe el segundo mensaje. Incluso el primer mensaje falla si está "encadenado" a otro marco de origen cruzado (es decir, si el primer receptor reenvía el mensaje a otro).

Esto funciona con User Activation v2, tanto en el formato original como con el encadenamiento.