A Question About chrome.runtime.lastError

443 views
Skip to first unread message

Almog Idisis

unread,
May 31, 2023, 3:53:51 AM5/31/23
to Chromium Extensions
Hey :)
We have experienced a somewhat weird behavior related to chrome.runtime.lastError that I could not explain, would appreciate your help with this one.

In order to communicate with our content script, we utilize the `chrome.tabs.sendMessage` API.

For convenience, we wrapped this API in a wrapper function, named `sendMessageToTab`.
Screenshot 2023-05-31 at 10.40.31.png
As far as we know, trying to communicate with the content script in `chrome://` like tabs won't work. 
We expect `chrome.runtime.lastError` to contain the following in such cases:
Could not establish connection. Receiving end does not exist.
This is indeed the case.

However, if we were to call the callback function inside the callback of `chrome.tabs.get` resolved promise, chrome.runtime.lastError value would be undefined.
Screenshot 2023-05-31 at 10.51.39.png

So, it brings me to wonder:
- Is this an intended behavior?
- Are there better ways to handle failed sendMessage events in forbidden tabs?

Thanks, team!

wOxxOm

unread,
May 31, 2023, 10:22:11 AM5/31/23
to Chromium Extensions, Almog Idisis
It is the correct behavior because lastError is a getter, and its value is recalculated after each asynchronous API call. While it can be argued this API design is misleading and it'd be more straightforward to use an explicit function chrome.runtime.getLastError(), but on the other hand it'd be overly verbose and anyway getters are widely used in JS, e.g. for DOM things like clientWidth.

Solution: don't use lastError for the natively promisified methods. Use .catch() or try-catch.

function foo1(tabId, msg, cb) {
  return chrome.tabs.sendMessage(tabId, msg).catch(err => {
    cb?.(err);
    // .......
  });
}

async function foo2(tabId, msg, cb) {
  try {
    return await chrome.tabs.sendMessage(tabId, msg);
    // note the need for `await` to catch the exception!
  } catch (err) {
    cb?.(err);
    // .......
  }
}

Almog Idisis

unread,
Jun 1, 2023, 3:10:33 AM6/1/23
to Chromium Extensions, wOxxOm, Almog Idisis
Yep, that makes sense.
So, just to confirm I understand it
What happened is that we invoked an async API call, `chrome.tabs.sendMessage`, which resulted in an error. At this moment, chrome.runtime.lastError contained the value of that error.
Then, because we called another async operation, `chrome.tabs.get`, which this time was successful, it caused the reset of the previous value of chrome.runtime.lastError?

wOxxOm

unread,
Jun 1, 2023, 3:58:28 AM6/1/23
to Chromium Extensions, Almog Idisis, wOxxOm
Yes.

I guess the source of the confusion was that you expected lastError to change its value only when an error actually occurred. While the name is somewhat misleading it actually means "last [asynchronous API call's] error [status]" and this is similar to the way low-level system API work in general e.g. GetLastError in Windows API.

Simeon Velichkov

unread,
Jun 1, 2023, 5:19:38 AM6/1/23
to Chromium Extensions, wOxxOm, Almog Idisis
Generally in pre-Promise JavaScript there was a pattern to forward your errors as the first argument of your callback function. But since that is not the case with the Browser Extesnion API and otherwise there won't be a way for you to catch the out-of-context (scope) error, they added a global pointer instead, accessible from everywhere (any scope) holding the error, as already mentioned. So this is really only useful when using the callback API, because if using Promises the error will be forwarded correctly to the callers context and you will be able to handle it if you want to, again, as already mentioned, but it just made me think myself ..

Almog Idisis

unread,
Jun 4, 2023, 6:56:37 AM6/4/23
to Chromium Extensions, Simeon Velichkov, wOxxOm, Almog Idisis
Thanks to both of you! I have shared this knowledge with the team and it makes perfect sense.
Reply all
Reply to author
Forward
0 new messages