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. Chrome ermöglicht beispielsweise den unvollständigen Zugriff auf aktivierungsgesteuerte APIs über postMessage()- und setTimeout()-Aufrufe. Die Nutzeraktivierung wurde mit Promises, XHR, Gamepad-Interaktionen usw. nicht unterstützt. Beachten Sie, dass einige dieser Fehler beliebte, aber seit Langem bestehende Fehler sind.

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 Festlegen 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 aktivierungsgesteuerte APIs auf unterschiedliche Weise von der Nutzeraktivierung abhängig sind. In der neuen API wird dieses API-spezifische Verhalten nicht geändert. Beispiel: Es ist nur ein Pop-up pro Nutzeraktivierung zulässig, da window.open() die Nutzeraktivierung wie bisher verarbeitet. Navigator.prototype.vibrate() ist weiterhin effektiv, wenn ein Frame (oder einer seiner Subframes) jemals eine Nutzeraktion gesehen hat usw.

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 Problemumgehung, um die Sichtbarkeit auf alle Frames mit demselben Ursprung zu erweitern. 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 Version 2 schlug sie ohne Bedingungen fehl.
  • Mehrere nicht verwendete Nutzerinteraktionen innerhalb des Ablaufintervalls werden zu einer einzelnen Aktivierung zusammengeführt, 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 Versuch 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. Selbst die erste Nachricht schlägt fehl, wenn sie mit einem anderen ursprungsübergreifenden Frame „verkettet“ wird (mit anderen Worten, 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.