To remove the global variables used in a persistent MV2 background script when migrating to an MV2 event page or 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 plicated 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?
To remove the global variables used in a persistent MV2 background script when migrating to an MV2 event page or 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 plicated 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?
-
A bit off topic but just curious, what's the reason to push to
activatedTabs
only if it's empty? Doesn't it only keep track of the first activated tab? – pkacprzak Commented Sep 21, 2022 at 11:16
1 Answer
Reset to default 12The number of variables in the state doesn't change the approach:
- read the state on the start of the script
- save the state on change
You can use global variables as long as you await
for their initialization.
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-patible types i.e. string, number, boolean, null, arrays/objects of such types. There's also IndexedDB for Blob or Uint8Array.
Separate variables
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();
}
});
Single object with properties
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();
}
});
If you change storage outside the background script
...then you'll need to subscribe to chrome.storage.onChanged event.
Notes
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.