keepalive-helper.js (7362B)
1 // Utility functions to help testing keepalive requests. 2 3 // Returns a URL to an iframe that loads a keepalive URL on iframe loaded. 4 // 5 // The keepalive URL points to a target that stores `token`. The token will then 6 // be posted back on iframe loaded to the parent document. 7 // `method` defaults to GET. 8 // `frameOrigin` to specify the origin of the iframe to load. If not set, 9 // default to a different site origin. 10 // `requestOrigin` to specify the origin of the fetch request target. 11 // `sendOn` to specify the name of the event when the keepalive request should 12 // be sent instead of the default 'load'. 13 // `mode` to specify the fetch request's CORS mode. 14 // `disallowCrossOrigin` to ask the iframe to set up a server that disallows 15 // cross origin requests. 16 function getKeepAliveIframeUrl(token, method, { 17 frameOrigin = 'DEFAULT', 18 requestOrigin = '', 19 sendOn = 'load', 20 mode = 'cors', 21 disallowCrossOrigin = false 22 } = {}) { 23 const https = location.protocol.startsWith('https'); 24 frameOrigin = frameOrigin === 'DEFAULT' ? 25 get_host_info()[https ? 'HTTPS_NOTSAMESITE_ORIGIN' : 'HTTP_NOTSAMESITE_ORIGIN'] : 26 frameOrigin; 27 return `${frameOrigin}/fetch/api/resources/keepalive-iframe.html?` + 28 `token=${token}&` + 29 `method=${method}&` + 30 `sendOn=${sendOn}&` + 31 `mode=${mode}&` + (disallowCrossOrigin ? `disallowCrossOrigin=1&` : ``) + 32 `origin=${requestOrigin}`; 33 } 34 35 // Returns a different-site URL to an iframe that loads a keepalive URL. 36 // 37 // By default, the keepalive URL points to a target that redirects to another 38 // same-origin destination storing `token`. The token will then be posted back 39 // to parent document. 40 // 41 // The URL redirects can be customized from `origin1` to `origin2` if provided. 42 // Sets `withPreflight` to true to get URL enabling preflight. 43 function getKeepAliveAndRedirectIframeUrl( 44 token, origin1, origin2, withPreflight) { 45 const https = location.protocol.startsWith('https'); 46 const frameOrigin = 47 get_host_info()[https ? 'HTTPS_NOTSAMESITE_ORIGIN' : 'HTTP_NOTSAMESITE_ORIGIN']; 48 return `${frameOrigin}/fetch/api/resources/keepalive-redirect-iframe.html?` + 49 `token=${token}&` + 50 `origin1=${origin1}&` + 51 `origin2=${origin2}&` + (withPreflight ? `with-headers` : ``); 52 } 53 54 async function iframeLoaded(iframe) { 55 return new Promise((resolve) => iframe.addEventListener('load', resolve)); 56 } 57 58 // Obtains the token from the message posted by iframe after loading 59 // `getKeepAliveAndRedirectIframeUrl()`. 60 async function getTokenFromMessage() { 61 return new Promise((resolve) => { 62 window.addEventListener('message', (event) => { 63 resolve(event.data); 64 }, {once: true}); 65 }); 66 } 67 68 // Tells if `token` has been stored in the server. 69 async function queryToken(token) { 70 const response = await fetch(`../resources/stash-take.py?key=${token}`); 71 const json = await response.json(); 72 return json; 73 } 74 75 // A helper to assert the existence of `token` that should have been stored in 76 // the server by fetching ../resources/stash-put.py. 77 // 78 // This function simply wait for a custom amount of time before trying to 79 // retrieve `token` from the server. 80 // `expectTokenExist` tells if `token` should be present or not. 81 // 82 // NOTE: 83 // In order to parallelize the work, we are going to have an async_test 84 // for the rest of the work. Note that we want the serialized behavior 85 // for the steps so far, so we don't want to make the entire test case 86 // an async_test. 87 function assertStashedTokenAsync( 88 testName, token, {expectTokenExist = true} = {}) { 89 async_test(test => { 90 new Promise(resolve => test.step_timeout(resolve, 3000 /*ms*/)) 91 .then(test.step_func(() => { 92 return queryToken(token); 93 })) 94 .then(test.step_func(result => { 95 if (expectTokenExist) { 96 assert_equals(result, 'on', `token should be on (stashed).`); 97 test.done(); 98 } else { 99 assert_not_equals( 100 result, 'on', `token should not be on (stashed).`); 101 return Promise.reject(`Failed to retrieve token from server`); 102 } 103 })) 104 .catch(test.step_func(e => { 105 if (expectTokenExist) { 106 test.unreached_func(e); 107 } else { 108 test.done(); 109 } 110 })); 111 }, testName); 112 } 113 114 /** 115 * In an iframe, and in `load` event handler, test to fetch a keepalive URL that 116 * involves in redirect to another URL. 117 * 118 * `unloadIframe` to unload the iframe before verifying stashed token to 119 * simulate the situation that unloads after fetching. Note that this test is 120 * different from `keepaliveRedirectInUnloadTest()` in that the latter 121 * performs fetch() call directly in `unload` event handler, while this test 122 * does it in `load`. 123 */ 124 function keepaliveRedirectTest(desc, { 125 origin1 = '', 126 origin2 = '', 127 withPreflight = false, 128 unloadIframe = false, 129 expectFetchSucceed = true, 130 } = {}) { 131 desc = `[keepalive][iframe][load] ${desc}` + 132 (unloadIframe ? ' [unload at end]' : ''); 133 promise_test(async (test) => { 134 const tokenToStash = token(); 135 const iframe = document.createElement('iframe'); 136 iframe.src = getKeepAliveAndRedirectIframeUrl( 137 tokenToStash, origin1, origin2, withPreflight); 138 document.body.appendChild(iframe); 139 await iframeLoaded(iframe); 140 assert_equals(await getTokenFromMessage(), tokenToStash); 141 if (unloadIframe) { 142 iframe.remove(); 143 } 144 145 assertStashedTokenAsync( 146 desc, tokenToStash, {expectTokenExist: expectFetchSucceed}); 147 }, `${desc}; setting up`); 148 } 149 150 /** 151 * Opens a different site window, and in `unload` event handler, test to fetch 152 * a keepalive URL that involves in redirect to another URL. 153 */ 154 function keepaliveRedirectInUnloadTest(desc, { 155 origin1 = '', 156 origin2 = '', 157 url2 = '', 158 withPreflight = false, 159 expectFetchSucceed = true 160 } = {}) { 161 desc = `[keepalive][new window][unload] ${desc}`; 162 163 promise_test(async (test) => { 164 const targetUrl = 165 `${HTTP_NOTSAMESITE_ORIGIN}/fetch/api/resources/keepalive-redirect-window.html?` + 166 `origin1=${origin1}&` + 167 `origin2=${origin2}&` + 168 `url2=${url2}&` + (withPreflight ? `with-headers` : ``); 169 const w = window.open(targetUrl); 170 const token = await getTokenFromMessage(); 171 w.close(); 172 173 assertStashedTokenAsync( 174 desc, token, {expectTokenExist: expectFetchSucceed}); 175 }, `${desc}; setting up`); 176 } 177 178 /** 179 * utility to create pending keepalive fetch requests 180 * The pending request state is achieved by ensuring the server (trickle.py) does not 181 * immediately respond to the fetch requests. 182 * The response delay is set as a url parameter. 183 */ 184 185 function createPendingKeepAliveRequest(delay, remote = false) { 186 // trickle.py is a script that can make a delayed response to the client request 187 const trickleRemoteURL = get_host_info().HTTPS_REMOTE_ORIGIN + '/fetch/api/resources/trickle.py?count=1&ms='; 188 const trickleLocalURL = get_host_info().HTTP_ORIGIN + '/fetch/api/resources/trickle.py?count=1&ms='; 189 url = remote ? trickleRemoteURL : trickleLocalURL; 190 191 const body = '*'.repeat(10); 192 return fetch(url + delay, { keepalive: true, body, method: 'POST' }).then(res => { 193 return res.text(); 194 }).then(() => { 195 return new Promise(resolve => step_timeout(resolve, 1)); 196 }).catch((error) => { 197 return Promise.reject(error);; 198 }) 199 }