Реалізація Content Script (модифікація сторінок) у браузерному розширенні
Content Script — це JavaScript-файл, який браузер внедрює у контекст цільової сторінки. Він працює в ізольованому середовищі ("isolated world"): має доступ до DOM сторінки, але не до змінних JS-коду самої сторінки. Це і захист, і обмеження одночасно.
Як браузер завантажує content script
У manifest.json (MV3) оголошуєте список скриптів та умови їх запуску:
{
"manifest_version": 3,
"content_scripts": [
{
"matches": ["https://*.example.com/*", "https://other-site.com/app/*"],
"js": ["content/injected.js"],
"css": ["content/injected.css"],
"run_at": "document_idle",
"all_frames": false,
"world": "ISOLATED"
}
]
}
run_at приймає три значення: document_start (до побудови DOM), document_end (DOM готовий, ресурси завантажуються), document_idle (після DOMContentLoaded — безпечний дефолт).
Для динамічного внедрення з service worker:
// background/service-worker.js
chrome.action.onClicked.addListener(async (tab) => {
await chrome.scripting.executeScript({
target: { tabId: tab.id, allFrames: false },
files: ['content/injected.js'],
world: 'ISOLATED' // або 'MAIN' для доступу до JS-контексту сторінки
});
});
world: 'MAIN' дає доступ до змінних сторінки, але втрачаєте ізоляцію — використовуйте тільки коли це реально потрібно (перехват викликів, monkey-patching).
Робота з DOM
Content Script бачить повний DOM. Проста задача — підсвітити всі ціни на сторінці:
// content/price-highlighter.js
function highlightPrices() {
const walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
{
acceptNode(node) {
return /\$[\d,]+\.?\d{0,2}/.test(node.textContent)
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_SKIP;
}
}
);
const nodes = [];
while (walker.nextNode()) nodes.push(walker.currentNode);
nodes.forEach(node => {
const span = document.createElement('span');
span.innerHTML = node.textContent.replace(
/(\$[\d,]+\.?\d{0,2})/g,
'<mark class="ext-price-highlight">$1</mark>'
);
node.parentNode.replaceChild(span, node);
});
}
// Сторінка могла завантажити контент через XHR/fetch після DOMContentLoaded
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.addedNodes.length > 0) {
highlightPrices();
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
highlightPrices();
Спілкування з background
// content/index.js
chrome.runtime.sendMessage({ type: 'PAGE_DATA', url: window.location.href }, (response) => {
console.log('Background відповів:', response);
});
// Слухати повідомлення з background
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'PERFORM_ACTION') {
performAction(message.payload);
sendResponse({ success: true });
}
});
Терміни
Простий DOM highlighter або injector: 1–2 дні. Складний парсер з фоновою синхронізацією: 3–5 днів.







