My chrome extension has the following two javascripts:
background.js
, running as background script:
chrome.runtime.onMessage.addListene
If you're getting this error message while developing a Chrome Extension be sure to disable other Chrome extensions you have installed in your browser. In particular the "AdBlock" extension seemed to interfere with messaging and resulted in my extension throwing this error. Disabling AdBlock resolved this issue for me.
Some security changes in chrome seems like you have to take a slightly different approach when messaging between content scripts and the background script.
The calls to use are
chrome.runtime.onMessageExternal.addListener
Notice the
External part.
manifest.json
needs extra permissions "externally_connectable": {
"ids": ["abcdefghijklmnopqrstuvwxyzabcdef"],
"matches": ["https://example.com/*"],
"accepts_tls_channel_id": false
},
"abcdefghijklmnopqrstuvwxyzabcdef"
is your extension id.
"https://example.com/*"
is the domain the content script runs on.
From the content script:
chrome.runtime.sendMessage
/ chrome.runtime.connect
with a extensionId
as the first parameter.
Read more here https://developer.chrome.com/extensions/manifest/externally_connectable
OK. I found out what the problem is. This is a change in chromes behavior since 72 I guess. The problem is if you try to call chrome.runtime.connect() before you have opened a channel on the other end in Background or popup page then you will get that error.
What Chrome docs say is that you can send a message immediately. In the past this would just work and the messages would get either delivered or dropped. But now it is failing.
chrome docs: Upon calling tabs.connect, runtime.connect or runtime.connectNative, a Port is created. This port can immediately be used for sending messages to the other end via postMessage.
So our workaround is to make sure the connection listener is setup fist before calling connect() by just delaying the connect() call:
chrome.runtime.onConnect.addListener(port => {
console.log('connected ', port);
if (port.name === 'hi') {
port.onMessage.addListener(this.processMessage);
}
});
If you setup a listener for disconnect event on the content script side it actually gets called when you try to chrome.runtime.connect and you don't have anything listening on the other end. Which is correct behavior according the Port LifeTime
port = chrome.runtime.connect(null, {name: 'hi'});
port.onDisconnect.addListener(obj => {
console.log('disconnected port');
})
I don't know if there is way to avoid this than with setTimeout and trying to get the chrome.runtime.connect to come after there is chrome.runtime.onConnect.addListener is called. This is not a good solution because it leads to timing errors. Maybe another workaround is to reverse the direction of the channel. And initiate the connect from popup instead of contentscript.
Update: I made a minimum repro extension for this issue.
Here's a pattern I use to solve this. Content script tries to reach background script. If background script is not available yet then we can see that from chrome.runtime.lastError
being set (instead of being undefined). In this case try again in 1000 ms.
contentScript.js
function ping() {
chrome.runtime.sendMessage('ping', response => {
if(chrome.runtime.lastError) {
setTimeout(ping, 1000);
} else {
// Do whatever you want, background script is ready now
}
});
}
ping();
backgroundScript.js
chrome.runtime.onConnect.addListener(port => {
port.onMessage.addListener(msg => {
// Handle message however you want
}
);
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => sendResponse('pong'));