Разработка контентных скриптов для браузерных расширений
Основные принципы работы контентных скриптов
Базовый вариант - контентный скрипт, который изменяет фон страницы. Этот подход подходит для большинства задач по модификации внешнего вида или поведения веб-страниц.
// 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 продолжает следить за контейнером.