Choosing the Right Offscreen Reason
Pick the correct chrome.offscreen reason for each task: DOM_PARSER, CLIPBOARD, AUDIO_PLAYBACK, USER_MEDIA, WORKERS, LOCAL_STORAGE, MATCH_MEDIA, GEOLOCATION and more, with lifetime rules, combining reasons and review expectations.
Table of Contents
chrome.offscreen.createDocument requires a reasons array and a justification, and the choice is not cosmetic. Some reasons change the document’s lifetime — an AUDIO_PLAYBACK document is closed automatically after thirty seconds of silence — and all of them document to reviewers what the hidden page is for. Using DOM_SCRAPING because it sounds general, or TESTING to avoid thinking about it, invites review questions and surprises at runtime. This guide maps each common task to its reason and explains how reasons interact when one document does several jobs. It belongs to offscreen documents and DOM access.
What reasons are for
Offscreen documents exist to give MV3 extensions access to DOM-dependent web APIs that the service worker lacks, without bringing back permanent background pages. Reasons make that contract explicit: each reason names a capability the document needs, Chrome uses some of them to manage lifetime, and the Chrome Web Store expects the declared reasons to match what the document actually does. An extension may have only one offscreen document at a time, but that document may declare several reasons, so a single hidden page can parse HTML, access the clipboard and play sounds. Choosing reasons carefully keeps both behaviour and review predictable.
Step-by-step: match tasks to reasons
1. Name the capability, not the feature
1// Parsing an RSS feed fetched by the worker
2await chrome.offscreen.createDocument({
3 url: "offscreen.html",
4 reasons: ["DOM_PARSER"],
5 justification: "Parse RSS XML with DOMParser",
6});
Execution context: the service worker. The question is “which web API does the document call that the worker cannot?” — DOMParser → DOM_PARSER; navigator.clipboard or execCommand('copy') → CLIPBOARD; new Audio() → AUDIO_PLAYBACK; getUserMedia → USER_MEDIA; new Worker() → WORKERS; matchMedia → MATCH_MEDIA; navigator.geolocation → GEOLOCATION; reading localStorage written by an MV2 background page → LOCAL_STORAGE. The justification should state that API and the feature it serves in one line.
2. Prefer worker-native APIs when they exist
1// No offscreen document needed for these in the service worker:
2await fetch(url); // network
3new OffscreenCanvas(32, 32); // image work
4await createImageBitmap(blob); // decoding
5crypto.subtle.digest("SHA-256", data); // hashing
6indexedDB.open("db"); // storage
Execution context: the service worker. Many tasks that needed a background page in MV2 work directly in the worker now. An offscreen document costs creation time, memory and a review question; use one only when the worker genuinely lacks the API.
3. Combine reasons in one shared document
1async function ensureOffscreen(reasonsNeeded) {
2 const docs = await chrome.runtime.getContexts({ contextTypes: ["OFFSCREEN_DOCUMENT"] });
3 if (docs.length) {
4 const { offscreenReasons = [] } = await chrome.storage.session.get("offscreenReasons");
5 if (reasonsNeeded.every((r) => offscreenReasons.includes(r))) return;
6 await chrome.offscreen.closeDocument(); // recreate with the union of reasons
7 }
8 const { offscreenReasons = [] } = await chrome.storage.session.get("offscreenReasons");
9 const reasons = [...new Set([...offscreenReasons, ...reasonsNeeded])];
10 await chrome.offscreen.createDocument({ url: "offscreen.html", reasons, justification: "Clipboard, HTML parsing and notification sounds" });
11 await chrome.storage.session.set({ offscreenReasons: reasons });
12}
Execution context: the service worker. Because only one document may exist, an extension using several DOM features needs either one document declaring all their reasons or a close-and-recreate step when a new reason is needed. Tracking declared reasons in session storage lets the worker decide. Note that combining AUDIO_PLAYBACK with other reasons does not exempt the document from audio’s automatic closing behaviour; design for the document disappearing.
4. Handle AUDIO_PLAYBACK’s automatic close
1// sw.js
2export async function playChime() {
3 await ensureOffscreen(["AUDIO_PLAYBACK"]);
4 await chrome.runtime.sendMessage({ target: "offscreen", type: "play", src: "sounds/chime.mp3" });
5}
Execution context: the service worker. Chrome closes a document whose only reason is audio playback after it has been silent for about thirty seconds. Always call ensureOffscreen before each use rather than assuming the document still exists. See playing audio from an extension.
5. Avoid TESTING and vague reasons in production
TESTING exists for automated tests and should not appear in a published extension. DOM_SCRAPING is specifically for loading pages in an iframe to extract content — not a catch-all for “DOM stuff”. Reviewers compare declared reasons, the justification and the document’s code; a mismatch is an easy rejection.
6. Write justifications reviewers can verify
1Good: "Parse the HTML of saved articles with DOMParser to extract the reading view."
2Good: "Write the formatted citation to the clipboard when the user clicks Copy."
3Weak: "Needed for functionality."
4Weak: "Background processing."
Execution context: the justification string and your store listing. A concrete justification names the API and the user-visible feature. It also helps your future self understand why the document exists.
7. Close documents you no longer need
Even documents that Chrome does not close automatically should be closed when idle. An offscreen document is a full renderer with memory and, for some reasons, ongoing resource use. A small idle timer in the worker — close after a minute without requests — keeps the extension light.
Common mistakes
- Using an offscreen document for something the worker can do. Extra cost, extra review.
- A vague or wrong reason. Review questions and unpredictable lifetime.
- Assuming the document still exists. Audio documents close themselves; always check.
- Creating a second document. It throws; reuse or recreate with combined reasons.
- Leaving documents open indefinitely. Memory for nothing.
Cross-browser variation
- Chrome / Edge:
chrome.offscreenfrom Chrome 109; new reasons were added over later versions (for exampleGEOLOCATION) — check availability for your minimum version. - Firefox: no offscreen API; the event-page background has a DOM for these tasks.
- Safari: no offscreen API; use extension pages while open, worker-native APIs, or the containing app.
Verification
- For each offscreen use, confirm the declared reasons match the APIs called in the document’s code.
- Play a sound, wait a minute, play again, and confirm the document is recreated without errors.
- Check
chrome://inspect→ Other for the offscreen document and confirm it closes when idle. - Grep the production manifest and code for
TESTING: none.
FAQ
Can the offscreen document be visible?
No. It has no UI and cannot be shown; use a popup, side panel or tab for UI.
Which extension APIs can the document use?
Only chrome.runtime for messaging. It communicates with the worker for anything else.
Does the reason affect permissions?
Some features still need their own permission — geolocation, clipboardWrite in some cases — regardless of the reason.
Can I change reasons without closing the document?
No. Reasons are fixed at creation. To add one, close the document and create it again with the combined list — which is why tracking the declared reasons in session storage is useful.
What happens if my document uses an API its reasons don’t name?
The API usually still works — reasons are not a sandbox — but the mismatch is a review risk and may break if Chrome begins enforcing reasons more strictly. Declare what you use.
Is there a cost to declaring many reasons?
Not at runtime, but each reason is a claim reviewers may check. Declare the ones the document actually needs, and document why in the justification.
Related
- Creating and closing offscreen documents — the lifecycle API.
- Running WASM and heavy compute outside the service worker — the WORKERS reason in practice.
- Replacing DOM and XHR in the background context — which tasks need a document at all.
- Offscreen documents and DOM access — the parent topic.