DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

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 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));
}
Enter fullscreen mode Exit fullscreen mode

Lifecycle events in sw.js:

  1. install: fires when the SW is first registered or updated. Cache resources here.
  2. activate: fires after install, when the SW takes control. Clean up old caches here.
  3. 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
});
Enter fullscreen mode Exit fullscreen mode

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;
      })
    )
  );
});
Enter fullscreen mode Exit fullscreen mode

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
);
Enter fullscreen mode Exit fullscreen mode

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;
    })
  )
);
Enter fullscreen mode Exit fullscreen mode

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)
});
Enter fullscreen mode Exit fullscreen mode

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)
  );
});
Enter fullscreen mode Exit fullscreen mode

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());
  }
});
Enter fullscreen mode Exit fullscreen mode

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' })
);
Enter fullscreen mode Exit fullscreen mode

Workbox works in Firefox and generates production-ready SW code without writing the plumbing manually.

Top comments (0)