Bug 1337791 - Part 3: http/2 origin frame extension test. r=nwgh
authorPatrick McManus <mcmanus@ducksong.com>
Mon, 03 Apr 2017 17:24:49 -0400
changeset 351184 71b228f12fc112f938250b2fd8418d0370a9e147
parent 351183 fbb9934d1a90a696f659b71c08886895b2edbbdd
child 351185 48ad3b64dea66fa8b2183ff2f7921e1c40f19c8a
push id88808
push userryanvm@gmail.com
push dateTue, 04 Apr 2017 18:42:26 +0000
treeherdermozilla-inbound@71b228f12fc1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnwgh
bugs1337791
milestone55.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 1337791 - Part 3: http/2 origin frame extension test. r=nwgh
netwerk/test/unit/test_origin.js
netwerk/test/unit/xpcshell.ini
testing/xpcshell/moz-http2/http2-cert.pem
testing/xpcshell/moz-http2/moz-http2.js
testing/xpcshell/node-http2/lib/protocol/connection.js
testing/xpcshell/node-http2/lib/protocol/flow.js
testing/xpcshell/node-http2/lib/protocol/framer.js
testing/xpcshell/node-http2/lib/protocol/stream.js
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_origin.js
@@ -0,0 +1,254 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var h2Port;
+var prefs;
+var spdypref;
+var http2pref;
+
+function run_test() {
+  var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+  h2Port = env.get("MOZHTTP2_PORT");
+  do_check_neq(h2Port, null);
+  do_check_neq(h2Port, "");
+
+  // Set to allow the cert presented by our H2 server
+  do_get_profile();
+  prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+  spdypref = prefs.getBoolPref("network.http.spdy.enabled");
+  http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2");
+
+  prefs.setBoolPref("network.http.spdy.enabled", true);
+  prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+  prefs.setCharPref("network.dns.localDomains", "foo.example.com, alt1.example.com");
+
+  // The moz-http2 cert is for {foo, alt1, alt2}.example.com and is signed by CA.cert.der
+  // so add that cert to the trust list as a signing cert.
+  let certdb = Cc["@mozilla.org/security/x509certdb;1"]
+                  .getService(Ci.nsIX509CertDB);
+  addCertFromFile(certdb, "CA.cert.der", "CTu,u,u");
+
+  doTest1();
+}
+
+function resetPrefs() {
+  prefs.setBoolPref("network.http.spdy.enabled", spdypref);
+  prefs.setBoolPref("network.http.spdy.enabled.http2", http2pref);
+  prefs.clearUserPref("network.dns.localDomains");
+}
+
+function readFile(file) {
+  let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
+                  .createInstance(Ci.nsIFileInputStream);
+  fstream.init(file, -1, 0, 0);
+  let data = NetUtil.readInputStreamToString(fstream, fstream.available());
+  fstream.close();
+  return data;
+}
+
+function addCertFromFile(certdb, filename, trustString) {
+  let certFile = do_get_file(filename, false);
+  let der = readFile(certFile);
+  certdb.addCert(der, trustString);
+}
+
+function makeChan(origin) {
+  return NetUtil.newChannel({
+    uri: origin,
+    loadUsingSystemPrincipal: true
+  }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+var nextTest;
+var nextPortExpectedToBeSame = false;
+var currentPort = 0;
+var forceReload = false;
+var forceFailListener = false;
+
+var Listener = function() {};
+Listener.prototype.clientPort = 0;
+Listener.prototype = {
+  onStartRequest: function testOnStartRequest(request, ctx) {
+    do_check_true(request instanceof Components.interfaces.nsIHttpChannel);
+
+    if (!Components.isSuccessCode(request.status)) {
+      do_throw("Channel should have a success code! (" + request.status + ")");
+    }
+    do_check_eq(request.responseStatus, 200);
+    this.clientPort = parseInt(request.getResponseHeader("x-client-port"));
+  },
+
+  onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
+    read_stream(stream, cnt);
+  },
+
+  onStopRequest: function testOnStopRequest(request, ctx, status) {
+    do_check_true(Components.isSuccessCode(status));
+    if (nextPortExpectedToBeSame) {
+     do_check_eq(currentPort, this.clientPort);
+    } else {
+     do_check_neq(currentPort, this.clientPort);
+    }
+    currentPort = this.clientPort;
+    nextTest();
+    do_test_finished();
+  }
+};
+
+var FailListener = function() {};
+FailListener.prototype = {
+  onStartRequest: function testOnStartRequest(request, ctx) {
+    do_check_true(request instanceof Components.interfaces.nsIHttpChannel);
+    do_check_false(Components.isSuccessCode(request.status));
+  },
+  onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
+    read_stream(stream, cnt);
+  },
+  onStopRequest: function testOnStopRequest(request, ctx, status) {
+    do_check_false(Components.isSuccessCode(request.status));
+    nextTest();
+    do_test_finished();
+  }
+};
+
+function testsDone()
+{
+  dump("testsDone\n");
+  resetPrefs();
+}
+
+function doTest()
+{
+  dump("execute doTest " + origin + "\n");
+  var chan = makeChan(origin);
+  var listener;
+  if (!forceFailListener) {
+    listener = new Listener();
+  } else {
+    listener = new FailListener();
+  }
+  forceFailListener = false;
+
+  if (!forceReload) {
+    chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+  } else {
+    chan.loadFlags = Ci.nsIRequest.LOAD_FRESH_CONNECTION |
+                     Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+  }
+  forceReload = false;
+  chan.asyncOpen2(listener);
+}
+
+function doTest1()
+{
+  dump("doTest1()\n");
+  origin = "https://foo.example.com:" + h2Port + "/origin-1";
+  nextTest = doTest2;
+  nextPortExpectedToBeSame = false;
+  do_test_pending();
+  doTest();
+}
+
+function doTest2()
+{
+  // plain connection reuse
+  dump("doTest2()\n");
+  origin = "https://foo.example.com:" + h2Port + "/origin-2";
+  nextTest = doTest3;
+  nextPortExpectedToBeSame = true;
+  do_test_pending();
+  doTest();
+}
+
+function doTest3()
+{
+  // 7540 style coalescing
+  dump("doTest3()\n");
+  origin = "https://alt1.example.com:" + h2Port + "/origin-3";
+  nextTest = doTest4;
+  nextPortExpectedToBeSame = true;
+  do_test_pending();
+  doTest();
+}
+
+function doTest4()
+{
+  // forces an empty origin frame to be omitted
+  dump("doTest4()\n");
+  origin = "https://foo.example.com:" + h2Port + "/origin-4";
+  nextTest = doTest5;
+  nextPortExpectedToBeSame = true;
+  do_test_pending();
+  doTest();
+}
+
+function doTest5()
+{
+  // 7540 style coalescing should not work due to empty origin set
+  dump("doTest5()\n");
+  origin = "https://alt1.example.com:" + h2Port + "/origin-5";
+  nextTest = doTest6;
+  nextPortExpectedToBeSame = false;
+  do_test_pending();
+  doTest();
+}
+
+function doTest6()
+{
+  // get a fresh connection with alt1 and alt2 in origin set
+  // note that there is no dns for alt2
+  dump("doTest6()\n");
+  origin = "https://foo.example.com:" + h2Port + "/origin-6";
+  nextTest = doTest7;
+  nextPortExpectedToBeSame = false;
+  forceReload = true;
+  do_test_pending();
+  doTest();
+}
+
+function doTest7()
+{
+  // check conn reuse to ensure sni is implicit in origin set
+  dump("doTest7()\n");
+  origin = "https://foo.example.com:" + h2Port + "/origin-7";
+  nextTest = doTest8;
+  nextPortExpectedToBeSame = true;
+  do_test_pending();
+  doTest();
+}
+
+function doTest8()
+{
+  // alt1 is in origin set (and is 7540 eligible)
+  dump("doTest8()\n");
+  origin = "https://alt1.example.com:" + h2Port + "/origin-8";
+  nextTest = doTest9;
+  nextPortExpectedToBeSame = true;
+  do_test_pending();
+  doTest();
+}
+
+function doTest9()
+{
+  // alt2 is in origin set but does not have dns
+  dump("doTest9()\n");
+  origin = "https://alt2.example.com:" + h2Port + "/origin-9";
+  nextTest = doTest10;
+  nextPortExpectedToBeSame = true;
+  do_test_pending();
+  doTest();
+}
+
+function doTest10()
+{
+  // bar is in origin set but does not have dns like alt2
+  // but the cert is not valid for bar. so expect a failure
+  dump("doTest10()\n");
+  origin = "https://bar.example.com:" + h2Port + "/origin-10";
+  nextTest = testsDone;
+  nextPortExpectedToBeSame = false;
+  forceFailListener = true;
+  do_test_pending();
+  doTest();
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -245,16 +245,17 @@ run-sequentially = node server exception
 [test_multipart_streamconv_missing_boundary_lead_dashes.js]
 [test_multipart_streamconv-byte-by-byte.js]
 [test_nestedabout_serialize.js]
 [test_net_addr.js]
 # Bug 732363: test fails on windows for unknown reasons.
 skip-if = os == "win"
 [test_nojsredir.js]
 [test_offline_status.js]
+[test_origin.js]
 [test_original_sent_received_head.js]
 [test_parse_content_type.js]
 [test_permmgr.js]
 [test_plaintext_sniff.js]
 [test_post.js]
 [test_private_necko_channel.js]
 [test_private_cookie_changed.js]
 [test_progress.js]
--- a/testing/xpcshell/moz-http2/http2-cert.pem
+++ b/testing/xpcshell/moz-http2/http2-cert.pem
@@ -1,79 +1,24 @@
-Certificate:
-    Data:
-        Version: 3 (0x2)
-        Serial Number: 1 (0x1)
-    Signature Algorithm: sha256WithRSAEncryption
-        Issuer: C=US, ST=Maine, O=CA Example
-        Validity
-            Not Before: Apr 29 05:29:19 2015 GMT
-            Not After : Apr 26 05:29:19 2025 GMT
-        Subject: C=US, ST=Maine, O=Example Com, CN=foo.example.com
-        Subject Public Key Info:
-            Public Key Algorithm: rsaEncryption
-                Public-Key: (2048 bit)
-                Modulus:
-                    00:cf:ff:c0:27:3b:a3:11:b5:7f:5d:4f:22:f9:75:
-                    48:47:d9:3a:ce:9b:66:82:4e:e4:ae:ab:78:d3:4c:
-                    3a:9a:5c:37:97:b2:7b:4e:2a:54:77:16:2a:3e:6f:
-                    52:ee:4b:49:46:1d:6b:18:9a:ed:b1:ad:64:9f:8b:
-                    e5:fa:e4:60:7b:39:0e:db:e8:b4:2d:4b:e8:ab:37:
-                    e8:90:ec:eb:0f:3e:6b:40:7a:d1:da:e6:68:b3:f4:
-                    f6:68:54:5b:27:90:6d:c2:c3:04:de:85:23:2b:3c:
-                    66:4e:06:79:58:93:a1:71:d7:ec:74:55:a4:84:9d:
-                    41:22:2a:7a:76:ae:56:b1:6f:15:2d:f2:f5:9c:64:
-                    3e:4f:0f:6e:8f:b6:28:66:e9:89:04:5d:1d:21:77:
-                    f8:03:d3:89:ed:7c:f4:3b:42:02:c8:8d:de:47:74:
-                    1f:4a:5d:fe:8d:d1:57:37:08:54:bf:89:d8:f7:27:
-                    22:a7:2a:5d:aa:d5:b0:61:22:9b:96:75:ee:ab:09:
-                    ca:a9:cb:2b:1e:88:7c:5a:53:7e:5f:88:c4:43:ea:
-                    e8:a7:db:35:6c:b2:89:ad:98:e0:96:c9:83:c4:c1:
-                    e7:2a:5c:f8:99:5c:9e:01:9c:e6:99:bd:18:5c:69:
-                    d4:10:f1:46:88:37:0b:4e:76:5f:6a:1a:21:c2:a4:
-                    16:d1
-                Exponent: 65537 (0x10001)
-        X509v3 extensions:
-            X509v3 Subject Key Identifier: 
-                76:BC:13:90:F7:85:1B:1C:24:A1:CC:65:8A:4F:4C:0C:7F:10:D3:F5
-            X509v3 Authority Key Identifier: 
-                keyid:F7:FC:76:AF:C5:1A:E9:C9:42:6C:38:DF:8B:07:9E:2B:2C:E5:8E:20
-
-            X509v3 Basic Constraints: 
-                CA:FALSE
-            X509v3 Key Usage: 
-                Digital Signature, Key Encipherment
-    Signature Algorithm: sha256WithRSAEncryption
-         03:ab:2a:9e:e5:cd:5c:88:5a:6c:f7:4b:7a:7c:ef:85:2c:31:
-         df:03:79:31:a6:c5:c8:2b:c6:21:a5:33:2b:a0:4b:e2:7e:0a:
-         86:9b:72:25:b6:75:43:41:7c:30:9f:15:b4:9f:34:50:57:eb:
-         87:f9:1e:9f:b6:cd:81:36:92:61:66:d5:fe:e2:c5:ed:de:f1:
-         ce:85:0b:f9:6a:2b:32:4d:29:f1:a9:94:57:a3:0f:74:93:12:
-         c9:0a:28:5e:72:9f:4f:0f:78:f5:84:11:5a:9f:d7:1c:4c:fd:
-         13:d8:3d:4c:f8:dd:4c:c6:1c:fd:63:ee:f5:d5:96:f5:00:2c:
-         e6:bb:c9:4c:d8:6a:19:59:58:2b:d4:05:ab:57:47:1c:49:d6:
-         c5:56:1a:e3:64:10:19:9b:44:3e:74:8b:19:73:28:86:96:b4:
-         d1:2a:49:23:07:25:97:64:8f:1b:1c:64:76:12:e0:df:e3:cf:
-         55:d5:7c:e9:77:d4:69:2f:c7:9a:fd:ce:1a:29:ab:d7:88:68:
-         93:de:75:e4:d6:85:29:e2:b6:b7:59:20:e3:b5:20:b7:e8:0b:
-         23:9b:4c:b4:e8:d9:90:cf:e9:2f:9e:a8:22:a2:ef:6a:68:65:
-         f6:c4:81:ed:75:77:88:01:f2:47:03:1a:de:1f:44:38:47:fa:
-         aa:69:f2:98
 -----BEGIN CERTIFICATE-----
-MIIDVDCCAjygAwIBAgIBATANBgkqhkiG9w0BAQsFADAyMQswCQYDVQQGEwJVUzEO
-MAwGA1UECAwFTWFpbmUxEzARBgNVBAoMCkNBIEV4YW1wbGUwHhcNMTUwNDI5MDUy
-OTE5WhcNMjUwNDI2MDUyOTE5WjBNMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFTWFp
-bmUxFDASBgNVBAoMC0V4YW1wbGUgQ29tMRgwFgYDVQQDDA9mb28uZXhhbXBsZS5j
-b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP/8AnO6MRtX9dTyL5
-dUhH2TrOm2aCTuSuq3jTTDqaXDeXsntOKlR3Fio+b1LuS0lGHWsYmu2xrWSfi+X6
-5GB7OQ7b6LQtS+irN+iQ7OsPPmtAetHa5miz9PZoVFsnkG3CwwTehSMrPGZOBnlY
-k6Fx1+x0VaSEnUEiKnp2rlaxbxUt8vWcZD5PD26Ptihm6YkEXR0hd/gD04ntfPQ7
-QgLIjd5HdB9KXf6N0Vc3CFS/idj3JyKnKl2q1bBhIpuWde6rCcqpyyseiHxaU35f
-iMRD6uin2zVssomtmOCWyYPEwecqXPiZXJ4BnOaZvRhcadQQ8UaINwtOdl9qGiHC
-pBbRAgMBAAGjWjBYMB0GA1UdDgQWBBR2vBOQ94UbHCShzGWKT0wMfxDT9TAfBgNV
-HSMEGDAWgBT3/HavxRrpyUJsON+LB54rLOWOIDAJBgNVHRMEAjAAMAsGA1UdDwQE
-AwIFoDANBgkqhkiG9w0BAQsFAAOCAQEAA6sqnuXNXIhabPdLenzvhSwx3wN5MabF
-yCvGIaUzK6BL4n4KhptyJbZ1Q0F8MJ8VtJ80UFfrh/ken7bNgTaSYWbV/uLF7d7x
-zoUL+WorMk0p8amUV6MPdJMSyQooXnKfTw949YQRWp/XHEz9E9g9TPjdTMYc/WPu
-9dWW9QAs5rvJTNhqGVlYK9QFq1dHHEnWxVYa42QQGZtEPnSLGXMohpa00SpJIwcl
-l2SPGxxkdhLg3+PPVdV86XfUaS/Hmv3OGimr14hok9515NaFKeK2t1kg47Ugt+gL
-I5tMtOjZkM/pL56oIqLvamhl9sSB7XV3iAHyRwMa3h9EOEf6qmnymA==
+MIID+TCCAuGgAwIBAgIJAKu6XZkGFQ8NMA0GCSqGSIb3DQEBCwUAMDIxCzAJBgNV
+BAYTAlVTMQ4wDAYDVQQIDAVNYWluZTETMBEGA1UECgwKQ0EgRXhhbXBsZTAeFw0x
+NzAzMjgwMjIxMjZaFw0yNzAzMjYwMjIxMjZaMIHIMQswCQYDVQQGEwJVUzERMA8G
+A1UECAwITmV3IFlvcmsxEjAQBgNVBAcMCVJvY2hlc3RlcjESMBAGA1UECgwJRW5k
+IFBvaW50MRcwFQYDVQQLDA5UZXN0aW5nIERvbWFpbjFLMEkGCSqGSIb3DQEJARY8
+eW91ci1hZG1pbmlzdHJhdGl2ZS1hZGRyZXNzQHlvdXItYXdlc29tZS1leGlzdGlu
+Zy1kb21haW4uY29tMRgwFgYDVQQDDA9mb28uZXhhbXBsZS5jb20wggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP/8AnO6MRtX9dTyL5dUhH2TrOm2aCTuSu
+q3jTTDqaXDeXsntOKlR3Fio+b1LuS0lGHWsYmu2xrWSfi+X65GB7OQ7b6LQtS+ir
+N+iQ7OsPPmtAetHa5miz9PZoVFsnkG3CwwTehSMrPGZOBnlYk6Fx1+x0VaSEnUEi
+Knp2rlaxbxUt8vWcZD5PD26Ptihm6YkEXR0hd/gD04ntfPQ7QgLIjd5HdB9KXf6N
+0Vc3CFS/idj3JyKnKl2q1bBhIpuWde6rCcqpyyseiHxaU35fiMRD6uin2zVssomt
+mOCWyYPEwecqXPiZXJ4BnOaZvRhcadQQ8UaINwtOdl9qGiHCpBbRAgMBAAGjezB5
+MB8GA1UdIwQYMBaAFPf8dq/FGunJQmw434sHniss5Y4gMAkGA1UdEwQCMAAwCwYD
+VR0PBAQDAgTwMD4GA1UdEQQ3MDWCD2Zvby5leGFtcGxlLmNvbYIQYWx0MS5leGFt
+cGxlLmNvbYIQYWx0Mi5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEARitg
+nxH87RYCMo/wbfSyttkiDmlw1vuEFI6dq9D6iTYcJK9pD9VvQ8e/k0J0FdtnIGBD
+O9+kKuPCRIjJt0mRToQHXI4SFIEqUraI5xA5VdXT2FR5KsshNSw6LjV25gvv0hcI
+6YBOlJ1IzntSA3h7lGGhgqH2ln32hzTQ8ob8F8i3GecOIk6mDkgCHTPRe7tfyTKw
+7c6Z8By6Es84RCQdxXf6AouhJw9SfZl1T5bcy5vDbBcNYenfvueCLezNX6kK7orh
+KsqnxWr2cG8c3X1OIuuvAEUbQ78InOb4OPiQQXcfv+dzxnv7tK6pNRcmMUhabwM8
+J3i97uzqNXPwTFMu3Q==
 -----END CERTIFICATE-----
--- a/testing/xpcshell/moz-http2/moz-http2.js
+++ b/testing/xpcshell/moz-http2/moz-http2.js
@@ -738,16 +738,31 @@ function handleRequest(req, res) {
   else if (u.pathname === "/immutable-test-with-attribute") {
     res.setHeader('Cache-Control', 'max-age=100000, immutable');
     res.setHeader('Etag', '2');
      if (req.headers["if-none-match"]) {
        res.setHeader("x-conditional", "true");
      }
    // default response from here
   }
+  else if (u.pathname === "/origin-4") {
+   var originList = [ ];
+   req.stream.connection.originFrame(originList);
+   res.setHeader("x-client-port", req.remotePort);
+  }
+  else if (u.pathname === "/origin-6") {
+   var originList = [ "https://alt1.example.com:" + serverPort,
+		      "https://alt2.example.com:" + serverPort,
+                      "https://bar.example.com:" + serverPort ];
+   req.stream.connection.originFrame(originList);
+   res.setHeader("x-client-port", req.remotePort);
+  }
+  else if (u.pathname.substring(0,8) === "/origin-") { // test_origin.js coalescing
+    res.setHeader("x-client-port", req.remotePort);
+  }
 
   res.setHeader('Content-Type', 'text/html');
   if (req.httpVersionMajor != 2) {
     res.setHeader('Connection', 'close');
   }
   res.writeHead(200);
   res.end(content);
 }
--- a/testing/xpcshell/node-http2/lib/protocol/connection.js
+++ b/testing/xpcshell/node-http2/lib/protocol/connection.js
@@ -118,17 +118,17 @@ Connection.prototype._initializeStreamMa
   this.on('RECEIVING_SETTINGS_MAX_CONCURRENT_STREAMS', this._updateStreamLimit);
 };
 
 // `_writeControlFrame` is called when there's an incoming frame in the `_control` stream. It
 // broadcasts the message by creating an event on it.
 Connection.prototype._writeControlFrame = function _writeControlFrame(frame) {
   if ((frame.type === 'SETTINGS') || (frame.type === 'PING') ||
       (frame.type === 'GOAWAY') || (frame.type === 'WINDOW_UPDATE') ||
-      (frame.type === 'ALTSVC')) {
+      (frame.type === 'ALTSVC') || (frame.type == 'ORIGIN')) {
     this._log.debug({ frame: frame }, 'Receiving connection level frame');
     this.emit(frame.type, frame);
   } else {
     this._log.error({ frame: frame }, 'Invalid connection level frame');
     this.emit('error', 'PROTOCOL_ERROR');
   }
 };
 
