clear-cache-helper.sub.js (7345B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict" 5 6 const sameOrigin = 7 'https://{{host}}:{{ports[https][0]}}'; 8 const subdomainOrigin = 9 'https://{{hosts[][www2]}}:{{ports[https][0]}}'; 10 const crossSiteOrigin = 11 'https://{{hosts[alt][]}}:{{ports[https][0]}}'; 12 const subdomainCrossSiteOrigin = 13 'https://{{hosts[alt][www2]}}:{{ports[https][0]}}'; 14 15 /** 16 * Constructs a url for an intermediate "bounce" hop which represents a tracker. 17 * @param {string} cacheHelper - Unique uuid for this test 18 * @param {*} options - URL generation options. 19 * @param {boolean} [options.crossSite = false] - whether domain should be a different site 20 * @param {boolean} [options.subdomain = false] - whether the domain should start with 21 * a different subdomain to make a request cross origin 22 * @param {boolean} [options.cache = false] - whether the resource should be cacheable 23 * @param {(null|'cache'|'all')} [options.clear] - whether to send the 24 * Clear-Site-Data header. 25 * @param {(null|'cache'|'all')} [options.clear_first] - whether to send the 26 * Clear-Site-Data header on first response 27 * @param {string} [response] - which response to elict - defaults to "single_html". Other 28 * options can be found in "clear-site-data-cache.py" server helper. 29 * @param {*} [options.iframe] - iframe same parameters as options (recursive). Only works on 30 * "single_html" variation of response 31 */ 32 function getUrl(cacheHelper, { 33 subdomain = false, 34 crossSite = false, 35 cache = false, 36 clear = null, 37 clearFirst = null, 38 response = "single_html", 39 iframe = null, 40 }) { 41 let url; 42 if (subdomain && crossSite) { 43 url = subdomainCrossSiteOrigin; 44 } else if (subdomain) { // && !crossSite 45 url = subdomainOrigin; 46 } else if (crossSite) { // && !subdomain 47 url = crossSiteOrigin; 48 } else { // !crossSite && !subdomain 49 url = sameOrigin; 50 } 51 url += "/clear-site-data/support/clear-site-data-cache.py"; 52 url = new URL(url); 53 let params = new URLSearchParams(); 54 params.append("cache_helper", cacheHelper); 55 params.append("response", response) 56 if (clear !== null) { 57 params.append("clear", clear); 58 } 59 if (clearFirst != null) { 60 params.append("clear_first", clearFirst); 61 } 62 if (cache) { 63 params.append("cache", ""); 64 } 65 if (iframe != null) { 66 let iframeUrl = getUrl(cacheHelper, iframe); 67 params.append("iframe", iframeUrl); 68 } 69 url.search = params; 70 return url.toString(); 71 } 72 73 /** 74 * Opens test pages sequentially, compares first and last uuid. Makes sure test cleans up properly 75 * @param test - test clean up 76 * @param {string} firstUuid - uuid returned by first url 77 * @param {array[string]} testUrls - array of all urls that should be visited 78 * @param {integer} curIdx - index in testUrls that is visited in the current function call 79 * @param {function assert_not_equal|assert_equal} assert - function that gets passed first and last 80 * uuid and determines the success of the test case 81 * @param {function} resolve - function to call when test case is complete 82 * @param {*} options - URL generation options. 83 */ 84 function openTestPageHelper(test, firstUuid, testUrls, curIdx, assert, resolve) { 85 window.addEventListener("message", test.step_func(e => { 86 let curUuid = e.data; 87 if (firstUuid === null) { 88 firstUuid = curUuid; 89 } 90 91 if (curIdx + 1 < testUrls.length) { 92 openTestPageHelper(test, firstUuid, testUrls, curIdx + 1, assert, resolve); 93 } else { 94 // Last Step 95 assert(firstUuid, curUuid); 96 resolve(); 97 } 98 }), {once: true}); 99 100 window.open(testUrls[curIdx]); 101 } 102 103 // Here's the set-up for this test: Step 1 and Step 2 are repeated for each param in params 104 // Step 1 (main window) Open popup window with url generated with `getUrl` 105 // Step 2 (first window) Message main window with potentially cached uuid and close popup 106 // Last Step (main window): Assert first and last uuid not equal due to `clear-site-data: "cache"` header 107 // 108 // Basic diagram visualizing how the test works: 109 // 110 // main window opens sequentially: 111 // (1) (2) (last) = (1) 112 // | Step 1 | Step 3 | Step 4 113 // | | | 114 // +--------v---------+ +------v----------+ +------v-----------+ 115 // | first / second | | Clear Data? | | | 116 // | origin | | | | | 117 // | | | | | | 118 // | +-iframe-------+ | | +-(iframe?)---+ | ... | +-iframe-------+ | 119 // | | first/second | | | | Clear Data? | | | | | | 120 // | | origin | | | | | | | | | | 121 // | +-----------+--+ | | +-------------+ | | +-+------------+ | 122 // +-------------+----+ +-----------------+ +---+--------------+ 123 // | | 124 // | Step 2 +----------------+ Step 5 125 // | | 126 // v v 127 // Last Step: is uuid from (1) different from (last)? 128 function testCacheClear(test, params, assert) { 129 if (params.length < 2) { 130 // fail test case 131 return new Promise((resolve, reject) => reject()); 132 } 133 134 const cacheHelper = self.crypto.randomUUID(); 135 const testUrls = params.map((param) => getUrl(cacheHelper, param)); 136 137 return new Promise(resolve => { 138 openTestPageHelper(test, null, testUrls, 0, assert, resolve) 139 }); 140 } 141 142 // The tests are built on top of the back-forward-cache test harness. 143 // Here is the steps for the tests: 144 // 1. Open a new window and navigate to a test URL. 145 // 2. Navigate the window to a second page. 146 // 3. Trigger the clear-site-data header either by window.open() or loading an 147 // iframe from the second page. 148 // 4. Navigate back to the first page. 149 // 5. Assert that the first page is or is not cached. 150 151 function runBfCacheClearTest(params, description) { 152 runBfcacheTest( 153 { 154 targetOrigin: sameOrigin, 155 scripts: ["/clear-site-data/support/clear-cache-helper.sub.js"], 156 funcBeforeBackNavigation: async (getUrlParams, mode) => { 157 158 const cacheHelper = self.crypto.randomUUID(); 159 const testUrl = getUrl(cacheHelper, getUrlParams); 160 161 let clearingPromise; 162 if (mode === "window") { 163 clearingPromise = new Promise(resolve => { 164 window.addEventListener("message", resolve, {once: true}); 165 window.open(testUrl); 166 }); 167 } else if (mode === "iframe") { 168 clearingPromise = new Promise(resolve => { 169 const iframe = document.createElement("iframe"); 170 iframe.src = testUrl; 171 document.body.appendChild(iframe); 172 iframe.onload = resolve; 173 }); 174 } else { 175 throw new Error("Unsupported mode"); 176 } 177 178 await clearingPromise; 179 }, 180 argsBeforeBackNavigation: [params.getUrlParams, params.mode], 181 ...params, 182 }, 183 description 184 ); 185 }