/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
"use strict";
const { HttpServer } = ChromeUtils.importESModule(
"resource://testing-common/httpd.sys.mjs"
);
var httpserver =
new HttpServer();
httpserver.start(-
1);
const PORT = httpserver.identity.primaryPort;
function make_channel(url) {
return NetUtil.newChannel({
uri: url,
loadUsingSystemPrincipal:
true,
}).QueryInterface(Ci.nsIHttpChannel);
}
let gResponseBody =
"blahblah";
let g200Counter =
0;
let g304Counter =
0;
function test_handler(metadata, response) {
response.setHeader(
"Content-Type",
"text/plain");
response.setHeader(
"Cache-Control",
"no-cache");
response.setHeader(
"ETag",
"test-etag1");
let etag;
try {
etag = metadata.getHeader(
"If-None-Match");
}
catch (ex) {
etag =
"";
}
if (etag ==
"test-etag1") {
response.setStatusLine(metadata.httpVersion,
304,
"Not Modified");
g304Counter++;
}
else {
response.setStatusLine(metadata.httpVersion,
200,
"OK");
response.bodyOutputStream.write(gResponseBody, gResponseBody.length);
g200Counter++;
}
}
function cached_handler(metadata, response) {
response.setHeader(
"Content-Type",
"text/plain");
response.setHeader(
"Cache-Control",
"max-age=3600");
response.setHeader(
"ETag",
"test-etag1");
response.setStatusLine(metadata.httpVersion,
200,
"OK");
response.bodyOutputStream.write(gResponseBody, gResponseBody.length);
g200Counter++;
}
let gResponseCounter =
0;
let gIsFromCache =
0;
function checkContent(request, buffer, context, isFromCache) {
Assert.equal(buffer, gResponseBody);
info(
"isRacing: " +
request.QueryInterface(Ci.nsICacheInfoChannel).isRacing() +
"\n"
);
gResponseCounter++;
if (isFromCache) {
gIsFromCache++;
}
executeSoon(() => {
testGenerator.next();
});
}
function run_test() {
do_get_profile();
// In this test, we manually use |TriggerNetwork| to prove we could send
// net and cache reqeust simultaneously. Therefore we should disable
// racing in the HttpChannel first.
Services.prefs.setBoolPref(
"network.http.rcwn.enabled",
false);
httpserver.registerPathHandler(
"/rcwn", test_handler);
httpserver.registerPathHandler(
"/rcwn_cached", cached_handler);
testGenerator.next();
do_test_pending();
}
let testGenerator = testSteps();
function* testSteps() {
/*
* In this test, we have a relatively low timeout of 200ms and an assertion that
* the timer works properly by checking that the time was greater than 200ms.
* With a timer precision of 100ms (for example) we will clamp downwards to 200
* and cause the assertion to fail. To resolve this, we hardcode a precision of
* 20ms.
*/
Services.prefs.setBoolPref(
"privacy.reduceTimerPrecision",
true);
Services.prefs.setIntPref(
"privacy.resistFingerprinting.reduceTimerPrecision.microseconds",
20000
);
registerCleanupFunction(
function () {
Services.prefs.clearUserPref(
"privacy.reduceTimerPrecision");
Services.prefs.clearUserPref(
"privacy.resistFingerprinting.reduceTimerPrecision.microseconds"
);
});
// Initial request. Stores the response in the cache.
let channel = make_channel(
"http://localhost:" + PORT + "/rcwn");
channel.asyncOpen(
new ChannelListener(checkContent,
null));
yield undefined;
equal(gResponseCounter,
1);
equal(g200Counter,
1,
"check number of 200 responses");
equal(g304Counter,
0,
"check number of 304 responses");
// Checks that response is returned from the cache, after a 304 response.
channel = make_channel(
"http://localhost:" + PORT + "/rcwn");
channel.asyncOpen(
new ChannelListener(checkContent,
null));
yield undefined;
equal(gResponseCounter,
2);
equal(g200Counter,
1,
"check number of 200 responses");
equal(g304Counter,
1,
"check number of 304 responses");
// Checks that delaying the response from the cache works.
channel = make_channel(
"http://localhost:" + PORT + "/rcwn");
channel
.QueryInterface(Ci.nsIRaceCacheWithNetwork)
.test_delayCacheEntryOpeningBy(
200);
let startTime = Date.now();
channel.asyncOpen(
new ChannelListener(checkContent,
null));
yield undefined;
greaterOrEqual(
Date.now() - startTime,
200,
"Check that timer works properly"
);
equal(gResponseCounter,
3);
equal(g200Counter,
1,
"check number of 200 responses");
equal(g304Counter,
2,
"check number of 304 responses");
// Checks that we can trigger the cache open immediately, even if the cache delay is set very high.
channel = make_channel(
"http://localhost:" + PORT + "/rcwn");
channel
.QueryInterface(Ci.nsIRaceCacheWithNetwork)
.test_delayCacheEntryOpeningBy(
100000);
channel.asyncOpen(
new ChannelListener(checkContent,
null));
do_timeout(
50,
function () {
channel
.QueryInterface(Ci.nsIRaceCacheWithNetwork)
.test_triggerDelayedOpenCacheEntry();
});
yield undefined;
equal(gResponseCounter,
4);
equal(g200Counter,
1,
"check number of 200 responses");
equal(g304Counter,
3,
"check number of 304 responses");
// Sets a high delay for the cache fetch, and triggers the network activity.
channel = make_channel(
"http://localhost:" + PORT + "/rcwn");
channel
.QueryInterface(Ci.nsIRaceCacheWithNetwork)
.test_delayCacheEntryOpeningBy(
100000);
channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(
50);
channel.asyncOpen(
new ChannelListener(checkContent,
null));
// Trigger network after 50 ms.
yield undefined;
equal(gResponseCounter,
5);
equal(g200Counter,
2,
"check number of 200 responses");
equal(g304Counter,
3,
"check number of 304 responses");
// Sets a high delay for the cache fetch, and triggers the network activity.
// While the network response is produced, we trigger the cache fetch.
// Because the network response was the first, a non-conditional request is sent.
channel = make_channel(
"http://localhost:" + PORT + "/rcwn");
channel
.QueryInterface(Ci.nsIRaceCacheWithNetwork)
.test_delayCacheEntryOpeningBy(
100000);
channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(
50);
channel.asyncOpen(
new ChannelListener(checkContent,
null));
yield undefined;
equal(gResponseCounter,
6);
equal(g200Counter,
3,
"check number of 200 responses");
equal(g304Counter,
3,
"check number of 304 responses");
// Triggers cache open before triggering network.
channel = make_channel(
"http://localhost:" + PORT + "/rcwn");
channel
.QueryInterface(Ci.nsIRaceCacheWithNetwork)
.test_delayCacheEntryOpeningBy(
100000);
channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(
5000);
channel.asyncOpen(
new ChannelListener(checkContent,
null));
channel
.QueryInterface(Ci.nsIRaceCacheWithNetwork)
.test_triggerDelayedOpenCacheEntry();
yield undefined;
equal(gResponseCounter,
7);
equal(
g200Counter,
3,
`check number of
200 responses |
200: ${g200Counter},
304: ${g304Counter}`
);
equal(
g304Counter,
4,
`check number of
304 responses |
200: ${g200Counter},
304: ${g304Counter}`
);
// Load the cached handler so we don't need to revalidate
channel = make_channel(
"http://localhost:" + PORT + "/rcwn_cached");
channel.asyncOpen(
new ChannelListener(checkContent,
null));
yield undefined;
equal(gResponseCounter,
8);
equal(g200Counter,
4,
"check number of 200 responses");
equal(g304Counter,
4,
"check number of 304 responses");
// Make sure response is loaded from the cache, not the network
channel = make_channel(
"http://localhost:" + PORT + "/rcwn_cached");
channel.asyncOpen(
new ChannelListener(checkContent,
null));
yield undefined;
equal(gResponseCounter,
9);
equal(g200Counter,
4,
"check number of 200 responses");
equal(g304Counter,
4,
"check number of 304 responses");
// Cache times out, so we trigger the network
gIsFromCache =
0;
channel = make_channel(
"http://localhost:" + PORT + "/rcwn_cached");
channel
.QueryInterface(Ci.nsIRaceCacheWithNetwork)
.test_delayCacheEntryOpeningBy(
100000);
// trigger network after 50 ms
channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(
50);
channel.asyncOpen(
new ChannelListener(checkContent,
null));
yield undefined;
equal(gResponseCounter,
10);
equal(gIsFromCache,
0,
"should be from the network");
equal(g200Counter,
5,
"check number of 200 responses");
equal(g304Counter,
4,
"check number of 304 responses");
// Cache callback comes back right after network is triggered.
channel = make_channel(
"http://localhost:" + PORT + "/rcwn_cached");
channel
.QueryInterface(Ci.nsIRaceCacheWithNetwork)
.test_delayCacheEntryOpeningBy(
55);
channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(
50);
channel.asyncOpen(
new ChannelListener(checkContent,
null));
yield undefined;
equal(gResponseCounter,
11);
info(
"IsFromCache: " + gIsFromCache +
"\n");
info(
"Number of 200 responses: " + g200Counter +
"\n");
equal(g304Counter,
4,
"check number of 304 responses");
// Set an increasingly high timeout to trigger opening the cache entry
// This way we ensure that some of the entries we will get from the network,
// and some we will get from the cache.
gIsFromCache =
0;
for (
var i =
0; i <
50; i++) {
channel = make_channel(
"http://localhost:" + PORT + "/rcwn_cached");
channel
.QueryInterface(Ci.nsIRaceCacheWithNetwork)
.test_delayCacheEntryOpeningBy(i *
100);
channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(
10);
channel.asyncOpen(
new ChannelListener(checkContent,
null));
// This may be racy. The delay was chosen because the distribution of net-cache
// results was around 25-25 on my machine.
yield undefined;
}
greater(gIsFromCache,
0,
"Some of the responses should be from the cache");
less(gIsFromCache,
50,
"Some of the responses should be from the net");
httpserver.stop(do_test_finished);
}