Hardware FixRecommendedDevice not working? Your driver may be the problemCheck updates for common hardware issues.Fix DriversHalloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run Scan×
FREEDOM251

Freedom251 › Blog › browser monitoring

Your App Freezes but Monitoring Says Everything’s Fine: Catching Main-Thread Stalls with a Web Worker Watchdog

By Freedom251 Team · · 8 min read

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When a page freezes, the code that would normally report the freeze is usually frozen with it. Error reporters, metric beacons and dashboard timers run on the same main thread as the interface, so a long synchronous task can stop them from running at all. The result is a dashboard that looks healthy because nothing arrived, not because nothing went wrong.

A Web Worker can close that gap. It can run a timer on its own thread, ask the page to answer a small message, and report a suspected unresponsive main thread when the answer is overdue. That is an engineering pattern built from standard worker messaging and browser scheduling, not a built-in browser feature that detects freezes. It also tells you less than it seems to. A missed reply shows that the page did not handle a message in time. It does not prove a crash, and it does not explain why.

As an Amazon Associate I earn from qualifying purchases.

Why monitoring goes silent during a freeze

Browsers run page JavaScript on the main thread, and the same thread handles event handling and document updates. MDN’s documentation on background tasks describes the main thread’s event-loop duties and the lag that appears when work piles up on it. MDN’s “How browsers work: Interactivity” explains the same mechanism from the rendering side: while a long task runs, the browser cannot promptly process input or repaint.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

That has a direct consequence for monitoring. A monitor written as ordinary page JavaScript is queued behind the work that is freezing the page. Three common failure patterns follow:

#1 Best Overall
Kaisi Professional Electronics Opening Pry Tool Repair Kit Metal Spudger | Non-Abrasive Nylon Spudgers and Anti-Static Tweezers for Cellphone iPhone Laptops Tablets and More, 20 Piece
  • Kaisi 20 pcs opening pry tools kit for smart phone,laptop,computer tablet,electronics, apple watch, iPad, iPod, Macbook, computer, LCD screen, battery and more disassembly and repair
  • Professional grade stainless steel construction spudger tool kit ensures repeated use
  • Includes 7 plastic nylon pry tools and 2 steel pry tools, two ESD tweezers
  • Includes 1 protective film tools and three screwdriver, 1 magic cloth,cleaning cloths are great for cleaning the screen of mobile phone and laptop after replacement.
  • Easy to replacement the screen cover, fit for any plastic cover case such as smartphone / tablets etc
  • Error reports never send. The handler that would catch the exception or stall cannot run until the task finishes.
  • Periodic metrics show a gap, not a spike. A missing data point looks like low traffic or a quiet period unless someone checks for absence.
  • Aggregates average the stall away. A ten-second freeze inside a five-minute reporting window can disappear into averages that are otherwise normal.

A watchdog has to live somewhere the stalled task cannot block. A Web Worker is that place.

What a Web Worker changes

MDN’s Web Workers API documentation states: “Web Workers makes it possible to run a script operation in a background thread separate from the main execution thread of a web application.” The worker communicates with the page through messages. It cannot manipulate the DOM, so it cannot see what the interface looks like. It can only send and receive messages and run its own timers.

That limitation is useful. The worker’s clock keeps running while the page is busy, and the only way the page can respond is by processing a message, which it can do only when its main thread is free. The worker therefore measures something concrete: how long the page took to handle a message it was sent.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The heartbeat cycle, step by step

The pattern works as a repeating ping and pong with a deadline:

  1. Start the worker. The page creates a dedicated worker with new Worker('heartbeat-worker.js') at application start.
  2. Ping on an interval. The worker’s setInterval posts a ping message with a sequence number and records when it was sent.
  3. Reply on the main thread. The page’s onmessage handler posts a pong back immediately. Keep this handler tiny, because its only job is to prove the main thread reached it.
  4. Clear the pending ping. When a matching pong arrives, the worker clears the outstanding ping.
  5. Flag an overdue ping. If a ping is still unanswered after the threshold, the worker posts suspected-stall to the page with the elapsed time.
  6. Report recovery. When a pong finally arrives after a flagged stall, the worker posts recovered so that the stall has an end time.

