Skip to content

Instantly share code, notes, and snippets.

@mloughran
Created March 16, 2012 19:27
Show Gist options
  • Select an option

  • Save mloughran/2052006 to your computer and use it in GitHub Desktop.

Select an option

Save mloughran/2052006 to your computer and use it in GitHub Desktop.

Revisions

  1. mloughran revised this gist Mar 16, 2012. 2 changed files with 17 additions and 4 deletions.
    14 changes: 11 additions & 3 deletions readme.md
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,10 @@
    The following outlines a crashing bug which affects iOS devices running versions 5.0.x & 5.1 (iPhone & iPad), and a workaround for the crash.
    # Mobile Safari crash when returning to WebSocket page

    The crash occurs due to the order in which WebSocket events are handled by the browser, and that calling `send()` on a closed WebSocket object causes a crash.
    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

    @@ -13,11 +17,15 @@ The crash occurs due to the order in which WebSocket events are handled by the b
    });

    * 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 time out (often this is 1 minute, but it seems to vary)
    * 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
    7 changes: 6 additions & 1 deletion server.rb
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,10 @@
    # This example required em-websocket. Install using
    # This example required em-websocket. Install em-websocket using
    #
    # gem install em-websocket
    #
    # Run this server
    #
    # ruby server.rb

    require 'rubygems'
    require 'em-websocket'
  2. mloughran revised this gist Mar 16, 2012. 2 changed files with 47 additions and 0 deletions.
    27 changes: 27 additions & 0 deletions page.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,27 @@
    <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>
    20 changes: 20 additions & 0 deletions server.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,20 @@
    # This example required em-websocket. Install using
    # gem install em-websocket

    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
  3. Martyn Loughran created this gist Mar 16, 2012.
    37 changes: 37 additions & 0 deletions readme.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,37 @@
    The following outlines a crashing bug which affects iOS devices running versions 5.0.x & 5.1 (iPhone & iPad), and a workaround for the crash.

    The crash occurs due to the order in which WebSocket events are handled by the browser, and that calling `send()` on a closed WebSocket object causes a crash.

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

    * Close the WebSocket connection. You could either kill the server, or wait for the connection to time out (often this is 1 minute, but it seems to vary)

    * Switch back to the page. **Safari will crash**

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