Interakcja z metodą document.write()

Czy ostatnio w Konsoli programisty w Chrome pojawiło się ostrzeżenie podobne do tego?

(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.

Łatwość pracy jest jedną z największych mocnych stron internetu, dzięki której możemy łatwo integrować się z usługami innych firm w celu tworzenia nowych, świetnych usług. Jedną z wad kompozycji jest to, że zakłada ona odpowiedzialność za wrażenia użytkownika. Jeśli integracja nie będzie optymalna, wpłynie to negatywnie na wrażenia użytkowników.

Jedną ze znanych przyczyn niskiej wydajności jest używanie na stronach kodu document.write(), zwłaszcza tych, które wstawiają skrypty. Może to być tak nieszkodliwe, ale może przysporzyć problemów u użytkowników.

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

Zanim przeglądarka wyrenderuje stronę, musi utworzyć drzewo DOM, analizując znaczniki HTML. Za każdym razem, gdy parser napotka skrypt, musi go zatrzymać i wykonać, zanim będzie mógł kontynuować analizę kodu HTML. Jeśli skrypt dynamicznie wstawia inny skrypt, parser musi czekać jeszcze dłużej na pobranie zasobu, co może spowodować naliczenie co najmniej jednej transmisji w obie strony sieci i opóźnienie czasu pierwszego wyrenderowania strony.

W przypadku użytkowników korzystających z wolnych połączeń, np. 2G, skrypty zewnętrzne umieszczane dynamicznie za pomocą interfejsu document.write() mogą opóźniać wyświetlanie zawartości strony głównej o dziesiątki sekund albo spowodować, że strony nie będą się ładować lub tak długo, że użytkownik odejdzie. Na podstawie instrumentacji w Chrome dowiedzieliśmy się, że strony zawierające skrypty innych firm wstawiane za pomocą interfejsu document.write() zwykle wczytują się 2 razy wolniej niż inne strony w sieci 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 ładowań stron w sieci 2G uwzględnia co najmniej 1 skrypt blokujący parser, który został umieszczony w dokumencie najwyższego poziomu za pomocą document.write(). Zablokowanie wczytywania tych skryptów zaobserwowaliśmy w nich następujące ulepszenia:

  • Strona wczytuje się o 10% więcej po pierwszym wyrenderowaniu treści (wizualne potwierdzenie dla użytkownika, że strona prawidłowo się wczytuje), o 25% więcej stron wczytywanych do momentu pełnego przeanalizowania i o 10% mniej przypadków ponownego załadowania, co może obniżyć frustrację użytkownika.
  • O 21% skrócenie średniego czasu (o ponad sekundę) do pierwszego wyrenderowania treści.
  • O 38% skrócenie średniego czasu potrzebnego na analizę strony. To wzrost o prawie 6 sekund, co znacząco skraca czas wyświetlania treści istotnych dla użytkownika.

Mając to na uwadze, Chrome od wersji 55 działa w imieniu wszystkich użytkowników, gdy wykryjemy ten znany nieprawidłowy wzorzec, zmieniając sposób obsługi document.write() w Chrome (zobacz Stan Chrome). W szczególności Chrome nie uruchamia elementów <script> wstrzykiwanych przez document.write(), gdy są spełnione wszystkie te warunki:

  1. Użytkownik korzysta z wolnego połączenia, zwłaszcza gdy korzysta z sieci 2G. (W przyszłości ta zmiana może obejmować innych użytkowników korzystających z wolnych połączeń, np. 3G lub Wi-Fi).
  2. document.write() jest w dokumencie najwyższego poziomu. Ta interwencja nie dotyczy skryptów document.write w elementach iframe, bo nie blokują one renderowania strony głównej.
  3. Skrypt w pliku document.write() blokuje parser. Skrypty z atrybutem „async” lub „defer” będą nadal wykonywane.
  4. Skrypt nie jest hostowany w tej samej witrynie. Innymi słowy, Chrome nie będzie interweniować w przypadku skryptów z pasującym adresem eTLD+1 (np. skrypt z domeny js.example.org wstawiony na stronie 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ą powodować opóźnień sieciowych i nadal będą wykonywane.
  6. Żądanie nie dotyczy ponownego załadowania strony. Jeśli użytkownik uruchomił ponowne załadowanie strony, Chrome nie zareaguje i wykona ją jak zwykle.

Fragmenty kodu innych firm czasami ładują skrypty za pomocą parametru document.write(). Na szczęście większość firm zewnętrznych udostępnia alternatywy ładowania asynchronicznego, które umożliwiają wczytywanie skryptów innych firm bez blokowania wyświetlania reszty strony.

Jak rozwiązać ten problem?

To prosta odpowiedź: nie wstrzyknij skryptów za pomocą funkcji 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 funkcji asynchronicznego wczytywania skryptów na stronie, zachęcamy do skontaktowania się z nami i poinformowania nas o tym.

Jeśli Twój dostawca udostępnia fragment kodu, który zawiera atrybut document.write(), być może możesz dodać do elementu skryptu atrybut async lub elementy skryptu za pomocą interfejsów DOM API, np. document.appendChild() lub parentNode.insertBefore().

Jak sprawdzić, czy zmiany dotyczą Twojej witryny

Istnieje wiele kryteriów, które określają, czy ograniczenie jest egzekwowane. Jak zatem sprawdzić, czy dana sytuacja Cię dotyczy?

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 interfejsu Network Information API, który jest dostępny w Chrome, a następnie wysłać powiadomienie do swoich systemów analitycznych lub wskaźników RUM.

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 Chrome 53 Narzędzia deweloperskie wysyłają ostrzeżenia w przypadku 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 o zapisie dokumentu.

Ostrzeżenia są wyświetlane w Narzędziach deweloperskich w Chrome bardzo dobrze, ale jak wykrywasz je na dużą skalę? Możesz sprawdzić nagłówki HTTP, które są wysyłane na serwer w momencie interwencji.

Sprawdź nagłówki HTTP w zasobie skryptu

Gdy skrypt wstawiony przez 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ą interfejsu document.write, który może zostać zablokowany w różnych okolicznościach, Chrome może wysłać:

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

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

Co niesie przyszłość?

Początkowy plan zakłada wykonanie tej interwencji, gdy wykryjemy, że kryteria spełniają kryteria. Zaczęliśmy od wyświetlania tylko ostrzeżenia w Konsoli programisty w Chrome 53. (Wersja beta była dostępna w lipcu 2016 r. Spodziewamy się, że wersja stabilna zostanie udostępniona wszystkim użytkownikom we wrześniu 2016 r.

W pierwszej kolejności podejmiemy działania, aby blokować skrypty wstrzykiwane przez użytkowników korzystających z 2G, począwszy od Chrome 54, który według szacunków zostanie w wersji stabilnej dla wszystkich użytkowników w połowie października 2016 r. Więcej informacji znajdziesz w artykule Stan Chrome.

Z czasem postanowiliśmy interweniować w sytuacjach, gdy użytkownicy mają wolne łącze (np.3G lub Wi-Fi). Postępuj zgodnie z tym wpisem o stanie Chrome.

Chcesz dowiedzieć się więcej?

Więcej informacji znajdziesz w tych dodatkowych materiałach: