home / skills / shipshitdev / library / content-script-developer

This skill helps you implement safe, idempotent browser content scripts that robustly inject UI and manage dynamic SPA pages.

npx playbooks add skill shipshitdev/library --skill content-script-developer

Review the files below or copy the command above to add this skill to your agents.

Files (2)
SKILL.md
3.6 KB
---
name: content-script-developer
description: Expert in browser extension content scripts, DOM integration, and safe page augmentation across modern web apps.
---

# 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 `requestAnimationFrame` or debouncing.
- Re-check anchors on navigation events.

### 4) Message and Store Data

- Use `chrome.runtime.sendMessage` for background/service worker calls.
- Use `chrome.storage` for 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.

Overview

This skill packages expert guidance and practical patterns for building low-impact content scripts for Chrome MV3 browser extensions. It focuses on stable DOM integration, safe styling, idempotent injection, and reliable operation on single-page applications. The guidance emphasizes performance, accessibility, secure messaging, and maintainability.

How this skill works

It inspects target pages to find stable anchors and SPA navigation signals, then injects a single root container or shadow root to host extension UI. It uses scoped styles or shadow DOM to avoid style collisions, MutationObserver plus throttling to react to dynamic changes, and Chrome runtime/storage APIs for messaging and persistence. Built-in patterns ensure idempotence, cleanup, and minimal main-thread work.

When to use it

  • Building or updating a content script for a Chrome MV3 extension
  • Injecting UI into third-party pages without breaking host layout or styles
  • Reading or scraping page state in an extension while respecting page scope
  • Handling SPA navigation, URL changes, or root swaps in dynamic web apps

Best practices

  • Create a single root container and return it from an ensureRoot helper to remain idempotent
  • Scope styles with a unique prefix or use a shadow root to avoid global CSS leakage
  • Use robust anchors (data-attributes, IDs, landmark roles) instead of brittle class chains
  • Throttle MutationObserver handlers with requestAnimationFrame or debouncing to avoid blocking the main thread
  • Keep sensitive tokens in extension storage and send messages via chrome.runtime.sendMessage using explicit message types

Example use cases

  • Add a floating action button anchored to a stable element on a third-party dashboard
  • Scrape post or comment data from an SPA and send it to a background worker for processing
  • Inject an accessibility toolbar with keyboard-focusable controls and ARIA labels scoped to a shadow root
  • Monitor DOM mutations to reattach tooltips or badges after client-side navigation
  • Persist user preferences in chrome.storage and reflect them in injected UI across navigations

FAQ

How do I avoid injecting the UI multiple times on SPA navigation?

Use a unique root container ID and an ensureRoot helper that returns the existing root if present; observe navigation events and re-check anchors rather than reinjecting.

When should I use a shadow root versus scoped styles?

Use shadow DOM when host styles frequently conflict with your UI or you need complete encapsulation; prefer scoped, prefixed CSS for simpler additions where shadow DOM breaks integration with page semantics.