reporting-navigation.https.html (6461B)
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/get-host-info.sub.js"></script> 8 <script src="./credentialless/resources/common.js"></script> 9 <script> 10 const {ORIGIN, REMOTE_ORIGIN} = get_host_info(); 11 const COEP = '|header(cross-origin-embedder-policy,require-corp)'; 12 const COEP_RO = 13 '|header(cross-origin-embedder-policy-report-only,require-corp)'; 14 const CORP_CROSS_ORIGIN = 15 '|header(cross-origin-resource-policy,cross-origin)'; 16 const CSP_FRAME_ANCESTORS_NONE = 17 '|header(content-security-policy,frame-ancestors \'none\')'; 18 const XFRAMEOPTIONS_DENY = 19 '|header(x-frame-options,deny)'; 20 const FRAME_URL = `${ORIGIN}/common/blank.html?pipe=`; 21 const REMOTE_FRAME_URL = `${REMOTE_ORIGIN}/common/blank.html?pipe=`; 22 23 function checkCorpReport(report, contextUrl, blockedUrl, disposition) { 24 assert_equals(report.type, 'coep'); 25 assert_equals(report.url, contextUrl); 26 assert_equals(report.body.type, 'corp'); 27 assert_equals(report.body.blockedURL, blockedUrl); 28 assert_equals(report.body.disposition, disposition); 29 assert_equals(report.body.destination, 'iframe'); 30 } 31 32 function checkCoepMismatchReport(report, contextUrl, blockedUrl, disposition) { 33 assert_equals(report.type, 'coep'); 34 assert_equals(report.url, contextUrl); 35 assert_equals(report.body.type, 'navigation'); 36 assert_equals(report.body.blockedURL, blockedUrl); 37 assert_equals(report.body.disposition, disposition); 38 } 39 40 function loadFrame(document, url) { 41 return new Promise((resolve, reject) => { 42 const frame = document.createElement('iframe'); 43 frame.src = url; 44 frame.onload = () => resolve(frame); 45 frame.onerror = reject; 46 document.body.appendChild(frame); 47 }); 48 } 49 50 // |parentSuffix| is a suffix for the parent frame URL. 51 // When |withEmptyFrame| is true, this function creates an empty frame 52 // between the parent and target frames. 53 // |targetUrl| is a URL for the target frame. 54 async function loadFrames(test, parentSuffix, withEmptyFrame, targetUrl) { 55 const frame = await loadFrame(document, FRAME_URL + parentSuffix); 56 test.add_cleanup(() => frame.remove()); 57 let parent; 58 if (withEmptyFrame) { 59 parent = frame.contentDocument.createElement('iframe'); 60 frame.contentDocument.body.appendChild(parent); 61 } else { 62 parent = frame; 63 } 64 // Here we don't need "await". This loading may or may not succeed, and 65 // we're not interested in the result. 66 loadFrame(parent.contentDocument, targetUrl); 67 68 return parent; 69 } 70 71 async function observeReports(global, expected_count) { 72 const reports = []; 73 const receivedEveryReports = new Promise(resolve => { 74 if (expected_count == 0) 75 resolve(); 76 77 const observer = new global.ReportingObserver((rs) => { 78 for (const r of rs) { 79 reports.push(r.toJSON()); 80 } 81 if (expected_count <= reports.length) 82 resolve(); 83 }); 84 observer.observe(); 85 86 }); 87 88 // Wait 5000 ms more to catch additionnal unexpected reports. 89 await receivedEveryReports; 90 await new Promise(r => step_timeout(r, 5000)); 91 return reports; 92 } 93 94 // CASES is a list of test case. Each test case consists of: 95 // parent: the suffix of the URL of the parent frame. 96 // target: the suffix of the URL of the target frame. 97 // reports: the expectation of reports to be made. Each report is one of: 98 // 'CORP': CORP violation 99 // 'CORP-RO' CORP violation (report only) 100 // 'NAV': COEP mismatch between the frames. 101 // 'NAV-RO': COEP mismatch between the frames (report only). 102 const CASES = [ 103 { parent: '', target: '', reports: [] }, 104 { parent: '', target: COEP, reports: [] }, 105 { parent: COEP, target: COEP, reports: ['CORP'] }, 106 { parent: COEP, target: '', reports: ['CORP'] }, 107 108 { parent: '', target: CORP_CROSS_ORIGIN, reports: [] }, 109 { parent: COEP, target: CORP_CROSS_ORIGIN, reports: ['NAV'] }, 110 111 { parent: '', target: COEP + CORP_CROSS_ORIGIN, reports: [] }, 112 { parent: COEP, target: COEP + CORP_CROSS_ORIGIN, reports: [] }, 113 114 { parent: COEP_RO, target: COEP, reports: ['CORP-RO'] }, 115 { parent: COEP_RO, target: '', reports: ['CORP-RO', 'NAV-RO'] }, 116 { parent: COEP_RO, target: CORP_CROSS_ORIGIN, reports: ['NAV-RO'] }, 117 { parent: COEP_RO, target: COEP + CORP_CROSS_ORIGIN, reports: [] }, 118 119 { parent: COEP, target: COEP_RO + CORP_CROSS_ORIGIN, reports: ['NAV'] }, 120 121 // Test ordering of CSP frame-ancestors, COEP, and X-Frame-Options 122 { parent: COEP, target: CORP_CROSS_ORIGIN + CSP_FRAME_ANCESTORS_NONE, reports: [] }, 123 { parent: COEP, target: CORP_CROSS_ORIGIN + XFRAMEOPTIONS_DENY, reports: ['NAV'] }, 124 ]; 125 126 for (const testcase of CASES) { 127 for (const withEmptyFrame of [false, true]) { 128 function desc(s) { 129 return s === '' ? '(none)' : s; 130 } 131 // These tests are very slow, so they must be run in parallel using 132 // async_test. 133 async_test(t => { 134 const targetUrl = REMOTE_FRAME_URL + testcase.target; 135 loadFrames(t, testcase.parent, withEmptyFrame, targetUrl) 136 .then(t.step_func(parent => { 137 const contextUrl = parent.src ? parent.src : 'about:blank'; 138 observeReports(parent.contentWindow, testcase.reports.length) 139 .then(t.step_func(reports => { 140 assert_equals(reports.length, testcase.reports.length); 141 for (let i = 0; i < reports.length; i += 1) { 142 const report = reports[i]; 143 switch (testcase.reports[i]) { 144 case 'CORP': 145 checkCorpReport(report, contextUrl, targetUrl, 'enforce'); 146 break; 147 case 'CORP-RO': 148 checkCorpReport(report, contextUrl, targetUrl, 'reporting'); 149 break; 150 case 'NAV': 151 checkCoepMismatchReport(report, contextUrl, targetUrl, 'enforce'); 152 break; 153 case 'NAV-RO': 154 checkCoepMismatchReport(report, contextUrl, targetUrl, 'reporting'); 155 break; 156 default: 157 assert_unreached( 158 'Unexpected report expeaction: ' + testcase.reports[i]); 159 } 160 } 161 t.done(); 162 })).catch(t.step_func(e => { throw e; })); 163 })).catch(t.step_func(e => { throw e; })); 164 }, `parent: ${desc(testcase.parent)}, target: ${desc(testcase.target)}, ` + 165 `with empty frame: ${withEmptyFrame}`); 166 } 167 } 168 169 </script> 170 </body></html>