Interakcja z metodą document.write()

Czy ostatnio w Konsoli deweloperów w Chrome pojawiło się takie ostrzeżenie?

(index):34 A Parser-blocking, cross-origin script,
https://paul.kinlan.me/ad-inject.js, is invoked via document.write().
This may be blocked by the browser if the device has poor network connectivity.

Złożoność jest jedną z wielkich zalet internetu, ponieważ umożliwia nam łatwą integrację z usługami innych firm, co pozwala tworzyć świetne nowe produkty. Jednym z negatywnych aspektów modułowości jest to, że zakłada ona współodpowiedzialność za wrażenia użytkownika. Jeśli integracja nie jest optymalna, wrażenia użytkownika mogą być negatywnie wpływione.

Jedną ze znanych przyczyn niskiej wydajności jest używanie właściwości document.write() na stronach, zwłaszcza takich, które wstawiają skrypty. Chociaż wygląda to niewinnie, może to spowodować poważne problemy dla użytkowników.

document.write('<script src="https://example.com/ad-inject.js"></script>');

Zanim przeglądarka będzie mogła renderować stronę, musi utworzyć drzewo DOM, analizując znaczniki HTML. Gdy tylko analizator napotka skrypt, musi się zatrzymać i go wykonać, zanim będzie mógł kontynuować analizowanie kodu HTML. Jeśli skrypt dynamicznie wstrzykuje inny skrypt, procesor musi czekać jeszcze dłużej na pobranie zasobu, co może spowodować więcej niż 1 przesyłkę w obie strony, a w efekcie wydłużyć czas pierwszego renderowania strony.

W przypadku wolnych połączeń, np. 2G, skrypty zewnętrzne umieszczane dynamicznie za pomocą instrukcji document.write() mogą opóźniać wyświetlanie zawartości strony głównej o kilkadziesiąt sekund lub spowodować, że strony wcale się nie załadują lub będą ładowały się tak długo, że użytkownik po prostu się podda. Na podstawie pomiarów w Chrome stwierdziliśmy, że strony, które zawierają skrypty innych firm wstawione za pomocą metody document.write(), ładują się zwykle dwa razy wolniej niż inne strony w 2G.

Zebraliśmy dane z 28-dniowego okresu próbnego obejmującego 1% użytkowników stabilnych Chrome, ograniczonych do użytkowników korzystających z połączeń 2G. Zauważyliśmy, że 7,6% wszystkich wczytanych stron w sieci 2G zawierało co najmniej 1 skrypt blokujący parsowanie, który został wstawiony za pomocą document.write() w dokumencie najwyższego poziomu. Po zablokowaniu wczytywania tych skryptów zaobserwowaliśmy następujące ulepszenia:

  • 10% więcej wczytań stron osiągających pierwsze wyrenderowanie treści (wizualne potwierdzenie dla użytkownika, że strona się wczytuje), 25% więcej wczytań stron osiągających stan pełnego zanalizowania i o 10% mniej ponownych wczytań, co sugeruje zmniejszenie frustracji użytkowników.
  • 21% spadek średniego czasu (o ponad sekundę krótszy) do pierwszego wyrenderowania treści
  • 38% krótszego średniego czasu analizowania strony, co oznacza skrócenie czasu wyświetlania tego, co jest ważne dla użytkownika, o prawie 6 sekund.

Mając to na uwadze, Chrome od wersji 55 interweniuje w imieniu wszystkich użytkowników, gdy wykryje ten znany nieprawidłowy wzorzec, zmieniając sposób obsługi document.write() w Chrome (patrz Stan Chrome). W szczególności Chrome nie wykona elementów <script> wstrzykniętych za pomocą document.write(), gdy spełnione są wszystkie te warunki:

  1. Użytkownik ma wolne połączenie, zwłaszcza gdy korzysta z sieci 2G. (w przyszłości zmiana może zostać rozszerzona na innych użytkowników korzystających z wolnych połączeń, np. 3G lub Wi-Fi).
  2. document.write() znajduje się w dokumencie najwyższego poziomu. Ta interwencja nie dotyczy skryptów document.written w ramkach iframe, ponieważ nie blokują one renderowania strony głównej.
  3. Skrypt w document.write() blokuje parsowanie. Skrypty z atrybutami async lub defer będą nadal wykonywane.
  4. Skrypt nie jest hostowany w tej samej witrynie. Inaczej mówiąc, Chrome nie będzie interweniować w przypadku skryptów z pasującym eTLD+1 (np. skrypt hostowany na js.example.org wstawiony na www.example.org).
  5. Skryptu nie ma jeszcze w pamięci podręcznej HTTP przeglądarki. Skrypty w pamięci podręcznej nie będą opóźnione przez sieć i nadal będą się wykonywać.
  6. Żądanie wyświetlenia strony nie jest ponownym wczytaniem. Jeśli użytkownik uruchomił ponowne załadowanie strony, Chrome nie zareaguje i wykona ją jak zwykle.

