chrome.permissions.request needs to be called directly from input handler, making it impossible to check for permissions first
Categories
(WebExtensions :: Compatibility, defect, P3)
Tracking
(Not tracked)
People
(Reporter: aleks.dimitrov, Unassigned)
References
(Blocks 1 open bug)
Details
(Whiteboard: [design-decision-approved][addons-jira])
Updated•7 years ago
|
Comment 1•7 years ago
|
||
Comment 2•7 years ago
|
||
Reporter | ||
Comment 3•7 years ago
|
||
Updated•7 years ago
|
Updated•7 years ago
|
Comment 6•7 years ago
|
||
i can confirm aleks' comments from three years ago while working with 74.0b9:
-
checking whether url(!) permissions are granted via chrome.permissions.contains beforehand cause the infamuous
'permissions.request may only be called from a user input handler' -
calling chrome.permissions.request straight for urls works as expected... in google chrome - chrome recognizes that the permission has already been granted when declared in manifest.json#permissions. at its current stage – 74.0b9 – firefox makes me declare the same permission in optional_permissions or it won't grant it; in some situations this requires the use of <all_urls> – something we certainly try to avoid – since we cannot foresee which sites the user may want to access.
may i ask to put this on higher priority?
Comment 8•5 years ago
|
||
(In reply to tom from comment #7)
i can confirm aleks' comments from three years ago while working with 74.0b9:
- checking whether url(!) permissions are granted via chrome.permissions.contains beforehand cause the infamuous
'permissions.request may only be called from a user input handler'
Not true. The contains
method can unconditionally be called.
- calling chrome.permissions.request straight for urls works as expected... in google chrome - chrome recognizes that the permission has already been granted when declared in manifest.json#permissions. at its current stage – 74.0b9 – firefox makes me declare the same permission in optional_permissions or it won't grant it; in some situations this requires the use of <all_urls> – something we certainly try to avoid – since we cannot foresee which sites the user may want to access.
Requiring a permission to be matched by a value in optional_permissions before it can be requested as an optional permission looks like intended behavior.
The fact that an optional permission is not prompted for if there is already a matching mandatory permission is a bug that tracked at bug 1502987.
may i ask to put this on higher priority?
The current priority looks about right. There is no need to call contains() before request, and removing that call resolves the issue.
Extending the lifetime of the user interaction is somewhat risky, because it can be abused to extend the user interaction for (much) longer than desired.
(In reply to Rob Wu [:robwu] from comment #8)
- checking whether url(!) permissions are granted via chrome.permissions.contains beforehand cause the infamuous
'permissions.request may only be called from a user input handler'Not true. The
contains
method can unconditionally be called.
sorry, i wasn't clear enough: a subsequent call to #request causes the error above, not the call to #contains itself
- calling chrome.permissions.request straight for urls works as expected... in google chrome - chrome recognizes that the permission has already been granted when declared in manifest.json#permissions. at its current stage – 74.0b9 – firefox makes me declare the same permission in optional_permissions or it won't grant it; in some situations this requires the use of <all_urls> – something we certainly try to avoid – since we cannot foresee which sites the user may want to access.
Requiring a permission to be matched by a value in optional_permissions before it can be requested as an optional permission looks like intended behavior.
even if we have to maintain it in both permissions and optional_permissions in manifest.json?
The fact that an optional permission is not prompted for if there is already a matching mandatory permission is a bug that tracked at bug 1502987.
ah, thank you. so i guess that one needs higher prio ;)
Updated•2 years ago
|
Comment 11•2 years ago
|
||
The severity field for this bug is relatively low, S4. However, the bug has 3 duplicates.
:rpl, could you consider increasing the bug severity?
For more information, please visit auto_nag documentation.
Comment 12•2 years ago
|
||
We discussed about this bug in a pretty recent triage and agreed on setting it as an S4, the duplicates were already known at that time, and so we will keep the S4 set for now.
(But there is still works ongoing around the changes to host permissions behaviors in manifest_version 3 and so I added it as a SeeAlso to the related tracking bug, as something to keep it in mind and consider to bump in priority).
Comment 13•2 years ago
|
||
Inability to call any async function before permission request sounds like a serious obstacle on the route to manifest v3 and service worker.
Consider an add-on that declares some optional permissions and specific permission required to complete user actions depends on configuration. Non-persistent background page or service worker may be unloaded any time. Since browser.storage.local.get()
is an async method, it is impossible to conditionally request optional permission from browser.action.onClicked
handler. With persistent background page it is not an issue since configuration may be fetched from storage on extension load, so it is almost certainly ready in user action handlers.
I just confirmed the issue with "manifest_version": 3
in Firefox-109. I can post a tiny demo add-on.
Comment 14•7 months ago
|
||
(In reply to Rob Wu [:robwu] from comment #8)
The current priority looks about right. There is no need to call contains() before request, and removing that call resolves the issue.
Since other bugs have been marked as duplicates of this one, I suggest to drop permissions.contains
from the title. Please, consider: "No API calls allowed before permissions.request
".
Chromium allows the following before permissions.request()
, however it requires a trick. API calls must be fired in parallel:
- Load extension settings from
storage.local
to determine if some permission is necessary. - Check if all selected tab titles and URLs are available and request "tabs" otherwise.
- Request permissions on demand.
addon-swsp-bg.js
"use strict";
var gConfig = undefined;
var gTabs = undefined;
var gPending = 0;
const DEFAULT_CONFIG = {
permissions: [ /* "clipboardWrite", */ "nativeMessaging"],
permissionsOnDemand: true,
};
function swspGetParams(tab) {
console.log("swspGetParams", tab);
++gPending;
chrome.storage.local.get(
DEFAULT_CONFIG,
function swspConfigLoaded(config) {
console.log("swspConfigLoaded", JSON.stringify(config));
gConfig = config;
swspCallAction();
});
++gPending;
chrome.tabs.query(
{ highlighted: true, lastFocusedWindow: true },
function swspTabsQueryCb(tabs) {
console.log("swspTabsQueryCb", tabs?.length);
gTabs = tabs;
swspCallAction();
});
}
function swspCallAction() {
if (--gPending != 0) {
console.log("swspCallAction pending", gPending);
return;
}
swspAction();
}
async function swspAction() {
const hasUnavailableTab = gTabs?.some(t => t?.url == null);
if (gConfig.permissionsOnDemand) {
const tabsPermission = hasUnavailableTab
? chrome.permissions.request({ permissions: ["tabs"] }) : null;
const actionPermission =
chrome.permissions.request({ permissions: gConfig.permissions });
const [ tabsGranted, actionGranted ]
= await Promise.allSettled([ tabsPermission, actionPermission ]);
console.log("granted", "tabs", tabsGranted, "action", actionGranted);
if (tabsGranted.value) {
gTabs = await chrome.tabs.query(
{ highlighted: true, lastFocusedWindow: true });
}
}
console.table(gTabs.map(t => ({ id: t?.id, url: t?.url, title: t?.title})));
// Either copy to clipboard or pass tabs to a native host application.
}
chrome.action.onClicked.addListener(tab => void swspGetParams(tab));
manifest.json
{
"manifest_version": 3,
"name": "perm in cb",
"version": "0.1",
"action": { "default_title": "Permissions request after API calls" },
"background": { "scripts": [ "addon-swsp-bg.js" ], "service_worker": "addon-swsp-bg.js" },
"permissions": [ "storage", "activeTab" ],
"optional_permissions": [ "clipboardWrite", "nativeMessaging", "tabs" ],
"description": "FF Bug #1398833: no async/callback API method allowed before permissions.request"
}
storage
is not a blocker for mv2 add-ons with persistent background page since settings may be loaded with background scripts, not in response to an event.
Comment 16•6 months ago
|
||
Copying relevant discussion from bug 1800401:
(Comment by Simeon Vincent [:dotproto] from comment #6 at bug 1800401)
The same underlying issue is still present in other async event handlers, such as situations where a value needs to be retrieved from storage before calling
permissions.request()
or interacting with the system clipboard.To my knowledge the primary concern with passing a user input flag down a promise chain is that this could enable abuse scenarios where an extension holds onto a promise with a valid user interaction and consume it later without the user's knowledge. I think the most obvious way to mitigate this kind of abuse is to make a user interaction invalid a short amount of time after the input. For example, a user input flag might be consumable for up to 200 millisecond after the user initiates the interaction.
The main drawback to this approach is that a fixed time limit may feel arbitrary and unpredictable for extension developers. For example, some browser.storage operations will complete within this time period while others will not. External factors may also impact whether or not a promise chain resolves in time, such as lower powered devices or resource contention on higher power systems.
A fixed lifetime isn't ideal, but at the moment it strikes me as the most practical solution for a material problem facing developers.
(Comment by Rob Wu [:robwu] from comment #7 at bug 1800401)
Nowadays the web platform defines the concept of "transient user activation", which is a short-lived period of time where an API gated by user activation can be used. Some APIs consume the transient user activation upon invocation.
Documentation: https://developer.mozilla.org/en-US/docs/Web/Security/User_activation#transient_activation
The recommended value for the timeout is specified to be "at most a few seconds", and in practice Firefox uses 5 seconds in Firefox: https://searchfox.org/mozilla-central/rev/5756c5a3dea4f2896cdb3c8bb15d0ced5e2bf690/modules/libpref/init/StaticPrefList.yaml#4356-4360
I think that we can consider integrating the concept of user activation in the extension framework to support this use case more broadly.
Updated•6 months ago
|
Comment hidden (off-topic) |
Comment 19•6 months ago
|
||
We are going to look into the feasibility of extending the duration of validity for user interaction.
Next step is to raise this topic in the WECG to align and specify the desired behavior.
Updated•6 months ago
|
Comment hidden (off-topic) |
Comment 21•6 months ago
|
||
(In reply to Rob Wu [:robwu] from comment #20)
This is off-topic
Sorry, I failed to express the idea. If there were guarantee that some API calls, fired during loading of event page/service worker, are completed before invocation of event handlers then the issue would be significantly alleviated. I mean purely local API methods like storage.local.get
that do not rely on message exchanging with other add-on components. Loading extension preferences is more close to initialization than to reaction on some event. This approach is not the best one to allow tabs.query
and similar functions that are part of event handling. Perhaps reliable initialization is harder to implement. On the other hand timeout has some shortcoming as well (Bug #1838845).
Indeed you are right that callback of storage.local.get
fired from top-level script may be invoked later than action.onClicked
listener and without user action gesture, but my attempt to break example from comment 14 in a similar way has failed (in Chromium).
Description
•