Implementation

The following code is a minimal, illustrative version. The interval and threshold values are examples only; they are not validated defaults, and you should choose them against the behavior of your own application.

Worker script (heartbeat-worker.js)

const INTERVAL_MS = 1000;  // how often to ping (example value)nconst TIMEOUT_MS = 4000;   // how long a pong may be overdue (example value)nnlet seq = 0;nlet outstanding = null;    // { seq, sentAt } while a pong is pendingnlet reportedStall = false;nnsetInterval(() => {n  const now = Date.now();n  if (outstanding === null) {n    seq += 1;n    outstanding = { seq, sentAt: now };n    postMessage({ type: 'ping', seq });n    return;n  }n  if (!reportedStall && now - outstanding.sentAt > TIMEOUT_MS) {n    reportedStall = true;n    postMessage({n      type: 'suspected-stall',n      seq: outstanding.seq,n      elapsedMs: now - outstanding.sentAtn    });n  }n}, INTERVAL_MS);nnonmessage = (event) => {n  const msg = event.data;n  if (msg.type !== 'pong' || outstanding === null || msg.seq !== outstanding.seq) return;n  if (reportedStall) {n    postMessage({n      type: 'recovered',n      seq: msg.seq,n      elapsedMs: Date.now() - outstanding.sentAtn    });n    reportedStall = false;n  }n  outstanding = null;n};

Note the arrow functions: the => in the worker’s onmessage assignment is the standard arrow syntax, written with HTML escapes here.

Page script

const worker = new Worker('heartbeat-worker.js');nnworker.onmessage = (event) => {n  const msg = event.data;n  if (msg.type === 'ping') {n    worker.postMessage({ type: 'pong', seq: msg.seq });n  } else if (msg.type === 'suspected-stall') {n    report('suspected-unresponsive-main-thread', msg);n  } else if (msg.type === 'recovered') {n    report('main-thread-responding-again', msg);n  }n};

The report function should record the event and its context. Do not send it through the same code path that is being monitored unless you accept that it may be delayed, and do not rely on it alone for a stall that never ends; a page that is fully stuck may never send the recovered event.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Record visibility with every event

Attach the document’s visibility state to each report, and keep a timestamp of the last visibility change so the event can be read in context:

