[Manifest V3] Keep track of ports from runtime.connect (storage API?)

I’m trying my hand at writing extensions, but I have troubles with the new V3 Manifest and runtime + storage API.

I have a content-script that can initiate a connection to a service-worker in order to start a download. I want to keep the port open and periodically send updates to the content-script on the download state.

Here’s the simplified idea; the worker may interact with several pages at the same time hence the Map for tracking the downloads and their corresponding port.

// content script
function onUserAction(url) {
    browser.runtime.connect((port) => {
        port.onMessage.addListener(m => {
             console.log("Progress: ", m.progress)
        port.sendMessage({ url: url })
// service worker
// with manifest v2, I would have done:
const initiators = new Map()

browser.runtime.onConnect.addListener((port) => {
     port.onMessage.addListener(m => {
          // start new download, store the mapping downloadID => port
          chrome.downloads.download(m.url).then(id => {
              initiators[id] = port
browser.downloads.onChanged.addListener((delta) => {
     const port = initiators[delta.id]
     port.sendMessage({ progress: ... })

The problem is, now using globals is deprecated (instead we should use the storage API); but I can’t store a runtime.Port because it’s an Object, not JSON serializable.
I’ve tried to store the port’s name instead; but I’ve got no clue how to get back the Port object from it.
And the fact that listeners should be added synchronously means that I cannot add the download listener inside the onConnected callbacks, where I could have kept a list/map of Port.

So any help / suggestions would be appreciated, or please tell me if I’m going about it the wrong way - I’m relatively new to JS and add-on dev.

1 Like

What you’ve found is a classic oversight by the people who thought that Manifest V3 was a good idea. They didn’t think anything through and therefore broke some of the most basic functionality.

It’s things like this that have made me hate MV3 from day 1.


I think the service worker won’t be killed if you have an opened port. Well, until the hard limit is reached, which is 5 minutes maybe?

In any case, maybe you could use messaging system instead, sadly broadcasting using browser.runtime.sendMessage won’t reach content scripts so you would have to send the progress into the specific tabs using: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/sendMessage

But then again, you need to store the tabs ID somehow and the session storage is not yet implemented. So you could use the persistent storage for now.

In any case, unless you plan to support also Chrome, it’s probably best to stay on MV2.

I see, I’ll try to use the tabs first and keep going a bit with the MV3. Then I’ll fallback on MV2 if I can’t make it work.
Thanks for your answer!