Gebruikersactivatie consistent maken tussen API's, Gebruikersactivatie consistent maken tussen API's

Mustaq Ahmed
Joe Medley
Joe Medley

Om te voorkomen dat kwaadaardige scripts misbruik maken van gevoelige API's zoals pop-ups, volledig scherm enz., controleren browsers de toegang tot die API's via gebruikersactivatie. Gebruikersactivatie is de status van een browsersessie met betrekking tot gebruikersacties: een "actieve" status houdt doorgaans in dat de gebruiker momenteel interactie heeft met de pagina, of een interactie heeft voltooid sinds het laden van de pagina. Gebruikersgebaar is een populaire maar misleidende term voor hetzelfde idee. Een veeg- of veeggebaar van een gebruiker activeert bijvoorbeeld geen pagina en is dus, vanuit scriptoogpunt, geen gebruikersactivatie.

De grote browsers vertonen tegenwoordig zeer uiteenlopende gedragingen rond de manier waarop gebruikersactivatie de activeringsgestuurde API's bestuurt. In Chrome was de implementatie gebaseerd op een op tokens gebaseerd model dat te complex bleek te zijn om consistent gedrag voor alle activeringsgated API's te definiëren. Chrome heeft bijvoorbeeld onvolledige toegang tot activatie-gated API's toegestaan ​​via postMessage() en setTimeout() aanroepen ; en gebruikersactivatie werd niet ondersteund met Promises , XHR , Gamepad-interactie , enz. Merk op dat sommige hiervan populaire maar al lang bestaande bugs zijn.

In versie 72 levert Chrome User Activation v2, waardoor de beschikbaarheid van gebruikersactivatie compleet is voor alle activatie-gated API's. Dit lost de bovengenoemde inconsistenties op (en nog een paar, zoals MessageChannels ), waarvan we denken dat ze de webontwikkeling rond gebruikersactivatie zouden vergemakkelijken. Bovendien biedt de nieuwe implementatie een referentie-implementatie voor een voorgestelde nieuwe specificatie die tot doel heeft alle browsers op de lange termijn samen te brengen.

Hoe werkt Gebruikersactivatie v2?

De nieuwe API onderhoudt een twee-bits gebruikersactiveringsstatus bij elk window in de framehiërarchie: een sticky bit voor de historische gebruikersactiveringsstatus (als een frame ooit een gebruikersactivatie heeft gezien) en een tijdelijke bit voor de huidige status (als een frame ooit een gebruikersactivatie heeft gezien) frame heeft binnen ongeveer een seconde een gebruikersactivatie gezien). Het plakkerige bit wordt nooit gereset tijdens de levensduur van het frame nadat het is ingesteld. De tijdelijke bit wordt ingesteld bij elke gebruikersinteractie en wordt gereset na een verloopinterval (ongeveer een seconde) of via een aanroep naar een activeringsconsumerende API (bijvoorbeeld window.open() ).

Houd er rekening mee dat verschillende activeringsgestuurde API's op verschillende manieren afhankelijk zijn van gebruikersactivatie; de nieuwe API verandert niets aan dit API-specifieke gedrag. Er is bijvoorbeeld slechts één pop-up per gebruikersactivatie toegestaan, omdat window.open() gebruikersactivatie verbruikt zoals vroeger, Navigator.prototype.vibrate() blijft effectief als een frame (of een van zijn subframes) ooit gebruikersactie heeft gezien , enzovoort.

Wat verandert er?

  • User Activation v2 formaliseert het idee van zichtbaarheid van gebruikersactivatie over framegrenzen heen: een gebruikersinteractie met een bepaald frame activeert nu alle frames die deze bevatten (en alleen die frames), ongeacht hun oorsprong. (In Chrome 72 hebben we een tijdelijke oplossing om de zichtbaarheid uit te breiden naar alle frames van dezelfde oorsprong. We zullen deze oplossing verwijderen zodra we een manier hebben om gebruikersactivatie expliciet door te geven aan subframes .)
  • Wanneer een activatie-gated API wordt aangeroepen vanuit een geactiveerd frame maar van buiten een gebeurtenishandlercode, zal deze werken zolang de gebruikersactiveringsstatus "actief" is (bijvoorbeeld niet verlopen of verbruikt). Vóór gebruikersactivatie v2 zou het onvoorwaardelijk mislukken.
  • Meerdere ongebruikte gebruikersinteracties binnen het vervaltijdsinterval versmelten tot één enkele activering die overeenkomt met de laatste interactie.

Voorbeelden van consistentie in activeringsgestuurde API's

Hier zijn twee voorbeelden met pop-upvensters (geopend met window.open() ) die laten zien hoe User Activation v2 het gedrag van activeringsafhankelijke API's consistent maakt.

Geketende setTimeout() -aanroepen

Dit voorbeeld komt uit onze setTimeout() demo . Als een click binnen een seconde een pop-up probeert te openen, wordt verwacht dat dit zal lukken, ongeacht hoe de code de vertraging "componeert". User Activation v2 voldoet aan deze verwachting, dus elk van de volgende gebeurtenishandlers opent een pop-up na een click (met een vertraging van 100 ms):

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

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

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

Zonder gebruikersactivatie v2 mislukt de tweede gebeurtenishandler in alle browsers die we hebben getest. (Zelfs de eerste faalt in sommige gevallen .)

postMessage() -aanroepen tussen domeinen

Hier is een voorbeeld uit onze postMessage() -demo . Stel dat een click in een cross-origin-subframe twee berichten rechtstreeks naar het bovenliggende frame verzendt. Het bovenliggende frame zou een pop-up moeten kunnen openen na ontvangst van een van deze berichten (maar niet beide):

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

Zonder gebruikersactivatie v2 kan het bovenliggende frame geen pop-up openen bij ontvangst van het tweede bericht. Zelfs het eerste bericht mislukt als het is "gekoppeld" aan een ander cross-origin frame (met andere woorden, als de eerste ontvanger het bericht doorstuurt naar een ander).

Dit werkt met User Activation v2, zowel in de originele vorm als met de koppeling.