| // This file defines a directory_test() function that can be used to define |
| // tests that require a FileSystemDirectoryHandle. The implementation of that |
| // function in this file will ask the user to select an empty directory and uses |
| // that directory. |
| // |
| // Another implementation of this function exists in |
| // fs/resources/sandboxed-fs-test-helpers.js, where that version uses the |
| // sandboxed file system instead. |
| |
| function getFileSystemType() { |
| return 'local'; |
| } |
| |
| const directory_promise = (async () => { |
| await new Promise(resolve => { |
| window.addEventListener('DOMContentLoaded', resolve); |
| }); |
| |
| // Small delay to give chrome's test automation a chance to actually install |
| // itself. |
| await new Promise(resolve => step_timeout(resolve, 100)); |
| |
| await window.test_driver.bless( |
| 'show a file picker.<br />Please select an empty directory'); |
| const entries = await self.showDirectoryPicker(); |
| assert_true(entries instanceof FileSystemHandle); |
| assert_true(entries instanceof FileSystemDirectoryHandle); |
| for await (const entry of entries) { |
| assert_unreached('Selected directory is not empty'); |
| } |
| return entries; |
| })(); |
| |
| async function cleanupDirectory(dir, ignoreRejections) { |
| // Get a snapshot of the entries. |
| const entries = await Array.fromAsync(dir.values()); |
| |
| // Call removeEntry on all of them. |
| const remove_entry_promises = entries.map( |
| entry => |
| dir.removeEntry(entry.name, {recursive: entry.kind === 'directory'})); |
| |
| // Wait for them all to resolve or reject. |
| if (ignoreRejections) { |
| await Promise.allSettled(remove_entry_promises); |
| } else { |
| await Promise.all(remove_entry_promises); |
| } |
| } |
| |
| function directory_test(func, description) { |
| promise_test(async t => { |
| const directory = await directory_promise; |
| |
| // To be extra resilient against bad tests, cleanup before every test. |
| await cleanupDirectory(directory, /*ignoreRejections=*/ false); |
| |
| // Cleanup after every test. |
| t.add_cleanup(async () => { |
| // Ignore any rejections since other cleanup code may have deleted them |
| // before we could. |
| await cleanupDirectory(directory, /*ignoreRejections=*/ true); |
| }); |
| |
| await func(t, directory); |
| }, description); |
| } |
| |
| directory_test(async (t, dir) => { |
| assert_equals(await dir.queryPermission({mode: 'read'}), 'granted'); |
| }, 'User succesfully selected an empty directory.'); |
| |
| directory_test(async (t, dir) => { |
| const status = await dir.queryPermission({mode: 'readwrite'}); |
| if (status == 'granted') |
| return; |
| |
| await window.test_driver.bless('ask for write permission'); |
| assert_equals(await dir.requestPermission({mode: 'readwrite'}), 'granted'); |
| }, 'User granted write access.'); |
| |
| const child_frame_js = (origin, frameFn, done) => ` |
| const importScript = ${importScript}; |
| await importScript("/html/cross-origin-embedder-policy/credentialless" + |
| "/resources/common.js"); |
| await importScript("/html/anonymous-iframe/resources/common.js"); |
| await importScript("/common/utils.js"); |
| await send("${done}", ${frameFn}("${origin}")); |
| `; |
| |
| /** |
| * Context identifiers for executor subframes of framed tests. Individual |
| * contexts (or convenience context lists below) can be used to send JavaScript |
| * for evaluation in each frame (see framed_test below). |
| * |
| * Note that within framed tests: |
| * - firstParty represents the top-level document. |
| * - thirdParty represents an embedded context (iframe). |
| * - ancestorBit contexts include a cross-site ancestor iframe. |
| * - anonymousFrame contexts are third-party anonymous iframe contexts. |
| */ |
| const FRAME_CONTEXT = { |
| firstParty: 0, |
| thirdPartySameSite: 1, |
| thirdPartySameSite_AncestorBit: 2, |
| thirdPartyCrossSite: 3, |
| anonymousFrameSameSite: 4, |
| anonymousFrameSameSite_AncestorBit: 5, |
| anonymousFrameCrossSite: 6, |
| }; |
| |
| // TODO(crbug.com/1322897): Add AncestorBit contexts. |
| const sameSiteContexts = [ |
| FRAME_CONTEXT.firstParty, |
| FRAME_CONTEXT.thirdPartySameSite, |
| FRAME_CONTEXT.anonymousFrameSameSite, |
| ]; |
| |
| // TODO(crbug.com/1322897): Add AncestorBit contexts. |
| const crossSiteContexts = [ |
| FRAME_CONTEXT.thirdPartyCrossSite, |
| FRAME_CONTEXT.anonymousFrameCrossSite, |
| ]; |
| |
| // TODO(crbug.com/1322897): Add AncestorBit contexts. |
| const childContexts = [ |
| FRAME_CONTEXT.thirdPartySameSite, |
| FRAME_CONTEXT.thirdPartyCrossSite, |
| FRAME_CONTEXT.anonymousFrameSameSite, |
| FRAME_CONTEXT.anonymousFrameCrossSite, |
| ]; |
| |
| /** |
| * Creates a promise test with same- & cross-site executor subframes. |
| * |
| * In addition to the standard testing object, the provided func will be called |
| * with a sendTo function. sendTo expects: |
| * - contexts: an Iterable of FRAME_CONTEXT constants representing the |
| * frame(s) in which the provided script will be concurrently run. |
| * - js_gen: a function which should generate a script string when called |
| * with a string token. sendTo will wait until a "done" message |
| * is sent to this queue. |
| */ |
| function framed_test(func, description) { |
| const same_site_origin = get_host_info().HTTPS_ORIGIN; |
| const cross_site_origin = get_host_info().HTTPS_NOTSAMESITE_ORIGIN; |
| const frames = Object.values(FRAME_CONTEXT); |
| |
| promise_test(async (t) => { |
| return new Promise(async (resolve, reject) => { |
| try { |
| // Set up handles to all third party frames. |
| const handles = [ |
| null, // firstParty |
| newIframe(same_site_origin), // thirdPartySameSite |
| null, // thirdPartySameSite_AncestorBit |
| newIframe(cross_site_origin), // thirdPartyCrossSite |
| newIframeCredentialless(same_site_origin), // anonymousFrameSameSite |
| null, // anonymousFrameSameSite_AncestorBit |
| newIframeCredentialless( |
| cross_site_origin), // anonymousFrameCrossSite |
| ]; |
| // Set up nested SameSite frames for ancestor bit contexts. |
| const setUpQueue = token(); |
| send(newIframe(cross_site_origin), |
| child_frame_js(same_site_origin, "newIframe", setUpQueue)); |
| handles[FRAME_CONTEXT.thirdPartySameSite_AncestorBit] = |
| await receive(setUpQueue); |
| send( |
| newIframeCredentialless(cross_site_origin), |
| child_frame_js( |
| same_site_origin, 'newIframeCredentialless', setUpQueue)); |
| handles[FRAME_CONTEXT.anonymousFrameSameSite_AncestorBit] = |
| await receive(setUpQueue); |
| |
| const sendTo = (contexts, js_generator) => { |
| // Send to all contexts in parallel to minimize timeout concerns. |
| return Promise.all(contexts.map(async (context) => { |
| const queue = token(); |
| const js_string = js_generator(queue, context); |
| switch (context) { |
| case FRAME_CONTEXT.firstParty: |
| // Code is executed directly in this frame via eval() rather |
| // than in a new context to avoid differences in API access. |
| eval(`(async () => {${js_string}})()`); |
| break; |
| case FRAME_CONTEXT.thirdPartySameSite: |
| case FRAME_CONTEXT.thirdPartyCrossSite: |
| case FRAME_CONTEXT.anonymousFrameSameSite: |
| case FRAME_CONTEXT.anonymousFrameCrossSite: |
| case FRAME_CONTEXT.thirdPartySameSite_AncestorBit: |
| case FRAME_CONTEXT.anonymousFrameSameSite_AncestorBit: |
| send(handles[context], js_string); |
| break; |
| default: |
| reject(`Cannot execute in context: ${context}`); |
| } |
| if (await receive(queue) != "done") { |
| reject(`Script failed in frame ${context}: ${js_string}`); |
| } |
| })); |
| }; |
| |
| await func(t, sendTo); |
| } catch (e) { |
| reject(e); |
| } |
| resolve(); |
| }); |
| }, description); |
| } |