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. W Chrome implementacja była oparta na modelu opartym na tokenach, który okazał się zbyt skomplikowany, aby zdefiniować spójne działanie we wszystkich interfejsach API wymagających aktywacji. 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 stron 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 resetuje się nigdy. Bit przejściowy jest ustawiany przy każdej interakcji z użytkownikiem i resetowany po upływie okresu ważności (około sekundy) lub przez wywołanie interfejsu API wymagającego 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ń. Przykład: dozwolone jest tylko 1 wyświetlanie pop-up na aktywację użytkownika, ponieważ window.open() zużywa aktywację użytkownika tak jak wcześniej, Navigator.prototype.vibrate() nadal jest skuteczne, jeśli ramka (lub dowolna jej podramka) została kiedykolwiek wyświetlona w reakcji na działanie użytkownika, itd.

Co się zmienia?

  • Wersja 2 aktywacji przez użytkownika formalizuje pojęcie widoczności aktywacji przez użytkownika w okolicach klatki: interakcja użytkownika z konkretną klatką spowoduje teraz aktywację wszystkich otaczających ją klatek (i tylko tych klatek), 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 znajdziemy sposób na jawne przekazywanie aktywacji użytkownika do podramek.
  • 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 wersją 2 funkcji aktywacji użytkownika nie byłoby to możliwe.
  • 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 oknami (otwieranymi za pomocą window.open()), które pokazują, jak aktywacja użytkownika 2 zapewnia spójne działanie interfejsów API z wymaganą aktywacją.

Łańcuchowe setTimeout()połączenia

Ten przykład pochodzi z demonstracji setTimeout(). Jeśli w ciągu sekundy menedżer click spróbuje otworzyć wyskakujące okienko, powinien się ono otworzyć 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 rejestratorów zdarzeń 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. (nawet pierwszy kończy się niepowodzeniem w niektórych przypadkach).

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 bezpośrednio 2 wiadomości 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 przejdzie weryfikacji, jeśli jest „połączona” z innym elementem w ramach innej witryny (czyli jeśli pierwszy odbiorca przekaże wiadomość innemu).

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