Search code examples
javascriptgoogle-chrome-extension

How to fix error: only a single offscreen document may be created?


My extension use chrome.offscreen API. The main target is run offscreen at startup browser. Seldom and random at startup browser occurs error Only a single offscreen document may be created. The extension in such case stop working (content script dont inject elements to site and no error in site console). Only reloading the extension or browser fix problem.

I have check that offscreen document created early 3 ways: with global isOffscreenCreating promise, with chrome.offscreen.hasDocument() native function and custom hasOffscreenDocument(). In fact, the custom hasOffscreenDocument() always return false. I dont know why, but it from documentation example.

Do you have suggestion why error happen?

// background.js
chrome.runtime.onStartup.addListener(setupOffscreenDocument)
setupOffscreenDocument()

const OFFSCREEN_DOCUMENT_PATH = 'assets/html/offscreen.html'
let isOffscreenCreating

export async function setupOffscreenDocument() {
    if (isOffscreenCreating) {
        await isOffscreenCreating;
    } else if ((chrome.offscreen.hasDocument && !(await chrome.offscreen.hasDocument())) || !(await hasOffscreenDocument())) {
        try {
            isOffscreenCreating = chrome.offscreen.createDocument({
                url: OFFSCREEN_DOCUMENT_PATH,
                reasons: [chrome.offscreen.Reason.IFRAME_SCRIPTING],
                justification: 'Listening WebSocket messages',
            })
            await isOffscreenCreating;
        } catch (error) {
            console.error(error)
        } finally {
            isOffscreenCreating = null
        }
    }
}

async function hasOffscreenDocument() {
    const matchedClients = await clients.matchAll();
    for (const client of matchedClients) {
        if (client.url.endsWith(OFFSCREEN_DOCUMENT_PATH)) {
            return true;
        }
    }
    return false;
}

Solution

  • There are several problems: your code throws because there's no chrome.offscreen.hasDocument anymore, you can't use typescript directly as Promise<boolean> without compiling the code, and the example in the documentation is unnecessarily complicated.

    All you need is a trivial try/catch:

    async function setupOffscreenDocument() {
      try {
        await chrome.offscreen.createDocument({
          url: 'offscreen.html',
          reasons: ['IFRAME_SCRIPTING'],
          justification: 'Listening to WebSocket messages',
        });
      } catch (error) {
        if (!error.message.startsWith('Only a single offscreen'))
          throw error;
      }
    }