Content Script: модифікація сторінок у браузерному розширенні

Як правильно впровадити content script у браузерне розширення?

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Content Script: модифікація сторінок у браузерному розширенні
Середній
~2-3 дні

Наші компетенції:

Часті запитання

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1419
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1287
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    984
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1248
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    984
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    998

Як правильно впровадити content script у браузерне розширення?

Уявіть: ви розробили розширення для Chrome, яке підсвічує ціни на сторінці інтернет-магазину. Content script працює при першому завантаженні, але коли користувач переходить у кошик через pushState, скрипт перестає реагувати — MutationObserver не спрацьовує. Така ситуація — класика SPA-пасток. Ми стикаємося з подібними кейсами на кожному другому проєкті. У цій статті розберемо, як коректно впровадити content script, обробити SPA-навігацію, організувати обмін даними з background і уникнути типових помилок.

Content script — це механізм ін'єкції коду на сторінку. Ми інтегруємо content script — JavaScript-файл, який браузер впроваджує в контекст цільової сторінки в ізольованому середовищі, як зазначено в документації Chrome Extensions. Це дає доступ до DOM, але не до змінних сторінки, що є і захистом, і обмеженням. Наш досвід — 5+ років і 30+ успішних проєктів у сфері браузерних розширень. Content script призначений для модифікації DOM цільової сторінки.

Як браузер завантажує content script

У manifest.json (MV3) оголошуєте список скриптів та умови їх запуску. Параметр run_at визначає момент впровадження. Нижче — порівняння значень:

run_at Момент впровадження Коли використовувати
document_start До побудови DOM Для перехоплення початкових запитів
document_end DOM готовий, ресурси ще завантажуються Для модифікації структури до рендерингу
document_idle Після DOMContentLoaded Безпечний дефолт для більшості завдань
{ "manifest_version": 3, "content_scripts": [ { "matches": ["https://*.example.com/*"], "js": ["content/injected.js"], "css": ["content/injected.css"], "run_at": "document_idle", "world": "ISOLATED" } ] } 

Якщо потрібно впровадити скрипт динамічно — з service worker або на вимогу — використовуйте chrome.scripting.executeScript:

// 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' }); }); 

Порівняння ISOLATED і MAIN world

world Доступ до DOM Доступ до JS сторінки Ізоляція Коли використовувати
ISOLATED Повний Ні Висока За замовчуванням
MAIN Повний Повний Низька Monkey-patching, перехоплення викликів

world: 'MAIN' дає доступ до змінних сторінки, але втрачає ізоляцію — використовуйте лише коли це дійсно потрібно (перехоплення викликів нативних API).

Чому content script не спрацьовує на SPA-сторінках?

