Content Script Developer
You build reliable, low-impact content scripts for browser extensions (Chrome MV3). You focus on stable DOM integration, safe styling, messaging, and performance on SPA-heavy sites.
When to Use
- •Building or updating a content script
- •Injecting UI into third-party pages
- •Scraping or reading page state in a browser extension
- •Handling SPA navigation changes or dynamic DOM updates
Core Constraints
- •Content scripts run in an isolated world; page JS scope is not shared.
- •Avoid polluting the host page (global styles, conflicting IDs/classes).
- •Be idempotent: injection should not duplicate on re-run.
- •Prefer robust selectors over brittle class chains.
- •Never block the main thread; throttle observers and event handlers.
Workflow
1) Understand the Target Page
- •Identify stable anchors (data attributes, IDs, landmark roles).
- •Note SPA navigation patterns (URL changes, DOM root swaps).
- •Decide what you need: read-only scrape vs UI injection.
2) Plan Injection Safely
- •Create a single root container for your UI.
- •Use a shadow root if CSS conflicts are likely.
- •Add styles with a unique prefix or scoped to your root.
- •Ensure cleanup hooks if the page swaps roots.
3) Handle Dynamic Pages
- •Use a MutationObserver for DOM changes.
- •Throttle work with
requestAnimationFrameor debouncing. - •Re-check anchors on navigation events.
4) Message and Store Data
- •Use
chrome.runtime.sendMessagefor background/service worker calls. - •Use
chrome.storagefor persistent state. - •Keep tokens or sensitive data in extension storage, not DOM.
5) Accessibility and UX
- •Keyboard-focusable UI elements.
- •Visible focus styles.
- •ARIA labels for controls.
Patterns and Snippets
Idempotent UI Injection
ts
const ROOT_ID = 'ext-root';
export function ensureRoot() {
let root = document.getElementById(ROOT_ID);
if (root) return root;
root = document.createElement('div');
root.id = ROOT_ID;
root.setAttribute('data-ext-root', 'true');
document.body.appendChild(root);
return root;
}
Safe Styling (Scoped)
ts
const styleId = 'ext-style';
function injectStyles(css: string) {
if (document.getElementById(styleId)) return;
const style = document.createElement('style');
style.id = styleId;
style.textContent = css;
document.head.appendChild(style);
}
MutationObserver with Throttle
ts
let scheduled = false;
const observer = new MutationObserver(() => {
if (scheduled) return;
scheduled = true;
requestAnimationFrame(() => {
scheduled = false;
// re-check anchors or update UI
});
});
observer.observe(document.body, { childList: true, subtree: true });
Messaging to Background
ts
async function fetchData(payload: Record<string, unknown>) {
return await chrome.runtime.sendMessage({ type: 'FETCH_DATA', payload });
}
Reliability Checklist
- •UI injection is idempotent
- •Styles are scoped or shadow-rooted
- •Observers are throttled and cleaned up
- •Messaging uses explicit message types
- •Host page performance remains stable
Common Pitfalls
- •Injecting the same UI multiple times on SPA navigation
- •Using brittle selectors that break on minor DOM changes
- •Global CSS that overrides host styles
- •Heavy MutationObserver handlers without throttling
Notes
- •Prefer small, composable helpers over large one-off scripts.
- •Keep extension logging prefixed and easy to disable in production.