Firefox Extension API - permissions.request may on

2020-06-28 09:24发布

I'm using the Firefox permissions API documented HERE

I'm having a problem with the request method, wherein all of my permissions requests result in:

Error: permissions.request may only be called from a user input handler

You can produce this in firefox by debugging any addon or extension and entering browser.permissions.request({origins: ["https://google.com/*"]}) into the console.

I find it hard to swallow that a permissions request must always have a user input event callback in the parent stack trace. I'm using Vue.js, and my Permissions are due to user interaction, but my user interactions are decoupled from the events they trigger.

  • What counts as a user input handler?
  • Why does it work like this?
  • Is there a good work-around?

2条回答
▲ chillily
2楼-- · 2020-06-28 09:53

What counts as a user input handler?

A DOM event handler that corresponds to user input (e.g., target.addEventHandler("click", ...) or a WebExtension event listener that corresponds to user input (e.g., browser.browserAction.onClicked.addListener(...)

Why does it work like this?

Partly for basic UX (if a user is not directly interacting with an extension and a prompt for the extension suddenly prompts up, it can easily confuse them), but also to avoid clickjacking attacks where the prompt is put up at a carefully chosen moment when the user is likely to be expecting some unrelated prompt.

Is there a good work-around?

I think just organizing your code so that you request permissions from a user input handler is probably your best bet.

查看更多
迷人小祖宗
3楼-- · 2020-06-28 09:56

Is there a good work-around"

I'd like to add onto Andrew's answer with some code examples.

As it turns out, promise chains destroy the browser's notion of what is and isn't triggered by a user input handler. Take the code below, for example:

document.getElementById('foo').addEventListener('click', event => {
  browser.permissions.request({origins: ["https://google.com/*"]})
})

This code works as expected. I originally assumed that it was Vue.js's unique event handling framework that was eating my "browser events", such as when you do <div @click="somefunc"></div>. This actually works just fine, as long as you put your permissions request in somefunc.

Now it gets fun. If you replace your permissions request with a promise that resolves and then does a permissions request, VIOLA!

Promise.resolve('foobar').then(foobar => {
    browser.permissions.request({origins: ["https://google.com/*"]})
})

Results in:

Error: permissions.request may only be called from a user input handler

Why does this happen?

I'm going to guess it has to do with stack traces. Firefox can't detect that a permission came from a stack with a user input event at the root if the permissions request happens in a promise chain.

I consider this to be a pretty egregious design choice. My app is large (>4K LoC) and to keep it simple I rely on promise chains to keep the spaghetti away. This has crippled my ability to write clean code, and as a result, I've moved from asking for optional_permissions and then prompting the user for permissions only when needed to just being overly permissive at the time of installation.

GG, Firefox.

查看更多
登录 后发表回答