- PVSM.RU - https://www.pvsm.ru -
Предположим, что у нас есть сайт по продаже билетов, на котором мы хотим в document.title показывать цену, чтобы увеличить удовлетворённость пользователей и дать им знать, что в какой вкладке у них открыто (т.к. мы не знаем, будет ли меняться место отправления/прибытия, дата, любимый перевозчик и так далее, а цена, напротив, для различных комбинаций практически всегда разная). При этом мы знаем, что, начиная с версии 56, Chrome проводит политику блокировки фоновых вкладок. Что это означает для данного функционала?
Согласно документации [1], rAF (requestAnimationFrame) обычно вызывается 60 раз в секунду. Для экранов, у которых частота обновления выше, это значение будет также выше (хотя вы можете и не замечать это глазом).
Далее написано, что, если вкладка фоновая и не активная, то частота может уменьшиться, что и происходит, как мы увидим ниже.
Проведём эксперимент. В коде под спойлером по requestAnimationFrame простейшим образом обновляется тайтл:
<!DOCTYPE>
<html>
<head>
<script>
requestAnimationFrame(function() {
document.title += ' RAF'
});
</script>
<title>tab</title>
</head>
<body>tab</body>
</html>
Результат в хроме выглядит следующим образом:
Да, так и должно быть. Давайте посмотрим, что делают другие браузеры:
* Edge работает аналогично
Результат — только Firefox выполняет rAF в фоновой вкладке. Казалось бы удивительно, что даже IE11 не выполняет rAF в фоне, однако, Per the documentation, Chrome does not call requestAnimationFrame() when a page is in the background. This behavior has been in place since 2011 [5]. С 2011, Карл!, что означает, что таким поведением никого не удивить. Не удивить, даже если в бэкграунде играет музыка (через <audio src="sound.mp3" autoplay></audio>
или что бы то ни было ещё), rAF, в фоне, при любых условиях, в любом браузере, отличным от FF, выполняться не будет.
Поэтому, отвечая на изначальный вопрос «Как показать цену в тайтле», ответ достаточно прост — показывать её не в момент отображения компонента, а в момент получения данных из API. Вторым решением был бы вынос из функции, которая используется для анимации страницы при возврате данных с API (большой лоадер на весь экран красиво убираем просто)), функции-инициализатора. К сожалению, так сложилось, что таких функций почему-то больше чем одна, поэтому этот вариант влечёт за собой большие перестановки и от него решено было отказаться. К слову, setTimeout/setInterval, выполняются корректно, через них возможен третий вариант решения проблемы.
На этом стори можно доделывать и закрывать, а теперь перейдём к тому, что происходит на самом деле, используя исходный код requestAnimationFrame из WebKit [6]:
int Document::requestAnimationFrame(Ref<RequestAnimationFrameCallback>&& callback)
{
if (!m_scriptedAnimationController) {
#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
m_scriptedAnimationController = ScriptedAnimationController::create(*this, page() ? page()->chrome().displayID() : 0);
#else
m_scriptedAnimationController = ScriptedAnimationController::create(*this, 0);
#endif
// It's possible that the Page may have suspended scripted animations before
// we were created. We need to make sure that we don't start up the animation
// controller on a background tab, for example.
if (!page() || page()->scriptedAnimationsSuspended())
m_scriptedAnimationController->suspend();
if (page() && page()->isLowPowerModeEnabled())
m_scriptedAnimationController->addThrottlingReason(ScriptedAnimationController::ThrottlingReason::LowPowerMode);
if (!topOrigin().canAccess(securityOrigin()) && !hasHadUserInteraction())
m_scriptedAnimationController->addThrottlingReason(ScriptedAnimationController::ThrottlingReason::NonInteractedCrossOriginFrame);
}
return m_scriptedAnimationController->registerCallback(WTFMove(callback));
}
Первый if довольно понятен, подробнее по suspendScriptedAnimations [7] и setIsVisibleInternal [8].
Во втором if, значение функции isLowPowerModeEnabled [9], у каждой ОС будет браться из разных источников; в iOS, вероятно, оно берётся из _didReceiveLowPowerModeChange [10].
Третий if наиболее интересен; topOrigin [11] это просто SecurityOrigin [12] документа верхнего уровня; сам метод находится здесь [13], в комментариях в этом методе достаточно подробно расписано, что в нём происходит. Этот метод возвращает true, если два скрипта с разных origin [14] могут общаться между собой (на чтение или на запись). В то же время, у пользователя остаётся место для манёвра при помощи NonInteractedCrossOriginFrame.
Таким образом, webkit-браузеры будут получать блокировку rAF каждый раз, когда они заходят в один из трёх if.
Вернёмся к Firefox. Преобразуем исходную функцию, меняющую заголовок страницы, на следующую:
requestAnimationFrame(function handler() {
document.title += 'R'
requestAnimationFrame(handler);
});
Как можно заметить, в Firefox/Gecko [15] всё происходит немного иначе, чем в webkit. Если найти реализацию request_animation_frame [16], то можно заметить, что:
Даже не знаю, кто круче.
Хорошие ссылки:
Автор: samsdemon
Источник [23]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/259455
Ссылки в тексте:
[1] документации: https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
[2] http-server: https://www.npmjs.com/package/http-server
[3] lt -p 8080: https://localtunnel.github.io/www/
[4] Electrolysis: https://wiki.mozilla.org/Electrolysis
[5] Per the documentation, Chrome does not call requestAnimationFrame() when a page is in the background. This behavior has been in place since 2011: https://developers.google.com/web/updates/2017/03/background_tabs
[6] исходный код requestAnimationFrame из WebKit: https://github.com/WebKit/webkit/blob/master/Source/WebCore/dom/Document.cpp#L6259
[7] suspendScriptedAnimations: https://github.com/WebKit/webkit/blob/master/Source/WebCore/page/Page.cpp#L1150
[8] setIsVisibleInternal: https://github.com/WebKit/webkit/blob/master/Source/WebCore/page/Page.cpp#L1669
[9] isLowPowerModeEnabled: https://github.com/WebKit/webkit/blob/master/Source/WebCore/page/Page.cpp#L1014
[10] _didReceiveLowPowerModeChange: https://github.com/WebKit/webkit/blob/master/Source/WebCore/platform/ios/LowPowerModeNotifierIOS.mm#L61
[11] topOrigin: https://github.com/WebKit/webkit/blob/master/Source/WebCore/dom/Document.h#L1268
[12] SecurityOrigin: https://lazka.github.io/pgi-docs/WebKit-3.0/classes/SecurityOrigin.html
[13] здесь: https://github.com/WebKit/webkit/blob/ba3ae6914e920af375b07235f3b5c60c427faffe/Source/WebCore/page/SecurityOrigin.cpp#L237
[14] origin: https://developer.mozilla.org/en-US/docs/Web/API/Document/origin
[15] Firefox/Gecko: https://github.com/mozilla/gecko-dev/blob/master/servo/components/script/dom/window.rs#L734
[16] request_animation_frame: https://github.com/mozilla/gecko-dev/blob/master/servo/components/script/dom/document.rs#L1538
[17] is_faking_animation_frames: https://github.com/mozilla/gecko-dev/blob/master/servo/components/script/dom/document.rs#L2481
[18] много : https://github.com/mozilla/gecko-dev/blob/master/servo/components/script/dom/document.rs#L153
[19] Background tabs & offscreen frames — Chrome team future plans: https://docs.google.com/document/d/18_sX-KGRaHcV3xe5Xk_l6NNwXoxm-23IOepgMx4OlE4/pub
[20] requestAnimationFrame Scheduling For Nerds: https://medium.com/@paul_irish/requestanimationframe-scheduling-for-nerds-9c57f7438ef4
[21] VSync if Firefox: http://www.vsynctester.com/firefoxisbroken.html
[22] How to dramatically improve Chrome's requestAnimationFrame VSYNC accuracy in Windows: https://bugs.chromium.org/p/chromium/issues/detail?id=467617
[23] Источник: https://habrahabr.ru/post/332174/?utm_source=habrahabr&utm_medium=rss&utm_campaign=sandbox
Нажмите здесь для печати.