Home > Mobile >  Chrome extension manifest V3 - unexpected promise errors
Chrome extension manifest V3 - unexpected promise errors

Time:03-18

I'm working on an extension that does a lot of message-passing between its content scripts and the background service worker (manifest V3), and I've noticed an odd issue with the new Promise-based V3 APIs, specifically with the sendResponse() function.

With API calls that expect a response, everything works fine. But if I don't need a response and don't provide a callback function or use the promise's .then() method (or async/await), a Promise error is thrown - it says "The message port closed before a response was received."

Strangely, the call still works, so I guess this error is more like a warning.

Code example:

In a content script, sending a message to the background:

chrome.runtime.sendMessage({ type: 'toggle_setting' })

The background script gets the message and does something, then exits without sending a response:

chrome.runtime.onMessage.addListener( (message, sender, sendResponse) => {
  if (message.type === 'toggle-setting') {
    //* do whatever it does
  }
})

That background code is what throws the error described above. But if I add one line to it and call the sendResponse() function with no parameters, no error happens.

chrome.runtime.onMessage.addListener( (message, sender, sendResponse) => {
  sendResponse()
  if (message.type === 'toggle-setting') {
    //* do whatever it does
  }
})

So this gets rid of the error messages, but I'm not really clear on why it's necessary when there is no response needed or expected. Is there some other way to signal that to the Promise-based V3 APIs, or is it now necessary to call sendResponse() even if you don't need to?

CodePudding user response:

It's a bug in Chrome 99 .

The reason is that sendMessage is now promisified internally, so we can await it, but the byproduct is that when we don't specify a callback ourselves, it is added internally anyway in order for the call to return a Promise, which means that since we don't call sendResponse in onMessage, the API will think that it was us who made a mistake of using a callback and not providing a response, and report it as such.

Workaround 1: call sendResponse() inside chrome.runtime.onMessage

  • sender (in the question it's the content script):

    chrome.runtime.sendMessage('test');
    
  • receiver (in the question it's the background script):

    chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
      doSomethingWithoutResponding(msg);
      sendResponse();
    });
    

Workaround 2: suppress this specific error

Also patching the API's inherent lack of a callstack prior to the call.

// sender (in the question it's the content script)
sendMessage('foo');

// sendMessage('foo').then(res => whatever(res));
// await sendMessage('foo');

function sendMessage(msg) {
  const err1 = new Error('Callstack before sendMessage:');
  return new Promise((resolve, reject) => {
    chrome.runtime.sendMessage(msg, res => {
      let err2 = chrome.runtime.lastError;
      if (!err2 || err2.message.startsWith('The message port closed before')) {
        resolve(res);
      } else {
        err2 = new Error(err2.message);
        err2.stack  = err1.stack.replace(/^Error:\s*/, '');
        reject(err2);
      }
    });
  });
}

CodePudding user response:

Add a return true at the end of your message listener to allow the asynchronous response

  • Related