Memory leaks in background scripts and content scripts accumulate silently across browsing sessions and tabs. A module-scope Map that grows on every webRequest without eviction, or event listeners added in repeated callbacks without removal, will grow until Chrome kills the extension process or the user notices slowdown. CWE-770 (Allocation of Resources Without Limits or Throttling) and ISO 25010:2011 performance-efficiency.resource-utilisation both cover this pattern. Extensions that trigger Chrome's "High memory usage" warning in the Task Manager get uninstalled.
High because unbounded memory growth from caches or listener accumulation eventually degrades system performance and triggers process termination, losing all in-memory state.
Add explicit size limits and eviction to every cache, and use named listener functions that can be removed when their context is no longer valid.
// Bounded cache with FIFO eviction
const MAX_CACHE_SIZE = 500;
const resultsCache = new Map();
function cacheResult(key, value) {
if (resultsCache.size >= MAX_CACHE_SIZE) {
resultsCache.delete(resultsCache.keys().next().value);
}
resultsCache.set(key, value);
}
// Removable listener — never add anonymous functions inside callbacks
function onTabUpdate(tabId, changeInfo, tab) { /* ... */ }
chrome.tabs.onUpdated.addListener(onTabUpdate);
// When done: chrome.tabs.onUpdated.removeListener(onTabUpdate);
Audit all module-scope Map, Set, and array declarations for unbounded growth paths.
ID: extension-ux-performance.bundle-memory.memory-under-50mb
Severity: high
What to look for: Examine the background script and content scripts for memory leak patterns. Look for: event listeners added without corresponding removal, large data structures accumulated in module-scope variables without cleanup, chrome.storage.local used as an unbounded cache with no eviction policy, DOM references retained after the associated elements are removed, and caches that grow without limits (e.g., Map or Set that is only ever added to, never cleared). Count all instances found and enumerate each.
Pass criteria: No obvious memory leak patterns are present. Data structures used for caching have explicit size limits or eviction logic. Event listeners are removed when their associated context is destroyed. Module-scope accumulating collections have TTL-based expiration or max-size enforcement. At least 1 implementation must be confirmed.
Fail criteria: Module-scope Map, Set, or array accumulates entries without any eviction (unbounded growth). Event listeners added in loops or repeated callbacks without removal. Large JSON blobs accumulated in memory from repeated API calls with no cleanup. Do NOT pass if memory grows monotonically over time without stabilizing — this indicates a memory leak even if the initial measurement is under 50MB.
Skip (N/A) when: The extension has no background script or service worker with persistent state.
Detail on fail: Identify the leak pattern. Example: "Module-level resultsCache Map grows without limit — new entries added on each webRequest, never evicted, leading to unbounded memory growth" or "Message listener added inside chrome.tabs.onUpdated callback, creating duplicate listeners on every tab update."
Remediation: Add eviction and cleanup to caches:
const MAX_CACHE_SIZE = 500;
const resultsCache = new Map();
function cacheResult(key, value) {
if (resultsCache.size >= MAX_CACHE_SIZE) {
// Evict oldest entry (first inserted in Map iteration order)
resultsCache.delete(resultsCache.keys().next().value);
}
resultsCache.set(key, value);
}
For listeners in callbacks, use named functions and remove them when no longer needed:
function onTabUpdate(tabId, changeInfo, tab) { /* ... */ }
chrome.tabs.onUpdated.addListener(onTabUpdate);
// Later: chrome.tabs.onUpdated.removeListener(onTabUpdate);