@@ -552,16 +552,27 @@ Connection.prototype._receivePing = func
         ACK: true
       },
       stream: 0,
       data: frame.data
     });
   }
 };
 
+Connection.prototype.originFrame = function originFrame(originList) {
+  this._log.debug(originList, 'emitting origin frame');
+
+  this.push({
+    type: 'ORIGIN',
+    flags: {},
+    stream: 0,
+    originList : originList,
+  });
+};
+
 // Terminating the connection
 Connection.prototype.close = function close(error) {
   if (this._closed) {
     this._log.warn('Trying to close an already closed connection');
     return;
   }
 
   this._log.debug({ error: error }, 'Closing the connection');
--- a/testing/xpcshell/node-http2/lib/protocol/flow.js
+++ b/testing/xpcshell/node-http2/lib/protocol/flow.js
@@ -59,17 +59,16 @@ var INITIAL_WINDOW_SIZE = 65535;
 function Flow(flowControlId) {
   Duplex.call(this, { objectMode: true });
 
   this._window = this._initialWindow = INITIAL_WINDOW_SIZE;
   this._flowControlId = flowControlId;
   this._queue = [];
   this._ended = false;
   this._received = 0;
-  this._blocked = false;
 }
 Flow.prototype = Object.create(Duplex.prototype, { constructor: { value: Flow } });
 
 // Incoming frames
 // ---------------
 
 // `_receive` is called when there's an incoming frame.
 Flow.prototype._receive = function _receive(frame, callback) {
@@ -152,40 +151,33 @@ Flow.prototype._read = function _read() 
   // * if the flow control queue is empty, then let the user push more frames
   if (this._queue.length === 0) {
     this._send();
   }
 
   // * if there are items in the flow control queue, then let's put them into the output queue (to
   //   the extent it is possible with respect to the window size and output queue feedback)
   else if (this._window > 0) {
-    this._blocked = false;
     this._readableState.sync = true; // to avoid reentrant calls
     do {
       var moreNeeded = this._push(this._queue[0]);
       if (moreNeeded !== null) {
         this._queue.shift();
       }
     } while (moreNeeded && (this._queue.length > 0));
     this._readableState.sync = false;
 
     assert((!moreNeeded) ||                              // * output queue is full
            (this._queue.length === 0) ||                         // * flow control queue is empty
            (!this._window && (this._queue[0].type === 'DATA'))); // * waiting for window update
   }
 
   // * otherwise, come back when the flow control window is positive
-  else if (!this._blocked) {
-    this._parentPush({
-      type: 'BLOCKED',
-      flags: {},
-      stream: this._flowControlId
-    });
+  else {
     this.once('window_update', this._read);
-    this._blocked = true;
   }
 };
 
 var MAX_PAYLOAD_SIZE = 4096; // Must not be greater than MAX_HTTP_PAYLOAD_SIZE which is 16383
 
 // `read(limit)` is like the `read` of the Readable class, but it guarantess that the 'flow control
 // size' (0 for non-DATA frames, length of the payload for DATA frames) of the returned frame will
 // be under `limit`.
--- a/testing/xpcshell/node-http2/lib/protocol/framer.js
+++ b/testing/xpcshell/node-http2/lib/protocol/framer.js
@@ -1064,36 +1064,34 @@ Deserializer.ALTSVC = function readAltSv
       frame.port = parseInt(hostport[1], 10);
     } else if (chosenAltSvc[i].name == 'ma') {
       frame.maxAge = parseInt(chosenAltSvc[i].value, 10);
     }
     // Otherwise, we just ignore this
   }
 };
 
