Manifest V3 replaced the persistent background page with a service worker. Great for memory, hard for anything heavy: a service worker has no DOM, gets killed after ~30 seconds of idle, and is a shaky host for WebAssembly. OmniBrief needs all three - ONNX embedding models, PDF.js text extraction, and Origin Private File System (OPFS) persistence.

The escape hatch is the offscreen document: a hidden, full-DOM page the extension creates on demand. OmniBrief runs its embeddings, PDF parsing, and file writes there, while the service worker keeps doing lightweight coordination and on-device Gemini Nano work.

Key Technical Concepts Covered

  • Why the worker isn't enough: no DOM, short lifetime, unreliable WASM.
  • Create-once semantics: probe for an existing offscreen doc before creating another.
  • Justified reasons: MV3 requires you to declare why the document exists.
  • Message routing: the worker delegates heavy jobs and awaits a reply.

Part of the engineering behind OmniBrief.

Tested with Chrome 149 (July 2026). Chrome's built-in AI APIs are experimental - availability varies by Chrome version and hardware.

One Document, Created On Demand

Only one offscreen document may exist per extension, so you must check before creating. OmniBrief uses chrome.runtime.getContexts() to see whether one is already open, then creates it with an explicit reason and justification:

async function ensureOffscreen() {
  const existing = await chrome.runtime.getContexts({
    contextTypes: ['OFFSCREEN_DOCUMENT'],
  });
  if (existing.length > 0) return;          // already open

  await chrome.offscreen.createDocument({
    url: 'src/offscreen/offscreen.html',
    reasons: ['WORKERS', 'BLOBS'],           // WASM + file/blob work
    justification: 'Run ONNX embeddings, PDF extraction, and OPFS writes.',
  });
}

Delegating the Heavy Work

The service worker never runs the model itself. When a page needs embedding, it makes sure the document exists and forwards the job as a message, then awaits the result:

async function embed(texts) {
  await ensureOffscreen();
  return chrome.runtime.sendMessage({
    target: 'offscreen',
    type: 'EMBED',
    texts,
  });
}

Inside the offscreen document - a real page with a full DOM - transformers.js loads its ONNX runtime and WASM binaries normally, PDF.js spins up its worker, and OPFS is available for persistence. None of that is dependable inside the service worker.

Keeping the Split Clean

Not everything belongs offscreen. Chrome's built-in Gemini Nano Summarizer and LanguageModel APIs are supported directly in the service worker, so OmniBrief deliberately keeps summarization there and reserves the offscreen document for WASM/DOM/OPFS work. The rule of thumb: if it needs the DOM, heavy WebAssembly, or long-lived file handles, it goes offscreen; otherwise it stays in the worker.

// offscreen.js - heavy libraries load in a normal page context
import { pipeline, env } from '@xenova/transformers';
import * as pdfjs from 'pdfjs-dist';
import { saveBrief } from '../storage/opfs.js';

env.backends.onnx.wasm.wasmPaths = chrome.runtime.getURL('wasm/');
pdfjs.GlobalWorkerOptions.workerSrc = chrome.runtime.getURL('pdf.worker.min.mjs');

Conclusion: The Right Context for the Job

The offscreen document is the piece that makes a genuinely local-first extension possible under Manifest V3. By creating one on demand, declaring its purpose, and routing only DOM/WASM/file work to it, OmniBrief runs semantic search, PDF ingestion, and encrypted local storage entirely on the user's machine - without fighting the service worker's limitations.