Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop .request() #83

Closed
martinthomson opened this issue Apr 18, 2016 · 62 comments · Fixed by #159
Closed

Drop .request() #83

martinthomson opened this issue Apr 18, 2016 · 62 comments · Fixed by #159

Comments

@martinthomson
Copy link
Member

I realize that this might be a minority opinion here, but I think that .request() stands to undermine a lot of the work that we've done to make permissions on the web meaningful and relevant to users. The most important aspect of that being that permissions requests are made in context.

You have probably all read this before, but it's worth a refresher. That summarizes where this is coming from. I will note that model has been vindicated in that Android has moved to making requests in context also.

What I want to focus on here, something I consider more important than speculation about how applications might actually use .request(), is the consequence aspect of making individual APIs trigger use consent interactions.

When an application calls getUserMedia() (for the first time), it causes two things to happen: a user consent interaction, and acquisition of a resource. The same for geolocation. As a consequence, the users sees that the application is then recording media or accessing their GPS (both of which usually have indicators in chrome that the user has already learned to recognize).

I believe that coupling of request and consequence to be important. It provides applications with an incentive to defer requests until the point where the capability provided by the API is needed. More so in browsers that don't implement persistent-by-default permissions. That preserves the contextual relationship between asking and using.

An API like .request() undermines this model. We already have ways to request permission from users, and I don't see any suggestion that we're going to change that. That means that this API only serves to endorse modes where asking is decoupled from consequence.

There might be exceptions to this, and here I note that we don't appear to have a good substitute for just asking ahead of time when it comes to push notifications. The Notifications API has its own analogue of .request() for that purpose, which turns out to be bad UX, but the best we've been able to come up with thus far. That doesn't justify the creation of a generic permission request capability.

@jimmywarting
Copy link

jimmywarting commented Apr 18, 2016

I like request method because how it unifies the API on how you request permissions. Read #48 as to why I hate the existing individual request permission model. Each one of them have there own syntax and some of them are "broken" or lacks useful informations as "was the permission rejected?"

Asking ahead of time will be entirely up to the developers. I think most developers has a common sense and would only disturb the user with a prompt when needed

@jan-ivar
Copy link
Member

Asking ahead of time will be entirely up to the developers. I think most developers has a common sense and would only disturb the user with a prompt when needed

@jimmywarting why do common sense developers need .request then?

@jyasskin
Copy link
Member

jyasskin commented Apr 18, 2016

Thanks for filing this issue @martinthomson. I don't have a preference either way, but I wanted to be more precise about the Android model: Android M moved from request-at-install-time to an explicit request function that looks a lot like Permissions.request(): Activity.requestPermissions().

I'll try to get some more folks with opinions to comment.

@jimmywarting
Copy link

jimmywarting commented Apr 18, 2016

@jan-ivar To get a unified API that works the same way for all permissions and isen't broken ofc

@jan-ivar
Copy link
Member

@jimmywarting thanks for clarifying. Unfortunately, as @martinthomson points out .request is different in significant and problematic ways from existing _access_ APIs, yet your use-case doesn't seem to need these differences. Ergonomics aside, I'm confident we can fix existing problems without requiring new APIs.

@slightlyoff
Copy link

Hey @martinthomson, @jan-ivar:

I'm not sure I actually understand the concerns being raised. Requesting a geolocation permission via Permission.request() will have exactly the same side-effect (UI being shown) as navigator.geolocation.getCurrentPosition() or navigator.geolocation.watchPosition() and conceptually can also do acquisition (use) by providing the resource in the per-API subclass of PermissionStatus. The separation of "acquisition" in the case of geolocation is down to legacy API design.

In a future where all API designers can rely on browsers to have implemented Permission.request(), it will be possible for them to avoid doubling up API or separating these. In the interim period where Mozilla (and others) have failed to implement the full API, it's harder to give advice to API authors that they should not also provide their own API. That said, the TAG is working toward that future by making sure that all APIs which can have a permission are at least designed with the Permissions API in mind.

Next, as @jyasskin points out, we don't have data to confirm your fear despite have quite a lot of use of similar APIs in Android M. The risk of showing UI is itself the primary consequence that developers seek to avoid (and why the reflection side of the Permission API is so powerful) and that is what creates incentive to put use in-context.

Lastly, as a matter of platform consistency, you should think of the Permission API similarly to the Promises API: it's a new primitive that we need to work to expand integration and use of and is a project we're actively working on as new APIs come across the transom. Pointing to the fact tha some legacy APIs already have their own (inconsistent, frequently broken, badly designed) request* methods doesn't say anything about how we should improve the situation. With that perspective, I hope you'll come to view the Permissions API as the solution and not the problem. Having personally done the (relatively brutal) TAG review of the API, I certainly came to that conclusion.

