Rendere l'attivazione utente coerente tra le API

Mustaq Ahmed
Joe Medley
Mario Bianchi

Per evitare che script dannosi abusano di API sensibili come popup, schermo intero e così via, i browser controllano l'accesso a queste API tramite l'attivazione da parte dell'utente. L'attivazione utente è lo stato di una sessione di navigazione in relazione alle azioni dell'utente: uno stato "attivo" in genere implica che l'utente sta interagendo con la pagina o ha completato un'interazione dal caricamento della pagina. Gesto dell'utente è un termine comune ma fuorviante per la stessa idea. Ad esempio, un gesto di scorrimento o scorrimento di un utente non attiva una pagina e quindi, dal punto di vista dello script, non è un'attivazione dell'utente.

Oggi i principali browser mostrano comportamenti molto divergenti nel modo in cui l'attivazione dell'utente controlla le API protette dall'attivazione. In Chrome, l'implementazione era basata su un modello basato su token che si è rivelato troppo complesso per definire un comportamento coerente in tutte le API controllate dall'attivazione. Ad esempio, Chrome ha consentito l'accesso incompleto alle API controllate dall'attivazione tramite le chiamate postMessage() e setTimeout() e l'attivazione dell'utente non era supportata con Promises, XHR, Interazione con il Gamepad e così via. Tieni presente che alcuni di questi sono bug comuni, ma di lunga data.

Nella versione 72, Chrome spedisce Attivazione utente v2, in modo che la disponibilità di attivazione utente sia completa per tutte le API che richiedono l'attivazione. In questo modo vengono risolte le incoerenze menzionate sopra (e alcune altre, come MessageChannels), che riteniamo faciliterebbe lo sviluppo web riguardo all'attivazione dell'utente. Inoltre, la nuova implementazione fornisce un'implementazione di riferimento per una proposta di nuova specifica che mira a riunire tutti i browser nel lungo periodo.

Come funziona Attivazione utente v2?

La nuova API mantiene uno stato di attivazione utente a due bit per ogni oggetto window nella gerarchia dei frame: un bit fisso per lo stato storico di attivazione dell'utente (se un frame ha registrato l'attivazione di un utente) e un bit temporaneo per lo stato attuale (se un frame ha rilevato l'attivazione di un utente nell'arco di circa un secondo). Il bit fisso non si reimposta mai durante la durata del frame dopo che è stato impostato. Il bit temporaneo viene impostato a ogni interazione dell'utente e viene reimpostato dopo un intervallo di scadenza (circa un secondo) o tramite una chiamata a un'API che utilizza l'attivazione, ad esempio window.open().

Tieni presente che le diverse API con gate di attivazione si basano sull'attivazione utente in modi diversi; la nuova API non modifica nessuno di questi comportamenti specifici dell'API. Ad esempio, è consentito un solo popup per attivazione utente perché window.open() utilizza l'attivazione utente come in passato, Navigator.prototype.vibrate() continua a essere efficace se un frame (o uno qualsiasi dei suoi frame secondari) ha mai rilevato l'azione dell'utente e così via.

Cosa cambierà?

  • Attivazione utente v2 formalizza la nozione di visibilità dell'attivazione utente oltre i limiti dei frame: un'interazione utente con un particolare frame ora attiverà tutti i frame contenenti (e solo quei frame) indipendentemente dalla loro origine. In Chrome 72 abbiamo una soluzione alternativa temporanea per espandere la visibilità a tutti i frame della stessa origine. Rimuoveremo questa soluzione alternativa una volta che avremo un modo per passare esplicitamente l'attivazione dell'utente ai frame secondari.
  • Quando un'API con accesso all'attivazione viene richiamata da un frame attivato, ma dall'esterno di un codice del gestore di eventi, funziona purché lo stato di attivazione dell'utente sia "attivo" (ad esempio, non è scaduto né è stato utilizzato). Prima dell'attivazione utente v2, l'attivazione non sarebbe riuscita incondizionatamente.
  • Più interazioni degli utenti non utilizzati all'interno dell'intervallo di scadenza si uniscono in una singola attivazione corrispondente all'ultima interazione.

Esempi di coerenza nelle API controllate dall'attivazione

Ecco due esempi di finestre popup (aperte utilizzando window.open()) che mostrano come l'attivazione dell'utente v2 rende coerente il comportamento delle API con accesso limitato all'attivazione.

Chiamate setTimeout() concatenate

Questo esempio è tratto dalla nostra demo di setTimeout(). Se un gestore click tenta di aprire un popup entro un secondo, l'operazione dovrebbe riuscire, indipendentemente dal modo in cui il codice "compone" il ritardo. Attivazione utente v2 soddisfa questa aspettativa, pertanto ciascuno dei seguenti gestori di eventi apre un popup in click (con un ritardo di 100 ms):

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

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

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

Senza Attivazione utente v2, il secondo gestore di eventi ha esito negativo in tutti i browser che abbiamo testato. Anche la prima non funziona in alcuni casi.

Chiamate postMessage() interdominio

Ecco un esempio tratto dalla nostra demo di postMessage(). Supponiamo che un gestore click in un frame secondario multiorigine invii due messaggi direttamente al frame principale. Il frame principale dovrebbe essere in grado di aprire un popup alla ricezione di uno di questi messaggi (ma non di entrambi):

// 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);
});

Senza Attivazione utente v2, il frame principale non può aprire un popup alla ricezione del secondo messaggio. Anche il primo messaggio fallisce se è "connesso" a un altro frame multiorigine (in altre parole, se il primo destinatario inoltra il messaggio a un altro).

Funziona con Attivazione utente v2, sia nel formato originale sia con il concatenamento.