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 en relación con las acciones del usuario: un estado "activo" suele implicar 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 deslizamiento o deslizamiento 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.

Actualmente, los navegadores principales muestran un comportamiento muy divergente en cuanto a cómo la activación del usuario controla las APIs con control de activación. En Chrome, la implementación se basó en un modelo basado en tokens que resultó ser demasiado complejo para definir un comportamiento coherente en todas las APIs con control de acceso por activación. Por ejemplo, Chrome permitía el acceso incompleto a las APIs con control de activación a través de llamadas postMessage() y setTimeout(), y la activación del usuario no era compatible con Promises, XHR, interacción con Gamepad, etc. Ten en cuenta que algunos de estos son errores 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ían el desarrollo web en torno a la activación del usuario. Además, la nueva implementación proporciona una implementación de referencia para una especificación nueva propuesta que tiene como objetivo reunir a todos los navegadores a largo plazo.

¿Cómo funciona la activación de usuarios v2?

La nueva API mantiene un estado de activación del usuario de dos bits en cada objeto window de la jerarquía de marcos: un bit fijo para el estado histórico de activación del usuario (si un marco vio una activación del usuario) y un bit transitorio para el estado actual (si un marco vio una activación del usuario en aproximadamente un segundo). El bit de estado fijo nunca se restablece durante la vida útil de la trama después de que se establece. 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 a través de una llamada a una API que consume activación (p.ej., window.open()).

Ten en cuenta que las diferentes APIs con control de activación dependen de la activación del usuario de diferentes maneras. La API nueva no cambia 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?

  • La versión 2 de la activación del usuario formaliza la noción de visibilidad de la activación del usuario en los límites de los marcos: una interacción del usuario con un marco en particular ahora activará todos los marcos que lo contengan (y solo esos marcos), independientemente de su origen. (En Chrome 72, tenemos una solución temporal para expandir la visibilidad a todos los marcos de origen). Quitaremos esta solución alternativa una vez que tengamos una forma de pasar explícitamente 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 activación del usuario v2, fallaría de forma incondicional.
  • 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 las APIs con control 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.

Llamadas setTimeout() encadenadas

Este ejemplo es de nuestra demo de setTimeout(). Si un controlador click intenta abrir una ventana emergente en un segundo, se espera que se realice correctamente, independientemente de 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 una demora de 100 ms):

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

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

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

Sin la activación del usuario 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.