Einheitliche Nutzeraktivierung über APIs hinweg

Mustaq Ahmed
Joe Medley
Joe Medley

Um zu verhindern, dass schädliche Scripts sensible APIs wie Pop-ups oder Vollbildmodus missbrauchen, steuern Browser den Zugriff auf diese APIs über die Nutzeraktivierung. Die Nutzeraktivität ist der Zustand einer Browser-Sitzung im Hinblick auf Nutzeraktionen: Ein „aktiver“ Zustand bedeutet in der Regel, dass der Nutzer entweder gerade mit der Seite interagiert oder seit dem Laden der Seite eine Interaktion ausgeführt hat. Nutzergeste ist ein beliebter, aber irreführender Begriff für dieselbe Idee. Wenn ein Nutzer beispielsweise wischt oder tippt, wird dadurch keine Seite aktiviert und es handelt sich daher aus Sicht des Scripts nicht um eine Nutzeraktivierung.

Die gängigen Browser unterscheiden sich heute stark bei der Frage, wie die APIs mit Aktivierungsvoraussetzung durch die Nutzeraktivierung gesteuert werden. In Chrome basierte die Implementierung auf einem tokenbasierten Modell, das sich als zu komplex erwies, um ein einheitliches Verhalten für alle APIs mit Aktivierungsbeschränkung zu definieren. Beispielsweise erlaubte Chrome über postMessage()- und setTimeout()-Aufrufe keinen vollständigen Zugriff auf APIs, die eine Aktivierung erfordern. Außerdem wurde die Nutzeraktivierung nicht mit Promises, XHR und Gamepad-Interaktionen unterstützt. Einige dieser Fehler sind bekannt und schon lange vorhanden.

In Version 72 wird in Chrome die Nutzeraktivierung v2 bereitgestellt, die die Nutzeraktivierung für alle APIs mit Aktivierungsvoraussetzung vollständig verfügbar macht. Dadurch werden die oben genannten Inkonsistenzen (und einige weitere, z. B. MessageChannels) behoben, was unserer Meinung nach die Webentwicklung im Zusammenhang mit der Nutzeraktivierung erleichtern würde. Außerdem dient die neue Implementierung als Referenzimplementierung für eine vorgeschlagene neue Spezifikation, die langfristig alle Browser zusammenführen soll.

Wie funktioniert die Nutzeraktivierung v2?

Die neue API verwaltet für jedes window-Objekt in der Framehierarchie einen zweibittigen Nutzeraktivierungsstatus: ein Sticky-Bit für den bisherigen Nutzeraktivierungsstatus (wenn für einen Frame schon einmal eine Nutzeraktivierung stattgefunden hat) und ein sitzungsspezifisches Bit für den aktuellen Status (wenn für einen Frame innerhalb von etwa einer Sekunde eine Nutzeraktivierung stattgefunden hat). Das Sticky-Bit wird nach dem Setzen während der Lebensdauer des Frames nie zurückgesetzt. Das sitzungsspezifische Bit wird bei jeder Nutzerinteraktion festgelegt und entweder nach einem Ablaufintervall (ungefähr eine Sekunde) oder durch einen Aufruf einer API zurückgesetzt, die eine Aktivierung erfordert (z.B. window.open()).

Beachten Sie, dass verschiedene APIs mit Aktivierungsbeschränkung die Nutzeraktivierung auf unterschiedliche Weise erfordern. Die neue API ändert keines dieser API-spezifischen Verhaltensweisen. Beispiel: Es ist nur ein Pop-up pro Nutzeraktivierung zulässig, weil window.open() die Nutzeraktivierung wie gewohnt in Anspruch nimmt. Navigator.prototype.vibrate() ist weiterhin effektiv, wenn für einen Frame (oder einen seiner Unterframes) eine Nutzeraktion erfolgt ist.

Was ändert sich?

  • Bei der Nutzeraktivierung v2 wird die Sichtbarkeit der Nutzeraktivierung über Frame-Grenzen hinweg formalisiert: Durch eine Nutzerinteraktion mit einem bestimmten Frame werden jetzt alle enthaltenen Frames aktiviert (und nur diese Frames), unabhängig von ihrem Ursprung. In Chrome 72 gibt es eine vorübergehende Lösung, mit der die Sichtbarkeit auf alle Frames desselben Ursprungs erweitert wird. Wir werden diese Umgehung entfernen, sobald wir eine Möglichkeit haben, die Nutzerautorisierung explizit an Unterframes weiterzugeben.
  • Wenn eine API mit Aktivierungsvoraussetzung von einem aktivierten Frame, aber außerhalb eines Ereignis-Handler-Codes aufgerufen wird, funktioniert sie, solange der Aktivierungsstatus des Nutzers „aktiv“ ist (d.h., er ist nicht abgelaufen und wurde nicht verbraucht). Vor der Nutzeraktivierung v2 wäre der Vorgang fehlgeschlagen.
  • Mehrere nicht verwendete Nutzerinteraktionen innerhalb des Ablaufzeitintervalls werden zu einer einzigen Aktivierung zusammengefasst, die der letzten Interaktion entspricht.

Beispiele für Konsistenz bei APIs mit Aktivierung

Hier sind zwei Beispiele mit Pop-up-Fenster (mit window.open() geöffnet), die zeigen, wie die Nutzeraktivierung v2 das Verhalten von APIs mit Aktivierungsvoraussetzung einheitlich macht.

Verkettete setTimeout()-Anrufe

Dieses Beispiel stammt aus unserer setTimeout()-Demo. Wenn ein click-Handler versucht, innerhalb einer Sekunde ein Pop-up zu öffnen, sollte dies unabhängig davon gelingen, wie die Verzögerung im Code „zusammengesetzt“ wird. Die Nutzeraktivierung v2 erfüllt diese Anforderung. Daher öffnet jeder der folgenden Ereignishandler ein Pop-up auf einer click (mit einer Verzögerung von 100 Millisekunden):

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

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

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

Ohne Nutzeraktivierung v2 schlägt der zweite Ereignishandler in allen von uns getesteten Browsern fehl. In einigen Fällen schlägt sogar der erste fehl.

Domainübergreifende postMessage()-Aufrufe

Hier ein Beispiel aus unserer postMessage()-Demo. Angenommen, ein click-Handler in einem plattformübergreifenden Unterframe sendet zwei Nachrichten direkt an den übergeordneten Frame. Der übergeordnete Frame sollte ein Pop-up öffnen können, wenn eine dieser Nachrichten empfangen wird (aber nicht 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);
});

Ohne Nutzeraktivierung v2 kann der übergeordnete Frame beim Empfang der zweiten Nachricht kein Pop-up öffnen. Auch die erste Nachricht schlägt fehl, wenn sie an einen anderen Frame mit unterschiedlichen Ursprüngen „angehängt“ ist, d. h., wenn der erste Empfänger die Nachricht an einen anderen weiterleitet.

Das funktioniert mit der Nutzeraktivierung 2, sowohl in der ursprünglichen Form als auch mit der Verknüpfung.