Closed chrisdavidmills closed 3 years ago
So … what are the next best actions?
Adjusting the note regarding unload
event?
Or at least mention the other two events (pagehide
and visibilitychange
) to allow developers to do more research?
Closed in favour of https://github.com/mdn/content/issues/2692.
@sideshowbarker commented on Fri Sep 25 2020
Per discussion in the comments at https://volument.com/blog/sendbeacon-is-broken, we should update the https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon article to accurately reflect actual recommended best practices for using
sendBeacon
— which at least apparently definitely do not include using it with theonunload
event.@igrigorik, @tipiirai, @devinrhode2: I’m taking the liberty of pinging y’all on this in the hopes you can help us get the article refined into what it should be. But certainly feel free to unsubscribe yourselves if you lack time to weigh in on this here.
When I say “get that article refined into what it should be”, that necessarily may include adding more warnings for developers, given that https://volument.com/blog/sendbeacon-is-broken seems to indicate that developers at the very least must be very careful about how they use
sendBeacon
, and must have the right expectations about if/how it’ll work for their needs.But also at a minimum, I think the article should have an example that aligns with how
sendBeacon
is recommended to be used — which based on @igrigorik’s comments for @tipiirai’s blog post seems to clearly not include usingonunload
.So for starters, I’ve removed the example the article had previously — because that example used
onunload
— and I added an admonition at the top of the article about usingvisibilitychange
andpagehide
rather thanonunload
, as well as another another prominent Warning box in the body of the article, with a link to the blog post.One thing I’ve gleaned so far: using
pagehide
is maybe only a workaround for Safari due to limitations its current support? And the actual recommended long-term solution is: just usevisibilitychange
? If so, I guess the wording in the admonitions should be refined to reflect that, and any example we end up adding should also at least have comments to reflect that.Anyway, I am happy to put time myself into working on updating the article; but at this point, I have near-zero experience with actually using
sendBeacon
— which is why I pinged others for guidance and help.(Big thanks to @devinrhode2 for making the initial edit to the article which made me notice the problems with the article.)
@igrigorik commented on Fri Sep 25 2020
/cc @philipwalton @yoavweiss
@sideshowbarker commented on Fri Sep 25 2020
Marginally related BCD PR: https://github.com/mdn/browser-compat-data/pull/6763
@devinrhode2 commented on Fri Sep 25 2020
This looks to be quite the authoritative reference: https://developers.google.com/web/updates/2018/07/page-lifecycle-api#event-focus Surprise Surprise - written by Philip Walton who Ilya just tagged. I think sendBeacon is mostly not the concern here, but rather, what's the best/most reliable cross-browser library for calling sendBeacon.
Browser stack tests would be nice in a way, but will not tell you if real-world browsers are getting bogged down and drop the calls for performance reasons. Would be cool if a production service like Volument re-did their study with a couple different page visibility.
It seems like the javascript on the docs page I linked to (written by Philip) might actually be the most reliable JS for most modern browsers
@tipiirai commented on Sat Sep 26 2020
Hey. Thanks for the constructive discussion around the topic. I'm happy to update the document with a snippet, that showcases the recommended use of sendBeacon. Can you hand it to me here directly? There seems to be a lot of pointers, but I'm having a hard time finding the exact code you are looking for.
We are definitely going to make another test round with the given snippet once we move to production and have time for this again. We have a strong motivation since sendBeacon would significantly reduce our server load.
Please note that the "visibilitychange" is not the event we want to use. It's different from "unload" and doesn't give clues that the session was ended. It's should be considered as a "hack" to overcome the unreliability of page unload event. Also, as far as I know, there is no evidence on "unload" event being broken on the browsers where it is supported.
@sideshowbarker commented on Sat Sep 26 2020
OK, thanks much @devinrhode2 — based on that feedback, I’ve made further updates to the MDN article such that in the place where we previously that example which used the
unload
event, we have (for now) just this warning:…and the See Also section now has this:
@sideshowbarker commented on Sat Sep 26 2020
I don’t have a such a snippet handy. That’s in large part why I raised this issue. So I hope one concrete outcome from this issue is that we end up with a snippet we can add to the MDN
sendBeacon
article as an example. But we’re not there yet.So in the meantime, what I have done to try help developers is to make the additions I outlined in https://github.com/mdn/sprints/issues/3722#issuecomment-699283671.
At least for the general purpose of what to document for web developers in MDN articles — and as far as I understand so far from reading the https://developers.google.com/web/updates/2018/07/page-lifecycle-api article and @igrigorik’s comments for your blog post —
visibilitychange
is in fact the event that we want to document that developers should be using.But I could still be wrong about that. I’ll admit I still have a lot of learning to do myself about this area.
However, I can also recognize that
visibilitychange
may be not at all the right choice for the specifics of your use case.As far as I have seen in the discussions so far, nobody has been claiming the
unload
event is broken; instead what I’ve seen are statements such as, for example, (from the blog post) the following comments from @igrigorik:…and from https://www.igvita.com/2015/11/20/dont-lose-user-and-app-state-use-page-visibility/, the following:
@tipiirai commented on Sat Sep 26 2020
My point is that if both the
unload
event and thesendBeacon
method are supported on a browser then the reliability should be 100%. However, as the data points, the calls still fail and the culprit is either the fact that the unload event was never called or the sendBeacon call doesn't reach the server. I strongly assume that the culprit is sendBeacon, because there is no evidence on the unload event being broken. This is why I think there is no such setup at all that would make sendBeacon work as expected in all browsers. We can give a recommendation, but no guarantee that it's going to work.visibilitychange
is an entirely different event than theunload
event and is meant for a different purpose. Just likeonmouseover
versusonmouseout
, for example. It's not that our use case is special.@philipwalton commented on Sat Sep 26 2020
I think the most important update to make to that page is to remove the use of
unload
in the code example. Realistically, most developers are going to copy/paste the code in the example, and having it there (even with a warning below) is sending mixed signals.If you want to show the old, bad way for contrast, then show both the old and the new way in the same code example, so it's clear that one is not recommended:
I'd also recommend updating the warning text to recommend never using the
unload
event—not with with API or any any other APIs.On the Chrome team, we're trying to get the message out that using the unload event is never a good idea. For example, Lighthouse v6.2.0 has added a
no-unload-listeners
audit, which will warn developers if any JavaScript on their pages (including from third-party libraries) adds an unload event listener, and we may even add a console warning to pages where the unload event is used.@philipwalton commented on Sat Sep 26 2020
@tipiirai:
You're correct that
visibilitychange
is not functionally the same asunload
, butvisibilitychange
it is still the event you want to use.The functional equivalent of the
unload
event is thepagehide
event (which is a bfcache-friendly, drop-in replacement forunload
), but evenpagehide
is not reliable on mobile because there are many, many cases where thepagehide
event will not fire.Here's one specific scenario:
In the above scenario, the
pagehide
,unload
, andbeforeunload
events will all not fire. However, thevisibilitychange
event will fire after step 2.This is not a bug, and it is not browser doing it wrong, it's the nature of how mobile platforms work. Sessions are not thought of in terms of distinct page loads, they're thought of in terms of in terms of foreground vs. background.
The
unload
event is broken, at least in the sense that in many browsers (current Safari and Chrome 86+ on android) it will not fire as the user is navigating to another page in the same tab. The reasonunload
won't fire is because the browser is going to attempt to cache the page, so it can be restored later if the user clicks the back button.In other words, if you're on page A and you click a link to page B, the
unload
event is not going to fire when page A is unloading. (Note: thepagehide
event will still fire in this case, as it was designed to work with bfcache).Anyway, while yes, I do agree that the
visibilitychange
event is not exactly the same as theunload
event. Practically speaking, thevisibilitychange
event is still the one we need to recommend to developers—because after it fires, there's no guarantee that the browser will ever run any code on that page ever again.@tipiirai commented on Mon Sep 28 2020
Is it guaranteed that the
visibilitychange
event will always fire when the browser tab is closed? And is there a way to detect whether the cause of the change: was the tab unloaded or made invisible? Most importantly: when closed, issendBeacon
100% reliable withvisibilitychange
?If all the above is true, then the documentation should absolutely recommend visibilitychange in favor of unload. And I would be really happy as a developer :)
If we cannot detect the "unload" event in any meaningful way then the use-case of
sendBeacon
is kind of lost:I would probablhy use the more versatile and battle-tested
XMLHttpRequest
instead.@philipwalton commented on Mon Sep 28 2020
There are always browser bugs (as with any API), but it is certainly the intention of the Page Visibility API that the
visibilitychange
event will fire at the end of every foreground session—including when the page is unloading. And in cases where the page is unloaded in the background (where thepagehide
andunload
events may not get dispatched), thevisibilitychange
event should have already fired when the page was backgrounded.Note: in terms of bugs, this issue tracks the list of bugs I'm aware of around
visibilitychange
, and many of them have been fixed since this was filed.Yep :)
I think there's some (understandable) confusion here between the
unload
event and the page being "unloaded".When a page is being unloaded, here are the events that are expected to fire:
pagehide
,visibilitychange
.pagehide
,visibilitychange
,unload
.Note: in the third case above, the
visibilitychange
event would have already fired when the page was initially backgrounded, and that's what makes it more reliable than other other events. Also note that the other events do sometimes fire on desktop, but they basically never fire on mobile.The
sendBeacon()
method is still the best way to send HTTP requests as the page is unloading. The confusing part is it should not be used in anunload
event listener, since theunload
event will not fire on many browsers when a user is navigating to another page (due to bfcache, as I mentioned above). In that scenario, both thevisibilitychange
andpagehide
events will fire, and in such cases thesendBeacon()
method will be far more reliable than theXMLHttpRequest
API.In short, if the browser is able to run event callbacks while a page is unloading then in such cases it's much better to be using the
sendBeacon()
method. But in most cases it's better to send the data as soon as thevisibilitychange
event is fired because in many cases the browser is not able to run event callbacks as a page is unloaded, e.g. if the user closes a background tab or a browser app with multiple background tabs.Sorry for the long explanation, hopefully that makes things more clear.
@tipiirai commented on Tue Sep 29 2020
Thanks for the explanation. It definitely sounds like
visibilitychange
would give us a better success rate. But I feel this is all speculation at this point and we need to make another round of tests with all the events in the mix.I still think we should at least consider the fact that the
sendBeacon
call may also fail. The worst performing browsers on our data set seem to fail 99% of the time. For example, Chrome 73 on Linux failed 99.7% of the time with over 300k page loads sampled. Blaming it all on the failingunload
event doesn't sound right. After all the unload handler has been in browsers for decades and the Beacon API is still labeled as "experimental technology".@Krinkle commented on Tue Sep 29 2020
From the current revision (emphasis mine):
I think recommending
visibilitychange
and strongly discouraging use ofunload
is fine and would indeed help avoid developers from making this common mistake.However, I think the current intro is emphasising this too much. It now sounds like
sendBeacon()
is exclusively designed for use with visibility-related tracking.There is much broader range of statistics and beacon use cases that developers may need to send beacons for. E.g. logging client-side errors, measuring feature-use (e.g. button click when opening a certain dialog), measuring success/failure paths (e.g. anonimised metadata associated with a user action, such as ajax-based saving of a comment on an issue tracker like this one), measuring performance timeline from the window-onload event, etc.
For all these, it is imho the current best practice for developers to use
sendBeacon()
for all the same performance and reliability reasons that make it appropiate forvisibilitychange
.Anecdotally – Some years ago at Wikipedia we briefly held a campaign to measure when attempts to contribute fail to complete. This was triggered on-click for an anchor link. Due to the termination of the browsing contexts upon page navigation, use of async XHR or image fetches was ineffective (this predated the Beacon API).
Likewise, for things like error logging, these could happen at any time during the browsing session, even when there is no unload in sight. Yet, I would argue it is still best practice to use
sendBeacon()
in that case so that it reduces impact on the main thread, and because even if the event being tracked isn't logically related to an upcoming unload, the user or user agent may of course at any time close the tab, in which case you wouldn't want those fetches to be aborted.@devinrhode2 commented on Tue Sep 29 2020
Along that same thread... I really feel like there's a better api that the code inside of navigator.sendBeacon could be servicing. Maybe the fetch api gets a new option for "backgroundable" or "critical" for
beacon: true
. What's to stop web developers from using sendBeacon for everything? This discussion seems to suggest that it would be more reliable. If that's not a good idea, why? What caveats are there with sendBeacon, which should be documented?Also, it might be nice if sendBeacon returned a promise... but I perhaps a promise gives developers the wrong idea. There's no guarantee a beacon will finish (like if someone force-quits the browser, etc) but it's more likely in the event of a user leaving a site. (please correct me if I'm missing something)
-Devin http://devinrhode2.github.io
On Mon, Sep 28, 2020 at 9:03 PM Timo Tijhof notifications@github.com wrote:
@tipiirai commented on Sat Oct 24 2020
@philipwalton I did a simple test with Chrome and Firefox where I closed a background tab and posted "beforeunload", "unload", and "visibilitychange" events to the opener window using
postMessage
. The "beforeunload" and "unload" events worked every time and the "visibilitychane" event never fired. You can try that yourself too and verify the results.It's pretty certain that we won't be using "visibilitychange" on our use case and I'm not sure it's the right event to recommend on the Beacon API documentation.
@philipwalton commented on Sun Oct 25 2020
@tipiirai please provide a repo, otherwise it's impossible to say what could be causing the failure you're seeing. I just tried creating a demo using
window.open()
(presumably that's what you were using since you mentioned "opener"), and it worked fine in both Chrome and Firefox.Note, I didn't try using postMessage to inform the opening window, but I did try using
sendBeacon()
(since that's the issue we're discussing), and it worked fine.If there are bugs with
visibilitychange
then we can fix those bugs and we can recommend workarounds (e.g. there is a bug in Chrome <=85 wherevisibilitychange
andpagehide
won't fire when closing a tab if anunload
orbeforeunload
event isn't registered), but we definitely cannot recommend usingbeforeunload
andunload
as a reliable end-of-session signal because (as already explained in this issue) there are numerous cases where the expectation is those events will not fire.@tipiirai commented on Tue Oct 27 2020
Here are the steps to repeat the issue:
This is probably what you expected to happen since you mentioned earlier that none of the events can capture the unloading of the page when the tab is in the background. However, I realized that the unload and beforeunload are fired on the browsers I tested.
edit: I'm using Chrome 86.0.4 on OSX.
@philipwalton commented on Tue Oct 27 2020
If the page was already in the background, then I wouldn't expect the
visibilitychange
event to fire when the tab is closed because thevisibilitychange
event should have already fired when it was backgrounded.If you want to specifically listen for the page being unloaded then you can listen for the
pagehide
event, but the point I've been arguing in this issue is that listening for a page to be backgrounded is more reliable than listening for it to be unloaded.@tipiirai commented on Wed Oct 28 2020
Three things that I have trouble understanding on the visibilitychange recommendation:
There is a common browsing habit of opening multiple background tabs with CMD- key pressed. There's even a name for it: "page parking". We cannot build a solid analytics service without knowing when a page was never opened or read. visibilitychange is useless in page parking.
Would XHR work equally with visibilitychange? Does sendBeacon give any added value compared to the more versatile and flexible XHR? What exactly?
Is there any evidence on the unload and beforeunload event not fired on browsers where they are supported? Where?
@philipwalton commented on Tue Nov 24 2020
As I mentioned above, you case use a combination of the
pagehide
andvisibilitychange
events to handle this case. But note that (again, as I mentioned above)pagehide
cannot always be reliably fired in all cases, especially on mobile if a tab is backgrounded.No, XHR is not reliable when the page is unloading. Since the
visibilitychange
event will sometimes fire when the page is unloading, XHR would likely not work in many of those cases, whereas thesendBeacon()
API likely would.Yes, Chrome has now shipped bfcache, which means it will not fire the unload event when a user navigates away from a page—if that page is able to be cached. This behavior matches the current Safari behavior, which has been the way it is for several years.
As for
beforeunload
, it's fairly easy to create a test case where this doesn't work. You can use a test page like this back/forward cache tester to see what events fire in certain situations. The steps to reproduce this on mobile are the same as I outlined above:@tipiirai commented on Wed Dec 09 2020
Which implies that
visibilitychange
+pagehide
combo is not any better thanunload
in the page parking scenario.We have similar results with sendBeacon on the study. Either due unload event not firing or sendBeacon call not able to reach the server. Hard for me to believe the
unload
event fails 94.1% of the time on Linux.I guess we can debate this on and on, but the conclusion will be that there is no way to reliably send information to the server prior to unloading of the document, which is the primary use case for sendBeacon. I don't seem to find any clear use cases for sendBeacon where it offers benefits over XHR. Would love to know one.
@Krinkle commented on Wed Dec 09 2020
@tipiirai I have found no evidence of sendBeacon being called but the request not happening or being aborted when a browser tab is closed or navigated away. As I understand it, the issue you found in your study bears no relation to
sendBeacon
. Rather, it merely observed that theunload
event doesn't fire in some cases, especially on mobile. And thatvisibilitychange
had a bug in WebKit that was since fixed.When a tab is closed or navigated away, any on-going network request from subsources, hidden images, or XHR, are cancelled. This is not the case for sendBeacon. This means it is significanly more reliable for a wide range of use cases.
This applies both to "close call" scenarios as in your story, but also improves innocuous scenarios that have nothing to do with the closing or navigating of tabs.
Innocuous scenario – For example, when instrumenting something interactive with a web application (e.g. a modal dialog, or typeahead suggestions), any statistic or error logging beacon would could be lost if the user happens to close or navigate the page before these finish in the background. This can especially affect slower connections where a cellular radio may take a while to wake up before a new connection can be established.
sendBeacon
fixes this."Close call" scenarios:
<a>
tag, or thesubmit
event on a form. This is reliable, but also synchronous. Using XHR or Image here will almost certainly result in the request never making it to the server as the browser immediately starts tearing down the JS context and aborting on-going fetches after that click event returns.sendBeacon
fixes this.location
assignment, which is equally harmful for performance, and also tends to break user expectations in terms middle-click or shift-enter for opening links or forms in a tab, and other such things that are easy to miss when trying to approximate a native event.)visibilitychange
orpagehide
events to gracefully send them to the server.sendBeacon
would allow browsers in the future to optimise this natively by aggressively buffering and delayingsendBeacon
calls to e.g. wait a minute before dispatching them for real if the radio is off and no connection exists for the target domain. Afaik browsers don't do this yet, which is why historically on desktop it was common to useunload
as a way to gracefully send non-urgent data. Usingpagehide
andvisibilitychange
allows this pattern to continue in a way that also works fairly reliably on mobile devices.sendBeacon
would not.Nothing is perfectly reliable, of course. On desktop one can force quit a browser process, disable WiFi, or pull out the battery or power cord. On mobile, there are also ways to quit applications.
Where mobile browsers are different is that they popularized the idea of a "frozen" tab or even an entirely frozen/background browser application, which can then be closed from a tab switcher or app switcher without it waking up first. Thus no events fire. Under normal conditions, I have not been able to close a browser tab on desktop without any events firing during that interaction. On mobile this is fairly simple to do.
This is where visibility and pagehide events come in. They are not a strict replacement for
unload
event, and may still not fire during the actual "close" user action. They are not signals that a tab is or will be closed. If your user case is "user abandoned page" or "tab was closed", then this will still not work and likely never will (also privacy implications). Instead, these events fire when a tab is first backgrounded or minimised. Which is great for saving state and sending beacons, which is often whatunload
was used for on desktop.For visibilitychange on WebKit in particular, there was a bug that caused it to sometimes not fire, which Wikipedia currently works around by also listerning for
pagehide
(which does seem to fire). Either way this has since been fixed in the latest WebKit. In the future, the Page Lifecycle API will hopefully provide an easier mental model for all this.@tipiirai commented on Wed Dec 09 2020
Thanks for the detailed write-up!
I guess the main issue is that there is no good replacement for the "unload" event, which is the primary use-case for sendBeacon. And without such an event one must resort to hacks and things get ugly very quickly. Duplicate events, missing events, and not knowing whether the page was unloaded or not. Ultimately the outcome is that you are not relying on the data you see on the server. You don't know whether it's the event that wasn't called (due to background tab for example) or was it the call that failed.
That is: sendBeacon is designed for a use case that doesn't currently exist. Hope we get some "reliable-unload" event in the future.
@tipiirai commented on Wed Dec 09 2020
I find it weird that everyone here seems to read the results in such a way that sendBeacon is 100% reliable and the unload event is broken. If the failure rate is 100%, then it's easy to believe the culprit is the event and not beacon. However the data shows that the calls work mostly. Given the complexity of the call as opposed to firing just an event it just might be that the culprit is sendBeacon on some occasions. It's pretty clear that another study is needed before making such hasty conclusions.
@Krinkle commented on Thu Dec 10 2020
There is no replacement for the firing of an event at the exact moment that a user closes a tab or browser. If there is a spec or documentation page that suggested or promised otherwise, then someone should fix that! 🙂
I believe browsers decided years ago that it would be detremental to UX and incompatible with their interaction model, to allow apps to wake up their thread and run code. That's why unload isn't supported. The potential for abuse and tracking is also high. I think browsers have fundamentally declined this request for the Web, and is unlikely to change in the future.
I'm not sure this is true. sendBeacon is designed as alternative for XHR, fetch,
new Image
etc. Through my expeirence with it on Wikipedia, I've only seen success. Existing use cases became more stable, and new use cases went from impossible to possible.If your application is buffering information that you want to persist to a server, it is common to debouce and buffer this to save costs. That's great for performance. In the past people sometimes used
unload
for this with nearly perfect-efficiency (waits for as long as possible, and only runs once). The alternative for this use case ofunload
is now visibilitychange+pagehide. Neither of these are directly related to sendBeacon, though, they are just events to run a JavaScript function. What you do inside that function is up to you.Of course, since these events may fire multiple times, your code is responsible for clearing it buffer before or after each dispatch. Since JavaScript is single-threaded, though, this is fairly simple to do. E.g. making the array empty, or flip a boolean to indicate that the information was already sent.
I understand that with it sometimes working for you, it naturally casts equal suspicion on all technologies involved until they are individually ruled out as root cause. I guess what others and I are saying is that we have already exensively tested and understood these technologies for many years, and have a good intuition for what is and isn't likely to be relevant to a particular issue when observed in the wild.
I don't deny the theoretical possibility, but I have trouble imagining that a browser might change the way its events are fired based on which code a developer has placed inside of an event handler.
window.onunload = function () { … })
is known and expected to not fire on mobile in certain cases. It doesn't matter whether the function callsalert()
, or writes tolocalStorage
, or makes a network request using XHR, fetch,new Image
or even sendBeacon. It doesn't change that the event doesn't and isn't expected to fire in certain scenarios.I encourage you to verify this for yourself on a simple page and play with it in different ways, you'll find your JavaScript doesn't run at all in the cases where the beacon appears to be lost. Even the "holy grail" of perfection for persisting data (the horrible synchronous XHR) or
alert()
will not work in that case.@philipwalton commented on Mon Jan 11 2021
FYI: some excellent research related to the reliability of the various beaconing APIs and events: https://calendar.perfplanet.com/2020/beaconing-in-practice/
@tipiirai you might find this helpful ^