summaryrefslogtreecommitdiffstats
path: root/netwerk/test/unit/http2_test_common.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /netwerk/test/unit/http2_test_common.js
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/test/unit/http2_test_common.js')
-rw-r--r--netwerk/test/unit/http2_test_common.js1504
1 files changed, 1504 insertions, 0 deletions
diff --git a/netwerk/test/unit/http2_test_common.js b/netwerk/test/unit/http2_test_common.js
new file mode 100644
index 0000000000..341aa191da
--- /dev/null
+++ b/netwerk/test/unit/http2_test_common.js
@@ -0,0 +1,1504 @@
+// test HTTP/2
+
+"use strict";
+
+/* import-globals-from head_channels.js */
+
+// 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(250000));
+posts.push(generateContent(128000));
+
+// pre-calculated md5sums (in hex) of the above posts
+var md5s = [
+ "f1b708bba17f1ce948dc979f4d7092bc",
+ "2ef8d3b6c8f329318eb1a119b12622b6",
+];
+
+var bigListenerData = generateContent(128 * 1024);
+var bigListenerMD5 = "8f607cfdd2c87d6a7eedb657dafbd836";
+
+function checkIsHttp2(request) {
+ try {
+ if (request.getResponseHeader("X-Firefox-Spdy") == "h2") {
+ if (request.getResponseHeader("X-Connection-Http2") == "yes") {
+ return true;
+ }
+ return false; // Weird case, but the server disagrees with us
+ }
+ } catch (e) {
+ // Nothing to do here
+ }
+ return false;
+}
+
+var Http2CheckListener = function () {};
+
+Http2CheckListener.prototype = {
+ onStartRequestFired: false,
+ onDataAvailableFired: false,
+ isHttp2Connection: false,
+ shouldBeHttp2: true,
+ accum: 0,
+ expected: -1,
+ shouldSucceed: true,
+
+ onStartRequest: function testOnStartRequest(request) {
+ this.onStartRequestFired = true;
+ if (this.shouldSucceed && !Components.isSuccessCode(request.status)) {
+ do_throw("Channel should have a success code! (" + request.status + ")");
+ } else if (
+ !this.shouldSucceed &&
+ Components.isSuccessCode(request.status)
+ ) {
+ do_throw("Channel succeeded unexpectedly!");
+ }
+
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+ Assert.equal(request.requestSucceeded, this.shouldSucceed);
+ if (this.shouldSucceed) {
+ Assert.equal(request.responseStatus, 200);
+ }
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ this.accum += cnt;
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ Assert.ok(this.onStartRequestFired);
+ if (this.expected != -1) {
+ Assert.equal(this.accum, this.expected);
+ }
+
+ if (this.shouldSucceed) {
+ Assert.ok(Components.isSuccessCode(status));
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection == this.shouldBeHttp2);
+ } else {
+ Assert.ok(!Components.isSuccessCode(status));
+ }
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+ },
+};
+
+/*
+ * Support for testing valid multiplexing of streams
+ */
+
+var multiplexContent = generateContent(30 * 1024);
+
+/* Listener class to control the testing of multiplexing */
+var Http2MultiplexListener = function () {};
+
+Http2MultiplexListener.prototype = new Http2CheckListener();
+
+Http2MultiplexListener.prototype.streamID = 0;
+Http2MultiplexListener.prototype.buffer = "";
+
+Http2MultiplexListener.prototype.onDataAvailable = function (
+ request,
+ stream,
+ off,
+ cnt
+) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ this.streamID = parseInt(request.getResponseHeader("X-Http2-StreamID"));
+ var data = read_stream(stream, cnt);
+ this.buffer = this.buffer.concat(data);
+};
+
+Http2MultiplexListener.prototype.onStopRequest = function (request, status) {
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection);
+ Assert.ok(this.buffer == multiplexContent);
+
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ // This is what does most of the hard work for us
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ var streamID = this.streamID;
+ this.finish({ httpProxyConnectResponseCode, streamID });
+};
+
+// Does the appropriate checks for header gatewaying
+var Http2HeaderListener = function (name, callback) {
+ this.name = name;
+ this.callback = callback;
+};
+
+Http2HeaderListener.prototype = new Http2CheckListener();
+Http2HeaderListener.prototype.value = "";
+
+Http2HeaderListener.prototype.onDataAvailable = function (
+ request,
+ stream,
+ off,
+ cnt
+) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ var hvalue = request.getResponseHeader(this.name);
+ Assert.notEqual(hvalue, "");
+ this.callback(hvalue);
+ read_stream(stream, cnt);
+};
+
+var Http2PushListener = function (shouldBePushed) {
+ this.shouldBePushed = shouldBePushed;
+};
+
+Http2PushListener.prototype = new Http2CheckListener();
+
+Http2PushListener.prototype.onDataAvailable = function (
+ request,
+ stream,
+ off,
+ cnt
+) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ if (
+ request.originalURI.spec ==
+ `https://localhost:${this.serverPort}/push.js` ||
+ request.originalURI.spec ==
+ `https://localhost:${this.serverPort}/push2.js` ||
+ request.originalURI.spec == `https://localhost:${this.serverPort}/push5.js`
+ ) {
+ Assert.equal(
+ request.getResponseHeader("pushed"),
+ this.shouldBePushed ? "yes" : "no"
+ );
+ }
+ read_stream(stream, cnt);
+};
+
+const pushHdrTxt =
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+const pullHdrTxt = pushHdrTxt.split("").reverse().join("");
+
+function checkContinuedHeaders(getHeader, headerPrefix, headerText) {
+ for (var i = 0; i < 265; i++) {
+ Assert.equal(getHeader(headerPrefix + 1), headerText);
+ }
+}
+
+var Http2ContinuedHeaderListener = function () {};
+
+Http2ContinuedHeaderListener.prototype = new Http2CheckListener();
+
+Http2ContinuedHeaderListener.prototype.onStopsLeft = 2;
+
+Http2ContinuedHeaderListener.prototype.QueryInterface = ChromeUtils.generateQI([
+ "nsIHttpPushListener",
+ "nsIStreamListener",
+]);
+
+Http2ContinuedHeaderListener.prototype.getInterface = function (aIID) {
+ return this.QueryInterface(aIID);
+};
+
+Http2ContinuedHeaderListener.prototype.onDataAvailable = function (
+ request,
+ stream,
+ off,
+ cnt
+) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ if (
+ request.originalURI.spec ==
+ `https://localhost:${this.serverPort}/continuedheaders`
+ ) {
+ // This is the original request, so the only one where we'll have continued response headers
+ checkContinuedHeaders(
+ request.getResponseHeader,
+ "X-Pull-Test-Header-",
+ pullHdrTxt
+ );
+ }
+ read_stream(stream, cnt);
+};
+
+Http2ContinuedHeaderListener.prototype.onStopRequest = function (
+ request,
+ status
+) {
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(Components.isSuccessCode(status));
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection);
+
+ --this.onStopsLeft;
+ if (this.onStopsLeft === 0) {
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+ }
+};
+
+Http2ContinuedHeaderListener.prototype.onPush = function (
+ associatedChannel,
+ pushChannel
+) {
+ Assert.equal(
+ associatedChannel.originalURI.spec,
+ "https://localhost:" + this.serverPort + "/continuedheaders"
+ );
+ Assert.equal(pushChannel.getRequestHeader("x-pushed-request"), "true");
+ checkContinuedHeaders(
+ pushChannel.getRequestHeader,
+ "X-Push-Test-Header-",
+ pushHdrTxt
+ );
+
+ pushChannel.asyncOpen(this);
+};
+
+// Does the appropriate checks for a large GET response
+var Http2BigListener = function () {};
+
+Http2BigListener.prototype = new Http2CheckListener();
+Http2BigListener.prototype.buffer = "";
+
+Http2BigListener.prototype.onDataAvailable = function (
+ request,
+ stream,
+ off,
+ cnt
+) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(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
+ Assert.equal(bigListenerMD5, request.getResponseHeader("X-Expected-MD5"));
+};
+
+Http2BigListener.prototype.onStopRequest = function (request, status) {
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection);
+
+ // Don't want to flood output, so don't use do_check_eq
+ Assert.ok(this.buffer == bigListenerData);
+
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+};
+
+var Http2HugeSuspendedListener = function () {};
+
+Http2HugeSuspendedListener.prototype = new Http2CheckListener();
+Http2HugeSuspendedListener.prototype.count = 0;
+
+Http2HugeSuspendedListener.prototype.onDataAvailable = function (
+ request,
+ stream,
+ off,
+ cnt
+) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ this.count += cnt;
+ read_stream(stream, cnt);
+};
+
+Http2HugeSuspendedListener.prototype.onStopRequest = function (
+ request,
+ status
+) {
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection);
+ Assert.equal(this.count, 1024 * 1024 * 1); // 1mb of data expected
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+};
+
+// Does the appropriate checks for POSTs
+var Http2PostListener = function (expected_md5) {
+ this.expected_md5 = expected_md5;
+};
+
+Http2PostListener.prototype = new Http2CheckListener();
+Http2PostListener.prototype.expected_md5 = "";
+
+Http2PostListener.prototype.onDataAvailable = function (
+ request,
+ stream,
+ off,
+ cnt
+) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ read_stream(stream, cnt);
+ Assert.equal(
+ this.expected_md5,
+ request.getResponseHeader("X-Calculated-MD5")
+ );
+};
+
+var ResumeStalledChannelListener = function () {};
+
+ResumeStalledChannelListener.prototype = {
+ onStartRequestFired: false,
+ onDataAvailableFired: false,
+ isHttp2Connection: false,
+ shouldBeHttp2: true,
+ resumable: null,
+
+ onStartRequest: function testOnStartRequest(request) {
+ this.onStartRequestFired = true;
+ if (!Components.isSuccessCode(request.status)) {
+ do_throw("Channel should have a success code! (" + request.status + ")");
+ }
+
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+ Assert.equal(request.responseStatus, 200);
+ Assert.equal(request.requestSucceeded, true);
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(Components.isSuccessCode(status));
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection == this.shouldBeHttp2);
+ this.resumable.resume();
+ },
+};
+
+// test a large download that creates stream flow control and
+// confirm we can do another independent stream while the download
+// stream is stuck
+async function test_http2_blocking_download(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/bigdownload`);
+ var internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internalChannel.initialRwin = 500000; // make the stream.suspend push back in h2
+ var p = new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ listener.expected = 3 * 1024 * 1024;
+ chan.asyncOpen(listener);
+ chan.suspend();
+ });
+ // wait 5 seconds so that stream flow control kicks in and then see if we
+ // can do a basic transaction (i.e. session not blocked). afterwards resume
+ // channel
+ do_timeout(5000, function () {
+ var simpleChannel = makeHTTPChannel(`https://localhost:${serverPort}/`);
+ var sl = new ResumeStalledChannelListener();
+ sl.resumable = chan;
+ simpleChannel.asyncOpen(sl);
+ });
+ return p;
+}
+
+// Make sure we make a HTTP2 connection and both us and the server mark it as such
+async function test_http2_basic(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/`);
+ var p = new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+ return p;
+}
+
+async function test_http2_basic_unblocked_dep(serverPort) {
+ var chan = makeHTTPChannel(
+ `https://localhost:${serverPort}/basic_unblocked_dep`
+ );
+ var cos = chan.QueryInterface(Ci.nsIClassOfService);
+ cos.addClassFlags(Ci.nsIClassOfService.Unblocked);
+ return new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+}
+
+// make sure we don't use h2 when disallowed
+async function test_http2_nospdy(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/`);
+ return new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ var internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internalChannel.allowSpdy = false;
+ listener.shouldBeHttp2 = false;
+ chan.asyncOpen(listener);
+ });
+}
+
+// Support for making sure XHR works over SPDY
+function checkXhr(xhr, finish) {
+ if (xhr.readyState != 4) {
+ return;
+ }
+
+ Assert.equal(xhr.status, 200);
+ Assert.equal(checkIsHttp2(xhr), true);
+ finish();
+}
+
+// Fires off an XHR request over h2
+async function test_http2_xhr(serverPort) {
+ return new Promise(resolve => {
+ var req = new XMLHttpRequest();
+ req.open("GET", `https://localhost:${serverPort}/`, true);
+ req.addEventListener("readystatechange", function (evt) {
+ checkXhr(req, resolve);
+ });
+ req.send(null);
+ });
+}
+
+var Http2ConcurrentListener = function () {};
+
+Http2ConcurrentListener.prototype = new Http2CheckListener();
+Http2ConcurrentListener.prototype.count = 0;
+Http2ConcurrentListener.prototype.target = 0;
+Http2ConcurrentListener.prototype.reset = 0;
+Http2ConcurrentListener.prototype.recvdHdr = 0;
+
+Http2ConcurrentListener.prototype.onStopRequest = function (request, status) {
+ this.count++;
+ Assert.ok(this.isHttp2Connection);
+ if (this.recvdHdr > 0) {
+ Assert.equal(request.getResponseHeader("X-Recvd"), this.recvdHdr);
+ }
+
+ if (this.count == this.target) {
+ if (this.reset > 0) {
+ Services.prefs.setIntPref(
+ "network.http.http2.default-concurrent",
+ this.reset
+ );
+ }
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+ }
+};
+
+async function test_http2_concurrent(concurrent_channels, serverPort) {
+ var p = new Promise(resolve => {
+ var concurrent_listener = new Http2ConcurrentListener();
+ concurrent_listener.finish = resolve;
+ concurrent_listener.target = 201;
+ concurrent_listener.reset = Services.prefs.getIntPref(
+ "network.http.http2.default-concurrent"
+ );
+ Services.prefs.setIntPref("network.http.http2.default-concurrent", 100);
+
+ for (var i = 0; i < concurrent_listener.target; i++) {
+ concurrent_channels[i] = makeHTTPChannel(
+ `https://localhost:${serverPort}/750ms`
+ );
+ concurrent_channels[i].loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ concurrent_channels[i].asyncOpen(concurrent_listener);
+ }
+ });
+ return p;
+}
+
+async function test_http2_concurrent_post(concurrent_channels, serverPort) {
+ return new Promise(resolve => {
+ var concurrent_listener = new Http2ConcurrentListener();
+ concurrent_listener.finish = resolve;
+ concurrent_listener.target = 8;
+ concurrent_listener.recvdHdr = posts[2].length;
+ concurrent_listener.reset = Services.prefs.getIntPref(
+ "network.http.http2.default-concurrent"
+ );
+ Services.prefs.setIntPref("network.http.http2.default-concurrent", 3);
+
+ for (var i = 0; i < concurrent_listener.target; i++) {
+ concurrent_channels[i] = makeHTTPChannel(
+ `https://localhost:${serverPort}/750msPost`
+ );
+ concurrent_channels[i].loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.data = posts[2];
+ var uchan = concurrent_channels[i].QueryInterface(Ci.nsIUploadChannel);
+ uchan.setUploadStream(stream, "text/plain", stream.available());
+ concurrent_channels[i].requestMethod = "POST";
+ concurrent_channels[i].asyncOpen(concurrent_listener);
+ }
+ });
+}
+
+// Test to make sure we get multiplexing right
+async function test_http2_multiplex(serverPort) {
+ let chan1 = makeHTTPChannel(`https://localhost:${serverPort}/multiplex1`);
+ let chan2 = makeHTTPChannel(`https://localhost:${serverPort}/multiplex2`);
+ let listener1 = new Http2MultiplexListener();
+ let listener2 = new Http2MultiplexListener();
+
+ let promises = [];
+ let p1 = new Promise(resolve => {
+ listener1.finish = resolve;
+ });
+ promises.push(p1);
+ let p2 = new Promise(resolve => {
+ listener2.finish = resolve;
+ });
+ promises.push(p2);
+
+ chan1.asyncOpen(listener1);
+ chan2.asyncOpen(listener2);
+ return Promise.all(promises);
+}
+
+// Test to make sure we gateway non-standard headers properly
+async function test_http2_header(serverPort) {
+ let chan = makeHTTPChannel(`https://localhost:${serverPort}/header`);
+ let hvalue = "Headers are fun";
+ chan.setRequestHeader("X-Test-Header", hvalue, false);
+ return new Promise(resolve => {
+ let listener = new Http2HeaderListener("X-Received-Test-Header", function (
+ received_hvalue
+ ) {
+ Assert.equal(received_hvalue, hvalue);
+ });
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+}
+
+// Test to make sure headers with invalid characters in the name are rejected
+async function test_http2_invalid_response_header(serverPort, invalid_kind) {
+ return new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ listener.shouldSucceed = false;
+ var chan = makeHTTPChannel(
+ `https://localhost:${serverPort}/invalid_response_header/${invalid_kind}`
+ );
+ chan.asyncOpen(listener);
+ });
+}
+
+// Test to make sure cookies are split into separate fields before compression
+async function test_http2_cookie_crumbling(serverPort) {
+ var chan = makeHTTPChannel(
+ `https://localhost:${serverPort}/cookie_crumbling`
+ );
+ var cookiesSent = ["a=b", "c=d01234567890123456789", "e=f"].sort();
+ chan.setRequestHeader("Cookie", cookiesSent.join("; "), false);
+ return new Promise(resolve => {
+ var listener = new Http2HeaderListener("X-Received-Header-Pairs", function (
+ pairsReceived
+ ) {
+ var cookiesReceived = JSON.parse(pairsReceived)
+ .filter(function (pair) {
+ return pair[0] == "cookie";
+ })
+ .map(function (pair) {
+ return pair[1];
+ })
+ .sort();
+ Assert.equal(cookiesReceived.length, cookiesSent.length);
+ cookiesReceived.forEach(function (cookieReceived, index) {
+ Assert.equal(cookiesSent[index], cookieReceived);
+ });
+ });
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push1(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push`);
+ chan.loadGroup = loadGroup;
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push2(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push.js`);
+ chan.loadGroup = loadGroup;
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push3(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push2`);
+ chan.loadGroup = loadGroup;
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push4(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push2.js`);
+ chan.loadGroup = loadGroup;
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push5(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push5`);
+ chan.loadGroup = loadGroup;
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push6(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push5.js`);
+ chan.loadGroup = loadGroup;
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+// this is a basic test where the server sends a simple document with 2 header
+// blocks. bug 1027364
+async function test_http2_doubleheader(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/doubleheader`);
+ return new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+}
+
+// Make sure we handle GETs that cover more than 2 frames properly
+async function test_http2_big(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/big`);
+ return new Promise(resolve => {
+ var listener = new Http2BigListener();
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_huge_suspended(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/huge`);
+ return new Promise(resolve => {
+ var listener = new Http2HugeSuspendedListener();
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ chan.suspend();
+ do_timeout(500, chan.resume);
+ });
+}
+
+// Support for doing a POST
+function do_post(content, chan, listener, method) {
+ 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 = method;
+
+ chan.asyncOpen(listener);
+}
+
+// Make sure we can do a simple POST
+async function test_http2_post(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/post`);
+ var p = new Promise(resolve => {
+ var listener = new Http2PostListener(md5s[0]);
+ listener.finish = resolve;
+ do_post(posts[0], chan, listener, "POST");
+ });
+ return p;
+}
+
+async function test_http2_empty_post(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/post`);
+ var p = new Promise(resolve => {
+ var listener = new Http2PostListener("0");
+ listener.finish = resolve;
+ do_post("", chan, listener, "POST");
+ });
+ return p;
+}
+
+// Make sure we can do a simple PATCH
+async function test_http2_patch(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/patch`);
+ return new Promise(resolve => {
+ var listener = new Http2PostListener(md5s[0]);
+ listener.finish = resolve;
+ do_post(posts[0], chan, listener, "PATCH");
+ });
+}
+
+// Make sure we can do a POST that covers more than 2 frames
+async function test_http2_post_big(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/post`);
+ return new Promise(resolve => {
+ var listener = new Http2PostListener(md5s[1]);
+ listener.finish = resolve;
+ do_post(posts[1], chan, listener, "POST");
+ });
+}
+
+// When a http proxy is used alt-svc is disable. Therefore if withProxy is true,
+// try numberOfTries times to connect and make sure that alt-svc is not use and we never
+// connect to the HTTP/2 server.
+var altsvcClientListener = function (
+ finish,
+ httpserv,
+ httpserv2,
+ withProxy,
+ numberOfTries
+) {
+ this.finish = finish;
+ this.httpserv = httpserv;
+ this.httpserv2 = httpserv2;
+ this.withProxy = withProxy;
+ this.numberOfTries = numberOfTries;
+};
+
+altsvcClientListener.prototype = {
+ onStartRequest: function test_onStartR(request) {
+ Assert.equal(request.status, Cr.NS_OK);
+ },
+
+ onDataAvailable: function test_ODA(request, stream, offset, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ var isHttp2Connection = checkIsHttp2(
+ request.QueryInterface(Ci.nsIHttpChannel)
+ );
+ if (!isHttp2Connection) {
+ dump("/altsvc1 not over h2 yet - retry\n");
+ if (this.withProxy && this.numberOfTries == 0) {
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+ return;
+ }
+ let chan = makeHTTPChannel(
+ `http://foo.example.com:${this.httpserv}/altsvc1`,
+ this.withProxy
+ ).QueryInterface(Ci.nsIHttpChannel);
+ // we use this header to tell the server to issue a altsvc frame for the
+ // speficied origin we will use in the next part of the test
+ chan.setRequestHeader(
+ "x-redirect-origin",
+ `http://foo.example.com:${this.httpserv2}`,
+ false
+ );
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.asyncOpen(
+ new altsvcClientListener(
+ this.finish,
+ this.httpserv,
+ this.httpserv2,
+ this.withProxy,
+ this.numberOfTries - 1
+ )
+ );
+ } else {
+ Assert.ok(isHttp2Connection);
+ let chan = makeHTTPChannel(
+ `http://foo.example.com:${this.httpserv2}/altsvc2`
+ ).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.asyncOpen(
+ new altsvcClientListener2(this.finish, this.httpserv, this.httpserv2)
+ );
+ }
+ },
+};
+
+var altsvcClientListener2 = function (finish, httpserv, httpserv2) {
+ this.finish = finish;
+ this.httpserv = httpserv;
+ this.httpserv2 = httpserv2;
+};
+
+altsvcClientListener2.prototype = {
+ onStartRequest: function test_onStartR(request) {
+ Assert.equal(request.status, Cr.NS_OK);
+ },
+
+ onDataAvailable: function test_ODA(request, stream, offset, cnt) {
+ read_stream(stream, cnt);
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ var isHttp2Connection = checkIsHttp2(
+ request.QueryInterface(Ci.nsIHttpChannel)
+ );
+ if (!isHttp2Connection) {
+ dump("/altsvc2 not over h2 yet - retry\n");
+ var chan = makeHTTPChannel(
+ `http://foo.example.com:${this.httpserv2}/altsvc2`
+ ).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.asyncOpen(
+ new altsvcClientListener2(this.finish, this.httpserv, this.httpserv2)
+ );
+ } else {
+ Assert.ok(isHttp2Connection);
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+ }
+ },
+};
+
+async function test_http2_altsvc(httpserv, httpserv2, withProxy) {
+ var chan = makeHTTPChannel(
+ `http://foo.example.com:${httpserv}/altsvc1`,
+ withProxy
+ ).QueryInterface(Ci.nsIHttpChannel);
+ return new Promise(resolve => {
+ var numberOfTries = 0;
+ if (withProxy) {
+ numberOfTries = 20;
+ }
+ chan.asyncOpen(
+ new altsvcClientListener(
+ resolve,
+ httpserv,
+ httpserv2,
+ withProxy,
+ numberOfTries
+ )
+ );
+ });
+}
+
+var Http2PushApiListener = function (finish, serverPort) {
+ this.finish = finish;
+ this.serverPort = serverPort;
+};
+
+Http2PushApiListener.prototype = {
+ checksPending: 9, // 4 onDataAvailable and 5 onStop
+
+ getInterface(aIID) {
+ return this.QueryInterface(aIID);
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIHttpPushListener",
+ "nsIStreamListener",
+ ]),
+
+ // nsIHttpPushListener
+ onPush: function onPush(associatedChannel, pushChannel) {
+ Assert.equal(
+ associatedChannel.originalURI.spec,
+ "https://localhost:" + this.serverPort + "/pushapi1"
+ );
+ Assert.equal(pushChannel.getRequestHeader("x-pushed-request"), "true");
+
+ pushChannel.asyncOpen(this);
+ if (
+ pushChannel.originalURI.spec ==
+ "https://localhost:" + this.serverPort + "/pushapi1/2"
+ ) {
+ pushChannel.cancel(Cr.NS_ERROR_ABORT);
+ } else if (
+ pushChannel.originalURI.spec ==
+ "https://localhost:" + this.serverPort + "/pushapi1/3"
+ ) {
+ Assert.ok(pushChannel.getRequestHeader("Accept-Encoding").includes("br"));
+ }
+ },
+
+ // normal Channel listeners
+ onStartRequest: function pushAPIOnStart(request) {},
+
+ onDataAvailable: function pushAPIOnDataAvailable(
+ request,
+ stream,
+ offset,
+ cnt
+ ) {
+ Assert.notEqual(
+ request.originalURI.spec,
+ `https://localhost:${this.serverPort}/pushapi1/2`
+ );
+
+ var data = read_stream(stream, cnt);
+
+ if (
+ request.originalURI.spec ==
+ `https://localhost:${this.serverPort}/pushapi1`
+ ) {
+ Assert.equal(data[0], "0");
+ --this.checksPending;
+ } else if (
+ request.originalURI.spec ==
+ `https://localhost:${this.serverPort}/pushapi1/1`
+ ) {
+ Assert.equal(data[0], "1");
+ --this.checksPending; // twice
+ } else if (
+ request.originalURI.spec ==
+ `https://localhost:${this.serverPort}/pushapi1/3`
+ ) {
+ Assert.equal(data[0], "3");
+ --this.checksPending;
+ } else {
+ Assert.equal(true, false);
+ }
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ if (
+ request.originalURI.spec ==
+ `https://localhost:${this.serverPort}/pushapi1/2`
+ ) {
+ Assert.equal(request.status, Cr.NS_ERROR_ABORT);
+ } else {
+ Assert.equal(request.status, Cr.NS_OK);
+ }
+
+ --this.checksPending; // 5 times - one for each push plus the pull
+ if (!this.checksPending) {
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+ }
+ },
+};
+
+// pushAPI testcase 1 expects
+// 1 to pull /pushapi1 with 0
+// 2 to see /pushapi1/1 with 1
+// 3 to see /pushapi1/1 with 1 (again)
+// 4 to see /pushapi1/2 that it will cancel
+// 5 to see /pushapi1/3 with 3 with brotli
+
+async function test_http2_pushapi_1(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/pushapi1`);
+ chan.loadGroup = loadGroup;
+ return new Promise(resolve => {
+ var listener = new Http2PushApiListener(resolve, serverPort);
+ chan.notificationCallbacks = listener;
+ chan.asyncOpen(listener);
+ });
+}
+
+var WrongSuiteListener = function () {};
+
+WrongSuiteListener.prototype = new Http2CheckListener();
+WrongSuiteListener.prototype.shouldBeHttp2 = false;
+WrongSuiteListener.prototype.onStopRequest = function (request, status) {
+ Services.prefs.setBoolPref(
+ "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256",
+ true
+ );
+ Services.prefs.clearUserPref("security.tls.version.max");
+ Http2CheckListener.prototype.onStopRequest.call(this, request, status);
+};
+
+// test that we use h1 without the mandatory cipher suite available when
+// offering at most tls1.2
+async function test_http2_wrongsuite_tls12(serverPort) {
+ Services.prefs.setBoolPref(
+ "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256",
+ false
+ );
+ Services.prefs.setIntPref("security.tls.version.max", 3);
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/wrongsuite`);
+ chan.loadFlags =
+ Ci.nsIRequest.LOAD_FRESH_CONNECTION |
+ Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return new Promise(resolve => {
+ var listener = new WrongSuiteListener();
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+}
+
+// test that we use h2 when offering tls1.3 or higher regardless of if the
+// mandatory cipher suite is available
+async function test_http2_wrongsuite_tls13(serverPort) {
+ Services.prefs.setBoolPref(
+ "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256",
+ false
+ );
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/wrongsuite`);
+ chan.loadFlags =
+ Ci.nsIRequest.LOAD_FRESH_CONNECTION |
+ Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return new Promise(resolve => {
+ var listener = new WrongSuiteListener();
+ listener.finish = resolve;
+ listener.shouldBeHttp2 = true;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_h11required_stream(serverPort) {
+ var chan = makeHTTPChannel(
+ `https://localhost:${serverPort}/h11required_stream`
+ );
+ return new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ listener.shouldBeHttp2 = false;
+ chan.asyncOpen(listener);
+ });
+}
+
+function H11RequiredSessionListener() {}
+
+H11RequiredSessionListener.prototype = new Http2CheckListener();
+
+H11RequiredSessionListener.prototype.onStopRequest = function (
+ request,
+ status
+) {
+ var streamReused = request.getResponseHeader("X-H11Required-Stream-Ok");
+ Assert.equal(streamReused, "yes");
+
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection == this.shouldBeHttp2);
+
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+};
+
+async function test_http2_h11required_session(serverPort) {
+ var chan = makeHTTPChannel(
+ `https://localhost:${serverPort}/h11required_session`
+ );
+ return new Promise(resolve => {
+ var listener = new H11RequiredSessionListener();
+ listener.finish = resolve;
+ listener.shouldBeHttp2 = false;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_retry_rst(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/rstonce`);
+ return new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_continuations(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(
+ `https://localhost:${serverPort}/continuedheaders`
+ );
+ chan.loadGroup = loadGroup;
+ return new Promise(resolve => {
+ var listener = new Http2ContinuedHeaderListener();
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.notificationCallbacks = listener;
+ chan.asyncOpen(listener);
+ });
+}
+
+function Http2IllegalHpackValidationListener() {}
+
+Http2IllegalHpackValidationListener.prototype = new Http2CheckListener();
+Http2IllegalHpackValidationListener.prototype.shouldGoAway = false;
+
+Http2IllegalHpackValidationListener.prototype.onStopRequest = function (
+ request,
+ status
+) {
+ var wentAway = request.getResponseHeader("X-Did-Goaway") === "yes";
+ Assert.equal(wentAway, this.shouldGoAway);
+
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection == this.shouldBeHttp2);
+
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+};
+
+function Http2IllegalHpackListener() {}
+Http2IllegalHpackListener.prototype = new Http2CheckListener();
+Http2IllegalHpackListener.prototype.shouldGoAway = false;
+
+Http2IllegalHpackListener.prototype.onStopRequest = function (request, status) {
+ var chan = makeHTTPChannel(
+ `https://localhost:${this.serverPort}/illegalhpack_validate`
+ );
+ var listener = new Http2IllegalHpackValidationListener();
+ listener.finish = this.finish;
+ listener.shouldGoAway = this.shouldGoAway;
+ chan.asyncOpen(listener);
+};
+
+async function test_http2_illegalhpacksoft(serverPort) {
+ var chan = makeHTTPChannel(
+ `https://localhost:${serverPort}/illegalhpacksoft`
+ );
+ return new Promise(resolve => {
+ var listener = new Http2IllegalHpackListener();
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ listener.shouldGoAway = false;
+ listener.shouldSucceed = false;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_illegalhpackhard(serverPort) {
+ var chan = makeHTTPChannel(
+ `https://localhost:${serverPort}/illegalhpackhard`
+ );
+ return new Promise(resolve => {
+ var listener = new Http2IllegalHpackListener();
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ listener.shouldGoAway = true;
+ listener.shouldSucceed = false;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_folded_header(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/foldedheader`);
+ chan.loadGroup = loadGroup;
+ return new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ listener.shouldSucceed = false;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_empty_data(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/emptydata`);
+ return new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push_firstparty1(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push`);
+ chan.loadGroup = loadGroup;
+ chan.loadInfo.originAttributes = { firstPartyDomain: "foo.com" };
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push_firstparty2(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push.js`);
+ chan.loadGroup = loadGroup;
+ chan.loadInfo.originAttributes = { firstPartyDomain: "bar.com" };
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(false);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push_firstparty3(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push.js`);
+ chan.loadGroup = loadGroup;
+ chan.loadInfo.originAttributes = { firstPartyDomain: "foo.com" };
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push_userContext1(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push`);
+ chan.loadGroup = loadGroup;
+ chan.loadInfo.originAttributes = { userContextId: 1 };
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push_userContext2(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push.js`);
+ chan.loadGroup = loadGroup;
+ chan.loadInfo.originAttributes = { userContextId: 2 };
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(false);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_push_userContext3(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/push.js`);
+ chan.loadGroup = loadGroup;
+ chan.loadInfo.originAttributes = { userContextId: 1 };
+ return new Promise(resolve => {
+ var listener = new Http2PushListener(true);
+ listener.finish = resolve;
+ listener.serverPort = serverPort;
+ chan.asyncOpen(listener);
+ });
+}
+
+async function test_http2_status_phrase(serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/statusphrase`);
+ return new Promise(resolve => {
+ var listener = new Http2CheckListener();
+ listener.finish = resolve;
+ listener.shouldSucceed = false;
+ chan.asyncOpen(listener);
+ });
+}
+
+var PulledDiskCacheListener = function () {};
+PulledDiskCacheListener.prototype = new Http2CheckListener();
+PulledDiskCacheListener.prototype.EXPECTED_DATA = "this was pulled via h2";
+PulledDiskCacheListener.prototype.readData = "";
+PulledDiskCacheListener.prototype.onDataAvailable =
+ function testOnDataAvailable(request, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.isHttp2Connection = checkIsHttp2(request);
+ this.accum += cnt;
+ this.readData += read_stream(stream, cnt);
+ };
+PulledDiskCacheListener.prototype.onStopRequest = function testOnStopRequest(
+ request,
+ status
+) {
+ Assert.equal(this.EXPECTED_DATA, this.readData);
+ Http2CheckListener.prorotype.onStopRequest.call(this, request, status);
+};
+
+const DISK_CACHE_DATA = "this is from disk cache";
+
+var FromDiskCacheListener = function (finish, loadGroup, serverPort) {
+ this.finish = finish;
+ this.loadGroup = loadGroup;
+ this.serverPort = serverPort;
+};
+FromDiskCacheListener.prototype = {
+ onStartRequestFired: false,
+ onDataAvailableFired: false,
+ readData: "",
+
+ onStartRequest: function testOnStartRequest(request) {
+ this.onStartRequestFired = true;
+ if (!Components.isSuccessCode(request.status)) {
+ do_throw("Channel should have a success code! (" + request.status + ")");
+ }
+
+ Assert.ok(request instanceof Ci.nsIHttpChannel);
+ Assert.ok(request.requestSucceeded);
+ Assert.equal(request.responseStatus, 200);
+ },
+
+ onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+ this.onDataAvailableFired = true;
+ this.readData += read_stream(stream, cnt);
+ },
+
+ onStopRequest: function testOnStopRequest(request, status) {
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(Components.isSuccessCode(status));
+ Assert.ok(this.onDataAvailableFired);
+ Assert.equal(this.readData, DISK_CACHE_DATA);
+
+ evict_cache_entries("disk");
+ syncWithCacheIOThread(() => {
+ // Now that we know the entry is out of the disk cache, check to make sure
+ // we don't have this hiding in the push cache somewhere - if we do, it
+ // didn't get cancelled, and we have a bug.
+ var chan = makeHTTPChannel(
+ `https://localhost:${this.serverPort}/diskcache`
+ );
+ var listener = new PulledDiskCacheListener();
+ listener.finish = this.finish;
+ chan.loadGroup = this.loadGroup;
+ chan.asyncOpen(listener);
+ });
+ },
+};
+
+var Http2DiskCachePushListener = function () {};
+Http2DiskCachePushListener.prototype = new Http2CheckListener();
+
+Http2DiskCachePushListener.onStopRequest = function (request, status) {
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(Components.isSuccessCode(status));
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection == this.shouldBeHttp2);
+
+ // Now we need to open a channel to ensure we get data from the disk cache
+ // for the pushed item, instead of from the push cache.
+ var chan = makeHTTPChannel(`https://localhost:${this.serverPort}/diskcache`);
+ var listener = new FromDiskCacheListener(
+ this.finish,
+ this.loadGroup,
+ this.serverPort
+ );
+ chan.loadGroup = this.loadGroup;
+ chan.asyncOpen(listener);
+};
+
+function continue_test_http2_disk_cache_push(
+ status,
+ entry,
+ finish,
+ loadGroup,
+ serverPort
+) {
+ // TODO - store stuff in cache entry, then open an h2 channel that will push
+ // this, once that completes, open a channel for the cache entry we made and
+ // ensure it came from disk cache, not the push cache.
+ var outputStream = entry.openOutputStream(0, -1);
+ outputStream.write(DISK_CACHE_DATA, DISK_CACHE_DATA.length);
+
+ // Now we open our URL that will push data for the URL above
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/pushindisk`);
+ var listener = new Http2DiskCachePushListener();
+ listener.finish = finish;
+ listener.loadGroup = loadGroup;
+ listener.serverPort = serverPort;
+ chan.loadGroup = loadGroup;
+ chan.asyncOpen(listener);
+}
+
+async function test_http2_disk_cache_push(loadGroup, serverPort) {
+ return new Promise(resolve => {
+ asyncOpenCacheEntry(
+ `https://localhost:${serverPort}/diskcache`,
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ function (status, entry) {
+ continue_test_http2_disk_cache_push(
+ status,
+ entry,
+ resolve,
+ loadGroup,
+ serverPort
+ );
+ },
+ false
+ );
+ });
+}
+
+var Http2DoublepushListener = function () {};
+Http2DoublepushListener.prototype = new Http2CheckListener();
+Http2DoublepushListener.prototype.onStopRequest = function (request, status) {
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(Components.isSuccessCode(status));
+ Assert.ok(this.onDataAvailableFired);
+ Assert.ok(this.isHttp2Connection == this.shouldBeHttp2);
+
+ var chan = makeHTTPChannel(
+ `https://localhost:${this.serverPort}/doublypushed`
+ );
+ var listener = new Http2DoublypushedListener();
+ listener.finish = this.finish;
+ chan.loadGroup = this.loadGroup;
+ chan.asyncOpen(listener);
+};
+
+var Http2DoublypushedListener = function () {};
+Http2DoublypushedListener.prototype = new Http2CheckListener();
+Http2DoublypushedListener.prototype.readData = "";
+Http2DoublypushedListener.prototype.onDataAvailable = function (
+ request,
+ stream,
+ off,
+ cnt
+) {
+ this.onDataAvailableFired = true;
+ this.accum += cnt;
+ this.readData += read_stream(stream, cnt);
+};
+Http2DoublypushedListener.prototype.onStopRequest = function (request, status) {
+ Assert.ok(this.onStartRequestFired);
+ Assert.ok(Components.isSuccessCode(status));
+ Assert.ok(this.onDataAvailableFired);
+ Assert.equal(this.readData, "pushed");
+
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ let httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
+ this.finish({ httpProxyConnectResponseCode });
+};
+
+function test_http2_doublepush(loadGroup, serverPort) {
+ var chan = makeHTTPChannel(`https://localhost:${serverPort}/doublepush`);
+ return new Promise(resolve => {
+ var listener = new Http2DoublepushListener();
+ listener.finish = resolve;
+ listener.loadGroup = loadGroup;
+ listener.serverPort = serverPort;
+ chan.loadGroup = loadGroup;
+ chan.asyncOpen(listener);
+ });
+}