Дайджесты новостей
Визуальный срез двух параллельных потоков браузера, показывающий блокировку Web Worker через телеметрию Long Animation Frames в Chrome 153.

Зависший Web Worker: как Chrome 153 позволяет измерить задержки фонового потока

Каждый фронтендер знает золотое правило оптимизации: «Тяжёлые вычисления — долой из основного потока!» Если нужно отфильтровать массив на сто тысяч товаров, распарсить массивный Excel-файл или обработать картинку в Canvas, мы создаём Web Worker и радостно перекладываем задачу в фоновый поток.

С точки зрения браузерного движка всё выглядит великолепно: главный поток (Main Thread) свободен, интерфейс не зависает, кадры скролла рендерятся с частотой 60 fps, а ключевая метрика отзывчивости INP (Interaction to Next Paint) в панелях мониторинга горит безупречно зелёным цветом.

Но что видит реальный человек перед экраном? Он нажимает на фильтр или генерирует отчёт и видит бесконечный спиннер. Страница формально не зависла, но приложение не отвечает, потому что цикл событий (event loop) самого воркера намертво встал под грузом расчётов. До недавнего времени у команд не было нативного способа собирать объективную RUM-телеметрию из воркеров: они оставались «чёрным ящиком».

Эту слепую зону закрывает Long Animation Frames API в Chrome 153, расширенный на контекст DedicatedWorkerGlobalScope.

Как запустить PerformanceObserver внутри Web Worker

Хотя в воркере нет понятий «отрисовка кадра» или перерисовки DOM, браузер переиспользует семантику спецификации LoAF для измерения длинных блокирующих задач в фоновом цикле событий.

Теперь прямо внутри файла скрипта воркера можно инициализировать стандартный наблюдатель производительности:

// telemetry-worker.js
if (typeof PerformanceObserver !== 'undefined' && 
    PerformanceObserver.supportedEntryTypes.includes('long-animation-frame')) {
  
  const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // Пересылаем телеметрию блокировки в главный поток
      self.postMessage({
        type: 'PERF_TELEMETRY_LOAF',
        payload: {
          duration: entry.duration,
          startTime: entry.startTime,
          scripts: entry.scripts.map((script) => ({
            invoker: script.invoker,
            sourceURL: script.sourceURL,
            executionDuration: script.executionDuration,
          })),
        },
      });
    }
  });

  observer.observe({ type: 'long-animation-frame', buffered: true });
}

А в основном потоке остаётся лишь слушать входящие сообщения и отправлять их в аналитическую систему:

// main.js
const worker = new Worker('/telemetry-worker.js');

worker.onmessage = (event) => {
  if (event.data?.type === 'PERF_TELEMETRY_LOAF') {
    const { duration, scripts } = event.data.payload;
    console.warn(`[Worker LoAF] Воркер заблокирован на ${duration} мс:`, scripts);
    // Отправка в RUM-систему (Datadog, Sentry или собственную аналитику)
  }
};

Архитектурные нюансы и ограничения реализации

При проектировании мониторинга важно учитывать специфику рантайма:

  1. Изоляция контекстов: наблюдатель, созданный в window, не видит события воркеров! Наблюдатель обязан запускаться именно внутри воркера.
  2. Связка с кликом пользователя: чтобы понять, какой именно клик пользователя заблокировал воркер, передавай вместе с задачей сквозной correlationId и привязывай его к событиям PerformanceObserver.
  3. Ограничение первой версии: как отмечает эксперт по производительности Гарри Робертс в блоге CSS Wizardry, текущая реализация ловит одну непрерывную долгую задачу (long task). Она пока не детектирует «пробку» из сотен быстрых микрозадач по 5 миллисекунд, которые суммарно создают задержку в несколько секунд.

Инструмент превращает Web Workers из тёмной комнаты в прозрачный конвейер. Если тебе интересна тема глубокой диагностики веб-приложений, рекомендую изучить наше практическое руководство по панели Performance в Chrome DevTools.