Regards

@jan-ivar
Copy link
Member

@slightlyoff promises solved real problems like error propagation in asynchronous flow. What problem does .request solve?

@slightlyoff
Copy link

Promises helped resolve some of the nasty timing and API inconsistency issues we experience across the platform. As I said over and over again in the Promises standards effort "the single biggest thing Promises give us is one way to do it". The same is true of Promise.request() (and friends). Today there's no uniform way of packing arguments, registering callbacks, dealing with return values for permission states, or any of the rest.

The Permissions API, like Promises before it, open up the ability to heal that rift across the platform, both for legacy APIs like geolocation and (more excitingly) for new APIs that will be discouraged from (badly) re-inventing such APIs each time they need to expose this sort of thing.

@jyasskin
Copy link
Member

With Promises, we can take advantage of the uniform API to write libraries or even language features that treat a bunch of kinds of Promises generically. With Permissions.request(), I don't immediately see that opportunity, especially since different permissions need different extra data in their request() calls. Do we have any examples of web developers out in the wild building libraries that abstract permission calls like this?

I do see the benefit of establishing a pattern for request calls to follow. That could be done simply as TAG guidance for new APIs, plus an attempt to retrofit it onto older APIs like geolocation and notifications. It sounds like that's the path @jan-ivar and @martinthomson want to take. However, a new API gives an easier place to do that retrofit and makes it harder to miss the TAG guidance in the spec for a new permission.

@martinthomson
Copy link
Member Author

Yes, promises are great. And I get that our primitive engineer brains love to seek out patterns and try to tame them.

Where promises serve to replace an existing feature in a superior way, .request() cannot ever serve as a replacement. The API that does something still has to exist.

Many of the benefits you claim to gain from .request() are actually realized by defining .query(). I'm not arguing against that, it's a good idea. Indeed, I see much of those benefits being realized in terms of codifying what permissions an API might require. Those have a clarifying effect on the APIs themselves for the reasons @jyasskin describes. We're learning a lot about the assumptions we've made and we don't need .request() for that.

