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

Mustaq Ahmed
Joe Medley
Joe Medley

Aby złośliwe skrypty nie nadużywały wrażliwych interfejsów API, takich jak wyskakujące okienka czy pełny ekran, przeglądarki kontrolują dostęp do tych interfejsów API przez aktywację użytkownika. Aktywacja użytkownika to stan sesji przeglądania związanej z działaniami użytkownika. Stan „aktywny” oznacza zazwyczaj, że użytkownik w danej chwili wchodzi w interakcję ze stroną lub zakończył interakcję od czasu jej wczytania. Gest użytkownika to popularne, ale mylące pojęcie dotyczące tego samego pomysłu. Na przykład gest przesunięcia lub przesunięcia przez użytkownika nie aktywuje strony, więc z punktu widzenia skryptu nie jest aktywacją użytkownika.

Główne przeglądarki wykazują obecnie bardzo rozbieżne w obsłudze sposób kontrolowania interfejsów API z aktywacją przez aktywację użytkowników. 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 umożliwiał niepełny dostęp do interfejsów API z aktywacją za pomocą wywołań postMessage() i setTimeout(), a aktywacja użytkowników nie była obsługiwana w przypadku Promises, XHR, interakcji z Gamepadem itp. Warto zauważyć, że niektóre z tych błędów występują od dawna i są popularne.

Chrome w wersji 72 udostępnia wersję 2 aktywacji użytkownika, dzięki której aktywacja wszystkich użytkowników jest dostępna we wszystkich interfejsach API objętych aktywacją. Eliminuje to niespójności wymienione powyżej (i kilka innych, np. MessageChannels), które naszym zdaniem ułatwią tworzenie stron internetowych związanych z aktywacją użytkowników. Ponadto nowa implementacja udostępnia referencyjną implementację proponowanej nowej specyfikacji, która w dłuższej perspektywie ma na celu połączenie wszystkich przeglądarek.

Jak działa Aktywacja użytkowników w wersji 2?

Nowy interfejs API zachowuje 2-bitowy stan aktywacji użytkownika w każdym obiekcie window w hierarchii ramek: bit przyklejony do historycznego stanu aktywacji użytkownika (jeśli ramka kiedykolwiek wykryła aktywację użytkownika) i bitowy bit przejściowy dla bieżącego stanu (jeśli ramka wykryła aktywację użytkownika w ciągu mniej więcej sekundy). Po ustawieniu bit przyklejony nie jest resetowany przez cały okres istnienia klatki. 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 z aktywacją wymagają aktywacji użytkownika w różny sposób. Nowy interfejs API nie zmienia żadnych działań specyficznych dla tego interfejsu. Na przykład można użyć tylko jednego 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ą powoduje teraz aktywowanie wszystkich zawierających klatki (i tylko te), niezależnie od ich pochodzenia. (W Chrome 72 oferujemy tymczasowe obejście pozwalające rozszerzyć widoczność na wszystkie ramki z tego samego źródła. Usuniemy to obejście, gdy będziemy mieć sposób jednoznacznego przekazania aktywacji użytkownika do ramek podrzędnych).
  • Gdy interfejs API z blokadą aktywacji jest wywoływany z aktywowanej ramki, ale spoza kodu modułu obsługi zdarzeń, będzie działać, dopóki stan aktywacji użytkownika to „aktywny” (np. nie stracił ważności ani nie został wykorzystany). Przed włączeniem aktywacji użytkownika w wersji 2 kończyła się ona bezwarunkowo niepowodzenie.
  • Wiele nieużywanych interakcji użytkownika w przedziale czasu wygaśnięcia powoduje połączenie w jedną aktywację odpowiadającą ostatniej interakcji.

Przykłady spójności w interfejsach API objętych aktywacją

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 naszej wersji demonstracyjnej setTimeout(). Jeśli moduł obsługi click spróbuje otworzyć wyskakujące okienko w ciągu sekundy, powinien się to udać niezależnie od tego, jak kod „tworzy” opóźnienie. Aktywacja użytkownika w wersji 2 spełnia to oczekiwanie, więc każdy z tych modułów obsługi zdarzeń otwiera wyskakujące okienko w 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 aktywacji użytkownika w wersji 2 drugi moduł obsługi zdarzeń kończy się niepowodzeniem we wszystkich testowanych przeglądarkach. W niektórych przypadkach nawet pierwsza czynność zakończy się niepowodzeniem.

postMessage() – połączenia między domenami

Oto przykład z naszej prezentacji postMessage(). Załóżmy, że moduł obsługi click w ramce podrzędnej z innej domeny wysyła 2 wiadomości bezpośrednio do ramki nadrzędnej. 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 aktywacji użytkownika w wersji 2 ramka nadrzędna nie może otworzyć wyskakującego okienka po otrzymaniu drugiej wiadomości. Nawet pierwsza wiadomość nie powiedzie się, jeśli jest „w łańcuchu” do innej ramki z innej domeny (czyli jeśli pierwszy odbiorca przekaże ją do innej).

Ta funkcja działa z aktywacją użytkowników w wersji 2 – zarówno w wersji pierwotnej, jak i w łańcuchu.