Cross-browser permissions.request?

I have an extension that injects content scripts into certain sites. I know that asking for <all_urls> permission is bad so I listed each origin I support one-by-one in my manifest’s permissions.

This works fine, but makes for a sketchy install-time popup, asking for permission to read many sites. I thought I would switch to an optional_permissions model and request the specific origin permission whenever the user manually enables my extension for a tab. However, I have run into multiple roadblocks trying to get this to work.

My first attempt I created a popup to show from my browser_action and placed a button in there. It turns out this doesn’t work, due to some Firefox bug. You receive an error telling you that you can only use permissions.request API from a user action.

Okay, so popup doesn’t work. I will just make clicking the browser_action itself toggle the extension’s behavior. This ended up working on desktop Firefox. Click the browser_icon, and the permissions dialog appears. When I started testing on Firefox Android however, I again receive the error telling me I can only use permissions.request API from a user action! Is this another bug?

My question is this:

Is there a stable cross-browser (Firefox, Firefox Android, Chrome) way to work with optional_permissions and permissions.request which is intuitive for users?

I am considering abandoning this attempt at user advocacy and return to asking for all supported origins during installation time. Looking for some sliver of hope to motivate me to keep fighting. Thank you.

Hi a13o, thanks for getting in touch. The permissions.request API does indeed require a user interaction. Without seeing your code it is difficult to judge if it is a bug or not, but if it works differently between Firefox Desktop and Android then it might indeed be one. Do you have your code available publicly by chance?

A common caveat is running asynchronous operations from the user interaction, and then attempting to use the request API once the async operation completes. While it may feel like it is in response to a user action, technically it is difficult to tell.

This kind of thing should work:

button.addEventListener("click", (event) => {

while this may not:

button.addEventListener("click", async (event) => {
  let prefs = await;

Hi Philipp, thank you for taking an interest in my situation. I have the code available online:

I did make sure not to slip anything asynchronous in front of the permissions.request, having learned that lesson the hard way.

What I have now is a mostly straightfwd call to permissions.request from inside a listener given to browserAction.onClicked.addListener. If this smells like a bug I can try to put a POC extension together which will rule out the rest of my code as the culprit.

I’m contemplating a third approach now, which is to use options_ui opened in a tab, to manage all the user interactions around permissioning. I work on this extension in my free time so I haven’t validated that approach yet. What I like about it is that it stays away from the browser_action entirely, which is where I seem to keep running into trouble. The browser_action implementations greatly differ across Firefox and Firefox Android so I could see it being a magnet for cross-platform issues.

Here is my solution for anyone stumbling across this post.

I tested using options_ui to show an options page; and the permissions.request call works fine there, for both Firefox and Firefox Android.

// manifest.json
"options_ui": {
    "page": "options.html",
    "open_in_tab": true
<!-- options.html -->
<button id="request_permissions">Request Permissions</button>
<script src="options.js"></script>
// options.js
const $requestPermissions = document.getElementById('request_permissions');
  $requestPermissions.addEventListener('click', () => {
      // etc