summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/file-system-access/resources
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/file-system-access/resources')
-rw-r--r--testing/web-platform/tests/file-system-access/resources/data/testfile.txt1
-rw-r--r--testing/web-platform/tests/file-system-access/resources/local-fs-test-helpers.js186
-rw-r--r--testing/web-platform/tests/file-system-access/resources/opaque-origin-sandbox.html39
-rw-r--r--testing/web-platform/tests/file-system-access/resources/test-helpers.js80
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;
+}