Bug 308073
| Summary: | new WebSocket() hangs at readyState CONNECTING permanently after iOS Safari background resume — HTTP fetch unaffected | ||
|---|---|---|---|
| Product: | WebKit | Reporter: | graham.mickle.dev |
| Component: | Page Loading | Assignee: | Nobody <webkit-unassigned> |
| Status: | NEW | ||
| Severity: | Major | CC: | beidson, cdumez, richardbakker, webkit-bug-importer, youennf |
| Priority: | P2 | Keywords: | InRadar |
| Version: | Safari 26 | ||
| Hardware: | iPhone / iPad | ||
| OS: | iOS 26 | ||
graham.mickle.dev
STEPS TO REPRODUCE
1. Open a page that connects to a WebSocket server on a local network (ws://, not
wss://)
2. Verify the WebSocket connection is established (readyState = 1)
3. Background Safari (switch to another app or go to home screen)
4. Wait 10+ seconds
5. Return to Safari
6. Observe the existing WebSocket close with code 1006 (expected)
7. JavaScript calls `new WebSocket(url)` to reconnect
EXPECTED RESULT
The new WebSocket connection completes — onopen fires, readyState transitions to 1
(OPEN).
ACTUAL RESULT
The WebSocket object stays at readyState 0 (CONNECTING) permanently. onopen,
onerror, and onclose never fire. The connection attempt hangs forever. Every
subsequent `new WebSocket()` call also hangs at CONNECTING.
HTTP fetch() to the same server returns 200 OK immediately, proving the network is
functional. Only the WebSocket subsystem is broken.
The page context is permanently stuck — no amount of retrying will establish a
WebSocket connection. Only a full page reload (window.location.reload() or manual
refresh) restores WebSocket functionality.
COMPARISON WITH OTHER iOS BROWSERS
Tested identically on the same device, same server, same network:
Safari 26.3 / iOS 26.3 — BROKEN
After resume: new WebSocket() → readyState stays 0, no events fire
Every retry hangs. HTTP works fine.
Brave 1.86 (144) / iOS 26.3 (WKWebView) — WORKS
After resume: new WebSocket() → onopen fires, readyState = 1
Reconnects immediately on first attempt.
Chrome, Firefox on the same iPhone also reconnect immediately. Only Safari
exhibits this behavior, which points to Safari's native networking stack (not
WKWebView).
Note: Safari's UA string reports a frozen legacy "iPhone OS 18_7" while Brave
correctly reports "iPhone OS 26_3" for the same device.
DIAGNOSTIC LOG (Safari)
FREEZE detected (32s gap)
handleResume → socket claims OPEN, probing with ping
PING sent (multiple, no PONG returned)
ONERROR: WebSocket connection failed (error)
ONCLOSE code=1006 clean=false
reconnect scheduled in 1000ms
preflight fetch → http://192.168.1.2:8765/api/health
preflight OK (200) ← HTTP works
new WebSocket(ws://192.168.1.2:8765/ws?_t=...)
CONNECT TIMEOUT (5000ms) — readyState still CONNECTING ← WS broken
[repeats indefinitely with unique URLs, every attempt hangs]
DIAGNOSTIC LOG (Brave — same test)
FREEZE detected (18s gap)
ONERROR → ONCLOSE code=1006
reconnect scheduled in 1000ms
new WebSocket(ws://192.168.1.2:8765/ws)
ONOPEN readyState=1 ← immediate success
ADDITIONAL FINDINGS
- Cache-buster query params (unique URL per attempt) do not help
- HTTP preflight fetch before WebSocket does not "wake up" the WS subsystem
- The issue occurs on both ws:// in Safari tab and standalone (Add to Home Screen)
mode
- perMessageDeflate negotiation also has separate issues with Safari (worked
around with perMessageDeflate: false on the ws Node.js server library)
ENVIRONMENT
- iPhone running iOS 26.3, Safari 26.3
- Server: Node.js, ws library v8.18.1, HTTP (not HTTPS), LAN connection
- No reverse proxy or CDN — direct connection from iPhone to server
WORKAROUND
Detect background resume via setInterval timer drift (visibilitychange requires
HTTPS on iOS), then force a full page reload after a short delay. This is the only
reliable workaround found.
RELATED BUGS
- Bug 245350 — WebSocket disconnects when user switches to another app
- Bug 228296 — REGRESSION (iOS 15): WebSocket connection getting closed
| Attachments | ||
|---|---|---|
| Add attachment proposed patch, testcase, etc. |
Radar WebKit Bug Importer
<rdar://problem/170606256>
richardbakker
Another workaround is to setup DNS to map the IP to a hostname and then connect to the url using the hostname instead.
In short: it will fail to connect to websockets when using (LAN) IP's.