Разработка контентных скриптов для браузерных расширений

Раздел: Веб-разработка -> Разработка расширений и модификация страниц

Основные принципы работы контентных скриптов

Базовый вариант - контентный скрипт, который изменяет фон страницы. Этот подход подходит для большинства задач по модификации внешнего вида или поведения веб-страниц.

// manifest.json (MV3)
{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0",
  "content_scripts": [
    {
      "matches": ["https://*/*"],
      "js": ["content.js"]
    }
  ]
}

// content.js
document.body.style.backgroundColor = 'lightblue';

Content js (контентный скрипт javascript)

Пояснение: Скрипт выполняется на каждой странице, соответствующей шаблону https://*/*. Изменение фона происходит сразу после загрузки DOM.

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

  • Скрипт может не успеть сработать до загрузки DOM - используйте document.addEventListener('DOMContentLoaded', ...).
  • Content Security Policy страницы может блокировать инлайн-стили. Рекомендуется добавлять стили через document.createElement('style').
  • Неправильно задан matches - скрипт не запустится. Проверяйте паттерн.

Как изменить текст на странице?

// content.js
const heading = document.querySelector('h1');
if (heading) {
  heading.innerText = 'Новый заголовок';
}

Цель: персонализация контента, например, замена рекламных заголовков.

Если элемент не найден, возникает ошибка Cannot read property 'innerText' of null. Решение - проверка существования элемента через if.

Как добавить новый элемент на страницу?

// content.js
const button = document.createElement('button');
button.textContent = 'Моя кнопка';
button.style.position = 'fixed';
button.style.top = '10px';
button.style.right = '10px';
document.body.appendChild(button);

Цель: внедрение пользовательского интерфейса (панели, формы).

Элемент может перекрывать важные части страницы. Используйте z-index и адаптивную верстку. Также возможны конфликты с CSS-фреймворками.

Как обработать клик и отправить сообщение в background-скрипт?

// content.js
button.addEventListener('click', () => {
  chrome.runtime.sendMessage({ action: 'buttonClicked', data: 'someValue' });
});

// background.js (service worker in MV3)
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === 'buttonClicked') {
    console.log('Кнопка нажата', message.data);
  }
});

Цель: взаимодействие контентного скрипта с фоновой логикой (запросы к API, сохранение данных).

В MV3 chrome.runtime.sendMessage работает, но для асинхронного ответа необходимо вернуть true из обработчика. Также не забудьте указать разрешение "permissions": ["scripting"] в некоторых случаях.

Как получить данные из chrome.storage?

// content.js
chrome.storage.local.get('settings', (data) => {
  if (data.settings && data.settings.theme) {
    document.body.classList.add(data.settings.theme);
  }
});

Цель: применение сохранённых пользовательских настроек.

chrome.storage - асинхронный API. Внутри колбэка можно безопасно работать с DOM. Ошибки: отсутствие разрешения "storage" в манифесте.

Как внедрить CSS-стили?

// content.js
const style = document.createElement('style');
style.textContent = `
  body { background: #f0f0f0 !important; }
  .my-class { color: red; }
`;
document.head.appendChild(style);

Цель: глобальное изменение стилей без переопределения каждого элемента.

Некоторые сайты используют CSP, блокирующие инлайн-стили. В таких случаях CSS следует подключать через chrome.declarativeContent.RequestContentScript или использовать chrome.scripting.insertCSS.

Как отслеживать появление новых элементов (динамический контент)?

// content.js
const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    mutation.addedNodes.forEach((node) => {
      if (node.nodeType === Node.ELEMENT_NODE && node.matches('.comment')) {
        node.style.border = '2px solid green';
      }
    });
  });
});
observer.observe(document.body, { childList: true, subtree: true });

Цель: обработка контента, загружаемого через AJAX или JavaScript.

Чрезмерное использование MutationObserver снижает производительность. Рекомендуется наблюдать только за конкретным контейнером и отключать наблюдатель, когда он больше не нужен.

Расширенные примеры контентных скриптов

Пример 1: Преобразование изображений в оттенки серого

Пример
// content.js
(async () => {
  const images = document.querySelectorAll('img');
  for (const img of images) {
    // Используем canvas для обработки
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = img.naturalWidth;
    canvas.height = img.naturalHeight;
    ctx.drawImage(img, 0, 0);
    
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const data = imageData.data;
    for (let i = 0; i < data.length; i += 4) {
      const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
      data[i] = data[i + 1] = data[i + 2] = gray;
    }
    ctx.putImageData(imageData, 0, 0);
    img.src = canvas.toDataURL();
  }
})();
Результат: все изображения на странице становятся чёрно-белыми. Обработка может занять время для большого числа картинок, рекомендуется использовать requestAnimationFrame или Web Workers.

Пример 2: Добавление плавающей панели инструментов с кнопками

Пример
// content.js
const panel = document.createElement('div');
panel.id = 'my-panel';
panel.style.cssText = `
  position: fixed;
  top: 20px;
  left: 20px;
  background: white;
  border: 1px solid #ccc;
  padding: 10px;
  z-index: 99999;
`;

const button = document.createElement('button');
button.textContent = 'Скрыть изображения';
button.onclick = () => {
  document.querySelectorAll('img').forEach(img => img.style.display = 'none');
};
panel.appendChild(button);
document.body.appendChild(panel);
Результат: в левом верхнем углу страницы появляется панель с кнопкой, которая скрывает все изображения. Панель остаётся при прокрутке.

Пример 3: Сбор всех ссылок и отправка в background для сохранения

Пример
// content.js
const links = Array.from(document.links).map(a => a.href);
chrome.runtime.sendMessage({ action: 'saveLinks', data: links });

// background.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === 'saveLinks') {
    chrome.storage.local.set({ savedLinks: message.data }, () => {
      console.log('Ссылки сохранены');
    });
  }
});
Результат: все абсолютные ссылки текущей страницы сохраняются в chrome.storage.local под ключом 'savedLinks'. Можно обработать массив для дальнейшего экспорта.

Пример 4: Автоматическая подсветка новых комментариев с помощью MutationObserver

Пример
// content.js
const commentContainer = document.querySelector('.comments-section'); // предполагается селектор
if (commentContainer) {
  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      mutation.addedNodes.forEach((node) => {
        if (node.nodeType === Node.ELEMENT_NODE && node.matches('.comment')) {
          node.style.backgroundColor = '#ffff99';
          // Удаляем подсветку через 2 секунды
          setTimeout(() => { node.style.backgroundColor = ''; }, 2000);
        }
      });
    });
  });
  observer.observe(commentContainer, { childList: true, subtree: true });
}
Результат: каждый новый комментарий, добавленный динамически, получает жёлтый фон на 2 секунды, после чего фон возвращается к исходному. Observer продолжает следить за контейнером.

Контентный скрипт JavaScript - comments

En
Content js (javascript)