Chrome Extension Webworker Offloading — Best Practices

4 min read

Web Worker Offloading Patterns

Web Workers enable running computationally intensive tasks off the main thread, keeping extension UIs responsive.

Where Workers Are Available

Web Workers work in these extension contexts:

Not available in:

Creating a Worker

// In an extension page (popup, options, side panel)
const worker = new Worker(chrome.runtime.getURL('worker.js'));

// Module worker (ES modules support)
const worker = new Worker(chrome.runtime.getURL('worker.js'), { 
  type: 'module' 
});

Offscreen Document + Worker Pipeline

For background CPU-intensive tasks, combine offscreen documents with Workers:

// background.js - create offscreen with worker
chrome.offscreen.createDocument({
  url: 'offscreen.html',
  reasons: ['WORKERS'],
  justification: 'Heavy data processing'
});

// offscreen.js - create worker inside offscreen
const worker = new Worker(chrome.runtime.getURL('processor.js'));
worker.postMessage({ data: 'input' });
worker.onmessage = (e) => {
  // Handle results
  chrome.runtime.sendMessage({ result: e.data });
};

Use Cases

Message Passing

// Main thread
worker.postMessage({ type: 'process', payload: data });
worker.onmessage = (e) => { console.log('Result:', e.data); };
worker.onerror = (e) => { console.error('Worker error:', e.message); };

// Worker thread (worker.js)
self.onmessage = (e) => {
  const result = processData(e.data.payload);
  self.postMessage({ result });
};

Transferable Objects

Use transferable objects for zero-copy transfer of large data:

// Send ArrayBuffer
const buffer = new ArrayBuffer(1024 * 1024);
worker.postMessage({ buffer }, [buffer]);

// Receive ImageBitmap
worker.onmessage = (e) => {
  const bitmap = e.data.bitmap;
  ctx.drawImage(bitmap, 0, 0);
};
worker.postMessage({ imageData }, [imageData.data.buffer]);

SharedWorker for Shared State

SharedWorkers allow multiple extension pages to share state:

const shared = new SharedWorker(chrome.runtime.getURL('shared.js'));
shared.port.postMessage({ action: 'increment' });
shared.port.onmessage = (e) => { console.log('Count:', e.data); };

Worker Termination

Always terminate workers when no longer needed:

// In popup - close when popup closes
window.addEventListener('unload', () => worker.terminate());

// In offscreen - terminate after completion
worker.onmessage = () => worker.terminate();

Error Handling

worker.onerror = (e) => {
  console.error('Worker failed:', e.message, e.filename, e.lineno);
};

try {
  // Wrap worker operations
} catch (err) {
  console.error('Operation failed:', err);
}

Bundling Workers

Vite:

// vite.config.js
export default {
  worker: {
    format: 'es'
  }
};
// Import: import Worker from './worker.js?worker'

Webpack:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      { test: /\.worker\.js$/, use: { loader: 'worker-loader' } }
    ]
  }
};

Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.