Fragmenty kodu innych firm czasami używają document.write() do wczytywania skryptów. Na szczęście większość firm zewnętrznych udostępnia alternatywne skrypty wczytywane asynchronicznie, które umożliwiają wczytywanie skryptów innych firm bez blokowania wyświetlania pozostałych treści na stronie.

Jak rozwiązać ten problem?

Najprostszą odpowiedzią jest niewstawianie skryptów za pomocą document.write(). Utrzymujemy zestaw znanych usług do obsługi ładowania asynchronicznego, do którego zachęcamy do sprawdzania.

Jeśli Twojego dostawcy nie ma na liście i obsługuje wczytywanie skryptu asynchronicznego, daj nam znać, a my zaktualizujemy stronę, aby pomóc wszystkim użytkownikom.

Jeśli Twój dostawca nie obsługuje asynchronicznego wczytywania skryptów na stronie, skontaktuj się z nim i poinformuj nas i jego, jak to wpłynie na jego usługi.

Jeśli Twój dostawca udostępnia Ci fragment kodu zawierający element document.write(), możesz dodać do niego atrybut async lub dodać elementy skryptu za pomocą interfejsu DOM API, np. document.appendChild() lub parentNode.insertBefore().

Jak sprawdzić, czy zmiany dotyczą Twojej witryny

Istnieje wiele kryteriów, które decydują o tym, czy ograniczenie jest egzekwowane. Jak więc sprawdzić, czy dotyczy ono Twojego konta?

Wykrywanie, kiedy użytkownik korzysta z sieci 2G

Aby zrozumieć potencjalny wpływ tej zmiany, musisz najpierw określić, ilu Twoich użytkowników będzie korzystać z sieci 2G. Możesz wykryć bieżący typ i szybkość sieci użytkownika, korzystając z dostępnego w Chrome interfejsu Network Information API, a potem wysłać powiadomienie do swoich systemów analitycznych lub RUM (Real User Metrics).

if(navigator.connection &&
    navigator.connection.type === 'cellular' &&
    navigator.connection.downlinkMax <= 0.115) {
    // Notify your service to indicate that you might be affected by this restriction.
}

Wyświetlanie ostrzeżeń w Narzędziach deweloperskich w Chrome

Od wersji 53 Chrome w Narzędziach deweloperskich wyświetla ostrzeżenia dotyczące problematycznych instrukcji document.write(). Jeśli żądanie document.write() spełnia kryteria od 2 do 5 (Chrome ignoruje kryteria połączenia, gdy wysyła to ostrzeżenie), ostrzeżenie będzie wyglądać np. tak:

Ostrzeżenie dotyczące zapisywania dokumentu.

Ostrzeżenia w Narzędziach dla programistów Chrome są świetne, ale jak wykrywać takie problemy na dużą skalę? Możesz sprawdzić nagłówki HTTP wysyłane na serwer, gdy nastąpi interwencja.

Sprawdź nagłówki HTTP zasobu skryptu.

Gdy skrypt wstawiony za pomocą document.write zostanie zablokowany, Chrome wyśle do żądanego zasobu ten nagłówek:

Intervention: <https://shorturl/relevant/spec>;

Gdy zostanie znaleziony skrypt wstawiony za pomocą document.write i może on zostać zablokowany w innych okolicznościach, Chrome może wysłać:

Intervention: <https://shorturl/relevant/spec>; level="warning"

Nagłówek interwencji zostanie wysłany jako część żądania GET skryptu (asyncjonalnie w przypadku rzeczywistej interwencji).

Co niesie przyszłość?

Początkowy plan zakłada wykonanie tej interwencji, gdy wykryjemy, że kryteria spełniają kryteria. Na początku w Chrome 53 wyświetlaliśmy tylko ostrzeżenie w Konsoli deweloperów. (wersja beta została opublikowana w lipcu 2016 r.). Spodziewamy się, że wersja stabilna zostanie udostępniona wszystkim użytkownikom we wrześniu 2016 r.).

Zamierzamy zablokować skrypty wstrzyknięte użytkownikom 2G, co nastąpi wstępnie od wersji Chrome 54, która według naszych szacunków zostanie stabilnie udostępniona wszystkim użytkownikom w połowie października 2016 roku. Więcej informacji znajdziesz w artykule Stan Chrome.

Z czasem będziemy interweniować, gdy któryś z użytkowników będzie miał wolne połączenie (np.wolne 3G lub Wi-Fi). Zapoznaj się z informacjami o stanie Chrome.

Chcesz dowiedzieć się więcej?

Więcej informacji znajdziesz w tych materiałach: