Solved: The message port closed before a response was received
I encountered the “port closed” exception after converting my extension to Manifest v3. This exception should be mostly harmless, but it was affecting the logic of my extension. I needed to find the root cause.
After some digging I found in Chrome extension documentation that browser.runtime.onMessage should call sendResponse if browser.runtime.sendMessage defines a callback listening for a message. I was pretty sure I was doing that already. Then noticed that during Manifest v3 conversion in some places I started using “await browser.runtime.sendMessage()” instead of passing a callback. This changed runtime behavior. Awaiting for a Promise does not throw an exception if sendResponse is not called in browser.runtime.onMessage.
I am still puzzled why I started noticing this after moving to promises even though using promises does not throw an exception. I probably made other refactorings for the Manifest v3 transition that changed code path for the browser.runtime.onMessage handler.
Lesson learned. Once you commit to the async/await pattern you should use it consistently across your code base.
Code example:
function start() {
chrome.runtime.sendMessage("hello", (result) => {
console.log(result);
});
}
async function startAsync() {
const result = await chrome.runtime.sendMessage("hello");
console.log(result);
}
// This will make sendMessage in start() throw an exception. startAsync() will be fine and sendMessage will return a Promise with undefined value.
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
// The fix is to call sendResponse() before this function returns
// or return true and then call sendResponse() async.
});
chrome.action.onClicked.addListener(tab => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
function: start, // Change to startAsync to observe different behaviour
});
});