Bug 719609 - Part 1: Add some basic unit tests for SPDY. r=mcmanus,ted
authorNick Hurley <hurley@todesschaf.org>
Wed, 25 Apr 2012 20:12:33 -0400
changeset 92455 301bf79e90299c12975096e22d0f891c2e64f304
parent 92454 adea606b56942b2a7d5ad218daaa31ed75ee72e5
child 92456 d0d3c5935c19370409814184981c948d013a102f
push idunknown
push userunknown
push dateunknown
reviewersmcmanus, ted
bugs719609
milestone15.0a1
Bug 719609 - Part 1: Add some basic unit tests for SPDY. r=mcmanus,ted
netwerk/test/unit/test_spdy.js
netwerk/test/unit/xpcshell.ini
testing/xpcshell/Makefile.in
testing/xpcshell/moz-spdy/README.txt
testing/xpcshell/moz-spdy/moz-spdy.js
testing/xpcshell/moz-spdy/spdy-ca.pem
testing/xpcshell/moz-spdy/spdy-cert.pem
testing/xpcshell/moz-spdy/spdy-key.pem
testing/xpcshell/node-spdy/README.md
testing/xpcshell/node-spdy/examples/twitlog/app.js
testing/xpcshell/node-spdy/examples/twitlog/autorestart.js
testing/xpcshell/node-spdy/examples/twitlog/keys/twitlog-cert.pem
testing/xpcshell/node-spdy/examples/twitlog/keys/twitlog-csr.pem
testing/xpcshell/node-spdy/examples/twitlog/keys/twitlog-key.pem
testing/xpcshell/node-spdy/examples/twitlog/keys/twtilog-ca.pem
testing/xpcshell/node-spdy/examples/twitlog/package.json
testing/xpcshell/node-spdy/examples/twitlog/public/favicon.png
testing/xpcshell/node-spdy/examples/twitlog/public/images/nodejs.png
testing/xpcshell/node-spdy/examples/twitlog/public/javascripts/main.js
testing/xpcshell/node-spdy/examples/twitlog/public/javascripts/socket.io.min.js
testing/xpcshell/node-spdy/examples/twitlog/public/stylesheets/bootstrap.min.css
testing/xpcshell/node-spdy/examples/twitlog/public/stylesheets/style.css
testing/xpcshell/node-spdy/examples/twitlog/realtime/index.js
testing/xpcshell/node-spdy/examples/twitlog/routes/index.js
testing/xpcshell/node-spdy/examples/twitlog/views/index.jade
testing/xpcshell/node-spdy/examples/twitlog/views/layout.jade
testing/xpcshell/node-spdy/keys/spdy-cert.pem
testing/xpcshell/node-spdy/keys/spdy-csr.pem
testing/xpcshell/node-spdy/keys/spdy-key.pem
testing/xpcshell/node-spdy/lib/spdy.js
testing/xpcshell/node-spdy/lib/spdy/parser.js
testing/xpcshell/node-spdy/lib/spdy/protocol/generic.js
testing/xpcshell/node-spdy/lib/spdy/protocol/v2/framer.js
testing/xpcshell/node-spdy/lib/spdy/protocol/v2/index.js
testing/xpcshell/node-spdy/lib/spdy/protocol/v2/protocol.js
testing/xpcshell/node-spdy/lib/spdy/response.js
testing/xpcshell/node-spdy/lib/spdy/scheduler.js
testing/xpcshell/node-spdy/lib/spdy/server.js
testing/xpcshell/node-spdy/lib/spdy/utils.js
testing/xpcshell/node-spdy/lib/spdy/zlib-pool.js
testing/xpcshell/node-spdy/package.json
testing/xpcshell/node-spdy/test/benchmarks/syn.js
testing/xpcshell/node-spdy/test/fixtures/frames.js
testing/xpcshell/node-spdy/test/fixtures/keys.js
testing/xpcshell/node-spdy/test/unit/framer-test.js
testing/xpcshell/node-spdy/test/unit/parser-test.js
testing/xpcshell/node-spdy/test/unit/server-test.js
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_spdy.js
@@ -0,0 +1,344 @@
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+// Generate a small and a large post with known pre-calculated md5 sums
+function generateContent(size) {
+  var content = "";
+  for (var i = 0; i < size; i++) {
+    content += "0";
+  }
+  return content;
+}
+
+var posts = [];
+posts.push(generateContent(10));
+posts.push(generateContent(128 * 1024));
+
+// pre-calculated md5sums (in hex) of the above posts
+var md5s = ['f1b708bba17f1ce948dc979f4d7092bc',
+            '8f607cfdd2c87d6a7eedb657dafbd836'];
+
+function checkIsSpdy(request) {
+  try {
+    if (request.getResponseHeader("X-Firefox-Spdy") == "1") {
+      if (request.getResponseHeader("X-Connection-Spdy") == "yes") {
+        return true;
+      }
+      return false; // Weird case, but the server disagrees with us
+    }
+  } catch (e) {
+    // Nothing to do here
+  }
+  return false;
+}
+
+var SpdyCheckListener = function() {};
+
+SpdyCheckListener.prototype = {
+  onStartRequestFired: false,
+  onDataAvailableFired: false,
+  isSpdyConnection: false,
+
+  onStartRequest: function testOnStartRequest(request, ctx) {
+    this.onStartRequestFired = true;
+
+    if (!Components.isSuccessCode(request.status))
+      do_throw("Channel should have a success code! (" + request.status + ")");
+    if (!(request instanceof Components.interfaces.nsIHttpChannel))
+      do_throw("Expecting an HTTP channel");
+
+    do_check_eq(request.responseStatus, 200);
+    do_check_eq(request.requestSucceeded, true);
+  },
+
+  onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
+    this.onDataAvailableFired = true;
+    this.isSpdyConnection = checkIsSpdy(request);
+
+    read_stream(stream, cnt);
+  },
+
+  onStopRequest: function testOnStopRequest(request, ctx, status) {
+    do_check_true(this.onStartRequestFired);
+    do_check_true(this.onDataAvailableFired);
+    do_check_true(this.isSpdyConnection);
+
+    run_next_test();
+    do_test_finished();
+  }
+};
+
+/*
+ * Support for testing valid multiplexing of streams
+ */
+
+var multiplexContent = generateContent(30*1024);
+var completed_channels = [];
+function register_completed_channel(listener) {
+  completed_channels.push(listener);
+  if (completed_channels.length == 2) {
+    do_check_neq(completed_channels[0].streamID, completed_channels[1].streamID);
+    run_next_test();
+    do_test_finished();
+  }
+}
+
+/* Listener class to control the testing of multiplexing */
+var SpdyMultiplexListener = function() {};
+
+SpdyMultiplexListener.prototype = new SpdyCheckListener();
+
+SpdyMultiplexListener.prototype.streamID = 0;
+SpdyMultiplexListener.prototype.buffer = "";
+
+SpdyMultiplexListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
+  this.onDataAvailableFired = true;
+  this.isSpdyConnection = checkIsSpdy(request);
+  this.streamID = parseInt(request.getResponseHeader("X-Spdy-StreamID"));
+  var data = read_stream(stream, cnt);
+  this.buffer = this.buffer.concat(data);
+};
+
+SpdyMultiplexListener.prototype.onStopRequest = function(request, ctx, status) {
+  do_check_true(this.onStartRequestFired);
+  do_check_true(this.onDataAvailableFired);
+  do_check_true(this.isSpdyConnection);
+  do_check_true(this.buffer == multiplexContent);
+  
+  // This is what does most of the hard work for us
+  register_completed_channel(this);
+};
+
+// Does the appropriate checks for header gatewaying
+var SpdyHeaderListener = function(value) {
+  this.value = value
+};
+
+SpdyHeaderListener.prototype = new SpdyCheckListener();
+SpdyHeaderListener.prototype.value = "";
+
+SpdyHeaderListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
+  this.onDataAvailableFired = true;
+  this.isSpdyConnection = checkIsSpdy(request);
+  do_check_eq(request.getResponseHeader("X-Received-Test-Header"), this.value);
+  read_stream(stream, cnt);
+};
+
+// Does the appropriate checks for a large GET response
+var SpdyBigListener = function() {};
+
+SpdyBigListener.prototype = new SpdyCheckListener();
+SpdyBigListener.prototype.buffer = "";
+
+SpdyBigListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
+  this.onDataAvailableFired = true;
+  this.isSpdyConnection = checkIsSpdy(request);
+  this.buffer = this.buffer.concat(read_stream(stream, cnt));
+  // We know the server should send us the same data as our big post will be,
+  // so the md5 should be the same
+  do_check_eq(md5s[1], request.getResponseHeader("X-Expected-MD5"));
+};
+
+SpdyBigListener.prototype.onStopRequest = function(request, ctx, status) {
+  do_check_true(this.onStartRequestFired);
+  do_check_true(this.onDataAvailableFired);
+  do_check_true(this.isSpdyConnection);
+
+  // Don't want to flood output, so don't use do_check_eq
+  do_check_true(this.buffer == posts[1]);
+
+  run_next_test();
+  do_test_finished();
+};
+
+// Does the appropriate checks for POSTs
+var SpdyPostListener = function(expected_md5) {
+  this.expected_md5 = expected_md5;
+};
+
+SpdyPostListener.prototype = new SpdyCheckListener();
+SpdyPostListener.prototype.expected_md5 = "";
+
+SpdyPostListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
+  this.onDataAvailableFired = true;
+  this.isSpdyConnection = checkIsSpdy(request);
+  read_stream(stream, cnt);
+  do_check_eq(this.expected_md5, request.getResponseHeader("X-Calculated-MD5"));
+};
+
+function makeChan(url) {
+  var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+  var chan = ios.newChannel(url, null, null).QueryInterface(Ci.nsIHttpChannel);
+
+  return chan;
+}
+
+// Make sure we make a spdy connection and both us and the server mark it as such
+function test_spdy_basic() {
+  var chan = makeChan("https://localhost:4443/");
+  var listener = new SpdyCheckListener();
+  chan.asyncOpen(listener, null);
+}
+
+// Support for making sure XHR works over SPDY
+function checkXhr(xhr) {
+  if (xhr.readyState != 4) {
+    return;
+  }
+
+  do_check_eq(xhr.status, 200);
+  do_check_eq(checkIsSpdy(xhr), true);
+  run_next_test();
+  do_test_finished();
+}
+
+// Fires off an XHR request over SPDY
+function test_spdy_xhr() {
+  var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+            .createInstance(Ci.nsIXMLHttpRequest);
+  req.open("GET", "https://localhost:4443/", true);
+  req.addEventListener("readystatechange", function (evt) { checkXhr(req); },
+                       false);
+  req.send(null);
+}
+
+// Test to make sure we get multiplexing right
+function test_spdy_multiplex() {
+  var chan1 = makeChan("https://localhost:4443/multiplex1");
+  var chan2 = makeChan("https://localhost:4443/multiplex2");
+  var listener1 = new SpdyMultiplexListener();
+  var listener2 = new SpdyMultiplexListener();
+  chan1.asyncOpen(listener1, null);
+  chan2.asyncOpen(listener2, null);
+}
+
+// Test to make sure we gateway non-standard headers properly
+function test_spdy_header() {
+  var chan = makeChan("https://localhost:4443/header");
+  var hvalue = "Headers are fun";
+  var listener = new SpdyHeaderListener(hvalue);
+  chan.setRequestHeader("X-Test-Header", hvalue, false);
+  chan.asyncOpen(listener, null);
+}
+
+// Make sure we handle GETs that cover more than 2 frames properly
+function test_spdy_big() {
+  var chan = makeChan("https://localhost:4443/big");
+  var listener = new SpdyBigListener();
+  chan.asyncOpen(listener, null);
+}
+
+// Support for doing a POST
+function do_post(content, chan, listener) {
+  var stream = Cc["@mozilla.org/io/string-input-stream;1"]
+               .createInstance(Ci.nsIStringInputStream);
+  stream.data = content;
+
+  var uchan = chan.QueryInterface(Ci.nsIUploadChannel);
+  uchan.setUploadStream(stream, "text/plain", stream.available());
+
+  chan.requestMethod = "POST";
+
+  chan.asyncOpen(listener, null);
+}
+
+// Make sure we can do a simple POST
+function test_spdy_post() {
+  var chan = makeChan("https://localhost:4443/post");
+  var listener = new SpdyPostListener(md5s[0]);
+  do_post(posts[0], chan, listener);
+}
+
+// Make sure we can do a POST that covers more than 2 frames
+function test_spdy_post_big() {
+  var chan = makeChan("https://localhost:4443/post");
+  var listener = new SpdyPostListener(md5s[1]);
+  do_post(posts[1], chan, listener);
+}
+
+var tests = [ test_spdy_basic
+            , test_spdy_xhr
+            , test_spdy_multiplex
+            , test_spdy_header
+            , test_spdy_big
+            , test_spdy_post
+            , test_spdy_post_big
+            ];
+var current_test = 0;
+
+function run_next_test() {
+  if (current_test < tests.length) {
+    tests[current_test]();
+    current_test++;
+    do_test_pending();
+  }
+}
+
+// Support for making sure we can talk to the invalid cert the server presents
+var CertOverrideListener = function(host, port, bits) {
+  this.host = host;
+  if (port) {
+    this.port = port;
+  }
+  this.bits = bits;
+};
+
+CertOverrideListener.prototype = {
+  host: null,
+  port: -1,
+  bits: null,
+
+  getInterface: function(aIID) {
+    return this.QueryInterface(aIID);
+  },
+
+  QueryInterface: function(aIID) {
+    if (aIID.equals(Ci.nsIBadCertListener2) ||
+        aIID.equals(Ci.nsIInterfaceRequestor) ||
+        aIID.equals(Ci.nsISupports))
+      return this;
+    throw Components.results.NS_ERROR_NO_INTERFACE;
+  },
+
+  notifyCertProblem: function(socketInfo, sslStatus, targetHost) {
+    var cert = sslStatus.QueryInterface(Ci.nsISSLStatus).serverCert;
+    var cos = Cc["@mozilla.org/security/certoverride;1"].
+              getService(Ci.nsICertOverrideService);
+    cos.rememberValidityOverride(this.host, this.port, cert, this.bits, false);
+    return true;
+  },
+};
+
+function addCertOverride(host, port, bits) {
+  var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+            .createInstance(Ci.nsIXMLHttpRequest);
+  try {
+    var url;
+    if (port) {
+      url = "https://" + host + ":" + port + "/";
+    } else {
+      url = "https://" + host + "/";
+    }
+    req.open("GET", url, false);
+    req.channel.notificationCallbacks = new CertOverrideListener(host, port, bits);
+    req.send(null);
+  } catch (e) {
+    // This will fail since the server is not trusted yet
+  }
+}
+
+function run_test() {
+  // Set to allow the cert presented by our SPDY server
+  do_get_profile();
+  addCertOverride("localhost", 4443,
+                  Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+                  Ci.nsICertOverrideService.ERROR_MISMATCH |
+                  Ci.nsICertOverrideService.ERROR_TIME);
+
+  // Make sure spdy is enabled
+  var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+  prefs.setBoolPref("network.http.spdy.enabled", true);
+
+  // And make go!
+  run_next_test();
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -169,16 +169,19 @@ skip-if = os == "win"
 [test_resumable_channel.js]
 [test_resumable_truncate.js]
 [test_safeoutputstream.js]
 [test_simple.js]
 [test_sockettransportsvc_available.js]
 [test_socks.js]
 # Bug 675039: test hangs consistently on Android
 skip-if = os == "android"
+[test_spdy.js]
+# Bug 719609: this test has particular requirements
+skip-if = true
 [test_speculative_connect.js]
 [test_standardurl.js]
 [test_standardurl_port.js]
 [test_streamcopier.js]
 [test_traceable_channel.js]
 [test_unescapestring.js]
 [test_xmlhttprequest.js]
 [test_XHR_redirects.js]
--- a/testing/xpcshell/Makefile.in
+++ b/testing/xpcshell/Makefile.in
@@ -51,16 +51,18 @@ TEST_DIRS += example
 
 include $(topsrcdir)/config/rules.mk
 
 # Harness files from the srcdir
 TEST_HARNESS_FILES := \
   runxpcshelltests.py \
   remotexpcshelltests.py \
   head.js \
+  node-spdy \
+  moz-spdy \
   $(NULL)
 
 # Extra files needed from $(topsrcdir)/build
 EXTRA_BUILD_FILES := \
   automationutils.py \
   manifestparser.py \
   $(NULL)
 
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/moz-spdy/README.txt
@@ -0,0 +1,14 @@
+Test server for SPDY unit tests. To run it, you need node >= 0.7.0 (not provided)
+and node-spdy (provided). Just run
+
+node /path/to/moz-spdy.js
+
+And you will get a SPDY server listening on port 4443, then you can run the
+xpcshell unit tests in netwerk/test/unit/test_spdy.js
+
+*** A NOTE ON TLS CERTIFICATES ***
+
+The certificates used for this test (*.pem in this directory) are the ones
+provided as examples by node-spdy, and are copied directly from keys/ under
+its top-level source directory (slightly renamed to match the option names
+in the options dictionary passed to spdy.createServer).
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/moz-spdy/moz-spdy.js
@@ -0,0 +1,145 @@
+var spdy = require('../node-spdy/lib/spdy.js');
+var fs = require('fs');
+var url = require('url');
+var crypto = require('crypto');
+
+function getHttpContent(path) {
+  var content = '<!doctype html>' +
+                '<html>' +
+                '<head><title>HOORAY!</title></head>' +
+                '<body>You Win! (by requesting' + path + ')</body>' +
+                '</html>';
+  return content;
+}
+
+function getHugeContent(size) {
+  var content = '';
+
+  for (var i = 0; i < size; i++) {
+    content += '0';
+  }
+
+  return content;
+}
+
+/* This takes care of responding to the multiplexed request for us */
+var Multiplex = function() {};
+
+Multiplex.prototype = {
+  mp1res: null,
+  mp2res: null,
+  buf: null,
+  mp1start: 0,
+  mp2start: 0,
+
+  checkReady: function() {
+    if (this.mp1res != null && this.mp2res != null) {
+      this.buf = getHugeContent(30*1024);
+      this.mp1start = 0;
+      this.mp2start = 0;
+      this.send(this.mp1res, 0);
+      setTimeout(function() { this.send(this.mp2res, 0); }.bind(this), 5);
+    }
+  },
+
+  send: function(res, start) {
+    var end = start + 1024;
+    if (end > this.buf.length)
+      end = this.buf.length;
+    var content = this.buf.substring(start, end);
+    if (end < this.buf.length) {
+      res.write(content);
+      setTimeout(function() { this.send(res, end); }.bind(this), 10);
+    } else {
+      res.end(content);
+    }
+  },
+};
+
+var m = new Multiplex();
+
+var post_hash = null;
+function receivePostData(chunk) {
+  post_hash.update(chunk.toString());
+}
+
+function finishPost(res, content) {
+  var md5 = post_hash.digest('hex');
+  res.setHeader('X-Calculated-MD5', md5);
+  res.writeHead(200);
+  res.end(content);
+}
+
+function handleRequest(req, res) {
+  var u = url.parse(req.url);
+  var content = getHttpContent(u.pathname);
+
+  if (req.streamID) {
+    res.setHeader('X-Connection-Spdy', 'yes');
+    res.setHeader('X-Spdy-StreamId', '' + req.streamID);
+  } else {
+    res.setHeader('X-Connection-Spdy', 'no');
+  }
+
+  if (u.pathname == '/exit') {
+    res.setHeader('Content-Type', 'text/plain');
+    res.writeHead(200);
+    res.end('ok');
+    process.exit();
+  } else if (u.pathname == '/multiplex1' && req.streamID) {
+    res.setHeader('Content-Type', 'text/plain');
+    res.writeHead(200);
+    m.mp1res = res;
+    m.checkReady();
+    return;
+  } else if (u.pathname == '/multiplex2' && req.streamID) {
+    res.setHeader('Content-Type', 'text/plain');
+    res.writeHead(200);
+    m.mp2res = res;
+    m.checkReady();
+    return;
+  } else if (u.pathname == "/header") {
+    var val = req.headers["x-test-header"];
+    if (val) {
+      res.setHeader("X-Received-Test-Header", val);
+    }
+  } else if (u.pathname == "/big") {
+    content = getHugeContent(128 * 1024);
+    var hash = crypto.createHash('md5');
+    hash.update(content);
+    var md5 = hash.digest('hex');
+    res.setHeader("X-Expected-MD5", md5);
+  } else if (u.pathname == "/post") {
+    if (req.method != "POST") {
+      res.writeHead(405);
+      res.end('Unexpected method: ' + req.method);
+      return;
+    }
+
+    post_hash = crypto.createHash('md5');
+    req.on('data', receivePostData);
+    req.on('end', function () { finishPost(res, content); });
+
+    return;
+  }
+
+  res.setHeader('Content-Type', 'text/html');
+  res.writeHead(200);
+  res.end(content);
+}
+
+// Set up the SSL certs for our server
+var options = {
+  key: fs.readFileSync(__dirname + '/spdy-key.pem'),
+  cert: fs.readFileSync(__dirname + '/spdy-cert.pem'),
+  ca: fs.readFileSync(__dirname + '/spdy-ca.pem'),
+};
+
+spdy.createServer(options, handleRequest).listen(4443);
+console.log('SPDY server listening on port 4443');
+
+// Set up to exit when the user finishes our stdin
+process.stdin.resume();
+process.stdin.on('end', function () {
+  process.exit();
+});
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/moz-spdy/spdy-ca.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBkzCB/QIBADBUMQswCQYDVQQGEwJSVTETMBEGA1UECBMKU29tZS1TdGF0ZTEN
+MAsGA1UEBxMET21zazEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF
+3/Gnld/wcOE9J/M01y0RFiyVdPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+je
+i+mA1ZqyKXnMANZozVPSedXRGwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+
+A3vk+3j3hjJjIm79rwIDAQABoAAwDQYJKoZIhvcNAQEFBQADgYEAiNWhz6EppIVa
+FfUaB3sLeqfamb9tg9kBHtvqj/FJni0snqms0kPWaTySEPHZF0irIb7VVdq/sVCb
+3gseMVSyoDvPJ4lHC3PXqGQ7kM1mIPhDnR/4HDA3BhlGhTXSDIHgZnvI+HMBdsyC
+hC3dz5odyKqe4nmoofomALkBL9t4H8s=
+-----END CERTIFICATE REQUEST-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/moz-spdy/spdy-cert.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICHzCCAYgCCQCPPSUAa8QZojANBgkqhkiG9w0BAQUFADBUMQswCQYDVQQGEwJS
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTENMAsGA1UEBxMET21zazEhMB8GA1UEChMY
+SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTExMDQwOTEwMDY0NVoXDTExMDUw
+OTEwMDY0NVowVDELMAkGA1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUxDTAL
+BgNVBAcTBE9tc2sxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCB
+nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1bn25sPkv46wl70BffxradlkRd/x
+p5Xf8HDhPSfzNNctERYslXT2fX7Dmfd5w1XTVqqGqJ4izp5VewoVOHA8uavo3ovp
+gNWasil5zADWaM1T0nnV0RsFbZWzOTmm1U3D48K8rW3F5kOZ6f4yRq9QT1gF/gN7
+5Pt494YyYyJu/a8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQBuRZisIViI2G/R+w79
+vk21TzC/cJ+O7tKsseDqotXYTH8SuimEH5IWcXNgnWhNzczwN8s2362NixyvCipV
+yd4wzMpPbjIhnWGM0hluWZiK2RxfcqimIBjDParTv6CMUIuwGQ257THKY8hXGg7j
+Uws6Lif3P9UbsuRiYPxMgg98wg==
+-----END CERTIFICATE-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/moz-spdy/spdy-key.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF3/Gnld/wcOE9J/M01y0RFiyV
+dPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+jei+mA1ZqyKXnMANZozVPSedXR
+GwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+A3vk+3j3hjJjIm79rwIDAQAB
+AoGAAv2QI9h32epQND9TxwSCKD//dC7W/cZOFNovfKCTeZjNK6EIzKqPTGA6smvR
+C1enFl5adf+IcyWqAoe4lkqTvurIj+2EhtXdQ8DBlVuXKr3xvEFdYxXPautdTCF6
+KbXEyS/s1TZCRFjYftvCrXxc3pK45AQX/wg7z1K+YB5pyIECQQD0OJvLoxLYoXAc
+FZraIOZiDsEbGuSHqoCReFXH75EC3+XGYkH2bQ/nSIZ0h1buuwQ/ylKXOlTPT3Qt
+Xm1OQEBvAkEA4AjWsIO/rRpOm/Q2aCrynWMpoUXTZSbL2yGf8pxp/+8r2br5ier0
+M1LeBb/OPY1+k39NWLXxQoo64xoSFYk2wQJAd2wDCwX4HkR7HNCXw1hZL9QFK6rv
+20NN0VSlpboJD/3KT0MW/FiCcVduoCbaJK0Au+zEjDyy4hj5N4I4Mw6KMwJAXVAx
+I+psTsxzS4/njXG+BgIEl/C+gRYsuMQDnAi8OebDq/et8l0Tg8ETSu++FnM18neG
+ntmBeMacinUUbTXuwQJBAJp/onZdsMzeVulsGrqR1uS+Lpjc5Q1gt5ttt2cxj91D
+rio48C/ZvWuKNE8EYj2ALtghcVKRvgaWfOxt2GPguGg=
+-----END RSA PRIVATE KEY-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/README.md
@@ -0,0 +1,132 @@
+# SPDY Server for node.js [![Build Status](https://secure.travis-ci.org/indutny/node-spdy.png)](http://travis-ci.org/indutny/node-spdy)
+
+With this module you can create [SPDY](http://www.chromium.org/spdy) servers
+in node.js with natural http module interface and fallback to regular https
+(for browsers that doesn't support SPDY yet).
+
+## Node+OpenSSL building
+
+At the moment node-spdy requires zlib dictionary support, which will come to
+node.js only in 0.7.x version. To build 0.7.x version follow instructions below:
+
+```bash
+git clone git://github.com/joyent/node.git
+cd node
+./configure --prefix=$HOME/.node/dev # <- or any other dir
+
+make install -j4 # in -jN, N is number of CPU cores on your machine
+
+# Add node's bin to PATH env variable
+echo 'export PATH=$HOME/.node/dev/bin:$PATH' >> ~/.bashrc
+
+#
+# You have working node 0.7.x + NPN now !!!
+#
+```
+
+## Usage
+
+```javascript
+var spdy = require('spdy');
+
+var options = {
+  key: fs.readFileSync(__dirname + '/keys/spdy-key.pem'),
+  cert: fs.readFileSync(__dirname + '/keys/spdy-cert.pem'),
+  ca: fs.readFileSync(__dirname + '/keys/spdy-csr.pem')
+};
+
+spdy.createServer(options, function(req, res) {
+  res.writeHead(200);
+  res.end('hello world!');
+});
+
+spdy.listen(443);
+```
+
+## API
+
+API is compatible with `http` and `https` module, but you can use another
+function as base class for SPDYServer. For example,
+`require('express').HTTPSServer` given that as base class you'll get a server
+compatible with [express](https://github.com/visionmedia/express) API.
+
+```javascript
+spdy.createServer(
+  [base class constructor, i.e. https.Server or express.HTTPSServer],
+  { /* keys and options */ }, // <- the only one required argument
+  [request listener]
+).listen([port], [host], [callback]);
+```
+
+Request listener will receive two arguments: `request` and `response`. They're
+both instances of `http`'s `IncomingMessage` and `OutgoingMessage`. But two
+custom properties are added to both of them: `streamID` and `isSpdy`. The first
+one indicates on which spdy stream are sitting request and response. Latter one
+is always true and can be checked to ensure that incoming request wasn't
+received by HTTPS callback.
+
+### Push streams
+
+It's possible to initiate 'push' stream to send content to client, before one'll
+request it.
+
+```javascript
+spdy.createServer(options, function(req, res) {
+  var headers = { 'content-type': 'application/javascript' };
+  res.send('/main.js', headers, function(err, stream) {
+    if (err) return;
+
+    stream.end('alert("hello from push stream!");');
+  });
+
+  res.end('<script src="/main.js"></script>');
+}).listen(443);
+```
+
+`.push('full or relative url', { ... headers ... }, callback)`
+
+You can use either full ( `http://host/path` ) or relative ( `/path` ) urls with
+`.push()`. `headers` are the same as for regular response object. `callback`
+will receive two arguments: `err` (if any error is happened) and `stream`
+(stream object have API compatible with a
+[net.Socket](http://nodejs.org/docs/latest/api/net.html#net.Socket) ).
+
+### Options
+
+All options supported by
+[tls](http://nodejs.org/docs/latest/api/tls.html#tls.createServer) are working
+with node-spdy. In addition, `maxStreams` options is available. it allows you
+controlling [maximum concurrent streams][http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2#TOC-SETTINGS]
+protocol option (if client will start more streams than that limit, RST_STREAM
+will be sent for each additional stream).
+
+#### Contributors
+
+* [Fedor Indutny](https://github.com/indutny)
+* [Chris Storm](https://github.com/eee-c)
+* [Fran├žois de Metz](https://github.com/francois2metz)
+
+#### LICENSE
+
+This software is licensed under the MIT License.
+
+Copyright Fedor Indutny, 2012.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+USE OR OTHER DEALINGS IN THE SOFTWARE.
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/app.js
@@ -0,0 +1,64 @@
+/**
+ * Module dependencies.
+ */
+
+var fs = require('fs'),
+    io = require('socket.io'),
+    express = require('express'),
+    spdy = require('../../');
+    routes = require('./routes');
+    realtime = require('./realtime');
+
+var options = {
+  key: fs.readFileSync(__dirname + '/keys/twitlog-key.pem'),
+  cert: fs.readFileSync(__dirname + '/keys/twitlog-cert.pem'),
+  ca: fs.readFileSync(__dirname + '/keys/twitlog-csr.pem'),
+  ciphers: '!aNULL:!ADH:!eNull:!LOW:!EXP:RC4+RSA:MEDIUM:HIGH',
+  maxStreams: 15
+};
+
+var app = module.exports = spdy.createServer(express.HTTPSServer, options);
+
+io = io.listen(app, { log: false });
+io.set('transports', ['xhr-polling', 'jsonp-polling']);
+
+// Configuration
+
+app.configure(function(){
+  app.set('views', __dirname + '/views');
+  app.set('view engine', 'jade');
+  app.use(express.bodyParser());
+  app.use(express.methodOverride());
+  app.use(app.router);
+  app.use(express.staticCache());
+  app.use(express.static(__dirname + '/public'));
+});
+
+app.configure('development', function(){
+  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
+});
+
+app.configure('production', function(){
+  app.use(express.errorHandler());
+});
+
+// Routes
+
+app.get('/', routes.index);
+
+// Socket.io
+
+realtime.init(io);
+
+app.listen(8081);
+console.log(
+  'Express server listening on port %d in %s mode',
+  app.address().port,
+  app.settings.env
+);
+
+// Do not fail on exceptions
+
+process.on('uncaughtException', function(err) {
+  console.error(err.stack);
+});
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/autorestart.js
@@ -0,0 +1,15 @@
+var path = require('path'),
+    spawn = require('child_process').spawn;
+
+function start() {
+  var child = spawn(process.execPath, [path.resolve(__dirname, 'app.js')]);
+  process.stderr.write('started child with pid: ' + child.pid + '\n');
+
+  child.on('exit', function(code) {
+    process.stderr.write('child exit: ' + code + '!\n');
+    setTimeout(start, 100);
+  });
+  child.stdout.pipe(process.stdout);
+  child.stderr.pipe(process.stderr);
+}
+start();
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/keys/twitlog-cert.pem
@@ -0,0 +1,41 @@
+-----BEGIN CERTIFICATE-----
+MIIHNDCCBhygAwIBAgIDBRVaMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJ
+TDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0
+YWwgQ2VydGlmaWNhdGUgU2lnbmluZzE4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3Mg
+MSBQcmltYXJ5IEludGVybWVkaWF0ZSBTZXJ2ZXIgQ0EwHhcNMTIwMTA2MTAyMDQz
+WhcNMTMwMTA2MDc1NzQwWjBzMRkwFwYDVQQNExBqdDVKVUE2NHlXaTg4T2k3MQsw
+CQYDVQQGEwJSVTEhMB8GA1UEAxMYc3BkeS10d2l0bG9nLmluZHV0bnkuY29tMSYw
+JAYJKoZIhvcNAQkBFhdmZWRvci5pbmR1dG55QGdtYWlsLmNvbTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBANgiMg1hIHoxk1AuMouaERtSv8g/J+JbTgAe
+lD5kF7hGpQOB+qGLuCU+7QovsjftKee7aXXNduQDkzkCD7wg7C9m3QHJ+YXDk7vE
+IycgK6XIK7G1kAfMXfJm6D2gRD3JjM4fWRY/MYTxSvLnqDoAgHIrCBrwauNNuHdl
+4x5LclRKAWAl1sixQgdyw/CzLjuFJFgmRn1Oc1T/lwgzwcXcw0tBvTQtTdvaogvJ
++cqT+TrHVm+w+BRfp+CT9slZp+4b9JQTD5u4/0k3RoxbUBHvKRJ9FKOsq/GFNdlj
+KWMIfQ8ZdHJDKWVX8zlDydhpMTpofaynvdmSPurMcFNGGmYEmkUCAwEAAaOCA7Uw
+ggOxMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgOoMBMGA1UdJQQMMAoGCCsGAQUFBwMB
+MB0GA1UdDgQWBBTy7FYuO1wZ1yws0eNEAI2CVcS5yzAfBgNVHSMEGDAWgBTrQjTQ
+mLCrn/Qbawj3zGQu7w4sRTAwBgNVHREEKTAnghhzcGR5LXR3aXRsb2cuaW5kdXRu
+eS5jb22CC2luZHV0bnkuY29tMIICIQYDVR0gBIICGDCCAhQwggIQBgsrBgEEAYG1
+NwECAjCCAf8wLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3Bv
+bGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2lu
+dGVybWVkaWF0ZS5wZGYwgfcGCCsGAQUFBwICMIHqMCcWIFN0YXJ0Q29tIENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5MAMCAQEagb5UaGlzIGNlcnRpZmljYXRlIHdhcyBp
+c3N1ZWQgYWNjb3JkaW5nIHRvIHRoZSBDbGFzcyAxIFZhbGlkYXRpb24gcmVxdWly
+ZW1lbnRzIG9mIHRoZSBTdGFydENvbSBDQSBwb2xpY3ksIHJlbGlhbmNlIG9ubHkg
+Zm9yIHRoZSBpbnRlbmRlZCBwdXJwb3NlIGluIGNvbXBsaWFuY2Ugb2YgdGhlIHJl
+bHlpbmcgcGFydHkgb2JsaWdhdGlvbnMuMIGcBggrBgEFBQcCAjCBjzAnFiBTdGFy
+dENvbSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTADAgECGmRMaWFiaWxpdHkgYW5k
+IHdhcnJhbnRpZXMgYXJlIGxpbWl0ZWQhIFNlZSBzZWN0aW9uICJMZWdhbCBhbmQg
+TGltaXRhdGlvbnMiIG9mIHRoZSBTdGFydENvbSBDQSBwb2xpY3kuMDUGA1UdHwQu
+MCwwKqAooCaGJGh0dHA6Ly9jcmwuc3RhcnRzc2wuY29tL2NydDEtY3JsLmNybDCB
+jgYIKwYBBQUHAQEEgYEwfzA5BggrBgEFBQcwAYYtaHR0cDovL29jc3Auc3RhcnRz
+c2wuY29tL3N1Yi9jbGFzczEvc2VydmVyL2NhMEIGCCsGAQUFBzAChjZodHRwOi8v
+YWlhLnN0YXJ0c3NsLmNvbS9jZXJ0cy9zdWIuY2xhc3MxLnNlcnZlci5jYS5jcnQw
+IwYDVR0SBBwwGoYYaHR0cDovL3d3dy5zdGFydHNzbC5jb20vMA0GCSqGSIb3DQEB
+BQUAA4IBAQClU4BNyXLLlk8PryozRpx0WkkH83e799myaLMAY0IjyroQ3EH1rlXT
+vU9QJ8d8br+xm7KnLsrva0BDC/eVeq+7qCyUNoXUAAswBzpXPeL+Rm/4oWmOAcCA
+/8MQ6z+YLFPf9MqlWdO24aA5aoN7DwCxIvJ2IfhNbRFLzcztF/kFdqnRtw34S1L4
+GvSx/o0eYZ8sgyj9yET8QqXR/EUs+NvRBmDIjMx8DmJVvLQZHNk1sHnkdx/Z0AWP
+WUjaEkhfkvLVCvX+7vGmlMnvGwRZf1U5qaj4sp+O70ANlQVG99R6e8IEcf8JFy3/
+AUumo8jFsCfPIcByKJoctlz1nx9+zEDp
+-----END CERTIFICATE-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/keys/twitlog-csr.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICijCCAXICAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
+ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBANgiMg1hIHoxk1AuMouaERtSv8g/J+JbTgAelD5k
+F7hGpQOB+qGLuCU+7QovsjftKee7aXXNduQDkzkCD7wg7C9m3QHJ+YXDk7vEIycg
+K6XIK7G1kAfMXfJm6D2gRD3JjM4fWRY/MYTxSvLnqDoAgHIrCBrwauNNuHdl4x5L
+clRKAWAl1sixQgdyw/CzLjuFJFgmRn1Oc1T/lwgzwcXcw0tBvTQtTdvaogvJ+cqT
++TrHVm+w+BRfp+CT9slZp+4b9JQTD5u4/0k3RoxbUBHvKRJ9FKOsq/GFNdljKWMI
+fQ8ZdHJDKWVX8zlDydhpMTpofaynvdmSPurMcFNGGmYEmkUCAwEAAaAAMA0GCSqG
+SIb3DQEBBQUAA4IBAQCQFFq1j4zbi3mqqpRrGjS5NMhCqdS3m8lPgUtPCwDsbtAy
+ILtl6clYA5ruwxtLEQYOiowUZ7dgQQ2poqwwelk5hJ92D9JySAyU0bqWUZ3CgccC
+7LWhZMlmeyqBnDSE+oaFKsPRs9oTYGxh4zZv9OhPNL5ZFEAwUMccHXYMgsQk5n1E
+pbAdxw2KgyrliTz4PO7/hjwAfANszdSweP++qNBceix1HpWIp17hJ9aIH1Q+w5Ym
+Uzfykvz7Qc9YoQ/LHKJMxirsmZtdAjC0YApMuAOhjD42OkhL9JBVg5CFMIhXQBOU
+1fGAoWdbIKWre4owm9kjsqwe8jg+sWTZHE1h+TJD
+-----END CERTIFICATE REQUEST-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/keys/twitlog-key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDYIjINYSB6MZNQ
+LjKLmhEbUr/IPyfiW04AHpQ+ZBe4RqUDgfqhi7glPu0KL7I37Snnu2l1zXbkA5M5
+Ag+8IOwvZt0ByfmFw5O7xCMnICulyCuxtZAHzF3yZug9oEQ9yYzOH1kWPzGE8Ury
+56g6AIByKwga8GrjTbh3ZeMeS3JUSgFgJdbIsUIHcsPwsy47hSRYJkZ9TnNU/5cI
+M8HF3MNLQb00LU3b2qILyfnKk/k6x1ZvsPgUX6fgk/bJWafuG/SUEw+buP9JN0aM
+W1AR7ykSfRSjrKvxhTXZYyljCH0PGXRyQyllV/M5Q8nYaTE6aH2sp73Zkj7qzHBT
+RhpmBJpFAgMBAAECggEAdEKos+O8KZ7DRE0laUy9yPnRKfE3Dh7ZLV1Flu4WiEyP
+9PwVCpLywi5AKcuQTV8ovHtmdjTIsExwBClkt2jqQ3FMjurLazXSIR2XXzOB5xJu
+1o/44wj+vCa45HVyX94r/LCGJl5lz8JP86vDJTgh38ff+0W56X1kLe3Dpwckf8u1
+fMV0apePlyYbPL85EbulnUNToiIgURu/brnsdufB62RZBp1NhUjiaok9jODQq3Ee
+8J8fpMZNBhvwZqfv7h1ykMfYUASQ8IDtZKMKk7MCMuPjLmMb9nWKrXmUUIIJDAqL
+Z/cgKBE72Swj+l+V54/onr+2g9iTu2hlxsicEYcSIQKBgQD9lRO/BtbB9AXEJ3E5
+I1fwYvV5I1TJiVBNVo7VwC2gtwHN7ftXw06bEc78z2i5Od+/F39XeXes5yBxqCbO
+tv0XKF2jOHakMduMch4C7qyMiS4q1JtDkEbJd4jkBGySwePf45gHPmtmso7peQPT
+P4OoO/9MLzJjBYDeSCPbqiMvuQKBgQDaMbdvafxpWxg44SNqZtpX54CpLViUMoaY
+mrkyDIXrHMi2+yY/PzaNBNUASqlAryy4fxhGRsHFMMssZpjdfCBr2DnXbXSk4Li3
+CK8+m9hk2om9lv2FvDOPCQ+RGigmpYQC/a7UKo8xIjI2wzDRmevXDf8xBFjvLQTc
+bpanFbrM7QKBgD1JLUeKwJaJgmdA3RVhHFzFnewUBObcX+MBG24/jwd7k10QuiEg
+27uQl0T0X6v8d734UNd0TN8l0OqHKDHnec2B/Pd4qvvN7PDJl8U/p8YjVVwWnBu9
+H86LLDNnelIRuCAhIloF1PEyEGYO0ETa4dfkADSKZ5QU/Ws7ZictvGlJAoGANfA+
+YXt4226agU0enSoJ5dsj0i6UjCYlYco15+pynJmEAL/7R31P9fJw2V6bkpL7YiyB
+CrZpJl8WisZeGbqapS5RtjCnui6XWx/5eme6ScxAaq7Nw2av9DcQMxWdQVh/VuHx
+ex9+QG4srZ75DYeYZpReNnbVqWKepgNsmKdlg00CgYBsrbksX+66b11z6GwBjEK+
+0rMpzMhnOxhxGaDw6jTuxOIZEC1EyaxI5sxryH6+XdZJZYH/pN6O5skjPD28L8fC
+b9900FSsnQlnk/QMb7G5Tqyvf2oAAxSSfvX13zAFGd1Z79TEeiZWeY04hrXACvP+
+cAeTFiSRlCMiOLLgRhFauw==
+-----END PRIVATE KEY-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/keys/twtilog-ca.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIGNDCCBBygAwIBAgIBGDANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
+MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
+Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwHhcNMDcxMDI0MjA1NDE3WhcNMTcxMDI0MjA1NDE3WjCB
+jDELMAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsT
+IlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNVBAMTL1N0
+YXJ0Q29tIENsYXNzIDEgUHJpbWFyeSBJbnRlcm1lZGlhdGUgU2VydmVyIENBMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtonGrO8JUngHrJJj0PREGBiE
+gFYfka7hh/oyULTTRwbw5gdfcA4Q9x3AzhA2NIVaD5Ksg8asWFI/ujjo/OenJOJA
+pgh2wJJuniptTT9uYSAK21ne0n1jsz5G/vohURjXzTCm7QduO3CHtPn66+6CPAVv
+kvek3AowHpNz/gfK11+AnSJYUq4G2ouHI2mw5CrY6oPSvfNx23BaKA+vWjhwRRI/
+ME3NO68X5Q/LoKldSKqxYVDLNM08XMML6BDAjJvwAwNi/rJsPnIO7hxDKslIDlc5
+xDEhyBDBLIf+VJVSH1I8MRKbf+fAoKVZ1eKPPvDVqOHXcDGpxLPPr21TLwb0pwID
+AQABo4IBrTCCAakwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFOtCNNCYsKuf9BtrCPfMZC7vDixFMB8GA1UdIwQYMBaAFE4L7xqkQFul
+F2mHMMo0aEPQQa7yMGYGCCsGAQUFBwEBBFowWDAnBggrBgEFBQcwAYYbaHR0cDov
+L29jc3Auc3RhcnRzc2wuY29tL2NhMC0GCCsGAQUFBzAChiFodHRwOi8vd3d3LnN0
+YXJ0c3NsLmNvbS9zZnNjYS5jcnQwWwYDVR0fBFQwUjAnoCWgI4YhaHR0cDovL3d3
+dy5zdGFydHNzbC5jb20vc2ZzY2EuY3JsMCegJaAjhiFodHRwOi8vY3JsLnN0YXJ0
+c3NsLmNvbS9zZnNjYS5jcmwwgYAGA1UdIAR5MHcwdQYLKwYBBAGBtTcBAgEwZjAu
+BggrBgEFBQcCARYiaHR0cDovL3d3dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjA0
+BggrBgEFBQcCARYoaHR0cDovL3d3dy5zdGFydHNzbC5jb20vaW50ZXJtZWRpYXRl
+LnBkZjANBgkqhkiG9w0BAQUFAAOCAgEAIQlJPqWIbuALi0jaMU2P91ZXouHTYlfp
+tVbzhUV1O+VQHwSL5qBaPucAroXQ+/8gA2TLrQLhxpFy+KNN1t7ozD+hiqLjfDen
+xk+PNdb01m4Ge90h2c9W/8swIkn+iQTzheWq8ecf6HWQTd35RvdCNPdFWAwRDYSw
+xtpdPvkBnufh2lWVvnQce/xNFE+sflVHfXv0pQ1JHpXo9xLBzP92piVH0PN1Nb6X
+t1gW66pceG/sUzCv6gRNzKkC4/C2BBL2MLERPZBOVmTX3DxDX3M570uvh+v2/miI
+RHLq0gfGabDBoYvvF0nXYbFFSF87ICHpW7LM9NfpMfULFWE7epTj69m8f5SuauNi
+YpaoZHy4h/OZMn6SolK+u/hlz8nyMPyLwcKmltdfieFcNID1j0cHL7SRv7Gifl9L
+WtBbnySGBVFaaQNlQ0lxxeBvlDRr9hvYqbBMflPrj0jfyjO1SPo2ShpTpjMM0InN
+SRXNiTE8kMBy12VLUjWKRhFEuT2OKGWmPnmeXAhEKa2wNREuIU640ucQPl2Eg7PD
+wuTSxv0JS3QJ3fGz0xk+gA2iCxnwOOfFwq/iI9th4p1cbiCJSS4jarJiwUW0n6+L
+p/EiO/h94pDQehn7Skzj0n1fSoMD7SfWI55rjbRZotnvbIIp3XUZPD9MEI3vu3Un
+0q6Dp6jOW6c=
+-----END CERTIFICATE-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "application-name",
+  "version": "0.0.1",
+  "private": true,
+  "dependencies": {
+    "express": "2.5.x",
+    "jade": ">= 0.0.1",
+    "socket.io": "0.8.x",
+    "ntwitter": "0.2.x"
+  }
+}
new file mode 100644
index 0000000000000000000000000000000000000000..d535769aa6af5ff1437e1c1a41db0a02d686e2d8
GIT binary patch
literal 2582
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
new file mode 100644
index 0000000000000000000000000000000000000000..c7f41c274463b16ef4b79776b73aebb20cdcd926
GIT binary patch
literal 11587
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
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/public/javascripts/main.js
@@ -0,0 +1,52 @@
+!function() {
+  var socket = io.connect(),
+      container = document.getElementById('tweets'),
+      tweets = [],
+      first;
+
+  socket.emit('reqTweets');
+  socket.on('tweet', function(tweet) {
+    if (tweets.length > 18) {
+      // Remove first tweet
+      container.removeChild(tweets.shift());
+    }
+    tweets.push(createTweet(tweet));
+  });
+
+  function createTweet(tweet) {
+    var elem = document.createElement('article'),
+        avatar = document.createElement('img'),
+        textContainer = document.createElement('div'),
+        text = document.createElement('div'),
+        clear = document.createElement('div');
+
+    avatar.className = 'avatar';
+    avatar.src = tweet.user.image.replace(
+        /^http:\/\/a2\.twimg\.com\//,
+        '//twimg0-a.akamaihd.net/'
+    );
+    avatar.title = avatar.alt = tweet.user.name;
+
+    text.className = 'text';
+    text.innerHTML = tweet.text;
+
+    clear.className = 'clear';
+
+    textContainer.className = 'text-container';
+    textContainer.appendChild(text);
+    textContainer.appendChild(clear);
+
+    elem.className = 'tweet';
+    elem.appendChild(avatar);
+    elem.appendChild(textContainer);
+
+    if (first) {
+      container.insertBefore(elem, first);
+    } else {
+      container.appendChild(elem);
+    }
+    first = elem;
+
+    return elem;
+  };
+}();
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/public/javascripts/socket.io.min.js
@@ -0,0 +1,2 @@
+/*! Socket.IO.min.js build:0.8.4, production. Copyright(c) 2011 LearnBoost <dev@learnboost.com> MIT Licensed */
+(function(a){var b=a;b.version="0.8.4",b.protocol=1,b.transports=[],b.j=[],b.sockets={},b.connect=function(a,c){var d=b.util.parseUri(a),e,f;"undefined"!=typeof document&&(d.protocol=d.protocol||document.location.protocol.slice(0,-1),d.host=d.host||document.domain,d.port=d.port||document.location.port),e=b.util.uniqueUri(d);var g={host:d.host,secure:"https"==d.protocol,port:d.port||("https"==d.protocol?443:80),query:d.query||""};b.util.merge(g,c);if(g["force new connection"]||!b.sockets[e])f=new b.Socket(g);!g["force new connection"]&&f&&(b.sockets[e]=f),f=f||b.sockets[e];return f.of(d.path.length>1?d.path:"")}})("object"==typeof module?module.exports:window.io={}),function(a,b){var c=a.util={},d=/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,e=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];c.parseUri=function(a){var b=d.exec(a||""),c={},f=14;while(f--)c[e[f]]=b[f]||"";return c},c.uniqueUri=function(a){var c=a.protocol,d=a.host,e=a.port;"document"in b?(d=d||document.domain,e=e||(c=="https"&&document.location.protocol!=="https:"?443:document.location.port)):(d=d||"localhost",!e&&c=="https"&&(e=443));return(c||"http")+"://"+d+":"+(e||80)},c.query=function(a,b){var d=c.chunkQuery(a||""),e=[];c.merge(d,c.chunkQuery(b||""));for(var f in d)d.hasOwnProperty(f)&&e.push(f+"="+d[f]);return e.length?"?"+e.join("&"):""},c.chunkQuery=function(a){var b={},c=a.split("&"),d=0,e=c.length,f;for(;d<e;++d)f=c[d].split("="),f[0]&&(b[f[0]]=decodeURIComponent(f[1]));return b};var f=!1;c.load=function(a){if("document"in b&&document.readyState==="complete"||f)return a();c.on(b,"load",a,!1)},c.on=function(a,b,c,d){a.attachEvent?a.attachEvent("on"+b,c):a.addEventListener&&a.addEventListener(b,c,d)},c.request=function(a){if("undefined"!=typeof window){if(a&&window.XDomainRequest)return new XDomainRequest;if(window.XMLHttpRequest&&(!a||c.ua.hasCORS))return new XMLHttpRequest;if(!a)try{return new window.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}return null},"undefined"!=typeof window&&c.load(function(){f=!0}),c.defer=function(a){if(!c.ua.webkit)return a();c.load(function(){setTimeout(a,100)})},c.merge=function g(a,b,d,e){var f=e||[],g=typeof d=="undefined"?2:d,h;for(h in b)b.hasOwnProperty(h)&&c.indexOf(f,h)<0&&(typeof a[h]!="object"||!g?(a[h]=b[h],f.push(b[h])):c.merge(a[h],b[h],g-1,f));return a},c.mixin=function(a,b){c.merge(a.prototype,b.prototype)},c.inherit=function(a,b){function c(){}c.prototype=b.prototype,a.prototype=new c},c.isArray=Array.isArray||function(a){return Object.prototype.toString.call(a)==="[object Array]"},c.intersect=function(a,b){var d=[],e=a.length>b.length?a:b,f=a.length>b.length?b:a;for(var g=0,h=f.length;g<h;g++)~c.indexOf(e,f[g])&&d.push(f[g]);return d},c.indexOf=function(a,b,c){if(Array.prototype.indexOf)return Array.prototype.indexOf.call(a,b,c);for(var d=a.length,c=c<0?c+d<0?0:c+d:c||0;c<d&&a[c]!==b;c++);return d<=c?-1:c},c.toArray=function(a){var b=[];for(var c=0,d=a.length;c<d;c++)b.push(a[c]);return b},c.ua={},c.ua.hasCORS="undefined"!=typeof window&&window.XMLHttpRequest&&function(){try{var a=new XMLHttpRequest}catch(b){return!1}return a.withCredentials!=undefined}(),c.ua.webkit="undefined"!=typeof navigator&&/webkit/i.test(navigator.userAgent)}("undefined"!=typeof window?io:module.exports,this),function(a,b){function c(){}a.EventEmitter=c,c.prototype.on=function(a,c){this.$events||(this.$events={}),this.$events[a]?b.util.isArray(this.$events[a])?this.$events[a].push(c):this.$events[a]=[this.$events[a],c]:this.$events[a]=c;return this},c.prototype.addListener=c.prototype.on,c.prototype.once=function(a,b){function d(){c.removeListener(a,d),b.apply(this,arguments)}var c=this;d.listener=b,this.on(a,d);return this},c.prototype.removeListener=function(a,c){if(this.$events&&this.$events[a]){var d=this.$events[a];if(b.util.isArray(d)){var e=-1;for(var f=0,g=d.length;f<g;f++)if(d[f]===c||d[f].listener&&d[f].listener===c){e=f;break}if(e<0)return this;d.splice(e,1),d.length||delete this.$events[a]}else(d===c||d.listener&&d.listener===c)&&delete this.$events[a]}return this},c.prototype.removeAllListeners=function(a){this.$events&&this.$events[a]&&(this.$events[a]=null);return this},c.prototype.listeners=function(a){this.$events||(this.$events={}),this.$events[a]||(this.$events[a]=[]),b.util.isArray(this.$events[a])||(this.$events[a]=[this.$events[a]]);return this.$events[a]},c.prototype.emit=function(a){if(!this.$events)return!1;var c=this.$events[a];if(!c)return!1;var d=Array.prototype.slice.call(arguments,1);if("function"==typeof c)c.apply(this,d);else{if(!b.util.isArray(c))return!1;var e=c.slice();for(var f=0,g=e.length;f<g;f++)e[f].apply(this,d)}return!0}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(exports,nativeJSON){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i instanceof Date&&(i=date(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c<f;c+=1)h[c]=str(c,i)||"null";e=h.length===0?"[]":gap?"[\n"+gap+h.join(",\n"+gap)+"\n"+g+"]":"["+h.join(",")+"]",gap=g;return e}if(rep&&typeof rep=="object"){f=rep.length;for(c=0;c<f;c+=1)typeof rep[c]=="string"&&(d=rep[c],e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e))}else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e));e=h.length===0?"{}":gap?"{\n"+gap+h.join(",\n"+gap)+"\n"+g+"}":"{"+h.join(",")+"}",gap=g;return e}}function quote(a){escapable.lastIndex=0;return escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b=="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function date(a,b){return isFinite(a.valueOf())?a.getUTCFullYear()+"-"+f(a.getUTCMonth()+1)+"-"+f(a.getUTCDate())+"T"+f(a.getUTCHours())+":"+f(a.getUTCMinutes())+":"+f(a.getUTCSeconds())+"Z":null}function f(a){return a<10?"0"+a:a}"use strict";if(nativeJSON&&nativeJSON.parse)return exports.JSON={parse:nativeJSON.parse,stringify:nativeJSON.stringify};var JSON=exports.JSON={},cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;JSON.stringify=function(a,b,c){var d;gap="",indent="";if(typeof c=="number")for(d=0;d<c;d+=1)indent+=" ";else typeof c=="string"&&(indent=c);rep=b;if(!b||typeof b=="function"||typeof b=="object"&&typeof b.length=="number")return str("",{"":a});throw new Error("JSON.stringify")},JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&typeof e=="object")for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),d!==undefined?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver=="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")}}("undefined"!=typeof io?io:module.exports,typeof JSON!="undefined"?JSON:undefined),function(a,b){var c=a.parser={},d=c.packets=["disconnect","connect","heartbeat","message","json","event","ack","error","noop"],e=c.reasons=["transport not supported","client not handshaken","unauthorized"],f=c.advice=["reconnect"],g=b.JSON,h=b.util.indexOf;c.encodePacket=function(a){var b=h(d,a.type),c=a.id||"",i=a.endpoint||"",j=a.ack,k=null;switch(a.type){case"error":var l=a.reason?h(e,a.reason):"",m=a.advice?h(f,a.advice):"";if(l!==""||m!=="")k=l+(m!==""?"+"+m:"");break;case"message":a.data!==""&&(k=a.data);break;case"event":var n={name:a.name};a.args&&a.args.length&&(n.args=a.args),k=g.stringify(n);break;case"json":k=g.stringify(a.data);break;case"connect":a.qs&&(k=a.qs);break;case"ack":k=a.ackId+(a.args&&a.args.length?"+"+g.stringify(a.args):"")}var o=[b,c+(j=="data"?"+":""),i];k!==null&&k!==undefined&&o.push(k);return o.join(":")},c.encodePayload=function(a){var b="";if(a.length==1)return a[0];for(var c=0,d=a.length;c<d;c++){var e=a[c];b+="\ufffd"+e.length+"\ufffd"+a[c]}return b};var i=/([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/;c.decodePacket=function(a){var b=a.match(i);if(!b)return{};var c=b[2]||"",a=b[5]||"",h={type:d[b[1]],endpoint:b[4]||""};c&&(h.id=c,b[3]?h.ack="data":h.ack=!0);switch(h.type){case"error":var b=a.split("+");h.reason=e[b[0]]||"",h.advice=f[b[1]]||"";break;case"message":h.data=a||"";break;case"event":try{var j=g.parse(a);h.name=j.name,h.args=j.args}catch(k){}h.args=h.args||[];break;case"json":try{h.data=g.parse(a)}catch(k){}break;case"connect":h.qs=a||"";break;case"ack":var b=a.match(/^([0-9]+)(\+)?(.*)/);if(b){h.ackId=b[1],h.args=[];if(b[3])try{h.args=b[3]?g.parse(b[3]):[]}catch(k){}}break;case"disconnect":case"heartbeat":}return h},c.decodePayload=function(a){if(a.charAt(0)=="\ufffd"){var b=[];for(var d=1,e="";d<a.length;d++)a.charAt(d)=="\ufffd"?(b.push(c.decodePacket(a.substr(d+1).substr(0,e))),d+=Number(e)+1,e=""):e+=a.charAt(d);return b}return[c.decodePacket(a)]}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function c(a,b){this.socket=a,this.sessid=b}a.Transport=c,b.util.mixin(c,b.EventEmitter),c.prototype.onData=function(a){this.clearCloseTimeout(),this.setCloseTimeout();if(a!==""){var c=b.parser.decodePayload(a);if(c&&c.length)for(var d=0,e=c.length;d<e;d++)this.onPacket(c[d])}return this},c.prototype.onPacket=function(a){if(a.type=="heartbeat")return this.onHeartbeat();a.type=="connect"&&a.endpoint==""&&this.onConnect(),this.socket.onPacket(a);return this},c.prototype.setCloseTimeout=function(){if(!this.closeTimeout){var a=this;this.closeTimeout=setTimeout(function(){a.onDisconnect()},this.socket.closeTimeout)}},c.prototype.onDisconnect=function(){this.close&&this.close(),this.clearTimeouts(),this.socket.onDisconnect();return this},c.prototype.onConnect=function(){this.socket.onConnect();return this},c.prototype.clearCloseTimeout=function(){this.closeTimeout&&(clearTimeout(this.closeTimeout),this.closeTimeout=null)},c.prototype.clearTimeouts=function(){this.clearCloseTimeout(),this.reopenTimeout&&clearTimeout(this.reopenTimeout)},c.prototype.packet=function(a){this.send(b.parser.encodePacket(a))},c.prototype.onHeartbeat=function(a){this.packet({type:"heartbeat"})},c.prototype.onOpen=function(){this.open=!0,this.clearCloseTimeout(),this.socket.onOpen()},c.prototype.onClose=function(){var a=this;this.open=!1,this.setCloseTimeout(),this.socket.onClose()},c.prototype.prepareUrl=function(){var a=this.socket.options;return this.scheme()+"://"+a.host+":"+a.port+"/"+a.resource+"/"+b.protocol+"/"+this.name+"/"+this.sessid},c.prototype.ready=function(a,b){b.call(this)}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b,c){function e(){}function d(a){this.options={port:80,secure:!1,document:"document"in c?document:!1,resource:"socket.io",transports:b.transports,"connect timeout":1e4,"try multiple transports":!0,reconnect:!0,"reconnection delay":500,"reconnection limit":Infinity,"reopen delay":3e3,"max reconnection attempts":10,"sync disconnect on unload":!0,"auto connect":!0,"flash policy port":10843},b.util.merge(this.options,a),this.connected=!1,this.open=!1,this.connecting=!1,this.reconnecting=!1,this.namespaces={},this.buffer=[],this.doBuffer=!1;if(this.options["sync disconnect on unload"]&&(!this.isXDomain()||b.util.ua.hasCORS)){var d=this;b.util.on(c,"beforeunload",function(){d.disconnectSync()},!1)}this.options["auto connect"]&&this.connect()}a.Socket=d,b.util.mixin(d,b.EventEmitter),d.prototype.of=function(a){this.namespaces[a]||(this.namespaces[a]=new b.SocketNamespace(this,a),a!==""&&this.namespaces[a].packet({type:"connect"}));return this.namespaces[a]},d.prototype.publish=function(){this.emit.apply(this,arguments);var a;for(var b in this.namespaces)this.namespaces.hasOwnProperty(b)&&(a=this.of(b),a.$emit.apply(a,arguments))},d.prototype.handshake=function(a){function f(b){b instanceof Error?c.onError(b.message):a.apply(null,b.split(":"))}var c=this,d=this.options,g=["http"+(d.secure?"s":"")+":/",d.host+":"+d.port,this.options.resource,b.protocol,b.util.query(this.options.query,"t="+ +(new Date))].join("/");if(this.isXDomain()){var h=document.getElementsByTagName("script")[0],i=document.createElement("script");i.src=g+"&jsonp="+b.j.length,h.parentNode.insertBefore(i,h),b.j.push(function(a){f(a),i.parentNode.removeChild(i)})}else{var j=b.util.request();j.open("GET",g,!0),j.onreadystatechange=function(){j.readyState==4&&(j.onreadystatechange=e,j.status==200?f(j.responseText):!c.reconnecting&&c.onError(j.responseText))},j.send(null)}},d.prototype.getTransport=function(a){var c=a||this.transports,d;for(var e=0,f;f=c[e];e++)if(b.Transport[f]&&b.Transport[f].check(this)&&(!this.isXDomain()||b.Transport[f].xdomainCheck()))return new b.Transport[f](this,this.sessionid);return null},d.prototype.connect=function(a){if(this.connecting)return this;var c=this;this.handshake(function(d,e,f,g){function h(a){c.transport&&c.transport.clearTimeouts(),c.transport=c.getTransport(a);if(!c.transport)return c.publish("connect_failed");c.transport.ready(c,function(){c.connecting=!0,c.publish("connecting",c.transport.name),c.transport.open(),c.options["connect timeout"]&&(c.connectTimeoutTimer=setTimeout(function(){if(!c.connected){c.connecting=!1;if(c.options["try multiple transports"]){c.remainingTransports||(c.remainingTransports=c.transports.slice(0));var a=c.remainingTransports;while(a.length>0&&a.splice(0,1)[0]!=c.transport.name);a.length?h(a):c.publish("connect_failed")}}},c.options["connect timeout"]))})}c.sessionid=d,c.closeTimeout=f*1e3,c.heartbeatTimeout=e*1e3,c.transports=b.util.intersect(g.split(","),c.options.transports),h(),c.once("connect",function(){clearTimeout(c.connectTimeoutTimer),a&&typeof a=="function"&&a()})});return this},d.prototype.packet=function(a){this.connected&&!this.doBuffer?this.transport.packet(a):this.buffer.push(a);return this},d.prototype.setBuffer=function(a){this.doBuffer=a,!a&&this.connected&&this.buffer.length&&(this.transport.payload(this.buffer),this.buffer=[])},d.prototype.disconnect=function(){this.connected&&(this.open&&this.of("").packet({type:"disconnect"}),this.onDisconnect("booted"));return this},d.prototype.disconnectSync=function(){var a=b.util.request(),c=this.resource+"/"+b.protocol+"/"+this.sessionid;a.open("GET",c,!0),this.onDisconnect("booted")},d.prototype.isXDomain=function(){var a=window.location.port||("https:"==window.location.protocol?443:80);return this.options.host!==document.domain||this.options.port!=a},d.prototype.onConnect=function(){this.connected||(this.connected=!0,this.connecting=!1,this.doBuffer||this.setBuffer(!1),this.emit("connect"))},d.prototype.onOpen=function(){this.open=!0},d.prototype.onClose=function(){this.open=!1},d.prototype.onPacket=function(a){this.of(a.endpoint).onPacket(a)},d.prototype.onError=function(a){a&&a.advice&&a.advice==="reconnect"&&this.connected&&(this.disconnect(),this.reconnect()),this.publish("error",a&&a.reason?a.reason:a)},d.prototype.onDisconnect=function(a){var b=this.connected;this.connected=!1,this.connecting=!1,this.open=!1,b&&(this.transport.close(),this.transport.clearTimeouts(),this.publish("disconnect",a),"booted"!=a&&this.options.reconnect&&!this.reconnecting&&this.reconnect())},d.prototype.reconnect=function(){function f(){if(!!a.reconnecting){if(a.connected)return e();if(a.connecting&&a.reconnecting)return a.reconnectionTimer=setTimeout(f,1e3);a.reconnectionAttempts++>=b?a.redoTransports?(a.publish("reconnect_failed"),e()):(a.on("connect_failed",f),a.options["try multiple transports"]=!0,a.transport=a.getTransport(),a.redoTransports=!0,a.connect()):(a.reconnectionDelay<d&&(a.reconnectionDelay*=2),a.connect(),a.publish("reconnecting",a.reconnectionDelay,a.reconnectionAttempts),a.reconnectionTimer=setTimeout(f,a.reconnectionDelay))}}function e(){if(a.connected){for(var b in a.namespaces)a.namespaces.hasOwnProperty(b)&&""!==b&&a.namespaces[b].packet({type:"connect"});a.publish("reconnect",a.transport.name,a.reconnectionAttempts)}a.removeListener("connect_failed",f),a.removeListener("connect",f),a.reconnecting=!1,delete a.reconnectionAttempts,delete a.reconnectionDelay,delete a.reconnectionTimer,delete a.redoTransports,a.options["try multiple transports"]=c}this.reconnecting=!0,this.reconnectionAttempts=0,this.reconnectionDelay=this.options["reconnection delay"];var a=this,b=this.options["max reconnection attempts"],c=this.options["try multiple transports"],d=this.options["reconnection limit"];this.options["try multiple transports"]=!1,this.reconnectionTimer=setTimeout(f,this.reconnectionDelay),this.on("connect",f)}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports,this),function(a,b){function d(a,b){this.namespace=a,this.name=b}function c(a,b){this.socket=a,this.name=b||"",this.flags={},this.json=new d(this,"json"),this.ackPackets=0,this.acks={}}a.SocketNamespace=c,b.util.mixin(c,b.EventEmitter),c.prototype.$emit=b.EventEmitter.prototype.emit,c.prototype.of=function(){return this.socket.of.apply(this.socket,arguments)},c.prototype.packet=function(a){a.endpoint=this.name,this.socket.packet(a),this.flags={};return this},c.prototype.send=function(a,b){var c={type:this.flags.json?"json":"message",data:a};"function"==typeof b&&(c.id=++this.ackPackets,c.ack=!0,this.acks[c.id]=b);return this.packet(c)},c.prototype.emit=function(a){var b=Array.prototype.slice.call(arguments,1),c=b[b.length-1],d={type:"event",name:a};"function"==typeof c&&(d.id=++this.ackPackets,d.ack="data",this.acks[d.id]=c,b=b.slice(0,b.length-1)),d.args=b;return this.packet(d)},c.prototype.disconnect=function(){this.name===""?this.socket.disconnect():(this.packet({type:"disconnect"}),this.$emit("disconnect"));return this},c.prototype.onPacket=function(a){function d(){c.packet({type:"ack",args:b.util.toArray(arguments),ackId:a.id})}var c=this;switch(a.type){case"connect":this.$emit("connect");break;case"disconnect":this.name===""?this.socket.onDisconnect(a.reason||"booted"):this.$emit("disconnect",a.reason);break;case"message":case"json":var e=["message",a.data];a.ack=="data"?e.push(d):a.ack&&this.packet({type:"ack",ackId:a.id}),this.$emit.apply(this,e);break;case"event":var e=[a.name].concat(a.args);a.ack=="data"&&e.push(d),this.$emit.apply(this,e);break;case"ack":this.acks[a.ackId]&&(this.acks[a.ackId].apply(this,a.args),delete this.acks[a.ackId]);break;case"error":a.advice?this.socket.onError(a):a.reason=="unauthorized"?this.$emit("connect_failed",a.reason):this.$emit("error",a.reason)}},d.prototype.send=function(){this.namespace.flags[this.name]=!0,this.namespace.send.apply(this.namespace,arguments)},d.prototype.emit=function(){this.namespace.flags[this.name]=!0,this.namespace.emit.apply(this.namespace,arguments)}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function c(a){b.Transport.apply(this,arguments)}a.websocket=c,b.util.inherit(c,b.Transport),c.prototype.name="websocket",c.prototype.open=function(){var a=b.util.query(this.socket.options.query),c=this,d;d||(d=window.MozWebSocket||window.WebSocket),this.websocket=new d(this.prepareUrl()+a),this.websocket.onopen=function(){c.onOpen(),c.socket.setBuffer(!1)},this.websocket.onmessage=function(a){c.onData(a.data)},this.websocket.onclose=function(){c.onClose(),c.socket.setBuffer(!0)},this.websocket.onerror=function(a){c.onError(a)};return this},c.prototype.send=function(a){this.websocket.send(a);return this},c.prototype.payload=function(a){for(var b=0,c=a.length;b<c;b++)this.packet(a[b]);return this},c.prototype.close=function(){this.websocket.close();return this},c.prototype.onError=function(a){this.socket.onError(a)},c.prototype.scheme=function(){return this.socket.options.secure?"wss":"ws"},c.check=function(){return"WebSocket"in window&&!("__addTask"in WebSocket)||"MozWebSocket"in window},c.xdomainCheck=function(){return!0},b.transports.push("websocket")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function c(){b.Transport.websocket.apply(this,arguments)}a.flashsocket=c,b.util.inherit(c,b.Transport.websocket),c.prototype.name="flashsocket",c.prototype.open=function(){var a=this,c=arguments;WebSocket.__addTask(function(){b.Transport.websocket.prototype.open.apply(a,c)});return this},c.prototype.send=function(){var a=this,c=arguments;WebSocket.__addTask(function(){b.Transport.websocket.prototype.send.apply(a,c)});return this},c.prototype.close=function(){WebSocket.__tasks.length=0,b.Transport.websocket.prototype.close.call(this);return this},c.prototype.ready=function(a,d){function e(){var b=a.options,e=b["flash policy port"],g=["http"+(b.secure?"s":"")+":/",b.host+":"+b.port,b.resource,"static/flashsocket","WebSocketMain"+(a.isXDomain()?"Insecure":"")+".swf"];c.loaded||(typeof WEB_SOCKET_SWF_LOCATION=="undefined"&&(WEB_SOCKET_SWF_LOCATION=g.join("/")),e!==843&&WebSocket.loadFlashPolicyFile("xmlsocket://"+b.host+":"+e),WebSocket.__initialize(),c.loaded=!0),d.call(f)}var f=this;if(document.body)return e();b.util.load(e)},c.check=function(){return typeof WebSocket!="undefined"&&"__initialize"in WebSocket&&!!swfobject?swfobject.getFlashPlayerVersion().major>=10:!1},c.xdomainCheck=function(){return!0},typeof window!="undefined"&&(WEB_SOCKET_DISABLE_AUTO_INITIALIZATION=!0),b.transports.push("flashsocket")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports);var swfobject=function(){function V(b){var c=/[\\\"<>\.;]/,d=c.exec(b)!=null;return d&&typeof encodeURIComponent!=a?encodeURIComponent(b):b}function U(a,b){if(!!x){var c=b?"visible":"hidden";t&&P(a)?P(a).style.visibility=c:T("#"+a,"visibility:"+c)}}function T(c,d,e,f){if(!y.ie||!y.mac){var g=i.getElementsByTagName("head")[0];if(!g)return;var h=e&&typeof e=="string"?e:"screen";f&&(v=null,w=null);if(!v||w!=h){var j=Q("style");j.setAttribute("type","text/css"),j.setAttribute("media",h),v=g.appendChild(j),y.ie&&y.win&&typeof i.styleSheets!=a&&i.styleSheets.length>0&&(v=i.styleSheets[i.styleSheets.length-1]),w=h}y.ie&&y.win?v&&typeof v.addRule==b&&v.addRule(c,d):v&&typeof i.createTextNode!=a&&v.appendChild(i.createTextNode(c+" {"+d+"}"))}}function S(a){var b=y.pv,c=a.split(".");c[0]=parseInt(c[0],10),c[1]=parseInt(c[1],10)||0,c[2]=parseInt(c[2],10)||0;return b[0]>c[0]||b[0]==c[0]&&b[1]>c[1]||b[0]==c[0]&&b[1]==c[1]&&b[2]>=c[2]?!0:!1}function R(a,b,c){a.attachEvent(b,c),o[o.length]=[a,b,c]}function Q(a){return i.createElement(a)}function P(a){var b=null;try{b=i.getElementById(a)}catch(c){}return b}function O(a){var b=P(a);if(b){for(var c in b)typeof b[c]=="function"&&(b[c]=null);b.parentNode.removeChild(b)}}function N(a){var b=P(a);b&&b.nodeName=="OBJECT"&&(y.ie&&y.win?(b.style.display="none",function(){b.readyState==4?O(a):setTimeout(arguments.callee,10)}()):b.parentNode.removeChild(b))}function M(a,b,c){var d=Q("param");d.setAttribute("name",b),d.setAttribute("value",c),a.appendChild(d)}function L(c,d,f){var g,h=P(f);if(y.wk&&y.wk<312)return g;if(h){typeof c.id==a&&(c.id=f);if(y.ie&&y.win){var i="";for(var j in c)c[j]!=Object.prototype[j]&&(j.toLowerCase()=="data"?d.movie=c[j]:j.toLowerCase()=="styleclass"?i+=' class="'+c[j]+'"':j.toLowerCase()!="classid"&&(i+=" "+j+'="'+c[j]+'"'));var k="";for(var l in d)d[l]!=Object.prototype[l]&&(k+='<param name="'+l+'" value="'+d[l]+'" />');h.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+i+">"+k+"</object>",n[n.length]=c.id,g=P(c.id)}else{var m=Q(b);m.setAttribute("type",e);for(var o in c)c[o]!=Object.prototype[o]&&(o.toLowerCase()=="styleclass"?m.setAttribute("class",c[o]):o.toLowerCase()!="classid"&&m.setAttribute(o,c[o]));for(var p in d)d[p]!=Object.prototype[p]&&p.toLowerCase()!="movie"&&M(m,p,d[p]);h.parentNode.replaceChild(m,h),g=m}}return g}function K(a){var c=Q("div");if(y.win&&y.ie)c.innerHTML=a.innerHTML;else{var d=a.getElementsByTagName(b)[0];if(d){var e=d.childNodes;if(e){var f=e.length;for(var g=0;g<f;g++)(e[g].nodeType!=1||e[g].nodeName!="PARAM")&&e[g].nodeType!=8&&c.appendChild(e[g].cloneNode(!0))}}}return c}function J(a){if(y.ie&&y.win&&a.readyState!=4){var b=Q("div");a.parentNode.insertBefore(b,a),b.parentNode.replaceChild(K(a),b),a.style.display="none",function(){a.readyState==4?a.parentNode.removeChild(a):setTimeout(arguments.callee,10)}()}else a.parentNode.replaceChild(K(a),a)}function I(b,c,d,e){u=!0,r=e||null,s={success:!1,id:d};var g=P(d);if(g){g.nodeName=="OBJECT"?(p=K(g),q=null):(p=g,q=d),b.id=f;if(typeof b.width==a||!/%$/.test(b.width)&&parseInt(b.width,10)<310)b.width="310";if(typeof b.height==a||!/%$/.test(b.height)&&parseInt(b.height,10)<137)b.height="137";i.title=i.title.slice(0,47)+" - Flash Player Installation";var j=y.ie&&y.win?"ActiveX":"PlugIn",k="MMredirectURL="+h.location.toString().replace(/&/g,"%26")+"&MMplayerType="+j+"&MMdoctitle="+i.title;typeof c.flashvars!=a?c.flashvars+="&"+k:c.flashvars=k;if(y.ie&&y.win&&g.readyState!=4){var l=Q("div");d+="SWFObjectNew",l.setAttribute("id",d),g.parentNode.insertBefore(l,g),g.style.display="none",function(){g.readyState==4?g.parentNode.removeChild(g):setTimeout(arguments.callee,10)}()}L(b,c,d)}}function H(){return!u&&S("6.0.65")&&(y.win||y.mac)&&!(y.wk&&y.wk<312)}function G(c){var d=null,e=P(c);if(e&&e.nodeName=="OBJECT")if(typeof e.SetVariable!=a)d=e;else{var f=e.getElementsByTagName(b)[0];f&&(d=f)}return d}function F(){var b=m.length;if(b>0)for(var c=0;c<b;c++){var d=m[c].id,e=m[c].callbackFn,f={success:!1,id:d};if(y.pv[0]>0){var g=P(d);if(g)if(S(m[c].swfVersion)&&!(y.wk&&y.wk<312))U(d,!0),e&&(f.success=!0,f.ref=G(d),e(f));else if(m[c].expressInstall&&H()){var h={};h.data=m[c].expressInstall,h.width=g.getAttribute("width")||"0",h.height=g.getAttribute("height")||"0",g.getAttribute("class")&&(h.styleclass=g.getAttribute("class")),g.getAttribute("align")&&(h.align=g.getAttribute("align"));var i={},j=g.getElementsByTagName("param"),k=j.length;for(var l=0;l<k;l++)j[l].getAttribute("name").toLowerCase()!="movie"&&(i[j[l].getAttribute("name")]=j[l].getAttribute("value"));I(h,i,d,e)}else J(g),e&&e(f)}else{U(d,!0);if(e){var n=G(d);n&&typeof n.SetVariable!=a&&(f.success=!0,f.ref=n),e(f)}}}}function E(){var c=i.getElementsByTagName("body")[0],d=Q(b);d.setAttribute("type",e);var f=c.appendChild(d);if(f){var g=0;(function(){if(typeof f.GetVariable!=a){var b=f.GetVariable("$version");b&&(b=b.split(" ")[1].split(","),y.pv=[parseInt(b[0],10),parseInt(b[1],10),parseInt(b[2],10)])}else if(g<10){g++,setTimeout(arguments.callee,10);return}c.removeChild(d),f=null,F()})()}else F()}function D(){k?E():F()}function C(b){if(typeof h.addEventListener!=a)h.addEventListener("load",b,!1);else if(typeof i.addEventListener!=a)i.addEventListener("load",b,!1);else if(typeof h.attachEvent!=a)R(h,"onload",b);else if(typeof h.onload=="function"){var c=h.onload;h.onload=function(){c(),b()}}else h.onload=b}function B(a){t?a():l[l.length]=a}function A(){if(!t){try{var a=i.getElementsByTagName("body")[0].appendChild(Q("span"));a.parentNode.removeChild(a)}catch(b){return}t=!0;var c=l.length;for(var d=0;d<c;d++)l[d]()}}var a="undefined",b="object",c="Shockwave Flash",d="ShockwaveFlash.ShockwaveFlash",e="application/x-shockwave-flash",f="SWFObjectExprInst",g="onreadystatechange",h=window,i=document,j=navigator,k=!1,l=[D],m=[],n=[],o=[],p,q,r,s,t=!1,u=!1,v,w,x=!0,y=function(){var f=typeof i.getElementById!=a&&typeof i.getElementsByTagName!=a&&typeof i.createElement!=a,g=j.userAgent.toLowerCase(),l=j.platform.toLowerCase(),m=l?/win/.test(l):/win/.test(g),n=l?/mac/.test(l):/mac/.test(g),o=/webkit/.test(g)?parseFloat(g.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):!1,p=!1,q=[0,0,0],r=null;if(typeof j.plugins!=a&&typeof j.plugins[c]==b)r=j.plugins[c].description,r&&(typeof j.mimeTypes==a||!j.mimeTypes[e]||!!j.mimeTypes[e].enabledPlugin)&&(k=!0,p=!1,r=r.replace(/^.*\s+(\S+\s+\S+$)/,"$1"),q[0]=parseInt(r.replace(/^(.*)\..*$/,"$1"),10),q[1]=parseInt(r.replace(/^.*\.(.*)\s.*$/,"$1"),10),q[2]=/[a-zA-Z]/.test(r)?parseInt(r.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0);else if(typeof h.ActiveXObject!=a)try{var s=new ActiveXObject(d);s&&(r=s.GetVariable("$version"),r&&(p=!0,r=r.split(" ")[1].split(","),q=[parseInt(r[0],10),parseInt(r[1],10),parseInt(r[2],10)]))}catch(t){}return{w3:f,pv:q,wk:o,ie:p,win:m,mac:n}}(),z=function(){!y.w3||((typeof i.readyState!=a&&i.readyState=="complete"||typeof i.readyState==a&&(i.getElementsByTagName("body")[0]||i.body))&&A(),t||(typeof i.addEventListener!=a&&i.addEventListener("DOMContentLoaded",A,!1),y.ie&&y.win&&(i.attachEvent(g,function(){i.readyState=="complete"&&(i.detachEvent(g,arguments.callee),A())}),h==top&&function(){if(!t){try{i.documentElement.doScroll("left")}catch(a){setTimeout(arguments.callee,0);return}A()}}()),y.wk&&function(){if(!t){if(!/loaded|complete/.test(i.readyState)){setTimeout(arguments.callee,0);return}A()}}(),C(A)))}(),W=function(){y.ie&&y.win&&window.attachEvent("onunload",function(){var a=o.length;for(var b=0;b<a;b++)o[b][0].detachEvent(o[b][1],o[b][2]);var c=n.length;for(var d=0;d<c;d++)N(n[d]);for(var e in y)y[e]=null;y=null;for(var f in swfobject)swfobject[f]=null;swfobject=null})}();return{registerObject:function(a,b,c,d){if(y.w3&&a&&b){var e={};e.id=a,e.swfVersion=b,e.expressInstall=c,e.callbackFn=d,m[m.length]=e,U(a,!1)}else d&&d({success:!1,id:a})},getObjectById:function(a){if(y.w3)return G(a)},embedSWF:function(c,d,e,f,g,h,i,j,k,l){var m={success:!1,id:d};y.w3&&!(y.wk&&y.wk<312)&&c&&d&&e&&f&&g?(U(d,!1),B(function(){e+="",f+="";var n={};if(k&&typeof k===b)for(var o in k)n[o]=k[o];n.data=c,n.width=e,n.height=f;var p={};if(j&&typeof j===b)for(var q in j)p[q]=j[q];if(i&&typeof i===b)for(var r in i)typeof p.flashvars!=a?p.flashvars+="&"+r+"="+i[r]:p.flashvars=r+"="+i[r];if(S(g)){var s=L(n,p,d);n.id==d&&U(d,!0),m.success=!0,m.ref=s}else{if(h&&H()){n.data=h,I(n,p,d,l);return}U(d,!0)}l&&l(m)})):l&&l(m)},switchOffAutoHideShow:function(){x=!1},ua:y,getFlashPlayerVersion:function(){return{major:y.pv[0],minor:y.pv[1],release:y.pv[2]}},hasFlashPlayerVersion:S,createSWF:function(a,b,c){return y.w3?L(a,b,c):undefined},showExpressInstall:function(a,b,c,d){y.w3&&H()&&I(a,b,c,d)},removeSWF:function(a){y.w3&&N(a)},createCSS:function(a,b,c,d){y.w3&&T(a,b,c,d)},addDomLoadEvent:B,addLoadEvent:C,getQueryParamValue:function(a){var b=i.location.search||i.location.hash;if(b){/\?/.test(b)&&(b=b.split("?")[1]);if(a==null)return V(b);var c=b.split("&");for(var d=0;d<c.length;d++)if(c[d].substring(0,c[d].indexOf("="))==a)return V(c[d].substring(c[d].indexOf("=")+1))}return""},expressInstallCallback:function(){if(u){var a=P(f);a&&p&&(a.parentNode.replaceChild(p,a),q&&(U(q,!0),y.ie&&y.win&&(p.style.display="block")),r&&r(s)),u=!1}}}}();(function(){if(!window.WebSocket){var a=window.console;if(!a||!a.log||!a.error)a={log:function(){},error:function(){}};if(!swfobject.hasFlashPlayerVersion("10.0.0")){a.error("Flash Player >= 10.0.0 is required.");return}location.protocol=="file:"&&a.error("WARNING: web-socket-js doesn't work in file:///... URL unless you set Flash Security Settings properly. Open the page via Web server i.e. http://..."),WebSocket=function(a,b,c,d,e){var f=this;f.__id=WebSocket.__nextId++,WebSocket.__instances[f.__id]=f,f.readyState=WebSocket.CONNECTING,f.bufferedAmount=0,f.__events={},b?typeof b=="string"&&(b=[b]):b=[],setTimeout(function(){WebSocket.__addTask(function(){WebSocket.__flash.create(f.__id,a,b,c||null,d||0,e||null)})},0)},WebSocket.prototype.send=function(a){if(this.readyState==WebSocket.CONNECTING)throw"INVALID_STATE_ERR: Web Socket connection has not been established";var b=WebSocket.__flash.send(this.__id,encodeURIComponent(a));if(b<0)return!0;this.bufferedAmount+=b;return!1},WebSocket.prototype.close=function(){this.readyState!=WebSocket.CLOSED&&this.readyState!=WebSocket.CLOSING&&(this.readyState=WebSocket.CLOSING,WebSocket.__flash.close(this.__id))},WebSocket.prototype.addEventListener=function(a,b,c){a in this.__events||(this.__events[a]=[]),this.__events[a].push(b)},WebSocket.prototype.removeEventListener=function(a,b,c){if(a in this.__events){var d=this.__events[a];for(var e=d.length-1;e>=0;--e)if(d[e]===b){d.splice(e,1);break}}},WebSocket.prototype.dispatchEvent=function(a){var b=this.__events[a.type]||[];for(var c=0;c<b.length;++c)b[c](a);var d=this["on"+a.type];d&&d(a)},WebSocket.prototype.__handleEvent=function(a){"readyState"in a&&(this.readyState=a.readyState),"protocol"in a&&(this.protocol=a.protocol);var b;if(a.type=="open"||a.type=="error")b=this.__createSimpleEvent(a.type);else if(a.type=="close")b=this.__createSimpleEvent("close");else{if(a.type!="message")throw"unknown event type: "+a.type;var c=decodeURIComponent(a.message);b=this.__createMessageEvent("message",c)}this.dispatchEvent(b)},WebSocket.prototype.__createSimpleEvent=function(a){if(document.createEvent&&window.Event){var b=document.createEvent("Event");b.initEvent(a,!1,!1);return b}return{type:a,bubbles:!1,cancelable:!1}},WebSocket.prototype.__createMessageEvent=function(a,b){if(document.createEvent&&window.MessageEvent&&!window.opera){var c=document.createEvent("MessageEvent");c.initMessageEvent("message",!1,!1,b,null,null,window,null);return c}return{type:a,data:b,bubbles:!1,cancelable:!1}},WebSocket.CONNECTING=0,WebSocket.OPEN=1,WebSocket.CLOSING=2,WebSocket.CLOSED=3,WebSocket.__flash=null,WebSocket.__instances={},WebSocket.__tasks=[],WebSocket.__nextId=0,WebSocket.loadFlashPolicyFile=function(a){WebSocket.__addTask(function(){WebSocket.__flash.loadManualPolicyFile(a)})},WebSocket.__initialize=function(){if(!WebSocket.__flash){WebSocket.__swfLocation&&(window.WEB_SOCKET_SWF_LOCATION=WebSocket.__swfLocation);if(!window.WEB_SOCKET_SWF_LOCATION){a.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");return}var b=document.createElement("div");b.id="webSocketContainer",b.style.position="absolute",WebSocket.__isFlashLite()?(b.style.left="0px",b.style.top="0px"):(b.style.left="-100px",b.style.top="-100px");var c=document.createElement("div");c.id="webSocketFlash",b.appendChild(c),document.body.appendChild(b),swfobject.embedSWF(WEB_SOCKET_SWF_LOCATION,"webSocketFlash","1","1","10.0.0",null,null,{hasPriority:!0,swliveconnect:!0,allowScriptAccess:"always"},null,function(b){b.success||a.error("[WebSocket] swfobject.embedSWF failed")})}},WebSocket.__onFlashInitialized=function(){setTimeout(function(){WebSocket.__flash=document.getElementById("webSocketFlash"),WebSocket.__flash.setCallerUrl(location.href),WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);for(var a=0;a<WebSocket.__tasks.length;++a)WebSocket.__tasks[a]();WebSocket.__tasks=[]},0)},WebSocket.__onFlashEvent=function(){setTimeout(function(){try{var b=WebSocket.__flash.receiveEvents();for(var c=0;c<b.length;++c)WebSocket.__instances[b[c].webSocketId].__handleEvent(b[c])}catch(d){a.error(d)}},0);return!0},WebSocket.__log=function(b){a.log(decodeURIComponent(b))},WebSocket.__error=function(b){a.error(decodeURIComponent(b))},WebSocket.__addTask=function(a){WebSocket.__flash?a():WebSocket.__tasks.push(a)},WebSocket.__isFlashLite=function(){if(!window.navigator||!window.navigator.mimeTypes)return!1;var a=window.navigator.mimeTypes["application/x-shockwave-flash"];return!a||!a.enabledPlugin||!a.enabledPlugin.filename?!1:a.enabledPlugin.filename.match(/flashlite/i)?!0:!1},window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION||(window.addEventListener?window.addEventListener("load",function(){WebSocket.__initialize()},!1):window.attachEvent("onload",function(){WebSocket.__initialize()}))}})(),function(a,b,c){function e(){}function d(a){!a||(b.Transport.apply(this,arguments),this.sendBuffer=[])}a.XHR=d,b.util.inherit(d,b.Transport),d.prototype.open=function(){this.socket.setBuffer(!1),this.onOpen(),this.get(),this.setCloseTimeout();return this},d.prototype.payload=function(a){var c=[];for(var d=0,e=a.length;d<e;d++)c.push(b.parser.encodePacket(a[d]));this.send(b.parser.encodePayload(c))},d.prototype.send=function(a){this.post(a);return this},d.prototype.post=function(a){function f(){this.onload=e,b.socket.setBuffer(!1)}function d(){this.readyState==4&&(this.onreadystatechange=e,b.posting=!1,this.status==200?b.socket.setBuffer(!1):b.onClose())}var b=this;this.socket.setBuffer(!0),this.sendXHR=this.request("POST"),c.XDomainRequest&&this.sendXHR instanceof XDomainRequest?this.sendXHR.onload=this.sendXHR.onerror=f:this.sendXHR.onreadystatechange=d,this.sendXHR.send(a)},d.prototype.close=function(){this.onClose();return this},d.prototype.request=function(a){var c=b.util.request(this.socket.isXDomain()),d=b.util.query(this.socket.options.query,"t="+ +(new Date));c.open(a||"GET",this.prepareUrl()+d,!0);if(a=="POST")try{c.setRequestHeader?c.setRequestHeader("Content-type","text/plain;charset=UTF-8"):c.contentType="text/plain"}catch(e){}return c},d.prototype.scheme=function(){return this.socket.options.secure?"https":"http"},d.check=function(a,c){try{if(b.util.request(c))return!0}catch(d){}return!1},d.xdomainCheck=function(){return d.check(null,!0)}}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports,this),function(a,b){function c(a){b.Transport.XHR.apply(this,arguments)}a.htmlfile=c,b.util.inherit(c,b.Transport.XHR),c.prototype.name="htmlfile",c.prototype.get=function(){this.doc=new ActiveXObject("htmlfile"),this.doc.open(),this.doc.write("<html></html>"),this.doc.close(),this.doc.parentWindow.s=this;var a=this.doc.createElement("div");a.className="socketio",this.doc.body.appendChild(a),this.iframe=this.doc.createElement("iframe"),a.appendChild(this.iframe);var c=this,d=b.util.query(this.socket.options.query,"t="+ +(new Date));this.iframe.src=this.prepareUrl()+d,b.util.on(window,"unload",function(){c.destroy()})},c.prototype._=function(a,b){this.onData(a);try{var c=b.getElementsByTagName("script")[0];c.parentNode.removeChild(c)}catch(d){}},c.prototype.destroy=function(){if(this.iframe){try{this.iframe.src="about:blank"}catch(a){}this.doc=null,this.iframe.parentNode.removeChild(this.iframe),this.iframe=null,CollectGarbage()}},c.prototype.close=function(){this.destroy();return b.Transport.XHR.prototype.close.call(this)},c.check=function(){if("ActiveXObject"in window)try{var a=new ActiveXObject("htmlfile");return a&&b.Transport.XHR.check()}catch(c){}return!1},c.xdomainCheck=function(){return!1},b.transports.push("htmlfile")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b,c){function e(){}function d(){b.Transport.XHR.apply(this,arguments)}a["xhr-polling"]=d,b.util.inherit(d,b.Transport.XHR),b.util.merge(d,b.Transport.XHR),d.prototype.name="xhr-polling",d.prototype.open=function(){var a=this;b.Transport.XHR.prototype.open.call(a);return!1},d.prototype.get=function(){function d(){this.onload=e,a.onData(this.responseText),a.get()}function b(){this.readyState==4&&(this.onreadystatechange=e,this.status==200?(a.onData(this.responseText),a.get()):a.onClose())}if(!!this.open){var a=this;this.xhr=this.request(),c.XDomainRequest&&this.xhr instanceof XDomainRequest?this.xhr.onload=this.xhr.onerror=d:this.xhr.onreadystatechange=b,this.xhr.send(null)}},d.prototype.onClose=function(){b.Transport.XHR.prototype.onClose.call(this);if(this.xhr){this.xhr.onreadystatechange=this.xhr.onload=e;try{this.xhr.abort()}catch(a){}this.xhr=null}},d.prototype.ready=function(a,c){var d=this;b.util.defer(function(){c.call(d)})},b.transports.push("xhr-polling")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports,this),function(a,b){function c(a){b.Transport["xhr-polling"].apply(this,arguments),this.index=b.j.length;var c=this;b.j.push(function(a){c._(a)})}a["jsonp-polling"]=c,b.util.inherit(c,b.Transport["xhr-polling"]),c.prototype.name="jsonp-polling",c.prototype.post=function(a){function j(){c.iframe&&c.form.removeChild(c.iframe);try{h=document.createElement('<iframe name="'+c.iframeId+'">')}catch(a){h=document.createElement("iframe"),h.name=c.iframeId}h.id=c.iframeId,c.form.appendChild(h),c.iframe=h}function i(){j(),c.socket.setBuffer(!1)}var c=this,d=b.util.query(this.socket.options.query,"t="+ +(new Date)+"&i="+this.index);if(!this.form){var e=document.createElement("form"),f=document.createElement("textarea"),g=this.iframeId="socketio_iframe_"+this.index,h;e.className="socketio",e.style.position="absolute",e.style.top="-1000px",e.style.left="-1000px",e.target=g,e.method="POST",e.setAttribute("accept-charset","utf-8"),f.name="d",e.appendChild(f),document.body.appendChild(e),this.form=e,this.area=f}this.form.action=this.prepareUrl()+d,j(),this.area.value=a;try{this.form.submit()}catch(k){}this.iframe.attachEvent?h.onreadystatechange=function(){c.iframe.readyState=="complete"&&i()}:this.iframe.onload=i,this.socket.setBuffer(!0)},c.prototype.get=function(){var a=this,c=document.createElement("script"),d=b.util.query(this.socket.options.query,"t="+ +(new Date)+"&i="+this.index);this.script&&(this.script.parentNode.removeChild(this.script),this.script=null),c.async=!0,c.src=this.prepareUrl()+d,c.onerror=function(){a.onClose()};var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(c,e),this.script=c},c.prototype._=function(a){this.onData(a),this.open&&this.get();return this},c.check=function(){return!0},c.xdomainCheck=function(){return!0},b.transports.push("jsonp-polling")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports)
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/public/stylesheets/bootstrap.min.css
@@ -0,0 +1,356 @@
+html,body{margin:0;padding:0;}
+h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,cite,code,del,dfn,em,img,q,s,samp,small,strike,strong,sub,sup,tt,var,dd,dl,dt,li,ol,ul,fieldset,form,label,legend,button,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;font-weight:normal;font-style:normal;font-size:100%;line-height:1;font-family:inherit;}
+table{border-collapse:collapse;border-spacing:0;}
+ol,ul{list-style:none;}
+q:before,q:after,blockquote:before,blockquote:after{content:"";}
+html{overflow-y:scroll;font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}
+a:focus{outline:thin dotted;}
+a:hover,a:active{outline:0;}
+article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;}
+audio,canvas,video{display:inline-block;*display:inline;*zoom:1;}
+audio:not([controls]){display:none;}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}
+sup{top:-0.5em;}
+sub{bottom:-0.25em;}
+img{border:0;-ms-interpolation-mode:bicubic;}
+button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;}
+button,input{line-height:normal;*overflow:visible;}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}
+button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;}
+input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;}
+input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}
+textarea{overflow:auto;vertical-align:top;}
+body{background-color:#ffffff;margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:18px;color:#404040;}
+.container{width:940px;margin-left:auto;margin-right:auto;zoom:1;}.container:before,.container:after{display:table;content:"";zoom:1;}
+.container:after{clear:both;}
+.container-fluid{position:relative;min-width:940px;padding-left:20px;padding-right:20px;zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";zoom:1;}
+.container-fluid:after{clear:both;}
+.container-fluid>.sidebar{position:absolute;top:0;left:20px;width:220px;}
+.container-fluid>.content{margin-left:240px;}
+a{color:#0069d6;text-decoration:none;line-height:inherit;font-weight:inherit;}a:hover{color:#00438a;text-decoration:underline;}
+.pull-right{float:right;}
+.pull-left{float:left;}
+.hide{display:none;}
+.show{display:block;}
+.row{zoom:1;margin-left:-20px;}.row:before,.row:after{display:table;content:"";zoom:1;}
+.row:after{clear:both;}
+.row>[class*="span"]{display:inline;float:left;margin-left:20px;}
+.span1{width:40px;}
+.span2{width:100px;}
+.span3{width:160px;}
+.span4{width:220px;}
+.span5{width:280px;}
+.span6{width:340px;}
+.span7{width:400px;}
+.span8{width:460px;}
+.span9{width:520px;}
+.span10{width:580px;}
+.span11{width:640px;}
+.span12{width:700px;}
+.span13{width:760px;}
+.span14{width:820px;}
+.span15{width:880px;}
+.span16{width:940px;}
+.span17{width:1000px;}
+.span18{width:1060px;}
+.span19{width:1120px;}
+.span20{width:1180px;}
+.span21{width:1240px;}
+.span22{width:1300px;}
+.span23{width:1360px;}
+.span24{width:1420px;}
+.row>.offset1{margin-left:80px;}
+.row>.offset2{margin-left:140px;}
+.row>.offset3{margin-left:200px;}
+.row>.offset4{margin-left:260px;}
+.row>.offset5{margin-left:320px;}
+.row>.offset6{margin-left:380px;}
+.row>.offset7{margin-left:440px;}
+.row>.offset8{margin-left:500px;}
+.row>.offset9{margin-left:560px;}
+.row>.offset10{margin-left:620px;}
+.row>.offset11{margin-left:680px;}
+.row>.offset12{margin-left:740px;}
+.span-one-third{width:300px;}
+.span-two-thirds{width:620px;}
+.row>.offset-one-third{margin-left:340px;}
+.row>.offset-two-thirds{margin-left:660px;}
+p{font-size:13px;font-weight:normal;line-height:18px;margin-bottom:9px;}p small{font-size:11px;color:#bfbfbf;}
+h1,h2,h3,h4,h5,h6{font-weight:bold;color:#404040;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{color:#bfbfbf;}
+h1{margin-bottom:18px;font-size:30px;line-height:36px;}h1 small{font-size:18px;}
+h2{font-size:24px;line-height:36px;}h2 small{font-size:14px;}
+h3,h4,h5,h6{line-height:36px;}
+h3{font-size:18px;}h3 small{font-size:14px;}
+h4{font-size:16px;}h4 small{font-size:12px;}
+h5{font-size:14px;}
+h6{font-size:13px;color:#bfbfbf;text-transform:uppercase;}
+ul,ol{margin:0 0 18px 25px;}
+ul ul,ul ol,ol ol,ol ul{margin-bottom:0;}
+ul{list-style:disc;}
+ol{list-style:decimal;}
+li{line-height:18px;color:#808080;}
+ul.unstyled{list-style:none;margin-left:0;}
+dl{margin-bottom:18px;}dl dt,dl dd{line-height:18px;}
+dl dt{font-weight:bold;}
+dl dd{margin-left:9px;}
+hr{margin:20px 0 19px;border:0;border-bottom:1px solid #eee;}
+strong{font-style:inherit;font-weight:bold;}
+em{font-style:italic;font-weight:inherit;line-height:inherit;}
+.muted{color:#bfbfbf;}
+blockquote{margin-bottom:18px;border-left:5px solid #eee;padding-left:15px;}blockquote p{font-size:14px;font-weight:300;line-height:18px;margin-bottom:0;}
+blockquote small{display:block;font-size:12px;font-weight:300;line-height:18px;color:#bfbfbf;}blockquote small:before{content:'\2014 \00A0';}
+address{display:block;line-height:18px;margin-bottom:18px;}
+code,pre{padding:0 3px 2px;font-family:Monaco, Andale Mono, Courier New, monospace;font-size:12px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+code{background-color:#fee9cc;color:rgba(0, 0, 0, 0.75);padding:1px 3px;}
+pre{background-color:#f5f5f5;display:block;padding:8.5px;margin:0 0 18px;line-height:18px;font-size:12px;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;white-space:pre;white-space:pre-wrap;word-wrap:break-word;}
+form{margin-bottom:18px;}
+fieldset{margin-bottom:18px;padding-top:18px;}fieldset legend{display:block;padding-left:150px;font-size:19.5px;line-height:1;color:#404040;*padding:0 0 5px 145px;*line-height:1.5;}
+form .clearfix{margin-bottom:18px;zoom:1;}form .clearfix:before,form .clearfix:after{display:table;content:"";zoom:1;}
+form .clearfix:after{clear:both;}
+label,input,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:normal;}
+label{padding-top:6px;font-size:13px;line-height:18px;float:left;width:130px;text-align:right;color:#404040;}
+form .input{margin-left:150px;}
+input[type=checkbox],input[type=radio]{cursor:pointer;}
+input,textarea,select,.uneditable-input{display:inline-block;width:210px;height:18px;padding:4px;font-size:13px;line-height:18px;color:#808080;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+select{padding:initial;}
+input[type=checkbox],input[type=radio]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;border:none;}
+input[type=file]{background-color:#ffffff;padding:initial;border:initial;line-height:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
+input[type=button],input[type=reset],input[type=submit]{width:auto;height:auto;}
+select,input[type=file]{height:27px;*height:auto;line-height:27px;*margin-top:4px;}
+select[multiple]{height:inherit;background-color:#ffffff;}
+textarea{height:auto;}
+.uneditable-input{background-color:#ffffff;display:block;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;}
+:-moz-placeholder{color:#bfbfbf;}
+::-webkit-input-placeholder{color:#bfbfbf;}
+input,textarea{-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);}
+input:focus,textarea:focus{outline:0;border-color:rgba(82, 168, 236, 0.8);-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);}
+input[type=file]:focus,input[type=checkbox]:focus,select:focus{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;outline:1px dotted #666;}
+form .clearfix.error>label,form .clearfix.error .help-block,form .clearfix.error .help-inline{color:#b94a48;}
+form .clearfix.error input,form .clearfix.error textarea{color:#b94a48;border-color:#ee5f5b;}form .clearfix.error input:focus,form .clearfix.error textarea:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;}
+form .clearfix.error .input-prepend .add-on,form .clearfix.error .input-append .add-on{color:#b94a48;background-color:#fce6e6;border-color:#b94a48;}
+form .clearfix.warning>label,form .clearfix.warning .help-block,form .clearfix.warning .help-inline{color:#c09853;}
+form .clearfix.warning input,form .clearfix.warning textarea{color:#c09853;border-color:#ccae64;}form .clearfix.warning input:focus,form .clearfix.warning textarea:focus{border-color:#be9a3f;-webkit-box-shadow:0 0 6px #e5d6b1;-moz-box-shadow:0 0 6px #e5d6b1;box-shadow:0 0 6px #e5d6b1;}
+form .clearfix.warning .input-prepend .add-on,form .clearfix.warning .input-append .add-on{color:#c09853;background-color:#d2b877;border-color:#c09853;}
+form .clearfix.success>label,form .clearfix.success .help-block,form .clearfix.success .help-inline{color:#468847;}
+form .clearfix.success input,form .clearfix.success textarea{color:#468847;border-color:#57a957;}form .clearfix.success input:focus,form .clearfix.success textarea:focus{border-color:#458845;-webkit-box-shadow:0 0 6px #9acc9a;-moz-box-shadow:0 0 6px #9acc9a;box-shadow:0 0 6px #9acc9a;}
+form .clearfix.success .input-prepend .add-on,form .clearfix.success .input-append .add-on{color:#468847;background-color:#bcddbc;border-color:#468847;}
+.input-mini,input.mini,textarea.mini,select.mini{width:60px;}
+.input-small,input.small,textarea.small,select.small{width:90px;}
+.input-medium,input.medium,textarea.medium,select.medium{width:150px;}
+.input-large,input.large,textarea.large,select.large{width:210px;}
+.input-xlarge,input.xlarge,textarea.xlarge,select.xlarge{width:270px;}
+.input-xxlarge,input.xxlarge,textarea.xxlarge,select.xxlarge{width:530px;}
+textarea.xxlarge{overflow-y:auto;}
+input.span1,textarea.span1{display:inline-block;float:none;width:30px;margin-left:0;}
+input.span2,textarea.span2{display:inline-block;float:none;width:90px;margin-left:0;}
+input.span3,textarea.span3{display:inline-block;float:none;width:150px;margin-left:0;}
+input.span4,textarea.span4{display:inline-block;float:none;width:210px;margin-left:0;}
+input.span5,textarea.span5{display:inline-block;float:none;width:270px;margin-left:0;}
+input.span6,textarea.span6{display:inline-block;float:none;width:330px;margin-left:0;}
+input.span7,textarea.span7{display:inline-block;float:none;width:390px;margin-left:0;}
+input.span8,textarea.span8{display:inline-block;float:none;width:450px;margin-left:0;}
+input.span9,textarea.span9{display:inline-block;float:none;width:510px;margin-left:0;}
+input.span10,textarea.span10{display:inline-block;float:none;width:570px;margin-left:0;}
+input.span11,textarea.span11{display:inline-block;float:none;width:630px;margin-left:0;}
+input.span12,textarea.span12{display:inline-block;float:none;width:690px;margin-left:0;}
+input.span13,textarea.span13{display:inline-block;float:none;width:750px;margin-left:0;}
+input.span14,textarea.span14{display:inline-block;float:none;width:810px;margin-left:0;}
+input.span15,textarea.span15{display:inline-block;float:none;width:870px;margin-left:0;}
+input.span16,textarea.span16{display:inline-block;float:none;width:930px;margin-left:0;}
+input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{background-color:#f5f5f5;border-color:#ddd;cursor:not-allowed;}
+.actions{background:#f5f5f5;margin-top:18px;margin-bottom:18px;padding:17px 20px 18px 150px;border-top:1px solid #ddd;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;}.actions .secondary-action{float:right;}.actions .secondary-action a{line-height:30px;}.actions .secondary-action a:hover{text-decoration:underline;}
+.help-inline,.help-block{font-size:13px;line-height:18px;color:#bfbfbf;}
+.help-inline{padding-left:5px;*position:relative;*top:-5px;}
+.help-block{display:block;max-width:600px;}
+.inline-inputs{color:#808080;}.inline-inputs span{padding:0 2px 0 1px;}
+.input-prepend input,.input-append input{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
+.input-prepend .add-on,.input-append .add-on{position:relative;background:#f5f5f5;border:1px solid #ccc;z-index:2;float:left;display:block;width:auto;min-width:16px;height:18px;padding:4px 4px 4px 5px;margin-right:-1px;font-weight:normal;line-height:18px;color:#bfbfbf;text-align:center;text-shadow:0 1px 0 #ffffff;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
+.input-prepend .active,.input-append .active{background:#a9dba9;border-color:#46a546;}
+.input-prepend .add-on{*margin-top:1px;}
+.input-append input{float:left;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
+.input-append .add-on{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;margin-right:0;margin-left:-1px;}
+.inputs-list{margin:0 0 5px;width:100%;}.inputs-list li{display:block;padding:0;width:100%;}
+.inputs-list label{display:block;float:none;width:auto;padding:0;margin-left:20px;line-height:18px;text-align:left;white-space:normal;}.inputs-list label strong{color:#808080;}
+.inputs-list label small{font-size:11px;font-weight:normal;}
+.inputs-list .inputs-list{margin-left:25px;margin-bottom:10px;padding-top:0;}
+.inputs-list:first-child{padding-top:6px;}
+.inputs-list li+li{padding-top:2px;}
+.inputs-list input[type=radio],.inputs-list input[type=checkbox]{margin-bottom:0;margin-left:-20px;float:left;}
+.form-stacked{padding-left:20px;}.form-stacked fieldset{padding-top:9px;}
+.form-stacked legend{padding-left:0;}
+.form-stacked label{display:block;float:none;width:auto;font-weight:bold;text-align:left;line-height:20px;padding-top:0;}
+.form-stacked .clearfix{margin-bottom:9px;}.form-stacked .clearfix div.input{margin-left:0;}
+.form-stacked .inputs-list{margin-bottom:0;}.form-stacked .inputs-list li{padding-top:0;}.form-stacked .inputs-list li label{font-weight:normal;padding-top:0;}
+.form-stacked div.clearfix.error{padding-top:10px;padding-bottom:10px;padding-left:10px;margin-top:0;margin-left:-10px;}
+.form-stacked .actions{margin-left:-20px;padding-left:20px;}
+table{width:100%;margin-bottom:18px;padding:0;font-size:13px;border-collapse:collapse;}table th,table td{padding:10px 10px 9px;line-height:18px;text-align:left;}
+table th{padding-top:9px;font-weight:bold;vertical-align:middle;}
+table td{vertical-align:top;border-top:1px solid #ddd;}
+table tbody th{border-top:1px solid #ddd;vertical-align:top;}
+.condensed-table th,.condensed-table td{padding:5px 5px 4px;}
+.bordered-table{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.bordered-table th+th,.bordered-table td+td,.bordered-table th+td{border-left:1px solid #ddd;}
+.bordered-table thead tr:first-child th:first-child,.bordered-table tbody tr:first-child td:first-child{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;}
+.bordered-table thead tr:first-child th:last-child,.bordered-table tbody tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;}
+.bordered-table tbody tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;}
+.bordered-table tbody tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;}
+table .span1{width:20px;}
+table .span2{width:60px;}
+table .span3{width:100px;}
+table .span4{width:140px;}
+table .span5{width:180px;}
+table .span6{width:220px;}
+table .span7{width:260px;}
+table .span8{width:300px;}
+table .span9{width:340px;}
+table .span10{width:380px;}
+table .span11{width:420px;}
+table .span12{width:460px;}
+table .span13{width:500px;}
+table .span14{width:540px;}
+table .span15{width:580px;}
+table .span16{width:620px;}
+.zebra-striped tbody tr:nth-child(odd) td,.zebra-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;}
+.zebra-striped tbody tr:hover td,.zebra-striped tbody tr:hover th{background-color:#f5f5f5;}
+table .header{cursor:pointer;}table .header:after{content:"";float:right;margin-top:7px;border-width:0 4px 4px;border-style:solid;border-color:#000 transparent;visibility:hidden;}
+table .headerSortUp,table .headerSortDown{background-color:rgba(141, 192, 219, 0.25);text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);}
+table .header:hover:after{visibility:visible;}
+table .headerSortDown:after,table .headerSortDown:hover:after{visibility:visible;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;}
+table .headerSortUp:after{border-bottom:none;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000;visibility:visible;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;}
+table .blue{color:#049cdb;border-bottom-color:#049cdb;}
+table .headerSortUp.blue,table .headerSortDown.blue{background-color:#ade6fe;}
+table .green{color:#46a546;border-bottom-color:#46a546;}
+table .headerSortUp.green,table .headerSortDown.green{background-color:#cdeacd;}
+table .red{color:#9d261d;border-bottom-color:#9d261d;}
+table .headerSortUp.red,table .headerSortDown.red{background-color:#f4c8c5;}
+table .yellow{color:#ffc40d;border-bottom-color:#ffc40d;}
+table .headerSortUp.yellow,table .headerSortDown.yellow{background-color:#fff6d9;}
+table .orange{color:#f89406;border-bottom-color:#f89406;}
+table .headerSortUp.orange,table .headerSortDown.orange{background-color:#fee9cc;}
+table .purple{color:#7a43b6;border-bottom-color:#7a43b6;}
+table .headerSortUp.purple,table .headerSortDown.purple{background-color:#e2d5f0;}
+.topbar{height:40px;position:fixed;top:0;left:0;right:0;z-index:10000;overflow:visible;}.topbar a{color:#bfbfbf;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);}
+.topbar h3 a:hover,.topbar .brand:hover,.topbar ul .active>a{background-color:#333;background-color:rgba(255, 255, 255, 0.05);color:#ffffff;text-decoration:none;}
+.topbar h3{position:relative;}
+.topbar h3 a,.topbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;color:#ffffff;font-size:20px;font-weight:200;line-height:1;}
+.topbar p{margin:0;line-height:40px;}.topbar p a:hover{background-color:transparent;color:#ffffff;}
+.topbar form{float:left;margin:5px 0 0 0;position:relative;filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1;}
+.topbar form.pull-right{float:right;}
+.topbar input{background-color:#444;background-color:rgba(255, 255, 255, 0.3);font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:normal;font-weight:13px;line-height:1;padding:4px 9px;color:#ffffff;color:rgba(255, 255, 255, 0.75);border:1px solid #111;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.topbar input:-moz-placeholder{color:#e6e6e6;}
+.topbar input::-webkit-input-placeholder{color:#e6e6e6;}
+.topbar input:hover{background-color:#bfbfbf;background-color:rgba(255, 255, 255, 0.5);color:#ffffff;}
+.topbar input:focus,.topbar input.focused{outline:0;background-color:#ffffff;color:#404040;text-shadow:0 1px 0 #ffffff;border:0;padding:5px 10px;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);}
+.topbar-inner,.topbar .fill{background-color:#222;background-color:#222222;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#333333), to(#222222));background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #333333), color-stop(100%, #222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);}
+.topbar div>ul,.nav{display:block;float:left;margin:0 10px 0 0;position:relative;left:0;}.topbar div>ul>li,.nav>li{display:block;float:left;}
+.topbar div>ul a,.nav a{display:block;float:none;padding:10px 10px 11px;line-height:19px;text-decoration:none;}.topbar div>ul a:hover,.nav a:hover{color:#ffffff;text-decoration:none;}
+.topbar div>ul .active>a,.nav .active>a{background-color:#222;background-color:rgba(0, 0, 0, 0.5);}
+.topbar div>ul.secondary-nav,.nav.secondary-nav{float:right;margin-left:10px;margin-right:0;}.topbar div>ul.secondary-nav .menu-dropdown,.nav.secondary-nav .menu-dropdown,.topbar div>ul.secondary-nav .dropdown-menu,.nav.secondary-nav .dropdown-menu{right:0;border:0;}
+.topbar div>ul a.menu:hover,.nav a.menu:hover,.topbar div>ul li.open .menu,.nav li.open .menu,.topbar div>ul .dropdown-toggle:hover,.nav .dropdown-toggle:hover,.topbar div>ul .dropdown.open .dropdown-toggle,.nav .dropdown.open .dropdown-toggle{background:#444;background:rgba(255, 255, 255, 0.05);}
+.topbar div>ul .menu-dropdown,.nav .menu-dropdown,.topbar div>ul .dropdown-menu,.nav .dropdown-menu{background-color:#333;}.topbar div>ul .menu-dropdown a.menu,.nav .menu-dropdown a.menu,.topbar div>ul .dropdown-menu a.menu,.nav .dropdown-menu a.menu,.topbar div>ul .menu-dropdown .dropdown-toggle,.nav .menu-dropdown .dropdown-toggle,.topbar div>ul .dropdown-menu .dropdown-toggle,.nav .dropdown-menu .dropdown-toggle{color:#ffffff;}.topbar div>ul .menu-dropdown a.menu.open,.nav .menu-dropdown a.menu.open,.topbar div>ul .dropdown-menu a.menu.open,.nav .dropdown-menu a.menu.open,.topbar div>ul .menu-dropdown .dropdown-toggle.open,.nav .menu-dropdown .dropdown-toggle.open,.topbar div>ul .dropdown-menu .dropdown-toggle.open,.nav .dropdown-menu .dropdown-toggle.open{background:#444;background:rgba(255, 255, 255, 0.05);}
+.topbar div>ul .menu-dropdown li a,.nav .menu-dropdown li a,.topbar div>ul .dropdown-menu li a,.nav .dropdown-menu li a{color:#999;text-shadow:0 1px 0 rgba(0, 0, 0, 0.5);}.topbar div>ul .menu-dropdown li a:hover,.nav .menu-dropdown li a:hover,.topbar div>ul .dropdown-menu li a:hover,.nav .dropdown-menu li a:hover{background-color:#191919;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#292929), to(#191919));background-image:-moz-linear-gradient(top, #292929, #191919);background-image:-ms-linear-gradient(top, #292929, #191919);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #292929), color-stop(100%, #191919));background-image:-webkit-linear-gradient(top, #292929, #191919);background-image:-o-linear-gradient(top, #292929, #191919);background-image:linear-gradient(top, #292929, #191919);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#292929', endColorstr='#191919', GradientType=0);color:#ffffff;}
+.topbar div>ul .menu-dropdown .active a,.nav .menu-dropdown .active a,.topbar div>ul .dropdown-menu .active a,.nav .dropdown-menu .active a{color:#ffffff;}
+.topbar div>ul .menu-dropdown .divider,.nav .menu-dropdown .divider,.topbar div>ul .dropdown-menu .divider,.nav .dropdown-menu .divider{background-color:#222;border-color:#444;}
+.topbar ul .menu-dropdown li a,.topbar ul .dropdown-menu li a{padding:4px 15px;}
+li.menu,.dropdown{position:relative;}
+a.menu:after,.dropdown-toggle:after{width:0;height:0;display:inline-block;content:"&darr;";text-indent:-99999px;vertical-align:top;margin-top:8px;margin-left:4px;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #ffffff;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;}
+.menu-dropdown,.dropdown-menu{background-color:#ffffff;float:left;display:none;position:absolute;top:40px;z-index:900;min-width:160px;max-width:220px;_width:160px;margin-left:0;margin-right:0;padding:6px 0;zoom:1;border-color:#999;border-color:rgba(0, 0, 0, 0.2);border-style:solid;border-width:0 1px 1px;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.menu-dropdown li,.dropdown-menu li{float:none;display:block;background-color:none;}
+.menu-dropdown .divider,.dropdown-menu .divider{height:1px;margin:5px 0;overflow:hidden;background-color:#eee;border-bottom:1px solid #ffffff;}
+.topbar .dropdown-menu a,.dropdown-menu a{display:block;padding:4px 15px;clear:both;font-weight:normal;line-height:18px;color:#808080;text-shadow:0 1px 0 #ffffff;}.topbar .dropdown-menu a:hover,.dropdown-menu a:hover,.topbar .dropdown-menu a.hover,.dropdown-menu a.hover{background-color:#dddddd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#eeeeee), to(#dddddd));background-image:-moz-linear-gradient(top, #eeeeee, #dddddd);background-image:-ms-linear-gradient(top, #eeeeee, #dddddd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #eeeeee), color-stop(100%, #dddddd));background-image:-webkit-linear-gradient(top, #eeeeee, #dddddd);background-image:-o-linear-gradient(top, #eeeeee, #dddddd);background-image:linear-gradient(top, #eeeeee, #dddddd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#dddddd', GradientType=0);color:#404040;text-decoration:none;-webkit-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);}
+.open .menu,.dropdown.open .menu,.open .dropdown-toggle,.dropdown.open .dropdown-toggle{color:#ffffff;background:#ccc;background:rgba(0, 0, 0, 0.3);}
+.open .menu-dropdown,.dropdown.open .menu-dropdown,.open .dropdown-menu,.dropdown.open .dropdown-menu{display:block;}
+.tabs,.pills{margin:0 0 18px;padding:0;list-style:none;zoom:1;}.tabs:before,.pills:before,.tabs:after,.pills:after{display:table;content:"";zoom:1;}
+.tabs:after,.pills:after{clear:both;}
+.tabs>li,.pills>li{float:left;}.tabs>li>a,.pills>li>a{display:block;}
+.tabs{border-color:#ddd;border-style:solid;border-width:0 0 1px;}.tabs>li{position:relative;margin-bottom:-1px;}.tabs>li>a{padding:0 15px;margin-right:2px;line-height:34px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.tabs>li>a:hover{text-decoration:none;background-color:#eee;border-color:#eee #eee #ddd;}
+.tabs .active>a,.tabs .active>a:hover{color:#808080;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;}
+.tabs .menu-dropdown,.tabs .dropdown-menu{top:35px;border-width:1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;}
+.tabs a.menu:after,.tabs .dropdown-toggle:after{border-top-color:#999;margin-top:15px;margin-left:5px;}
+.tabs li.open.menu .menu,.tabs .open.dropdown .dropdown-toggle{border-color:#999;}
+.tabs li.open a.menu:after,.tabs .dropdown.open .dropdown-toggle:after{border-top-color:#555;}
+.pills a{margin:5px 3px 5px 0;padding:0 15px;line-height:30px;text-shadow:0 1px 1px #ffffff;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}.pills a:hover{color:#ffffff;text-decoration:none;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);background-color:#00438a;}
+.pills .active a{color:#ffffff;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);background-color:#0069d6;}
+.pills-vertical>li{float:none;}
+.tab-content>.tab-pane,.pill-content>.pill-pane,.tab-content>div,.pill-content>div{display:none;}
+.tab-content>.active,.pill-content>.active{display:block;}
+.breadcrumb{padding:7px 14px;margin:0 0 18px;background-color:#f5f5f5;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ffffff), to(#f5f5f5));background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;}.breadcrumb li{display:inline;text-shadow:0 1px 0 #ffffff;}
+.breadcrumb .divider{padding:0 5px;color:#bfbfbf;}
+.breadcrumb .active a{color:#404040;}
+.hero-unit{background-color:#f5f5f5;margin-bottom:30px;padding:60px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;}
+.hero-unit p{font-size:18px;font-weight:200;line-height:27px;}
+footer{margin-top:17px;padding-top:17px;border-top:1px solid #eee;}
+.page-header{margin-bottom:17px;border-bottom:1px solid #ddd;-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}.page-header h1{margin-bottom:8px;}
+.btn.danger,.alert-message.danger,.btn.danger:hover,.alert-message.danger:hover,.btn.error,.alert-message.error,.btn.error:hover,.alert-message.error:hover,.btn.success,.alert-message.success,.btn.success:hover,.alert-message.success:hover,.btn.info,.alert-message.info,.btn.info:hover,.alert-message.info:hover{color:#ffffff;}
+.btn .close,.alert-message .close{font-family:Arial,sans-serif;line-height:18px;}
+.btn.danger,.alert-message.danger,.btn.error,.alert-message.error{background-color:#c43c35;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#c43c35 #c43c35 #882a25;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
+.btn.success,.alert-message.success{background-color:#57a957;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#57a957 #57a957 #3d773d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
+.btn.info,.alert-message.info{background-color:#339bb9;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#339bb9 #339bb9 #22697d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
+.btn{cursor:pointer;display:inline-block;background-color:#e6e6e6;background-repeat:no-repeat;background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6);background-image:-ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);padding:5px 14px 6px;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);color:#333;font-size:13px;line-height:normal;border:1px solid #ccc;border-bottom-color:#bbb;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-webkit-transition:0.1s linear all;-moz-transition:0.1s linear all;-ms-transition:0.1s linear all;-o-transition:0.1s linear all;transition:0.1s linear all;}.btn:hover{background-position:0 -15px;color:#333;text-decoration:none;}
+.btn:focus{outline:1px dotted #666;}
+.btn.primary{color:#ffffff;background-color:#0064cd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd));background-image:-moz-linear-gradient(top, #049cdb, #0064cd);background-image:-ms-linear-gradient(top, #049cdb, #0064cd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd));background-image:-webkit-linear-gradient(top, #049cdb, #0064cd);background-image:-o-linear-gradient(top, #049cdb, #0064cd);background-image:linear-gradient(top, #049cdb, #0064cd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#049cdb', endColorstr='#0064cd', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#0064cd #0064cd #003f81;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
+.btn.active,.btn:active{-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);}
+.btn.disabled{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
+.btn[disabled]{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
+.btn.large{font-size:15px;line-height:normal;padding:9px 14px 9px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
+.btn.small{padding:7px 9px 7px;font-size:11px;}
+:root .alert-message,:root .btn{border-radius:0 \0;}
+button.btn::-moz-focus-inner,input[type=submit].btn::-moz-focus-inner{padding:0;border:0;}
+.close{float:right;color:#000000;font-size:20px;font-weight:bold;line-height:13.5px;text-shadow:0 1px 0 #ffffff;filter:alpha(opacity=25);-khtml-opacity:0.25;-moz-opacity:0.25;opacity:0.25;}.close:hover{color:#000000;text-decoration:none;filter:alpha(opacity=40);-khtml-opacity:0.4;-moz-opacity:0.4;opacity:0.4;}
+.alert-message{position:relative;padding:7px 15px;margin-bottom:18px;color:#404040;background-color:#eedc94;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94));background-image:-moz-linear-gradient(top, #fceec1, #eedc94);background-image:-ms-linear-gradient(top, #fceec1, #eedc94);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94));background-image:-webkit-linear-gradient(top, #fceec1, #eedc94);background-image:-o-linear-gradient(top, #fceec1, #eedc94);background-image:linear-gradient(top, #fceec1, #eedc94);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#eedc94 #eedc94 #e4c652;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);border-width:1px;border-style:solid;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);}.alert-message .close{margin-top:1px;*margin-top:0;}
+.alert-message a{font-weight:bold;color:#404040;}
+.alert-message.danger p a,.alert-message.error p a,.alert-message.success p a,.alert-message.info p a{color:#ffffff;}
+.alert-message h5{line-height:18px;}
+.alert-message p{margin-bottom:0;}
+.alert-message div{margin-top:5px;margin-bottom:2px;line-height:28px;}
+.alert-message .btn{-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);}
+.alert-message.block-message{background-image:none;background-color:#fdf5d9;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);padding:14px;border-color:#fceec1;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}.alert-message.block-message ul,.alert-message.block-message p{margin-right:30px;}
+.alert-message.block-message ul{margin-bottom:0;}
+.alert-message.block-message li{color:#404040;}
+.alert-message.block-message .alert-actions{margin-top:5px;}
+.alert-message.block-message.error,.alert-message.block-message.success,.alert-message.block-message.info{color:#404040;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}
+.alert-message.block-message.error{background-color:#fddfde;border-color:#fbc7c6;}
+.alert-message.block-message.success{background-color:#d1eed1;border-color:#bfe7bf;}
+.alert-message.block-message.info{background-color:#ddf4fb;border-color:#c6edf9;}
+.alert-message.block-message.danger p a,.alert-message.block-message.error p a,.alert-message.block-message.success p a,.alert-message.block-message.info p a{color:#404040;}
+.pagination{height:36px;margin:18px 0;}.pagination ul{float:left;margin:0;border:1px solid #ddd;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);}
+.pagination li{display:inline;}
+.pagination a{float:left;padding:0 14px;line-height:34px;border-right:1px solid;border-right-color:#ddd;border-right-color:rgba(0, 0, 0, 0.15);*border-right-color:#ddd;text-decoration:none;}
+.pagination a:hover,.pagination .active a{background-color:#c7eefe;}
+.pagination .disabled a,.pagination .disabled a:hover{background-color:transparent;color:#bfbfbf;}
+.pagination .next a{border:0;}
+.well{background-color:#f5f5f5;margin-bottom:20px;padding:19px;min-height:20px;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);}
+.modal-backdrop{background-color:#000000;position:fixed;top:0;left:0;right:0;bottom:0;z-index:10000;}.modal-backdrop.fade{opacity:0;}
+.modal-backdrop,.modal-backdrop.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}
+.modal{position:fixed;top:50%;left:50%;z-index:11000;width:560px;margin:-250px 0 0 -280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal .close{margin-top:7px;}
+.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;}
+.modal.fade.in{top:50%;}
+.modal-header{border-bottom:1px solid #eee;padding:5px 15px;}
+.modal-body{padding:15px;}
+.modal-body form{margin-bottom:0;}
+.modal-footer{background-color:#f5f5f5;padding:14px 15px 15px;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;zoom:1;margin-bottom:0;}.modal-footer:before,.modal-footer:after{display:table;content:"";zoom:1;}
+.modal-footer:after{clear:both;}
+.modal-footer .btn{float:right;margin-left:5px;}
+.modal .popover,.modal .twipsy{z-index:12000;}
+.twipsy{display:block;position:absolute;visibility:visible;padding:5px;font-size:11px;z-index:1000;filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}.twipsy.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}
+.twipsy.above .twipsy-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
+.twipsy.left .twipsy-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
+.twipsy.below .twipsy-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
+.twipsy.right .twipsy-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
+.twipsy-inner{padding:3px 8px;background-color:#000000;color:white;text-align:center;max-width:200px;text-decoration:none;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.twipsy-arrow{position:absolute;width:0;height:0;}
+.popover{position:absolute;top:0;left:0;z-index:1000;padding:5px;display:none;}.popover.above .arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
+.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
+.popover.below .arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
+.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
+.popover .arrow{position:absolute;width:0;height:0;}
+.popover .inner{background:#000000;background:rgba(0, 0, 0, 0.8);padding:3px;overflow:hidden;width:280px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);}
+.popover .title{background-color:#f5f5f5;padding:9px 15px;line-height:1;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;border-bottom:1px solid #eee;}
+.popover .content{background-color:#ffffff;padding:14px;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover .content p,.popover .content ul,.popover .content ol{margin-bottom:0;}
+.fade{-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;opacity:0;}.fade.in{opacity:1;}
+.label{padding:1px 3px 2px;font-size:9.75px;font-weight:bold;color:#ffffff;text-transform:uppercase;white-space:nowrap;background-color:#bfbfbf;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}.label.important{background-color:#c43c35;}
+.label.warning{background-color:#f89406;}
+.label.success{background-color:#46a546;}
+.label.notice{background-color:#62cffc;}
+.media-grid{margin-left:-20px;margin-bottom:0;zoom:1;}.media-grid:before,.media-grid:after{display:table;content:"";zoom:1;}
+.media-grid:after{clear:both;}
+.media-grid li{display:inline;}
+.media-grid a{float:left;padding:4px;margin:0 0 18px 20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);}.media-grid a img{display:block;}
+.media-grid a:hover{border-color:#0069d6;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);}
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/public/stylesheets/style.css
@@ -0,0 +1,92 @@
+html, body {
+  padding: 0;
+  margin: 0;
+  height: 100%;
+
+  background: white url(/images/nodejs.png) 50% 50% no-repeat;
+}
+
+header {
+  background: #333;
+  padding: 0 16px;
+}
+
+header h1 {
+  color: white;
+  line-height: 40px;
+}
+
+footer {
+  width: 100%;
+  height: 40px;
+  margin-top: 0;
+  padding: 16px 16px 0 16px;
+  line-height: 24px;
+  position: fixed;
+  bottom: 0;
+  background: white;
+}
+
+section#content {
+  padding: 64px 32px;
+}
+
+section#notice {
+  font-size: 32px;
+  line-height: 48px;
+}
+
+section#tweets {
+}
+
+section#tweets article.tweet {
+  padding: 2px 8px;
+}
+
+section#tweets article.tweet .clear {
+  clear: both;
+}
+
+section#tweets article.tweet img.avatar {
+  display: inline;
+  float: left;
+  width: 24px;
+  height: 24px;
+  padding: 0;
+  margin: 0 4px 0 0;
+}
+
+section#tweets article.tweet div.text-container {
+  overflow: hidden;
+  height: 24px;
+  position: relative;
+}
+
+section#tweets article.tweet div.text-container div.text {
+  display: inline-block;
+  float: left;
+  line-height: 24px;
+  width: 10000px;
+}
+
+section#tweets article.tweet div.text:before {
+  position: absolute;
+  top: 0;
+  right: 0px;
+
+  background: -webkit-linear-gradient(right, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
+  background: -moz-linear-gradient(right, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
+  background: linear-gradient(right, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
+  content: '';
+  width: 50px;
+  height: 24px;
+}
+
+div#tweet-button {
+  line-height: 32px;
+}
+
+div#tweet-button iframe {
+  position: relative;
+  top: 6px;
+}
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/realtime/index.js
@@ -0,0 +1,83 @@
+var twitter = new (require('ntwitter'))({
+  consumer_key: '',
+  consumer_secret: '',
+  access_token_key: '',
+  access_token_secret: ''
+});
+
+exports.init = function(io) {
+  var tweets = [];
+  twitter.search(
+    '#spdytwitlog',
+    {
+      result_type: 'recent',
+      include_entities: 1
+    },
+    function(err, result) {
+      if (err) return console.error(err);
+      result.results.sort(function(a, b) {
+        return (+new Date(a.created_at)) -
+               (+new Date(b.created_at));
+      }).forEach(receive);
+    }
+  );
+
+  function watchStream(method, query) {
+    twitter.stream(method, query, function(stream) {
+      stream.on('data', receive);
+
+      stream.on('end', retry);
+      stream.on('destroy', retry);
+
+      var once = false;
+      function retry() {
+        if (once) return;
+        once = true;
+
+        setTimeout(function() {
+          watchStream(method, query);
+        }, 5000);
+      }
+    });
+  }
+
+  watchStream('statuses/filter', {
+    track: '#spdytwitlog',
+    include_entities: 1
+  });
+
+  function receive(tweet) {
+    if (tweet.entities && tweet.entities.urls) {
+      tweet.entities.urls.sort(function(a, b) {
+        return b.indices[0] - a.indices[0];
+      }).forEach(function(url) {
+        tweet.text = tweet.text.slice(0, url.indices[0]) +
+                     url.display_url.link(url.expanded_url || url.url) +
+                     tweet.text.slice(url.indices[1]);
+      });
+    }
+    tweet = {
+      text: tweet.text,
+      user: tweet.user ? {
+        name: tweet.user.screen_name,
+        image: tweet.user.profile_image_url
+      } : {
+        name: tweet.from_user,
+        image: tweet.profile_image_url
+      }
+    };
+    io.sockets.emit('tweet', tweet);
+
+    tweets.push(tweet);
+    // remember only last 18 tweets
+    tweets = tweets.slice(tweets.length - 18, tweets.length);
+  };
+
+  io.sockets.on('connection', function(socket) {
+    socket.on('reqTweets', function() {
+      tweets.forEach(function(tweet) {
+        socket.emit('tweet', tweet);
+      });
+    });
+  });
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/routes/index.js
@@ -0,0 +1,36 @@
+var fs = require('fs');
+
+/*
+ * GET home page.
+ */
+
+var image = fs.readFileSync(__dirname + '/../public/images/nodejs.png');
+
+exports.index = function(req, res){
+  if (res.isSpdy) {
+    var ua = req.headers['user-agent'];
+
+    // Firefox doesn't support push streams now
+    // should be fixed once: https://bugzilla.mozilla.org/show_bug.cgi?id=718210
+    // will reach all it's builds
+    if (!ua || ua.match(/Firefox/i) === null) {
+      // Push stream image
+      res.push(
+        '/images/nodejs.png',
+        { 'content-type': 'image/png' },
+        function(err, stream) {
+          if (err) return;
+          stream.on('error', function() {});
+          stream.end(image);
+        }
+      );
+    }
+  }
+  res.render('index', {
+    title: 'SPDY - Twitlog',
+    notice: req.isSpdy ?
+      'Yay! This page was requested via SPDY protocol'
+      :
+      'Oh, no... your browser requested this page via HTTPS'
+  });
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/views/index.jade
@@ -0,0 +1,6 @@
+div(id='tweet-button')
+  = 'Post a '
+  a(href='https://twitter.com/intent/tweet?button_hashtag=spdytwitlog&text=This%20text%20will%20appear%20on',class='twitter-hashtag-button',data-size='small',data-related='indutny',data-url='https://spdy-twitlog.indutny.com/') tweet
+  = ' and it\'ll be displayed here'
+
+section(id='tweets')
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/views/layout.jade
@@ -0,0 +1,29 @@
+!!! 5
+html
+  head
+    title= title
+    link(rel='icon', type='image/png', href='favicon.png')
+    link(rel='stylesheet', href='/stylesheets/bootstrap.min.css')
+    link(rel='stylesheet', href='/stylesheets/style.css')
+  body
+    header.topbar
+      h1= title
+    section#content
+      - if (notice)
+        section#notice= notice
+      != body
+    footer
+      = 'Made using '
+      a(href='http://nodejs.org/') node.js
+      = ', '
+      a(href='https://github.com/indutny/node-spdy') node-spdy
+      = ', '
+      a(href='http://socket.io/') socket.io
+      = ' and '
+      a(href='http://twitter.github.com/bootstrap/') twitter bootstrap
+
+    script(src='//platform.twitter.com/widgets.js', async=true, defer=true)
+    script(src='/javascripts/socket.io.min.js')
+    script(src='/javascripts/main.js')
+
+    script var _gaq =_gaq||[];_gaq.push(['_setAccount', 'UA-28159099-1']);_gaq.push(['_trackPageview']);(function() {var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);})();
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/keys/spdy-cert.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICHzCCAYgCCQCPPSUAa8QZojANBgkqhkiG9w0BAQUFADBUMQswCQYDVQQGEwJS
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTENMAsGA1UEBxMET21zazEhMB8GA1UEChMY
+SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTExMDQwOTEwMDY0NVoXDTExMDUw
+OTEwMDY0NVowVDELMAkGA1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUxDTAL
+BgNVBAcTBE9tc2sxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCB
+nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1bn25sPkv46wl70BffxradlkRd/x
+p5Xf8HDhPSfzNNctERYslXT2fX7Dmfd5w1XTVqqGqJ4izp5VewoVOHA8uavo3ovp
+gNWasil5zADWaM1T0nnV0RsFbZWzOTmm1U3D48K8rW3F5kOZ6f4yRq9QT1gF/gN7
+5Pt494YyYyJu/a8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQBuRZisIViI2G/R+w79
+vk21TzC/cJ+O7tKsseDqotXYTH8SuimEH5IWcXNgnWhNzczwN8s2362NixyvCipV
+yd4wzMpPbjIhnWGM0hluWZiK2RxfcqimIBjDParTv6CMUIuwGQ257THKY8hXGg7j
+Uws6Lif3P9UbsuRiYPxMgg98wg==
+-----END CERTIFICATE-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/keys/spdy-csr.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBkzCB/QIBADBUMQswCQYDVQQGEwJSVTETMBEGA1UECBMKU29tZS1TdGF0ZTEN
+MAsGA1UEBxMET21zazEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF
+3/Gnld/wcOE9J/M01y0RFiyVdPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+je
+i+mA1ZqyKXnMANZozVPSedXRGwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+
+A3vk+3j3hjJjIm79rwIDAQABoAAwDQYJKoZIhvcNAQEFBQADgYEAiNWhz6EppIVa
+FfUaB3sLeqfamb9tg9kBHtvqj/FJni0snqms0kPWaTySEPHZF0irIb7VVdq/sVCb
+3gseMVSyoDvPJ4lHC3PXqGQ7kM1mIPhDnR/4HDA3BhlGhTXSDIHgZnvI+HMBdsyC
+hC3dz5odyKqe4nmoofomALkBL9t4H8s=
+-----END CERTIFICATE REQUEST-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/keys/spdy-key.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF3/Gnld/wcOE9J/M01y0RFiyV
+dPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+jei+mA1ZqyKXnMANZozVPSedXR
+GwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+A3vk+3j3hjJjIm79rwIDAQAB
+AoGAAv2QI9h32epQND9TxwSCKD//dC7W/cZOFNovfKCTeZjNK6EIzKqPTGA6smvR
+C1enFl5adf+IcyWqAoe4lkqTvurIj+2EhtXdQ8DBlVuXKr3xvEFdYxXPautdTCF6
+KbXEyS/s1TZCRFjYftvCrXxc3pK45AQX/wg7z1K+YB5pyIECQQD0OJvLoxLYoXAc
+FZraIOZiDsEbGuSHqoCReFXH75EC3+XGYkH2bQ/nSIZ0h1buuwQ/ylKXOlTPT3Qt
+Xm1OQEBvAkEA4AjWsIO/rRpOm/Q2aCrynWMpoUXTZSbL2yGf8pxp/+8r2br5ier0
+M1LeBb/OPY1+k39NWLXxQoo64xoSFYk2wQJAd2wDCwX4HkR7HNCXw1hZL9QFK6rv
+20NN0VSlpboJD/3KT0MW/FiCcVduoCbaJK0Au+zEjDyy4hj5N4I4Mw6KMwJAXVAx
+I+psTsxzS4/njXG+BgIEl/C+gRYsuMQDnAi8OebDq/et8l0Tg8ETSu++FnM18neG
+ntmBeMacinUUbTXuwQJBAJp/onZdsMzeVulsGrqR1uS+Lpjc5Q1gt5ttt2cxj91D
+rio48C/ZvWuKNE8EYj2ALtghcVKRvgaWfOxt2GPguGg=
+-----END RSA PRIVATE KEY-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy.js
@@ -0,0 +1,31 @@
+var spdy = exports;
+
+// Exports utils
+spdy.utils = require('./spdy/utils');
+
+// Export parser&framer
+spdy.protocol = {};
+
+try {
+  spdy.protocol.generic = require('./spdy/protocol/generic.node');
+} catch (e) {
+  spdy.protocol.generic = require('./spdy/protocol/generic.js');
+}
+
+// Only SPDY v2 is supported now
+spdy.protocol[2] = require('./spdy/protocol/v2');
+
+spdy.parser = require('./spdy/parser');
+
+// Export ServerResponse
+spdy.response = require('./spdy/response');
+
+// Export Scheduler
+spdy.scheduler = require('./spdy/scheduler');
+
+// Export ZlibPool
+spdy.zlibpool = require('./spdy/zlib-pool');
+
+// Export server
+spdy.server = require('./spdy/server');
+spdy.createServer = spdy.server.create;
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/parser.js
@@ -0,0 +1,185 @@
+var parser = exports;
+
+var spdy = require('../spdy'),
+    util = require('util'),
+    stream = require('stream'),
+    Buffer = require('buffer').Buffer;
+
+//
+// ### function Parser (connection, deflate, inflate)
+// #### @connection {spdy.Connection} connection
+// #### @deflate {zlib.Deflate} Deflate stream
+// #### @inflate {zlib.Inflate} Inflate stream
+// SPDY protocol frames parser's @constructor
+//
+function Parser(connection, deflate, inflate) {
+  stream.Stream.call(this);
+
+  this.drained = true;
+  this.paused = false;
+  this.buffer = [];
+  this.buffered = 0;
+  this.waiting = 8;
+
+  this.state = { type: 'frame-head' };
+  this.deflate = deflate;
+  this.inflate = inflate;
+  this.framer = null;
+
+  this.connection = connection;
+
+  this.readable = this.writable = true;
+}
+util.inherits(Parser, stream.Stream);
+
+//
+// ### function create (connection, deflate, inflate)
+// #### @connection {spdy.Connection} connection
+// #### @deflate {zlib.Deflate} Deflate stream
+// #### @inflate {zlib.Inflate} Inflate stream
+// @constructor wrapper
+//
+parser.create = function create(connection, deflate, inflate) {
+  return new Parser(connection, deflate, inflate);
+};
+
+//
+// ### function write (data)
+// #### @data {Buffer} chunk of data
+// Writes or buffers data to parser
+//
+Parser.prototype.write = function write(data) {
+  if (data !== undefined) {
+    // Buffer data
+    this.buffer.push(data);
+    this.buffered += data.length;
+  }
+
+  // Notify caller about state (for piping)
+  if (this.paused) return false;
+
+  // We shall not do anything until we get all expected data
+  if (this.buffered < this.waiting) return;
+
+  // Mark parser as not drained
+  if (data !== undefined) this.drained = false;
+
+  var self = this,
+      buffer = new Buffer(this.waiting),
+      sliced = 0,
+      offset = 0;
+
+  while (this.waiting > offset && sliced < this.buffer.length) {
+    var chunk = this.buffer[sliced++],
+        overmatched = false;
+
+    // Copy chunk into `buffer`
+    if (chunk.length > this.waiting - offset) {
+      chunk.copy(buffer, offset, 0, this.waiting - offset);
+
+      this.buffer[--sliced] = chunk.slice(this.waiting - offset);
+      this.buffered += this.buffer[sliced].length;
+
+      overmatched = true;
+    } else {
+      chunk.copy(buffer, offset);
+    }
+
+    // Move offset and decrease amount of buffered data
+    offset += chunk.length;
+    this.buffered -= chunk.length;
+
+    if (overmatched) break;
+  }
+
+  // Remove used buffers
+  this.buffer = this.buffer.slice(sliced);
+
+  // Executed parser for buffered data
+  this.paused = true;
+  this.execute(this.state, buffer, function (err, waiting) {
+    // And unpause once execution finished
+    self.paused = false;
+
+    // Propagate errors
+    if (err) return self.emit('error', err);
+
+    // Set new `waiting`
+    self.waiting = waiting;
+
+    if (self.waiting <= self.buffered) {
+      self.write();
+    } else {
+      process.nextTick(function() {
+        if (self.drained) return;
+
+        // Mark parser as drained
+        self.drained = true;
+        self.emit('drain');
+      });
+    }
+  });
+};
+
+//
+// ### function end ()
+// Stream's end() implementation
+//
+Parser.prototype.end = function end() {
+  this.emit('end');
+};
+
+//
+// ### function execute (state, data, callback)
+// #### @state {Object} Parser's state
+// #### @data {Buffer} Incoming data
+// #### @callback {Function} continuation callback
+// Parse buffered data
+//
+Parser.prototype.execute = function execute(state, data, callback) {
+  if (state.type === 'frame-head') {
+    var header = state.header = spdy.protocol.generic.parseHeader(data);
+
+    // Lazily create framer
+    if (!this.framer && header.control) {
+      if (spdy.protocol[header.version]) {
+        this.framer = new spdy.protocol[header.version].Framer(
+          spdy.utils.zwrap(this.deflate),
+          spdy.utils.zwrap(this.inflate)
+        );
+
+        // Propagate framer to connection
+        this.connection.framer = this.framer;
+        this.emit('_framer', this.framer);
+      }
+    }
+
+    state.type = 'frame-body';
+    callback(null, header.length);
+  } else if (state.type === 'frame-body') {
+    var self = this;
+
+    // Data frame
+    if (!state.header.control) {
+      return onFrame(null, {
+        type: 'DATA',
+        id: state.header.id,
+        fin: (state.header.flags & 0x01) === 0x01,
+        compressed: (state.header.flags & 0x02) === 0x02,
+        data: data
+      });
+    } else {
+    // Control frame
+      this.framer.execute(state.header, data, onFrame);
+    }
+
+    function onFrame(err, frame) {
+      if (err) return callback(err);
+
+      self.emit('frame', frame);
+
+      state.type = 'frame-head';
+      callback(null, 8);
+    };
+  }
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/protocol/generic.js
@@ -0,0 +1,24 @@
+//
+// ### function parseHeader (data)
+// ### @data {Buffer} incoming data
+// Returns parsed SPDY frame header
+//
+exports.parseHeader = function parseHeader(data) {
+  var header = {
+    control: (data.readUInt8(0) & 0x80) === 0x80 ? true : false,
+    version: null,
+    type: null,
+    id: null,
+    flags: data.readUInt8(4),
+    length: data.readUInt32BE(4) & 0x00ffffff
+  };
+
+  if (header.control) {
+    header.version = data.readUInt16BE(0) & 0x7fff;
+    header.type = data.readUInt16BE(2);
+  } else {
+    header.id = data.readUInt32BE(0) & 0x7fffffff;
+  }
+
+  return header;
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/protocol/v2/framer.js
@@ -0,0 +1,280 @@
+var framer = exports;
+
+var spdy = require('../../../spdy'),
+    Buffer = require('buffer').Buffer,
+    protocol = require('./');
+
+//
+// ### function Framer (deflate, inflate)
+// #### @deflate {zlib.Deflate} Deflate stream
+// #### @inflate {zlib.Inflate} Inflate stream
+// Framer constructor
+//
+function Framer(deflate, inflate) {
+  this.deflate = deflate;
+  this.inflate = inflate;
+}
+exports.Framer = Framer;
+
+
+//
+// ### function execute (header, body, callback)
+// #### @header {Object} Frame headers
+// #### @body {Buffer} Frame's body
+// #### @callback {Function} Continuation callback
+// Parse frame (decompress data and create streams)
+//
+Framer.prototype.execute = function execute(header, body, callback) {
+  // SYN_STREAM or SYN_REPLY
+  if (header.type === 0x01 || header.type === 0x02) {
+    var frame = protocol.parseSynHead(header.type, header.flags, body);
+
+    body = body.slice(frame._offset);
+
+    this.inflate(body, function(err, chunks, length) {
+      var pairs = new Buffer(length);
+      for (var i = 0, offset = 0; i < chunks.length; i++) {
+        chunks[i].copy(pairs, offset);
+        offset += chunks[i].length;
+      }
+
+      frame.headers = protocol.parseHeaders(pairs);
+
+      callback(null, frame);
+    });
+  // RST_STREAM
+  } else if (header.type === 0x03) {
+    callback(null, protocol.parseRst(body));
+  // SETTINGS
+  } else if (header.type === 0x04) {
+    callback(null, { type: 'SETTINGS' });
+  } else if (header.type === 0x05) {
+    callback(null, { type: 'NOOP' });
+  // PING
+  } else if (header.type === 0x06) {
+    callback(null, { type: 'PING', pingId: body });
+  // GOAWAY
+  } else if (header.type === 0x07) {
+    callback(null, protocol.parseGoaway(body));
+  } else {
+    callback(null, { type: 'unknown: ' + header.type, body: body });
+  }
+};
+
+//
+// internal, converts object into spdy dictionary
+//
+function headersToDict(headers, preprocess) {
+  function stringify(value) {
+    if (value !== undefined) {
+      if (Array.isArray(value)) {
+        return value.join('\x00');
+      } else if (typeof value === 'string') {
+        return value;
+      } else {
+        return value.toString();
+      }
+    } else {
+      return '';
+    }
+  }
+
+  // Lower case of all headers keys
+  var loweredHeaders = {};
+  Object.keys(headers || {}).map(function(key) {
+    loweredHeaders[key.toLowerCase()] = headers[key];
+  });
+
+  // Allow outer code to add custom headers or remove something
+  if (preprocess) preprocess(loweredHeaders);
+
+  // Transform object into kv pairs
+  var len = 2,
+      pairs = Object.keys(loweredHeaders).filter(function(key) {
+        var lkey = key.toLowerCase();
+        return lkey !== 'connection' && lkey !== 'keep-alive' &&
+               lkey !== 'proxy-connection' && lkey !== 'transfer-encoding';
+      }).map(function(key) {
+        var klen = Buffer.byteLength(key),
+            value = stringify(loweredHeaders[key]),
+            vlen = Buffer.byteLength(value);
+
+        len += 4 + klen + vlen;
+        return [klen, key, vlen, value];
+      }),
+      result = new Buffer(len);
+
+  result.writeUInt16BE(pairs.length, 0, true);
+
+  var offset = 2;
+  pairs.forEach(function(pair) {
+    // Write key length
+    result.writeUInt16BE(pair[0], offset, true);
+    // Write key
+    result.write(pair[1], offset + 2);
+
+    offset += pair[0] + 2;
+
+    // Write value length
+    result.writeUInt16BE(pair[2], offset, true);
+    // Write value
+    result.write(pair[3], offset + 2);
+
+    offset += pair[2] + 2;
+  });
+
+  return result;
+};
+
+Framer.prototype._synFrame = function _synFrame(type, id, assoc, dict,
+                                                callback) {
+  // Compress headers
+  this.deflate(dict, function (err, chunks, size) {
+    if (err) return callback(err);
+
+    var offset = type === 'SYN_STREAM' ? 18 : 14,
+        total = (type === 'SYN_STREAM' ? 10 : 6) + size,
+        frame = new Buffer(offset + size);;
+
+    frame.writeUInt16BE(0x8002, 0, true); // Control + Version
+    frame.writeUInt16BE(type === 'SYN_STREAM' ? 1 : 2, 2, true); // type
+    frame.writeUInt32BE(total & 0x00ffffff, 4, true); // No flag support
+    frame.writeUInt32BE(id & 0x7fffffff, 8, true); // Stream-ID
+
+    if (type === 'SYN_STREAM') {
+      frame[4] = 2;
+      frame.writeUInt32BE(assoc & 0x7fffffff, 12, true); // Stream-ID
+    }
+
+    for (var i = 0; i < chunks.length; i++) {
+      chunks[i].copy(frame, offset);
+      offset += chunks[i].length;
+    }
+
+    callback(null, frame);
+  });
+};
+
+//
+// ### function replyFrame (id, code, reason, headers, callback)
+// #### @id {Number} Stream ID
+// #### @code {Number} HTTP Status Code
+// #### @reason {String} (optional)
+// #### @headers {Object|Array} (optional) HTTP headers
+// #### @callback {Function} Continuation function
+// Sends SYN_REPLY frame
+//
+Framer.prototype.replyFrame = function replyFrame(id, code, reason, headers,
+                                                  callback) {
+  var dict = headersToDict(headers, function(headers) {
+        headers.status = code + ' ' + reason;
+        headers.version = 'HTTP/1.1';
+      });
+
+  this._synFrame('SYN_REPLY', id, null, dict, callback);
+};
+
+//
+// ### function streamFrame (id, assoc, headers, callback)
+// #### @id {Number} stream id
+// #### @assoc {Number} associated stream id
+// #### @meta {Object} meta headers ( method, scheme, url, version )
+// #### @headers {Object} stream headers
+// #### @callback {Function} continuation callback
+// Create SYN_STREAM frame
+// (needed for server push and testing)
+//
+Framer.prototype.streamFrame = function streamFrame(id, assoc, meta, headers,
+                                                    callback) {
+  var dict = headersToDict(headers, function(headers) {
+    headers.status = 200;
+    headers.version = 'HTTP/1.1';
+    headers.url = meta.url;
+  });
+
+  this._synFrame('SYN_STREAM', id, assoc, dict, callback);
+};
+
+//
+// ### function dataFrame (id, fin, data)
+// #### @id {Number} Stream id
+// #### @fin {Bool} Is this data frame last frame
+// #### @data {Buffer} Response data
+// Sends DATA frame
+//
+Framer.prototype.dataFrame = function dataFrame(id, fin, data) {
+  if (!fin && !data.length) return [];
+
+  var frame = new Buffer(8 + data.length);
+
+  frame.writeUInt32BE(id & 0x7fffffff, 0, true);
+  frame.writeUInt32BE(data.length & 0x00ffffff, 4, true);
+  frame.writeUInt8(fin ? 0x01 : 0x0, 4, true);
+
+  if (data.length) data.copy(frame, 8);
+
+  return frame;
+};
+
+//
+// ### function pingFrame (id)
+// #### @id {Buffer} Ping ID
+// Sends PING frame
+//
+Framer.prototype.pingFrame = function pingFrame(id) {
+  var header = new Buffer(12);
+
+  header.writeUInt32BE(0x80020006, 0, true); // Version and type
+  header.writeUInt32BE(0x00000004, 4, true); // Length
+  id.copy(header, 8, 0, 4); // ID
+
+  return header;
+};
+
+//
+// ### function rstFrame (id, code)
+// #### @id {Number} Stream ID
+// #### @code {NUmber} RST Code
+// Sends PING frame
+//
+Framer.prototype.rstFrame = function rstFrame(id, code) {
+  var header;
+
+  if (!(header = Framer.rstCache[code])) {
+    header = new Buffer(16);
+
+    header.writeUInt32BE(0x80020003, 0, true); // Version and type
+    header.writeUInt32BE(0x00000008, 4, true); // Length
+    header.writeUInt32BE(id & 0x7fffffff, 8, true); // Stream ID
+    header.writeUInt32BE(code, 12, true); // Status Code
+
+    Framer.rstCache[code] = header;
+  }
+
+  return header;
+};
+Framer.rstCache = {};
+
+//
+// ### function maxStreamsFrame (count)
+// #### @count {Number} Max Concurrent Streams count
+// Sends SETTINGS frame with MAX_CONCURRENT_STREAMS
+//
+Framer.prototype.maxStreamsFrame = function maxStreamsFrame(count) {
+  var settings;
+
+  if (!(settings = Framer.settingsCache[count])) {
+    settings = new Buffer(20);
+
+    settings.writeUInt32BE(0x80020004, 0, true); // Version and type
+    settings.writeUInt32BE(0x0000000C, 4, true); // length
+    settings.writeUInt32BE(0x00000001, 8, true); // Count of entries
+    settings.writeUInt32LE(0x01000004, 12, true); // Entry ID and Persist flag
+    settings.writeUInt32BE(count, 16, true); // 100 Streams
+
+    Framer.settingsCache[count] = settings;
+  }
+
+  return settings;
+};
+Framer.settingsCache = {};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/protocol/v2/index.js
@@ -0,0 +1,10 @@
+var v2;
+
+try {
+  v2 = require('./protocol.node');
+} catch (e) {
+  v2 = require('./protocol.js');
+}
+module.exports = v2;
+
+v2.Framer = require('./framer').Framer;
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/protocol/v2/protocol.js
@@ -0,0 +1,67 @@
+var protocol = exports;
+
+//
+// ### function parseSynHead (type, flags, data)
+// #### @type {Number} Frame type
+// #### @flags {Number} Frame flags
+// #### @data {Buffer} input data
+// Returns parsed syn_* frame's head
+//
+protocol.parseSynHead = function parseSynHead(type, flags, data) {
+  var stream = type === 0x01;
+
+  return {
+    type: stream ? 'SYN_STREAM' : 'SYN_REPLY',
+    id: data.readUInt32BE(0, true) & 0x7fffffff,
+    associated: stream ? data.readUInt32BE(4, true) & 0x7fffffff : 0,
+    priority: stream ? data[8] >> 6 : 0,
+    fin: (flags & 0x01) === 0x01,
+    unidir: (flags & 0x02) === 0x02,
+    _offset: stream ? 10 : 6
+  };
+};
+
+//
+// ### function parseHeaders (pairs)
+// #### @pairs {Buffer} header pairs
+// Returns hashmap of parsed headers
+//
+protocol.parseHeaders = function parseHeaders(pairs) {
+  var count = pairs.readUInt16BE(0, true),
+      headers = {};
+
+  pairs = pairs.slice(2);
+
+  function readString() {
+    var len = pairs.readUInt16BE(0, true),
+        value = pairs.slice(2, 2 + len);
+
+    pairs = pairs.slice(2 + len);
+
+    return value.toString();
+  }
+
+  while(count > 0) {
+    headers[readString()] = readString();
+    count--;
+  }
+
+  return headers;
+};
+
+//
+// ### function parsesRst frame
+protocol.parseRst = function parseRst(data) {
+  return {
+    type: 'RST_STREAM',
+    id: data.readUInt32BE(0, true) & 0x7fffffff,
+    status: data.readUInt32BE(4, true)
+  };
+};
+
+protocol.parseGoaway = function parseGoaway(data) {
+  return {
+    type: 'GOAWAY',
+    lastId: data.readUInt32BE(0, true) & 0x7fffffff
+  };
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/response.js
@@ -0,0 +1,146 @@
+var spdy = require('../spdy'),
+    http = require('http');
+
+//
+// ### function _renderHeaders ()
+// Copy pasted from lib/http.js
+// (added lowercase)
+//
+exports._renderHeaders = function() {
+  if (this._header) {
+    throw new Error("Can't render headers after they are sent to the client.");
+  }
+
+  if (!this._headers) return {};
+
+  var headers = {};
+  var keys = Object.keys(this._headers);
+  for (var i = 0, l = keys.length; i < l; i++) {
+    var key = keys[i];
+    headers[(this._headerNames[key] || '').toLowerCase()] = this._headers[key];
+  }
+  return headers;
+};
+
+//
+// ### function writeHead (statusCode)
+// #### @statusCode {Number} HTTP Status code
+// .writeHead() wrapper
+// (Sorry, copy pasted from lib/http.js)
+//
+exports.writeHead = function(statusCode) {
+  if (this._headerSent) return;
+  this._headerSent = true;
+
+  var reasonPhrase, headers, headerIndex;
+
+  if (typeof arguments[1] == 'string') {
+    reasonPhrase = arguments[1];
+    headerIndex = 2;
+  } else {
+    reasonPhrase = http.STATUS_CODES[statusCode] || 'unknown';
+    headerIndex = 1;
+  }
+  this.statusCode = statusCode;
+
+  var obj = arguments[headerIndex];
+
+  if (obj && this._headers) {
+    // Slow-case: when progressive API and header fields are passed.
+    headers = this._renderHeaders();
+
+    // handle object case
+    var keys = Object.keys(obj);
+    for (var i = 0; i < keys.length; i++) {
+      var k = keys[i];
+      if (k) headers[k] = obj[k];
+    }
+  } else if (this._headers) {
+    // only progressive api is used
+    headers = this._renderHeaders();
+  } else {
+    // only writeHead() called
+    headers = obj;
+  }
+
+  // cleanup
+  this._header = '';
+
+  // Do not send data to new connections after GOAWAY
+  if (this.socket.isGoaway()) return;
+
+  this.socket.lock(function() {
+    var socket = this;
+
+    this.framer.replyFrame(
+      this.id,
+      statusCode,
+      reasonPhrase,
+      headers,
+      function (err, frame) {
+        // TODO: Handle err
+        socket.connection.write(frame);
+        socket.unlock();
+      }
+    );
+  });
+};
+
+//
+// ### function push (url, headers, callback)
+// #### @url {String} absolute or relative url (from root anyway)
+// #### @headers {Object} response headers
+// #### @callbacks {Function} continuation that will receive stream object
+// Initiates push stream
+//
+exports.push = function push(url, headers, callback) {
+  if (this.socket._destroyed) {
+    return callback(Error('Can\'t open push stream, parent socket destroyed'));
+  }
+
+  this.socket.lock(function() {
+    var socket = this,
+        id = socket.connection.pushId += 2,
+        fullUrl = /^\//.test(url) ?
+                      this.frame.headers.scheme + '://' +
+                      (this.frame.headers.host || 'localhost') +
+                      url
+                      :
+                      url;
+
+    this.framer.streamFrame(
+      id,
+      this.id,
+      {
+        method: 'GET',
+        url: fullUrl,
+        schema: 'https',
+        version: 'HTTP/1.1'
+      },
+      headers,
+      function(err, frame) {
+        if (err) {
+          socket.unlock();
+          callback(err);
+        } else {
+          socket.connection.write(frame);
+          socket.unlock();
+
+          var stream = new spdy.server.Stream(socket.connection, {
+            type: 'SYN_STREAM',
+            push: true,
+            id: id,
+            assoc: socket.id,
+            priority: 0,
+            headers: {}
+          });
+
+          socket.connection.streams[id] = stream;
+          socket.pushes.push(stream);
+
+          callback(null, stream);
+        }
+      }
+    );
+  });
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/scheduler.js
@@ -0,0 +1,60 @@
+var scheduler = exports;
+
+//
+// ### function Scheduler (connection)
+// #### @connection {spdy.Connection} active connection
+// Connection's streams scheduler
+//
+function Scheduler(connection) {
+  this.connection = connection;
+  this.priorities = [[], [], [], []];
+  this._tickListener = null;
+}
+
+//
+// ### function create (connection)
+// #### @connection {spdy.Connection} active connection
+//
+exports.create = function create(connection) {
+  return new Scheduler(connection);
+};
+
+//
+// ### function schedule (stream, data)
+// #### @stream {spdy.Stream} Source stream
+// #### @data {Buffer} data to write on tick
+// Use stream priority to invoke callbacks in right order
+//
+Scheduler.prototype.schedule = function schedule(stream, data) {
+  this.priorities[stream.priority].push(data);
+};
+
+//
+// ### function tick ()
+// Add .nextTick callback if not already present
+//
+Scheduler.prototype.tick = function tick() {
+  if (this._tickListener !== null) return;
+  var self = this;
+  this._tickListener = function() {
+    var priorities = self.priorities;
+
+    self._tickListener = null;
+    self.priorities = [[], [], [], []];
+
+    // Run all priorities
+    for (var i = 0; i < 4; i++) {
+      for (var j = 0; j < priorities[i].length; j++) {
+        self.connection.write(
+          priorities[i][j]
+        );
+      }
+    }
+  };
+
+  if (this.connection.parser.drained) {
+    process.nextTick(this._tickListener);
+  } else {
+    this.connection.parser.once('drain', this._tickListener);
+  }
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/server.js
@@ -0,0 +1,544 @@
+var spdy = require('../spdy'),
+    util = require('util'),
+    https = require('https'),
+    stream = require('stream'),
+    Buffer = require('buffer').Buffer;
+
+//
+// ### function instantiate (HTTPSServer)
+// #### @HTTPSServer {https.Server|Function} Base server class
+// Will return constructor for SPDY Server, based on the HTTPSServer class
+//
+function instantiate(HTTPSServer) {
+  //
+  // ### function Server (options, requestListener)
+  // #### @options {Object} tls server options
+  // #### @requestListener {Function} (optional) request callback
+  // SPDY Server @constructor
+  //
+  function Server(options, requestListener) {
+    if (!options) options = {};
+    if (!options.maxStreams) options.maxStreams = 100;
+    options.NPNProtocols = ['spdy/2', 'http/1.1', 'http/1.0'];
+
+    HTTPSServer.call(this, options, requestListener);
+
+    // Use https if NPN is not supported
+    if (!process.features.tls_npn && !options.debug) return;
+
+    // Wrap connection handler
+    var self = this,
+        connectionHandler = this.listeners('secureConnection')[0];
+
+    var pool = spdy.zlibpool.create();
+
+    this.removeAllListeners('secureConnection');
+    this.on('secureConnection', function secureConnection(socket) {
+      // Fallback to HTTPS if needed
+      if (socket.npnProtocol !== 'spdy/2' && !options.debug) {
+        return connectionHandler.call(this, socket);
+      }
+
+      // Wrap incoming socket into abstract class
+      var connection = new Connection(socket, pool, options);
+
+      // Emulate each stream like connection
+      connection.on('stream', connectionHandler);
+
+      connection.on('request', function(req, res) {
+        res._renderHeaders = spdy.response._renderHeaders;
+        res.writeHead = spdy.response.writeHead;
+        res.push = spdy.response.push;
+        res.streamID = req.streamID = req.socket.id;
+        res.isSpdy = req.isSpdy = true;
+
+        res.on('finish', function() {
+          req.connection.end();
+        });
+        self.emit('request', req, res);
+      });
+
+      connection.on('error', function(e) {
+        socket.destroy(e.errno === 'EPIPE' ? undefined : e);
+      });
+    });
+  }
+  util.inherits(Server, HTTPSServer);
+
+  return Server;
+}
+exports.instantiate = instantiate;
+
+// Export Server instantiated from https.Server
+var Server = instantiate(https.Server);
+exports.Server = Server;
+
+//
+// ### function create (base, options, requestListener)
+// #### @base {Function} (optional) base server class (https.Server)
+// #### @options {Object} tls server options
+// #### @requestListener {Function} (optional) request callback
+// @constructor wrapper
+//
+exports.create = function create(base, options, requestListener) {
+  var server;
+  if (typeof base === 'function') {
+    server = instantiate(base);
+  } else {
+    server = Server;
+
+    requestListener = options;
+    options = base;
+    base = null;
+  }
+
+  return new server(options, requestListener);
+};
+
+//
+// ### function Connection (socket, pool, options)
+// #### @socket {net.Socket} server's connection
+// #### @pool {spdy.ZlibPool} zlib pool
+// #### @options {Object} server's options
+// Abstract connection @constructor
+//
+function Connection(socket, pool, options) {
+  process.EventEmitter.call(this);
+
+  var self = this;
+
+  this._closed = false;
+
+  this.pool = pool;
+  var pair = pool.get();
+
+  this.deflate = pair.deflate;
+  this.inflate = pair.inflate;
+
+  // Init streams list
+  this.streams = {};
+  this.streamsCount = 0;
+  this.pushId = 0;
+  this.goaway = false;
+
+  this.framer = null;
+
+  // Initialize parser
+  this.parser = spdy.parser.create(this, this.deflate, this.inflate);
+  this.parser.on('frame', function (frame) {
+    if (this._closed) return;
+
+    var stream;
+
+    // Create new stream
+    if (frame.type === 'SYN_STREAM') {
+      self.streamsCount++;
+
+      stream = self.streams[frame.id] = new Stream(self, frame);
+
+      // If we reached stream limit
+      if (self.streamsCount > options.maxStreams) {
+        stream.once('error', function() {});
+        // REFUSED_STREAM
+        stream.rstCode = 3;
+        stream.destroy(true);
+      } else {
+        self.emit('stream', stream);
+
+        stream.init();
+      }
+    } else {
+      if (frame.id) {
+        // Load created one
+        stream = self.streams[frame.id];
+
+        // Fail if not found
+        if (stream === undefined) {
+          if (frame.type === 'RST_STREAM') return;
+          return self.emit('error', 'Stream ' + frame.id + ' not found');
+        }
+      }
+
+      // Emit 'data' event
+      if (frame.type === 'DATA') {
+        if (frame.data.length > 0){
+          if (stream.closedBy.client) {
+            stream.rstCode = 2;
+            stream.emit('error', 'Writing to half-closed stream');
+          } else {
+            stream._read(frame.data);
+          }
+        }
+      // Destroy stream if we was asked to do this
+      } else if (frame.type === 'RST_STREAM') {
+        stream.rstCode = 0;
+        if (frame.status === 5) {
+          // If client "cancels" connection - close stream and
+          // all associated push streams without error
+          stream.pushes.forEach(function(stream) {
+            stream.close();
+          });
+          stream.close();
+        } else {
+          // Emit error on destroy
+          stream.destroy(new Error('Received rst: ' + frame.status));
+        }
+      // Respond with same PING
+      } else if (frame.type === 'PING') {
+        self.write(self.framer.pingFrame(frame.pingId));
+      // Ignore SETTINGS for now
+      } else if (frame.type === 'SETTINGS' || frame.type === 'NOOP') {
+        // TODO: Handle something?
+      } else if (frame.type === 'GOAWAY') {
+        self.goaway = frame.lastId;
+      } else {
+        console.error('Unknown type: ', frame.type);
+      }
+    }
+
+    // Handle half-closed
+    if (frame.fin) {
+      // Don't allow to close stream twice
+      if (stream.closedBy.client) {
+        stream.rstCode = 2;
+        stream.emit('error', 'Already half-closed');
+      } else {
+        stream.closedBy.client = true;
+        stream.handleClose();
+      }
+    }
+  });
+
+  this.parser.on('_framer', function(framer) {
+    // Generate custom settings frame and send
+    self.write(
+      framer.maxStreamsFrame(options.maxStreams)
+    );
+  });
+
+  // Initialize scheduler
+  this.scheduler = spdy.scheduler.create(this);
+
+  // Store socket and pipe it to parser
+  this.socket = socket;
+
+  socket.pipe(this.parser);
+
+  // 2 minutes socket timeout
+  socket.setTimeout(2 * 60 * 1000);
+  socket.once('timeout', function() {
+    socket.destroy();
+  });
+
+  // Allow high-level api to catch socket errors
+  socket.on('error', function(e) {
+    self.emit('error', e);
+  });
+
+  socket.on('close', function() {
+    self._closed = true;
+    pool.put(pair);
+  });
+
+  socket.on('drain', function () {
+    self.emit('drain');
+  });
+}
+util.inherits(Connection, process.EventEmitter);
+exports.Connection = Connection;
+
+//
+// ### function write (data, encoding)
+// #### @data {String|Buffer} data
+// #### @encoding {String} (optional) encoding
+// Writes data to socket
+//
+Connection.prototype.write = function write(data, encoding) {
+  if (this.socket.writable) {
+    return this.socket.write(data, encoding);
+  }
+};
+
+//
+// ### function Stream (connection, frame)
+// #### @connection {Connection} SPDY Connection
+// #### @frame {Object} SYN_STREAM data
+// Abstract stream @constructor
+//
+function Stream(connection, frame) {
+  var self = this;
+  stream.Stream.call(this);
+
+  this.connection = connection;
+  this.framer = connection.framer;
+
+  this.ondata = this.onend = null;
+
+  // RST_STREAM code if any
+  this.rstCode = 1;
+  this._destroyed = false;
+
+  this.closedBy = {
+    client: false,
+    server: false
+  };
+
+  // Lock data
+  this.locked = false;
+  this.lockBuffer = [];
+
+  // Store id
+  this.id = frame.id;
+
+  // Store priority
+  this.priority = frame.priority;
+
+  // Array of push streams associated to that one
+  this.pushes = [];
+
+  this._paused = false;
+  this._buffer = [];
+
+  // Create compression streams
+  this.deflate = connection.deflate;
+  this.inflate = connection.inflate;
+
+  // Store headers
+  this.headers = frame.headers;
+
+  this.frame = frame;
+
+  this.readable = this.writable = true;
+}
+util.inherits(Stream, stream.Stream);
+exports.Stream = Stream;
+
+//
+// ### function isGoaway ()
+// Returns true if any writes to that stream should be ignored
+//
+Stream.prototype.isGoaway = function isGoaway() {
+  return this.connection.goaway && this.id > this.connection.goaway;
+};
+
+//
+// ### function init ()
+// Initialize stream, internal
+//
+Stream.prototype.init = function init() {
+  var headers = this.headers,
+      req = [headers.method + ' ' + headers.url + ' ' + headers.version];
+
+  Object.keys(headers).forEach(function (key) {
+    if (key !== 'method' && key !== 'url' && key !== 'version' &&
+        key !== 'scheme') {
+      req.push(key + ': ' + headers[key]);
+    }
+  });
+
+  // Add '\r\n\r\n'
+  req.push('', '');
+
+  req = new Buffer(req.join('\r\n'));
+
+  this._read(req);
+};
+
+//
+// ### function lock (callback)
+// #### @callback {Function} continuation callback
+// Acquire lock
+//
+Stream.prototype.lock = function lock(callback) {
+  if (!callback) return;
+
+  if (this.locked) {
+    this.lockBuffer.push(callback);
+  } else {
+    this.locked = true;
+    callback.call(this, null);
+  }
+};
+
+//
+// ### function unlock ()
+// Release lock and call all buffered callbacks
+//
+Stream.prototype.unlock = function unlock() {
+  if (this.locked) {
+    this.locked = false;
+    this.lock(this.lockBuffer.shift());
+  }
+};
+
+//
+// ### function setTimeout ()
+// TODO: use timers.enroll, timers.active, timers.unenroll
+//
+Stream.prototype.setTimeout = function setTimeout(time) {};
+
+//
+// ### function handleClose ()
+// Close stream if it was closed by both server and client
+//
+Stream.prototype.handleClose = function handleClose() {
+  if (this.closedBy.client && this.closedBy.server) {
+    this.close();
+  }
+};
+
+//
+// ### function close ()
+// Destroys stream
+//
+Stream.prototype.close = function close() {
+  this.destroy();
+};
+
+//
+// ### function destroy (error)
+// #### @error {Error} (optional) error
+// Destroys stream
+//
+Stream.prototype.destroy = function destroy(error) {
+  if (this._destroyed) return;
+  this._destroyed = true;
+
+  delete this.connection.streams[this.id];
+  this.connection.streamsCount--;
+
+  if (error) {
+    if (this.rstCode) {
+      this.connection.write(this.framer.rstFrame(this.id, this.rstCode));
+    }
+  }
+
+  var self = this;
+  process.nextTick(function() {
+    if (error) self.emit('error', error);
+    self.emit('close');
+  });
+};
+
+//
+// ### function _writeData (fin, buffer)
+// #### @fin {Boolean}
+// #### @buffer {Buffer}
+// Internal function
+//
+Stream.prototype._writeData = function _writeData(fin, buffer) {
+  this.lock(function() {
+    var stream = this,
+        frame = this.framer.dataFrame(this.id, fin, buffer);
+
+    stream.connection.scheduler.schedule(stream, frame);
+    stream.connection.scheduler.tick();
+
+    if (fin) this.close();
+
+    this.unlock();
+  });
+};
+
+//
+// ### function write (data, encoding)
+// #### @data {Buffer|String} data
+// #### @encoding {String} data encoding
+// Writes data to connection
+//
+Stream.prototype.write = function write(data, encoding) {
+  // Do not send data to new connections after GOAWAY
+  if (this.isGoaway()) return;
+
+  var buffer;
+
+  // Send DATA
+  if (typeof data === 'string') {
+    buffer = new Buffer(data, encoding);
+  } else {
+    buffer = data;
+  }
+
+  // TCP Frame size is equal to:
+  // data frame size + data frame headers (8 bytes) +
+  // tls frame headers and data (encrypted) ~ 140 bytes
+  // So setting MTU equal to 1300 is most optimal
+  // as end packet will have size ~ 1442 bytes
+  // (My MTU is 1492 bytes)
+  var MTU = 1300;
+
+  // Try to fit MTU
+  if (buffer.length < MTU) {
+    this._writeData(false, buffer);
+  } else {
+    var len = buffer.length - MTU;
+    for (var offset = 0; offset < len; offset += MTU) {
+      this._writeData(false, buffer.slice(offset, offset + MTU));
+    }
+    this._writeData(false, buffer.slice(offset));
+  }
+};
+
+//
+// ### function end (data)
+// #### @data {Buffer|String} (optional) data to write before ending stream
+// #### @encoding {String} (optional) string encoding
+// Send FIN data frame
+//
+Stream.prototype.end = function end(data, encoding) {
+  // Do not send data to new connections after GOAWAY
+  if (this.isGoaway()) return;
+
+  if (data) this.write(data, encoding);
+
+  this._writeData(true, []);
+  this.closedBy.server = true;
+  this.handleClose();
+};
+
+//
+// ### function pause ()
+// Start buffering all data
+//
+Stream.prototype.pause = function pause() {
+  if (this._paused) return;
+  this._paused = true;
+};
+
+//
+// ### function resume ()
+// Start buffering all data
+//
+Stream.prototype.resume = function resume() {
+  if (!this._paused) return;
+  this._paused = false;
+
+  var self = this,
+      buffer = this._buffer;
+
+  this._buffer = [];
+
+  process.nextTick(function() {
+    buffer.forEach(function(chunk) {
+      self._read(chunk);
+    });
+  });
+};
+
+//
+// ### function _read (data)
+// #### @data {Buffer} buffer to read
+// (internal)
+//
+Stream.prototype._read = function _read(data) {
+  if (this._paused) {
+    this._buffer.push(data);
+    return;
+  }
+
+  var self = this;
+  process.nextTick(function() {
+    if (self.ondata) self.ondata(data, 0, data.length);
+    self.emit('data', data);
+  });
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/utils.js
@@ -0,0 +1,120 @@
+var utils = exports;
+
+var zlib = require('zlib'),
+    Buffer = require('buffer').Buffer;
+
+// SPDY deflate/inflate dictionary
+var dictionary = new Buffer([
+  'optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-',
+  'languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi',
+  'f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser',
+  '-agent10010120020120220320420520630030130230330430530630740040140240340440',
+  '5406407408409410411412413414415416417500501502503504505accept-rangesageeta',
+  'glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic',
+  'ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran',
+  'sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati',
+  'oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo',
+  'ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe',
+  'pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic',
+  'ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1',
+  '.1statusversionurl\x00'
+].join(''));
+
+//
+// ### function createDeflate ()
+// Creates deflate stream with SPDY dictionary
+//
+utils.createDeflate = function createDeflate() {
+  var deflate = zlib.createDeflate({ dictionary: dictionary, windowBits: 11 });
+
+  // Define lock information early
+  deflate.locked = false;
+  deflate.lockBuffer = [];
+
+  return deflate;
+};
+
+//
+// ### function createInflate ()
+// Creates inflate stream with SPDY dictionary
+//
+utils.createInflate = function createInflate() {
+  var inflate = zlib.createInflate({ dictionary: dictionary, windowBits: 15 });
+
+  // Define lock information early
+  inflate.locked = false;
+  inflate.lockBuffer = [];
+
+  return inflate;
+};
+
+//
+// ### function resetZlibStream (stream)
+// #### @stream {zlib.Stream} stream
+// Resets zlib stream and associated locks
+//
+utils.resetZlibStream = function resetZlibStream(stream, callback) {
+  if (stream.locked) {
+    stream.lockBuffer.push(function() {
+      resetZlibStream(stream, callback);
+    });
+    return;
+  }
+
+  stream.reset();
+  stream.lockBuffer = [];
+
+  callback(null);
+};
+
+var delta = 0;
+//
+// ### function zstream (stream, buffer, callback)
+// #### @stream {Deflate|Inflate} One of streams above
+// #### @buffer {Buffer} Input data (to compress or to decompress)
+// #### @callback {Function} Continuation to callback
+// Compress/decompress data and pass it to callback
+//
+utils.zstream = function zstream(stream, buffer, callback) {
+  var flush = stream._flush,
+      chunks = [],
+      total = 0;
+
+  if (stream.locked) {
+    stream.lockBuffer.push(function() {
+      zstream(stream, buffer, callback);
+    });
+    return;
+  }
+  stream.locked = true;
+
+  function collect(chunk) {
+    chunks.push(chunk);
+    total += chunk.length;
+  }
+  stream.on('data', collect);
+  stream.write(buffer);
+
+  stream.flush(function() {
+    stream.removeAllListeners('data');
+    stream._flush = flush;
+
+    callback(null, chunks, total);
+
+    stream.locked = false;
+    var deferred = stream.lockBuffer.shift();
+    if (deferred) deferred();
+  });
+};
+
+//
+// ### function zwrap (stream)
+// #### @stream {zlib.Stream} stream to wrap
+// Wraps stream within function to allow simple deflate/inflate
+//
+utils.zwrap = function zwrap(stream) {
+  return function(data, callback) {
+    utils.zstream(stream, data, callback);
+  };
+};
+
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/zlib-pool.js
@@ -0,0 +1,52 @@
+var zlibpool = exports,
+    spdy = require('../spdy');
+
+//
+// ### function Pool ()
+// Zlib streams pool
+//
+function Pool() {
+  this.pool = [];
+}
+
+//
+// ### function create ()
+// Returns instance of Pool
+//
+zlibpool.create = function create() {
+  return new Pool();
+};
+
+var x = 0;
+//
+// ### function get ()
+// Returns pair from pool or a new one
+//
+Pool.prototype.get = function get(callback) {
+  if (this.pool.length > 0) {
+    return this.pool.pop();
+  } else {
+    return {
+      deflate: spdy.utils.createDeflate(),
+      inflate: spdy.utils.createInflate()
+    };
+  }
+};
+
+//
+// ### function put (pair)
+// Puts pair into pool
+//
+Pool.prototype.put = function put(pair) {
+  var self = this,
+      waiting = 2;
+
+  spdy.utils.resetZlibStream(pair.inflate, done);
+  spdy.utils.resetZlibStream(pair.deflate, done);
+
+  function done() {
+    if (--waiting === 0) {
+      self.pool.push(pair);
+    }
+  }
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/package.json
@@ -0,0 +1,33 @@
+{
+  "name": "spdy",
+  "version": "1.2.0",
+  "description": "Implementation of the SPDY protocol on node.js.",
+  "keywords": [
+    "spdy"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/indutny/node-spdy.git"
+  },
+  "homepage": "https://github.com/indutny/node-spdy",
+  "bugs": {
+    "email": "node-spdy+bugs@indutny.com",
+    "url": "https://github.com/indunty/node-spdy/issues"
+  },
+  "author": "Fedor Indutny <fedor.indutny@gmail.com>",
+  "contributors": [
+    "Chris Storm <github@eeecooks.com>",
+    "Fran├žois de Metz <francois@2metz.fr>"
+  ],
+  "dependencies": {},
+  "devDependencies": {
+    "mocha": "~ 0.10.1"
+  },
+  "scripts": {
+    "test": "mocha --ui tdd --growl --reporter spec test/unit/*-test.js"
+  },
+  "engines": [
+    "node ~ 0.7.0"
+  ],
+  "main": "./lib/spdy"
+}
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/test/benchmarks/syn.js
@@ -0,0 +1,48 @@
+var tls = require('tls'),
+    frames = require('../fixtures/frames');
+
+var uri = require('url').parse(process.argv[2]),
+    host = uri.hostname,
+    port = +uri.port,
+    url = uri.path;
+
+frames.createSynStream(host, url, function(syn_stream) {
+  var start = +new Date,
+      num = 3000;
+
+  batch(port, host, syn_stream, 100, num, function() {
+    var end = +new Date;
+    console.log('requests/sec : %d', 1000 * num / (end - start));
+  });
+});
+
+function request(port, host, data, callback) {
+  var socket = tls.connect(port, host, {NPNProtocols: ['spdy/2']}, function() {
+    socket.write(data);
+    socket.once('data', function() {
+      socket.destroy();
+      callback();
+    });
+  });
+
+  return socket;
+};
+
+function batch(port, host, data, parallel, num, callback) {
+  var left = num,
+      done = 0;
+
+  for (var i = 0; i < parallel; i++) {
+    run(i);
+  }
+
+  function run(i) {
+    left--;
+    if (left < 0) return;
+
+    request(port, host, data, function() {
+      if (++done ===  num) return callback();
+      run(i);
+    });
+  }
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/test/fixtures/frames.js
@@ -0,0 +1,38 @@
+var spdy = require('../../lib/spdy'),
+    Buffer = require('buffer').Buffer;
+
+exports.createSynStream = function(host, url, callback) {
+  var deflate = spdy.utils.createDeflate(),
+      chunks = [],
+      chunksTotal = 0,
+      syn_stream;
+
+  deflate.on('data', function(chunk) {
+    chunks.push(chunk);
+    chunksTotal += chunk.length;
+  });
+  deflate.write(new Buffer([ 0x00, 0x02, 0x00, 0x04 ]));
+  deflate.write('host');
+  deflate.write(new Buffer([ 0x00, host.length ]));
+  deflate.write(host);
+  deflate.write(new Buffer([ 0x00, 0x03 ]));
+  deflate.write('url');
+  deflate.write(new Buffer([ 0x00, url.length ]));
+  deflate.write(url);
+
+  deflate.flush(function() {
+    syn_stream = new Buffer(18 + chunksTotal);
+    syn_stream.writeUInt32BE(0x80020001, 0);
+    syn_stream.writeUInt32BE(chunksTotal + 8, 4);
+    syn_stream.writeUInt32BE(0x00000001, 8);
+    syn_stream.writeUInt32BE(0x00000000, 12);
+
+    var offset = 18;
+    chunks.forEach(function(chunk) {
+      chunk.copy(syn_stream, offset);
+      offset += chunk.length;
+    });
+
+    callback(syn_stream);
+  });
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/test/fixtures/keys.js
@@ -0,0 +1,11 @@
+var fs = require('fs'),
+    path = require('path');
+
+var keysDir = require('path').resolve(__dirname, '../../keys/'),
+    options = {
+      key: fs.readFileSync(keysDir + '/spdy-key.pem'),
+      cert: fs.readFileSync(keysDir + '/spdy-cert.pem'),
+      ca: fs.readFileSync(keysDir + '/spdy-csr.pem')
+    };
+
+module.exports = options;
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/test/unit/framer-test.js
@@ -0,0 +1,190 @@
+var assert = require('assert'),
+    spdy = require('../../'),
+    Buffer = require('buffer').Buffer,
+    Stream = require('stream').Stream;
+
+suite('A Framer of SPDY module', function() {
+  var inflate,
+      deflate,
+      framer;
+
+  setup(function() {
+    inflate = spdy.utils.zwrap(spdy.utils.createInflate());
+    deflate = spdy.utils.zwrap(spdy.utils.createDeflate());
+    framer = new spdy.protocol[2].Framer(deflate, inflate);
+  });
+
+  /*
+    deflate.on('data', function(b) {console.log(b)});
+    deflate.write(new Buffer([
+      0x00, 0x02, // Number of name+value
+      0, 0x04, // Name length
+      0x68, 0x6f, 0x73, 0x74, // 'host'
+      0, 0x09, // Value length
+      0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, // 'localhost'
+      0, 0x06,
+      0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, // 'custom',
+      0, 0x1,
+      0x31 // '1'
+    ]));
+    deflate.flush();
+   */
+
+  suite('frame parsing', function() {
+    test('given a SYN_STREAM should return correct frame', function(done) {
+      var body = new Buffer([
+        0x00, 0x00, 0x00, 0x01, // Stream ID
+        0x00, 0x00, 0x00, 0x00, // Associated Stream ID
+        0x00, 0x00, // Priority + Unused
+        0x78, 0xbb, 0xdf, 0xa2, 0x51, 0xb2, // Deflated Name/Value pairs
+        0x62, 0x60, 0x62, 0x60, 0x01, 0xe5, 0x12,
+        0x06, 0x4e, 0x50, 0x50, 0xe6, 0x80, 0x99,
+        0x6c, 0xc9, 0xa5, 0xc5, 0x25, 0xf9, 0xb9,
+        0x0c, 0x8c, 0x86, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff
+      ]);
+      framer.execute({
+        control: true,
+        type: 1,
+        length: body.length
+      }, body, function(err, frame) {
+        assert.ok(!err);
+        assert.equal(frame.type, 'SYN_STREAM');
+        assert.equal(frame.id, 1);
+        assert.equal(frame.associated, 0);
+        assert.equal(frame.headers.host, 'localhost');
+        assert.equal(frame.headers.custom, '1');
+        done();
+      });
+    });
+
+    test('given a SYN_REPLY should return correct frame', function(done) {
+      var body = new Buffer([
+        0x00, 0x00, 0x00, 0x01, // Stream ID
+        0x00, 0x00, // Unused
+        0x78, 0xbb, 0xdf, 0xa2, 0x51, 0xb2, // Deflated Name/Value pairs
+        0x62, 0x60, 0x62, 0x60, 0x01, 0xe5, 0x12,
+        0x06, 0x4e, 0x50, 0x50, 0xe6, 0x80, 0x99,
+        0x6c, 0xc9, 0xa5, 0xc5, 0x25, 0xf9, 0xb9,
+        0x0c, 0x8c, 0x86, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff
+      ]);
+      framer.execute({
+        control: true,
+        type: 2,
+        length: body.length
+      }, body, function(err, frame) {
+        assert.ok(!err);
+        assert.equal(frame.type, 'SYN_REPLY');
+        assert.equal(frame.id, 1);
+        assert.equal(frame.headers.host, 'localhost');
+        assert.equal(frame.headers.custom, '1');
+        done();
+      });
+    });
+
+    test('given a RST_STREAM should return correct frame', function(done) {
+      var body = new Buffer([0, 0, 0, 1, 0, 0, 0, 2]);
+      framer.execute({
+        control: true,
+        type: 3,
+        length: body.length
+      }, body, function(err, frame) {
+        assert.ok(!err);
+        assert.equal(frame.type, 'RST_STREAM');
+        assert.equal(frame.id, 1);
+        assert.equal(frame.status, 2);
+        done();
+      });
+    });
+
+    test('given a NOOP frame should return correct frame', function(done) {
+      framer.execute({
+        control: true,
+        type: 5,
+        length: 0
+      }, new Buffer(0), function(err, frame) {
+        assert.ok(!err);
+        assert.equal(frame.type, 'NOOP');
+        done();
+      });
+    });
+
+    test('given a PING frame should return correct frame', function(done) {
+      framer.execute({
+        control: true,
+        type: 6,
+        length: 0
+      }, new Buffer(0), function(err, frame) {
+        assert.ok(!err);
+        assert.equal(frame.type, 'PING');
+        done();
+      });
+    });
+
+    test('given a GOAWAY frame should return correct frame', function(done) {
+      var body = new Buffer([0, 0, 0, 1]);
+      framer.execute({
+        control: true,
+        type: 7,
+        length: body.length
+      }, body, function(err, frame) {
+        assert.ok(!err);
+        assert.equal(frame.type, 'GOAWAY');
+        assert.equal(frame.lastId, 1);
+        done();
+      });
+    });
+  });
+
+  suite('frame generation', function() {
+    test('.replyFrame() should generate correct frame', function(done) {
+      framer.replyFrame(1, 200, 'ok', {}, function(err, chunks) {
+        assert.equal(err, null);
+        assert.ok(chunks.length > 1);
+        done();
+      });
+    });
+
+    test('.streamFrame() should generate correct frame', function(done) {
+      framer.streamFrame(2, 1, { url : '/' }, {}, function(err, chunks) {
+        assert.equal(err, null);
+        assert.ok(chunks.length > 1);
+        done();
+      });
+    });
+
+    test('.dataFrame() w/o fin should generate correct frame', function() {
+      var frame = framer.dataFrame(1, false, new Buffer(123));
+      assert.equal(frame[4], 0);
+      assert.ok(frame.length > 8);
+    });
+
+    test('.dataFrame() with fin should generate correct frame', function() {
+      var frame = framer.dataFrame(1, true, new Buffer(123));
+      assert.equal(frame[4], 1);
+      assert.ok(frame.length > 8);
+    });
+
+    test('.pingFrame() should generate correct frame', function() {
+      var frame = framer.pingFrame(new Buffer([0, 1, 2, 3]));
+      assert.ok(frame.length > 0);
+    });
+
+    test('.rstFrame() should generate correct frame', function() {
+      var frame = framer.rstFrame(1, 2);
+      assert.ok(frame.length > 0);
+
+      // Verify that cache works
+      var frame = framer.rstFrame(1, 2);
+      assert.ok(frame.length > 0);
+    });
+
+    test('.maxStreamsFrame() should generate correct frame', function() {
+      var frame = framer.maxStreamsFrame(13);
+      assert.ok(frame.length > 0);
+
+      // Verify that cache works
+      var frame = framer.maxStreamsFrame(13);
+      assert.ok(frame.length > 0);
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/test/unit/parser-test.js
@@ -0,0 +1,91 @@
+var assert = require('assert'),
+    spdy = require('../../'),
+    Buffer = require('buffer').Buffer;
+
+suite('A Parser of SPDY module', function() {
+  var parser;
+
+  setup(function() {
+    var deflate = spdy.utils.createDeflate(),
+        inflate = spdy.utils.createInflate();
+
+    parser = new spdy.parser.create(deflate, inflate);
+  });
+
+  test('should wait for headers initially', function() {
+    assert.equal(parser.waiting, 8);
+  });
+
+  test('should update buffered property once given < 8 bytes', function() {
+    parser.write(new Buffer(5));
+    assert.equal(parser.buffered, 5);
+  });
+
+  test('given SYN_STREAM header should start waiting for body', function() {
+    parser.write(new Buffer([
+      0x80, 0x02, 0x00, 0x01, // Control frame, version, type (SYN_STREAM)
+      0x00, 0x00, 0x12, 0x34
+    ]));
+
+    assert.equal(parser.waiting, 0x1234);
+    assert.equal(parser.state.type, 'frame-body');
+    assert.ok(parser.state.header.control);
+    assert.equal(parser.state.header.flags, 0);
+    assert.equal(parser.state.header.length, 0x1234);
+  });
+
+  test('given DATA header should start waiting for body', function() {
+    parser.write(new Buffer([
+      0x00, 0x00, 0x00, 0x01, // Data frame, stream ID
+      0x00, 0x00, 0x12, 0x34
+    ]));
+
+    assert.equal(parser.waiting, 0x1234);
+    assert.equal(parser.state.type, 'frame-body');
+    assert.ok(!parser.state.header.control);
+    assert.equal(parser.state.header.id, 1);
+    assert.equal(parser.state.header.flags, 0);
+    assert.equal(parser.state.header.length, 0x1234);
+  });
+
+  test('given chunked header should not fail', function() {
+    parser.write(new Buffer([
+      0x80, 0x02, 0x00, 0x01 // Control frame, version, type (SYN_STREAM)
+    ]));
+    assert.equal(parser.buffered, 4);
+
+    parser.write(new Buffer([
+      0x00, 0x00, 0x12, 0x34
+    ]));
+    assert.equal(parser.buffered, 0);
+
+    assert.equal(parser.waiting, 0x1234);
+    assert.equal(parser.state.type, 'frame-body');
+    assert.ok(parser.state.header.control);
+    assert.equal(parser.state.header.flags, 0);
+    assert.equal(parser.state.header.length, 0x1234);
+  });
+
+  test('given header and body should emit `frame`', function(done) {
+    parser.on('frame', function(frame) {
+      assert.ok(frame.type === 'DATA');
+      assert.equal(frame.id, 1);
+      assert.equal(frame.data.length, 4);
+      assert.equal(frame.data[0], 0x01);
+      assert.equal(frame.data[1], 0x02);
+      assert.equal(frame.data[2], 0x03);
+      assert.equal(frame.data[3], 0x04);
+      done();
+    });
+
+    parser.write(new Buffer([
+      0x00, 0x00, 0x00, 0x01, // Data frame, stream ID
+      0x00, 0x00, 0x00, 0x04,
+      0x01, 0x02, 0x03, 0x04  // Body
+    ]));
+
+    // Waits for next frame
+    assert.equal(parser.waiting, 8);
+    assert.equal(parser.state.type, 'frame-head');
+  });
+});
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/test/unit/server-test.js
@@ -0,0 +1,142 @@
+var assert = require('assert'),
+    spdy = require('../../'),
+    keys = require('../fixtures/keys'),
+    https = require('https'),
+    tls = require('tls'),request
+    Buffer = require('buffer').Buffer;
+
+
+suite('A SPDY Server', function() {
+  var server;
+  setup(function(done) {
+    server = spdy.createServer(keys, function(req, res) {
+      res.end('ok');
+    });
+
+    server.listen(8081, done);
+  });
+
+  teardown(function(done) {
+    server.once('close', done);
+    server.close();
+  });
+
+  test('should respond on regular https requests', function(done) {
+    https.request({
+      host: 'localhost',
+      port: 8081,
+      path: '/',
+      method: 'GET'
+    }, function(res) {
+      assert.equal(res.statusCode, 200);
+      done();
+    }).end();
+  });
+
+  test('should respond on spdy requests', function(done) {
+    var socket = tls.connect(
+      8081,
+      'localhost',
+      { NPNProtocols: ['spdy/2'] },
+      function() {
+        var deflate = spdy.utils.createDeflate(),
+            chunks = [],
+            length = 0;
+
+        deflate.on('data', function(chunk) {
+          chunks.push(chunk);
+          length += chunk.length;
+        });
+
+        // Deflate headers
+        deflate.write(new Buffer([
+          0x00, 0x04, // method, url, version = 3 fields
+          0x00, 0x06,
+          0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // method
+          0x00, 0x03,
+          0x47, 0x45, 0x54, // get
+          0x00, 0x03,
+          0x75, 0x72, 0x6c, // url
+          0x00, 0x01,
+          0x2f, // '/'
+          0x00, 0x07,
+          0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, // version
+          0x00, 0x08,
+          0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, // HTTP/1.1
+          0x00, 0x04,
+          0x68, 0x6f, 0x73, 0x74, // host
+          0x00, 0x09,
+          0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74 //localhost
+        ]));
+
+        deflate.flush(function() {
+          // StreamID + Associated StreamID
+          length += 10;
+
+          // Headers
+          socket.write(new Buffer([
+            0x80, 0x02, 0x00, 0x01, // Control, Version, SYN_STREAM
+            0x00, 0x00, 0x00, length, // Flags, length (1 byte in this case)
+            0x00, 0x00, 0x00, 0x01, // StreamID
+            0x00, 0x00, 0x00, 0x00, // Associated StreamID
+            0x00, 0x00 // Priority + Unused
+          ]));
+
+          // Write compressed headers
+          chunks.forEach(function(chunk) {
+            socket.write(chunk);
+          });
+        });
+
+        var response = new Buffer(85),
+            offset = 0;
+
+        socket.on('data', function(chunk) {
+          assert.ok(offset + chunk.length <= 85);
+
+          chunk.copy(response, offset);
+          offset += chunk.length;
+
+          if (offset === 85) {
+            var frames = [];
+
+            offset = 0;
+            while (offset < response.length) {
+              var len = (response.readUInt32BE(offset + 4) & 0x00ffffff) + 8;
+              frames.push(response.slice(offset, offset + len));
+
+              offset += len;
+            }
+
+            // SYN_STREAM frame
+            assert.ok(frames.some(function(frame) {
+              return frame[0] === 0x80 && // Control frame
+                     frame[1] === 0x02 && // Version
+                     frame.readUInt16BE(2) === 0x0002 && // SYN_STREAM
+                     frame.readUInt32BE(8) === 0x0001; // StreamID
+            }));
+
+            // Data frames
+            assert.ok(frames.some(function(frame) {
+              return frame[0] === 0x00 && // Data frame
+                     frame.readUInt32BE(0) === 0x0001 && // StreamID
+                     frame.slice(8).toString() === 'ok';
+            }));
+
+            socket.destroy();
+          }
+        });
+
+        socket.on('close', function() {
+          done();
+        });
+      }
+    );
+
+    server.on('request', function(req, res) {
+      assert.equal(req.url, '/');
+      assert.equal(req.method, 'GET');
+      res.end('ok');
+    });
+  });
+});