early-hints-helpers.sub.js (7094B)
1 "use strict"; 2 3 const SAME_ORIGIN = "https://{{host}}:{{ports[h2][0]}}"; 4 const CROSS_ORIGIN = "https://{{hosts[alt][www]}}:{{ports[h2][0]}}"; 5 6 const RESOURCES_PATH = "/loading/early-hints/resources"; 7 const SAME_ORIGIN_RESOURCES_URL = SAME_ORIGIN + RESOURCES_PATH; 8 const CROSS_ORIGIN_RESOURCES_URL = CROSS_ORIGIN + RESOURCES_PATH; 9 10 /** 11 * Navigate to a test page with an Early Hints response. 12 * 13 * @typedef {Object} Preload 14 * @property {string} url - A URL to preload. Note: This is relative to the 15 * `test_url` parameter of `navigateToTestWithEarlyHints()`. 16 * @property {string} as_attr - `as` attribute of this preload. 17 * @property {string} [crossorigin_attr] - `crossorigin` attribute of this 18 * preload. 19 * @property {string} [fetchpriority_attr] - `fetchpriority` attribute of this 20 * preload. 21 * 22 * @param {string} test_url - URL of a test after the Early Hints response. 23 * @param {Array<Preload>} preloads - Preloads included in the Early Hints response. 24 * @param {bool} exclude_preloads_from_ok_response - Whether to exclude the preloads from the 200 OK reponse. 25 */ 26 function navigateToTestWithEarlyHints(test_url, preloads, exclude_preloads_from_ok_response) { 27 const params = new URLSearchParams(); 28 params.set("test_url", test_url); 29 params.set("exclude_preloads_from_ok_response", 30 (!!exclude_preloads_from_ok_response).toString()); 31 for (const preload of preloads) { 32 params.append("preloads", JSON.stringify(preload)); 33 } 34 const url = RESOURCES_PATH +"/early-hints-test-loader.h2.py?" + params.toString(); 35 window.location.replace(new URL(url, window.location)); 36 } 37 38 /** 39 * Parses the query string of the current window location and returns preloads 40 * in the Early Hints response sent via `navigateToTestWithEarlyHints()`. 41 * 42 * @returns {Array<Preload>} 43 */ 44 function getPreloadsFromSearchParams() { 45 const params = new URLSearchParams(window.location.search); 46 const encoded_preloads = params.getAll("preloads"); 47 const preloads = []; 48 for (const encoded of encoded_preloads) { 49 preloads.push(JSON.parse(encoded)); 50 } 51 return preloads; 52 } 53 54 /** 55 * Fetches a script or an image. 56 * 57 * @param {string} element - "script" or "img". 58 * @param {string} url - URL of the resource. 59 */ 60 async function fetchResource(element, url) { 61 return new Promise((resolve, reject) => { 62 const el = document.createElement(element); 63 el.src = url; 64 el.onload = resolve; 65 el.onerror = _ => reject(new Error("Failed to fetch resource: " + url)); 66 document.body.appendChild(el); 67 }); 68 } 69 70 /** 71 * Fetches a script. 72 * 73 * @param {string} url 74 */ 75 async function fetchScript(url) { 76 return fetchResource("script", url); 77 } 78 79 /** 80 * Fetches an image. 81 * 82 * @param {string} url 83 */ 84 async function fetchImage(url) { 85 return fetchResource("img", url); 86 } 87 88 /** 89 * Returns true when the resource is preloaded via Early Hints. 90 * 91 * @param {string} url 92 * @returns {boolean} 93 */ 94 function isPreloadedByEarlyHints(url) { 95 const entries = performance.getEntriesByName(url); 96 if (entries.length === 0) { 97 return false; 98 } 99 assert_equals(entries.length, 1); 100 return entries[0].initiatorType === "early-hints"; 101 } 102 103 /** 104 * Navigate to the referrer policy test page. 105 * 106 * @param {string} referrer_policy - A value of Referrer-Policy to test. 107 */ 108 function testReferrerPolicy(referrer_policy) { 109 const params = new URLSearchParams(); 110 params.set("referrer-policy", referrer_policy); 111 const same_origin_preload_url = SAME_ORIGIN_RESOURCES_URL + "/fetch-and-record-js.h2.py?id=" + token(); 112 params.set("same-origin-preload-url", same_origin_preload_url); 113 const cross_origin_preload_url = CROSS_ORIGIN_RESOURCES_URL + "/fetch-and-record-js.h2.py?id=" + token(); 114 params.set("cross-origin-preload-url", cross_origin_preload_url); 115 116 const path = "resources/referrer-policy-test-loader.h2.py?" + params.toString(); 117 const url = new URL(path, window.location); 118 window.location.replace(url); 119 } 120 121 /** 122 * Navigate to the content security policy basic test. The test page sends an 123 * Early Hints response with a cross origin resource preload. CSP headers are 124 * configured based on the given policies. A policy should be one of the 125 * followings: 126 * "absent" - Do not send Content-Security-Policy header 127 * "allowed" - Set Content-Security-Policy to allow the cross origin preload 128 * "disallowed" - Set Content-Security-Policy to disallow the cross origin preload 129 * 130 * @param {string} early_hints_policy - The policy for the Early Hints response 131 * @param {string} final_policy - The policy for the final response 132 */ 133 function navigateToContentSecurityPolicyBasicTest( 134 early_hints_policy, final_policy) { 135 const params = new URLSearchParams(); 136 params.set("resource-origin", CROSS_ORIGIN); 137 params.set("resource-url", 138 CROSS_ORIGIN_RESOURCES_URL + "/empty.js?" + token()); 139 params.set("early-hints-policy", early_hints_policy); 140 params.set("final-policy", final_policy); 141 142 const url = "resources/csp-basic-loader.h2.py?" + params.toString(); 143 window.location.replace(new URL(url, window.location)); 144 } 145 146 /** 147 * Navigate to a test page which sends an Early Hints containing a cross origin 148 * preload link with/without Content-Security-Policy header. The CSP header is 149 * configured based on the given policy. The test page disallows the preload 150 * while the preload is in-flight. The policy should be one of the followings: 151 * "absent" - Do not send Content-Security-Policy header 152 * "allowed" - Set Content-Security-Policy to allow the cross origin preload 153 * 154 * @param {string} early_hints_policy 155 */ 156 function navigateToContentSecurityPolicyDocumentDisallowTest(early_hints_policy) { 157 const resource_id = token(); 158 const params = new URLSearchParams(); 159 params.set("resource-origin", CROSS_ORIGIN); 160 params.set("resource-url", 161 CROSS_ORIGIN_RESOURCES_URL + "/delayed-js.h2.py?id=" + resource_id); 162 params.set("resume-url", 163 CROSS_ORIGIN_RESOURCES_URL + "/resume-delayed-js.h2.py?id=" + resource_id); 164 params.set("early-hints-policy", early_hints_policy); 165 166 const url = "resources/csp-document-disallow-loader.h2.py?" + params.toString(); 167 window.location.replace(new URL(url, window.location)); 168 } 169 170 /** 171 * Navigate to a test page which sends different Cross-Origin-Embedder-Policy 172 * values in an Early Hints response and the final response. 173 * 174 * @param {string} early_hints_policy - The policy for the Early Hints response 175 * @param {string} final_policy - The policy for the final response 176 */ 177 function navigateToCrossOriginEmbedderPolicyMismatchTest( 178 early_hints_policy, final_policy) { 179 const params = new URLSearchParams(); 180 params.set("resource-url", 181 CROSS_ORIGIN_RESOURCES_URL + "/empty-corp-absent.js?" + token()); 182 params.set("early-hints-policy", early_hints_policy); 183 params.set("final-policy", final_policy); 184 185 const url = "resources/coep-mismatch.h2.py?" + params.toString(); 186 window.location.replace(new URL(url, window.location)); 187 }