Closed owencm closed 7 years ago
What's the status of spec'ing the beforeinstallprompt
event? It shipped in blink based only on the explainer which doesn't seem very precise about the current design.
Anyway it seems pretty close to what you want to me. Eg. imagine it had been named 'installopportunity' instead. Do developers care to differentiate between the UA choosing to show a banner, vs. the user explicitly finding the menu option?
@RByers the problem with "beforeinstallprompt" is that it assumes Chrome's UI install flow. That might not match what all UAs do. For example, in Firefox, we are working on "installing" apps into the about:newtab page, which doesn't mandate any install flow.
One current solution to this problem is the use the start URL and just add a query string:
{
"start_url": "foo/?launched-from=homescreen"
}
Another way of detecting if the app has been installed is if the display mode has been applied (using matchMedia).
matchMedia("(display-mode: fullscreen)").matches
I guess the beforeinstallprompt
could be added in a non-normative section?
Changing the start_url apparently affects Service Worker caching - or so I heard.
Using start_url
or matchMedia
only gives information about usage of added to homescreen web apps, not about how often they are added. Users might add web apps but never actually use them or the other way around.
Regarding the initial proposal, we could have an install
event which could be fired whenever the app is installed. Not entirely a big fan of this but we kind of opened the door for this when adding beforeinstallprompt
.
Speaking from the outside (I don't have any real context / expertise here) it would be great if we could figure out how to abstract the different use cases into a single API we could all support. Eg. what about an 'installstatuschanged' event with a few possible enum values (not all of which would necessarily be applicable in all scenarios/UAs/platforms) like: "manually installed", "automatic prompt", "prompt accepted", "prompt declined", etc?
@RByers Yes, that sounds like a good idea. I know that some people also have ideas of tying this in with search engines, so that might open up for a few other options.
What about
engagementchange(EngagementChangeReason reason)
enum EngagementChangeReason {
"uninstalled",
"installed-manually",
"install-prompt-shown",
"install-prompt-cancelled",
"install-prompt-accepted",
"install-prompt-denied"
}
Why not simply use the events? The current one (beforeinstallprompt
)
combined with installed
should offer the same information.
On Thu, 17 Dec 2015, at 12:41, Kenneth Rohde Christiansen wrote:
What about
engagementchange(EngagementChangeReason reason) enum EngagementChangeReason { "uninstalled", "installed-manually", "install-prompt-shown", "install-prompt-cancelled", "install-prompt-accepted", "install-prompt-denied" }
Reply to this email directly or view it on GitHub: https://github.com/w3c/manifest/issues/417#issuecomment-165442341
That is an option, though you could argue that it is not a prompt in all cases (manual, from search engine?) and that doesn't cover uninstall.
If there is no prompt, there is no beforeinstallprompt
event but there is an install
event.
Regarding uninstall
, I'm not sure this event makes a lot of sense: would you open the web app to tell it has been uninstalled? (FWIW, Chrome wouldn't be able to implement this.)
Ok, so if I understand you correctly, you want to standardize beforeinstallprompt and additionally add an install event? I am fine with that. You are probably right that uninstall is not that useful.
@marcoscaceres could you explain more about Firefox's problem with onbeforeinstallprompt?
I can imagine if you're automatically installing apps into your new tab page you wouldn't need this event, is there more to it than that?
FWIW in Chrome web apps don't know how they have been launched / started, and our suggestion is to use a similar parameter in the start_url, but that seems like a different problem to what onbeforeinstallprompt was designed for.
Today my web app uses the Chrome prompt, but it also should support Safari in iOS home screen, we created a custom modal telling the users they can save the web app to their home screen.. The problem we had is that there's no way to remove this modal once the users has added the web app to their home screen.
This isn't a problem with analytics purposes but implementation.
On Safari we have a non-standard window.navigator.standalone
which is terrible, but other informations on navigator would be appreciated to answer these question.
if (!('AddToHomeScreen' in window.navigator)) {
// browser doesn't support it
return
}
if (window.navigator.AddToHomeScreen) {
// icon in the home screen, based on this you can take lots of decisions
} else {
// app isn't in the home screen
// And isn't Chrome, what about showing the user how to add this to its home screen?
}
The issue I'd have with @marcoscaceres https://github.com/w3c/manifest/issues/417#issuecomment-164981366 mediaMatch solution is that I could use on manifest.json "display": "browser"
, not only "display":"standalone"
.
The issue I'd have with only a installed
events, is that is will be fired once, then later I'll need to save it to my localStorage, or make some other workaround to know if the user has installed it or not
I'd suggest something like this
window.addEventListener('addedToHomeScreen', handleAddedToHomeScreen)
window.navigator.addedToHomeScreen // Boolean // Easy to check for browser support
function handleAddedToHomeScreen() {
// Send analytics convertion, this user seems to love our web app
}
It sounds to me like we're converging on introducing a new install
event (subject to name bikeshedding).
@felquis - I see the desire for the boolean too, although I worry that it's overly simple since the user may have installed the site but since uninstalled it (which Chrome is unable to detect today, FWIW), or visited it now in browser mode for some reason despite having installed it previously. I suggest for now that you listen for the event and write it to localstorage, which provides basically what you want but without requiring another piece of standardization.
I suggest we go ahead with standardizing the new event. Sound good?
Ok, install
is growing on me. Basically, install
translates to successfully dropping an icon + name of app somewhere.
I support what @owencm is saying: being able to check, in the sense of a boolean, if an app is installed is going to be very hard (if not impossible) to implement on various OSs... as @owencm also points out, it would need to be done async, because the user could trash the icon right after install - or at any point... if at all possible, the UA would need to somehow check if the icon it dropped on the homescreen is still present (which means at least IO or IPC somewhere).
I also understand the value of onbeforeinstall
and us our implementation on Android ramps up, it will likely mean that Fennec will need this event also: however, I don't know yet how our Android UX team will want to implement the install flow for a web app (i.e., if they will need it at all, or if they will do something similar to Chrome). I hope I can answer that in the next few months.
Let's add it then.
Does the "isInstalled" boolean have to always be 100% up to date or could it be a cached value?
Let's add it then.
"It" being "onbeforeinstall", allowing UAs to not necessarily make us of this. This event is cancellable, does not bubble, etc. Chrome folks, let me know what you do here exactly and I'll spec it.
And "install" event, happens on successful "installation" (i.e., whatever that means to the UA, as to where it allows the user to access the "installed" web application). This event is not cancellable, does not bubble, etc.
Does the "isInstalled" boolean have to always be 100% up to date or could it be a cached value?
I say we punt on isInstalled
. It just won't be reliable.
Marcos, do you mean beforeinstall
or beforeinstallprompt
?
On 15 Feb 2016, at 7:43 PM, Mounir Lamouri notifications@github.com wrote:
Marcos, do you mean beforeinstall or beforeinstallprompt?
Sorry, I meant beforeinstallprompt.
— Reply to this email directly or view it on GitHub.
@marcoscaceres Chrome Platform Status suggests explainer.md is the documentation for beforeinstallprompt
. LayoutTests should tell what is implemented (assuming test coverage is good, @mounirlamouri?).
Without a strong use case I'd punt e.platforms
for later. It exposes the related_applications
that "are provided as options in an install prompt".
@anssiko, the layout tests have been updated for the latest changes, this one is probably the best one to look at.
The explainer.md is a little out of date, so the tests are probably a better reference for what Chrome has implemented. Specific things I noticed: accepted
is returned for completed installs, not installed
, and e.prompt()
returns a promise, which explainer.md doesn't explain.
@benfredwells, this is super helpful! thanks.
@marcoscaceres If you're looking at speccing this feature I'm happy to review and contribute.
@benfredwells Thanks for confirming the layout tests' status.
Will start on this tomorrow.
Ok, so we really want to deal with 2 cases here:
The Chrome proposal only deals with 2, so I think that is insufficient for us to standardize on.
This action is non-cancellable, occurs independently of the application (i.e., it is not observable), but the application should still be notified that it was "installed".
Solution: install event. This simple event fires after the UA has installed the application. What "install" means is left up to the UA, but generally means that the icon and application name has been added to the homescreen (or other place where apps are installed, like about:newtab).
(mostly the Chrome case)
.preventDefault()
applies. .prompt();
.What do people think?
Question: should we deal with install errors or punt on them for now?
enum InstallationChoice {
"installed",
"dismissed",
"denied",
};
interface BeforeInstallEvent : Event {
Promise<InstallationChoice> prompt();
};
[NoInterfaceObject]
interface AppInstallEventsMixin {
attribute EventHandler onbeforeinstallprompt;
attribute EventHandler oninstall;
};
window implements AppInstallEventsMixin;
@owencm, @RByers, as OPs, would like to hear your thoughts on https://github.com/w3c/manifest/issues/417#issuecomment-200192679.
@mounirlamouri, how would the above sit with you? Would you be willing to change your implementation?
Regarding: "dismissed" vs "denied" - dismissed would be the user not explicitly saying they don't want to install the application (e.g., clicking a close button, or pressing the "esc" key). "denied" is effectively saying "no, thanks!... and don't bug me again about this site".
They are different signals and it's probably important to distinguish between them: high dismissal rates might be indicative that something is wrong with the UX (or prompting might be occurring at a bad time), for example.
Slightly off topic: Dismissed probably will mean that it will reappear next time the site is loaded which may make some people say "no thanks" if busy with other things (ie, driving - when you really shouldn't use your phone :)). In a way, I would like a 'not now' but maybe we could do some recommendations to dismiss the install prompts at least for a while (one hour?)
Did you see the API implemented in Chrome? https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/modules/app_banner/AppBannerPromptResult.idl https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/modules/app_banner/BeforeInstallPromptEvent.idl
Maybe you could mention the reasons regarding the differences?
@marcoscaceres Thanks for the proposal. Is the promise returned by prompt()
fulfilled with "installed"
when the installation actually succeeds (analogous to install
event firing) or when the user clicks the "install the app" button (in which case the installation can still fail)? I feel the former would be better.
@anssiko, yes, the former. Sorry for not being clear.
@mounirlamouri asked:
Maybe you could mention the reasons regarding the differences?
Mainly, the Chrome implementation fails to handle case 1.
Also, there are a number of unclear things about the Chrome implementation. Like, why is the platform needed? Won't the platform always just match the platform you are on (e.g., As a user: I'm in Chrome on Android; I see the app banner and install the Android app... I can't install the iOS app, as I'm, you know, on Android!?).
The same applies to BeforeInstallPromptEventInit, why do you need the platform there?
Why do you need both Promise<void> prompt()
and .userChoice, when prompt() can answer the question for you?
@mounirlamouri, the Chrome proposal also conflates denied and dismissed. Firefox non-modal prompts use 3 states... yes, it's a bit of a nightmare... don't get me started.
Like, why is the platform needed? Won't the platform always just match the platform you are on (e.g., As a user: I'm in Chrome on Android; I see the app banner and install the Android app... I can't install the iOS app, as I'm, you know, on Android!?).
In that case there are two possible platforms: web and android. With the related_applications manifest field web apps can have both related non-web apps (e.g. Android) as well as the web app itself. Both of these could be installed.
The prefer_related_applications field allows a web app to give a hint about which platform it would prefer to have installed, but UAs are under no obligation to follow that hint.
Quick update... currently working on this in the "install_event" branch. Hope to have a PR soon.
@felquis requested support for a specific use case "Detect if the user has the app installed" which was de-prioritized in favor of implementing the install events.
Would anyone be opposed to me logging a new issue with this explicit requirement?
My rationale is that the Web Payments WG [1] is considering making app manifests a central part of how Payment Apps [2] will work and we have a similar use case.
[1] https://github.com/w3c/webpayments/ [2] https://w3c.github.io/browser-payment-api/specs/architecture.html#payment-apps
@adrianhopebailie asked:
Would anyone be opposed to me logging a new issue with this explicit requirement?
No objections. Please file a new bug and we can pick that up there.
I'm having trouble deciding what to do if the installation process fails: That is, once the user makes a choice to proceed with installation, it can fail because: out of space, security error, etc. It doesn't seem right to me to make .prompt()
's promise handle this case, as was previously suggested by @slightlyoff.
If we are going to handle it at all, I feel there should be an window.oninstallerror
event handler instead.
Thoughts @mounirlamouri, @kenchris, @anssiko ?
Over on Moz's Bugzilla, @bzbarsky pointed out that BeforeInstallPromptEvent
has a pretty serious issue:
So the idea is that you can only prompt if no one else has prevented you from doing so? And hence the behavior is event listener ordering dependent? :(
Boris is right and that's indeed pretty borked. @mounirlamouri, @slightlyoff, we might need to come up with some other way to deal with beforeinstallprompt
.
I might separate install into its own bug, as that event should not be controversial.
I've spun off the install event to https://github.com/w3c/manifest/issues/455 and sent a PR #453. @owencm, your review would be appreciated.
Hi, I'd like to pick up where this discussion left off in April (since it seems we dropped the ball on the Chrome side). I've chatted with Dom, Mounir, Ben, Alex and Owen (all from Chrome) to get up to speed.
Here's the state of play wrt beforeinstallprompt
(BIP) and install
events:
It looks like install is going ahead and it's relatively simple and non-controversial, so I've started implementation in Chrome (I just sent an Intent to Implement email today; Chrome tracking bug).
However, we still think there's value in BIP. Last signal from Marcos on this was:
we might need to come up with some other way to deal with
beforeinstallprompt
.
I don't think install
satisfies this requirement. We want a way to report to the website before showing the install prompt, to give the site an opportunity to suppress the banner and expose it to the user at a more convenient time (e.g., instead of it popping up in the user's face, it can provide a banner/button to install).
Now I'm trying to understand what Boris's objections are to the current implementation of BIP in Chrome. I don't really understand this:
So the idea is that you can only prompt if no one else has prevented you from doing so? And hence the behavior is event listener ordering dependent? :(
My understanding is that you can only prompt iff you've previously called preventDefault
on the event. So it seems more like the "event listener ordering dependent" problem is if you have two listeners, one calls preventDefault
and the other calls prompt:
window.addEventListener('beforeinstallprompt', e => e.preventDefault());
window.addEventListener('beforeinstallprompt', e => e.prompt());
Now in this particular case, it's unspecified whether the prompt is shown, depending on whether the first or second event fires first. Is that Boris's problem? Please explain if I'm mistaken.
I'm trying to figure out how serious a problem this is. On the one hand, it's trivial for a web developer to write code that depends on the delivery order of events (and we can't do anything about that). But if we really want to solve this, we could make it so that prompt()
is always successful: the very existence of the BeforeInstallPromptEvent
means that the UA has permitted an install prompt, so you should always be able to show the prompt if you have such an object. Calling preventDefault()
on the event after calling prompt()
would have no effect. Therefore, as prompt()
always trumps preventDefault()
, if you call both you will see the prompt regardless of the order. Does that satisfy your concerns?
On the other hand, I do find there something a bit weird about using the event object itself to do this (potentially having to store the event object around for a long time until you are ready to prompt it). Some more approaches (backwards-incompatible with the current Chrome implementation):
navigator.showInstallPrompt()
that lets you explicitly ask to show an install prompt, which will succeed if you've been given a BIP event and haven't yet prompted, but fails otherwise.prompt
method (rather than the event object itself). That way you can store that object without having to store the whole event object.I hope we can get beforeinstallprompt into the spec as it provides real value and we don't want it to just sit around as a Chrome-only thing.
I agree that the beforeinstallprompt is quite useful. Showing the prompt at the right time can really make a difference (watch the presentation by booking.com at the PWA summit: https://youtu.be/LUwdZfEW0C4?t=9m58s). It can also make the install prompt less annoying, like the way Flipkart is doing it.
Like @mgiuca I also didn't fully understand the complains.
I think the problem with navigator.showInstallPrompt()
is that way you don't necessarily know whether it will succeed or not, which means that you cannot adopt your UI in the way Flipkart does it. Of course, we could add another method for querying that, or people can set some state when receiving the beforeinstallevent
event, but this way it might be a bit more disconnected than the solution currently in Chrome.
Yep I agree with Ken's analysis on showInstallPrompt
. (Just trying to throw out some alternatives to make sure we aren't going with the current status quo just because it's already implemented.)
I think the problem is that another script can block you from ever seeing the prompt in an undeterminable way. Having a separate method is in some ways worse, as a third party script might try calling the show method at a point the host site doesn't intent, even if it wasn't the same script calling preventDefault()
.
Are you saying there's a problem with a malicious third-party JavaScript running in your site's JS context (capturing the install event, calling preventDefault, and never prompting)? I think if you're letting untrustworthy JS code run on your site (due to XSS presumably) then it's game over already; we can't design around that scenario.
It looks like install is going ahead and it's relatively simple and non-controversial, so I've started implementation in Chrome (I just sent an Intent to Implement email today; Chrome tracking bug).
🎉
I don't think install satisfies this requiement. We want a way to report to the website before showing the install prompt, to give the site an opportunity to suppress the banner and expose it to the user at a more convenient time (e.g., instead of it popping up in the user's face, it can provide a banner/button to install.
We are all in 100% agreement with the above - and that "install" is a separate use case. We don't need to discuss it here as it's solved.
Now in this particular case, it's unspecified whether the prompt is shown, depending on whether the first or second event fires first. Is that Boris's problem?
I think it's actually the opposite - (but hopefully @bzbarsky can comment here). I think it's the opposite:
// Script 1 - totally show it! (<script async>)
window.addEventListener('beforeinstallprompt', e => e.prompt());
// Script 2 - whoa! don't show it! (<script async>)
window.addEventListener('beforeinstallprompt', e => e.preventDefault());
Calling preventDefault() on the event after calling prompt() would have no effect.
Ok, but this might be where you end up getting into funky race conditions depending on ordering of in which scripts are executed (using script@async): event order registration is not deterministic.
- A global method like navigator.showInstallPrompt() that lets you explicitly ask to show an install prompt, which will succeed if you've been given a BIP event and haven't yet prompted, but fails otherwise.
I kinda like this. I also don't like the idea of having to hang onto random objects from the event.
Are we sure we can't do a flipkart here?
var wasPrompted = false;
addEventListener("beforeinstallprompt", e => {
e.defaultPrevented();
wasPrompted = true;
icon.shakeThatCutePhoneIcon();
});
icon.onclick = (e) => {
if(icon.isShaking && wasPrompted){
navigator.showInstallPrompt();
}
}
A little out of left field, but we might even consider if there is overlap with the permissions API?
navigator.permissions.query({name:'install'}).then(function(result) {
if (result.state == 'prompt') {
navigator.permissions.request({name:'install'}).then(...)
}
});
I hope we can get beforeinstallprompt into the spec as it provides real value and we don't want it to just sit around as a Chrome-only thing.
Us too! I've emailed Boris and Olli Pettay (who Gecko's events model probably better than anyone). They are the ones that will need to approve if this goes into Gecko, so I'm hoping they will have good feedback.
I think it's the opposite:
window.addEventListener('beforeinstallprompt', e => e.prompt()); window.addEventListener('beforeinstallprompt', e => e.preventDefault());
That's the same as my example with the two registrations swapped around, right? My assumption was that the registration order doesn't matter (so this is the same as my example)... either way, the point is that as it stands now (in Chrome), it will either prompt or not prompt depending on the order the events get delivered (which may or may not be the same as the declaration order).
The question is whether it's acceptable to have a web API that behaves differently depending on event order. It doesn't seem too horrible given that I can easily write some code like this:
var pressed = False;
window.addEventListener('keydown', e => { pressed = true; });
window.addEventListener('keydown', e => {
if (pressed)
alert('You pressed the key');
});
Whether the alert is shown depends on the execution order of the listeners, which is unspecified. Any stateful API can be made to behave differently depending on event listener evaluation order. So I'm not really seeing the big deal of adding a new API whose behaviour can be made to depend on event listener evaluation order (really, it just means the API is stateful).
Anyway, assuming we don't want the API to be abuseable in this way...
Ok, but this might be where you end up getting into funky race conditions depending on ordering of in which scripts are executed (using script@async): event order registration is not deterministic.
My suggestion was to make prompt
show the prompt regardless of whether preventDefault
had been called, and conversely, make preventDefault
not prevent the prompt if prompt
has been explicitly called. This should mean in the above example (regardless of which order the listeners are registered or which order the UA decides to execute the listeners in) it behaves the same way.
prompt
goes before preventDefault
, it shows the prompt and preventDefault
has no effect.preventDefault
goes before prompt
, the automatic prompt is cancelled, but then prompt
causes the manual prompt to be displayed.So either way, the user will see the prompt.
Are we sure we can't do a flipkart here?
Your example captures the way I imagined a hypothetical navigator.showInstallPrompt
to work. However, note that this API by itself doesn't solve the unspecified order problem:
window.addEventListener('beforeinstallprompt', e => e.preventDefault());
window.addEventListener('beforeinstallprompt', e => navigator.showInstallPrompt());
You have the same problem. So the question of whether to make BeforeInstallPromptEvent.prompt()
(or navigator.showInstallPrompt
) not require preventDefault
is orthogonal to the question of whether it should be a method on BeforeInstallPromptEvent
or navigator
.
A little out of left field, but we might even consider if there is overlap with the permissions API?
I'm not really a fan of this approach because it implies the user would be prompted right away and/or that the page has to continuously poll to see whether the permission is available. I'd rather it be delivered as an event.
FWIW, I think the aspect of onbeforeinstall that allows a site to hold the event until later to use the prompt method to be very unusual. If we could normalize that part to an API that can be called once a permission is granted, that would be great.
I'm not really a fan of this approach because it implies the user would be prompted right away and/or that the page has to continuously poll to see whether the permission is available. I'd rather it be delivered as an event.
sorry, I should have clarified the proposal.
The BIP would still be fired - but it would be a simple Event that is cancellable. It does not imply that or require to be prompted right away: the UA is still in control of the prompting, in coordination with the page.
The page would not be required to continuously poll because the Permission API notifies of changes to permission states (or else the Permission API itself would be completely broken - because then everyone would need to poll for every permission type).
The installation model currently proposed replicates much of the Permissions API's model, and I'm starting to think we should give serious consideration about retrofitting the current installation model to fit.
The only difference is that installation's initial state is "denied", then switches to "prompt" if BIP's default is prevented. And "granted" does nothing, as its been installed.
I've heard some feedback from developers that they are very excited by encouraging users to press the 'Add to Home Screen' button in Chrome, and the equivalents in Opera etc, but that it is a problem that they can't track that event for analytics purposes.
It would be great if we could somehow expose to developers that the user has installed their web app via some UA-provided button.
We could create a new event for this, or one alternate idea would be to fire a non-cancellable BeforeInstallPrompt event which immediately resolves the
userChoice
promise whenever the user presses a UA-provided button to install the web app.