-// BLOCKED
-// ------------------------------------------------------------
-//
-// The BLOCKED frame (type=0xB) indicates that the sender is unable to send data
-// due to a closed flow control window.
-//
-// The BLOCKED frame does not define any flags and contains no payload.
+frameTypes[0xB] = 'ORIGIN';
+frameFlags.ORIGIN = [];
+typeSpecificAttributes.ORIGIN = ['originList'];
 
-frameTypes[0xB] = 'BLOCKED';
-
-frameFlags.BLOCKED = [];
-
-typeSpecificAttributes.BLOCKED = [];
-
-Serializer.BLOCKED = function writeBlocked(frame, buffers) {
+Serializer.ORIGIN = function writeOrigin(frame, buffers) {
+  for (var i = 0; i < frame.originList.length; i++) {
+    var buffer = new Buffer(2);
+    buffer.writeUInt16BE(frame.originList[i].length, 0);
+    buffers.push(buffer);
+    buffers.push(new Buffer(frame.originList[i], 'ascii'));
+  }
 };
 
-Deserializer.BLOCKED = function readBlocked(buffer, frame) {
+Deserializer.ORIGIN = function readOrigin(buffer, frame) {
+    // ignored
 };
 
+
 // [Error Codes](https://tools.ietf.org/html/rfc7540#section-7)
 // ------------------------------------------------------------
 
 var errorCodes = [
   'NO_ERROR',
   'PROTOCOL_ERROR',
   'INTERNAL_ERROR',
   'FLOW_CONTROL_ERROR',
--- a/testing/xpcshell/node-http2/lib/protocol/stream.js
+++ b/testing/xpcshell/node-http2/lib/protocol/stream.js
@@ -248,17 +248,17 @@ Stream.prototype._writeUpstream = functi
     this._processedHeaders = true;
     this._onHeaders(frame);
   } else if (frame.type === 'PUSH_PROMISE') {
     this._onPromise(frame);
   } else if (frame.type === 'PRIORITY') {
     this._onPriority(frame);
   } else if (frame.type === 'ALTSVC') {
     // TODO
-  } else if (frame.type === 'BLOCKED') {
+  } else if (frame.type === 'ORIGIN') {
     // TODO
   }
 
   // * If it's an invalid stream level frame, emit error
   else if ((frame.type !== 'DATA') &&
            (frame.type !== 'WINDOW_UPDATE') &&
            (frame.type !== 'RST_STREAM')) {
     this._log.error({ frame: frame }, 'Invalid stream level frame');
@@ -408,27 +408,27 @@ function activeState(state) {
 // `_transition` is called every time there's an incoming or outgoing frame. It manages state
 // transitions, and detects stream errors. A stream error is always caused by a frame that is not
 // allowed in the current state.
 Stream.prototype._transition = function transition(sending, frame) {
   var receiving = !sending;
   var connectionError;
   var streamError;
 
-  var DATA = false, HEADERS = false, PRIORITY = false, ALTSVC = false, BLOCKED = false;
+  var DATA = false, HEADERS = false, PRIORITY = false, ALTSVC = false, ORIGIN = false;
   var RST_STREAM = false, PUSH_PROMISE = false, WINDOW_UPDATE = false;
   switch(frame.type) {
     case 'DATA'         : DATA          = true; break;
     case 'HEADERS'      : HEADERS       = true; break;
     case 'PRIORITY'     : PRIORITY      = true; break;
     case 'RST_STREAM'   : RST_STREAM    = true; break;
     case 'PUSH_PROMISE' : PUSH_PROMISE  = true; break;
     case 'WINDOW_UPDATE': WINDOW_UPDATE = true; break;
     case 'ALTSVC'       : ALTSVC        = true; break;
-    case 'BLOCKED'      : BLOCKED       = true; break;
+    case 'ORIGIN'       : ORIGIN        = true; break;
   }
 
   var previousState = this.state;
 
   switch (this.state) {
     // All streams start in the **idle** state. In this state, no frames have been exchanged.
     //
     // * Sending or receiving a HEADERS frame causes the stream to become "open".
@@ -478,17 +478,17 @@ Stream.prototype._transition = function 
     // * Receiving a HEADERS frame causes the stream to transition to "half closed (local)".
     // * An endpoint MAY send PRIORITY frames in this state to reprioritize the stream.
     // * Receiving any other type of frame MUST be treated as a stream error of type PROTOCOL_ERROR.
     case 'RESERVED_REMOTE':
       if (RST_STREAM) {
         this._setState('CLOSED');
       } else if (receiving && HEADERS) {
         this._setState('HALF_CLOSED_LOCAL');
-      } else if (BLOCKED || PRIORITY) {
+      } else if (PRIORITY || ORIGIN) {
         /* No state change */
       } else {
         connectionError = 'PROTOCOL_ERROR';
       }
       break;
 
     // The **open** state is where both peers can send frames. In this state, sending peers observe
     // advertised stream level flow control limits.
@@ -513,17 +513,17 @@ Stream.prototype._transition = function 
     //
     // * A stream transitions from this state to "closed" when a frame that contains a END_STREAM
     //   flag is received, or when either peer sends a RST_STREAM frame.
     // * An endpoint MAY send or receive PRIORITY frames in this state to reprioritize the stream.
     // * WINDOW_UPDATE can be sent by a peer that has sent a frame bearing the END_STREAM flag.
     case 'HALF_CLOSED_LOCAL':
       if (RST_STREAM || (receiving && frame.flags.END_STREAM)) {
         this._setState('CLOSED');
-      } else if (BLOCKED || ALTSVC || receiving || PRIORITY || (sending && WINDOW_UPDATE)) {
+      } else if (ORIGIN || ALTSVC || receiving || PRIORITY || (sending && WINDOW_UPDATE)) {
         /* No state change */
       } else {
         connectionError = 'PROTOCOL_ERROR';
       }
       break;
 
     // A stream that is **half closed (remote)** is no longer being used by the peer to send frames.
     // In this state, an endpoint is no longer obligated to maintain a receiver flow control window
@@ -533,17 +533,17 @@ Stream.prototype._transition = function 
     //   respond with a stream error of type STREAM_CLOSED.
     // * A stream can transition from this state to "closed" by sending a frame that contains a
     //   END_STREAM flag, or when either peer sends a RST_STREAM frame.
     // * An endpoint MAY send or receive PRIORITY frames in this state to reprioritize the stream.
     // * A receiver MAY receive a WINDOW_UPDATE frame on a "half closed (remote)" stream.
     case 'HALF_CLOSED_REMOTE':
       if (RST_STREAM || (sending && frame.flags.END_STREAM)) {
         this._setState('CLOSED');
-      } else if (BLOCKED || ALTSVC || sending || PRIORITY || (receiving && WINDOW_UPDATE)) {
+      } else if (ORIGIN || ALTSVC || sending || PRIORITY || (receiving && WINDOW_UPDATE)) {
         /* No state change */
       } else {
         connectionError = 'PROTOCOL_ERROR';
       }
       break;
 
     // The **closed** state is the terminal state.
     //
@@ -564,17 +564,17 @@ Stream.prototype._transition = function 
     //   time as being in error.
     // * An endpoint might receive a PUSH_PROMISE frame after it sends RST_STREAM. PUSH_PROMISE
     //   causes a stream to become "reserved". If promised streams are not desired, a RST_STREAM
     //   can be used to close any of those streams.
     case 'CLOSED':
       if (PRIORITY || (sending && RST_STREAM) ||
           (receiving && WINDOW_UPDATE) ||
           (receiving && this._closedByUs &&
-           (this._closedWithRst || RST_STREAM || ALTSVC))) {
+           (this._closedWithRst || RST_STREAM || ALTSVC || ORIGIN))) {
         /* No state change */
       } else {
         streamError = 'STREAM_CLOSED';
       }
       break;
   }
 
   // Noting that the connection was closed by the other endpoint. It may be important in edge cases.