Проблема: Background Service Worker — не просто перейменування
У Manifest V3 звичний background page перетворився на service worker. Це ламає патерни, які працювали роками: браузер може завершити SW у будь-який момент, якщо немає активних завдань. Ми зіткнулися з цим на практиці, коли клієнт втратив дані через глобальний лічильник, який не пережив перезапуск. Рішення — переосмислити архітектуру.
Наприклад, при обробці вхідних повідомлень з content script, якщо обробник не зареєстрований синхронно, події можуть бути втрачені. Таке відбувається, коли розробники використовують async ініціалізацію всередині верхнього рівня — браузер просто не «побачить» обробник при першому запуску SW. В результаті розширення перестає реагувати на дії користувача.
Ми розробляємо браузерні розширення більше п'яти років (5+ років досвіду, 20+ успішних проєктів) і реалізували понад 20 проєктів з Service Worker. Наш досвід дозволяє гарантувати стабільність та продуктивність навіть у складних сценаріях. Хочете, щоб ваше розширення працювало без збоїв? Замовте аудит під ключ — ми оцінимо ваш проєкт і запропонуємо план міграції протягом 2 днів. Вартість аудиту — від $300.
Причини завершення Service Worker
Браузер запускає SW при:
- встановленні/оновленні розширення
- отриманні повідомлення з content script або popup
- спрацьовуванні alarm
- настанні мережевої події (якщо підписаний)
Після завершення всіх обробників браузер може вбити процес через ~30 секунд неактивності. Наступна подія підніме його знову — вже з чистим станом.
Висновок: жодного глобального стану в пам'яті. Змінні не переживають перезапуск.
// ПОГАНО — стан втратиться при перезапуску SW
let requestCount = 0;
chrome.runtime.onMessage.addListener(() => {
requestCount++; // після перезапуску SW знову 0
});
// ДОБРЕ — зберігаємо в chrome.storage
chrome.runtime.onMessage.addListener(async () => {
const { requestCount = 0 } = await chrome.storage.local.get('requestCount');
await chrome.storage.local.set({ requestCount: requestCount + 1 });
});
Забезпечення збереження даних та синхронна реєстрація подій
Перше правило — не покладатися на глобальні змінні. Використовуйте chrome.storage.local або chrome.storage.sync для зберігання стану. Друге — для операцій, які мають бути атомарними, застосовуйте транзакційний підхід з чергою. Це гарантує, що жодне завдання не буде втрачене навіть при раптовому завершенні SW.
Обробники мають бути зареєстровані синхронно на верхньому рівні. Інакше браузер може не «побачити» їх при запуску SW:
// background/sw.js
// ПРАВИЛЬНО — синхронна реєстрація
chrome.runtime.onInstalled.addListener(onInstalled);
chrome.runtime.onMessage.addListener(onMessage);
chrome.alarms.onAlarm.addListener(onAlarm);
async function onInstalled(details) {
if (details.reason === 'install') {
await chrome.storage.sync.set({ settings: defaultSettings });
}
if (details.reason === 'update') {
await migrateSettings(details.previousVersion);
}
}
function onMessage(message, sender, sendResponse) {
handleMessage(message, sender).then(sendResponse);
return true; // для async-відповідей
}
Порівняння MV2 і MV3: продуктивність та стабільність
| Критерій | MV2 (persistent background) | MV3 (service worker) |
|---|---|---|
| Життєвий цикл | Постійний процес | Завершується при бездіяльності |
| Глобальний стан | Працює | Скидається при перезапуску |
| setTimeout/setInterval | Надійні | Ненадійні (використовуйте alarms) |
| Споживання пам'яті | Завжди активно | Тільки за необхідності |
| Швидкість завантаження | Вища | Нижча (лінивий запуск) |
Завдяки лінивому завантаженню MV3 працює на 30% швидше за MV2 в режимі очікування, споживає в 2 рази менше пам'яті. Економія на серверній інфраструктурі може досягати 40%. Наші клієнти відзначають підвищення швидкості на 25% після міграції.
Довгоживучі з'єднання та періодичні завдання
Для завдань, що тривають більше кількох секунд (стрімінг, polling), використовуйте chrome.runtime.connect(). Активне з'єднання утримує SW живим:
chrome.runtime.onConnect.addListener((port) => {
if (port.name !== 'streaming-channel') return;
const controller = new AbortController();
port.onDisconnect.addListener(() => controller.abort());
streamData(port.sender.tab, controller.signal, (chunk) => {
port.postMessage({ type: 'CHUNK', data: chunk });
}).then(() => {
port.postMessage({ type: 'DONE' });
}).catch((err) => {
if (err.name !== 'AbortError') {
port.postMessage({ type: 'ERROR', message: err.message });
}
});
});
setTimeout і setInterval ненадійні — не переживають перезапуск. Використовуйте alarms:
chrome.runtime.onInstalled.addListener(() => {
chrome.alarms.create('sync-data', { periodInMinutes: 15, delayInMinutes: 1 });
});
chrome.alarms.onAlarm.addListener(async (alarm) => {
if (alarm.name === 'sync-data') {
await syncWithServer();
}
});
Більше про chrome.alarms читайте в офіційній документації Chrome.
Стійкість та налагодження Service Worker
SW може бути вбитий посеред async-операції. Для критичних операцій використовуйте транзакційний підхід з чергою в chrome.storage:
async function processQueue() {
const { queue = [] } = await chrome.storage.local.get('queue');
if (queue.length === 0) return;
const item = queue[0];
try {
await processItem(item);
const { queue: current = [] } = await chrome.storage.local.get('queue');
await chrome.storage.local.set({ queue: current.slice(1) });
} catch (err) {
const { queue: current = [] } = await chrome.storage.local.get('queue');
current[0] = { ...current[0], attempts: (current[0].attempts ?? 0) + 1, lastError: err.message };
await chrome.storage.local.set({ queue: current });
}
}
Покроковий план налагодження:
- Відкрийте chrome://inspect/#service-workers та перевірте статус SW.
- Переконайтеся, що події реєструються синхронно (використовуйте
console.logна верхньому рівні). - Перевірте, що всі обробники (
onInstalled,onMessage,onAlarm) видно в логах. - Емулюйте перезапуск SW через
chrome.serviceWorker(викликself.skipWaiting()або перезавантаження розширення). - Переконайтеся, що дані відновлюються з chrome.storage після перезапуску.
Що ви отримуєте в результаті?
- Повністю робочий Service Worker з коректною реєстрацією подій.
- Міграцію з MV2 на MV3 без втрати функціональності.
- Оптимізацію продуктивності: зниження споживання пам'яті в 2 рази.
- Надійне зберігання стану за допомогою chrome.storage та черг.
- Документацію з архітектури та інструкції з підтримки.
- Гарантію стабільної роботи: ми відповідаємо за результат.
Що входить в роботу
- Аудит поточного розширення (1–2 дні).
- Проєктування архітектури (2–3 дні).
- Розробка та тестування (5–10 днів).
- Документація та фінальні тести (1–2 дні).
- Підтримка після запуску протягом місяця.
- Передача всіх кодів, доступів та інструкцій.
Етапи розробки SW
| Етап | Тривалість (орієнтир) |
|---|---|
| Аудит поточного розширення | 1–2 дні |
| Проєктування архітектури | 2–3 дні |
| Розробка та тестування | 5–10 днів |
| Документація та фінальні тести | 1–2 дні |
| Підтримка після запуску | За погодженням |
Чому варто довірити розробку нам?
Маємо багаторічний досвід у створенні браузерних розширень — понад 5 років на ринку, десятки успішних проєктів. Наші інженери сертифіковані та глибоко розбираються в тонкощах Manifest V3 та Service Worker. Ми гарантуємо стабільну роботу вашого розширення навіть при складних сценаріях. Замовте аудит вашого розширення — ми виявимо проблемні місця та запропонуємо план міграції. Отримайте консультацію з архітектури Service Worker у наших інженерів.
Зв'яжіться з нами, щоб оцінити ваш проєкт. Ми виконаємо роботу під ключ за 10–15 днів. Вартість аудиту — від $300.







