DEV Community

Cover image for How to Create Offscreen Documents in Chrome Extensions: A Complete Guide
Himanshu
Himanshu

Posted on

How to Create Offscreen Documents in Chrome Extensions: A Complete Guide

If you're building Chrome extensions with Manifest V3, you've likely encountered the challenge of service workers dying frequently. This is where offscreen documents come to the rescue. In this comprehensive guide, we'll explore what offscreen documents are, where they live, and how to implement them in your Chrome extension.

What is an Offscreen Document?

An offscreen document is a hidden HTML document that runs in the background of your Chrome extension. Unlike service workers that terminate quickly to save resources, offscreen documents can maintain persistent state and perform continuous operations without being visible to the user.

Think of it as an invisible webpage that your extension controls, providing a stable environment for long-running tasks that would otherwise be interrupted by service worker lifecycle limitations.

When Should You Use Offscreen Documents?

Offscreen documents are ideal for several scenarios:

  • Persistent Data Processing: When you need to maintain data structures like Map() or Set() objects that would be lost when service workers terminate
  • Heavy Computations: Processing large datasets or performing complex calculations that need consistent access to memory
  • Audio/Video Processing: Handling media that requires continuous operation

Important Limitations:

  • Offscreen documents cannot access the host website's DOM elements (like buttons or content)
  • also they're only available in Manifest V3 (Chrome extensions only, not Firefox)
  • they run in an isolated context separate from your content scripts
  • they can only access one chrome api which is chrome.runtime for messaging, they can't access storage or tabs api

Step 1: Add Offscreen Permission to manifest.json

First, declare the offscreen permission in your manifest file:

{
  "manifest_version": 3,
  "name": "Your billion dollar Extension Name",
  "version": "1.0.0",
  "description": "a really not so long extension description here",

  "permissions": ["offscreen"],

  "background": {
    "service_worker": "background.js",
    "type": "module"
  },

}

Enter fullscreen mode Exit fullscreen mode

The "offscreen" permission is important and must be explicitly declared for your extension to create offscreen documents.

Step 2: Create the Offscreen HTML File

Create an offscreen.html file in your extension's root directory:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Offscreen Document</title>
</head>
<body>
    <!-- This document runs invisibly in the background -->
</body>
<script type="module" src="offscreen.js"></script>
</html>

Enter fullscreen mode Exit fullscreen mode

This HTML file serves as the entry point for your offscreen document. It's never displayed to users but provides the execution environment (this is some chrome’s stupidity, it’s purely useless but necessary)

Step 3: Create the Offscreen JavaScript File

Create offscreen.js where you'll handle your persistent operations:

// Perform heavy processing or maintain persistent state
console.log('hey human!, your offscreen document is live');

// Listen for messages from the background script or content script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'Blah_Blah') {
    // Perform your processing here

  }
  return true; // Keep the message channel open for async response
});

Enter fullscreen mode Exit fullscreen mode

btw Your offscreen console logs will appear in the same inspector as your service worker. Navigate to chrome://extensions/, enable Developer mode, and click the "service worker" link.

Step 4: initializing Offscreen Document from Background Script

The most important part, create the offscreen document from your background script (background.js):

let creatingOffscreen = null;

async function ensureOffscreen() {
  const offscreenUrl = chrome.runtime.getURL('offscreen.html');

  // Check if offscreen document already exists
  if ('getContexts' in chrome.runtime) {
    const contexts = await chrome.runtime.getContexts({
      contextTypes: ['OFFSCREEN_DOCUMENT'],
      documentUrls: [offscreenUrl],
    });

    if (contexts.length > 0) {
      console.log('Offscreen document already exists');
      return;
    }
  } else {
    // Fallback for older Chrome versions
    const clients = await self.clients.matchAll();
    if (clients.some((client) => client.url.includes(chrome.runtime.id))) {
      return;
    }
  }

  // Create offscreen document
  // put a good looking 'justification' here because they will review it
  if (!creatingOffscreen) {
    creatingOffscreen = chrome.offscreen.createDocument({
      url: 'offscreen.html',
      reasons: ['WORKERS'],
      justification: 'Maintain persistent data structures for extension functionality',

    });

    await creatingOffscreen;
    creatingOffscreen = null;
    console.log('Offscreen document created successfully');
  } else {
    await creatingOffscreen;
  }
}

// Create offscreen on browser startup
chrome.runtime.onStartup.addListener(() => {
  ensureOffscreen();
  console.log('Extension started, offscreen document ensured');
});

// Create offscreen on extension installation
chrome.runtime.onInstalled.addListener(() => {
  ensureOffscreen();
  console.log('Extension installed, offscreen document created');
});

// Handle messages (from content script) to ensure offscreen exists
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  if (msg && msg.type === 'ensure_offscreen') {
    ensureOffscreen()
      .then(() => sendResponse({ ok: true }))
      .catch((err) => sendResponse({ ok: false, error: String(err) }));
    return true;
  }
});

Enter fullscreen mode Exit fullscreen mode

The ensureOffscreen() Function:

This function implements a pattern:

  1. Check if offscreen exists: Uses chrome.runtime.getContexts() (preferred) or self.clients.matchAll() (fallback) to check for existing offscreen documents
  2. Prevent duplicate creation: Uses a creatingOffscreen promise to prevent race conditions
  3. Create if needed: Only creates a new offscreen document if one doesn't exist

Required Parameters for createDocument()

When creating an offscreen document, keep these things in mind:

  • url: The path to your offscreen HTML file
  • reasons: An array of valid reasons from the Chrome API documentation. Valid reasons include:

    • AUDIO_PLAYBACK
    • BLOBS
    • CLIPBOARD
    • DOM_PARSER
    • DOM_SCRAPING
    • IFRAME_SCRIPTING
    • LOCAL_STORAGE
    • TESTING
    • WORKERS
    • etc
  • justification: A clear explanation of why you need the offscreen document. This is reviewed by the Chrome team during the extension review process.

Closing Offscreen Documents

When you no longer need the offscreen document, close it to free up resources, though i never tried this code, because i never closed it, but i think it will work, but check it yourself pls:

async function closeOffscreen() {
  try {
    await chrome.offscreen.closeDocument();
    console.log('Offscreen document closed');
  } catch (error) {
    console.error('Error closing offscreen document:', error);
  }
}

Enter fullscreen mode Exit fullscreen mode

See the official documentation for more details.

Communication Between Background and Offscreen

Use Chrome's messaging API to communicate between your background script and offscreen document:

From background to offscreen:

chrome.runtime.sendMessage({
  type: 'Blah_Blah',
}, (response) => {
  console.log('Response from offscreen:', response);
});
Enter fullscreen mode Exit fullscreen mode

Important

  • Browser Support: Offscreen documents are Manifest V3 only meaning you can only use offscreen document in chrome and other chromium based browser and it is not available for Firefox extensions (manifest version 2)
  • DOM Access: offscreen cannot access the host website's DOM elements, you need to transfer a lot of info via messaging
  • Persistence: Unlike service workers, offscreen documents can maintain state between operations (most cool thing)
  • Visibility: Completely hidden from users, no UI elements

btw i created a chrome extension boilerplate that contains auth, subscription, forget password, contact form and everything you need to build and monetize extension fast, you can check it out here: extFast

thank you sooo much for reading 💛

have a nice day,

bye bye :)

Top comments (0)