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

Mustaq Ahmed
Joe Medley
Jo 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 mediante la activación del usuario. La activación del usuario es el estado de una sesión de navegación con respecto a sus acciones. Por lo general, el estado "activo" implica que el usuario está interactuando con la página o 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 deslizamiento o giro de un usuario no activa una página y, por lo tanto, no es, desde el punto de vista de una secuencia de comandos, una activación del usuario.

En la actualidad, los principales navegadores muestran un comportamiento muy divergente en torno a cómo la activación del usuario controla las APIs con restricción de activación. En Chrome, la implementación se basó en un modelo basado en tokens que resultaba demasiado complejo para definir un comportamiento coherente en todas las APIs con acceso restringido. Por ejemplo, Chrome permitió el acceso incompleto a las APIs con activación a través de postMessage() y llamadas setTimeout(). Además, la activación del usuario no era compatible con promesas, XHR, interacción con mando de videojuegos, etc. Ten en cuenta que algunos de estos son errores populares, pero de larga data.

En la versión 72, Chrome envía la versión 2 de la activación del usuario, que completa la disponibilidad de activación de usuarios para todas las APIs con acceso restringido. Esto resuelve las inconsistencias mencionadas anteriormente (y algunas más, como MessageChannels), que creemos que facilitarían el desarrollo web en torno a la activación de usuarios. Además, la implementación nueva proporciona una implementación de referencia para una nueva especificación propuesta que apunta a unir todos los navegadores 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 de la jerarquía de fotogramas: un bit fijo para el estado histórico de activación del usuario (si un fotograma 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). La parte adhesiva nunca se restablece durante la vida útil del fotograma una vez que se configura. El bit transitorio se establece 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 maneras diferentes. La nueva API no cambiará ninguno de estos comportamientos específicos de la API. Por ejemplo, solo se permite una ventana emergente por activación del usuario porque window.open() consume la activación del usuario como antes, Navigator.prototype.vibrate() sigue siendo eficaz si un fotograma (o cualquiera de sus submarcos) detectó alguna acción del usuario, y así sucesivamente.

¿Cuáles son los cambios?

  • La activación de usuarios 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 particular ahora activará todos los fotogramas que lo contienen (y solo esos fotogramas), independientemente de su origen. (En Chrome 72, tenemos una solución temporal para expandir la visibilidad a todos los marcos del mismo origen. Quitaremos esta solución una vez que tengamos una manera de pasar de forma explícita la activación del usuario a los submarcos).
  • Cuando se llama a una API con acceso restringido desde un marco activado, pero desde fuera del código del controlador de eventos, funcionará siempre y cuando el estado de activación del usuario sea "activo" (p.ej., no haya vencido ni se haya consumido). Antes de User Activation v2, fallaba de forma incondicional.
  • Las interacciones de varios usuarios sin usar dentro del intervalo de tiempo de vencimiento se fusionan en una sola activación correspondiente a la última interacción.

Ejemplos de coherencia en las APIs con acceso restringido

Estos son dos ejemplos con ventanas emergentes (que se abren mediante window.open()) que muestran cómo User Activation v2 hace que el comportamiento de las APIs con acceso restringido a la activación sea coherente.

setTimeout() llamadas en cadena

Este ejemplo es de nuestra demostración de setTimeout(). Si un controlador click intenta abrir una ventana emergente en un segundo, se espera que tenga éxito, sin importar cómo el código "componga" la demora. La activación del usuario 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 de postMessage()

Aquí hay 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 Activación del usuario 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 (en otras palabras, si el primer receptor reenvía el mensaje a otro).

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