Rendere l'attivazione utente coerente tra le API

Mustaq Ahmed
Joe Medley
Joe Medley

Per impedire a script dannosi di utilizzare in modo improprio 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 rispetto alle azioni dell'utente: uno stato "attivo" in genere implica che l'utente stia attualmente interagendo con la pagina o che abbia completato un'interazione dal caricamento della pagina. Gesto dell'utente è un termine comune, ma fuorviante, che indica la stessa idea. Ad esempio, un gesto di scorrimento o un movimento brusco di un utente non attiva una pagina e quindi, dal punto di vista dello script, non è un'attivazione dell'utente.

I principali browser oggi mostrano un comportamento molto diverso per quanto riguarda il modo in cui l'attivazione dell'utente controlla le API con attivazione. In Chrome, l'implementazione si basava su un modello basato su token che si è rivelato troppo complesso per definire un comportamento coerente in tutte le API con attivazione controllata. Ad esempio, Chrome consente da tempo accesso incompleto alle API con attivazione tramite chiamate postMessage() e setTimeout(); inoltre, l'attivazione utente non era supportata con Promise, XHR, interazione con il gamepad e così via. Tieni presente che alcuni di questi sono bug noti e di lunga data.

Nella versione 72, Chrome fornisce la versione 2 dell'attivazione utente, che rende completa la disponibilità dell'attivazione utente per tutte le API con attivazione obbligatoria. In questo modo vengono risolte le incoerenze sopra menzionate (e altre ancora, come MessageChannels), che riteniamo possano semplificare lo sviluppo web per l'attivazione degli utenti. 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 la versione 2 dell'attivazione utente?

La nuova API mantiene uno stato di attivazione utente a due bit in ogni oggetto window nella gerarchia del frame: un bit fisso per lo stato storico di attivazione utente (se un frame ha mai registrato un'attivazione utente) e un bit transitorio per lo stato corrente (se un frame ha registrato un'attivazione utente in circa un secondo). Il bit sticky non viene mai reimpostato durante la vita del frame dopo l'impostazione. Il bit transitorio viene impostato a ogni interazione dell'utente e reimpostato dopo un intervallo di scadenza (circa un secondo) o tramite una chiamata a un'API che utilizza l'attivazione (ad es. window.open()).

Tieni presente che API con attivazione obbligatoria diverse 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() consuma l'attivazione utente come in passato, Navigator.prototype.vibrate() continua a essere efficace se un frame (o uno dei relativi frame secondari) ha mai registrato un'azione dell'utente, e così via.

Cosa cambierà?

  • La versione 2 dell'attivazione utente formalizza il concetto di visibilità dell'attivazione utente tra i confini dei frame: un'interazione dell'utente con un determinato frame ora attiva tutti i frame contenenti (e solo quelli) indipendentemente dalla loro origine. In Chrome 72 è disponibile una soluzione temporanea per estendere la visibilità a tutti i frame dello stesso dominio. Rimuoveremo questa soluzione alternativa quando avremo un modo per trasmettere esplicitamente l'attivazione dell'utente ai frame secondari.
  • Quando un'API con attivazione gated viene chiamata da un frame attivato, ma al di fuori di un codice di gestore eventi, funzionerà purché lo stato di attivazione dell'utente sia "attivo" (ad es. non sia scaduto né sia stato utilizzato). Prima della versione 2 dell'attivazione dell'utente, l'operazione non andava a buon fine in modo incondizionato.
  • Più interazioni utente inutilizzate nell'intervallo di tempo di scadenza si fondono in un'unica attivazione corrispondente all'ultima interazione.

Esempi di coerenza nelle API con attivazione gated

Di seguito sono riportati due esempi con finestre popup (aperte utilizzando window.open()) che mostrano come la versione 2 dell'attivazione utente rende coerente il comportamento delle API con attivazione obbligatoria.

Chiamate setTimeout() in catena

Questo esempio è tratto dalla nostra demo di setTimeout(). Se un gestore click tenta di aprire un popup entro un secondo, dovrebbe riuscire, indipendentemente da come il codice "compone" il ritardo. La versione 2 dell'attivazione utente soddisfa questa aspettativa, pertanto ciascuno dei seguenti gestori eventi apre un popup su un 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 la versione 2 di Attivazione utente, il secondo gestore eventi non funziona in tutti i browser che abbiamo provato. Anche il primo non va a buon fine in alcuni casi.

Chiamate postMessage() interdominio

Ecco un esempio tratto dalla nostra demo di postMessage(). Supponiamo che un gestore click in un frame secondario cross-origin invii due messaggi direttamente al frame principale. Il frame principale deve essere in grado di aprire un popup al ricevimento 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 la versione 2 di Attivazione utente, il frame principale non può aprire un popup al ricevimento del secondo messaggio. Anche il primo messaggio non va a buon fine se è "incatenato" a un altro frame cross-origin (in altre parole, se il primo destinatario inoltra il messaggio a un altro).

Questa operazione è compatibile con la versione 2 dell'attivazione utente, sia nella forma originale sia con la catena.