Home > OS >  How to refactor global variables from MV2 to using chrome.storage in MV3 service worker?
How to refactor global variables from MV2 to using chrome.storage in MV3 service worker?

Time:07-24

To remove the global variables used in a MV2 background script when migrating to a MV3 service worker, all the guides I've found have just given an example of replacing a single global variable with a few lines of setting and then getting using chrome.storage, but it's still not clear to me how it can be used in a bit more complicated scenario.

For instance:

const activatedTabs = [];
let lastActiveTabInfo;
chrome.tabs.onActivated.addListener((activeInfo) => {
  if (activatedTabs.length === 0) {
    activatedTabs.push(activeInfo.tabId);
    lastActiveTabInfo = activeInfo;
  }
}

How could the snippet above be refactored to use chrome.storage and remove the global variables?

CodePudding user response:

The number of variables in the state doesn't change the approach:

  1. read the state on the start of the script
  2. save the state on change

For small data (1MB total) use chrome.storage.session, which is in-memory i.e. it doesn't write to disk, otherwise use chrome.storage.local. Both can only store JSON-compatible types i.e. string, number, boolean, null, arrays/objects of such types. There's also IndexedDB for Blob or Uint8Array.

let activatedTabs;
let lastActiveTabInfo;
let busy = chrome.storage.session.get().then(data => {
  activatedTabs = data.activatedTabs || [];
  lastActiveTabInfo = data.lastActiveTabInfo;
  busy = null;
});
const saveState = () => chrome.storage.session.set({
  activatedTabs,
  lastActiveTabInfo,
});

chrome.tabs.onActivated.addListener(async info => {
  if (!activatedTabs.length) {
    if (busy) await busy;
    activatedTabs.push(info.tabId);
    lastActiveTabInfo = info;
    await saveState();
  }
});

You can also maintain a single object with properties instead:

const state = {
  activatedTabs: [],
  lastActiveTabInfo: null,
};
const saveState = () => chrome.storage.session.set({ state });
let busy = chrome.storage.session.get('state').then(data => {
  Object.assign(state, data.state);
  busy = null;
});

chrome.tabs.onActivated.addListener(async info => {
  if (!state.activatedTabs.length) {
    if (busy) await busy;
    state.activatedTabs.push(info.tabId);
    state.lastActiveTabInfo = info;
    await saveState();
  }
});

Note that if you subscribe to frequent events like tabs.onActivated, your service worker may restart hundreds of times a day, which wastes much more resources than keeping an idle persistent background page. The Chromium team ignores this problem, but you shouldn't, and luckily there's a way to reduce the number of restarts by prolonging the SW lifetime. You still need to read/save the state as shown.

  • Related