Automatic update from web-platform-tests CompressionDictionaries - ensure encodedBodySize survives the cache DCB/DCZ assets are being decompressed before being cached, to avoid having to rely on their dictionary when retrieved from the cache. This creates a web-exposed issue with encodedBodySize, where it's equal to the decodedBodySize in those cases. This CL fixes that by storing the original encoded body size as metadata on the cached asset, and setting it upon retrieval. Bug: 457323840 Change-Id: I5820cb0cc36fa06b746228598e274b1410c380e7 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7613348 Commit-Queue: Yoav Weiss (@Shopify) <yoavweiss@chromium.org> Reviewed-by: Maks Orlovich <morlovich@chromium.org> Cr-Commit-Position: refs/heads/main@{#1593198} -- wpt-commits: 0e41cd4ffaf8048deb4014b0d6985c4fe0a76beb wpt-pr: 58223
126 lines
5.9 KiB
HTML
126 lines
5.9 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="timeout" content="long"/>
|
|
<title>PerformanceResourceTiming.encodedBodySize and transferSize for cached dictionary-compressed resources</title>
|
|
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-encodedbodysize" />
|
|
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-transfersize" />
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
<script src="/fetch/compression-dictionary/resources/compression-dictionary-util.sub.js"></script>
|
|
<script>
|
|
const waitForResourceTiming = (url) => {
|
|
return new Promise(resolve => {
|
|
const observer = new PerformanceObserver((list) => {
|
|
const entries = list.getEntries();
|
|
for (const entry of entries) {
|
|
if (entry.name.includes(url)) {
|
|
observer.disconnect();
|
|
resolve(entry);
|
|
}
|
|
}
|
|
});
|
|
observer.observe({ entryTypes: ["resource"] });
|
|
});
|
|
};
|
|
|
|
const registerDictionaryUrl = SAME_ORIGIN_RESOURCES_URL + '/register-dictionary.py';
|
|
const compressedDataUrl = SAME_ORIGIN_RESOURCES_URL + '/compressed-data.py';
|
|
const echoHeadersUrl = SAME_ORIGIN_RESOURCES_URL + '/echo-headers.py';
|
|
|
|
// The large test data: 10x the dictionary text plus extra content (348
|
|
// bytes decoded). This compresses well with the dictionary so the encoded
|
|
// size is genuinely smaller than the decoded size.
|
|
const kLargeExpectedData =
|
|
'This is a test dictionary. '.repeat(10) +
|
|
'This is additional test data that also references the test dictionary content.';
|
|
|
|
// Waits until the available-dictionary header is present in requests.
|
|
// We can't use waitUntilAvailableDictionaryHeader() from the shared util
|
|
// because it uses relative paths to echo-headers.py that don't resolve
|
|
// from the /resource-timing/ directory.
|
|
async function waitForDictionary(t) {
|
|
for (let i = 0; i < kCheckHeaderMaxRetry; i++) {
|
|
const headers = await (await fetch(echoHeadersUrl)).json();
|
|
if (headers['available-dictionary'] === kDefaultDictionaryHashBase64) {
|
|
return;
|
|
}
|
|
await new Promise(r => t.step_timeout(r, kCheckHeaderRetryTimeout));
|
|
}
|
|
assert_unreached('Dictionary was not registered in time');
|
|
}
|
|
|
|
// Runs a single encodedBodySize/transferSize cache preservation test.
|
|
async function runCacheTest(t, encoding, expectedData, expectedEncodedSize,
|
|
expectedDecodedSize, queryExtra) {
|
|
const dict = await (await fetch(registerDictionaryUrl)).text();
|
|
assert_equals(dict, kDefaultDictionaryContent);
|
|
await waitForDictionary(t);
|
|
|
|
const unique = Date.now();
|
|
const url = `${compressedDataUrl}?content_encoding=${encoding}&cacheable${queryExtra}&unique=${unique}`;
|
|
|
|
// First fetch: from network with dictionary compression.
|
|
performance.clearResourceTimings();
|
|
const timingPromise1 = waitForResourceTiming(url);
|
|
const response1 = await fetch(url);
|
|
const text1 = await response1.text();
|
|
assert_equals(text1, expectedData,
|
|
"First fetch: resource should decompress correctly");
|
|
const entry1 = await timingPromise1;
|
|
|
|
assert_equals(entry1.contentEncoding, encoding,
|
|
"First fetch: contentEncoding should indicate dictionary compression");
|
|
assert_equals(entry1.decodedBodySize, expectedDecodedSize,
|
|
"First fetch: decodedBodySize should match uncompressed size");
|
|
assert_equals(entry1.encodedBodySize, expectedEncodedSize,
|
|
"First fetch: encodedBodySize should match compressed size");
|
|
assert_greater_than(entry1.transferSize, 0,
|
|
"First fetch: transferSize should be greater than 0 for a network fetch");
|
|
|
|
// Second fetch: should come from cache.
|
|
performance.clearResourceTimings();
|
|
const timingPromise2 = waitForResourceTiming(url);
|
|
const response2 = await fetch(url);
|
|
const text2 = await response2.text();
|
|
assert_equals(text2, expectedData,
|
|
"Cached fetch: resource should still decompress correctly");
|
|
const entry2 = await timingPromise2;
|
|
|
|
assert_equals(entry2.contentEncoding, encoding,
|
|
"Cached fetch: contentEncoding should still indicate dictionary compression");
|
|
assert_equals(entry2.decodedBodySize, expectedDecodedSize,
|
|
"Cached fetch: decodedBodySize should match uncompressed size");
|
|
assert_equals(entry2.encodedBodySize, expectedEncodedSize,
|
|
"Cached fetch: encodedBodySize should match the original compressed size, not the decompressed cache size");
|
|
assert_equals(entry2.transferSize, 0,
|
|
"Cached fetch: transferSize should be 0 for a cache hit");
|
|
}
|
|
|
|
// These tests use large payloads where the encoded size is genuinely
|
|
// smaller than the decoded size (the typical real-world case). This
|
|
// ensures the cache truncation check (Content-Length vs cached body size)
|
|
// does not incorrectly discard the cached entry.
|
|
// 348 bytes decoded -> 93 bytes DCB / 117 bytes DCZ.
|
|
compression_dictionary_promise_test(async (t) => {
|
|
await runCacheTest(t, 'dcb', kLargeExpectedData, 93, 348, '&large');
|
|
}, "encodedBodySize and transferSize for dictionary-compressed (dcb) resource served from cache");
|
|
|
|
compression_dictionary_promise_test(async (t) => {
|
|
await runCacheTest(t, 'dcz', kLargeExpectedData, 117, 348, '&large');
|
|
}, "encodedBodySize and transferSize for dictionary-compressed (dcz) resource served from cache");
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<h1>Description</h1>
|
|
<p>
|
|
This test validates that PerformanceResourceTiming.encodedBodySize correctly
|
|
reports the original dictionary-compressed body size even when the resource
|
|
is served from the disk cache (where dictionary-compressed responses are
|
|
stored uncompressed), and that transferSize is 0 for cached responses.
|
|
</p>
|
|
</body>
|
|
</html>
|
|
|