If unification is your goal here (and leaving aside for the moment that we're not really talking about unification), we have to establish that a unified API has marginal utility that outweighs the negatives. You are right to ask what the actual negative impact of decoupling is. I've already seen some indication that these negatives are present with notifications, where a decoupled request is all we currently have. A large site conducted an experiment on what style of prompting for push notifications got the best conversion rate. Asking at page load time turned out to be best for their metrics.

I know that there is a desire to do the same for getUserMedia(). I believe that there are a sizeable number of web developers who find the whole consent experience very frustrating and who would rather have it out of the way.

Install Skype on Android and witness what it does to destroy the contextual permissions thing when you start it up.

@jyasskin
Copy link
Member

Let me belatedly summarize the pros and cons I see for permissions.request(), and the pros and cons I'm skeptical of so far:

Pro

  1. Provides a new spelling for several old APIs where we can get their interfaces right.
  2. Provides a uniform spelling for requesting a permission so developers don't have to learn the intricacies of each capability's request function. (This is tempered by each capability getting to refine its PermissionDescriptor, probably in non-uniform ways.)

Con

  1. Redundant with other, more tuned spellings of particular APIs. For example, getUserMedia() might have a way to express interacting constraints on the camera and microphone, which we wouldn't be able to express in the generic request() interface.
  2. Might cause extra permission prompts. In Firefox's model, even if you request and get permission for something like geolocation, you probably can't call getCurrentPosition() without another prompt. If folks call request() thinking it'll get them access to use location wherever, they'll wind up spamming the user.

Unconvincing Pro

  1. "request() is like Promises in that it'll help devs build libraries to treat a bunch of different permission requests uniformly." I think each capability is different enough that it'll be hard to do things like make arrays of PermissionDescriptors, and because we want devs to ask for permissions in context, that wouldn't even necessarily be a good thing.

Unconvincing Con

  1. "request() makes developers more likely to ask for permissions on startup." If devs find that prompting on startup gets them "the best conversion rate", they can do that with the existing functions. Just call watchLocation() on startup to get the prompt early, and then save the stream of values so you never have to ask again. We even have an extra lever with request() to inhibit asking on startup: it'll be web-compatible to require a user gesture for a call to request(), but it's less so to require it for watchLocation().

Did I catch everything?

@jan-ivar
Copy link
Member

Provides a uniform spelling for requesting a permission so developers don't have to learn the intricacies of each capability's request function.

Apples and oranges.

If "permissions" are mere elevation-level badges (prompt, grant, or deny), then sure, all permissions are the same, and a uniform API makes sense (unless you hate Android's challenge model).

But overload access onto this API, and similarities end. Why would accessing geo-location work anything like accessing the camera? They take totally different arguments and return totally different things! No developer thinks they can ignore these domains differences.

In Firefox's model, even if you request and get permission for something like geolocation, you probably can't call getCurrentPosition() without another prompt.

Right, mandating the Chrome permission model (implicit persistent permission on access) is a non-starter.

@martinthomson
Copy link
Member Author

Provides a new spelling for several old APIs where we can get their interfaces right.

I don't find this convincing myself. Old APIs are getting new spellings as we speak. The poster child here, geolocation, has been talking about a new promise-based API. But the problem there is that there is nothing functionally wrong with their API, it's just disgusting when viewed with 2016 eyes.

Provides a uniform spelling for requesting a permission so developers don't have to learn the intricacies of each capability's request function.

I very much agree with the point on tempering here. The complexity here is not in the high level "ask for X", it's in the nuance involved with the descriptor. Of course, we all emphasize the importance of that point that differently.

@jan-ivar's concern is related to this I think. We aren't dealing well with the different ways that browsers might choose to implement access control. If all we ever do is vend season passes for sites, then this API is a fine one, but we don't do that for "filesystem" (i.e., <input type="file">).

At some level, I think that we do need to dumb down the interaction across the different APIs, but thus far it's been a little slap-dash.

@alvestrand
Copy link
Collaborator

Note: I am in favor of two things, for different reasons:

  • I am in favor of an unified concept of "request access" across specs. That's because any time we try to define it in context, it grows hairs that are subtly different from everyone else's hairs, have very limited value to the user, and serve to confuse anyone working across multiple specs. The discussions around whether the descriptor includes the top level browsing context, the iframe context, either or both, is just one example. I prefer one ball of fuzz.
  • I am in favor of the request() method because I believe the "request by using" paradigm, while applicable to a lot of cases, has the nasty side effect that applications whose logic really lend themselves to request() will try to acquire sources they don't need in order to make sure they have already done the dialogs if they need them later.

I have a strong opinion / care a lot on the first and a weak opinion / don't care as much on the second, which is why I filed #94.

@jan-ivar
Copy link
Member

In the interest of separating discussion, I think we're only talking about the .request method here.

@jyasskin
Copy link
Member

Coming back to this issue, we've talked over the pros and cons within Google a bit, and we still think permissions.request() is the right thing to do:

  1. It will let us stop building request() functions in future interfaces. They'll still need to define their descriptors and result types, but the TAG won't have to double-check that the overall shape is right.
  2. A new polyfill shows that if folks want the request() shape today, they can get it, so the new API won't do any more to let sites ask for permissions out of context. Along with permissions.query(), we think it'll actually help sites ask in context, since it helps authors think about exactly when they're showing prompts to their users.
  3. If we keep request(), we'd like to add guidance that new specs should avoid showing permission prompts from any function other than permissions.request(). To accomplish this, permissions.request() could always include an object in its result that would be used to access the requested API, and request() would probably be the only way to get that object. In the very long run, we can think about deprecating the old spellings of functions like Notifications.requestPermission() and getUserMedia() in favor of permissions.request(), in order to remove the redundancy, but of course we wouldn't do that until usage fell low enough.

@annevk
Copy link
Member

annevk commented Aug 26, 2016

I think the main problem with request() (and also query() really) is that the answer is not uniform. Depending on the permission you request, the answer you get has vastly different implications with regards to scope. So unlike with promises, you cannot actually write generic code on top (e.g., using BroadcastChannel to share the answer with other globals will only work as expected for some permissions).

I think I would be much more comfortable with this API if we actually defined the underlying "request access" primitive in sufficient detail for the answer to be uniform and provide guarantees to developers.

@jan-ivar
Copy link
Member

@jyasskin Thanks for the polyfill. Hopefully it clarifies for everyone that .request() as proposed would be an API for requesting _functionality_, not for requesting permission for said functionality.

Historically, specs draft APIs around functionality, not externalities of functionality such as whether something is allowed or not. We have web developers ask for the camera, not ask for elevation of camera permissions in order to get a camera stream as an aside.

This PR violates that, and is stealing the API surface of any spec that has a powerful feature. Hopefully, there won't be a future API corralled around some other externality, like the configuration or storage needs of a feature, or we'll have three or four ways to do the same thing.

I understand the impulse that when we can query something, we want to set and clear those things as well. However, setting and clearing of permissions is expressly implicit in using functionality on the web, by design, for the good reasons outlined in roc's blog (to prevent the permission bundle problem).

About bundling, @alvestrand said:

... the "request by using" paradigm, while applicable to a lot of cases, has the nasty side effect that applications whose logic really lend themselves to request() will try to acquire sources they don't need in order to make sure they have already done the dialogs if they need them later.

Not all browsers auto-persist permissions, so the payout of such arm-twisting is more rewarding in Chrome. Roc calls those "Greedy Applications". He says: "One wrinkle is that lazy app developers can turn the "permissions in context" model back into the "bundled permissions" model by activating APIs up-front and refusing to let the application proceed until all requests are granted. My hope is that if most apps don't behave that way, users will develop higher expectations and be distrustful of lazy apps."

This has mostly held (he said that in 2011). In any case, the way around that problem is not to piggyback said functionality onto the setting of permissions. That's awkwardly backwards, an attempt at a cross-domain (lateral) API. Even WebIDL is screaming.

Asking web developers to pivot to "permissions" as a concept to access functionality seems to encourage bundling, if not in function then in form. It will make them "lazy".

Now, looking at the two lone pros that have been mentioned for this PR:

  1. Provides a new spelling for several old APIs where we can get their interfaces right.
  2. Provides a uniform spelling for requesting a permission so developers don't have to learn the intricacies of each capability's request function. (This is tempered by each capability getting to refine its PermissionDescriptor, probably in non-uniform ways.)

These both seem aimed at lazy developers who don't want "to learn". That's a con in my book.

For instance, did you know that to immediately relinquish a hardware device you've obtained with getUserMedia, you must call stream.getTracks().forEach(track => track.stop()) ?

We want people to learn the intricacies of each capability's request function. One is not like the other.

@alvestrand
Copy link
Collaborator

It almost goes without saying, but I concluded some time ago that I think Roc was wrong in his arguments against requesting permissions. The inability to "ask first, open later" is making applications more convoluted, not less.

Jan-Ivar also misinterprets what I meant with the comment about "acquire sources they don't need" - if permission is lost when relinquishing them, the applications that fall into this trap will hang on to the devices (which will work both in Chrome and Firefox). This consumes system resources for no better reason than working around a mildly inconvenient API.

I also think that the idea of returning a capability from requestPermission() has merit, but the polyfill illustrates that it's not the same thing as opening the device - when we're dealing with real hardware like a camera that has lots of configuration settings on its own, opening it has side effects and takes time. Checking whether or not you have permission to open it should be fast and have no side effects; requesting permission is likely to involve human interaction, so isn't fast - but it should not have side effects.

Developers are not lazy, but they have priorities; the greatest praise I've had for the WebRTC API was a developer who said "the WebRTC part of my app took 5 minutes, which meant I could spend my time on working on the other aspects instead".

@jan-ivar
Copy link
Member

@alvestrand One of us must have misunderstood the polyfill. .request seems identical to opening the device. @jyasskin Is the goal to support "request-before-use" as well as "request-on-use"?

Unless it is, I see no reason to argue roc's well-established principle behind web permissions today.

When apps bully users into giving permissions without any obvious benefit, we should protect the users' right to say no, not the users' right to say yes with less use of system resources.

I dare a greedy application to grab the user's camera and keep it on for the duration of their visit, with recording-indicator and hardware light on. That'd drive away users fast. As roc says, I hope "users will develop higher expectations and be distrustful of" such apps.

@jyasskin
Copy link
Member

When writing the polyfill, I assumed that if you drop the handle, you might have to re-prompt in order to get it back. My geolocation implementation is knowingly a bit wrong in that regard, since it only uses getCurrentPosition() instead of watchPosition(), but with enough time, I'd have it emulate a multiply-callable getCurrentPosition() by just keeping the watchPosition() stream open. I think we can probably improve on the polyfill's detailed behavior with a native API, and I think we'll still have to discuss those detailed improvements after the overall outline is figured out. For example, I know of some differences between what @alvestrand and I are imagining.

@marcoscaceres, were you saying that Firefox's reprompting within the same realm is probably a bug? That seems inconsistent with @jan-ivar's position that reprompting is desirable. What are Mozilla's goals here, as distinct from a bunch of individuals who work at Mozilla?

@jan-ivar
Copy link
Member

@jyasskin Can we get a clear answer on whether the goal is to support "request-before-use" as well as "request-on-use"? This is really confusing.

@alvestrand
Copy link
Collaborator

My naive interpretation is that .request() would check available information about the user's intent, and if the information available wasn't enough to decide conclusively that the user's intent is "grant" or "deny", it would prompt the user.

I don't think .request should support the model of "temporary permission via an object of bounded lifetime" - it simply makes the model so complex that it is likely to not fit all use cases.

I think Firefox' model is perfectly supportable, as long as we don't touch it - the "live streams imply accessibility" function of getusermedia is enough.

(The relevant text is:
"Retrieve the permission state for all candidate devices in candidateSet that are not attached to a live MediaStreamTrack in the current browsing context. Remove from candidateSet any device for which the permission state is "denied"."

....

"For the origin identified by originIdentifier, request permission for use of the devices, while considering all devices attached to a live MediaStreamTrack in the current browsing context to have permission status "granted", resulting in a set of provided media."

I'm sure the text can be made clearer.)

@jan-ivar
Copy link
Member

jan-ivar commented Sep 2, 2016

@alvestrand Wouldn't that prompt Firefox users twice unless they persist permission?

@jyasskin
Copy link
Member

jyasskin commented Sep 2, 2016

@alvestrand I think Jan's right that if request() can prompt the user, but doesn't return a live stream, then when the site does try to get a live stream, it's likely to wind up prompting the user a second time on Firefox and Edge-over-http. Sites would have to either avoid request() or detect their user agent in order to produce a good experience, and we don't want to build an API that requires that. I think we really do have to make request() equivalent to getUserMedia() or not do it at all.

@jimmywarting
Copy link

I kind of lost track when this discussion started to heated up and had no time to read everything.

But i just wanted to share with you my own way of dealing with query and requesting permission. I call it browser-su It isn't really a pollyfill since it doesn't follow the standard exactly but it dose more then what it's supposed to do (checkout the wiki). Maybe you can get some inspiration from that? I don't know...

I don't like the "ask first, open later" approach i always try to ignore them until i'm sure what it will do to me or that they get it when i want them to get it. That's why a user interaction should be required IMO. Kind of neutral if it stays persistent or not.
I don't like the fact that they can request multiple stuff at once. Like some old Android apps.

Either way i think a request model of some sort is useful since it can fix all the different way we have of asking for permission of something. Instead of dropping the request how about changing the definition of what it means to request permission for fullscreen, geolocation, midi, notifications, pointerlock, userMedia, push, filesystem, bluetooth, screen, NFC? cuz there is way to many way to request for all of this things.

What i tried to do with browser-su is to report back if a request have been denied or dismissed which i think is useful.

@alvestrand
Copy link
Collaborator

@jyasskin if we can't unconfuse ourselves, it may be better to drop it until we find something to do that's not confusing.

What I said at the beginning was that I wanted to have an algorithm that I could reference from the getUserMedia procedure description, so that I didn't have to define in getusermedia what the details of the interactions with permission was.

We have that. We may be better off not adding an API for it.

@martinthomson
Copy link
Member Author

I think we really do have to make request() equivalent to getUserMedia() or not do it at all.

That's where I'm at. I concluded on the second branch here a little more quickly though :)

@marcoscaceres
Copy link
Member

That's where I'm at. I concluded on the second branch here a little more quickly though :)

I'm also reaching the conclusion that .request() is not worth exposing. Still feel there is tremendous value in .query() tho.

@jyasskin
Copy link
Member

In the absence of vocal supporters, I'd be happy to move request() and revoke() into a WICG incubation. It'll probably take me ~2 weeks, and if someone else wants to do the wording work, that could speed it up.

@jan-ivar
Copy link
Member

@jyasskin request and revoke are still in the doc and it's confusing people. Can we remove them?

@garykac
Copy link
Member

garykac commented Sep 18, 2017

An argument for keeping something like request():

One place where I would like to use request() is with the clipboard API.

(1)
We would like to have a Permission that protects receiving the clipboardchange event (since even though the event doesn't contain clipboard data, it might still reveal info about the user).

The obvious approach would be to use request() and once it is granted, then the events will start being sent.

(2)
We've also had a request for separate "read" and "write" permissions. But without an entry-point that allows requesting "full" access, there will be multiple independent permission checks that might lead to user interaction: once for reading, once for writing and a separate time for listening ("clipboardchange").

@jan-ivar
Copy link
Member

Seeing this method still in the spec confuses people a year later. w3c/clipboard-apis#51 (comment).

@jyasskin
Copy link
Member

@jan-ivar Look 1 item up from your latest comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants