mdn / content

The content behind MDN Web Docs
https://developer.mozilla.org
Other
9.19k stars 22.47k forks source link

Make the sendBeacon article match reality (including adding a best-practice example at least) #1521

Closed chrisdavidmills closed 3 years ago

chrisdavidmills commented 3 years ago

@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 the onunload 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 using onunload.

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 using visibilitychange and pagehide rather than onunload, 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 use visibilitychange? 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

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

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:

The unload event (and onunload event handler) are not the right features to use with sendBeacon. Instead you should use the visibilitychange event and pagehide event. See discussion in the comments for the blog post Beacon API is broken.

You can use the PageLifecycle.js library to deal with with current cross-browser inconsistencies in handling of the visibilitychange event and other related behavior; that library normalizes cross-browser differences in event-firing order so that state changes always occur exactly as expected, consistently in all browsers.

For additional best-practices guidance in detail, see the Page Lifecycle API how-to article.

…and the See Also section now has this:

  • Page Lifecycle API is a how-to article that gives best-practices guidance on handling page-lifecyle behavior in your web applications.
  • PageLifecycle.js is a JavaScript library (<1K gzipped) that deals with cross-browser inconsistencies in page-lifecyle behavior, enabling you to focus on following the best-practices guidance in the Page Lifecycle API article. It normalizes cross-browser differences in event-firing order so that state changes always occur exactly as outlined in the chart and tables in that article (and do so consistently in all browsers).

@sideshowbarker 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.

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.

Please note that the "visibilitychange" is not the event we want to use.

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.

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.

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:

In short, you cannot rely on onunload and not because there is a bug in one or more browsers, but because it is the wrong event to be using because it doesn't account for mobile lifecycle

detecting and triggering the call for "when the page is unloaded" is hard: you can't rely on unload, and you need to use both visibilitychange and pagehide.

…and from https://www.igvita.com/2015/11/20/dont-lose-user-and-app-state-use-page-visibility/, the following:

You cannot rely on pagehide, beforeunload, and unload events to fire on mobile platforms. This is not a bug in your favorite browser; this is due to how all mobile operating systems work. An active application can transition into a “background state” via several routes… Once the application has transitioned to background state, it may be killed without any further ceremony… As a result, you should assume that “clean shutdowns” that fire the pagehide, beforeunload, and unload events are the exception, not the rule.

To provide a reliable and consistent user experience, both on desktop and mobile, the application must use Page Visibility API and execute its session save and restore logic whenever visibilityChange state changes. This is the only event your application can count on.


@tipiirai commented on Sat Sep 26 2020

My point is that if both the unload event and the sendBeacon 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 the unload event and is meant for a different purpose. Just like onmouseover versus onmouseout, 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:

// BAD! Do not use!
// The older, *extremely unreliable* way to send data to
// a server when a user leaves the page.
window.addEventListener('unload', function logData() {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/log', false); // third parameter of `false` means synchronous
  xhr.send(analyticsData); 
});

// GOOD!
// The newer, more reliable way to send data to
// a server when a user leaves the page, including when a user:
// - Closes a tab
// - Switches tabs
// - Closes the browser app
// - Switches apps (on mobile)
// - Navigates away
// - Refreshes the page
document.addEventListener('visbilitychange', function logData() {
  if (document.visbilityState === 'hidden') {
    navigator.sendBeacon('/log', analyticsData);
  }
});

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:

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.

You're correct that visibilitychange is not functionally the same as unload, but visibilitychange it is still the event you want to use.

The functional equivalent of the unload event is the pagehide event (which is a bfcache-friendly, drop-in replacement for unload), but even pagehide is not reliable on mobile because there are many, many cases where the pagehide event will not fire.

Here's one specific scenario:

  1. You visit a web page on your phone, read the content, and then decide to leave
  2. Instead of closing the tab (uncommon), you switch apps (extremely common)
  3. Later you decide to close the browser app on your phone

In the above scenario, the pagehide, unload, and beforeunload events will all not fire. However, the visibilitychange 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 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.

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 reason unload 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: the pagehide 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 the unload event. Practically speaking, the visibilitychange 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, is sendBeacon 100% reliable with visibilitychange?

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:

