// Clear in-memory DictionaryCache and purge cache entries from memory. // This forces dictionary origin entries to be reloaded from disk on next access, // triggering DictionaryOriginReader::OnCacheEntryAvailable.
async function clearDictionaryCacheAndPurgeMemory() { // Clear the DictionaryCache in-memory hashmap
let testingInterface = Services.cache2.QueryInterface(Ci.nsICacheTesting);
testingInterface.clearDictionaryCacheMemory();
// Force GC to release references to cache entries. Probably not strictly needed
gc();
}
/** *Testthatreplacingadictionaryresourcewithnon-dictionarycontent *properlyremovesthedictionarymetadata. * *Steps: *1.LoadaresourcewithUse-As-Dictionaryheader(createsdictionaryentry) *2.VerifyAvailable-Dictionaryissentformatchingresources *3.Force-reloadthedictionaryresourceWITHOUTUse-As-Dictionary *4.VerifyAvailable-DictionaryisNOLONGERsentformatchingresources
*/
add_task(async function test_dictionary_replacement_removes_metadata() { // Track Available-Dictionary headers received by server
let receivedAvailableDictionary = null;
// Register matching resource endpoint
await server.registerPathHandler( "/matching/test", function (request, response) { // Store the Available-Dictionary header value in global for later retrieval
global.lastAvailableDictionary =
request.headers["available-dictionary"] || null;
response.writeHead(200, { "Content-Type": "text/plain", "Cache-Control": "no-cache",
});
response.end("CONTENT_THAT_SHOULD_MATCH_DICTIONARY", "binary");
}
);
dump("**** Step 1: Load dictionary resource with Use-As-Dictionary\n");
let dictUrl = `https://localhost:${server.port()}/dict/resource`;
let chan = makeChan(dictUrl);
let [, data] = await channelOpenPromise(chan);
Assert.equal(data, DICTIONARY_CONTENT, "Dictionary content should match");
// Verify dictionary is stored in cache
await verifyDictionaryStored(dictUrl, true);
// Sync to ensure everything is written to disk
await syncCache();
dump("**** Step 1.5: Clear in-memory caches to force reload from disk\n");
// Clear in-memory DictionaryCache and purge cache entries from memory. // This forces dictionary entries to be reloaded from disk via // DictionaryOriginReader::OnCacheEntryAvailable, which is the code path // with the bug we're testing.
await clearDictionaryCacheAndPurgeMemory();
dump( "**** Step 2: Verify Available-Dictionary is sent for matching resource\n"
);
let matchingUrl = `https://localhost:${server.port()}/matching/test`;
chan = makeChan(matchingUrl);
await channelOpenPromise(chan);
// Get the Available-Dictionary value from the server
receivedAvailableDictionary = await server.execute( "global.lastAvailableDictionary"
);
Assert.notStrictEqual(
receivedAvailableDictionary, null, "Available-Dictionary header should be sent for matching resource"
); Assert.ok(
receivedAvailableDictionary.includes(":"), "Available-Dictionary should contain a hash"
);
dump(`**** Received Available-Dictionary: ${receivedAvailableDictionary}\n`);
dump( "**** Step 3: Force-reload dictionary resource WITHOUT Use-As-Dictionary\n"
);
// Re-register the dictionary endpoint to return content WITHOUT Use-As-Dictionary
await server.registerPathHandler( "/dict/resource", function (request, response) {
response.writeHead(200, { "Content-Type": "application/octet-stream", "Cache-Control": "max-age=3600", // No Use-As-Dictionary header!
});
response.end("REPLACED_CONTENT_WITHOUT_DICTIONARY_HEADER", "binary");
}
);
Assert.equal(
receivedAvailableDictionary, null, "Available-Dictionary header should NOT be sent after dictionary is replaced"
);
dump("**** Test passed: Dictionary metadata was properly removed\n");
});
/** *Testthesamescenariobutwithgzip-compressedreplacementcontent. *Thissimulatesthereal-worldcasewhereaservermightreturn *compressedcontentwithoutUse-As-Dictionary.
*/
add_task(async function test_dictionary_replacement_with_compressed_content() {
dump("**** Clear cache and start fresh\n");
let lci = Services.loadContextInfo.custom(false, {
partitionKey: `(https,localhost)`,
});
evict_cache_entries("all", lci);
// Also clear in-memory DictionaryCache to start fresh
let testingInterface = Services.cache2.QueryInterface(Ci.nsICacheTesting);
testingInterface.clearDictionaryCacheMemory();
dump("**** Step 1.5: Clear in-memory caches to force reload from disk\n");
await clearDictionaryCacheAndPurgeMemory();
dump("**** Step 2: Verify Available-Dictionary is sent\n");
let matchingUrl = `https://localhost:${server.port()}/compressed-match/test`;
chan = makeChan(matchingUrl);
await channelOpenPromise(chan);
receivedAvailableDictionary = await server.execute( "global.lastCompressedAvailDict"
); Assert.notStrictEqual(
receivedAvailableDictionary, null, "Available-Dictionary should be sent initially"
);
dump( "**** Step 3: Force-reload with gzip-compressed content (no Use-As-Dictionary)\n"
);
// Re-register to return gzip-compressed content without Use-As-Dictionary
await server.registerPathHandler( "/dict/compressed", function (request, response) { // Gzip-compressed version of "GZIP_COMPRESSED_REPLACEMENT" // Using Node.js zlib in the handler const zlib = require("zlib"); const compressed = zlib.gzipSync("GZIP_COMPRESSED_REPLACEMENT");
Assert.equal(
receivedAvailableDictionary, null, "Available-Dictionary should NOT be sent after dictionary replaced with compressed content"
);
dump( "**** Test passed: Dictionary metadata removed even with compressed replacement\n"
);
});
/** *Testthatmultiplesequentialreplacementsworkcorrectly. *Dictionary->Non-dictionary->Dictionary->Non-dictionary
*/
add_task(async function test_dictionary_multiple_replacements() {
dump("**** Clear cache and start fresh\n");
let lci = Services.loadContextInfo.custom(false, {
partitionKey: `(https,localhost)`,
});
evict_cache_entries("all", lci);
// Also clear in-memory DictionaryCache to start fresh
let testingInterface = Services.cache2.QueryInterface(Ci.nsICacheTesting);
testingInterface.clearDictionaryCacheMemory();
dump("**** Step 2: Verify Available-Dictionary is sent\n");
let matchingUrl = `https://localhost:${server.port()}/hash-match/test`;
chan = makeChan(matchingUrl);
await channelOpenPromise(chan);
receivedAvailableDictionary = await server.execute( "global.lastHashTestAvailDict"
); Assert.notStrictEqual(
receivedAvailableDictionary, null, "Available-Dictionary should be sent initially"
);
dump("**** Step 3: Corrupt the dictionary hash\n");
testingInterface.corruptDictionaryHash(dictUrl);
dump("**** Step 4: Clear dictionary data to force reload from disk\n");
// Clear dictionary data while keeping the corrupted hash. // When next prefetch happens, data will be reloaded and compared // against the corrupted hash, causing a mismatch.
testingInterface.clearDictionaryDataForTesting(dictUrl);
dump( "**** Step 5: Request matching resource - should fail due to hash mismatch\n"
);
// The request for the matching resource will try to prefetch the dictionary, // which will fail due to hash mismatch. The channel should be cancelled.
chan = makeChan(matchingUrl); try {
await channelOpenPromise(chan, true); // intermittent failure
} catch (e) {
dump(`**** Request failed with: ${e}\n`);
}
// Note: The request may or may not fail depending on timing. The important // thing is that the dictionary entry should be removed.
dump("**** Step 6: Verify dictionary entry was removed\n");
// Wait a bit for the removal to complete
await syncCache();
receivedAvailableDictionary = await server.execute( "global.lastHashTestAvailDict"
); Assert.equal(
receivedAvailableDictionary, null, "Available-Dictionary should NOT be sent after dictionary was removed due to hash mismatch"
);
dump("**** Test passed: Hash mismatch properly handled\n");
});
// Cleanup
add_task(async function cleanup() {
let lci = Services.loadContextInfo.custom(false, {
partitionKey: `(https,localhost)`,
});
evict_cache_entries("all", lci);
dump("**** All dictionary replacement tests completed\n");
});
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-09-27)
¤
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.