Configurable browser action for MV3 extension

I’m trying to make the browser action in my Firefox MV3 extension configurable so that the user can choose to either open an extension page in a popup window or show a sidebar UI. The configuration would be set in the extension preferences page, and the setting stored in extension storage (get/set via the browser.storage.local API).

But I’m running into a blocker due to browser.sidebarAction.open() only callable from a user action. In the browser action handler, I would need to read the user’s preference from storage to determine whether to open the sidebar or the popup window. But the following doesn’t work:

browser.action.onClicked.addListener(async (aTab) => {
  // Get the user's preferred action when the browser action is clicked
  let pref = await browser.storage.local.get("browserAction");
  if (pref.browserAction == 1) {
    // This doesn't work!
    browser.sidebarAction.open();
  }
  else {
    openThePopupWindow();
  }
});

And from what I understand, it doesn’t work because putting browser.sidebarAction.open() inside an async function causes it to lose the user action/input context. But it’s necessary because the browser.storage APIs are asynchronous.

What workarounds should I consider?

1 Like

There is a plan to remove the user-action requirement for the similar “openPopup” API:

I think the same reasoning could be used for the sidebar.
Especially when using non-persistent background scripts where you can’t access async store to read user preferences when the script just woke up.

The only workaround I can think of is to use “localStorage”, which is synchronous. But this will work only in Firefox since Chromium service workers can’t access localStorage anymore.

1 Like

The solution I’ve used in the past is to use the storage.onChanged API to listen for changes to the settings. Then you can change the browserAction.onClicked listener as needed by removing the old one and putting in the new one.

It’s potentially slightly more performant because you are handling the user’s setting before it’s needed, so you no longer need to waste time loading the setting when the user clicks.

2 Likes

Thanks @juraj.masiar - the solution is exactly what I needed! I’m not too concerned about incompatibility with Chrome since the extension is only intended to work in Firefox.

@ComputerWhiz This is what I would have done in the past as well, but my extension is MV3, and I found that when the suspended background script is restarted when the browser action is clicked, repopulating the setting in the background page won’t happen in time for the browser.action.onClicked listener to read it.

Building on what @juraj.masiar shared, there are also plans to remove the user interaction requirement from other similar UI surfaces like the pageAction and sidebarAction APIs.

That said, I still think we have a broader problem with reacting to user actions asynchronously in background event listeners. For example, say you use the action.onClicked event to trigger a permissions.request() call, but first you need to fetch a value from storage in order to make the correct permission request. While I can’t promise any specific action, it’s at least a problem that we’re aware of.

1 Like