Einheitliche Nutzeraktivierung über APIs hinweg

Mustaq Ahmed
Joe Medley
Joe Medley

Um zu verhindern, dass schädliche Skripts sensible APIs wie Pop-ups oder Vollbildanzeigen missbrauchen, steuern Browser den Zugriff auf diese APIs über die Nutzeraktivierung. Bei der Nutzeraktivierung handelt es sich um den Status einer Browsersitzung in Bezug auf Nutzeraktionen. Der Status „Aktiv“ bedeutet in der Regel, dass der Nutzer gerade mit der Seite interagiert oder seit dem Laden der Seite eine Interaktion abgeschlossen hat. Nutzergeste ist ein beliebter, aber irreführender Begriff für dieselbe Idee. Durch eine Wisch- oder Wischgeste eines Nutzers wird beispielsweise keine Seite aktiviert und es handelt sich somit aus Sicht eines Nutzers nicht um eine Nutzeraktivierung.

Größere Browser weisen heute ein stark abweichendes Verhalten bei der Steuerung der aktivierungsgesteuerten APIs durch die Nutzeraktivierung auf. Die Implementierung in Chrome basierte auf einem tokenbasierten Modell, das sich als zu komplex erwies, um ein einheitliches Verhalten für alle aktivierungsgesteuerten APIs zu definieren. So ermöglicht Chrome beispielsweise den unvollständigen Zugriff auf aktivierungsgesteuerte APIs über postMessage()- und setTimeout()-Aufrufe. Die Nutzeraktivierung wurde mit Promises, XHR, Gamepad-Interaktion usw. nicht unterstützt. Beachten Sie, dass einige dieser häufigen, aber seit Langem bestehenden Fehler sind.

In Version 72 stellt Chrome die Nutzeraktivierung v2 bereit. Dadurch ist die Verfügbarkeit der Nutzeraktivierung für alle aktivierungsgesteuerten APIs vollständig. Dadurch werden die oben genannten Inkonsistenzen und einige weitere wie MessageChannels behoben, die unserer Meinung nach die Webentwicklung rund um die Nutzeraktivierung vereinfachen würden. Darüber hinaus bietet die neue Implementierung eine Referenzimplementierung für eine vorgeschlagene neue Spezifikation, die auf lange Sicht alle Browser an einem Ort zusammenführen soll.

Wie funktioniert die Nutzeraktivierung v2?

Die neue API behält für jedes window-Objekt in der Framehierarchie einen 2-Bit-Nutzeraktivierungsstatus bei: ein fixiertes Bit für den bisherigen Nutzeraktivierungsstatus (wenn ein Frame jemals eine Nutzeraktivierung gesehen hat) und ein temporäres Bit für den aktuellen Status (wenn bei einem Frame in etwa einer Sekunde eine Nutzeraktivierung erkannt wurde). Das fixierte Bit wird während seiner Lebensdauer, nachdem es festgelegt wurde, nicht zurückgesetzt. Das temporäre Bit wird bei jeder Nutzerinteraktion festgelegt und entweder nach einem Ablaufintervall (etwa einer Sekunde) oder durch einen Aufruf einer API, die Aktivierungen benötigt (z.B. window.open()), zurückgesetzt.

Beachten Sie, dass verschiedene aktivierungsgesteuerte APIs auf unterschiedliche Weise von der Nutzeraktivierung abhängig sind. Die neue API ändert keines dieser API-spezifischen Verhaltensweisen. Beispiel: Pro Nutzeraktivierung ist nur ein Pop-up zulässig, weil window.open() die Nutzeraktivierung wie gewohnt nutzt. Navigator.prototype.vibrate() ist weiterhin effektiv, wenn ein Frame (oder einer seiner Subframes) jemals eine Nutzeraktion gesehen hat usw.

Was ändert sich?

  • Mit der Nutzeraktivierung v2 wird das Konzept der Sichtbarkeit der Nutzeraktivierung über Frame-Grenzen hinweg formalisiert: Bei einer Nutzerinteraktion mit einem bestimmten Frame werden jetzt alle enthaltenen Frames (und nur diese Frames) unabhängig von ihrem Ursprung aktiviert. In Chrome 72 gibt es eine vorübergehende Problemumgehung, um die Sichtbarkeit auf alle Frames desselben Ursprungs auszuweiten. Wir werden diese Problemumgehung entfernen, sobald wir eine Möglichkeit haben, die Nutzeraktivierung explizit an Subframes zu übergeben.)
  • Wenn eine aktivierungsgesteuerte API von einem aktivierten Frame, aber von außerhalb eines Event-Handler-Codes aufgerufen wird, funktioniert sie, solange der Nutzeraktivierungsstatus „Aktiv“ ist (also weder abgelaufen noch verbraucht wurde). Vor der Nutzeraktivierung v2 würde dieser unbedingt fehlschlagen.
  • Mehrere nicht verwendete Nutzerinteraktionen innerhalb des Ablaufintervalls werden zu einer einzelnen Aktivierung zusammengefasst, die der letzten Interaktion entspricht.

Beispiele für Konsistenz in aktivierungsgesteuerten APIs

Die folgenden zwei Beispiele mit Pop-up-Fenstern (geöffnet mit window.open()) zeigen, wie die Nutzeraktivierung Version 2 das Verhalten von APIs mit Aktivierungs-Tracking einheitlicher macht.

setTimeout() verkettete Anrufe

Dieses Beispiel stammt aus unserer setTimeout()-Demo. Wenn ein click-Handler versucht, ein Pop-up innerhalb einer Sekunde zu öffnen, wird davon ausgegangen, dass der Vorgang erfolgreich ist, unabhängig davon, wie der Code die Verzögerung „zusammensetzt“. Die Nutzeraktivierung v2 erfüllt diese Erwartungen, sodass jeder der folgenden Event-Handler ein Pop-up in einem click öffnet (mit einer Verzögerung von 100 ms):

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 Event-Handler in allen getesteten Browsern fehl. Selbst der erste schlägt in einigen Fällen fehl.

Domainübergreifende postMessage()-Anrufe

Hier ist ein Beispiel aus unserer postMessage()-Demo. Angenommen, ein click-Handler in einem ursprungsübergreifenden Subframe sendet zwei Nachrichten direkt an den übergeordneten Frame. Der übergeordnete Frame sollte ein Pop-up-Fenster öffnen können, wenn eine der folgenden 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 Meldung kein Pop-up-Fenster öffnen. Sogar die erste Nachricht schlägt fehl, wenn sie mit einem anderen ursprungsübergreifenden Frame "verkettet" wird, d. h. wenn der erste Empfänger die Nachricht an einen anderen weiterleitet.

Dies funktioniert mit der Nutzeraktivierung v2, sowohl in der ursprünglichen Form als auch mit der Verkettung.