Beacon requests are guaranteed to be initiated before page is unloaded

I would probablhy use the more versatile and battle-tested XMLHttpRequest instead.


@philipwalton 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, is sendBeacon 100% reliable with visibilitychange?

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 the pagehide and unload events may not get dispatched), the visibilitychange 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.

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 :)

Yep :)

If we cannot detect the "unload" event in any meaningful way then the use-case of sendBeacon is kind of lost:

Beacon requests are guaranteed to be initiated before page is unloaded

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:

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 an unload event listener, since the unload 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 the visibilitychange and pagehide events will fire, and in such cases the sendBeacon() method will be far more reliable than the XMLHttpRequest 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 the visibilitychange 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 failing unload 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):

The navigator.sendBeacon() method asynchronously sends a small amount of data over HTTP to a web server. It’s intended to be used in combination with the visibilitychange event (but not with the unload and beforeunload events).

I think recommending visibilitychange and strongly discouraging use of unload 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 for visibilitychange.

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:

From the current revision https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon

The navigator.sendBeacon() method asynchronously sends a small amount of data over HTTP to a web server. It’s intended to be used in combination with the visibilitychange event (but not with the unload and beforeunload events).

I think recommending visibilitychange and strongly discouraging use of unload is fine and would indeed help avoid developers from making this common mistake.

However, I think the current intro is emphasises 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 for visibilitychange.

Anecdotally – Some years ago at Wikipedia we briefly held a campaign to measure when certain attempts to contribute fail to compolete. 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.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/mdn/sprints/issues/3722#issuecomment-700381580, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEDZKGXW7YEZVQWTOKXWLDSIE56ZANCNFSM4RZAHC7Q .


@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.

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.

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 where visibilitychange and pagehide won't fire when closing a tab if an unload or beforeunload event isn't registered), but we definitely cannot recommend using beforeunload and unload 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:

  1. Create a page that informs another tab (or a server) when a "visibilitychange" event occurs
  2. Close that page when it is on the background (behind a new tab)
  3. See that the server or another tab never received the event

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

  1. Close that page when it is on the background (behind a new tab)

If the page was already in the background, then I wouldn't expect the visibilitychange event to fire when the tab is closed because the visibilitychange 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:

  1. 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.

  2. Would XHR work equally with visibilitychange? Does sendBeacon give any added value compared to the more versatile and flexible XHR? What exactly?

  3. 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

  1. 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.

As I mentioned above, you case use a combination of the pagehide and visibilitychange 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.

  1. Would XHR work equally with visibilitychange? Does sendBeacon give any added value compared to the more versatile and flexible XHR? What exactly?

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 the sendBeacon() API likely would.

  1. Is there any evidence on the unload and beforeunload event not fired on browsers where they are supported? Where?

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:

Here's one specific scenario:

  1. You visit a web page on your phone, read the content, and then decide to leave
  2. Instead of closing the tab (uncommon), you switch apps (extremely common)
  3. Later you decide to close the browser app on your phone

In the above scenario, the pagehide, unload, and beforeunload events will all not fire. However, the visibilitychange event will fire after step 2.


@tipiirai commented on Wed Dec 09 2020

pagehide cannot always be reliably fired

Which implies that visibilitychange + pagehide combo is not any better than unload in the page parking scenario.

XHR is not reliable when the page is unloading.

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 the unload event doesn't fire in some cases, especially on mobile. And that visibilitychange 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:


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 what unload 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

the issue you found in your study bears no relation to sendBeacon.

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 good replacement for the "unload" event, […] Hope we get some "reliable-unload" event in the future.

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.

there is no good replacement for the "unload" event, which is the primary use-case for sendBeacon. […] sendBeacon is designed for a use case that doesn't currently exist. […] > Duplicate events, missing events, and not knowing whether the page was unloaded or not.

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 of unload 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 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.

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 calls alert(), or writes to localStorage, 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 ^

Ryuno-Ki commented 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?

wbamberg commented 3 years ago

Closed in favour of https://github.com/mdn/content/issues/2692.