Skip to content

Instantly share code, notes, and snippets.

@mloughran
Created March 16, 2012 19:27
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mloughran/2052006 to your computer and use it in GitHub Desktop.
Save mloughran/2052006 to your computer and use it in GitHub Desktop.
Mobile Safari crash when returning to WebSocket page

Mobile Safari crash when returning to WebSocket page

While investigating an issue reported against the Pusher JavaScript library I gathered the following information. Posting in the hope that it can save someone else going through the laborious process I went through!

The crash is 100% reproducible, and affects iOS devices (tested on iPhone & iPad) running versions 5.0.x & 5.1. There is a simple workaround described below, and the code to reproduce the crash.

Summary: calling send() on a closed WebSocket object which reports an open readyState causes a crash. This scenario can occur when returning to a backgrounded page which received data and then closed when in the backgrounded state.

Steps to reproduce

  • Visit a page which connects to a WebSocket server. That page should periodically send out WebSocket messages.

  • In the page bind to onmessage, and send a message back to the server

    ws.onmessage(function(evt) {
      ws.send("foo");
    });
    
  • Switch away from the page (either by changing tabs or backgrounding Safari)

  • The WebSocket connection will stay open for some time. Wait for the server to send a message to the page. You should not receive a reply in the server (since JavaScript is not executing on the backgrounded page)

  • Close the WebSocket connection. You could either kill the server, or wait for the connection to be closed by iOS (often this is 1 minute for background pages, but it seems to vary)

  • Switch back to the page. Safari will crash

This gist includes a simple WebSocket server and web page which you can use. You just need to serve the html page somewhere, and change localhost to your IP address.

Workaround

Call ws.send in a setTimeout. When you do this the readyState will be correctly set to closed, and Safari won't crash

ws.onmessage(function(evt) {
  setTimeout(function() {
    ws.send("foo");
  });
});

Extra notes

When the onmessage event is called, the readyState is still 1 (open), even at this point in time the WebSocket is in fact closed.

The onclose event is fired after the onmessage events, so you can't manage your own connected state.

The issue also affects earlier iOS versions, but you have to background Safari since JavaScript continues to execute in background tabs.

<html>
<head>
<script>
function debug(string) {
var element = document.getElementById("debug");
var p = document.createElement("p");
p.appendChild(document.createTextNode(string));
element.appendChild(p);
}
var ws = new WebSocket("ws://localhost:8080/");
ws.onopen = function() {
debug("WebSocket open")
};
ws.onmessage = function(event) {
debug("WebSocket open, readyState = " + ws.readyState)
ws.send("foo")
};
ws.onclose = function(event) {
debug("WebSocket closed")
}
</script>
</head>
<body>
<div id="debug"></div>
</body>
</html>
# This example required em-websocket. Install em-websocket using
#
# gem install em-websocket
#
# Run this server
#
# ruby server.rb
require 'rubygems'
require 'em-websocket'
EM::WebSocket.start(:host => "0.0.0.0", :port => 8080, :debug => true) do |ws|
ws.onopen {
puts "WebSocket open"
EM.add_periodic_timer(5) {
ws.send('bar')
}
}
ws.onmessage { |msg|
puts "Received #{msg}"
}
ws.onclose {
puts "WebSocket closed"
}
end
@mloughran
Copy link
Author

Posted to the WebKit tracker: https://bugs.webkit.org/show_bug.cgi?id=81517

@mloughran
Copy link
Author

@jesalg
Copy link

jesalg commented Apr 1, 2013

I see a fix for this has been added to the Pusher client library but I'm still able to recreate this bug using Pusher on http://www.ruddl.com - If you switch to another section from the drop-down on the top right, it will crash on iOS/Safari. Any further ideas on how I can fix it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment