In my Chrome extension, the content script is supposed to receive a message from the background script on page load. But when I open a new tab and enter URL https://example.com, it does not receive the message. However, it works on page reload. Is it because page loaded from chrome://newtab?
// background script
chrome.runtime.onMessage.addListener(function (message, sender) {
if (message.action === "NOTIFY_CONTENT_SCRIPT_LOADED") {
console.log(`### ACK content script in page: ${message.pageURL} and tab: ${sender.tab.url}`);
chrome.tabs.sendMessage(sender.tab.id, { action: "CHANGE_BG" }, () => {
console.log("### ACK changed BG color of client page");
// do more stuff
});
}
});
// content script
chrome.runtime.onMessage.addListener((message, _, sendResponse) => {
if (message.action === "CHANGE_BG") { // <---- This is not received on first page load
document.body.style.background = "yellow";
sendResponse();
}
});
console.log("### Notifying background from page: " + location.href);
chrome.runtime.sendMessage({ action: "NOTIFY_CONTENT_SCRIPT_LOADED", pageURL: location.href });
Console log from background script:
### ACK content script in page: https://example.com/ and tab: chrome://newtab/
Please note that the sender.tab.url
is chrome://newtab/
instead of https://example.com/
.
This is intermittently happening. That is, does not happen in some cases:
I have created an example extension with same issue: https://github.com/lazyvab/chrome-newtab-issue
Edit: Tried suggestion from comment -
In case of multiple messages don't use sendMessage, but switch to chrome.runtime.connect.
// content script
const bgPort = chrome.runtime.connect();
bgPort.onMessage.addListener((msg) => {
console.log("### Received BG msg", msg);
});
// background script
chrome.runtime.onConnect.addListener((port) => {
port.onDisconnect.addListener((...args) => {
console.log("### disconnected port on tab", tabId, ...args);
});
console.log("### BG: sending ping to tab", tabId);
port.postMessage({ action: "ping" });
});
I opened https://example.com in new tab and here is the result.
A screenshot from background page console:
A screenshot from content script (page) console:
As can be seen in the screenshots, the port disconnects immediately after website loads and background script is not able to send messages using the port again.
I could finally solve this issue by establishing a long-lived port connection between content script and background script in case of pre-rendered pages.
// background.js
let clientPort = null;
let clientLoadSubscribers = [];
chrome.runtime.onConnect.addListener((port) => {
const tabId = port.sender.tab.id;
if (port.sender.documentLifecycle === "active") {
clientPort = port;
clientLoadSubscribers.forEach((subscriber) => subscriber());
clientLoadSubscribers = [];
}
port.onDisconnect.addListener(() => {
clientPort = null;
chrome.tabs.executeScript(
tabId,
{
code: "chrome.runtime.connect()",
},
() => {
if (chrome.runtime.lastError) {
// ignore
}
}
);
});
});
const sendMessageToClient = (tabId, ...restArgs) => {
const send = () => chrome.tabs.sendMessage(tabId, ...restArgs);
if (clientPort) {
send();
} else {
clientLoadSubscribers.push(send);
}
};
// content script
chrome.runtime.connect();
This way I could reliably send messages from background to content script using sendMessageToClient()
.