Реализация Content Script (модификация страниц) в браузерном расширении

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

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

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация Content Script (модификация страниц) в браузерном расширении
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы

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

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация 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' даёт доступ к переменным страницы, но теряете изоляцию — используйте только когда это реально нужно (перехват вызовов нативных API, monkey-patching).

Работа с DOM

Content script видит полный DOM, включая Shadow 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();

MutationObserver — обязательный инструмент для SPA, где контент постоянно меняется без перезагрузки страницы.

Общение с background service worker

Content script не может напрямую обращаться к chrome API (например, chrome.tabs), поэтому сообщения идут через messaging:

// content/injected.js — отправка сообщения
const response = await chrome.runtime.sendMessage({
  type: 'FETCH_USER_SETTINGS',
  payload: { domain: location.hostname }
});

if (response.theme === 'dark') {
  document.documentElement.classList.add('ext-dark-mode');
}
// background/service-worker.js — обработка
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'FETCH_USER_SETTINGS') {
    chrome.storage.sync.get(['settings'], (result) => {
      sendResponse(result.settings ?? {});
    });
    return true; // Важно: говорит браузеру, что ответ будет асинхронным
  }
});

Для двустороннего потока данных (например, стриминг) используйте chrome.runtime.connect():

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

Внедрение стилей без конфликтов

CSS content script применяется к странице и может конфликтовать со стилями сайта. Два подхода:

1. Shadow DOM — полная изоляция:

const host = document.createElement('div');
host.id = 'my-extension-root';
document.body.appendChild(host);

const shadow = host.attachShadow({ mode: 'open' });
shadow.innerHTML = `
  <style>
    :host { all: initial; display: block; }
    .panel { background: #1a1a2e; color: #fff; padding: 16px; }
  </style>
  <div class="panel">Контент расширения</div>
`;

2. CSS с высокой специфичностью + уникальные префиксы классов:

/* injected.css — все классы с префиксом ext- */
.ext-toolbar {
  all: initial;
  display: flex !important;
  position: fixed !important;
  top: 0;
  right: 0;
  z-index: 2147483647; /* максимальный z-index */
}

Передача данных из страницы в content script

Поскольку JS-контекст изолирован, для получения данных из скриптов страницы (window-переменных, событий) используют window.postMessage:

// Скрипт внутри страницы (или MAIN world)
window.postMessage({ source: 'my-extension-page', type: 'AUTH_TOKEN', token: authToken }, '*');

// Content script (ISOLATED world) — слушает сообщения
window.addEventListener('message', (event) => {
  if (event.source !== window) return;
  if (event.data?.source !== 'my-extension-page') return;

  if (event.data.type === 'AUTH_TOKEN') {
    chrome.runtime.sendMessage({ type: 'STORE_TOKEN', token: event.data.token });
  }
});

Типичные проблемы

Content script не срабатывает на SPA-навигацию. Браузер не перезагружает скрипт при history.pushState. Решение — слушать popstate или использовать MutationObserver на изменения <title>:

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

CSP блокирует inline-стили. Если страница использует строгий Content-Security-Policy, добавление inline-стилей через element.style работает, но <style> тег — нет. Используйте chrome.scripting.insertCSS() из service worker вместо добавления тега вручную.

Производительность. Тяжёлые операции с DOM блокируют рендеринг. Batch-обновления через requestAnimationFrame, обходы большого DOM — через requestIdleCallback.

Структура файлов типичного content script модуля

extension/
├── manifest.json
├── content/
│   ├── index.js          # точка входа, инициализация
│   ├── dom-modifier.js   # работа с DOM
│   ├── messaging.js      # обмен сообщениями с background
│   └── styles.css
└── background/
    └── service-worker.js

Сборка через webpack или Vite с несколькими точками входа — content script и service worker компилируются отдельно, потому что у них разные среды выполнения и разные ограничения на импорты.