local-fs-test-helpers.js (7862B)
1 // This file defines a directory_test() function that can be used to define 2 // tests that require a FileSystemDirectoryHandle. The implementation of that 3 // function in this file will ask the user to select an empty directory and uses 4 // that directory. 5 // 6 // Another implementation of this function exists in 7 // fs/resources/sandboxed-fs-test-helpers.js, where that version uses the 8 // sandboxed file system instead. 9 10 function getFileSystemType() { 11 return 'local'; 12 } 13 14 const directory_promise = (async () => { 15 await new Promise(resolve => { 16 window.addEventListener('DOMContentLoaded', resolve); 17 }); 18 19 // Small delay to give chrome's test automation a chance to actually install 20 // itself. 21 await new Promise(resolve => step_timeout(resolve, 100)); 22 23 await window.test_driver.bless( 24 'show a file picker.<br />Please select an empty directory'); 25 const entries = await self.showDirectoryPicker(); 26 assert_true(entries instanceof FileSystemHandle); 27 assert_true(entries instanceof FileSystemDirectoryHandle); 28 for await (const entry of entries) { 29 assert_unreached('Selected directory is not empty'); 30 } 31 return entries; 32 })(); 33 34 async function cleanupDirectory(dir, ignoreRejections) { 35 // Get a snapshot of the entries. 36 const entries = await Array.fromAsync(dir.values()); 37 38 // Call removeEntry on all of them. 39 const remove_entry_promises = entries.map( 40 entry => 41 dir.removeEntry(entry.name, {recursive: entry.kind === 'directory'})); 42 43 // Wait for them all to resolve or reject. 44 if (ignoreRejections) { 45 await Promise.allSettled(remove_entry_promises); 46 } else { 47 await Promise.all(remove_entry_promises); 48 } 49 } 50 51 function directory_test(func, description) { 52 promise_test(async t => { 53 const directory = await directory_promise; 54 55 // To be extra resilient against bad tests, cleanup before every test. 56 await cleanupDirectory(directory, /*ignoreRejections=*/ false); 57 58 // Cleanup after every test. 59 t.add_cleanup(async () => { 60 // Ignore any rejections since other cleanup code may have deleted them 61 // before we could. 62 await cleanupDirectory(directory, /*ignoreRejections=*/ true); 63 }); 64 65 await func(t, directory); 66 }, description); 67 } 68 69 directory_test(async (t, dir) => { 70 assert_equals(await dir.queryPermission({mode: 'read'}), 'granted'); 71 }, 'User succesfully selected an empty directory.'); 72 73 directory_test(async (t, dir) => { 74 const status = await dir.queryPermission({mode: 'readwrite'}); 75 if (status == 'granted') 76 return; 77 78 await window.test_driver.bless('ask for write permission'); 79 assert_equals(await dir.requestPermission({mode: 'readwrite'}), 'granted'); 80 }, 'User granted write access.'); 81 82 const child_frame_js = (origin, frameFn, done) => ` 83 const importScript = ${importScript}; 84 await importScript("/html/cross-origin-embedder-policy/credentialless" + 85 "/resources/common.js"); 86 await importScript("/html/anonymous-iframe/resources/common.js"); 87 await importScript("/common/utils.js"); 88 await send("${done}", ${frameFn}("${origin}")); 89 `; 90 91 /** 92 * Context identifiers for executor subframes of framed tests. Individual 93 * contexts (or convenience context lists below) can be used to send JavaScript 94 * for evaluation in each frame (see framed_test below). 95 * 96 * Note that within framed tests: 97 * - firstParty represents the top-level document. 98 * - thirdParty represents an embedded context (iframe). 99 * - ancestorBit contexts include a cross-site ancestor iframe. 100 * - anonymousFrame contexts are third-party anonymous iframe contexts. 101 */ 102 const FRAME_CONTEXT = { 103 firstParty: 0, 104 thirdPartySameSite: 1, 105 thirdPartySameSite_AncestorBit: 2, 106 thirdPartyCrossSite: 3, 107 anonymousFrameSameSite: 4, 108 anonymousFrameSameSite_AncestorBit: 5, 109 anonymousFrameCrossSite: 6, 110 }; 111 112 // TODO(crbug.com/1322897): Add AncestorBit contexts. 113 const sameSiteContexts = [ 114 FRAME_CONTEXT.firstParty, 115 FRAME_CONTEXT.thirdPartySameSite, 116 FRAME_CONTEXT.anonymousFrameSameSite, 117 ]; 118 119 // TODO(crbug.com/1322897): Add AncestorBit contexts. 120 const crossSiteContexts = [ 121 FRAME_CONTEXT.thirdPartyCrossSite, 122 FRAME_CONTEXT.anonymousFrameCrossSite, 123 ]; 124 125 // TODO(crbug.com/1322897): Add AncestorBit contexts. 126 const childContexts = [ 127 FRAME_CONTEXT.thirdPartySameSite, 128 FRAME_CONTEXT.thirdPartyCrossSite, 129 FRAME_CONTEXT.anonymousFrameSameSite, 130 FRAME_CONTEXT.anonymousFrameCrossSite, 131 ]; 132 133 /** 134 * Creates a promise test with same- & cross-site executor subframes. 135 * 136 * In addition to the standard testing object, the provided func will be called 137 * with a sendTo function. sendTo expects: 138 * - contexts: an Iterable of FRAME_CONTEXT constants representing the 139 * frame(s) in which the provided script will be concurrently run. 140 * - js_gen: a function which should generate a script string when called 141 * with a string token. sendTo will wait until a "done" message 142 * is sent to this queue. 143 */ 144 function framed_test(func, description) { 145 const same_site_origin = get_host_info().HTTPS_ORIGIN; 146 const cross_site_origin = get_host_info().HTTPS_NOTSAMESITE_ORIGIN; 147 const frames = Object.values(FRAME_CONTEXT); 148 149 promise_test(async (t) => { 150 return new Promise(async (resolve, reject) => { 151 try { 152 // Set up handles to all third party frames. 153 const handles = [ 154 null, // firstParty 155 newIframe(same_site_origin), // thirdPartySameSite 156 null, // thirdPartySameSite_AncestorBit 157 newIframe(cross_site_origin), // thirdPartyCrossSite 158 newIframeCredentialless(same_site_origin), // anonymousFrameSameSite 159 null, // anonymousFrameSameSite_AncestorBit 160 newIframeCredentialless( 161 cross_site_origin), // anonymousFrameCrossSite 162 ]; 163 // Set up nested SameSite frames for ancestor bit contexts. 164 const setUpQueue = token(); 165 send(newIframe(cross_site_origin), 166 child_frame_js(same_site_origin, "newIframe", setUpQueue)); 167 handles[FRAME_CONTEXT.thirdPartySameSite_AncestorBit] = 168 await receive(setUpQueue); 169 send( 170 newIframeCredentialless(cross_site_origin), 171 child_frame_js( 172 same_site_origin, 'newIframeCredentialless', setUpQueue)); 173 handles[FRAME_CONTEXT.anonymousFrameSameSite_AncestorBit] = 174 await receive(setUpQueue); 175 176 const sendTo = (contexts, js_generator) => { 177 // Send to all contexts in parallel to minimize timeout concerns. 178 return Promise.all(contexts.map(async (context) => { 179 const queue = token(); 180 const js_string = js_generator(queue, context); 181 switch (context) { 182 case FRAME_CONTEXT.firstParty: 183 // Code is executed directly in this frame via eval() rather 184 // than in a new context to avoid differences in API access. 185 eval(`(async () => {${js_string}})()`); 186 break; 187 case FRAME_CONTEXT.thirdPartySameSite: 188 case FRAME_CONTEXT.thirdPartyCrossSite: 189 case FRAME_CONTEXT.anonymousFrameSameSite: 190 case FRAME_CONTEXT.anonymousFrameCrossSite: 191 case FRAME_CONTEXT.thirdPartySameSite_AncestorBit: 192 case FRAME_CONTEXT.anonymousFrameSameSite_AncestorBit: 193 send(handles[context], js_string); 194 break; 195 default: 196 reject(`Cannot execute in context: ${context}`); 197 } 198 if (await receive(queue) != "done") { 199 reject(`Script failed in frame ${context}: ${js_string}`); 200 } 201 })); 202 }; 203 204 await func(t, sendTo); 205 } catch (e) { 206 reject(e); 207 } 208 resolve(); 209 }); 210 }, description); 211 }