All users were logged out of Bugzilla on October 13th, 2018

chrome.permissions.request needs to be called directly from input handler, making it impossible to check for permissions first

UNCONFIRMED
Unassigned

Status

P3
normal
UNCONFIRMED
a year ago
3 months ago

People

(Reporter: aleks.dimitrov, Unassigned)

Tracking

55 Branch

Firefox Tracking Flags

(Not tracked)

Details

(Whiteboard: [permissons])

(Reporter)

Description

a year ago
User Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:55.0) Gecko/20100101 Firefox/55.0
Build ID: 20170901071738

Steps to reproduce:

Called from the options UI of a webextension, the following code works in Chrome, but not in Firefox:

document.querySelector('#some-element').addEventListener('click', () => {
      const url = 'https://example.com';
      chrome.permissions.contains(
        { origins: [url] },
        (result) => {
          if (!result) {
            chrome.permissions.request(
              { origins: [url] },
              (granted) => {
                if (granted) {
                  console.log('granted permission');
                  );
                }
              },
            );
          }
        });
   });

It should be enough to stick this code in an HTML file, which has a <a id="some-element">click me</a>, and point the manifest to the file:

"options_ui": {
  "page": "options.html"
}


Actual results:

Clicking on #some-element yields the error message:

Unchecked lastError value: Error: May only request permissions from a user input handler

Calling chrome.permissions.request(...) directly from the handler and not putting it in a callback first works.


Expected results:

I'm unsure about the security implications, but the current model disallows requesting permissions if the decision to request them depends on a callback or promise. Chrome seems to have taken a different approach.
Component: Untriaged → WebExtensions: Compatibility
Product: Firefox → Toolkit
In this particular case, you don't actually need to check for the permission before requesting it, request() will just quietly return true if you request a permission you already have.
From your description, it sounds as if Chrome is propagating the "isHandlingUserInput" state across some asynchronous operations. Rob, do you know exactly what Chrome does here and do you think its worth doing the same?  Note that we have a handful of API methods that check this bit:
http://searchfox.org/mozilla-central/search?q=requireUserInput&case=false&regexp=false&path=.json%24
Flags: needinfo?(rob)

Comment 2

a year ago
(In reply to Andrew Swan [:aswan] from comment #1)
> Rob, do you
> know exactly what Chrome does here and do you think its worth doing the
> same?  Note that we have a handful of API methods that check this bit:
> http://searchfox.org/mozilla-central/
> search?q=requireUserInput&case=false&regexp=false&path=.json%24

Chrome propagates the user input flag across callbacks, ever since the patch for https://crbug.com/361116 landed.
It can make sense for us to do so too. I'm not sure whether it is technically feasible to implement this for our promise-based API though.

With callbacks, the desired lifetime of user gestures is obvious (from the start of the callback until return, consumed when an API requiring user input is called).

Is it possible to do something similar for promises (and simultaneously making it impossible to store the promise for later)? (perhaps by forcing the user gesture to expire one second after the promise is resolved?)

bug 1392624 is also relevant (because it also involves transferring the isHandlingUserInput flag across async functions).
Flags: needinfo?(rob)
(Reporter)

Comment 3

a year ago
(In reply to Andrew Swan [:aswan] from comment #1)
> In this particular case, you don't actually need to check for the permission
> before requesting it, request() will just quietly return true if you request
> a permission you already have.

Not in my experience, I just tested it again. It re-requests permission for the same domain that the user has already added permissions for. In my addon I'm using promises (the code sample above uses callbacks for simplicity,) but the call to request() is the same.

If you're interested, the code I'm using is here:

https://gitlab.com/adimit/gitlab-time-tracking-button/blob/master/src/ChromeAdapter.js#L13

Note the workaround I'm using for FF at the moment. The permission requesting code is a bit lower:

    return new Promise((resolve, reject) => {
      this.chrome.permissions.request({
        origins: [url],
      }, (granted) => {
        if (granted) {
          resolve(granted);
        } else {
          reject(Error(`Permission for ${url} not granted`));
        }
      });
    });

I don't think this should in principle behave any differently than a plain callback-call to request(). If you think this is a bug, I could file a new one.

Updated

a year ago
status-firefox57: --- → wontfix
Priority: -- → P3
Whiteboard: [permissons]

Updated

8 months ago
Duplicate of this bug: 1438465

Updated

8 months ago
Duplicate of this bug: 1439165

Updated

4 months ago
Product: Toolkit → WebExtensions

Updated

4 months ago
status-firefox57: wontfix → ---

Comment 6

3 months ago
Since bug 1438465 is duplicated here for "handling user input cross callback". I would prefer comment here instead of file another bug.

bug 1352598 implemented a new API for searching. Extensions may call `search.search(name, searchTerms, tabId)` to perform a searching. While, it is common to pass a newly created tab's id as `tabId` parameter. But `tabs.create` is async function, which means user input status is lost, and `search.search` won't work after it.

Updated

3 months ago
See Also: → bug 1477248
You need to log in before you can comment on or make changes to this bug.