reporting-to-endpoint.https.html (7641B)
1 <!doctype html> 2 <html> 3 <meta name="timeout" content="long"> 4 <body> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <script src="/common/utils.js"></script> 8 <script src="/common/get-host-info.sub.js"></script> 9 <script> 10 // This file consists of tests for COEP reporting. The tests make COEP 11 // violations and see whether reports are sent to the network as specified. 12 // We only have basic tests in this file - one for each kind of reports, 13 // because we can also test the reporting functionality with ReportingObserver, 14 // and that way is faster, easier to debug, and less flaky. 15 // 16 // For more detailed tests and tests with workers, see tests in other files 17 // such as 18 // - reporting-navigation.https.html 19 // - reporting-subresource-corp.https.html 20 // - cache-storage-reporting*.https.html 21 // . 22 23 const { REMOTE_ORIGIN } = get_host_info(); 24 const BASE = new URL("resources", location).pathname 25 const FRAME_URL = `resources/reporting-empty-frame.html` + 26 `?pipe=header(cross-origin-embedder-policy,require-corp;report-to="endpoint")` + 27 `|header(cross-origin-embedder-policy-report-only,require-corp;report-to="report-only-endpoint")`; 28 const WORKER_URL = `resources/shared-worker.js` + 29 '?pipe=header(cross-origin-embedder-policy,require-corp;report-to="endpoint")' + 30 `|header(cross-origin-embedder-policy-report-only,require-corp;report-to="report-only-endpoint")`; 31 const REPORT_UUID = "4d8b6d86-c9a8-47c1-871b-111169a8f79c"; 32 const REPORT_ONLY_UUID = "5d7c1e33-ef88-43c2-9ca3-c67ff300b8c2"; 33 34 function wait(ms) { 35 return new Promise(resolve => step_timeout(resolve, ms)); 36 } 37 38 async function fetchReports(endpoint) { 39 const res = await fetch(`resources/report.py?key=${endpoint}`, {cache: 'no-store'}); 40 if (res.status == 200) { 41 return await res.json(); 42 } 43 return []; 44 } 45 46 async function checkCorpReportExistence(endpoint, blockedUrl, contextUrl, destination, disposition) { 47 blockedUrl = new URL(blockedUrl, location).href; 48 contextUrl = new URL(contextUrl, location).href; 49 50 const timeout = 3000; 51 const retryDelay = 200; 52 for (let i = 0; i * retryDelay < timeout; i++) { 53 const reports = await fetchReports(endpoint); 54 for (const report of reports) { 55 if (report.type !== 'coep' || report.url !== contextUrl || 56 report.body.type !== 'corp') { 57 continue; 58 } 59 if (report.body.blockedURL === blockedUrl && 60 report.body.disposition === disposition) { 61 assert_equals(report.body.destination, destination); 62 return; 63 } 64 } 65 await wait(retryDelay); 66 } 67 assert_unreached(`A report whose blockedURL is ${blockedUrl.split("?")[0]} and url is ${contextUrl} is not found.`); 68 } 69 70 async function checkNavigationReportExistence(endpoint, blockedUrl, contextUrl, disposition) { 71 blockedUrl = new URL(blockedUrl, location).href; 72 contextUrl = new URL(contextUrl, location).href; 73 const timeout = 3000; 74 const retryDelay = 200; 75 for (let i = 0; i * retryDelay < timeout; i++) { 76 const reports = await fetchReports(endpoint); 77 for (const report of reports) { 78 if (report.type !== 'coep' || report.url !== contextUrl || 79 report.body.type !== 'navigation') { 80 continue; 81 } 82 if (report.body.blockedURL === blockedUrl && 83 report.body.disposition === disposition) { 84 return; 85 } 86 } 87 await wait(retryDelay); 88 } 89 assert_unreached(`A report whose blockedURL is ${blockedUrl.split("?")[0]} and url is ${contextUrl} is not found.`); 90 } 91 92 promise_test(async t => { 93 const iframe = document.createElement('iframe'); 94 t.add_cleanup(() => iframe.remove()); 95 96 iframe.src = FRAME_URL 97 document.body.appendChild(iframe); 98 await new Promise(resolve => { 99 iframe.addEventListener('load', resolve, {once: true}); 100 }); 101 102 const url = `${REMOTE_ORIGIN}/common/text-plain.txt?${token()}`; 103 const init = { mode: 'no-cors', cache: 'no-store' }; 104 // The response comes from cross-origin, and doesn't have a CORP 105 // header, so it is blocked. 106 iframe.contentWindow.fetch(url, init).catch(() => {}); 107 108 await checkCorpReportExistence(REPORT_UUID, url, iframe.src, '', 'enforce'); 109 await checkCorpReportExistence( 110 REPORT_ONLY_UUID, url, iframe.src, '', 'reporting'); 111 }, 'subresource CORP'); 112 113 promise_test(async t => { 114 const iframe = document.createElement('iframe'); 115 t.add_cleanup(() => iframe.remove()); 116 117 iframe.src = FRAME_URL 118 document.body.appendChild(iframe); 119 await new Promise(resolve => { 120 iframe.addEventListener('load', resolve, {once: true}); 121 }); 122 123 const w = iframe.contentWindow; 124 125 function attachFrame(url) { 126 const frame = w.document.createElement('iframe'); 127 frame.src = url; 128 w.document.body.appendChild(frame); 129 } 130 131 const url = `${REMOTE_ORIGIN}/common/blank.html?${token()}`; 132 // The nested frame comes from cross-origin and doesn't have a CORP 133 // header, so it is blocked. 134 attachFrame(url); 135 136 await checkCorpReportExistence( 137 REPORT_UUID, url, iframe.src, 'iframe', 'enforce'); 138 await checkCorpReportExistence( 139 REPORT_ONLY_UUID, url, iframe.src, 'iframe', 'reporting'); 140 }, 'navigation CORP'); 141 142 promise_test(async (t) => { 143 const iframe = document.createElement('iframe'); 144 t.add_cleanup(() => iframe.remove()); 145 146 iframe.src = FRAME_URL; 147 const targetUrl = `/common/blank.html?${token()}`; 148 iframe.addEventListener('load', t.step_func(() => { 149 const nested = iframe.contentDocument.createElement('iframe'); 150 nested.src = targetUrl; 151 // |nested| doesn't have COEP whereas |iframe| has, so it is blocked. 152 iframe.contentDocument.body.appendChild(nested); 153 }), {once: true}); 154 155 document.body.appendChild(iframe); 156 157 await checkNavigationReportExistence( 158 REPORT_UUID, targetUrl, iframe.src, 'enforce'); 159 await checkNavigationReportExistence( 160 REPORT_ONLY_UUID, targetUrl, iframe.src, 'reporting'); 161 }, 'COEP violation on nested frame navigation'); 162 163 promise_test(async (t) => { 164 const iframe = document.createElement('iframe'); 165 t.add_cleanup(() => iframe.remove()); 166 167 iframe.src = 'resources/reporting-empty-frame-multiple-headers.html.asis'; 168 const targetUrl = `/common/blank.html?${token()}`; 169 170 iframe.addEventListener('load', t.step_func(() => { 171 const nested = iframe.contentDocument.createElement('iframe'); 172 nested.src = targetUrl; 173 // |nested| doesn't have COEP whereas |iframe| has, so it is blocked. 174 iframe.contentDocument.body.appendChild(nested); 175 }), {once: true}); 176 177 document.body.appendChild(iframe); 178 179 await checkNavigationReportExistence( 180 REPORT_UUID, targetUrl, iframe.src, 'enforce'); 181 await checkNavigationReportExistence( 182 REPORT_ONLY_UUID, targetUrl, iframe.src, 'reporting'); 183 184 }, 'Two COEP headers, split inside report-to value'); 185 186 // Shared worker do not support observer currently, so add test for endpoint 187 // here. 188 promise_test(async (t) => { 189 const iframe = document.createElement('iframe'); 190 t.add_cleanup(() => iframe.remove()); 191 192 iframe.src = FRAME_URL; 193 const targetUrl = `${REMOTE_ORIGIN}/common/blank.html?${token()}`; 194 document.body.appendChild(iframe); 195 196 const worker = new iframe.contentWindow.SharedWorker(WORKER_URL); 197 worker.port.start(); 198 const script = 199 `fetch('${targetUrl}', {mode: 'no-cors', cache: 'no-store'}).catch(e => {});`; 200 worker.addEventListener('error', t.unreached_func('Worker.onerror')); 201 worker.port.postMessage(script); 202 203 await checkCorpReportExistence( 204 REPORT_UUID, targetUrl, WORKER_URL, 'iframe', 'enforce'); 205 await checkCorpReportExistence( 206 REPORT_ONLY_UUID, targetUrl, WORKER_URL, 'iframe', 'reporting'); 207 }, 'Shared worker fetch'); 208 209 </script>