2026-04-28
Stack Overflow: View Question
Tags: google-chrome-extension, memory-management, chrome-extension-manifest-v3
Score: 2 | Views: 91
The author has a Manifest V3 Chrome extension that polls a website every 2 seconds, scraping roughly 50KB of HTML each time. The background service worker starts at ~20MB of memory, then climbs to 60–200MB before occasionally dropping back. The heap snapshots in DevTools show a much smaller footprint than what the Task Manager reports, creating a confusing discrepancy. Occasionally the service worker terminates and restarts with a new worker ID.
This question is interesting because it sits at the intersection of three tricky topics: V8 garbage collection behavior, the Manifest V3 service worker lifecycle, and the subtleties of measuring memory in Chrome.
Why the heap snapshot doesn't match the Task Manager: Chrome's Task Manager reports the resident set size of the process, which includes V8's heap, compiled code, external allocations (like response buffers and parsed DOM trees from DOMParser or regex operations), and memory the OS hasn't reclaimed yet. A DevTools heap snapshot only shows the JS-managed heap. The gap between the two almost always means external or off-heap allocations are the real culprit.
Why it grows: Polling every 2 seconds with fetch() creates a rapid cycle of response body allocations. If the author is parsing the HTML—say, using DOMParser or creating a temporary document—each parse builds a full DOM tree that lives outside V8's tracked heap. Even if references are released, V8's generational GC doesn't immediately reclaim large short-lived allocations; they get promoted to old-space and sit there until a major GC sweep. At one poll every 2 seconds, new allocations outpace collection.
Approaches toward a solution:
string.indexOf() to extract the needed data rather than parsing 50KB of HTML into a DOM. This avoids the largest off-heap allocations entirely.response.body.cancel() or ensure response.text() promises are fully consumed and discarded. Dangling references to Response objects keep their underlying data buffers alive.EventSource / WebSocket to avoid repeated full-page fetches.chrome.alarms instead of setInterval. In MV3, setInterval doesn't survive service worker termination. The periodic restarts the author observes are the worker being killed and restarted—each restart leaks the interval state. chrome.alarms with a minimum period of 30 seconds is the sanctioned approach and would naturally reduce memory pressure.A key gotcha: MV3 service workers are designed to be short-lived. Fighting that by keeping one alive with rapid polling is working against the platform. The occasional worker ID changes aren't a bug—they're Chrome enforcing the intended lifecycle. Any fix needs to work with that model, not against it.
