Atav32
Atav32

Reputation: 1812

"Extension context invalidated" error when calling chrome.runtime.sendMessage()

I have a content script in a Chrome Extension that's passing messages. Every so often, when the content script calls

chrome.runtime.sendMessage({
  message: 'hello',
});

it throws an error:

Uncaught Error: Extension context invalidated.

What does this error mean? I couldn't find any documentation on it.

It doesn't happen consistently. In fact, it's hard to reproduce. Seems to happen if I just leave the page open for a while in the background.


Another clue: I've written many Chrome Extensions with content scripts that pass messages and I haven't seen this error before. The main difference is that this content script is injected by the background page using

chrome.tabs.executeScript({
  file: 'contentScript.js',
});

Does using executeScript instead of the manifest file somehow change the lifecycle of the content script?

Upvotes: 8

Views: 7288

Answers (1)

herodrigues
herodrigues

Reputation: 967

This is certainly related to the message listener being lost in the middle of the connection between content and background scripts.

I've been using this approach in my extensions, so that I have a single module that I can use in both background and content scripts.

messenger.js

const context = (typeof browser.runtime.getBackgroundPage !== 'function') ? 'content' : 'background'

chrome.runtime.onConnect.addListener(function (port) {
  port.onMessage.addListener(function (request) {
    try {
      const object = window.myGlobalModule[request.class]
      object[request.action].apply(module, request.data)
    } catch () {
      console.error(error)
    }
  })
})

export function postMessage (request) {
  if (context === 'content') {
    const port = chrome.runtime.connect()
    port.postMessage(request)
  }

  if (context === 'background') {
    if (request.allTabs) {
      chrome.tabs.query({}, (tabs) => {
        for (let i = 0; i < tabs.length; ++i) {
          const port = chrome.tabs.connect(tabs[i].id)
          port.postMessage(request)
        }
      })
    } else if (request.tabId) {
      const port = chrome.tabs.connect(request.tabId)
      port.postMessage(request)
    } else if (request.tabDomain) {
      const url = `*://*.${request.tabDomain}/*`
      chrome.tabs.query({ url }, (tabs) => {
        tabs.forEach((tab) => {
          const port = chrome.tabs.connect(tab.id)
          port.postMessage(request)
        })
      })
    } else {
      query({ active: true, currentWindow: true }, (tabs) => {
        const port = chrome.tabs.connect(tabs[0].id)
        port.postMessage(request)
      })
    }
  }
}

export default { postMessage }

Now you'll just need to import this module in both content and background script. If you want to send a message, just do:

messenger.postMessage({
   class: 'someClassInMyGlobalModuçe',
   action: 'someMethodOfThatClass',
   data: [] // any data type you want to send
})

You can specify if you want to send to allTabs: true, a specific domain tabDomain: 'google.com' or a single tab tabId: 12.

Upvotes: 4

Related Questions