Open clelland opened 4 years ago
I would prefer 4 unless there is a clear way this API can help in a way that the more widely deployed permissions.query cannot.
The uses that I can see are of two forms:
WPT uses allowsFeature(feature) extensively as a general-purpose mechanism for testing whether features are correctly delegated.
permissions.query
be updated to support features like fullscreen, sync-xhr, idle detection, usb, webauthn, xr-spatial-tracking, etc?)I've seen uses that test whether accelerometer and gyroscope are allowed, before registering an orientationChange event handler.
permissions.query
.sync-xhr
is not a Permissions Policy thingy, but for the others, yes, I think so. The more we can converge Permissions and Permissions Policy, the better.
cc @johannhof
I agree and I can imagine updating permissions.query for that.
Luckily, synchronous XHR has easily-detectable failure modes, so doesn't necessarily need support from permissions.query
, but it certainly is a thing, since https://github.com/whatwg/xhr/pull/177. (Permissions Policy is a rename from Feature Policy; not a completely new thing).
Support for the sync-xhr
feature is implemented in Blink and WebKit (WPT), and it's one of the most used features on the web: Chrome metrics sees it used in an allow
attribute in something like 0.75% of all page views. We certainly need to consider it when making changes.
Could the JS API provide the same document.featurePolicy.features()
and document.featurePolicy.allowedFeatures()
?
I use these to check that my FeaturePolicy header is specifying something for everything (as a default would be risky); and while I could check the permissions_policy_features.json5 file, that doesen't show which ones are currently enabled.
var sent = JSON.parse(meta_ref.getAttribute('content')), // Header keys, JSON encoded, in a <meta> tag.
accepted = document.featurePolicy.features(),
skipped = sent.filter(function(a) { return !accepted.includes(a) });
var ignore = ['cross-origin-isolated'],
allowed = document.featurePolicy.allowedFeatures().filter(function(a) { return !ignore.includes(a) });
Yeah, I think that usage shows that we need something like that still. I have something of a concrete proposal that I'm writing up; I'll post it here so folks can see whether it covers the right use cases.
Based on Chrome's UMA data (added in Chrome 91, which has been in stable release for a week or so, but I've been watching it since it was the Canary channel), there are only three components of the Feature Policy JS API which are used in practice:
document.featurePolicy.features()
document.featurePolicy.allowedFeatures()
document.featurePolicy.allowsFeature(feature)
(without an origin)The remaining API surface is effectively unused.
The feature list is by far the most used API, at a startling 11% of page visits. allowsFeature and allowedFeatures lag far behind at ~0.05% and ~.0005% respectively.
Given the rename of Feature Policy to Permissions Policy, the low usage of most of the API, and what seems like an apparent similarity between Permissions Policy and the Permissions API, there seems to be justification for merging the APIs. In practice, this would mean folding the functionality of the Permissions Policy JS API into the Permissions API. I have some concrete suggestions for doing that here:
document.featurePolicy.allowsFeature(feature)
with navigator.permissions.query(feature)
This isn't an exact replacement; there are a couple of a couple of discrepancies that will need to be resolved:
permissions.query
doesn't show the 'denied by policy' state that allowsFeature()
does.To fix the first, we'd need to expand the permissions registry to include these new non-permission features. This currently has support, but may raise the bar in the future for adding new features.
For the second, we need to look at the practical difference between permissions.query
and featurePolicy.allowsFeature
.
https://w3c.github.io/permissions/#reading-current-states already integrates with permissions policy, to return 'denied'
if it is denied by policy.
There are essentially 6 states that the feature can be in: (2x3) Allowed by policy, denied by policy; allowed by user, denied by user, user never asked (prompt).
Policy: Denied | Policy: Allowed | |
---|---|---|
User: Denied | Denied | Denied |
User: Prompt | Denied | Prompt |
User: Allowed | Denied | Allowed |
Currently the policy side is revealed by the featurePolicy
interface, while the user side is exposed by permissions.query
. Merging the two APIs would mean that there is no way to distinguish between "denied by policy" and "denied by user".
Q: Are there UI cases where "denied by policy" should be treated differently than "allowed by policy; denied by user"? I think the only thing it would allow is to give websites an extra opportunity to tell the user that they could change their mind, but this seems contradictory to the user's stated intent, and I don't know if that's a pattern that should be encouraged.
For most practical purposes, permissions.query
would be a usable replacement for allowsFeature
.
navigator.permissions.features
.This is a harder move, as document.featurePolicy.features
has non-trivial amounts of usage. Again, we would need to include other permissions policy features in the Permissions enum. I do think it makes sense in the long term, though. Once usage in the wild moves to permissions.features
, I expect that the entire document.featurePolicy
object can be deprecated.
document.featurePolicy.allowedFeatures
This API could be replaced with specific calls to query the state of individual features, rather than providing a list. Especially if the list of available features can be iterated over, it would be trivial to get this with the other API calls.
document.featurePolicy.allowsFeature(feature, origin)
, featurePolicy.getAllowlistForFeature
, and all of HTMLIframeElement.featurePolicy
These APIs see essentially zero usage in the wild. Their primary function seems to be to support WPT, by allowing the state of the policy in the browser to be probed. These tests can mostly be replaced with behavioral tests, and if there are tests that absolutely require access to the interior state of the policy, then a TestDriver interface can be added to expose that.
With the "11% of page visits", I assume that's some browser fingerprinting going on? I have a slight worry, even though I'm using it, that it's being used for less than ideal purposes?
I don't know -- it's a possibility, but document.featurePolicy.features()
essentially maps 1:1 to browser version.
My suspicion (mostly because I've seen examples) is that there are a couple of widespread libraries in the wild doing something like
if (document.featurePolicy.features().includes('featureX'))
some_frame_element.allow = "featureX";
which also isn't necessary at all, but I've seen it done.
It's also possible that it's being used as a generic feature-detection mechanism, rather than for any sort of permission control or delegation.
That's interesting (and strange they do that), thanks for checking... otherwise, while I don't have a vote, I'd be happy with any of those suggestions.
I think I would prefer to not expose .features
unless there is a compelling use case.
My use case for .features()
is to keep an eye on which features I can add to the permissions-policy
header (my homepage listing my projects/tools checks this API every day).
I want to keep as many restrictions in place (I'm hosting sensitive medical data), but I don't see a default working because some things shouldn't be disabled by default (e.g. cross-origin-isolated
).
It's not as though the folder of policies is kept up to date.
And I can't check permissions_policy_features.json5 as that's for Chrome Canary, and has no indication which depends_on
flags are enabled by default - e.g. ch-dpr
is enabled, but ambient-light-sensor
is not.
That seems like a transitional problem, but will over time be solved through improved documentation.
So we will rely on all things being documented, and website developers checking the list every month or so?
Considering document.featurePolicy.features()
already exists, why can't we effectively keep it? while I was thinking about the fingerprinting risk, as Ian points out it "essentially maps 1:1 to browser version".
FYI, I have posted https://github.com/w3c/webappsec-permissions-policy/issues/444 proposing a Permissions-Policy: unload
header.
@clelland:
Remove
document.featurePolicy.allowsFeature(feature, origin)
[and]featurePolicy.getAllowlistForFeature
[…] These tests can mostly be replaced with behavioral tests
Is there any way to test for Client Hint features that doesn't involve an actual request across the network? That's expensive.
Permissions Policy currently has the JavaScript API that was specced when it was Feature Policy, but it turns out that the semantics are now a bit different, because of the way that the header is interpreted and combined with the container policy. (#357, #378)
The policy.allowsFeature(feature, origin) method currently returns whether origin is in policy's allowlist for feature (or is part of the default allowlist).
With the old header behaviour, this answered the question "would this feature be allowed in a document from that origin, in an iframe with no
allow
attribute?" -- that is, would the feature be automatically delegated to that origin.Now, with the new behaviour, being present in that allowlist does not imply that the feature would be automatically delegated. Instead, for a third-party origin, it means that the feature could be delegated, if the
allow
attribute is used. (This gets even more vague and tentative if we start looking at an iframe element's policy object, because then it tests whether the feature could be delegated, by another iframe tag inside the framed document, if the framed document matches thesrc
attribute, and hasn't been navigated to another origin, and if the framed document's header policy doesn't change anything)We could resolve this in a few different ways:
'self'
)Number 3 is probably possible; I've been looking for any evidence of usage of that API on the web, and in the entirety of HTTPArchive and the top 100k sites in the Chrome User Experience report, there is absolutely none. Sites I can observe in the wild only use
document.featurePolicy.allowsFeature(feature)
-- no origin, and not on an iframe element, just the document. The only usage I can find anywhere of the other forms of the API are in WPT, and those can be removed / rewritten.