add_setup(async function () {
Services.prefs.setBoolPref("network.http.dictionaries.enable", true); if (!server) {
server = await setupServer();
} // Setup baseline dictionaries for compression testing
// Clear any existing cache
let lci = Services.loadContextInfo.custom(false, {
partitionKey: `(https,localhost)`,
});
evict_cache_entries("all", lci);
});
// Utility function to calculate SHA-256 hash
async function calculateSHA256(data) {
let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
Ci.nsICryptoHash
);
hasher.init(Ci.nsICryptoHash.SHA256);
// Convert string to UTF-8 bytes
let bytes = new TextEncoder().encode(data);
hasher.update(bytes, bytes.length); return hasher.finish(false);
}
// Setup dictionary test server
async function setupServer() {
let httpServer = new NodeHTTPSServer();
await httpServer.start();
// Verify dictionary is stored in cache function verifyDictionaryStored(url, shouldExist, callback) {
let lci = Services.loadContextInfo.custom(false, {
partitionKey: `(https,localhost)`,
});
asyncCheckCacheEntryPresence(url, "disk", shouldExist, lci, callback);
}
// Create channel for dictionary requests function makeChan(url) {
let chan = NetUtil.newChannel({
uri: url,
loadUsingSystemPrincipal: true,
contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
}).QueryInterface(Ci.nsIHttpChannel); return chan;
}
function channelOpenPromise(chan) { returnnew Promise(resolve => { function finish(req, buffer) {
resolve([req, buffer]);
}
chan.asyncOpen(new ChannelListener(finish, null, CL_ALLOW_UNKNOWN_CL));
});
}
// Test basic dictionary storage with Use-As-Dictionary header
add_task(async function test_basic_dictionary_storage() { // Clear any existing cache
evict_cache_entries("all");
let url = `https://localhost:${server.port()}/dict/small`;
let dict = TEST_DICTIONARIES.small;
let chan = makeChan(url);
let [req, data] = await channelOpenPromise(chan);
// Verify Use-As-Dictionary header was processed try {
let headerValue = req.getResponseHeader("Use-As-Dictionary"); Assert.ok(
headerValue.includes(`id="${dict.id}"`), "Header contains correct ID"
); Assert.ok(
headerValue.includes(`match="${dict.pattern}"`), "Header contains correct pattern"
);
} catch (e) { Assert.ok(false, "Use-As-Dictionary header should be present");
}
// Check that dictionary is stored in cache
await new Promise(resolve => {
verifyDictionaryStored(url, true, resolve);
});
});
// Test Use-As-Dictionary header parsing with various formats
add_task(async function test_dictionary_header_parsing() { const headerTests = [
{
header: 'match="*", id="dict1", type=raw',
valid: true,
description: "Basic valid header",
},
{
header: 'match="/api/*", id="api-dict", type=raw',
valid: true,
description: "Path pattern header",
},
{
header: 'match="*.js", id="js-dict"',
valid: true,
description: "Header without type (should default to raw)",
},
{
header: 'id="dict1", type=raw',
valid: false,
description: "Missing match parameter",
},
{
header: 'match="*"',
valid: false,
description: "Missing id parameter",
},
{
header: 'match="*", id="", type=raw',
valid: false,
description: "Empty id parameter",
},
];
let testIndex = 0; for (let test of headerTests) {
let testPath = `/dict/header-test-${testIndex++}`;
let func = `
global.testIndex = 0;
let test = ${JSON.stringify(test)};
response.writeHead(200, { "Content-Type": "application/octet-stream", "Use-As-Dictionary": test.header,
}); // We won't be using this, so it doesn't really matter
response.end("HEADER_TEST_DICT_" + global.testIndex++, "binary");
`;
let handler = newFunction("request", "response", func);
await server.registerPathHandler(testPath, handler);
let url = `https://localhost:${server.port()}${testPath}`;
let chan = makeChan(url);
await channelOpenPromise(chan); // XXX test if we have a dictionary entry. Need new APIs to let me test it, // or we can read dict:<origin> and look for this entry
// Note: Invalid dictionary headers still create regular cache entries, // they just aren't processed as dictionaries. So all should exist in cache.
await new Promise(resolve => {
verifyDictionaryStored(url, true, resolve);
});
}
});
// Test dictionary hash calculation and validation
add_task(async function test_dictionary_hash_calculation() {
dump("**** testing hashes\n");
let url = `https://localhost:${server.port()}/dict/small`;
let dict = TEST_DICTIONARIES.small;
// Calculate expected hash
let expectedHash = await calculateSHA256(dict.content); Assert.greater(expectedHash.length, 0, "Hash should be calculated");
let chan = makeChan(url);
await channelOpenPromise(chan);
// Calculate expected hash
let hashCalculatedHash = await calculateSHA256(dict.content); Assert.greater(hashCalculatedHash.length, 0, "Hash should be calculated");
// Check cache entry exists
await new Promise(resolve => {
let lci = Services.loadContextInfo.custom(false, {
partitionKey: `(https,localhost)`,
});
asyncOpenCacheEntry(
url, "disk",
Ci.nsICacheStorage.OPEN_READONLY,
lci, function (status, entry) { Assert.equal(status, Cr.NS_OK, "Cache entry should exist"); Assert.ok(entry, "Entry should not be null");
// Check if entry has dictionary metadata try {
let metaData = entry.getMetaDataElement("use-as-dictionary"); Assert.ok(metaData, "Dictionary metadata should exist");
// Verify metadata contains hash information // Note: The exact format may vary based on implementation Assert.ok(
metaData.includes(dict.id), "Metadata should contain dictionary ID"
);
} catch (e) { // Dictionary metadata might be stored differently
dump(`Dictionary metadata access failed: ${e}\n`);
}
resolve();
}
);
});
});
// Test dictionary expiration handling
add_task(async function test_dictionary_expiration() {
dump("**** testing expiration\n");
let url = `https://localhost:${server.port()}/dict/expires`;
// Fetch dictionary with 1-second expiration
let chan = makeChan(url);
let [, data] = await channelOpenPromise(chan);
// Note: Testing actual expiration behavior requires waiting and is complex // For now, just verify the dictionary was fetched // XXX FIX!
});
// Test multiple dictionaries per origin with different patterns
add_task(async function test_multiple_dictionaries_per_origin() {
dump("**** test multiple dictionaries per origin\n"); // Register multiple dictionary endpoints for same origin
await server.registerPathHandler("/dict/api", function (request, response) {
response.writeHead(200, { "Content-Type": "application/octet-stream", "Use-As-Dictionary": 'match="/api/*", id="api-dict", type=raw',
});
response.end("API_DICTIONARY_DATA", "binary");
});
let apiUrl = `https://localhost:${server.port()}/dict/api`;
let webUrl = `https://localhost:${server.port()}/dict/web`;
// Fetch both dictionaries
let apiChan = makeChan(apiUrl);
let [, apiData] = await channelOpenPromise(apiChan); Assert.equal(
apiData, "API_DICTIONARY_DATA", "API dictionary content matches"
);
let webChan = makeChan(webUrl);
let [, webData] = await channelOpenPromise(webChan); Assert.equal(
webData, "WEB_DICTIONARY_DATA", "Web dictionary content matches"
);
// Verify both dictionaries are stored
await new Promise(resolve => {
verifyDictionaryStored(apiUrl, true, () => {
verifyDictionaryStored(webUrl, true, resolve);
});
});
});
// Test dictionary size limits and validation
add_task(async function test_dictionary_size_limits() {
dump("**** test size limits\n");
let url = `https://localhost:${server.port()}/dict/large`;
let dict = TEST_DICTIONARIES.large;
let chan = makeChan(url);
let [, data] = await channelOpenPromise(chan);
// Verify large dictionary is stored
await new Promise(resolve => {
verifyDictionaryStored(url, true, resolve);
});
});
// Test error handling with invalid dictionary headers
add_task(async function test_invalid_dictionary_headers() {
dump("**** test error handling\n");
let url = `https://localhost:${server.port()}/dict/invalid`;
let chan = makeChan(url);
let [, data] = await channelOpenPromise(chan);
// Invalid dictionary should not be stored as dictionary // but the regular cache entry should exist
await new Promise(resolve => {
asyncOpenCacheEntry(
url, "disk",
Ci.nsICacheStorage.OPEN_READONLY, null, function (status, entry) { if (status === Cr.NS_OK && entry) { // Regular cache entry should exist // Note: Don't call entry.close() as it doesn't exist on this interface
} // But it should not be processed as a dictionary
resolve();
}
);
});
});
// Test cache integration and persistence
add_task(async function test_dictionary_cache_persistence() {
dump("**** test persistence\n"); // Force cache sync to ensure everything is written
await new Promise(resolve => {
syncWithCacheIOThread(resolve, true);
});
// Get cache statistics before
await new Promise(resolve => {
get_device_entry_count("disk", null, entryCount => { Assert.greater(entryCount, 0, "Cache should have entries");
resolve();
});
});
// Verify our test dictionaries are still present
let smallUrl = `https://localhost:${server.port()}/dict/small`;
let chan = makeChan(smallUrl);
await channelOpenPromise(chan);
await new Promise(resolve => {
verifyDictionaryStored(smallUrl, true, resolve);
});
});
// Test very long url which should fit in metadata
add_task(async function test_long_dictionary_url() { // Clear any existing cache
evict_cache_entries("all");
let url =
`https://localhost:${server.port()}/dict/large/` + "A".repeat(1024 * 20);
let dict = TEST_DICTIONARIES.large_url;
let chan = makeChan(url);
let [req, data] = await channelOpenPromise(chan);
// Check that dictionary is stored in cache
await new Promise(resolve => {
verifyDictionaryStored(url, true, resolve);
});
// Verify Use-As-Dictionary header was processed and it's an active dictionary
url = `https://localhost:${server.port()}/dict/large/file`;
chan = makeChan(url);
[req, data] = await channelOpenPromise(chan);
try {
let headerValue = req.getRequestHeader("Available-Dictionary"); Assert.ok(headerValue.includes(`:`), "Header contains a hash");
} catch (e) { Assert.ok( false, "Available-Dictionary header should be present with long URL for dictionary"
);
}
});
// Test url too long to store in metadata
add_task(async function test_too_long_dictionary_url() { // Clear any existing cache
evict_cache_entries("all");
let url =
`https://localhost:${server.port()}/dict/large/` + "B".repeat(1024 * 100);
let dict = TEST_DICTIONARIES.too_large_url;
let chan = makeChan(url);
let [req, data] = await channelOpenPromise(chan);
// Check that dictionary is stored in cache (even if it's not a dictionary)
await new Promise(resolve => {
verifyDictionaryStored(url, true, resolve);
});
// Verify Use-As-Dictionary header was NOT processed and active due to 64K limit to metadata // Since we can't store it on disk, we can't offer it as a dictionary. If we change the // metadata limit, this will need to change
url = `https://localhost:${server.port()}/too_large`;
chan = makeChan(url);
[req, data] = await channelOpenPromise(chan);
try { // we're just looking to see if it throws // eslint-disable-next-line no-unused-vars
let headerValue = req.getRequestHeader("Available-Dictionary"); Assert.ok(false, "Too-long dictionary was offered in Available-Dictionary");
} catch (e) { Assert.ok( true, "Available-Dictionary header should not be present with a too-long URL for dictionary"
);
}
});
// Test that regexp groups cause it to not be stored as a dictionary
add_task(async function test_regexp_group() { // Clear any existing cache
evict_cache_entries("all");
let url = `https://localhost:${server.port()}/api/regexp`;
let dict = TEST_DICTIONARIES.regexp_group;
let chan = makeChan(url);
let [req, data] = await channelOpenPromise(chan);
// Check that dictionary is stored in cache (even if it's not a dictionary)
await new Promise(resolve => {
verifyDictionaryStored(url, true, resolve);
});
// Verify Use-As-Dictionary header was NOT processed and active due to 64K limit to metadata // Since we can't store it on disk, we can't offer it as a dictionary. If we change the // metadata limit, this will need to change
url = `https://localhost:${server.port()}/api/v2/test.js`;
chan = makeChan(url);
[req, data] = await channelOpenPromise(chan);
try { // we're just looking to see if it throws // eslint-disable-next-line no-unused-vars
let headerValue = req.getRequestHeader("Available-Dictionary"); Assert.ok( false, "Dictionary with regexp group was offered in Available-Dictionary"
);
} catch (e) { Assert.ok( true, "Available-Dictionary header should not be present for a dictionary with regexp groups"
);
}
});
// Cleanup
add_task(async function cleanup() { // Clear cache
evict_cache_entries("all");
dump("**** all done\n");
});
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-10-02)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.