Chrome Extension Dom Observer Patterns — Best Practices

4 min read

DOM Observer Patterns for Content Scripts

This guide covers patterns for observing DOM changes in Chrome extension content scripts using MutationObserver, IntersectionObserver, and ResizeObserver.

Basic MutationObserver Setup

const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    console.log('Changed:', mutation.type, mutation.target);
  });
});

observer.observe(document.body, {
  childList: true,
  attributes: true,
  subtree: true
});

Waiting for Dynamic Elements

Observe until target element appears, then disconnect:

function waitForElement(selector, parent = document) {
  return new Promise((resolve) => {
    if (parent.querySelector(selector)) return resolve(parent.querySelector(selector));
    
    const observer = new MutationObserver(() => {
      const el = parent.querySelector(selector);
      if (el) {
        observer.disconnect();
        resolve(el);
      }
    });
    
    observer.observe(parent, { childList: true, subtree: true });
  });
}

SPA Navigation Detection

For SPAs that don’t trigger page reloads, observe URL and body changes:

let lastUrl = location.href;
const navObserver = new MutationObserver(() => {
  if (location.href !== lastUrl) {
    lastUrl = location.href;
    console.log('Route changed:', location.href);
    // Handle route change
  }
});
navObserver.observe(document.body, { childList: true, subtree: true });

Efficient Observation

function debounce(fn, delay) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

Disconnecting Observers

Always disconnect when done to avoid memory leaks:

// When element is removed or feature is disabled
observer.disconnect();

Shadow DOM Observation

Observe inside shadow roots by targeting shadow hosts:

const shadowHost = document.querySelector('#host');
const shadowRoot = shadowHost.shadowRoot;
const observer = new MutationObserver(callback);
observer.observe(shadowRoot, { childList: true, subtree: true });

Intersection Observer

Trigger when elements enter viewport:

const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      console.log('Visible:', entry.target);
    }
  });
}, { threshold: 0.5 });

observer.observe(document.querySelector('.lazy-load'));

Resize Observer

Detect layout changes on specific elements:

const resizeObserver = new ResizeObserver((entries) => {
  entries.forEach((entry) => {
    console.log('Size:', entry.contentRect);
  });
});

resizeObserver.observe(document.querySelector('#container'));

Performance Considerations

Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.