| <!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> |