javascript-url.https.html (8260B)
1 <!DOCTYPE html> 2 <title>Cross-Origin-Opener-Policy and a "javascript:" URL popup</title> 3 <meta charset="utf-8"> 4 <meta name="timeout" content="long"> 5 <meta name="variant" content="?1-2"> 6 <meta name="variant" content="?3-4"> 7 <meta name="variant" content="?5-6"> 8 <meta name="variant" content="?7-8"> 9 <meta name="variant" content="?9-10"> 10 <meta name="variant" content="?11-12"> 11 <meta name="variant" content="?13-14"> 12 <meta name="variant" content="?15-16"> 13 <meta name="variant" content="?17-last"> 14 <script src="/resources/testharness.js"></script> 15 <script src="/resources/testharnessreport.js"></script> 16 <script src="/common/dispatcher/dispatcher.js"></script> 17 <script src="/common/get-host-info.sub.js"></script> 18 <script src="/common/subset-tests.js"></script> 19 <script src="/common/utils.js"></script> 20 <script src="resources/common.js"></script> 21 22 <p>According to HTML's navigate algorithm, requests to <code>javascript:</code> 23 URLs should inherit the cross-origin opener policy of the active document. To 24 observe this, each subtest uses the following procedure.</p> 25 26 <ol> 27 <li>create popup with a given COOP (the <code>parentCOOP</code>)</li> 28 <li>navigate the popup to a <code>javascript:</code> URL (the new document is 29 expected to inherit the <code>parentCOOP</code>)</li> 30 <li>from the popup, create a second popup window with a given COOP (the 31 <code>childCOOP</code>)</li> 32 </ol> 33 34 <p>Both popup windows inspect state and report back to the test.</p> 35 36 <pre> 37 .---- test ----. 38 | open(https:) | 39 | parentCOOP | .----- subject -------. 40 | '---------> | --------. | 41 | | | v | 42 | | | assign(javascript:) | 43 | | | (COOP under test) | 44 | | | | | 45 | | | open(https:) | 46 | | | childCOOP | .- child -. 47 | | | '--------------> | | 48 | | '---------------------' '---------' 49 | | | | 50 | validate | <--status---+---------------------' 51 '--------------' 52 </pre> 53 54 <script> 55 'use strict'; 56 57 function getExecutorPath(uuid, origin, coopHeader) { 58 const executorPath = '/common/dispatcher/executor.html?'; 59 const coopHeaderPipe = 60 `|header(Cross-Origin-Opener-Policy,${encodeURIComponent(coopHeader)})`; 61 return origin + executorPath + `uuid=${uuid}` + '&pipe=' + coopHeaderPipe; 62 } 63 64 function assert_isolated(results) { 65 assert_equals(results.childName, '', 'child name'); 66 assert_false(results.childOpener, 'child opener'); 67 // The test subject's reference to the "child" window must report "closed" 68 // when COOP enforces isolation because the document initially created during 69 // the window open steps must be discarded when a new document object is 70 // created at the end of the navigation. 71 assert_true(results.childClosed, 'child closed'); 72 } 73 74 function assert_not_isolated(results, expectedName) { 75 assert_equals(results.childName, expectedName, 'child name'); 76 assert_true(results.childOpener, 'child opener'); 77 assert_false(results.childClosed, 'child closed'); 78 } 79 80 async function javascript_url_test(parentCOOP, childCOOP, origin, resultsVerification) { 81 promise_test(async t => { 82 const parentToken = token(); 83 const childToken = token(); 84 const responseToken = token(); 85 86 const parentURL = getExecutorPath( 87 parentToken, 88 SAME_ORIGIN.origin, 89 parentCOOP); 90 const childURL = getExecutorPath( 91 childToken, 92 origin.origin, 93 childCOOP); 94 95 // Open a first popup, referred to as the parent popup, and wait for it to 96 // load. 97 window.open(parentURL); 98 send(parentToken, `send('${responseToken}', 'Done loading');`); 99 assert_equals(await receive(responseToken), 'Done loading'); 100 101 // Make sure the parent popup is removed once the test has run, keeping a 102 // clean state. 103 add_completion_callback(() => { 104 send(parentToken, 'close'); 105 }); 106 107 // Navigate the popup to the javascript URL. It should inherit the current 108 // document's COOP. Because we're navigating to a page that is not an 109 // executor, we lose access to easy messaging, making things a bit more 110 // complicated. We use a predetermined scenario of communication that 111 // enables us to retrieve whether the child popup appears closed from the 112 // parent popup. 113 // 114 // Notes: 115 // - Splitting the script tag prevents HTML parsing to kick in. 116 // - The innermost double quotes need a triple backslash, because it goes 117 // through two rounds of consuming escape characters (\\\" -> \" -> "). 118 // - The javascript URL does not accept \n characters so we need to use 119 // a new template literal for each line. 120 send(parentToken, 121 `location.assign("javascript:'` + 122 // Include dispatcher.js to have access to send() and receive(). 123 `<script src=\\\"/common/dispatcher/dispatcher.js\\\"></scr` + `ipt>` + 124 `<script> (async () => {` + 125 126 // Open the child popup and keep a handle to it. 127 `const w = open(\\\"${childURL}\\\", \\\"${childToken}\\\");` + 128 129 // We wait for the main frame to query the w.closed property. 130 `await receive(\\\"${parentToken}\\\");` + 131 `send(\\\"${responseToken}\\\", w.closed);` + 132 133 // Finally we wait for the cleanup indicating that this popup can be 134 // closed. 135 `await receive(\\\"${parentToken}\\\");` + 136 `close();` + 137 `})()</scr` + `ipt>'");` 138 ); 139 140 // Make sure the javascript navigation ran, and the child popup was created. 141 send(childToken, `send('${responseToken}', 'Done loading');`); 142 assert_equals(await receive(responseToken), 'Done loading'); 143 144 // Make sure the child popup is removed once the test has run, keeping a 145 // clean state. 146 add_completion_callback(() => { 147 send(childToken, `close()`); 148 }); 149 150 // Give some time for things to settle across processes etc. before 151 // proceeding with verifications. 152 await new Promise(resolve => { t.step_timeout(resolve, 500); }); 153 154 // Gather information about the child popup and verify that they match what 155 // we expect. 156 const results = {}; 157 send(parentToken, 'query'); 158 results.childClosed = await receive(responseToken) === 'true'; 159 160 send(childToken, `send('${responseToken}', opener != null);`); 161 results.childOpener = await receive(responseToken) === 'true'; 162 163 send(childToken, `send('${responseToken}', name);`); 164 results.childName = await receive(responseToken); 165 166 resultsVerification(results, childToken); 167 }, `navigation: ${origin.name}; ` + `parentCOOP: ${parentCOOP}; ` + 168 `childCOOP: ${childCOOP}`); 169 } 170 171 const tests = [ 172 ['unsafe-none', 'unsafe-none', SAME_ORIGIN, assert_not_isolated], 173 ['unsafe-none', 'unsafe-none', SAME_SITE, assert_not_isolated], 174 ['unsafe-none', 'same-origin-allow-popups', SAME_ORIGIN, assert_isolated], 175 ['unsafe-none', 'same-origin-allow-popups', SAME_SITE, assert_isolated], 176 ['unsafe-none', 'same-origin', SAME_ORIGIN, assert_isolated], 177 ['unsafe-none', 'same-origin', SAME_SITE, assert_isolated], 178 ['same-origin-allow-popups', 'unsafe-none', SAME_ORIGIN, assert_not_isolated], 179 ['same-origin-allow-popups', 'unsafe-none', SAME_SITE, assert_not_isolated], 180 ['same-origin-allow-popups', 'same-origin-allow-popups', SAME_ORIGIN, assert_not_isolated], 181 ['same-origin-allow-popups', 'same-origin-allow-popups', SAME_SITE, assert_isolated], 182 ['same-origin-allow-popups', 'same-origin', SAME_ORIGIN, assert_isolated], 183 ['same-origin-allow-popups', 'same-origin', SAME_SITE, assert_isolated], 184 ['same-origin', 'unsafe-none', SAME_ORIGIN, assert_isolated], 185 ['same-origin', 'unsafe-none', SAME_SITE, assert_isolated], 186 ['same-origin', 'same-origin-allow-popups', SAME_ORIGIN, assert_isolated], 187 ['same-origin', 'same-origin-allow-popups', SAME_SITE, assert_isolated], 188 ['same-origin', 'same-origin', SAME_ORIGIN, assert_not_isolated], 189 ['same-origin', 'same-origin', SAME_SITE, assert_isolated], 190 ].forEach(([parentCOOP, childCOOP, origin, expectation]) => { 191 subsetTest( 192 javascript_url_test, 193 parentCOOP, 194 childCOOP, 195 origin, 196 expectation); 197 }); 198 199 </script>