При клієнтській навігації браузер не перезавантажує content script. MutationObserver на — надійний спосіб відстежити зміну маршруту, і він ефективніший, ніж періодичний опитування через setInterval — різниця в продуктивності сягає 5 разів при частих переходах.</p><pre><code class="language-javascript">let lastUrl = location.href; const urlObserver = new MutationObserver(() => { if (location.href !== lastUrl) { lastUrl = location.href; onNavigate(location.href); } }); urlObserver.observe(document.querySelector('title') ?? document.head, { subtree: true, characterData: true, childList: true }); </code></pre> <h2>Робота з DOM</h2><p>Content script бачить повний DOM, включаючи Shadow DOM. Для обробки динамічного контенту (SPA) обов'язковий MutationObserver. Приклад — підсвічування всіх цін на сторінці:</p><pre><code class="language-javascript">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); }); } 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(); </code></pre><p>У проєктах ми тестуємо до 10 цільових сайтів, обробляючи до 1000 DOM-вузлів за один прогін.</p><h2>Спілкування з background service worker</h2><p>Content script не має прямого доступу до <code>chrome.tabs</code>, тому використовуйте messaging. Для двостороннього потоку — встановлення порту:</p><pre><code class="language-javascript">// content/injected.js const port = chrome.runtime.connect({ name: 'content-stream' }); port.onMessage.addListener((msg) => { if (msg.type === 'DATA_CHUNK') appendChunk(msg.data); }); port.postMessage({ type: 'START_STREAM', url: location.href }); </code></pre><p>Основний сценарій — одноразовий запит через <code>chrome.runtime.sendMessage</code>. Background має повернути <code>true</code> у колбеку, якщо відповідь асинхронна.</p><h2>Як уникнути конфліктів стилів при впровадженні content script?</h2><p>Два підходи: Shadow DOM для повної ізоляції інтерфейсу розширення або CSS з високою специфічністю та унікальними префіксами класів (наприклад, <code>ext-</code>). Якщо сторінка використовує строгу CSP, додавайте стилі через <code>chrome.scripting.insertCSS</code> з service worker.</p><h2>Передача даних зі сторінки в content script</h2><p>Оскільки JS-контексти ізольовані, використовуйте <code>window.postMessage</code> зі скрипта сторінки (або MAIN world) і ловіть повідомлення в content script з перевіркою <code>event.source === window</code>.</p><h2>Що входить у роботу</h2><p>Розробка content script під ключ включає:</p><ul> <li>Налаштування маніфесту та декларацію скриптів.</li> <li>Обробку SPA-навігації та динамічного контенту.</li> <li>Інтеграцію з background service worker через messaging.</li> <li>Ізоляцію стилів та роботу з CSP.</li> <li>Тестування на цільових сторінках (до 10 сайтів).</li> <li>Документацію щодо використання та API розширення.</li> </ul> <h2>Типові проблеми та їх вирішення</h2><p>Часта проблема — content script не спрацьовує після SPA-переходу. Рішення — MutationObserver на <title>. Інша проблема — CSP блокує inline-стилі — тоді використовуйте chrome.scripting.insertCSS. Для підвищення продуктивності важкі операції з DOM виконуйте в requestAnimationFrame, обходи — в requestIdleCallback.</p><details> <summary>Додаткові складнощі</summary> <ul> <li>Якщо сторінка використовує Shadow DOM, переконайтеся, що ваш content script коректно проникає у відкриті shadow roots. Для закритих — без доступу.</li> <li>При роботі з iframe для впровадження у фрейми вкажіть <code>allFrames: true</code> в налаштуваннях.</li> </ul><p>Ми гарантуємо стабільну роботу розширення в Chrome, Edge та Opera. Зв'яжіться з нами для оцінки вашого проєкту або замовте розробку content script під ключ. Отримайте консультацію безкоштовно — наші інженери допоможуть реалізувати content script будь-якої складності.</p></div></div></main><div class="block relative mt-8 lg:hidden w-full"></div><footer class="relative hidden lg:block"><div class="mx-auto max-w-7xl py-12 px-6 lg:py-16 lg:px-8"><div class="pb-8 grid grid-cols-4 gap-8"><div class="mt-0"><div class="text-base text-black font-normal dark:text-white">Підтримка</div><ul class="mt-4 space-y-4"><li><a class="text-base text-black/75 dark:text-white/50 hover:text-black dark:hover:text-white transition-all" href="https://truetech.dev/ru/contacts">Безкоштовна консультація</a></li><li><a class="text-base text-black/75 dark:text-white/50 hover:text-black dark:hover:text-white transition-all" href="https://truetech.dev/ru/posts">Корисна інформація</a></li></ul></div><div><div class="text-base text-black font-normal dark:text-white">Рішення</div><ul class="mt-4 space-y-4"><li><a class="text-base text-black/75 dark:text-white/50 hover:text-black dark:hover:text-white transition-all" href="https://truetech.dev/ru/apps">Всі рішення</a></li></ul></div><div><div class="text-base text-black font-normal dark:text-white">Компанія</div><ul class="mt-4 space-y-4"><li><a class="text-base text-black/75 dark:text-white/50 hover:text-black dark:hover:text-white transition-all" href="https://truetech.dev/ru/contacts">Контакти</a></li><li><a class="text-base text-black/75 dark:text-white/50 hover:text-black dark:hover:text-white transition-all" href="https://truetech.dev/ru/about">Про компанію</a></li><li><a class="text-base text-black/75 dark:text-white/50 hover:text-black dark:hover:text-white transition-all" href="https://truetech.dev/ru/career">Робота в компанії</a></li><li><a class="text-base text-black/75 dark:text-white/50 hover:text-black dark:hover:text-white transition-all" href="https://truetech.dev/ru/affiliate-program">Партнерська програма</a></li></ul></div><div class="mt-0"><div class="text-base text-black font-normal dark:text-white">Language & Currency</div><div class="mt-4 flex flex-col gap-4"><div><div class="relative mt-1"><button class="relative rounded-lg w-full cursor-default py-3 pl-3 pr-10 text-left shadow-sm text-base font-normal text-black/75 dark:text-white/50 bg-light-item dark:bg-dark-item focus:outline-none focus:ring-2 focus:ring-dark-button" id="headlessui-listbox-button-:Rpkdl:" type="button" aria-haspopup="listbox" aria-expanded="false" data-headlessui-state=""><span class="block truncate">Український</span><span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon" class="h-5 w-5"><path fill-rule="evenodd" d="M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd"></path></svg></span></button></div></div><div><div class="relative"><button class="relative rounded-lg w-full cursor-default py-3 pl-3 pr-10 text-left shadow-sm text-base font-normal text-black/75 dark:text-white/50 bg-light-item dark:bg-dark-item focus:outline-none focus:ring-2 focus:ring-dark-button" id="headlessui-listbox-button-:Rqkdl:" type="button" aria-haspopup="listbox" aria-expanded="false" data-headlessui-state=""><span class="block truncate">USD</span><span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon" class="h-5 w-5"><path fill-rule="evenodd" d="M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd"></path></svg></span></button></div></div></div></div></div><div class="py-8 grid grid-cols-4 gap-8 border-t border-black/25 dark:border-white/25"><div><a class="text-base text-black/75 dark:text-white/50 hover:text-black dark:hover:text-white transition-all" href="https://truetech.dev/ru/privacy-policy">Політика конфіденційності</a></div><div><a class="text-base text-black/75 dark:text-white/50 hover:text-black dark:hover:text-white transition-all" href="https://truetech.dev/ru/terms-of-service">Умови використання</a></div><div class="col-span-2"><div class="text-base text-black/75 dark:text-white/50">© <!-- -->2026<!-- --> <!-- -->Справжні технології ТОВ. Всі права захищені.</div></div></div></div></footer></div><div class="fixed lg:hidden w-full bottom-0"><div class="flex gap-12 px-12 justify-center h-[3.25rem] bg-white border-t border-[#E8E8E8] dark:bg-dark-nav dark:border-none"><a class="grow flex items-center justify-center text-white h-full" aria-label="Головна" href="https://truetech.dev/ru"><span class="flex h-8 w-7 text-black dark:text-white opacity-100"><svg x="0px" y="0px" viewBox="0 0 28 32.7" fill="none" xmlns="http://www.w3.org/2000/svg" xml:space="preserve"><path d="M16.8,23v-6c0-0.3-0.1-0.5-0.3-0.7c-0.2-0.2-0.4-0.3-0.7-0.3h-4c-0.3,0-0.5,0.1-0.7,0.3 c-0.2,0.2-0.3,0.4-0.3,0.7v6c0,0.3-0.1,0.5-0.3,0.7c-0.2,0.2-0.4,0.3-0.7,0.3l-6,0c-0.1,0-0.3,0-0.4-0.1c-0.1-0.1-0.2-0.1-0.3-0.2 C3,23.6,3,23.5,2.9,23.3c-0.1-0.1-0.1-0.3-0.1-0.4V11.4c0-0.1,0-0.3,0.1-0.4c0.1-0.1,0.1-0.2,0.2-0.3l10-9.1 c0.2-0.2,0.4-0.3,0.7-0.3c0.2,0,0.5,0.1,0.7,0.3l10,9.1c0.1,0.1,0.2,0.2,0.2,0.3c0.1,0.1,0.1,0.3,0.1,0.4V23c0,0.1,0,0.3-0.1,0.4 c-0.1,0.1-0.1,0.2-0.2,0.3c-0.1,0.1-0.2,0.2-0.3,0.2C24.1,23.9,24,24,23.8,24l-6,0c-0.3,0-0.5-0.1-0.7-0.3 C17,23.5,16.8,23.2,16.8,23L16.8,23z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><line class="st1" x1="6.4" y1="32" x2="21.4" y2="32" fill="none" stroke="currentColor" stroke-linecap="round"></line></svg></span></a><a class="grow flex items-center justify-center text-white h-full" aria-label="Всі рішення" href="https://truetech.dev/ru/apps"><span class="flex h-8 w-7 text-black dark:text-white opacity-50"><svg x="0px" y="0px" viewBox="0 0 28 32.7" fill="none" xmlns="http://www.w3.org/2000/svg" xml:space="preserve"><path d="M11.8,3.1h-8v8h8V3.1z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M23.8,3.1h-8v8h8V3.1z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M11.8,15.1h-8v8h8V15.1z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M23.8,15.1h-8v8h8V15.1z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></span></a><a class="grow flex items-center justify-center text-white h-full" aria-label="Послуги" href="https://truetech.dev/ru#servicesList"><span class="flex h-8 w-7 text-black dark:text-white opacity-50"><svg x="0px" y="0px" viewBox="0 0 28 32.7" fill="none" xmlns="http://www.w3.org/2000/svg" xml:space="preserve"><path d="M2.8,13.2h22" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M2.8,5.2h22" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M2.8,21.2h22" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></span></a><a class="grow flex items-center justify-center text-white h-full" aria-label="Мій аккаунт" href="https://truetech.dev/ru/area51"><span class="flex h-8 w-7 text-black dark:text-white opacity-50"><svg x="0px" y="0px" viewBox="0 0 28 32.7" fill="none" xmlns="http://www.w3.org/2000/svg" xml:space="preserve"><path d="M13.8,17.2c4.4,0,8-3.6,8-8s-3.6-8-8-8s-8,3.6-8,8S9.4,17.2,13.8,17.2z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M1.7,24.2c1.2-2.1,3-3.9,5.1-5.1c2.1-1.2,4.5-1.9,7-1.9c2.5,0,4.9,0.6,7,1.9c2.1,1.2,3.9,3,5.1,5.1" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></span></a></div></div></div></div> <div id="dialog"></div> <div id="dialog-alert"></div> <div id="dialog-iframe" class="hidden"></div> </div> </body> </html>