fetchLater not sent on browser shutdown (last tab)

#408010432 Filed 2025-04-02 Status: Assigned P3 / S3

What this tests

fetchLater() is queued on page load. The page also sends sendBeacon() on pagehide and visibilitychange.

bug close the last tab of the last window: old Chromium often exits before the endpoint receives the queued request.

fixed endpoint receives via=fetchLater for the id shown below.

Endpoint

(generated on load)

Local server

Save as /tmp/server.sh, then run chmod +x /tmp/server.sh && /tmp/server.sh.

#!/usr/bin/env bash
set -euo pipefail

DIR=/tmp/i408010432
mkdir -p "$DIR"
cd "$DIR"

cat > log.py <<'PY'
from http.server import HTTPServer, BaseHTTPRequestHandler
import datetime

class H(BaseHTTPRequestHandler):
    def do_GET(self):
        self._log()

    def do_POST(self):
        self._log()

    def do_OPTIONS(self):
        self._cors()
        self.end_headers()

    def _cors(self):
        self.send_response(204)
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET,POST,OPTIONS')
        self.send_header('Access-Control-Allow-Headers', '*')

    def _log(self):
        length = int(self.headers.get('Content-Length') or 0)
        self.rfile.read(length)
        print(f"{datetime.datetime.now().isoformat(timespec='seconds')} "
              f"{self.command} {self.path}", flush=True)
        self._cors()
        self.send_header('Content-Length', '0')
        self.end_headers()

    def log_message(self, *args):
        pass

print('listening on http://127.0.0.1:8787', flush=True)
HTTPServer(('127.0.0.1', 8787), H).serve_forever()
PY

python3 log.py 2>&1 | tee -a requests.log

Real-world Linux test

# launch with the CL enabled
/tmp/server.sh

PROFILE=$(mktemp -d)
out/Default/chrome \
  --user-data-dir="$PROFILE" \
  --no-first-run \
  --enable-features=KeepAliveBrowserProcessAlive \
  --disable-features=LocalNetworkAccessChecks \
  https://static.januschka.com/i-408010432/
# good result
POST /beacon?via=pagehide&id=<id>&persisted=false
POST /beacon?via=visibilitychange&id=<id>
POST /beacon?via=fetchLater&id=<id>

What this page registers

fetchLater(endpoint + "?via=fetchLater&id=" + id, { method: "POST" });

document.addEventListener("visibilitychange", () => {
  if (document.visibilityState === "hidden")
    navigator.sendBeacon(endpoint + "?via=visibilitychange&id=" + id);
});

window.addEventListener("pagehide", (e) =>
  navigator.sendBeacon(endpoint + "?via=pagehide&id=" + id +
                       "&persisted=" + e.persisted)
);

Page log

(waiting...)

Links