Build content scripts for DOM manipulation, page interaction, and messaging between extension and web pages.
Static (manifest) vs Dynamic (programmatic):
Static (manifest.json):
"content_scripts": [{
"matches": ["*://*.example.com/*"],
"js": ["content.js"],
"css": ["content.css"],
"run_at": "document_idle",
"all_frames": false
}]
Dynamic (runtime injection):
// From background/popup — inject on demand
await chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ['content.js']
});
window.myPageVar)// Main world injection (to access page JS)
await chrome.scripting.executeScript({
target: { tabId },
world: 'MAIN',
func: () => window.myPageLibrary.getData()
});
Content script → Background:
// content.js
const response = await chrome.runtime.sendMessage({ action: 'getData', key: 'foo' });
// background.js
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.action === 'getData') {
sendResponse({ value: store[msg.key] });
}
return true; // async response
});
Page → Extension (custom events):
// Webpage dispatches event
window.dispatchEvent(new CustomEvent('myExt:request', { detail: { data } }));
// Content script listens
window.addEventListener('myExt:request', (e) => {
chrome.runtime.sendMessage({ data: e.detail.data });
});
MutationObserver for dynamic pages (SPAs)window.beforeunload// Get URL to bundled image/file
const imgUrl = chrome.runtime.getURL('images/icon.png');
Resource must be listed in web_accessible_resources in manifest.
document_start (before DOM), document_end (DOM ready), document_idle (default, after load)return true in message listeners for async responses?web_accessible_resources?return true for async messageswindow.myVar in isolated world returns undefined — page variables are invisible; use main world or postMessagechrome.runtime.sendMessage from content script fails if background service worker is terminated — add retry logic