Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
- Manifest: netwerk/test/unit/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
"use strict";
// A load that may not be persisted opens the memory-only cache storage. When an
// entry for that URL already exists on disk the cache replaces it with a
// memory-only one, but an entry picked up through the No-Vary-Search secondary
// index is stored under a different URL: replacing it would doom another URL's
// representation, so such a candidate must not be used at all.
const { HttpServer } = ChromeUtils.importESModule(
);
let gHttpServer;
let gPort;
let gRequests = [];
const PATH = "/nvs-mem";
function handler(metadata, response) {
gRequests.push(metadata.queryString);
response.setStatusLine(metadata.httpVersion, 200, "OK");
response.setHeader("Content-Type", "text/plain", false);
response.setHeader("Cache-Control", "max-age=10000", false);
response.setHeader("ETag", `"${metadata.queryString}"`, false);
if (metadata.queryString === "u=a") {
response.setHeader("No-Vary-Search", 'params=("u")', false);
}
const body = metadata.queryString;
response.bodyOutputStream.write(body, body.length);
}
function uri(query) {
}
function fetchURI(spec, loadFlags = 0) {
return new Promise(resolve => {
let chan = NetUtil.newChannel({
uri: spec,
loadUsingSystemPrincipal: true,
});
chan.loadFlags |= loadFlags;
chan.asyncOpen(
new ChannelListener(
(request, buffer) => resolve(buffer),
null,
CL_ALLOW_UNKNOWN_CL
)
);
});
}
function openEntry(spec, where = "disk") {
return new Promise((resolve, reject) => {
asyncOpenCacheEntry(
spec,
where,
Ci.nsICacheStorage.OPEN_READONLY | Ci.nsICacheStorage.OPEN_SECRETLY,
null,
(status, entry) => {
if (!Components.isSuccessCode(status)) {
reject(status);
return;
}
resolve(entry);
}
);
});
}
function entryBody(entry) {
return new Promise(resolve => {
pumpReadStream(entry.openInputStream(0), resolve);
});
}
// The key of the entry a lookup for |spec| resolves to, or null when there is
// none. This is not an exact-key probe: the read-only open runs the same
// No-Vary-Search secondary lookup a channel would.
async function resolvedKey(spec, where = "disk") {
try {
let entry = await openEntry(spec, where);
return entry.key;
} catch (status) {
return null;
}
}
add_setup(function () {
do_get_profile();
Services.prefs.setBoolPref("network.cache.no_vary_search", true);
gHttpServer = new HttpServer();
gHttpServer.registerPathHandler(PATH, handler);
gHttpServer.start(-1);
gPort = gHttpServer.identity.primaryPort;
registerCleanupFunction(() => {
gHttpServer.stop(() => {});
Services.prefs.clearUserPref("network.cache.no_vary_search");
});
});
add_task(async function test_memory_only_load_keeps_nvs_sibling_on_disk() {
Services.cache2.clear();
gRequests = [];
const a = uri("u=a");
const b = uri("u=b");
const c = uri("u=c");
Assert.equal(await fetchURI(a), "u=a", "A is primed");
Assert.equal(gRequests.length, 1, "A went to the network");
// B may not be persisted, so it is opened on the memory-only storage. Its
// exact key misses and No-Vary-Search offers A's disk entry as a candidate.
Assert.equal(
await fetchURI(b, Ci.nsIRequest.INHIBIT_PERSISTENT_CACHING),
"u=b",
"B is fetched"
);
Assert.equal(gRequests.length, 2, "B went to the network");
// A is fresh for a long time and must be untouched: with the bug its disk
// entry was doomed while staying in the entry table, so it can neither be
// served nor replaced for the rest of the session.
Assert.equal(await resolvedKey(a), a, "A's URL still resolves to A's entry");
let entryA = await openEntry(a);
Assert.ok(entryA.persistent, "A is still a persistent entry");
Assert.equal(await entryBody(entryA), "u=a", "A's entry holds A's response");
Assert.equal(await fetchURI(a), "u=a", "A is served from its own entry");
Assert.equal(gRequests.length, 2, "A did not need the network");
// And the secondary index still hands A out to the siblings it covers.
Assert.equal(await fetchURI(c), "u=a", "C reused A's fresh entry");
Assert.equal(gRequests.length, 2, "C did not need the network");
});