Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

/* Any copyright is dedicated to the Public Domain.
*/
"use strict";
const { HttpServer } = ChromeUtils.importESModule(
);
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);
}