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 92451 301bf79e90299c12975096e22d0f891c2e64f304
parent 92450 adea606b56942b2a7d5ad218daaa31ed75ee72e5
child 92452 d0d3c5935c19370409814184981c948d013a102f
push id8694
push userryanvm@gmail.com
push dateThu, 26 Apr 2012 00:12:38 +0000
treeherdermozilla-inbound@81736e2ab216 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcmanus, ted
bugs719609
milestone15.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
zc$@(i3hDKUP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F8000TyNkl<ZScS!x
zX>45Ad4`|6%#br24i}LkDao7>DN3R&%91Quwrt6A<T!EaxJF}0tx}_I;{r{Bq6iuU
zXj2qw?R5Fk0$q_GK_erulORcLxK<pwwrn|8ZCTdBMUo{_+=k?k5{JXt@4aX1k0Dt#
zw%Y_L(DUbB99*2|z3=(H?|cXTpNI8}e7tAhr?+)<_1(L=Be6XeZ%NcdVvQD9zE>`m
zON&$Uvr|K}6GQ)z9vgUnWMstuzY-8xxAowW?%NMM)zR5~m+ge@KuClTD5)_RY}>|l
zT|%J{V8}0|uTETk=eaBIy!0(7r+*m%wGSM5=0u`r`~G#UQO@*_v9MS|8!*OT05E`s
z1=zNQ>pDcEQR-s#_@%|!>t|p1i?ROW|L}1D=i}y&eCbdAtf9H%?;1P$b|u#~GdI7;
zmFqVUQlYd)DuoaVAv8)UwALu43529ns*ua&QC29{l-&Jrv^lXeGdlDd1m&McK+RYF
z>E!ns+q!?xA9=0yzWWdI`nf4C4bS4(pp-^PO{6x&s-`GS4Yh<^8$XcvzQndIY`bc#
z@O?xOP}`W;RKI5PLz$Vu<H$<>f7$};H;%sWy}b`Vc4+Rx>$sDzvh)7KeEpkm;rjxm
zG+nnfvv)_5mc|HL3IM|4FxU=P$8PZUxiPYNkD9PsUEP3XTUbslmN$E5@W)T@2ZE1u
z;J}mLIJ)`Z?|!+r(WfpJV<B~gJ9;`f@!k}bK=SCpE$rWM8&?O<bN19v7&>>FOBc^F
z(BIGW^bKy`nBY_QZzY>6F+G>Vb!?Wy#Au0A+q|y3I}uM^JNuIl2}o``aQEIX{Nu55
zxn%dYIYjCk$t*0=?#!d?C|i1yv_>^Q_|CT}=2BQ4Jy`2@V6WbY69#|b11_F<mwd@^
z=&}7Q<||ChEaEs8){@VG5VUl3-`y514oppseQ>J)>ybZv_IXpcF1e7ePzns`MM1nR
z$<*i-_HB%jDJfq1{xPC!Tgmnv#@+M~;njWE&71J!J!Bd;pf>Mh;qqxR=`5dqa1Y}%
zS;`fKZQB^CLUJX+t~+{moqy-mZvnyq=-6`azP*ROel(paAT)Ra!VWm0FroNr#PkKG
zhA%TF4C!sZ&ibA{TAeytA~tK99ac5OaieRQvRm-hB$0!!aQ>Y$RC2Q@KZm7#FdEx(
z&^R<SHa90)<mkxo@C6rO+x>@*_}V5=)y&oggocV2uraxo(n1rBkuhfCn@Ftfq>_Wy
zIzyt(VO88>v1rgrldV9Zu^l$w&3=7_aI~Ju$rLx|r&ufrDyoKP-5R{b4mNDu@+8NP
zf7?waleL|__Z%pdDrf_?1%Wd7!r-|Uv5+C<*}Qx467ISOQOZIHL!`!{uGV5zy^U0w
zuv-nbhz-gPF<aC;{k5-fBeTfV<Tz8K*SLCVfVtTj<|Zzo;yZV*>+bG$J2q_Ti8i+<
z=KTO`DKDimRD>bq8VrWACn%Q+L}Zz4NfK3(l4r=|HBuRh6+_80l(ok5Dzp}FaC0_I
zu~Z@)t*0%~Lwj-~TlXAbdh8mnzVdIBOZnQa&h>Y>@y6S_j9n8kTC-eM4NwsV*EW;`
zO{}32u&FD|FqN4jU*Ar1K7`g`A~oO(NEZZY70~XbDUbEj9<y;nAv{kYf&gPQ2Ixrk
z@c3_jfz<dYW$%?<*K%uHtYvJKzv4pC*Gy*xiS^yMv1aNDH>jJvz?{9COTbN0L)5nM
zjAm8}EN>Qf_$1!+Rn~vzVd`S_WD4`uQeZ%9P2hWItw|);6RnN4xyne(wlt1qEgdfZ
zRUiXpxIFE%b5AE5cihLosh4P<8v^N*S>4B2T`RWjqKzV)pP_y9-wDRg&=9HN6QBGP
zLTZFmO9C`nFP#}upwVEo<NC$?0xBTvgsU=dDUKG9&MD5PB){@&hqyUCPI~wvZTU3y
zbC>X=Ef~i^X+z}ZH9`yH*hcZlktf)3=WbH788D`5zt(82(MnZ?D24Bp7Tv|!;qh`Y
zTaLuqqLxu$EU@eq7c2`-U8~TxtBEJR^i^K`?lH#u-y%GJ86g4!Au+~)F~pmidGvFS
zbL5F9$t)JAc-5Z7XtM%WN}{AhD@8gzH|owzj113DU7zUcYwr!&8f`RYIS{b0Esz?1
z@P3ic^fmGMFMf^w_wOTj`3)w=uT$~^>f;S`ZP>v62M)4%%T{hK<jCZTSe8`<FT<ro
zNr93QDLgegI(*TcpPw&`5B~IYV(Z@Ccv!Pgk=QO6<E)5qERfpbhy98>Q*&%6jdN`#
z#a;L9<B`WcSFM=gFcnXbnw_Uysiw3s2BQ^POO%pGDUl+;4Y^E?PY+K{PWHP1WADBE
z!>teg_EYit7}>G{ZDHBBh_D^BwrS13kGOt<v)LIQ{MEzk+4le?-$zs`_(1_7Bzh^G
ztMF>GqNGAfffPPUN*WvLIrYZL7lEP;u#g@(HF5F8X=9+VMqyL{T4EW6W!Z=zpf&w6
z?c>j3=`v4z`K#=>=K%8yIr4=vrAmO1aw*r%O4KW@mIMf-2#_Km8jUbBGo5?$&6Cdp
zEFIW*L&v}Qmz7*vMO{VM3P1%|D1=vJRq9_^J^u!)6C3!QKlme>+dIkUa#f%)upH$}
z!C2jMy|U**1c<;V<T^xZ!#ww$WB*(z6#m<BOu3jH3FEhQZQs9JN<knMmJ=p3_%gQ@
zPV>n}4{_j;$MJ+A@VzQ%$>!xJ@H5da2z-yguMlz^;`K3Jd-cS7&mQ}i-v`Pc>WHRC
z2H)#wYrS**rtMv&vWHaQ_!-L67kTXP=aE(j5%^2FUHwzD^u8=e>ndD|fWWJuWI#9+
zB32h+p#MVZnWvBbmJnj(Lmknw5g(j;^X<;gHQP7ez9U&Im54O0C7UZyN?qiR9lH=p
zqm*2Mvl8vqc_jj*5J(|#EJJNgm}sPi3+K*`{rS;9eJY>NzkMt4qq^ek;JdHC8Lze4
zckkJ=)wV1mt?TI@7$QH>&+UDE7|TIPxdPWpB887u60H@sWe7Pok=hz;3toKjM{oVr
zH=g-oKA(T<XJH?AFIc=faOUL9$oaY6O&fbU6N#pl#8%D?USn+FG#k1)S+!~vMr({#
zfWb!Nx*<X#muRF49vU2&`rE(x>%ag0^UwdWl=AXNf&RCfkqvCS_Y<Ezy!)ZYKik%|
zvv(#nVW&n0+1{JrfqS;o(%gV$3`z-nufpuyjmlvE`43LK^5YYye|qW#A;bkhd>r8C
zy_|-D<eGI|yL#@pdsow%<mOz#U+tHRM6FUP9u{e>HL1zT$<dK(L)mQhGBCw0{r@j3
s;MP$K#DRLM9Yl_$lv)C+Uu3cW1QijSajz<FA^-pY07*qoM6N<$f~$2GtpET3
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');
+    });
+  });
+});