In an add-on for Firefox, I'm trying to inject code from a background script into a tab and then pass a message to it. Unfortunately, the content script seems to add the listener only after the message has already been sent, resulting in in error. What am I missing? Here is my sample code:
manifest.json:
{
"description": "Test background to content message passing",
"manifest_version": 2,
"name": "Background content message passing",
"version": "0.1.0",
"default_locale": "en",
"applications": {
"gecko": {
"id": "[email protected]",
"strict_min_version": "51.0"
}
},
"permissions": [
"contextMenus",
"<all_urls>"
],
"background": {
"scripts": ["background.js"]
}
}
background.js:
"use strict";
const {contextMenus, i18n, runtime, tabs} = browser;
contextMenus.onClicked.addListener(function(info, tab) {
if (info.menuItemId == "bgd-cnt-msg") {
tabs.executeScript(tab.id, {
file: "/content.js",
})
.then(runtime.sendMessage({"result": 42}))
.then(console.log("Debug: runtime message sent"))
.catch(console.error.bind(console));
}
});
contextMenus.create({
id: "bgd-cnt-msg",
title: "Test message passing",
contexts: ["all"],
documentUrlPatterns: ["<all_urls>"]
});
content.js
"use strict";
console.log("Debug: executing content script");
browser.runtime.onMessage.addListener(function (message) {
console.log("Debug: received message %O", message);
});
console.log("Debug: added listener");
The result of selecting the the context menu entry is
Debug: runtime message sent background.js:11:15
Debug: executing content script content.js:3
Debug: added listener content.js:9
Error: Could not establish connection. Receiving end does not exist. undefined
I.e., the context scripts executes after the message to the tab is sent. How can I add the listener before sending the message?
As suggested by @Thắng, I changed my code to use tabs.sendMessage instead of runtime.sendMessage:
contextMenus.onClicked.addListener(function(info, tab) {
if (info.menuItemId == "bgd-cnt-msg") {
tabs.executeScript(tab.id, {
file: "/content.js",
})
.then(tabs.sendMessage(tab.id, {"result": 42}))
.then(console.log("Debug: runtime message sent"))
.catch(console.error.bind(console));
}
});
Now the error is reported a little earlier:
Debug: runtime message sent background.js:11:15
Error: Could not establish connection. Receiving end does not exist. undefined
Debug: executing content script content.js:3
Debug: added listener content.js:9
Thanks to @Thắng, who provided a working solution, I fixed my code to not only use tabs.sendMessage but also pass functions for the callbacks:
contextMenus.onClicked.addListener(function(info, tab) {
if (info.menuItemId == "bgd-cnt-msg") {
tabs.executeScript(tab.id, {
file: "/content.js",
})
.then(function () { tabs.sendMessage(tab.id, {"result": 42}) })
.then(function () { console.log("Debug: runtime message sent") })
.catch(console.error.bind(console));
}
});
With an additional fix in content.js
browser.runtime.onMessage.addListener(function (message) {
console.log("Debug: result is " + message.result);
});
I now get
Debug: executing content script content.js:3
Debug: added listener content.js:9
Debug: runtime message sent background.js:11:15
Debug: result is 42 content.js:6
In background script, you need to let it know it should send the message to which tab, so don't use runtime.sendMessage for this.
var sending = chrome.tabs.sendMessage(
tabId, // integer
message, // any
options // optional object
)
See more here (for webExtensions but also compatible with Chrome): https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs/sendMessage
Your fully working extension here (you may need to change all the browser.* to chrome.*):
https://drive.google.com/file/d/1KGf8tCM1grhhiC9XcHOjsrbBsIZGff3e/view?usp=sharing
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With