diff options
Diffstat (limited to 'testing/web-platform/tests/file-system-access/resources')
4 files changed, 306 insertions, 0 deletions
diff --git a/testing/web-platform/tests/file-system-access/resources/data/testfile.txt b/testing/web-platform/tests/file-system-access/resources/data/testfile.txt new file mode 100644 index 0000000000..980a0d5f19 --- /dev/null +++ b/testing/web-platform/tests/file-system-access/resources/data/testfile.txt @@ -0,0 +1 @@ +Hello World! diff --git a/testing/web-platform/tests/file-system-access/resources/local-fs-test-helpers.js b/testing/web-platform/tests/file-system-access/resources/local-fs-test-helpers.js new file mode 100644 index 0000000000..4bb9793085 --- /dev/null +++ b/testing/web-platform/tests/file-system-access/resources/local-fs-test-helpers.js @@ -0,0 +1,186 @@ +// 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; +})(); + +function directory_test(func, description) { + promise_test(async t => { + const directory = await directory_promise; + // To be resilient against tests not cleaning up properly, cleanup before + // every test. + for await (let entry of directory.values()) { + await directory.removeEntry( + entry.name, {recursive: entry.kind === 'directory'}); + } + 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 + newAnonymousIframe(same_site_origin), // anonymousFrameSameSite + null, // anonymousFrameSameSite_AncestorBit + newAnonymousIframe(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(newAnonymousIframe(cross_site_origin), + child_frame_js(same_site_origin, "newAnonymousIframe", 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); +} diff --git a/testing/web-platform/tests/file-system-access/resources/opaque-origin-sandbox.html b/testing/web-platform/tests/file-system-access/resources/opaque-origin-sandbox.html new file mode 100644 index 0000000000..f489f889b3 --- /dev/null +++ b/testing/web-platform/tests/file-system-access/resources/opaque-origin-sandbox.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<script> + 'use strict' + + // Sends two messages to its creator: + // (1) The result of showDirectoryPicker(). + // (2) The result of navigator.storage.getDirectory(). + + function post_message(data) { + if (window.parent !== null) { + window.parent.postMessage(data, { targetOrigin: '*' }); + } + if (window.opener !== null) { + window.opener.postMessage(data, { targetOrigin: '*' }); + } + } + + try { + window.showDirectoryPicker() + .then(() => { + post_message('showDirectoryPicker(): FULFILLED'); + }).catch(error => { + post_message(`showDirectoryPicker(): REJECTED: ${error.name}`); + }); + } catch (error) { + post_message(`showDirectoryPicker(): EXCEPTION: ${error.name}`); + } + + try { + navigator.storage.getDirectory() + .then(() => { + post_message('navigator.storage.getDirectory(): FULFILLED'); + }).catch(error => { + post_message(`navigator.storage.getDirectory(): REJECTED: ${error.name}`); + }); + } catch (error) { + post_message(`navigator.storage.getDirectory(): EXCEPTION: ${error.name}`); + } +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/file-system-access/resources/test-helpers.js b/testing/web-platform/tests/file-system-access/resources/test-helpers.js new file mode 100644 index 0000000000..893cd19848 --- /dev/null +++ b/testing/web-platform/tests/file-system-access/resources/test-helpers.js @@ -0,0 +1,80 @@ +// A special path component meaning "this directory." +const kCurrentDirectory = '.'; + +// A special path component meaning "the parent directory." +const kParentDirectory = '..'; + +// Array of separators used to separate components in hierarchical paths. +let kPathSeparators; +if (navigator.userAgent.includes('Windows NT')) { + // Windows uses both '/' and '\' as path separators. + kPathSeparators = ['/', '\\']; +} else { + kPathSeparators = ['/']; +} + +async function getFileSize(handle) { + const file = await handle.getFile(); + return file.size; +} + +async function getFileContents(handle) { + const file = await handle.getFile(); + return new Response(file).text(); +} + +async function getDirectoryEntryCount(handle) { + let result = 0; + for await (let entry of handle) { + result++; + } + return result; +} + +async function getSortedDirectoryEntries(handle) { + let result = []; + for await (let entry of handle.values()) { + if (entry.kind === 'directory') + result.push(entry.name + '/'); + else + result.push(entry.name); + } + result.sort(); + return result; +} + +async function createDirectory(test, name, parent) { + const new_dir_handle = await parent.getDirectoryHandle(name, {create: true}); + test.add_cleanup(async () => { + try { + await parent.removeEntry(name, {recursive: true}); + } catch (e) { + // Ignore any errors when removing directories, as tests might + // have already removed the directory. + } + }); + return new_dir_handle; +} + +async function createEmptyFile(test, name, parent) { + const handle = await parent.getFileHandle(name, {create: true}); + test.add_cleanup(async () => { + try { + await parent.removeEntry(name); + } catch (e) { + // Ignore any errors when removing files, as tests might already remove + // the file. + } + }); + // Make sure the file is empty. + assert_equals(await getFileSize(handle), 0); + return handle; +} + +async function createFileWithContents(test, name, contents, parent) { + const handle = await createEmptyFile(test, name, parent); + const writer = await handle.createWritable(); + await writer.write(new Blob([contents])); + await writer.close(); + return handle; +} |