Bug 188329

Summary: navigator.sendBeacon does not work in pagehide callbacks
Product: WebKit Reporter: Philip Walton <philip>
Component: WebCore Misc.Assignee: Chris Dumez <cdumez>
Status: RESOLVED FIXED    
Severity: Normal CC: achristensen, bastien.caudan, cdumez, commit-queue, connum+webkitbugzilla, dbates, ews-watchlist, japhet, martin, rniwa, webkit-bug-importer, webkit, youennf
Priority: P2 Keywords: InRadar
Version: Safari Technology Preview   
Hardware: Mac   
OS: macOS 10.13   
See Also: https://bugs.webkit.org/show_bug.cgi?id=161851
Attachments:
Description Flags
Patch none

Description Philip Walton 2018-08-04 09:57:27 PDT
Initial support for navigator.sendBeacon was added here: https://bugs.webkit.org/show_bug.cgi?id=175007,
but if you try to invoke navigator.sendBeacon() in a pagehide event listener, it fails and you get a warning in the console:

> Beacon API cannot load ... due to access control checks

Note that this is the same error you'd get if you tried to do a fetch() or xhr in a pagehide event listener, so I suspect the same rules are being enforced. However, beacons should not be subject to the same restrictions since they're designed to not block or require waiting for a response. (Note, this is also true of any fetch() with the keepalive flag set to true and should be considered when webkit implements keepalive: https://bugs.webkit.org/show_bug.cgi?id=168865).

All other browsers that support the Beacon API or fetch keepalive allow these requests to be sent in the pagehide event listener.
Comment 1 Chris Dumez 2018-08-06 08:47:41 PDT
Likely caused:
    // Prevent new loads if we are in the PageCache or being added to the PageCache.
    // We query the top document because new frames may be created in pagehide event handlers
    // and their pageCacheState will not reflect the fact that they are about to enter page
    // cache.
    if (auto* topDocument = frame.mainFrame().document()) {
        if (topDocument->pageCacheState() != Document::NotInPageCache) {
            RELEASE_LOG_IF_ALLOWED("load: Already in page cache or being added to it (frame = %p)", &frame);
            failBeforeStarting();
            return;
        }
    }

in CachedResource::load(CachedResourceLoader&).

Seems like a bad bug indeed.
Comment 2 Chris Dumez 2018-08-07 16:03:51 PDT
Created attachment 346740 [details]
Patch
Comment 3 WebKit Commit Bot 2018-08-07 18:41:52 PDT
Comment on attachment 346740 [details]
Patch

Clearing flags on attachment: 346740

Committed r234684: <https://trac.webkit.org/changeset/234684>
Comment 4 WebKit Commit Bot 2018-08-07 18:41:54 PDT
All reviewed patches have been landed.  Closing bug.
Comment 5 Radar WebKit Bug Importer 2018-08-07 18:42:19 PDT
<rdar://problem/43030256>
Comment 6 Martin Kuba 2018-10-17 17:38:22 PDT
I am still seeing this in Safari 12.  Has this patch been included in Safari 12?
Comment 7 Chris Dumez 2018-10-17 18:47:58 PDT
(In reply to martin from comment #6)
> I am still seeing this in Safari 12.  Has this patch been included in Safari
> 12?

No, the change missed the cut-off date for iOS 12. It will be fixed in a later release but cannot comment on when.
Comment 8 Ryosuke Niwa 2018-11-12 16:04:47 PST
You can try out Safari Technology Preview, which has this fix: https://developer.apple.com/safari/technology-preview/
Comment 9 Chris Dumez 2019-04-23 09:11:24 PDT
This fix shipped in iOS 12.2 and macOS 10.14.4.
Comment 10 connum+webkitbugzilla 2020-11-13 05:15:12 PST
This was supposed to have been fixed with iOS 12.2, but I'm still experiencing this issue on an iPad (5th generation) with iPadOS 14.0.1. I now updated it to 14.2, but the issue persists.

Here's a very simple test file that should create an empty file with the current timestamp via PHP if the beacon is sent:

<?php
if (isset($_GET['beacon'])) {
    file_put_contents(microtime(true), '');
    exit;
}
?>
<script>
document.addEventListener('visibilitychange', function() {
    console.log(navigator.sendBeacon(window.location.href + '?beacon=1', new ArrayBuffer([])));
});
document.addEventListener('pagehide', function() {
    console.log(navigator.sendBeacon(window.location.href + '?beacon=1', new ArrayBuffer([])));
});
</script>
<a href="../">Link</a>
?>

It works when switching to another tab and back, but not when following a link or closing the tab.
Comment 11 Ryosuke Niwa 2020-11-17 23:32:18 PST
(In reply to connum+webkitbugzilla from comment #10)
> This was supposed to have been fixed with iOS 12.2, but I'm still
> experiencing this issue on an iPad (5th generation) with iPadOS 14.0.1. I
> now updated it to 14.2, but the issue persists.
> 
> Here's a very simple test file that should create an empty file with the
> current timestamp via PHP if the beacon is sent:
> 
> <?php
> if (isset($_GET['beacon'])) {
>     file_put_contents(microtime(true), '');
>     exit;
> }
> ?>
> <script>
> document.addEventListener('visibilitychange', function() {
>     console.log(navigator.sendBeacon(window.location.href + '?beacon=1', new
> ArrayBuffer([])));
> });
> document.addEventListener('pagehide', function() {
>     console.log(navigator.sendBeacon(window.location.href + '?beacon=1', new
> ArrayBuffer([])));
> });
> </script>
> <a href="../">Link</a>
> ?>
> 
> It works when switching to another tab and back, but not when following a
> link or closing the tab.

I think Chris recently fixed a bug that iOS WebKit didn't fire visibilitychange or pagehide event when we navigated to a new page or closed the tab.
Comment 12 Chris Dumez 2020-11-18 07:30:01 PST
(In reply to Ryosuke Niwa from comment #11)
> (In reply to connum+webkitbugzilla from comment #10)
> > This was supposed to have been fixed with iOS 12.2, but I'm still
> > experiencing this issue on an iPad (5th generation) with iPadOS 14.0.1. I
> > now updated it to 14.2, but the issue persists.
> > 
> > Here's a very simple test file that should create an empty file with the
> > current timestamp via PHP if the beacon is sent:
> > 
> > <?php
> > if (isset($_GET['beacon'])) {
> >     file_put_contents(microtime(true), '');
> >     exit;
> > }
> > ?>
> > <script>
> > document.addEventListener('visibilitychange', function() {
> >     console.log(navigator.sendBeacon(window.location.href + '?beacon=1', new
> > ArrayBuffer([])));
> > });
> > document.addEventListener('pagehide', function() {
> >     console.log(navigator.sendBeacon(window.location.href + '?beacon=1', new
> > ArrayBuffer([])));
> > });
> > </script>
> > <a href="../">Link</a>
> > ?>
> > 
> > It works when switching to another tab and back, but not when following a
> > link or closing the tab.
> 
> I think Chris recently fixed a bug that iOS WebKit didn't fire
> visibilitychange or pagehide event when we navigated to a new page or closed
> the tab.

pagehide should reliably fire when navigating away. It was "visibilitychange not firing on navigating away" that I fixed recently.
Comment 13 Daniel Dickison 2020-12-07 16:33:17 PST
FWIW, I'm also seeing sendBeacon fail to send when triggered from a pagehide event listener on iOS 14. I don't see the error message quoted in the original bug report:

> Beacon API cannot load ... due to access control checks

In fact, I don't see anything in the console when navigating away from a page with Preserve Log enabled. I'm only deducing that pagehide is firing by experimenting with https://event-logger.glitch.me

This is with iOS 14.2.1 on an iPhone 12 mini.

Should this be filed as a new bug?
Comment 14 Daniel Dickison 2020-12-08 11:20:12 PST
I apologize, my last comment is inaccurate. I was mislead by the fact the console.log during some page navigation pagehide events don't appear even with Preserve Log enabled, but the beacon requests do go out.

tl;dr: It's working for me on iOS 14.2.1.

(In reply to Daniel Dickison from comment #13)
> FWIW, I'm also seeing sendBeacon fail to send when triggered from a pagehide
> event listener on iOS 14. I don't see the error message quoted in the
> original bug report:
> 
> > Beacon API cannot load ... due to access control checks
> 
> In fact, I don't see anything in the console when navigating away from a
> page with Preserve Log enabled. I'm only deducing that pagehide is firing by
> experimenting with https://event-logger.glitch.me
> 
> This is with iOS 14.2.1 on an iPhone 12 mini.
> 
> Should this be filed as a new bug?