helpers.js (11263B)
1 'use strict'; 2 3 function processQueryParams() { 4 const url = new URL(window.location); 5 const queryParams = url.searchParams; 6 return { 7 topLevelDocument: window === window.top, 8 testPrefix: queryParams.get("testCase") || "top-level-context", 9 }; 10 } 11 12 // Create an iframe element, set it up using `setUpFrame`, and optionally fetch 13 // tests in it. Returns the created frame, after it has loaded. 14 async function CreateFrameHelper(setUpFrame, fetchTests) { 15 const frame = document.createElement('iframe'); 16 const promise = new Promise((resolve, reject) => { 17 frame.onload = () => resolve(frame); 18 frame.onerror = reject; 19 }); 20 21 setUpFrame(frame); 22 23 if (fetchTests) { 24 await fetch_tests_from_window(frame.contentWindow); 25 } 26 return promise; 27 } 28 29 // Create an iframe element with content loaded from `sourceURL`, append it to 30 // the document, and optionally fetch tests. Returns the loaded frame, once 31 // ready. 32 function CreateFrame( 33 sourceURL, fetchTests = false, frameSandboxAttribute = undefined, frameAllowAttribute = undefined) { 34 return CreateFrameHelper((frame) => { 35 if (frameSandboxAttribute !== undefined) { 36 frame.sandbox = frameSandboxAttribute; 37 } 38 if (frameAllowAttribute !== undefined) { 39 frame.setAttribute("allow", frameAllowAttribute); 40 } 41 42 frame.src = sourceURL; 43 document.body.appendChild(frame); 44 }, fetchTests); 45 } 46 47 // Create a new iframe with content loaded from `sourceURL`, and fetches tests. 48 // Returns the loaded frame, once ready. 49 function RunTestsInIFrame(sourceURL, frameSandboxAttribute = undefined) { 50 return CreateFrame(sourceURL, true, frameSandboxAttribute); 51 } 52 53 function RunTestsInNestedIFrame(sourceURL) { 54 return CreateFrameHelper((frame) => { 55 document.body.appendChild(frame); 56 frame.contentDocument.write(` 57 <script src="/resources/testharness.js"></script> 58 <script src="helpers.js"></script> 59 <body> 60 <script> 61 RunTestsInIFrame("${sourceURL}"); 62 </script> 63 `); 64 frame.contentDocument.close(); 65 }, true); 66 } 67 68 function CreateDetachedFrame() { 69 const frame = document.createElement('iframe'); 70 document.body.append(frame); 71 const inner_doc = frame.contentDocument; 72 frame.remove(); 73 return inner_doc; 74 } 75 76 function CreateDocumentViaDOMParser() { 77 const parser = new DOMParser(); 78 const doc = parser.parseFromString('<html></html>', 'text/html'); 79 return doc; 80 } 81 82 function RunCallbackWithGesture(callback) { 83 return test_driver.bless('run callback with user gesture', callback); 84 } 85 86 // Sends a message to the given target window and returns a promise that 87 // resolves when a reply was sent. 88 function PostMessageAndAwaitReply(message, targetWindow) { 89 const timestamp = window.performance.now(); 90 const reply = ReplyPromise(timestamp); 91 targetWindow.postMessage({timestamp, ...message}, "*"); 92 return reply; 93 } 94 95 // Returns a promise that resolves when the next "reply" is received via 96 // postMessage. Takes a "timestamp" argument to validate that the received 97 // message belongs to its original counterpart. 98 function ReplyPromise(timestamp) { 99 return new Promise((resolve) => { 100 const listener = (event) => { 101 if (event.data.timestamp == timestamp) { 102 window.removeEventListener("message", listener); 103 resolve(event.data.data); 104 } 105 }; 106 window.addEventListener("message", listener); 107 }); 108 } 109 110 // Returns a promise that resolves when the given frame fires its load event. 111 function LoadPromise(frame) { 112 return new Promise((resolve) => { 113 frame.addEventListener("load", (event) => { 114 resolve(); 115 }, { once: true }); 116 }); 117 } 118 119 // Writes cookies via document.cookie in the given frame. 120 function SetDocumentCookieFromFrame(frame, cookie) { 121 return PostMessageAndAwaitReply( 122 { command: "write document.cookie", cookie }, frame.contentWindow); 123 } 124 125 // Reads cookies via document.cookie in the given frame. 126 function GetJSCookiesFromFrame(frame) { 127 return PostMessageAndAwaitReply( 128 { command: "document.cookie" }, frame.contentWindow); 129 } 130 131 async function DeleteCookieInFrame(frame, name, params) { 132 await SetDocumentCookieFromFrame(frame, `${name}=0; expires=${new Date(0).toUTCString()}; ${params};`); 133 assert_false(cookieStringHasCookie(name, '0', await GetJSCookiesFromFrame(frame)), `Verify that cookie '${name}' has been deleted.`); 134 } 135 136 // Sets a cookie in an unpartitioned context by opening a window that 137 // writes a cookie using document.cookie. 138 async function SetFirstPartyCookie(origin, cookie="cookie=unpartitioned;Secure;SameSite=None;Path=/") { 139 return new Promise((resolve) => { 140 const onMessage = (event) => { 141 if (event && event.data === 'set-document-cookie-complete') { 142 window.removeEventListener('message', onMessage); 143 resolve(); 144 } 145 }; 146 window.addEventListener('message', onMessage, { once: true }); 147 148 RunCallbackWithGesture(() => { 149 window.open(`${origin}/storage-access-api/resources/set-document-cookie.html?${cookie}`); 150 }); 151 }); 152 } 153 154 // Tests for the presence of the unpartitioned cookie set by SetFirstPartyCookie 155 // in both the `document.cookie` variable and same-origin subresource \ 156 // Request Headers in the given frame 157 async function HasUnpartitionedCookie(frame) { 158 let frameDocumentCookie = await GetJSCookiesFromFrame(frame); 159 let jsAccess = cookieStringHasCookie("cookie", "unpartitioned", frameDocumentCookie); 160 const httpCookie = await FetchSubresourceCookiesFromFrame(frame, ""); 161 let httpAccess = cookieStringHasCookie("cookie", "unpartitioned", httpCookie); 162 assert_equals(jsAccess, httpAccess, "HTTP and Javascript cookies must be in sync"); 163 return jsAccess && httpAccess; 164 } 165 166 // Tests whether the current frame can read and write cookies via HTTP headers. 167 // This deletes, writes, reads, then deletes a cookie named "cookie". 168 async function CanAccessCookiesViaHTTP() { 169 // We avoid reusing SetFirstPartyCookie here, since that bypasses the 170 // cookie-accessibility settings that we want to check here. 171 await fetch(`${window.location.origin}/storage-access-api/resources/set-cookie-header.py?cookie=1;path=/;SameSite=None;Secure`); 172 const http_cookies = await fetch(`${window.location.origin}/storage-access-api/resources/echo-cookie-header.py`) 173 .then((resp) => resp.text()); 174 const can_access = cookieStringHasCookie("cookie", "1", http_cookies); 175 176 erase_cookie_from_js("cookie", "SameSite=None;Secure;Path=/"); 177 178 return can_access; 179 } 180 181 // Tests whether the current frame can read and write cookies via 182 // document.cookie. This deletes, writes, reads, then deletes a cookie named 183 // "cookie". 184 function CanAccessCookiesViaJS() { 185 erase_cookie_from_js("cookie", "SameSite=None;Secure;Path=/"); 186 assert_false(cookieStringHasCookie("cookie", "1", document.cookie)); 187 188 document.cookie = "cookie=1;SameSite=None;Secure;Path=/"; 189 const can_access = cookieStringHasCookie("cookie", "1", document.cookie); 190 191 erase_cookie_from_js("cookie", "SameSite=None;Secure;Path=/"); 192 assert_false(cookieStringHasCookie("cookie", "1", document.cookie)); 193 194 return can_access; 195 } 196 197 // Reads cookies via the `httpCookies` variable in the given frame. 198 function GetHTTPCookiesFromFrame(frame) { 199 return PostMessageAndAwaitReply( 200 { command: "httpCookies" }, frame.contentWindow); 201 } 202 203 // Executes document.hasStorageAccess in the given frame. 204 function FrameHasStorageAccess(frame) { 205 return PostMessageAndAwaitReply( 206 { command: "hasStorageAccess" }, frame.contentWindow); 207 } 208 209 // Executes document.requestStorageAccess in the given frame. 210 function RequestStorageAccessInFrame(frame) { 211 return PostMessageAndAwaitReply( 212 { command: "requestStorageAccess" }, frame.contentWindow); 213 } 214 215 function GetPermissionInFrame(frame) { 216 return PostMessageAndAwaitReply( 217 { command: "get_permission" }, frame.contentWindow); 218 } 219 220 // Executes test_driver.set_permission in the given frame, with the provided 221 // arguments. 222 function SetPermissionInFrame(frame, args = []) { 223 return PostMessageAndAwaitReply( 224 { command: "set_permission", args }, frame.contentWindow); 225 } 226 227 // Waits for a storage-access permission change and resolves with the current 228 // state. 229 function ObservePermissionChange(frame, args = []) { 230 return PostMessageAndAwaitReply( 231 { command: "observe_permission_change", args }, frame.contentWindow); 232 } 233 234 // Executes `location.reload()` in the given frame. The returned promise 235 // resolves when the frame has finished reloading. 236 function FrameInitiatedReload(frame) { 237 const reload = LoadPromise(frame); 238 frame.contentWindow.postMessage({ command: "reload" }, "*"); 239 return reload; 240 } 241 242 // Executes `location.href = url` in the given frame. The returned promise 243 // resolves when the frame has finished navigating. 244 function FrameInitiatedNavigation(frame, url) { 245 const load = LoadPromise(frame); 246 frame.contentWindow.postMessage({ command: "navigate", url }, "*"); 247 return load; 248 } 249 250 // Makes a subresource request to the provided host in the given frame, and 251 // returns the cookies that were included in the request. 252 function FetchSubresourceCookiesFromFrame(frame, host) { 253 return FetchFromFrame(frame, `${host}/storage-access-api/resources/echo-cookie-header.py`); 254 } 255 256 // Makes a subresource request to the provided host in the given frame, and 257 // returns the response. 258 function FetchFromFrame(frame, url) { 259 return PostMessageAndAwaitReply( 260 { command: "cors fetch", url }, frame.contentWindow); 261 } 262 263 // Makes a subresource request to the provided host in the given frame with the 264 // mode set to 'no-cors'. Returns a promise that resolves with undefined, since 265 // no-cors responses are opaque to JavaScript. 266 function NoCorsFetchFromFrame(frame, url) { 267 return PostMessageAndAwaitReply( 268 { command: "no-cors fetch", url }, frame.contentWindow); 269 } 270 271 // Tries to set storage access policy, ignoring any errors. 272 // 273 // Note: to discourage the writing of tests that assume unpartitioned cookie 274 // access by default, any test that calls this with `value` == "blocked" should 275 // do so as the first step in the test. 276 async function MaybeSetStorageAccess(origin, embedding_origin, value) { 277 try { 278 await test_driver.set_storage_access(origin, embedding_origin, value); 279 } catch (e) { 280 // Ignore, can be unimplemented if the platform blocks cross-site cookies 281 // by default. If this failed without default blocking we'll notice it later 282 // in the test. 283 } 284 } 285 286 287 // Navigate the inner iframe using the given frame. 288 function NavigateChild(frame, url) { 289 return PostMessageAndAwaitReply( 290 { command: "navigate_child", url }, frame.contentWindow); 291 } 292 293 // Starts a dedicated worker in the given frame. 294 function StartDedicatedWorker(frame) { 295 return PostMessageAndAwaitReply( 296 { command: "start_dedicated_worker" }, frame.contentWindow); 297 } 298 299 // Sends a message to the dedicated worker in the given frame. 300 function MessageWorker(frame, message = {}) { 301 return PostMessageAndAwaitReply( 302 { command: "message_worker", message }, frame.contentWindow); 303 } 304 // Opens a WebSocket connection to origin from within frame, and 305 // returns the cookie header that was sent during the handshake. 306 function ReadCookiesFromWebSocketConnection(frame, origin) { 307 return PostMessageAndAwaitReply( 308 { command: "get_cookie_via_websocket", origin}, frame.contentWindow); 309 }