iframe-test.js (9064B)
1 // To use the functions below, be sure to include the following files in your 2 // test: 3 // - "/common/get-host-info.sub.js" to get the different origin values. 4 // - "common.js" to have the origins easily available. 5 // - "/common/dispatcher/dispatcher.js" for cross-origin messaging. 6 // - "/common/utils.js" for token(). 7 8 function getBaseExecutorPath(origin) { 9 return origin + '/common/dispatcher/executor.html'; 10 } 11 12 function getHeadersPipe(headers) { 13 const coop_header = headers.coop ? 14 `|header(Cross-Origin-Opener-Policy,${encodeURIComponent(headers.coop)})` : ''; 15 const coep_header = headers.coep ? 16 `|header(Cross-Origin-Embedder-Policy,${encodeURIComponent(headers.coep)})` : ''; 17 return coop_header + coep_header; 18 } 19 20 function getExecutorPath(uuid, origin, headers) { 21 return getBaseExecutorPath(origin) + 22 `?uuid=${uuid}` + 23 `&pipe=${getHeadersPipe(headers)}`; 24 } 25 26 function evaluate(target_token, script) { 27 const reply_token = token(); 28 send(target_token, `send('${reply_token}', ${script});`); 29 return receive(reply_token); 30 } 31 32 // Return true if an opened iframe can access |property| on a stored 33 // window.popup object without throwing an error. 34 function iframeCanAccessProperty(iframe_token, property) { 35 const reply_token = token(); 36 send(iframe_token, 37 `try { 38 const unused = window.popup['${property}']; 39 send('${reply_token}', 'true') 40 } catch (errors) { 41 send('${reply_token}', 'false') 42 }`); 43 return receive(reply_token); 44 } 45 46 // Returns the script necessary to open a popup, given the method in 47 // `popup_via`. Supported methods are 'window_open' that leverages 48 // window.open(), 'anchor' that creates an <a> HTML element and clicks on it, 49 // and 'form' that creates a form and submits it. 50 function popupOpeningScript(popup_via, popup_url, popup_origin, headers, 51 popup_token) { 52 if (popup_via === 'window_open') 53 return `window.popup = window.open('${popup_url}', '${popup_token}');`; 54 55 if (popup_via === 'anchor') { 56 return ` 57 const anchor = document.createElement('a'); 58 anchor.href = '${popup_url}'; 59 anchor.rel = "opener"; 60 anchor.target = '${popup_token}'; 61 anchor.innerText = "anchor"; 62 document.body.appendChild(anchor); 63 anchor.click(); 64 `; 65 } 66 67 if (popup_via === "form") { 68 return ` 69 const form = document.createElement("form"); 70 form.action = '${getBaseExecutorPath(popup_origin.origin)}'; 71 form.target = '${popup_token}'; 72 form.method = 'GET'; 73 const add_param = (name, value) => { 74 const input = document.createElement("input"); 75 input.name = name; 76 input.value = value; 77 form.appendChild(input); 78 }; 79 add_param("uuid", "${popup_token}"); 80 add_param("pipe", "${getHeadersPipe(headers)}"); 81 document.body.appendChild(form); 82 form.submit(); 83 `; 84 } 85 86 assert_not_reached('Unrecognized popup opening method.'); 87 } 88 89 function promise_test_parallel(promise, description) { 90 async_test(test => { 91 promise(test) 92 .then(() => test.done()) 93 .catch(test.step_func(error => { throw error; })); 94 }, description); 95 }; 96 97 // Verifies that a popup with origin `popup_origin` and headers `headers` has 98 // the expected `opener_state` after being opened from an iframe with origin 99 // `iframe_origin`. 100 function iframe_test(description, iframe_origin, popup_origin, headers, 101 expected_opener_state) { 102 for (const popup_via of ['window_open', 'anchor','form']) { 103 promise_test_parallel(async t => { 104 const iframe_token = token(); 105 const popup_token = token(); 106 const reply_token = token(); 107 108 const frame = document.createElement("iframe"); 109 const iframe_url = getExecutorPath( 110 iframe_token, 111 iframe_origin.origin, 112 {}); 113 114 frame.src = iframe_url; 115 document.body.append(frame); 116 117 send(iframe_token, `send('${reply_token}', 'Iframe loaded');`); 118 assert_equals(await receive(reply_token), 'Iframe loaded'); 119 120 const popup_url = getExecutorPath( 121 popup_token, 122 popup_origin.origin, 123 headers); 124 125 // We open popup and then ping it, it will respond after loading. 126 send(iframe_token, popupOpeningScript(popup_via, popup_url, popup_origin, 127 headers, popup_token)); 128 send(popup_token, `send('${reply_token}', 'Popup loaded');`); 129 assert_equals(await receive(reply_token), 'Popup loaded'); 130 131 // Make sure the popup and the iframe are removed once the test has run, 132 // keeping a clean state. 133 add_completion_callback(() => { 134 frame.remove(); 135 send(popup_token, `close()`); 136 }); 137 138 // Give some time for things to settle across processes etc. before 139 // proceeding with verifications. 140 await new Promise(resolve => { t.step_timeout(resolve, 500); }); 141 142 // Verify that the opener is in the state we expect it to be in. 143 switch (expected_opener_state) { 144 case 'preserved': { 145 assert_equals( 146 await evaluate(popup_token, 'opener != null'), "true", 147 'Popup has an opener?'); 148 assert_equals( 149 await evaluate(popup_token, `name === '${popup_token}'`), "true", 150 'Popup has a name?'); 151 152 // When the popup was created using window.open, we've kept a handle 153 // and we can do extra verifications. 154 if (popup_via === 'window_open') { 155 assert_equals( 156 await evaluate(iframe_token, 'popup != null'), "true", 157 'Popup handle is non-null in iframe?'); 158 assert_equals( 159 await evaluate(iframe_token, 'popup.closed'), "false", 160 'Popup appears closed from iframe?'); 161 assert_equals( 162 await iframeCanAccessProperty(iframe_token, "document"), 163 popup_origin === iframe_origin ? "true" : "false", 164 'Iframe has dom access to the popup?'); 165 assert_equals( 166 await iframeCanAccessProperty(iframe_token, "frames"), "true", 167 'Iframe has cross origin access to the popup?'); 168 } 169 break; 170 } 171 case 'restricted': { 172 assert_equals( 173 await evaluate(popup_token, 'opener != null'), "true", 174 'Popup has an opener?'); 175 assert_equals( 176 await evaluate(popup_token, `name === ''`), "true", 177 'Popup name is cleared?'); 178 179 // When the popup was created using window.open, we've kept a handle 180 // and we can do extra verifications. 181 if (popup_via === 'window_open') { 182 assert_equals( 183 await evaluate(iframe_token, 'popup != null'), "true", 184 'Popup handle is non-null in iframe?'); 185 assert_equals( 186 await evaluate(iframe_token, 'popup.closed'), "false", 187 'Popup appears closed from iframe?'); 188 assert_equals( 189 await iframeCanAccessProperty(iframe_token, "document"), "false", 190 'Iframe has dom access to the popup?'); 191 assert_equals( 192 await iframeCanAccessProperty(iframe_token, "frames"), "false", 193 'Iframe has cross origin access to the popup?'); 194 assert_equals( 195 await iframeCanAccessProperty(iframe_token, "closed"), "true", 196 'Iframe has limited cross origin access to the popup?'); 197 } 198 break; 199 } 200 case 'severed': { 201 assert_equals(await evaluate(popup_token, 'opener != null'), "false", 202 'Popup has an opener?'); 203 assert_equals( 204 await evaluate(popup_token, `name === ''`), "true", 205 'Popup name is cleared?'); 206 207 // When the popup was created using window.open, we've kept a handle 208 // and we can do extra verifications. 209 if (popup_via === 'window_open') { 210 assert_equals( 211 await evaluate(iframe_token, 'popup != null'), "true", 212 'Popup handle is non-null in iframe?'); 213 assert_equals( 214 await evaluate(iframe_token, 'popup.closed'), "true", 215 'Popup appears closed from iframe?'); 216 } 217 break; 218 } 219 case 'noopener': { 220 assert_equals(await evaluate(popup_token, 'opener != null'), "false", 221 'Popup has an opener?'); 222 assert_equals( 223 await evaluate(popup_token, `name === ''`), "true", 224 'Popup name is cleared?'); 225 226 // When the popup was created using window.open, we've kept a handle 227 // and we can do extra verifications. 228 if (popup_via === 'window_open') { 229 assert_equals( 230 await evaluate(iframe_token, 'popup == null'), "true", 231 'Popup handle is null in iframe?'); 232 } 233 break; 234 } 235 default: 236 assert_not_reached('Unrecognized opener state: ' + 237 expected_opener_state); 238 } 239 }, `${description} with ${popup_via}`); 240 } 241 }