Spójność aktywacji użytkowników we wszystkich interfejsach API

Mustaq Ahmed
Joe Medley
Joe Medley

Aby zapobiec nadużyciom interfejsów API, które mają dostęp do poufnych danych, takich jak wyskakujące okienka czy tryb pełnoekranowy, przeglądarki kontrolują dostęp do tych interfejsów API za pomocą aktywacji przez użytkownika. Aktywacja użytkownika to stan sesji przeglądania w związku z działaniami użytkownika: stan „aktywny” zwykle oznacza, że użytkownik obecnie wchodzi w interakcję ze stroną lub zakończył interakcję po jej załadowaniu. Gest użytkownika to popularny, ale mylący termin określający tę samą czynność. Na przykład gest przesunięcia palcem nie aktywuje strony, a więc nie jest z punktu widzenia skryptu aktywacją użytkownika.

Obecnie główne przeglądarki działają bardzo różnie w sposobie, w jaki aktywacja użytkownika kontroluje interfejsy API z wymaganiem aktywacji. Implementacja w Chrome opierała się na modelu opartym na tokenach, który okazał się zbyt złożony, aby można było zdefiniować spójne działanie we wszystkich interfejsach API objętych aktywacją. Na przykład Chrome zezwala na częściowy dostęp do interfejsów API wymagających aktywacji za pomocą wywołań postMessage()setTimeout(), a aktywacja użytkownika nie jest obsługiwana w przypadku obietnic, XHR, interakcji z kontrolerem gry itp. Pamiętaj, że niektóre z tych błędów są popularne, ale istnieją od dawna.

W wersji 72 Chrome udostępnia funkcję aktywacji użytkownika 2, która umożliwia pełną aktywację wszystkich interfejsów API wymagających aktywacji. Pozwoli to rozwiązać wspomniane powyżej niespójności (oraz kilka innych, np. MessageChannels), co naszym zdaniem ułatwi tworzenie aplikacji internetowych związanych z aktywacją użytkownika. Ponadto nowa implementacja zawiera implementację referencyjną proponowanej nowej specyfikacji, która ma na celu zbliżenie wszystkich przeglądarek w długim okresie.

Jak działa wersja 2 funkcji Aktywacja użytkownika?

Nowe API utrzymuje 2-bitowy stan aktywacji użytkownika w każdym obiekcie window w hierarchii ramki: stały bit dla historycznego stanu aktywacji użytkownika (jeśli ramka kiedykolwiek była aktywowana przez użytkownika) oraz przejściowy bit dla bieżącego stanu (jeśli ramka była aktywowana przez użytkownika w ciągu około sekundy). Po ustawieniu w ramce nie jest nigdy resetowany. Bit przejściowy jest ustawiany przy każdej interakcji użytkownika i resetowany po upływie okresu ważności (około sekundę) lub przez wywołanie interfejsu API, który wymaga aktywacji (np. window.open()).

Pamiętaj, że różne interfejsy API, które wymagają aktywacji, różnie aktywują użytkowników. Nowy interfejs API nie zmienia żadnego z tych zachowań. Na przykład można użyć tylko 1 wyskakującego okienka na aktywację użytkownika, ponieważ window.open() w takiej postaci, w jakiej używał aktywacji użytkownika, Navigator.prototype.vibrate() działa nadal, gdy ramka (lub którakolwiek z jej ramek podrzędnych) kiedykolwiek zarejestrowała działanie użytkownika.

Co się zmienia?

  • Aktywacja użytkownika w wersji 2 formalnie definiuje pojęcie widoczności aktywacji użytkownika na granicach ramek. Interakcja użytkownika z konkretną klatką będzie teraz aktywować wszystkie zawierające klatki (i tylko te), niezależnie od ich pochodzenia. (W Chrome 72 mamy tymczasowe obejście, które umożliwia rozszerzenie widoczności na wszystkie ramki tego samego pochodzenia. Usuniemy to obejście, gdy będziemy mieć sposób jednoznacznego przekazania aktywacji użytkownika do ramek podrzędnych).
  • Gdy interfejs API z ograniczeniem aktywacji jest wywoływany z aktywowanego ramki, ale poza kodem modułu obsługi zdarzenia, działa tak długo, jak długo stan aktywacji użytkownika jest „aktywny” (np. nie wygasł ani nie został wykorzystany). Przed włączeniem aktywacji użytkownika w wersji 2 kończyła się ona bezwarunkowo niepowodzenie.
  • Wiele niewykorzystanych interakcji użytkownika w okresie ważności łączy się w jedną aktywację odpowiadającą ostatniej interakcji.

Przykłady spójności w przypadku interfejsów API wymagających aktywacji

Oto 2 przykłady z wyskakującymi okienkami (otwieranymi za pomocą window.open()), które pokazują, jak spójne jest działanie interfejsów API objętych aktywacją w wersji 2.

Łańcuch setTimeout() połączeń

Ten przykład pochodzi z demonstracji setTimeout(). Jeśli w ciągu sekundy click próbuje otworzyć wyskakujące okienko, powinno się to udać niezależnie od tego, jak kod „składa” opóźnienie. Aktywacja użytkownika w wersji 2 spełnia te wymagania, więc każdy z tych elementów sterujących zdarzeniami otwiera wyskakujące okienko w przypadku click (z opóźnieniem 100 ms):

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

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

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

Bez wersji 2 funkcji aktywacji użytkownika drugi moduł obsługi zdarzeń nie działa we wszystkich przetestowanych przez nas przeglądarkach. W niektórych przypadkach nawet pierwsza czynność zakończy się niepowodzeniem.

Wywołania postMessage() w wielu domenach

Oto przykład z naszej wersji demonstracyjnej postMessage(). Załóżmy, że moduł obsługi click w ramce podrzędnej w innej domenie wysyła 2 wiadomości bezpośrednio do nadrzędnej ramki. Ramka nadrzędna powinna mieć możliwość otwarcia wyskakującego okienka po otrzymaniu jednej z tych wiadomości (ale nie obu):

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

Bez wersji 2 aktywacji użytkownika element nadrzędny nie może otworzyć wyskakującego okienka po otrzymaniu drugiej wiadomości. Nawet pierwsza wiadomość nie zadziała, jeśli jest „w łańcuchu” do innej ramki z innej domeny (czyli jeśli pierwszy odbiorca przekaże ją do innej).

Funkcja ta działa z aktywizacją użytkownika w wersji 2, zarówno w pierwotnej formie, jak i z łańcuchem.