<!doctype html>
<title>Cache Storage: verify cache_storage padding behavior</title>
<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-interface">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script>
'use strict';
const tmp_url = new URL('resources/simple.js', self.location);
tmp_url.hostname = get_host_info().REMOTE_HOST;
const TARGET_URL = tmp_url.href;
async function usage(init) {
const cache = await caches.open('padding');
if (!init.mode)
init.mode = 'no-cors';
let r = await fetch(TARGET_URL, init);
if (init.mode == 'no-cors')
assert_equals(r.type, 'opaque');
const clone = r.clone();
// Note, this stores the response under a Request key that has a different
// url than what we fetched above. This is purposeful to ensure
// that we are varying on how the Response was loaded and not on the Request
// key.
const cache_url = '/foo';
await cache.put(cache_url, r);
const usage1 = (await navigator.storage.estimate()).usageDetails.caches;
await cache.delete(cache_url);
await cache.put(cache_url, clone);
const usage2 = (await navigator.storage.estimate()).usageDetails.caches;
await cache.delete(cache_url);
assert_equals(usage1, usage2, 'cloned response have the same padding size');
return usage1;
}
promise_test(async t => {
const usage1 = await usage({ cache: 'reload' });
const usage2 = await usage({ cache: 'reload' });
assert_not_equals(usage1, usage2,
'Responses loaded from network should have different ' +
'padding size.');
}, 'Cache padding varies if loaded from network.');
promise_test(async t => {
// populate http cache
await usage({ cache: 'reload' });
const cache_usage1 = await usage({ cache: 'force-cache' });
const cache_usage2 = await usage({ cache: 'force-cache' });
assert_equals(cache_usage1, cache_usage2,
'Responses loaded from http cache should have the same ' +
'padding size.');
// repopulate http cache
await usage({ cache: 'reload' });
const cache_usage3 = await usage({ cache: 'force-cache' });
const cache_usage4 = await usage({ cache: 'force-cache' });
assert_not_equals(cache_usage1, cache_usage3,
'An updated http cache entry should result in a ' +
'different padding size.');
assert_equals(cache_usage3, cache_usage4,
'Responses loaded from http cache should have the same ' +
'padding size.');
}, 'Cache padding is stable if loaded from http cache.');
promise_test(async t => {
// Determine the actual size difference between GET and HEAD requests.
const cors_get_usage = await usage({ mode: 'cors', method: 'GET' });
const cors_head_usage = await usage({ mode: 'cors', method: 'HEAD' });
const cors_diff = cors_get_usage - cors_head_usage;
const net_get_usage = await usage({ method: 'GET', cache: 'reload' });
const net_head_usage = await usage({ method: 'HEAD', cache: 'reload' });
const opaque_net_diff = net_get_usage - net_head_usage;
assert_not_equals(net_get_usage, net_head_usage,
'Responses loaded from network with different methods ' +
'should have different padding size.');
assert_not_equals(opaque_net_diff, cors_diff,
'Responses loaded from network with different methods ' +
'should have usages that differ more than actual ' +
'response size.');
// populate http cache
await usage({ cache: 'reload' });
const cache_get_usage = await usage({ method: 'GET', cache: 'force-cache' });
const cache_head_usage = await usage({ method: 'HEAD', cache: 'force-cache' });
const opaque_cache_diff = cache_get_usage - cache_head_usage;
assert_not_equals(cache_get_usage, cache_head_usage,
'Responses loaded from http cache with different methods ' +
'should have the different padding size.');
assert_not_equals(opaque_cache_diff, cors_diff,
'Responses loaded from http cache with different methods ' +
'should have usages that differ more than actual ' +
'response size.');
}, 'Cache padding varies with request method.');
promise_test(async t => {
const cache1 = await caches.open('padding');
const cache2 = await caches.open('padding-2');
const net_response = await fetch(TARGET_URL, { mode: 'no-cors',
cache: 'reload' });
await cache1.put(TARGET_URL, net_response);
const usage1 = (await navigator.storage.estimate()).usageDetails.caches;
const cache_response = await cache1.match(TARGET_URL);
await cache2.put(TARGET_URL, cache_response);
await cache1.delete(TARGET_URL);
const usage2 = (await navigator.storage.estimate()).usageDetails.caches;
await cache2.delete(TARGET_URL);
assert_equals(usage1, usage2,
'The padding value should follow a response loaded from ' +
'cache_storage.');
}, 'Cache padding follows a response loaded from cache_storage.');
promise_test(async t => {
const script = './resources/padding-fetch-sw.js';
const scope = './resources/padding-fetch-frame.html';
// Setup a service worker.
const reg = await service_worker_unregister_and_register(t, script, scope);
t.add_cleanup(() => reg.unregister());
await wait_for_state(t, reg.installing, 'activated');
// Setup an iframe controlled by the service worker.
const frame = await with_iframe(scope);
t.add_cleanup(() => frame.remove());
// The service worker will send a padded usage value via postMessage.
// Prepare to receive this message.
const message_promise = new Promise(resolve => {
frame.contentWindow.navigator.serviceWorker.addEventListener('message',
evt => {
if (!('usage' in evt.data))
return;
resolve(evt.data.usage);
});
});
// Fetch an opaque response from the network. This will hit the
// service worker fetch handler which will compute a padded usage
// value before allowing this outer fetch to resolve. The SW
// usage value is sent via an out-of-band postMessage.
const request = new Request(TARGET_URL, { mode: 'no-cors',
cache: 'reload' });
const net_response = await frame.contentWindow.fetch(request);
// Compute a padded usage value based on the response returned from our
// outer fetch.
const cache = await caches.open('padding');
// Make sure to use the URL and not the full request. The main window
// and service worker requests may have different headers which can
// throw off the size comparison.
await cache.put(request.url, net_response);
const usage = (await navigator.storage.estimate()).usageDetails.caches;
await cache.delete(request.url);
// Compare our usage against the service worker usage. They should be the
// same. While a random padding is generated when a response is loaded
// over the network, that padding will be propagated through the service
// worker boundary to the outer fetch.
const sw_usage = await message_promise;
assert_equals(usage, sw_usage,
'The padding should propogate through the service worker ' +
'respondWith().');
}, 'Cache padding propagates through a service worker respondWith().');
promise_test(async t => {
const script = './resources/padding-install-sw.js';
const scope = './resources/padding-install-frame.html';
// populate http cache
await usage({ cache: 'reload' });
// Get usage for a response stored on the main thread. This will
// not include any code cache.
const main_window_usage = await usage({ cache: 'force-cache' });
// A utility function for creating a promise that waits for a message
// from the service worker sending usage information.
function make_message_promise() {
return new Promise(resolve => {
navigator.serviceWorker.addEventListener('message',
function onMessage(evt) {
if (!('usage' in evt.data))
return;
navigator.serviceWorker.removeEventListener('message', onMessage);
resolve(evt.data.usage);
});
});
}
// Prepare to get a usage value from the service worker.
const message_promise_1 = make_message_promise();
// Register a service worker that stores and computes usage in its install
// event. This will result in code cache being stored as side data.
const reg_1 = await service_worker_unregister_and_register(t, script, scope);
t.add_cleanup(() => reg_1.unregister());
await wait_for_state(t, reg_1.installing, 'activated');
// Wait for the service worker usage including code cache.
const sw_usage_1 = await message_promise_1;
assert_true(sw_usage_1 > main_window_usage,
'Usage with code cache should always be greater than ' +
'main_window_usage.');
// Prepare to get another usage value from a service worker.
const message_promise_2 = make_message_promise();
// Register a second service worker to get another usage with code cache.
const reg_2 = await service_worker_unregister_and_register(t, script, scope + '-2');
t.add_cleanup(() => reg_2.unregister());
await wait_for_state(t, reg_2.installing, 'activated');
// The usage with code cache should be stable.
const sw_usage_2 = await message_promise_2;
assert_equals(sw_usage_1, sw_usage_2,
'Usage with code cache should be stable for a response ' +
'loaded from http cache.');
}, 'Code cache padding should be stable.');
</script>