Service Workers in Firefox: Lifecycle, Caching Strategies, Push Notifications, and about:debugging
Service Workers are the foundation of Progressive Web Apps — they run in a background thread, intercept network requests, enable offline functionality, and handle push notifications. Firefox has full Service Worker suppo
Service Workers are the foundation of Progressive Web Apps — they run in a background thread, intercept network requests, enable offline functionality, and handle push notifications. Firefox has full Service Worker support. Here's a practical guide covering the lifecycle, caching strategies, and debugging in Firefox DevTools.
What Service Workers Actually Are
A Service Worker is a JavaScript file that:
- Runs in a separate thread from the page (Web Worker)
- Has no access to the DOM
- Intercepts and handles network requests for its scope
- Can cache resources and serve them offline
- Receives push notifications even when the page is closed
- Persists between page loads (unlike the page's JavaScript)
Browser support caveat: Firefox disables service workers in Private Browsing mode entirely. This is a Firefox-specific policy for privacy reasons.
The Service Worker Lifecycle
// Register in your page's JavaScript
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js', { scope: '/' })
.then(reg => console.log('SW registered:', reg.scope))
.catch(err => console.error('SW registration failed:', err));
}
Lifecycle events in sw.js:
- install: fires when the SW is first registered or updated. Cache resources here.
- activate: fires after install, when the SW takes control. Clean up old caches here.
- fetch: fires for every network request in the SW's scope.
// sw.js
const CACHE_NAME = 'app-v1';
const PRECACHE_URLS = ['/index.html', '/styles.css', '/app.js'];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => cache.addAll(PRECACHE_URLS))
);
self.skipWaiting(); // Activate immediately without waiting
});
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(
keys.filter(key => key !== CACHE_NAME).map(key => caches.delete(key))
)
)
);
self.clients.claim(); // Take control of pages immediately
});
Caching Strategies
Different strategies suit different resource types:
Cache First (for static assets with versioned URLs):
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(cached =>
cached || fetch(event.request).then(response => {
const clone = response.clone();
caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));
return response;
})
)
);
});
Network First (for API calls where fresh data matters):
event.respondWith(
fetch(event.request)
.then(response => {
const clone = response.clone();
caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));
return response;
})
.catch(() => caches.match(event.request)) // fallback to cache
);
Stale While Revalidate (serve cached, update in background):
event.respondWith(
caches.open(CACHE_NAME).then(cache =>
cache.match(event.request).then(cached => {
const fetchPromise = fetch(event.request).then(response => {
cache.put(event.request, response.clone());
return response;
});
return cached || fetchPromise;
})
)
);
Firefox DevTools: Service Worker Panel
about:debugging → This Firefox → Service Workers
Shows all registered service workers, their status (installing/waiting/active), and scope. Buttons to:
- Inspect: opens DevTools connected to the service worker's thread
- Push: send a test push notification
- Unregister: remove the SW
For per-page SW inspection: DevTools → Application tab (if available) or about:debugging.
The Service Worker's own Console errors appear in the Browser Console (Ctrl+Shift+J) rather than the page DevTools console.
Cache Storage Inspection
DevTools → Storage → Cache Storage
Shows all named caches. Expand each cache to see:
- Cached URLs
- Response status codes
- Response headers
- Full response body
Right-click → Delete to remove individual cache entries. Useful for testing: delete a cached resource and reload to verify the network request fires.
Service Worker Debugging Workflow
The service worker lifecycle makes debugging tricky — a running SW from a previous session intercepts your requests even after you change sw.js. Use these patterns:
Bypass for testing: in DevTools → Network → check "Disable Cache" and use the SW bypass. Or navigate to about:debugging → unregister the SW.
Console in the SW: since SW has no DOM, console.log() in the SW appears in the Browser Console (Ctrl+Shift+J) with a label indicating it's from the service worker.
Inspect SW directly: in about:debugging → click Inspect → opens DevTools for the SW thread. You can set breakpoints in the SW code, inspect scope, and use the console with SW-level access.
Push Notifications
// In page JavaScript — subscribe to push
const reg = await navigator.serviceWorker.ready;
const subscription = await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(PUBLIC_VAPID_KEY)
});
// Send subscription to your server
await fetch('/api/subscribe', {
method: 'POST',
body: JSON.stringify(subscription)
});
In the Service Worker — handle push events:
self.addEventListener('push', event => {
const data = event.data?.json() || { title: 'New notification' };
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.body,
icon: '/icon-192.png',
badge: '/badge-96.png',
data: { url: data.url }
})
);
});
self.addEventListener('notificationclick', event => {
event.notification.close();
event.waitUntil(
clients.openWindow(event.notification.data.url)
);
});
Firefox permission: users must grant notification permission. The Permission API will show a doorhanger. In Settings → Notifications, users can manage per-site notification permissions.
Background Sync
// Queue a sync when offline
navigator.serviceWorker.ready.then(reg =>
reg.sync.register('sync-messages')
);
// In service worker
self.addEventListener('sync', event => {
if (event.tag === 'sync-messages') {
event.waitUntil(syncPendingMessages());
}
});
Background Sync fires when the device regains connectivity, even if the page is closed. Firefox supports Background Sync (behind a flag in some versions, enabled in Firefox 124+).
Workbox
Writing raw service worker code is complex. Workbox (by Google) is a library that provides pre-built caching strategies:
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate, CacheFirst } from 'workbox-strategies';
// Cache images for 30 days
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({ cacheName: 'images', plugins: [
new ExpirationPlugin({ maxAgeSeconds: 30 * 24 * 60 * 60 })
]})
);
// API routes: stale-while-revalidate
registerRoute(
({ url }) => url.pathname.startsWith('/api/'),
new StaleWhileRevalidate({ cacheName: 'api-cache' })
);
Workbox works in Firefox and generates production-ready SW code without writing the plumbing manually.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.