I have a variable named tabMode that can take the value ‘openSidebar’ amongst others. It is stored in the options object in storage.sync. I’d like the sidebar to only open if the value ‘openSidebar’ is assigned to tabMode. Unfortunately, I keep getting an error:
Uncaught (in promise) Error: sidebarAction.open may only be called from a user input handler
Here is the latest code that I tried: (which fails)
async function f(info, tab) {
const options = await getOptions();
handleMenuClick(info, tab, options);
}
function addClickListener() {
if (!menuClickListenerAdded) {
browser.menus.onClicked.addListener(f);
menuClickListenerAdded = true;
}
}
function removeClickListener() {
if (menuClickListenerAdded) {
browser.menus.onClicked.removeListener(f);
menuClickListenerAdded = false;
}
}
async function handleMenuClick(info, tab, options) {
const id = (info.menuItemId.startsWith('cs-')) ? info.menuItemId.replace('cs-', '') : info.menuItemId;
const ignoreIds = ['download-video', 'reverse-image-search', 'google-lens', 'options', 'multitab', 'match', 'ai-search'];
if (logToConsole) console.log('Clicked on ' + id);
if (options.tabMode === 'openSidebar' && !ignoreIds.includes(id)) {
if (logToConsole) console.log('Opening the sidebar.');
await browser.sidebarAction.open();
await browser.sidebarAction.setPanel({ panel: '' });
} else {
if (logToConsole) console.log('Not opening the sidebar.');
}
await processSearch(info, tab);
}
Yep yep, this affects all browsers. As noted in 1398833#c19, “Next step is to raise this topic in the WECG to align and specify the desired behavior.” We (the WECG) kicked around some ideas at TPAC 2024, but we still need a proposal on how best to address this.
But there are some “bad” workarounds available, at least for Firefox.
you can use “localStorage” API - which is synchronous, so you can read and write it anytime and you won’t loose the “user-action flag” (I don’t know how to call it…).
However, it’s kind of “deprecated” (in MV3) and it stores everything as string, so not great.
since your background script is persistent, you could “simply” keep copy of your “options” in a global variable (available for sync. read operations).
This can be done pretty simply:
create async function that loads the “options” from storage to a global variable.
call this function each time storage changes (the browser.storage.local.onChanged.addListener handler)
call that function also on browser start (to populate it initially)
when you need sync. access to options, access it through the global variable
This second option is actually what I’ve been using all along since 2017 for all my addons for “options”.
Thanks, Juraj. Your second suggestion sounds good to me. Will give it a try. I always thought that the way things were designed was supposed to make our lives easier. I’ve witnessed that it’s not always so!
Alas, this is sage advice. Browser specs move slowly. Since I posted that comment I haven’t even been able to think about drafting a proposal. Too much other stuff going on
The HTML spec refers to this as user activation. It also defines a transient activation, which is an activation that lasts for a short duration after user interaction.
I’d tweak this slightly by saying that use of localStorage is discouraged because that API isn’t exposed in service workers, which are only required in Chrome. If you’re only targeting Firefox/Safari, then you can use it in your extension’s background context without issue.
The main thing to note about this pattern is that if you background context is started in response to an event, that event handler won’t have access to the data because the in memory cache hasn’t been populated yet. This may result in the extension feeling a bit broken because sometimes when the user triggers the extension it doesn’t work, but it will if they try again a second later.