let lastVisibilityChange = Date.now();ndocument.addEventListener('visibilitychange', () => {n  lastVisibilityChange = Date.now();n});nnfunction report(kind, detail) {n  const context = {n    kind,n    ...detail,n    visibilityState: document.visibilityState,n    msSinceVisibilityChange: Date.now() - lastVisibilityChange,n    reportedAt: new Date().toISOString()n  };n  // send context to your telemetry endpoint or log it locallyn}

What a missed heartbeat establishes and what it does not

A suspected-stall event establishes one narrow fact: during the interval shown, the page did not handle a message the worker sent within the threshold you configured. That is worth knowing, but it is an indirect signal.

  • It does not establish a crash. The page may have been busy, slow, or in a long but finite task that completed later.
  • It does not identify the cause. A missed reply does not say which function, component, or network call held the thread.
  • It does not establish that the user saw a freeze. If the tab was hidden, the delay may have no user-visible effect.
  • It cannot cover every failure of the worker. If the worker itself is starved or terminated, it cannot send any report, so silence from the worker is ambiguous.

Report the event with the wording the evidence supports, such as “suspected unresponsive main thread,” rather than “crash confirmed.”

Background tabs and throttling

Visibility is the most common source of false alarms. MDN’s Page Visibility API documentation states: “The Page Visibility API provides events you can watch for to know when a document becomes visible or hidden, as well as features to look at the current visibility state of the page.” In practice, that means you can tell whether a delayed heartbeat happened while the user was looking at the page.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Browsers apply scheduling policies to hidden tabs. Timers in background tabs can be throttled, and MDN notes that browsers may stop requestAnimationFrame() callbacks in hidden contexts. A heartbeat driven by main-thread timers can therefore slow down in a background tab for reasons unrelated to a freeze. Treat a stall reported while visibilityState is 'hidden' as a lower-priority signal, and check whether the delay is consistent with throttling before escalating it.

The reverse also matters. A stall that begins in the foreground and continues after the user switches away is still a stall. Use the visibility-change timestamp to split the interval at the point where the state changed, rather than discarding the event.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Pairing the heartbeat with other browser signals

The heartbeat is one of several signals, and each answers a different question. The Long Tasks API, published by the W3C, measures responsiveness and associates long tasks with symptoms users perceive as a locked-up page, including janky animation or scrolling. It is complementary evidence, not a replacement for an independent heartbeat, because it is recorded from the page’s own task activity.

A basic Long Tasks observer looks like this. Check current support on MDN before relying on it in production:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const longTaskObserver = new PerformanceObserver((list) => {n  for (const entry of list.getEntries()) {n    report('long-task', { durationMs: entry.duration, startTime: entry.startTime });n  }n});nlongTaskObserver.observe({ entryTypes: ['longtask'] });
Signal Depends on the main thread running? What it indicates Behavior in hidden tabs Can it prove a crash?
Worker heartbeat Yes. The page must process the ping to reply. A message was not handled within the configured threshold. Timers may be throttled; compare against visibility state. No
Long Tasks observations Yes. Entries are produced when a long task finishes. A task ran long enough to be recorded as a long task. Not stated in the sources reviewed for this article. No
Input responsiveness measurements Yes. Input must be handled by the page. User input was delayed. Not stated in the sources reviewed for this article. No
Page visibility state No. It reports document state. Whether the document is visible or hidden, and when that changed. Is the context itself. No

No single row proves a freeze. Together, a heartbeat stall that coincides with a long task in a visible page is a much stronger signal than either alone. The sources reviewed here do not establish that these signals cover all perceived jank, so keep them as complementary rather than exhaustive.

Troubleshooting a suspected stall

When your watchdog reports a stall, work through the event before escalating it:

  • Check visibility first. If visibilityState was 'hidden' for the whole interval, treat the event as background behavior until other evidence says otherwise.
  • Check for a matching long task. If a long-task entry overlaps the stall window, the page likely had a long synchronous task; look at what ran in that window.
  • Check for a recovery event. A recovered event with a similar elapsed time suggests a finite stall. If no recovery arrived, the page may still be stuck, or the worker may have stopped reporting; check whether the page is still rendering and whether the worker is alive.
  • Check whether the heartbeat handler was delayed. If the pong was late, verify that the page’s onmessage handler is small and does not wait on other work.
  • Check the threshold. If stalls are frequent but short, the threshold may be too tight for your application’s normal load on the target devices.

Limits of this pattern

The pattern tells you that something blocked the page’s message handling for longer than you allowed. It does not tell you why, and it does not replace error reporting or profiling. Use the heartbeat to decide when to capture more detail, such as the Long Tasks entries described above and a profile from the browser’s developer tools on a reproduction, rather than treating the watchdog itself as the diagnosis. The timing values in this article are examples, and no benchmark in the sources supports a specific default.

Quick Recap

Bestseller No. 1
Kaisi Professional Electronics Opening Pry Tool Repair Kit Metal Spudger | Non-Abrasive Nylon Spudgers and Anti-Static Tweezers for Cellphone iPhone Laptops Tablets and More, 20 Piece
Kaisi Professional Electronics Opening Pry Tool Repair Kit Metal Spudger | Non-Abrasive Nylon Spudgers and Anti-Static Tweezers for Cellphone iPhone Laptops Tablets and More, 20 Piece
Professional grade stainless steel construction spudger tool kit ensures repeated use; Includes 7 plastic nylon pry tools and 2 steel pry tools, two ESD tweezers
$9.99
SaleBestseller No. 2

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

browser monitoringFront-end performanceJavaScriptMain threadWeb Workers

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.