Einheitliche Nutzeraktivierung über APIs hinweg

Mustaq Ahmed
Joe Medley
Joe Medley

Um zu verhindern, dass schädliche Skripts vertrauliche APIs wie Pop-ups oder den Vollbildmodus missbrauchen, steuern Browser den Zugriff auf diese APIs durch Nutzeraktivierung. Die Nutzeraktivierung ist der Status einer Browsersitzung in Bezug auf Nutzeraktionen: Ein Status "Aktiv" deutet normalerweise darauf hin, dass der Nutzer derzeit mit der Seite interagiert oder eine Interaktion seit dem Laden der Seite abgeschlossen hat. Nutzergeste ist ein beliebter, aber irreführender Begriff für dieselbe Idee. Beispielsweise wird durch eine Wisch- oder Wischgeste eines Nutzers keine Seite aktiviert und es handelt sich aus Skriptsicht nicht um eine Nutzeraktivierung.

Bei den meisten gängigen Browsern ist das Verhalten bei der Steuerung der Aktivierungs-Gated APIs durch die Nutzeraktivierung sehr unterschiedlich. In Chrome basierte die Implementierung auf einem tokenbasierten Modell, das sich als zu komplex herausstellte, um ein einheitliches Verhalten für alle aktivierungsgesteuerten APIs 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 Version 2 für die Nutzeraktivierung ausgeliefert. Dadurch ist die Nutzeraktivierung für alle APIs mit aktivierter Aktivierung vollständig verfügbar. Damit werden die oben erwähnten Inkonsistenzen behoben (und einige weitere, z. B. MessageChannels), die unserer Meinung nach die Webentwicklung im Zusammenhang mit der Nutzeraktivierung vereinfachen würden. Darüber hinaus bietet die neue Implementierung eine Referenzimplementierung für eine vorgeschlagene neue Spezifikation, mit der langfristig alle Browser zusammengeführt werden sollen.

Wie funktioniert die Nutzeraktivierung v2?

Die neue API behält bei jedem window-Objekt in der Framehierarchie einen 2-Bit-Nutzeraktivierungsstatus bei: ein Sticky Bit für den bisherigen Nutzeraktivierungsstatus (wenn ein Frame jemals eine Nutzeraktivierung erkannt hat) und ein temporäres Bit für den aktuellen Zustand (wenn ein Frame innerhalb von etwa einer Sekunde eine Nutzeraktivierung gesehen hat). Das Sticky Bit wird während seiner Lebensdauer nie zurückgesetzt. Das temporäre Bit wird bei jeder Nutzerinteraktion festgelegt und entweder nach einem Ablaufintervall (etwa eine Sekunde) oder durch einen Aufruf an eine API, die die Aktivierung nutzt (z.B. window.open()), zurückgesetzt.

Beachten Sie, dass unterschiedliche 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 für einen Frame (oder einen seiner Subframes) jemals eine Nutzeraktion verzeichnet wurde usw.

Was ändert sich?

  • Bei der Nutzeraktivierung v2 wird das Konzept der Sichtbarkeit der Nutzeraktivierung über Framegrenzen hinweg formalisiert: Bei einer Nutzerinteraktion mit einem bestimmten Frame werden jetzt unabhängig von ihrem Ursprung alle enthaltenden Frames aktiviert (und nur diese Frames). In Chrome 72 gibt es eine vorübergehende Problemumgehung, um die Sichtbarkeit auf alle Frames mit demselben Ursprung zu erweitern. Wir werden diese Problemumgehung entfernen, sobald wir eine Möglichkeit haben, die Nutzeraktivierung explizit an Subframes weiterzuleiten.
  • Wenn eine Aktivierungs-API von einem aktivierten Frame, aber von außerhalb eines Event-Handler-Codes aufgerufen wird, funktioniert sie so lange, wie der Aktivierungsstatus des Nutzers „aktiv“ ist (d.h. weder abgelaufen ist noch verbraucht wurde). Vor Version 2 schlug sie ohne Einschränkungen fehl.
  • Mehrere nicht verwendete Nutzerinteraktionen innerhalb des Ablaufintervalls werden zu einer einzelnen Aktivierung zusammengeführt, die der letzten Interaktion entspricht.

Beispiele für Konsistenz in aktivierungsgesteuerten APIs

Die folgenden zwei Beispiele mit Pop-up-Fenstern, die mit window.open() geöffnet werden, zeigen, wie die Nutzeraktivierung Version 2 das Verhalten von APIs mit Aktivierungszugriff 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, ist davon auszugehen, dass er erfolgreich ist, unabhängig davon, wie der Code die Verzögerung "zusammensetzt". Die Nutzeraktivierung 2 erfüllt diese Erwartungen. Daher öffnet jeder der folgenden Event-Handler bei einem click ein Pop-up-Fenster (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. (Auch die erste schlägt in einigen Fällen fehl.)

Domainübergreifende postMessage()-Aufrufe

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 ö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 die Nutzeraktivierung (v2) kann über den übergeordneten Frame beim Empfang der zweiten Nachricht kein Pop-up geöffnet werden. 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).

Dies funktioniert mit der Nutzeraktivierung (Version 2) sowohl im ursprünglichen Formular als auch mit der Verkettung.