summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/fs/script-tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/fs/script-tests
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/fs/script-tests')
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-IndexedDB.js145
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-buckets.js42
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-getUniqueId.js89
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-isSameEntry.js107
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-BroadcastChannel.js82
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-Error.js244
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-frames.js44
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-windows.js35
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-workers.js40
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-frames.js40
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-windows.js31
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-workers.js35
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-remove.js105
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getDirectoryHandle.js117
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getFileHandle.js145
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-iteration.js100
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-removeEntry.js222
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-resolve.js27
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-create-sync-access-handle.js27
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-getFile.js52
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-move.js359
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemObserver.js57
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemSyncAccessHandle-flush.js8
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream-piped.js137
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream-write.js364
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream.js105
26 files changed, 2759 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-IndexedDB.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-IndexedDB.js
new file mode 100644
index 0000000000..9e114619bf
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-IndexedDB.js
@@ -0,0 +1,145 @@
+'use strict';
+
+directory_test(async (t, root_dir) => {
+ const handles = await create_file_system_handles(t, root_dir);
+
+ const db = await createDatabase(t, db => {
+ const store = db.createObjectStore('store');
+ });
+
+ const value = handles;
+
+ const tx = db.transaction('store', 'readwrite');
+ const store = tx.objectStore('store');
+ await promiseForRequest(t, store.put(value, 'key'));
+ const result = await promiseForRequest(t, store.get('key'));
+
+ await promiseForTransaction(t, tx);
+
+ assert_true(Array.isArray(result), 'Result should be an array');
+ assert_equals(result.length, value.length);
+ await assert_equals_cloned_handles(result, value);
+}, 'Store handle in IndexedDB and read from pending transaction.');
+
+directory_test(async (t, root_dir) => {
+ const handles = await create_file_system_handles(t, root_dir);
+
+ const db = await createDatabase(t, db => {
+ const store = db.createObjectStore('store');
+ });
+
+ const value = handles;
+
+ let tx = db.transaction('store', 'readwrite');
+ let store = tx.objectStore('store');
+ await promiseForRequest(t, store.put(value, 'key'));
+ await promiseForTransaction(t, tx);
+
+ tx = db.transaction('store', 'readonly');
+ store = tx.objectStore('store');
+ const result = await promiseForRequest(t, store.get('key'));
+ await promiseForTransaction(t, tx);
+
+ assert_true(Array.isArray(result), 'Result should be an array');
+ assert_equals(result.length, value.length);
+ await assert_equals_cloned_handles(result, value);
+}, 'Store handle in IndexedDB and read from new transaction.');
+
+directory_test(async (t, root_dir) => {
+ const handles = await create_file_system_handles(t, root_dir);
+
+ const db = await createDatabase(t, db => {
+ const store = db.createObjectStore('store');
+ });
+
+ const value = {handles, blob: new Blob(["foobar"])};
+
+ let tx = db.transaction('store', 'readwrite');
+ let store = tx.objectStore('store');
+ await promiseForRequest(t, store.put(value, 'key'));
+ await promiseForTransaction(t, tx);
+
+ tx = db.transaction('store', 'readonly');
+ store = tx.objectStore('store');
+ const result = await promiseForRequest(t, store.get('key'));
+ await promiseForTransaction(t, tx);
+
+ assert_true(Array.isArray(result.handles), 'Result should be an array');
+ assert_equals(result.handles.length, value.handles.length);
+ await assert_equals_cloned_handles(result.handles, value.handles);
+
+ assert_equals(await result.blob.text(), await value.blob.text());
+}, 'Store handles and blobs in IndexedDB.');
+
+directory_test(async (t, root_dir) => {
+ const handles = await create_file_system_handles(t, root_dir);
+
+ const db = await createDatabase(t, db => {
+ const store = db.createObjectStore('store');
+ });
+
+ const value = handles;
+
+ let tx = db.transaction('store', 'readwrite');
+ let store = tx.objectStore('store');
+ await promiseForRequest(t, store.put(value, 'key'));
+ await promiseForTransaction(t, tx);
+
+ tx = db.transaction('store', 'readonly');
+ store = tx.objectStore('store');
+ let cursor_request = store.openCursor();
+ await requestWatcher(t, cursor_request).wait_for('success');
+ const result = cursor_request.result.value;
+ await promiseForTransaction(t, tx);
+
+ assert_true(Array.isArray(result), 'Result should be an array');
+ assert_equals(result.length, value.length);
+ await assert_equals_cloned_handles(result, value);
+}, 'Store handle in IndexedDB and read using a cursor.');
+
+directory_test(async (t, root_dir) => {
+ const handles = await create_file_system_handles(t, root_dir);
+
+ const db = await createDatabase(t, db => {
+ const store = db.createObjectStore('store', {keyPath: 'key'});
+ });
+
+ const value = handles;
+ let tx = db.transaction('store', 'readwrite');
+ let store = tx.objectStore('store');
+ await promiseForRequest(t, store.put({key: 'key', value}));
+ await promiseForTransaction(t, tx);
+
+ tx = db.transaction('store', 'readonly');
+ store = tx.objectStore('store');
+ const result = await promiseForRequest(t, store.get('key'));
+ await promiseForTransaction(t, tx);
+
+ assert_true(Array.isArray(result.value), 'Result should be an array');
+ assert_equals(result.value.length, value.length);
+ await assert_equals_cloned_handles(result.value, value);
+}, 'Store handle in IndexedDB using inline keys.');
+
+directory_test(async (t, root_dir) => {
+ const expected_root_name = '';
+ assert_equals(root_dir.name, expected_root_name);
+
+ const db = await createDatabase(t, db => {
+ const store = db.createObjectStore('store', {keyPath: 'key'});
+ });
+
+ const value = [ root_dir ];
+ let tx = db.transaction('store', 'readwrite');
+ let store = tx.objectStore('store');
+ await promiseForRequest(t, store.put({key: 'key', value}));
+ await promiseForTransaction(t, tx);
+
+ tx = db.transaction('store', 'readonly');
+ store = tx.objectStore('store');
+ const result = await promiseForRequest(t, store.get('key'));
+ await promiseForTransaction(t, tx);
+
+ const actual = result.value[ 0 ];
+ assert_equals(actual.name, expected_root_name);
+ assert_true(await root_dir.isSameEntry(actual));
+}, 'Store and retrieve the root directory from IndexedDB.');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-buckets.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-buckets.js
new file mode 100644
index 0000000000..c06e940d7e
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-buckets.js
@@ -0,0 +1,42 @@
+'use strict';
+
+directory_test(async (t, root_dir) => {
+ await prepareForBucketTest(t);
+
+ const inboxBucket = await navigator.storageBuckets.open('inbox');
+ const inboxRootDir = await inboxBucket.getDirectory();
+
+ assert_false(await inboxRootDir.isSameEntry(root_dir));
+
+ const handle1 = await createEmptyFile(t, 'mtime.txt', inboxRootDir);
+ const handle2 = await inboxRootDir.getFileHandle('mtime.txt');
+ assert_true(await handle1.isSameEntry(handle2));
+}, 'isSameEntry works as expected with buckets');
+
+directory_test(async (t, root_dir) => {
+ await prepareForBucketTest(t);
+
+ const inboxBucket = await navigator.storageBuckets.open('inbox');
+ await navigator.storageBuckets.delete('inbox');
+ const directoryPromise = inboxBucket.getDirectory();
+ await promise_rejects_dom(t, 'InvalidStateError', directoryPromise);
+}, 'getDirectory promise rejects if bucket has been deleted');
+
+directory_test(async (t, root_dir) => {
+ await prepareForBucketTest(t);
+
+ const inboxBucket = await navigator.storageBuckets.open('inbox', {quota: 500});
+ const inboxRootDir = await inboxBucket.getDirectory();
+
+ // Short file succeeds.
+ const file =
+ await createFileWithContents(t, 'mtime.txt', 'short file', inboxRootDir);
+
+ // Longer file fails.
+ return promise_rejects_dom(
+ t, 'QuotaExceededError',
+ createFileWithContents(
+ t, 'mtime2.txt',
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum',
+ inboxRootDir));
+}, 'Bucket quota restricts the size of a file that can be created');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-getUniqueId.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-getUniqueId.js
new file mode 100644
index 0000000000..ee0cd5e349
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-getUniqueId.js
@@ -0,0 +1,89 @@
+'use strict';
+
+directory_test(async (t, root_dir) => {
+ assert_equals(await root_dir.getUniqueId(), await root_dir.getUniqueId());
+
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+ assert_equals(await subdir.getUniqueId(), await subdir.getUniqueId());
+}, 'identical directory handles return the same ID');
+
+directory_test(async (t, root_dir) => {
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+
+ assert_not_equals(await root_dir.getUniqueId(), await subdir.getUniqueId());
+}, 'different directories return different IDs');
+
+directory_test(async (t, root_dir) => {
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+ const subdir2 = await root_dir.getDirectoryHandle('subdir-name');
+
+ assert_equals(await subdir.getUniqueId(), await subdir2.getUniqueId());
+}, 'different handles for the same directory return the same ID');
+
+directory_test(async (t, root_dir) => {
+ const handle = await createEmptyFile(t, 'foo.txt', root_dir);
+
+ assert_equals(await handle.getUniqueId(), await handle.getUniqueId());
+}, 'identical file handles return the same unique ID');
+
+directory_test(async (t, root_dir) => {
+ const handle1 = await createEmptyFile(t, 'foo.txt', root_dir);
+ const handle2 = await createEmptyFile(t, 'bar.txt', root_dir);
+
+ assert_not_equals(await handle1.getUniqueId(), await handle2.getUniqueId());
+}, 'different files return different IDs');
+
+directory_test(async (t, root_dir) => {
+ const handle1 = await createEmptyFile(t, 'foo.txt', root_dir);
+ const handle2 = await root_dir.getFileHandle('foo.txt');
+
+ assert_equals(await handle1.getUniqueId(), await handle2.getUniqueId());
+}, 'different handles for the same file return the same ID');
+
+directory_test(async (t, root_dir) => {
+ const handle1 = await createEmptyFile(t, 'foo.txt', root_dir);
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+ const handle2 = await createEmptyFile(t, 'foo.txt', subdir);
+
+ assert_not_equals(await handle1.getUniqueId(), await handle2.getUniqueId());
+}, 'two files of the same name in different directories return different IDs');
+
+directory_test(async (t, root_dir) => {
+ const handle1 = await createEmptyFile(t, 'foo.txt', root_dir);
+ const handle2 = await createDirectory(t, 'subdir-name', root_dir);
+
+ assert_not_equals(await handle1.getUniqueId(), await handle2.getUniqueId());
+}, 'a file and a directory return different IDs');
+
+directory_test(async (t, root_dir) => {
+ const file_handle = await createEmptyFile(t, 'foo', root_dir);
+ const file_id = await file_handle.getUniqueId();
+
+ // Remove the file.
+ await root_dir.removeEntry('foo');
+
+ // Create a directory of the same name and path.
+ const dir_handle = await createDirectory(t, 'foo', root_dir);
+ assert_not_equals(await dir_handle.getUniqueId(), file_id);
+}, 'a file and a directory of the same path return different IDs');
+
+directory_test(async (t, root_dir) => {
+ const handle = await createEmptyFile(t, 'foo.txt', root_dir);
+ const id_before = await handle.getUniqueId();
+
+ // Write to the file. The unique ID should not change.
+ const writable = await cleanup_writable(t, await handle.createWritable());
+ await writable.write("blah");
+ await writable.close();
+
+ assert_equals(await handle.getUniqueId(), id_before);
+}, 'unique ID of a file handle does not change after writes');
+
+directory_test(async (t, root_dir) => {
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+
+ const UUIDRegex =
+ /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/
+ assert_true(UUIDRegex.test(await root_dir.getUniqueId()));
+ assert_true(UUIDRegex.test(await subdir.getUniqueId()));
+}, 'unique ID is in GUID version 4 format');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-isSameEntry.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-isSameEntry.js
new file mode 100644
index 0000000000..e3b6d1891e
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-isSameEntry.js
@@ -0,0 +1,107 @@
+'use strict';
+
+directory_test(async (t, root_dir) => {
+ assert_true(await root_dir.isSameEntry(root_dir));
+
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+ assert_true(await subdir.isSameEntry(subdir));
+}, 'isSameEntry for identical directory handles returns true');
+
+directory_test(async (t, root_dir) => {
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+
+ assert_false(await root_dir.isSameEntry(subdir));
+ assert_false(await subdir.isSameEntry(root_dir));
+}, 'isSameEntry for different directories returns false');
+
+directory_test(async (t, root_dir) => {
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+ const subdir2 = await root_dir.getDirectoryHandle('subdir-name');
+
+ assert_true(await subdir.isSameEntry(subdir2));
+ assert_true(await subdir2.isSameEntry(subdir));
+}, 'isSameEntry for different handles for the same directory');
+
+directory_test(async (t, root_dir) => {
+ const handle = await createEmptyFile(t, 'mtime.txt', root_dir);
+
+ assert_true(await handle.isSameEntry(handle));
+}, 'isSameEntry for identical file handles returns true');
+
+directory_test(async (t, root_dir) => {
+ const handle1 = await createEmptyFile(t, 'mtime.txt', root_dir);
+ const handle2 = await createEmptyFile(t, 'foo.txt', root_dir);
+
+ assert_false(await handle1.isSameEntry(handle2));
+ assert_false(await handle2.isSameEntry(handle1));
+}, 'isSameEntry for different files returns false');
+
+directory_test(async (t, root_dir) => {
+ const handle1 = await createEmptyFile(t, 'mtime.txt', root_dir);
+ const handle2 = await root_dir.getFileHandle('mtime.txt');
+
+ assert_true(await handle1.isSameEntry(handle2));
+ assert_true(await handle2.isSameEntry(handle1));
+}, 'isSameEntry for different handles for the same file');
+
+directory_test(async (t, root_dir) => {
+ const handle1 = await createEmptyFile(t, 'mtime.txt', root_dir);
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+ const handle2 = await createEmptyFile(t, 'mtime.txt', subdir);
+
+ assert_false(await handle1.isSameEntry(handle2));
+ assert_false(await handle2.isSameEntry(handle1));
+}, 'isSameEntry comparing a file to a file in a different directory returns false');
+
+directory_test(async (t, root_dir) => {
+ const handle1 = await createEmptyFile(t, 'mtime.txt', root_dir);
+ const handle2 = await createDirectory(t, 'subdir-name', root_dir);
+
+ assert_false(await handle1.isSameEntry(handle2));
+ assert_false(await handle2.isSameEntry(handle1));
+}, 'isSameEntry comparing a file to a directory returns false');
+
+directory_test(async (t, root_dir) => {
+ const filename = 'foo';
+ const handle1 = await createEmptyFile(t, filename, root_dir);
+ // Remove the file and create a new file of the same path.
+ await root_dir.removeEntry(filename);
+ const handle2 = await createEmptyFile(t, filename, root_dir);
+
+ assert_true(
+ await handle1.isSameEntry(handle2),
+ 'two file handles pointing at the same path should be considered the same entry');
+ assert_true(
+ await handle2.isSameEntry(handle1),
+ 'two file handles pointing at the same path should be considered the same entry');
+}, 'isSameEntry comparing two files pointing to the same path returns true');
+
+directory_test(async (t, root_dir) => {
+ const filename = 'foo';
+ const handle1 = await createDirectory(t, filename, root_dir);
+ // Remove the directory and create a new directory of the same path.
+ await root_dir.removeEntry(filename);
+ const handle2 = await createDirectory(t, filename, root_dir);
+
+ assert_true(
+ await handle1.isSameEntry(handle2),
+ 'two directory handles pointing at the same path should be considered the same entry');
+ assert_true(
+ await handle2.isSameEntry(handle1),
+ 'two directory handles pointing at the same path should be considered the same entry');
+}, 'isSameEntry comparing two directories pointing to the same path returns true');
+
+directory_test(async (t, root_dir) => {
+ const filename = 'foo';
+ const dir_handle = await createDirectory(t, filename, root_dir);
+ // Remove the directory and create a file of the same path.
+ await root_dir.removeEntry(filename);
+ const file_handle = await createEmptyFile(t, filename, root_dir);
+
+ assert_false(
+ await dir_handle.isSameEntry(file_handle),
+ 'a file and directory handle pointing at the same path should not be considered the same entry');
+ assert_false(
+ await file_handle.isSameEntry(dir_handle),
+ 'a file and directory handle pointing at the same path should not be considered the same entry');
+}, 'isSameEntry comparing a file to a directory of the same path returns false');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-BroadcastChannel.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-BroadcastChannel.js
new file mode 100644
index 0000000000..681037db2f
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-BroadcastChannel.js
@@ -0,0 +1,82 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /fs/resources/messaging-serialize-helpers.js
+// /fs/resources/test-helpers.js
+// /service-workers/service-worker/resources/test-helpers.sub.js
+
+// Sets up a new broadcast channel in |target|. Posts a message instructing
+// |target| to open the broadcast channel using |broadcast_channel_name|.
+async function create_broadcast_channel(
+ test, broadcast_channel_name, receiver, target, target_origin) {
+ target.postMessage(
+ { type: 'create-broadcast-channel', broadcast_channel_name },
+ { targetOrigin: target_origin });
+ const event_watcher = new EventWatcher(test, receiver, 'message');
+
+ // Wait until |target| is listening to the broad cast channel.
+ const message_event = await event_watcher.wait_for('message');
+ assert_equals(message_event.data.type, 'broadcast-channel-created',
+ 'The message target must receive a "broadcast-channel-created" message ' +
+ 'response.');
+}
+
+// This test is very similar to 'FileSystemBaseHandle-postMessage.js'. It
+// starts by creating three message targets for the broadcast channel:
+// an iframe, dedicated worker and a service worker. After setup, an array
+// of FileSystemHandles is sent across the broadcast channel. The test
+// expects three responses -- one from each message target.
+directory_test(async (t, root) => {
+ const broadcast_channel_name = 'file-system-file-handle-channel';
+ const broadcast_channel = new BroadcastChannel(broadcast_channel_name);
+ const broadcast_channel_event_watcher =
+ new EventWatcher(t, broadcast_channel, 'message');
+
+ const iframe = await add_iframe(t, { src: kDocumentMessageTarget });
+ await create_broadcast_channel(
+ t, broadcast_channel_name, self, iframe.contentWindow, '*');
+
+ const scope = `${kServiceWorkerMessageTarget}` +
+ '?post-message-to-broadcast-channel-with-file-handle';
+
+ const registration = await create_service_worker(
+ t, kServiceWorkerMessageTarget, scope);
+
+ await create_broadcast_channel(
+ t, broadcast_channel_name,
+ navigator.serviceWorker, registration.installing);
+
+ const dedicated_worker =
+ create_dedicated_worker(t, kDedicatedWorkerMessageTarget);
+
+ await create_broadcast_channel(
+ t, broadcast_channel_name, dedicated_worker, dedicated_worker);
+
+ const handles = await create_file_system_handles(t, root);
+
+ broadcast_channel.postMessage(
+ { type: 'receive-file-system-handles', cloned_handles: handles });
+
+ const expected_response_count = 3;
+ const responses = [];
+ for (let i = 0; i < expected_response_count; ++i) {
+ const message_event =
+ await broadcast_channel_event_watcher.wait_for('message');
+ responses.push(message_event.data);
+ }
+
+ const expected_serialized_handles = await serialize_handles(handles);
+
+ for (let i = 0; i < responses.length; ++i) {
+ assert_equals(responses[i].type, 'receive-serialized-file-system-handles',
+ 'The test runner must receive a "serialized-file-system-handles" ' +
+ `message response. Actual response: ${responses[i]}`);
+
+ assert_equals_serialized_handles(
+ responses[i].serialized_handles, expected_serialized_handles);
+
+ await assert_equals_cloned_handles(responses[i].cloned_handles, handles);
+ }
+}, 'Send and receive messages using a broadcast channel in an iframe, ' +
+'dedicated worker and service worker.'); \ No newline at end of file
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-Error.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-Error.js
new file mode 100644
index 0000000000..7c97a7da48
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-Error.js
@@ -0,0 +1,244 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /fs/resources/messaging-blob-helpers.js
+// /fs/resources/messaging-serialize-helpers.js
+// /fs/resources/test-helpers.js
+// /common/get-host-info.sub.js
+// /service-workers/service-worker/resources/test-helpers.sub.js
+
+// Define URL constants for cross origin windows.
+const kRemoteOrigin = get_host_info().HTTPS_REMOTE_ORIGIN;
+const kRemoteOriginDocumentMessageTarget = `${kRemoteOrigin}${base_path()}` +
+ kDocumentMessageTarget;
+
+// Sending a FileSystemHandle to a cross origin |target| through postMessage()
+// must dispatch the 'messageerror' event.
+//
+// This test sends a FileSystemHandle to |target|. |target| responds with a
+// serialized MessageEvent from the 'messageerror' event, allowing the test
+// runner to verify MessageEvent properties.
+async function do_send_message_error_test(
+ test,
+ root_dir,
+ receiver,
+ target,
+ target_origin,
+ // False when the MessageEvent's source is null.
+ expected_has_source,
+ // The origin of MessageEvents received by |target|.
+ expected_origin) {
+ const message_watcher = new EventWatcher(test, receiver, 'message');
+
+ // Send a file to |target|.
+ const file = await createFileWithContents(
+ test, 'test-error-file', 'test-error-file-contents', root_dir);
+ target.postMessage(
+ { type: 'receive-file-system-handles', cloned_file_system_handles: [file] },
+ { targetOrigin: target_origin });
+
+ // Wait for |target| to respond with results.
+ let message_event = await message_watcher.wait_for('message');
+ const first_response = message_event.data;
+ assert_equals(first_response.type, 'serialized-message-error',
+ 'The test runner must receive a "serialized-message-error" message ' +
+ 'in response to a FileSystemFileHandle message.');
+
+ // Verify the results.
+ assert_equals_serialized_message_error_event(
+ first_response.serialized_message_error_event,
+ expected_origin, expected_has_source);
+
+ // Send a directory to |target|.
+ const directory = await createDirectory(
+ test, 'test-error-directory', root_dir);
+
+ target.postMessage(
+ {
+ type: 'receive-file-system-handles',
+ cloned_file_system_handles: [directory]
+ }, { targetOrigin: target_origin });
+
+ // Wait for |target| to respond with results.
+ message_event = await message_watcher.wait_for('message');
+ const second_response = message_event.data;
+ assert_equals(second_response.type, 'serialized-message-error',
+ 'The test runner must receive a "serialized-message-error" message ' +
+ 'response to a FileSystemDirectoryHandle message.');
+
+ // Verify the results.
+ assert_equals_serialized_message_error_event(
+ second_response.serialized_message_error_event,
+ expected_origin, expected_has_source);
+}
+
+// This test receives a FileSystemHandle from |target|. This test runner
+// must dispatch the 'messageerror' event after receiving a handle from target.
+async function do_receive_message_error_test(
+ test,
+ receiver,
+ target,
+ target_origin,
+ // False when the MessageEvent's source is null.
+ expected_has_source,
+ // The origin of MessageEvents received by this test runner.
+ expected_origin) {
+ const error_watcher = new EventWatcher(test, receiver, 'messageerror');
+
+ // Receive a file from |target|.
+ target.postMessage(
+ { type: 'create-file' }, { targetOrigin: target_origin });
+ const first_error = await error_watcher.wait_for('messageerror');
+ const serialized_first_error = serialize_message_error_event(first_error);
+ assert_equals_serialized_message_error_event(
+ serialized_first_error, expected_origin, expected_has_source);
+
+ // Receive a directory from |target|.
+ target.postMessage(
+ { type: 'create-directory' }, { targetOrigin: target_origin });
+ const second_error = await error_watcher.wait_for('messageerror');
+ const serialized_second_error = serialize_message_error_event(second_error);
+ assert_equals_serialized_message_error_event(
+ serialized_second_error, expected_origin, expected_has_source);
+}
+
+// Performs the send message error test followed by the receive message error
+// test.
+async function do_send_and_receive_message_error_test(
+ test,
+ root_dir,
+ receiver,
+ target,
+ target_origin,
+ // False when the MessageEvent's source is null.
+ expected_has_source,
+ // The origin of MessageEvents received by |target|.
+ expected_origin,
+ // The origin of MessageEvents received by this test runner.
+ expected_remote_origin) {
+ await do_send_message_error_test(
+ test, root_dir, receiver, target, target_origin, expected_has_source,
+ expected_origin);
+ await do_receive_message_error_test(
+ test, receiver, target, target_origin, expected_has_source,
+ expected_remote_origin);
+}
+
+// Runs the same test as do_send_message_error_test(), but uses a MessagePort.
+// This test starts by establishing a message channel between the test runner
+// and |target|.
+async function do_send_message_port_error_test(
+ test, root_dir, target, target_origin) {
+ const message_port = create_message_channel(target, target_origin);
+ await do_send_message_error_test(
+ test, root_dir, /*receiver=*/message_port, /*target=*/message_port,
+ /*target_origin=*/undefined, /*expected_has_source=*/false,
+ /*expected_origin=*/'', /*expected_remote_origin=*/'');
+}
+
+// Runs the same test as do_receive_message_error_test(), but uses a MessagePort.
+async function do_receive_message_port_error_test(
+ test, target, target_origin) {
+ const message_port = create_message_channel(target, target_origin);
+ await do_receive_message_error_test(
+ test, /*receiver=*/message_port, /*target=*/message_port,
+ /*target_origin=*/undefined, /*expected_has_source=*/false,
+ /*expected_origin=*/'');
+}
+
+// Runs the same test as do_send_and_receive_message_error_test(), but uses a
+// MessagePort.
+async function do_send_and_receive_message_port_error_test(
+ test, root_dir, target, target_origin) {
+ await do_send_message_port_error_test(
+ test, root_dir, target, target_origin);
+ await do_receive_message_port_error_test(
+ test, target, target_origin);
+}
+
+directory_test(async (t, root_dir) => {
+ const iframe = await add_iframe(
+ t, { src: kRemoteOriginDocumentMessageTarget });
+ await do_send_and_receive_message_error_test(
+ t, root_dir, /*receiver=*/self, /*target=*/iframe.contentWindow,
+ /*target_origin=*/'*', /*expected_has_source=*/true,
+ /*expected_origin=*/location.origin,
+ /*expected_remote_origin=*/kRemoteOrigin);
+}, 'Fail to send and receive messages using a cross origin iframe.');
+
+directory_test(async (t, root_dir) => {
+ const iframe = await add_iframe(t, { src: kRemoteOriginDocumentMessageTarget });
+ await do_send_and_receive_message_port_error_test(
+ t, root_dir, /*target=*/iframe.contentWindow, /*target_origin=*/'*');
+}, 'Fail to send and receive messages using a cross origin message port in ' +
+'an iframe.');
+
+directory_test(async (t, root_dir) => {
+ const iframe = await add_iframe(
+ t, { src: kDocumentMessageTarget, sandbox: 'allow-scripts' });
+
+ await do_send_message_error_test(
+ t, root_dir, /*receiver=*/self, /*target=*/iframe.contentWindow,
+ /*target_origin=*/'*', /*expected_has_source*/true,
+ /*expected_origin=*/location.origin);
+}, 'Fail to send to a sandboxed iframe.');
+
+directory_test(async (t, root_dir) => {
+ const iframe = await add_iframe(
+ t, { src: kDocumentMessageTarget, sandbox: 'allow-scripts' });
+ await do_send_message_port_error_test(
+ t, root_dir, /*target=*/iframe.contentWindow, /*target_origin=*/'*');
+}, 'Fail to send messages using a message port to a sandboxed ' +
+'iframe.');
+
+directory_test(async (t, root_dir) => {
+ const iframe_data_uri = await create_message_target_data_uri(t);
+ const iframe = await add_iframe(t, { src: iframe_data_uri });
+ await do_send_message_error_test(t, root_dir, /*receiver=*/self,
+ /*target=*/iframe.contentWindow, /*target_origin=*/'*',
+ /*expected_has_source*/true, /*expected_origin=*/location.origin);
+ // Do not test receiving FileSystemHandles from the data URI iframe. Data URI
+ // iframes are insecure and do not expose the File System APIs.
+}, 'Fail to send messages to a data URI iframe.');
+
+directory_test(async (t, root_dir) => {
+ const iframe_data_uri = await create_message_target_data_uri(t);
+ const iframe = await add_iframe(t, { src: iframe_data_uri });
+ await do_send_message_port_error_test(
+ t, root_dir, /*target=*/iframe.contentWindow, /*target_origin=*/'*');
+}, 'Fail to send messages using a message port in a data URI iframe.');
+
+directory_test(async (t, root_dir) => {
+ const child_window = await open_window(t, kRemoteOriginDocumentMessageTarget);
+ await do_send_and_receive_message_error_test(
+ t, root_dir, /*receiver=*/self, /*target=*/child_window, /*target_origin=*/'*',
+ /*expected_has_source=*/true, /*expected_origin=*/location.origin,
+ /*expected_remote_origin=*/kRemoteOrigin);
+}, 'Fail to send and receive messages using a cross origin window.');
+
+directory_test(async (t, root_dir) => {
+ const child_window = await open_window(t, kRemoteOriginDocumentMessageTarget);
+ await do_send_message_port_error_test(
+ t, root_dir, /*target=*/child_window, /*target_origin=*/'*');
+}, 'Fail to send and receive messages using a cross origin message port in ' +
+'a window.');
+
+directory_test(async (t, root_dir) => {
+ const url = `${kDocumentMessageTarget}?pipe=header(Content-Security-Policy` +
+ ', sandbox allow-scripts)';
+ const child_window = await open_window(t, url);
+ await do_send_message_error_test(
+ t, root_dir, /*receiver=*/self, /*target=*/child_window,
+ /*target_origin=*/'*', /*expected_has_source*/true,
+ /*expected_origin=*/location.origin);
+}, 'Fail to send messages to a sandboxed window.');
+
+directory_test(async (t, root_dir) => {
+ const url = `${kDocumentMessageTarget}?pipe=header(Content-Security-Policy` +
+ ', sandbox allow-scripts)';
+ const child_window = await open_window(t, url);
+ await do_send_message_port_error_test(
+ t, root_dir, /*target=*/child_window, /*target_origin=*/'*');
+}, 'Fail to send messages using a message port to a sandboxed ' +
+'window.');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-frames.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-frames.js
new file mode 100644
index 0000000000..b70b2992c6
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-frames.js
@@ -0,0 +1,44 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /fs/resources/messaging-blob-helpers.js
+// /fs/resources/messaging-serialize-helpers.js
+// /fs/resources/test-helpers.js
+
+directory_test(
+ async (t, root_dir) => {
+ const iframe = await add_iframe(t, {src: kDocumentMessageTarget});
+ await do_message_port_test(
+ t, root_dir, /*target=*/ iframe.contentWindow,
+ /*target_origin=*/ '*');
+ },
+ 'Send and receive messages using a message port in a same origin ' +
+ 'iframe.');
+
+directory_test(
+ async (t, root_dir) => {
+ const iframe = await add_iframe(t, {
+ src: kDocumentMessageTarget,
+ sandbox: 'allow-scripts allow-same-origin'
+ });
+ await do_message_port_test(
+ t, root_dir, /*target=*/ iframe.contentWindow,
+ /*target_origin=*/ '*');
+ },
+ 'Send and receive messages using a message port in a sandboxed same ' +
+ 'origin iframe.');
+
+directory_test(async (t, root_dir) => {
+ const blob_url = await create_message_target_blob_url(t);
+ const iframe = await add_iframe(t, {src: blob_url});
+ await do_message_port_test(
+ t, root_dir, /*target=*/ iframe.contentWindow, /*target_origin=*/ '*');
+}, 'Send and receive messages using a message port in a blob iframe.');
+
+directory_test(async (t, root_dir) => {
+ const iframe_html = await create_message_target_html_without_subresources(t);
+ const iframe = await add_iframe(t, {srcdoc: iframe_html});
+ await do_message_port_test(
+ t, root_dir, /*target=*/ iframe.contentWindow, /*target_origin=*/ '*');
+}, 'Send and receive messages using a message port in an iframe srcdoc.');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-windows.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-windows.js
new file mode 100644
index 0000000000..dceb250ebb
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-windows.js
@@ -0,0 +1,35 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /fs/resources/messaging-blob-helpers.js
+// /fs/resources/messaging-serialize-helpers.js
+// /fs/resources/test-helpers.js
+
+directory_test(
+ async (t, root_dir) => {
+ const child_window = await open_window(t, kDocumentMessageTarget);
+ await do_message_port_test(
+ t, root_dir, /*target=*/ child_window, /*target_origin=*/ '*');
+ },
+ 'Send and receive messages using a message port in a same origin ' +
+ 'window.');
+
+directory_test(async (t, root_dir) => {
+ const blob_url = await create_message_target_blob_url(t);
+ const child_window = await open_window(t, blob_url);
+ await do_message_port_test(
+ t, root_dir, /*target=*/ child_window, /*target_origin=*/ '*');
+}, 'Send and receive messages using a message port in a blob window.');
+
+directory_test(
+ async (t, root_dir) => {
+ const url =
+ `${kDocumentMessageTarget}?pipe=header(Content-Security-Policy` +
+ ', sandbox allow-scripts allow-same-origin)';
+ const child_window = await open_window(t, url);
+ await do_message_port_test(
+ t, root_dir, /*target=*/ child_window, /*target_origin=*/ '*');
+ },
+ 'Send and receive messages using a message port in a sandboxed same ' +
+ 'origin window.');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-workers.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-workers.js
new file mode 100644
index 0000000000..b386527dbd
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-workers.js
@@ -0,0 +1,40 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /fs/resources/messaging-blob-helpers.js
+// /fs/resources/messaging-serialize-helpers.js
+// /fs/resources/test-helpers.js
+// /service-workers/service-worker/resources/test-helpers.sub.js
+
+directory_test(
+ async (t, root_dir) => {
+ const dedicated_worker =
+ create_dedicated_worker(t, kDedicatedWorkerMessageTarget);
+ await do_message_port_test(t, root_dir, /*target=*/ dedicated_worker);
+ },
+ 'Send and receive messages using a message port in a dedicated ' +
+ 'worker.');
+
+directory_test(
+ async (t, root_dir) => {
+ const scope = `${kServiceWorkerMessageTarget}` +
+ '?post-message-to-message-port-with-file-handle';
+ const registration =
+ await create_service_worker(t, kServiceWorkerMessageTarget, scope);
+ await do_message_port_test(
+ t, root_dir, /*target=*/ registration.installing);
+ },
+ 'Send and receive messages using a message port in a service ' +
+ 'worker.');
+
+if (self.SharedWorker !== undefined) {
+ directory_test(
+ async (t, root_dir) => {
+ const shared_worker = new SharedWorker(kSharedWorkerMessageTarget);
+ shared_worker.port.start();
+ await do_message_port_test(t, root_dir, /*target=*/ shared_worker.port);
+ },
+ 'Send and receive messages using a message port in a shared ' +
+ ' worker.');
+}
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-frames.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-frames.js
new file mode 100644
index 0000000000..1e77b89d77
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-frames.js
@@ -0,0 +1,40 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /fs/resources/messaging-blob-helpers.js
+// /fs/resources/messaging-serialize-helpers.js
+// /fs/resources/test-helpers.js
+
+directory_test(async (t, root_dir) => {
+ const iframe = await add_iframe(t, {src: kDocumentMessageTarget});
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ self, /*target=*/ iframe.contentWindow,
+ /*target_origin=*/ '*');
+}, 'Send and receive messages using a same origin iframe.');
+
+directory_test(async (t, root_dir) => {
+ const iframe = await add_iframe(t, {
+ src: kDocumentMessageTarget,
+ sandbox: 'allow-scripts allow-same-origin'
+ });
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ self, /*target=*/ iframe.contentWindow,
+ /*target_origin=*/ '*');
+}, 'Send and receive messages using a sandboxed same origin iframe.');
+
+directory_test(async (t, root_dir) => {
+ const blob_url = await create_message_target_blob_url(t);
+ const iframe = await add_iframe(t, {src: blob_url});
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ self, /*target=*/ iframe.contentWindow,
+ /*target_origin=*/ '*');
+}, 'Send and receive messages using a blob iframe.');
+
+directory_test(async (t, root_dir) => {
+ const iframe_html = await create_message_target_html_without_subresources(t);
+ const iframe = await add_iframe(t, {srcdoc: iframe_html});
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ self, /*target=*/ iframe.contentWindow,
+ /*target_origin=*/ '*');
+}, 'Send and receive messages using an iframe srcdoc.');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-windows.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-windows.js
new file mode 100644
index 0000000000..798d458534
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-windows.js
@@ -0,0 +1,31 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /fs/resources/messaging-blob-helpers.js
+// /fs/resources/messaging-serialize-helpers.js
+// /fs/resources/test-helpers.js
+
+directory_test(async (t, root_dir) => {
+ const child_window = await open_window(t, kDocumentMessageTarget);
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ self, /*target=*/ child_window,
+ /*target_origin=*/ '*');
+}, 'Send and receive messages using a same origin window.');
+
+directory_test(async (t, root_dir) => {
+ const blob_url = await create_message_target_blob_url(t);
+ const child_window = await open_window(t, blob_url);
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ self, /*target=*/ child_window,
+ /*target_origin=*/ '*');
+}, 'Send and receive messages using a blob window.');
+
+directory_test(async (t, root_dir) => {
+ const url = `${kDocumentMessageTarget}?pipe=header(Content-Security-Policy` +
+ ', sandbox allow-scripts allow-same-origin)';
+ const child_window = await open_window(t, url);
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ self, /*target=*/ child_window,
+ /*target_origin=*/ '*');
+}, 'Send and receive messages using a sandboxed same origin window.');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-workers.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-workers.js
new file mode 100644
index 0000000000..dbd8e5754d
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-workers.js
@@ -0,0 +1,35 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /fs/resources/messaging-blob-helpers.js
+// /fs/resources/messaging-serialize-helpers.js
+// /fs/resources/test-helpers.js
+// /service-workers/service-worker/resources/test-helpers.sub.js
+
+directory_test(async (t, root_dir) => {
+ const dedicated_worker =
+ create_dedicated_worker(t, kDedicatedWorkerMessageTarget);
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ dedicated_worker,
+ /*target=*/ dedicated_worker);
+}, 'Send and receive messages using a dedicated worker.');
+
+directory_test(async (t, root_dir) => {
+ const scope = `${kServiceWorkerMessageTarget}?post-message-with-file-handle`;
+ const registration =
+ await create_service_worker(t, kServiceWorkerMessageTarget, scope);
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ navigator.serviceWorker,
+ /*target=*/ registration.installing);
+}, 'Send and receive messages using a service worker.');
+
+if (self.SharedWorker !== undefined) {
+ directory_test(async (t, root_dir) => {
+ const shared_worker = new SharedWorker(kSharedWorkerMessageTarget);
+ shared_worker.port.start();
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ shared_worker.port,
+ /*target=*/ shared_worker.port);
+ }, 'Send and receive messages using a shared worker.');
+}
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-remove.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-remove.js
new file mode 100644
index 0000000000..021576310b
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-remove.js
@@ -0,0 +1,105 @@
+'use strict';
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await handle.remove();
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+ await promise_rejects_dom(t, 'NotFoundError', getFileContents(handle));
+}, 'remove() to remove a file');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await handle.remove();
+
+ await promise_rejects_dom(t, 'NotFoundError', handle.remove());
+}, 'remove() on an already removed file should fail');
+
+directory_test(async (t, root) => {
+ const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await dir.remove();
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+ await promise_rejects_dom(t, 'NotFoundError', getSortedDirectoryEntries(dir));
+}, 'remove() to remove an empty directory');
+
+directory_test(async (t, root) => {
+ const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+ await dir.remove();
+
+ await promise_rejects_dom(t, 'NotFoundError', dir.remove());
+}, 'remove() on an already removed directory should fail');
+
+directory_test(async (t, root) => {
+ const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+ t.add_cleanup(() => root.removeEntry('dir-to-remove', {recursive: true}));
+ await createEmptyFile(t, 'file-in-dir', dir);
+
+ await promise_rejects_dom(t, 'InvalidModificationError', dir.remove());
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir-to-remove/']);
+ assert_array_equals(await getSortedDirectoryEntries(dir), ['file-in-dir']);
+}, 'remove() on a non-empty directory should fail');
+
+directory_test(async (t, root) => {
+ // root
+ // ├──file-to-keep
+ // ├──dir-to-remove
+ // ├── file0
+ // ├── dir1-in-dir
+ // │   └── file1
+ // └── dir2
+ const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await createEmptyFile(t, 'file0', dir);
+ const dir1_in_dir = await createDirectory(t, 'dir1-in-dir', dir);
+ await createEmptyFile(t, 'file1', dir1_in_dir);
+ await createDirectory(t, 'dir2-in-dir', dir);
+
+ await dir.remove({recursive: true});
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+}, 'remove() on a directory recursively should delete all sub-items');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await handle.remove({recursive: true});
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+ await promise_rejects_dom(t, 'NotFoundError', getFileContents(handle));
+}, 'remove() on a file should ignore the recursive option');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+
+ const writable = await cleanup_writable(t, await handle.createWritable());
+ await promise_rejects_dom(t, 'NoModificationAllowedError', handle.remove());
+
+ await writable.close();
+ assert_array_equals(
+ await getSortedDirectoryEntries(root),
+ ['file-to-keep', 'file-to-remove']);
+
+ await handle.remove();
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+ await promise_rejects_dom(t, 'NotFoundError', getFileContents(handle));
+}, 'remove() while the file has an open writable fails');
+
+promise_test(async (t) => {
+ const root = await navigator.storage.getDirectory();
+ await root.getFileHandle('file.txt', {create: true});
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file.txt']);
+
+ await root.remove();
+
+ // Creates a fresh sandboxed file system.
+ const newRoot = await navigator.storage.getDirectory();
+ assert_array_equals(await getSortedDirectoryEntries(newRoot), []);
+}, 'can remove the root of a sandbox file system');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getDirectoryHandle.js b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getDirectoryHandle.js
new file mode 100644
index 0000000000..6a63edecc5
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getDirectoryHandle.js
@@ -0,0 +1,117 @@
+'use strict';
+
+directory_test(async (t, root) => {
+ await promise_rejects_dom(
+ t, 'NotFoundError', root.getDirectoryHandle('non-existing-dir'));
+}, 'getDirectoryHandle(create=false) rejects for non-existing directories');
+
+directory_test(async (t, root) => {
+ const handle =
+ await root.getDirectoryHandle('non-existing-dir', {create: true});
+ t.add_cleanup(() => root.removeEntry('non-existing-dir', {recursive: true}));
+
+ assert_equals(handle.kind, 'directory');
+ assert_equals(handle.name, 'non-existing-dir');
+ assert_equals(await getDirectoryEntryCount(handle), 0);
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['non-existing-dir/']);
+}, 'getDirectoryHandle(create=true) creates an empty directory');
+
+directory_test(async (t, root) => {
+ const existing_handle =
+ await root.getDirectoryHandle('dir-with-contents', {create: true});
+ t.add_cleanup(() => root.removeEntry('dir-with-contents', {recursive: true}));
+ const file_handle = await createEmptyFile(t, 'test-file', existing_handle);
+
+ const handle =
+ await root.getDirectoryHandle('dir-with-contents', {create: false});
+
+ assert_equals(handle.kind, 'directory');
+ assert_equals(handle.name, 'dir-with-contents');
+ assert_array_equals(await getSortedDirectoryEntries(handle), ['test-file']);
+}, 'getDirectoryHandle(create=false) returns existing directories');
+
+directory_test(async (t, root) => {
+ const existing_handle =
+ await root.getDirectoryHandle('dir-with-contents', {create: true});
+ t.add_cleanup(() => root.removeEntry('dir-with-contents', {recursive: true}));
+ const file_handle =
+ await existing_handle.getFileHandle('test-file', {create: true});
+
+ const handle =
+ await root.getDirectoryHandle('dir-with-contents', {create: true});
+
+ assert_equals(handle.kind, 'directory');
+ assert_equals(handle.name, 'dir-with-contents');
+ assert_array_equals(await getSortedDirectoryEntries(handle), ['test-file']);
+}, 'getDirectoryHandle(create=true) returns existing directories without erasing');
+
+directory_test(async (t, root) => {
+ await createEmptyFile(t, 'file-name', root);
+
+ await promise_rejects_dom(
+ t, 'TypeMismatchError', root.getDirectoryHandle('file-name'));
+ await promise_rejects_dom(
+ t, 'TypeMismatchError',
+ root.getDirectoryHandle('file-name', {create: false}));
+ await promise_rejects_dom(
+ t, 'TypeMismatchError',
+ root.getDirectoryHandle('file-name', {create: true}));
+}, 'getDirectoryHandle() when a file already exists with the same name');
+
+directory_test(async (t, dir) => {
+ await promise_rejects_js(
+ t, TypeError, dir.getDirectoryHandle('', {create: true}));
+ await promise_rejects_js(
+ t, TypeError, dir.getDirectoryHandle('', {create: false}));
+}, 'getDirectoryHandle() with empty name');
+
+directory_test(async (t, dir) => {
+ await promise_rejects_js(
+ t, TypeError, dir.getDirectoryHandle(kCurrentDirectory));
+ await promise_rejects_js(
+ t, TypeError, dir.getDirectoryHandle(kCurrentDirectory, {create: true}));
+}, `getDirectoryHandle() with "${kCurrentDirectory}" name`);
+
+directory_test(async (t, dir) => {
+ const subdir = await createDirectory(t, 'subdir-name', /*parent=*/ dir);
+
+ await promise_rejects_js(
+ t, TypeError, subdir.getDirectoryHandle(kParentDirectory));
+ await promise_rejects_js(
+ t, TypeError,
+ subdir.getDirectoryHandle(kParentDirectory, {create: true}));
+}, `getDirectoryHandle() with "${kParentDirectory}" name`);
+
+directory_test(async (t, dir) => {
+ const first_subdir_name = 'first-subdir-name';
+ const first_subdir =
+ await createDirectory(t, first_subdir_name, /*parent=*/ dir);
+
+ const second_subdir_name = 'second-subdir-name';
+ const second_subdir =
+ await createDirectory(t, second_subdir_name, /*parent=*/ first_subdir);
+
+ for (let i = 0; i < kPathSeparators.length; ++i) {
+ const path_with_separator =
+ `${first_subdir_name}${kPathSeparators[i]}${second_subdir_name}`;
+ await promise_rejects_js(
+ t, TypeError, dir.getDirectoryHandle(path_with_separator),
+ `getDirectoryHandle() must reject names containing "${
+ kPathSeparators[i]}"`);
+ }
+}, 'getDirectoryHandle(create=false) with a path separator when the directory exists');
+
+directory_test(async (t, dir) => {
+ const subdir_name = 'subdir-name';
+ const subdir = await createDirectory(t, subdir_name, /*parent=*/ dir);
+
+ for (let i = 0; i < kPathSeparators.length; ++i) {
+ const path_with_separator = `${subdir_name}${kPathSeparators[i]}file_name`;
+ await promise_rejects_js(
+ t, TypeError,
+ dir.getDirectoryHandle(path_with_separator, {create: true}),
+ `getDirectoryHandle(true) must reject names containing "${
+ kPathSeparators[i]}"`);
+ }
+}, 'getDirectoryHandle(create=true) with a path separator');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getFileHandle.js b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getFileHandle.js
new file mode 100644
index 0000000000..840e85b436
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getFileHandle.js
@@ -0,0 +1,145 @@
+'use strict';
+
+directory_test(async (t, dir) => {
+ await promise_rejects_dom(
+ t, 'NotFoundError', dir.getFileHandle('non-existing-file'));
+}, 'getFileHandle(create=false) rejects for non-existing files');
+
+directory_test(async (t, dir) => {
+ const handle = await dir.getFileHandle('non-existing-file', {create: true});
+ t.add_cleanup(() => dir.removeEntry('non-existing-file'));
+
+ assert_equals(handle.kind, 'file');
+ assert_equals(handle.name, 'non-existing-file');
+ assert_equals(await getFileSize(handle), 0);
+ assert_equals(await getFileContents(handle), '');
+}, 'getFileHandle(create=true) creates an empty file for non-existing files');
+
+directory_test(async (t, dir) => {
+ var name = '';
+ // test the ascii characters -- start after the non-character ASCII values, exclude DEL
+ for (let i = 32; i < 127; i++) {
+ // Path separators are disallowed
+ let disallow = false;
+ for (let j = 0; j < kPathSeparators.length; ++j) {
+ if (String.fromCharCode(i) == kPathSeparators[j]) {
+ disallow = true;
+ }
+ }
+ if (!disallow) {
+ name += String.fromCharCode(i);
+ }
+ }
+ // Add in CR, LF, FF, Tab, Vertical Tab
+ for (let i = 9; i < 14; i++) {
+ name += String.fromCharCode(i);
+ }
+ const handle = await dir.getFileHandle(name, {create: true});
+ t.add_cleanup(() => dir.removeEntry(name));
+
+ assert_equals(handle.kind, 'file');
+ assert_equals(handle.name, name);
+ assert_equals(await getFileSize(handle), 0);
+ assert_equals(await getFileContents(handle), '');
+}, 'getFileHandle(create=true) creates an empty file with all valid ASCII characters in the name');
+
+directory_test(async (t, dir) => {
+ var name;
+ // A non-ASCII name
+ name = 'Funny cat \u{1F639}'
+ const handle = await dir.getFileHandle(name, {create: true});
+ t.add_cleanup(() => dir.removeEntry(name));
+
+ assert_equals(handle.kind, 'file');
+ assert_equals(handle.name, name);
+ assert_equals(await getFileSize(handle), 0);
+ assert_equals(await getFileContents(handle), '');
+}, 'getFileHandle(create=true) creates an empty file with non-ASCII characters in the name');
+
+directory_test(async (t, dir) => {
+ const existing_handle = await createFileWithContents(
+ t, 'existing-file', '1234567890', /*parent=*/ dir);
+ const handle = await dir.getFileHandle('existing-file');
+
+ assert_equals(handle.kind, 'file');
+ assert_equals(handle.name, 'existing-file');
+ assert_equals(await getFileSize(handle), 10);
+ assert_equals(await getFileContents(handle), '1234567890');
+}, 'getFileHandle(create=false) returns existing files');
+
+directory_test(async (t, dir) => {
+ const existing_handle = await createFileWithContents(
+ t, 'file-with-contents', '1234567890', /*parent=*/ dir);
+ const handle = await dir.getFileHandle('file-with-contents', {create: true});
+
+ assert_equals(handle.kind, 'file');
+ assert_equals(handle.name, 'file-with-contents');
+ assert_equals(await getFileSize(handle), 10);
+ assert_equals(await getFileContents(handle), '1234567890');
+}, 'getFileHandle(create=true) returns existing files without erasing');
+
+directory_test(async (t, dir) => {
+ const dir_handle = await dir.getDirectoryHandle('dir-name', {create: true});
+ t.add_cleanup(() => dir.removeEntry('dir-name', {recursive: true}));
+
+ await promise_rejects_dom(
+ t, 'TypeMismatchError', dir.getFileHandle('dir-name'));
+}, 'getFileHandle(create=false) when a directory already exists with the same name');
+
+directory_test(async (t, dir) => {
+ const dir_handle = await dir.getDirectoryHandle('dir-name', {create: true});
+ t.add_cleanup(() => dir.removeEntry('dir-name', {recursive: true}));
+
+ await promise_rejects_dom(
+ t, 'TypeMismatchError', dir.getFileHandle('dir-name', {create: true}));
+}, 'getFileHandle(create=true) when a directory already exists with the same name');
+
+directory_test(async (t, dir) => {
+ await promise_rejects_js(t, TypeError, dir.getFileHandle('', {create: true}));
+ await promise_rejects_js(
+ t, TypeError, dir.getFileHandle('', {create: false}));
+}, 'getFileHandle() with empty name');
+
+directory_test(async (t, dir) => {
+ await promise_rejects_js(t, TypeError, dir.getFileHandle(kCurrentDirectory));
+ await promise_rejects_js(
+ t, TypeError, dir.getFileHandle(kCurrentDirectory, {create: true}));
+}, `getFileHandle() with "${kCurrentDirectory}" name`);
+
+directory_test(async (t, dir) => {
+ const subdir = await createDirectory(t, 'subdir-name', /*parent=*/ dir);
+
+ await promise_rejects_js(
+ t, TypeError, subdir.getFileHandle(kParentDirectory));
+ await promise_rejects_js(
+ t, TypeError, subdir.getFileHandle(kParentDirectory, {create: true}));
+}, `getFileHandle() with "${kParentDirectory}" name`);
+
+directory_test(async (t, dir) => {
+ const subdir_name = 'subdir-name';
+ const subdir = await createDirectory(t, subdir_name, /*parent=*/ dir);
+
+ const file_name = 'file-name';
+ await createEmptyFile(t, file_name, /*parent=*/ subdir);
+
+ for (let i = 0; i < kPathSeparators.length; ++i) {
+ const path_with_separator =
+ `${subdir_name}${kPathSeparators[i]}${file_name}`;
+ await promise_rejects_js(
+ t, TypeError, dir.getFileHandle(path_with_separator),
+ `getFileHandle() must reject names containing "${kPathSeparators[i]}"`);
+ }
+}, 'getFileHandle(create=false) with a path separator when the file exists.');
+
+directory_test(async (t, dir) => {
+ const subdir_name = 'subdir-name';
+ const subdir = await createDirectory(t, subdir_name, /*parent=*/ dir);
+
+ for (let i = 0; i < kPathSeparators.length; ++i) {
+ const path_with_separator = `${subdir_name}${kPathSeparators[i]}file_name`;
+ await promise_rejects_js(
+ t, TypeError, dir.getFileHandle(path_with_separator, {create: true}),
+ `getFileHandle(create=true) must reject names containing "${
+ kPathSeparators[i]}"`);
+ }
+}, 'getFileHandle(create=true) with a path separator');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-iteration.js b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-iteration.js
new file mode 100644
index 0000000000..815ae21936
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-iteration.js
@@ -0,0 +1,100 @@
+'use strict';
+
+directory_test(async (t, root) => {
+ const file_name1 = 'foo1.txt';
+ const file_name2 = 'foo2.txt';
+ await createFileWithContents(t, file_name1, 'contents', /*parent=*/ root);
+ await createFileWithContents(t, file_name2, 'contents', /*parent=*/ root);
+
+ for await (let entry of root) {
+ break;
+ }
+
+}, 'returning early from an iteration doesn\'t crash');
+
+directory_test(async (t, root) => {
+ const file_name1 = 'foo1.txt';
+ const file_name2 = 'foo2.txt';
+ await createFileWithContents(t, file_name1, 'contents', /*parent=*/ root);
+ await createFileWithContents(t, file_name2, 'contents', /*parent=*/ root);
+
+ let names = [];
+ for await (let entry of root) {
+ assert_true(Array.isArray(entry));
+ assert_equals(entry.length, 2);
+ assert_equals(typeof entry[0], 'string');
+ assert_true(entry[1] instanceof FileSystemFileHandle);
+ assert_equals(entry[0], entry[1].name);
+ names.push(entry[0]);
+ }
+ names.sort();
+ assert_array_equals(names, [file_name1, file_name2]);
+
+}, '@@asyncIterator: full iteration works');
+
+directory_test(async (t, root) => {
+ const file_name1 = 'foo1.txt';
+ const file_name2 = 'foo2.txt';
+ await createFileWithContents(t, file_name1, 'contents', /*parent=*/ root);
+ await createFileWithContents(t, file_name2, 'contents', /*parent=*/ root);
+
+ let names = [];
+ for await (let entry of root.entries()) {
+ assert_true(Array.isArray(entry));
+ assert_equals(entry.length, 2);
+ assert_equals(typeof entry[0], 'string');
+ assert_true(entry[1] instanceof FileSystemFileHandle);
+ assert_equals(entry[0], entry[1].name);
+ names.push(entry[0]);
+ }
+ names.sort();
+ assert_array_equals(names, [file_name1, file_name2]);
+}, 'entries: full iteration works');
+
+directory_test(async (t, root) => {
+ const file_name1 = 'foo1.txt';
+ const file_name2 = 'foo2.txt';
+ await createFileWithContents(t, file_name1, 'contents', /*parent=*/ root);
+ await createFileWithContents(t, file_name2, 'contents', /*parent=*/ root);
+
+ let names = [];
+ for await (let entry of root.values()) {
+ assert_true(entry instanceof FileSystemFileHandle);
+ names.push(entry.name);
+ }
+ names.sort();
+ assert_array_equals(names, [file_name1, file_name2]);
+}, 'values: full iteration works');
+
+directory_test(async (t, root) => {
+ const file_name1 = 'foo1.txt';
+ const file_name2 = 'foo2.txt';
+ await createFileWithContents(t, file_name1, 'contents', /*parent=*/ root);
+ await createFileWithContents(t, file_name2, 'contents', /*parent=*/ root);
+
+ let names = [];
+ for await (let entry of root.keys()) {
+ assert_equals(typeof entry, 'string');
+ names.push(entry);
+ }
+ names.sort();
+ assert_array_equals(names, [file_name1, file_name2]);
+}, 'keys: full iteration works');
+
+directory_test(async (t, root) => {
+ const file_name1 = 'foo1.txt';
+ await createFileWithContents(t, file_name1, 'contents', /*parent=*/ root);
+
+ const next = (() => {
+ const iterator = root.entries();
+ return iterator.next();
+ })();
+ garbageCollect();
+ let entry = await next;
+ assert_false(entry.done);
+ assert_true(Array.isArray(entry.value));
+ assert_equals(entry.value.length, 2);
+ assert_equals(entry.value[0], file_name1);
+ assert_true(entry.value[1] instanceof FileSystemFileHandle);
+ assert_equals(entry.value[1].name, file_name1);
+}, 'iteration while iterator gets garbage collected');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-removeEntry.js b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-removeEntry.js
new file mode 100644
index 0000000000..07d26e9011
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-removeEntry.js
@@ -0,0 +1,222 @@
+'use strict';
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await root.removeEntry('file-to-remove');
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+ await promise_rejects_dom(t, 'NotFoundError', getFileContents(handle));
+}, 'removeEntry() to remove a file');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await root.removeEntry('file-to-remove');
+
+ await promise_rejects_dom(
+ t, 'NotFoundError', root.removeEntry('file-to-remove'));
+}, 'removeEntry() on an already removed file should fail');
+
+directory_test(async (t, root) => {
+ const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await root.removeEntry('dir-to-remove');
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+}, 'removeEntry() to remove an empty directory');
+
+directory_test(async (t, root) => {
+ const dir = await createDirectory(t, 'dir-to-remove', root);
+ await createFileWithContents(t, 'file-in-dir', 'abc', dir);
+
+ await promise_rejects_dom(
+ t, 'InvalidModificationError', root.removeEntry('dir-to-remove'));
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir-to-remove/']);
+ assert_array_equals(await getSortedDirectoryEntries(dir), ['file-in-dir']);
+}, 'removeEntry() on a non-empty directory should fail');
+
+directory_test(async (t, root) => {
+ // root
+ // ├──file-to-keep
+ // ├──dir-to-remove
+ // ├── file0
+ // ├── dir1-in-dir
+ // │   └── file1
+ // └── dir2
+ const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await createEmptyFile(t, 'file0', dir);
+ const dir1_in_dir = await createDirectory(t, 'dir1-in-dir', dir);
+ await createEmptyFile(t, 'file1', dir1_in_dir);
+ await createDirectory(t, 'dir2-in-dir', dir);
+
+ await root.removeEntry('dir-to-remove', {recursive: true});
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+}, 'removeEntry() on a directory recursively should delete all sub-items');
+
+directory_test(async (t, root) => {
+ const dir = await createDirectory(t, 'dir', root);
+ await promise_rejects_js(t, TypeError, dir.removeEntry(''));
+}, 'removeEntry() with empty name should fail');
+
+directory_test(async (t, root) => {
+ const dir = await createDirectory(t, 'dir', root);
+ await promise_rejects_js(t, TypeError, dir.removeEntry(kCurrentDirectory));
+}, `removeEntry() with "${kCurrentDirectory}" name should fail`);
+
+directory_test(async (t, root) => {
+ const dir = await createDirectory(t, 'dir', root);
+ await promise_rejects_js(t, TypeError, dir.removeEntry(kParentDirectory));
+}, `removeEntry() with "${kParentDirectory}" name should fail`);
+
+directory_test(async (t, root) => {
+ const dir_name = 'dir-name';
+ const dir = await createDirectory(t, dir_name, root);
+
+ const file_name = 'file-name';
+ await createEmptyFile(t, file_name, dir);
+
+ for (let i = 0; i < kPathSeparators.length; ++i) {
+ const path_with_separator = `${dir_name}${kPathSeparators[i]}${file_name}`;
+ await promise_rejects_js(
+ t, TypeError, root.removeEntry(path_with_separator),
+ `removeEntry() must reject names containing "${kPathSeparators[i]}"`);
+ }
+}, 'removeEntry() with a path separator should fail.');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await root.removeEntry('file-to-remove');
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+ await promise_rejects_dom(t, 'NotFoundError', getFileContents(handle));
+}, 'removeEntry() to remove a file');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await root.removeEntry('file-to-remove');
+
+ await promise_rejects_dom(t, 'NotFoundError', root.removeEntry('file-to-remove'));
+}, 'removeEntry() on an already removed file should fail');
+
+directory_test(async (t, root) => {
+ const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await root.removeEntry('dir-to-remove');
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+}, 'removeEntry() to remove an empty directory');
+
+directory_test(async (t, root) => {
+ const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+ t.add_cleanup(() => root.removeEntry('dir-to-remove', {recursive: true}));
+ await createEmptyFile(t, 'file-in-dir', dir);
+
+ await promise_rejects_dom(
+ t, 'InvalidModificationError', root.removeEntry('dir-to-remove'));
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir-to-remove/']);
+ assert_array_equals(await getSortedDirectoryEntries(dir), ['file-in-dir']);
+}, 'removeEntry() on a non-empty directory should fail');
+
+directory_test(async (t, root) => {
+ // root
+ // ├──file-to-keep
+ // ├──dir-to-remove
+ // ├── file0
+ // ├── dir1-in-dir
+ // │   └── file1
+ // └── dir2
+ const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await createEmptyFile(t, 'file0', dir);
+ const dir1_in_dir = await createDirectory(t, 'dir1-in-dir', dir);
+ await createEmptyFile(t, 'file1', dir1_in_dir);
+ await createDirectory(t, 'dir2-in-dir', dir);
+
+ await root.removeEntry('dir-to-remove', {recursive: true});
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+}, 'removeEntry() on a directory recursively should delete all sub-items');
+
+directory_test(async (t, root) => {
+ const dir = await createDirectory(t, 'dir', root);
+ await promise_rejects_js(t, TypeError, dir.removeEntry(''));
+}, 'removeEntry() with empty name should fail');
+
+directory_test(async (t, root) => {
+ const dir = await createDirectory(t, 'dir', root);
+ await promise_rejects_js(t, TypeError, dir.removeEntry(kCurrentDirectory));
+}, `removeEntry() with "${kCurrentDirectory}" name should fail`);
+
+directory_test(async (t, root) => {
+ const dir = await createDirectory(t, 'dir', root);
+ await promise_rejects_js(t, TypeError, dir.removeEntry(kParentDirectory));
+}, `removeEntry() with "${kParentDirectory}" name should fail`);
+
+directory_test(async (t, root) => {
+ const dir_name = 'dir-name';
+ const dir = await createDirectory(t, dir_name, root);
+
+ const file_name = 'file-name';
+ await createEmptyFile(t, file_name, dir);
+
+ for (let i = 0; i < kPathSeparators.length; ++i) {
+ const path_with_separator = `${dir_name}${kPathSeparators[i]}${file_name}`;
+ await promise_rejects_js(
+ t, TypeError, root.removeEntry(path_with_separator),
+ `removeEntry() must reject names containing "${kPathSeparators[i]}"`);
+ }
+}, 'removeEntry() with a path separator should fail.');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+
+ const writable = await cleanup_writable(t, await handle.createWritable());
+ await promise_rejects_dom(
+ t, 'NoModificationAllowedError', root.removeEntry('file-to-remove'));
+
+ await writable.close();
+ await root.removeEntry('file-to-remove');
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+}, 'removeEntry() while the file has an open writable fails');
+
+directory_test(async (t, root) => {
+ const dir_name = 'dir-name';
+ const dir = await createDirectory(t, dir_name, root);
+
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', dir);
+ await createFileWithContents(t, 'file-to-keep', 'abc', dir);
+
+ const writable = await cleanup_writable(t, await handle.createWritable());
+ await promise_rejects_dom(
+ t, 'NoModificationAllowedError', root.removeEntry(dir_name));
+
+ await writable.close();
+ assert_array_equals(
+ await getSortedDirectoryEntries(dir), ['file-to-keep', 'file-to-remove']);
+
+ await dir.removeEntry('file-to-remove');
+ assert_array_equals(await getSortedDirectoryEntries(dir), ['file-to-keep']);
+}, 'removeEntry() of a directory while a containing file has an open writable fails');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await root.removeEntry('file-to-remove');
+
+ await promise_rejects_dom(t, 'NotFoundError', cleanup_writable(t, handle.createWritable({keepExistingData: true})));
+
+ assert_array_equals(
+ await getSortedDirectoryEntries(root),
+ []);
+}, 'createWritable after removeEntry succeeds but doesnt recreate the file');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-resolve.js b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-resolve.js
new file mode 100644
index 0000000000..a8900f97e5
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-resolve.js
@@ -0,0 +1,27 @@
+'use strict';
+
+directory_test(async (t, root_dir) => {
+ assert_array_equals(await root_dir.resolve(root_dir), []);
+}, 'Resolve returns empty array for same directory');
+
+directory_test(async (t, root_dir) => {
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+ const file = await createEmptyFile(t, 'file-name', subdir);
+
+ assert_array_equals(await root_dir.resolve(file), ['subdir-name', 'file-name']);
+}, 'Resolve returns correct path');
+
+directory_test(async (t, root_dir) => {
+ const subdir = await createDirectory(t, 'subdir😊', root_dir);
+ const file = await createEmptyFile(t, 'file-name', subdir);
+
+ assert_array_equals(await root_dir.resolve(file), ['subdir😊', 'file-name']);
+ assert_array_equals(await root_dir.resolve(subdir), ['subdir😊']);
+}, 'Resolve returns correct path with non-ascii characters');
+
+directory_test(async (t, root_dir) => {
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+ const file = await createEmptyFile(t, 'file-name', root_dir);
+
+ assert_equals(await subdir.resolve(file), null);
+}, 'Resolve returns null when entry is not a child');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-create-sync-access-handle.js b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-create-sync-access-handle.js
new file mode 100644
index 0000000000..b9eafaf0d8
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-create-sync-access-handle.js
@@ -0,0 +1,27 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /fs/resources/test-helpers.js
+
+directory_test(async (t, root_dir) => {
+ const fileSystemType = getFileSystemType();
+ assert_true(
+ fileSystemType == 'sandboxed' || fileSystemType == 'local',
+ 'File system type should be sandboxed or local.');
+ const expect_success = fileSystemType == 'sandboxed';
+
+ const dedicated_worker =
+ create_dedicated_worker(t, kDedicatedWorkerMessageTarget);
+ const file_handle =
+ await root_dir.getFileHandle('sync-access-handle-file', {create: true});
+
+ dedicated_worker.postMessage(
+ {type: 'create-sync-access-handle', file_handle});
+
+ const event_watcher = new EventWatcher(t, dedicated_worker, 'message');
+ const message_event = await event_watcher.wait_for('message');
+ const response = message_event.data;
+
+ assert_equals(response.success, expect_success);
+}, 'Attempt to create a sync access handle.');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-getFile.js b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-getFile.js
new file mode 100644
index 0000000000..be9fbcca6e
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-getFile.js
@@ -0,0 +1,52 @@
+'use strict';
+
+directory_test(async (t, root) => {
+ const fileContents = 'awesome content';
+ let handle = await createFileWithContents(t, 'foo.txt', fileContents, /*parent=*/ root);
+ let file = await handle.getFile();
+ let slice = file.slice(1, file.size);
+ let actualContents = await slice.text();
+ assert_equals(actualContents, fileContents.slice(1, fileContents.length));
+}, 'getFile() provides a file that can be sliced');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'mtime.txt', root);
+ let file = await handle.getFile();
+ const first_mtime = file.lastModified;
+
+ // We wait for 2s here to ensure that the files do not have the
+ // same modification time. Some filesystems have low resolutions
+ // for modification timestamps.
+ let timeout = new Promise(resolve => {
+ t.step_timeout(resolve, 2000);
+ });
+ await timeout;
+
+ const writer = await cleanup_writable(t, await handle.createWritable({keepExistingData: false}));
+ await writer.write(new Blob(['foo']));
+ await writer.close();
+
+ file = await handle.getFile();
+ const second_mtime = file.lastModified;
+
+ // We wait for 5 ms here to ensure that `lastModified`
+ // from the File objects is stable between getFile invocations.
+ timeout = new Promise(resolve => {
+ t.step_timeout(resolve, 5);
+ });
+ await timeout;
+ let fileReplica = await handle.getFile();
+ assert_equals(second_mtime, fileReplica.lastModified);
+
+ assert_less_than(first_mtime, second_mtime);
+}, 'getFile() returns last modified time');
+
+directory_test(async (t, root) => {
+ const fileName = "fileAttributesTest.txt";
+
+ const fileHandle = await createEmptyFile(t, fileName, root);
+ assert_equals(fileHandle.name, fileName);
+
+ const file = await fileHandle.getFile();
+ assert_equals(file.name, fileName);
+}, 'getFile() returns expected name');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-move.js b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-move.js
new file mode 100644
index 0000000000..c5d08f305d
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-move.js
@@ -0,0 +1,359 @@
+// META: script=resources/test-helpers.js
+
+'use strict';
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(t, 'file-before', 'foo', root);
+ await handle.move('file-after');
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-after']);
+ assert_equals(await getFileContents(handle), 'foo');
+ assert_equals(await getFileSize(handle), 3);
+}, 'move(name) to rename a file');
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(t, 'file-before', 'foo', root);
+ await handle.move('file-after');
+ const newhandle = await root.getFileHandle('file-after');
+ assert_equals(await getFileContents(newhandle), 'foo');
+ assert_equals(await getFileSize(newhandle), 3);
+}, 'get a handle to a moved file');
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(t, 'file-before', 'foo', root);
+ await handle.move('file-before');
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-before']);
+ assert_equals(await getFileContents(handle), 'foo');
+ assert_equals(await getFileSize(handle), 3);
+}, 'move(name) to rename a file the same name');
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(t, 'file-before', 'foo', root);
+ await promise_rejects_js(t, TypeError, handle.move(''));
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-before']);
+ assert_equals(await getFileContents(handle), 'foo');
+ assert_equals(await getFileSize(handle), 3);
+}, 'move("") to rename a file fails');
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(t, 'file-1', 'foo', root);
+
+ await handle.move('file-2');
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-2']);
+
+ await handle.move('file-3');
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-3']);
+
+ await handle.move('file-1');
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-1']);
+}, 'move(name) can be called multiple times');
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(t, 'file-before', 'foo', root);
+ await promise_rejects_js(t, TypeError, handle.move('test/test'));
+ await promise_rejects_js(t, TypeError, handle.move('test\\test'));
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-before']);
+ assert_equals(await getFileContents(handle), 'foo');
+ assert_equals(await getFileSize(handle), 3);
+}, 'move(name) with a name with path separators should fail');
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(t, 'file-before', 'abc', root);
+
+ // Cannot rename handle with an active writable.
+ const stream = await cleanup_writable(t, await handle.createWritable());
+ await promise_rejects_dom(
+ t, 'NoModificationAllowedError', handle.move('file-after'));
+
+ // Can move handle once the writable is closed.
+ await stream.close();
+ await handle.move('file-after');
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-after']);
+}, 'move(name) while the file has an open writable fails');
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(t, 'file-before', 'abc', root);
+ const handle_dest =
+ await createFileWithContents(t, 'file-after', '123', root);
+
+ // Cannot overwrite a handle with an active writable.
+ const stream = await cleanup_writable(t, await handle_dest.createWritable());
+ await promise_rejects_dom(
+ t, 'NoModificationAllowedError', handle.move('file-after'));
+
+ await stream.close();
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['file-after', 'file-before']);
+}, 'move(name) while the destination file has an open writable fails');
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(t, 'file-before', 'abc', root);
+ const handle_dest =
+ await createFileWithContents(t, 'file-after', '123', root);
+
+ await handle.move('file-after');
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-after']);
+ assert_equals(await getFileContents(handle), 'abc');
+ assert_equals(await getFileContents(handle_dest), 'abc');
+}, 'move(name) can overwrite an existing file');
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(t, 'file-before', 'foo', root);
+ await handle.move(root, 'file-after');
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-after']);
+ assert_equals(await getFileContents(handle), 'foo');
+ assert_equals(await getFileSize(handle), 3);
+}, 'move(dir, name) to rename a file');
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(t, 'file-before', 'foo', root);
+ await handle.move(root, 'file-before');
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-before']);
+ assert_equals(await getFileContents(handle), 'foo');
+ assert_equals(await getFileSize(handle), 3);
+}, 'move(dir, name) to rename a file the same name');
+
+directory_test(async (t, root) => {
+ const dir_src = await root.getDirectoryHandle('dir-src', {create: true});
+ const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true});
+ const file = await createFileWithContents(t, 'file', 'abc', dir_src);
+ await file.move(dir_dest);
+
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']);
+ assert_array_equals(await getSortedDirectoryEntries(dir_src), []);
+ assert_array_equals(await getSortedDirectoryEntries(dir_dest), ['file']);
+ assert_equals(await getFileContents(file), 'abc');
+ assert_equals(await getFileSize(file), 3);
+}, 'move(dir) to move a file to a new directory');
+
+directory_test(async (t, root) => {
+ const dir_src = await root.getDirectoryHandle('dir-src', {create: true});
+ const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true});
+ const file = await createFileWithContents(t, 'file', 'abc', dir_src);
+ await promise_rejects_js(t, TypeError, file.move(dir_dest, ''));
+
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']);
+ assert_array_equals(await getSortedDirectoryEntries(dir_src), ['file']);
+ assert_array_equals(await getSortedDirectoryEntries(dir_dest), []);
+ assert_equals(await getFileContents(file), 'abc');
+ assert_equals(await getFileSize(file), 3);
+}, 'move(dir, "") to move a file to a new directory fails');
+
+directory_test(async (t, root) => {
+ const dir_src = await root.getDirectoryHandle('dir-src', {create: true});
+ const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true});
+ const file =
+ await createFileWithContents(t, 'file-in-dir-src', 'abc', dir_src);
+ await file.move(dir_dest, 'file-in-dir-dest');
+
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']);
+ assert_array_equals(await getSortedDirectoryEntries(dir_src), []);
+ assert_array_equals(
+ await getSortedDirectoryEntries(dir_dest), ['file-in-dir-dest']);
+ assert_equals(await getFileContents(file), 'abc');
+ assert_equals(await getFileSize(file), 3);
+}, 'move(dir, name) to move a file to a new directory');
+
+directory_test(async (t, root) => {
+ const dir1 = await root.getDirectoryHandle('dir1', {create: true});
+ const dir2 = await root.getDirectoryHandle('dir2', {create: true});
+ const handle = await createFileWithContents(t, 'file', 'foo', root);
+
+ await handle.move(dir1);
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir1/', 'dir2/']);
+ assert_array_equals(await getSortedDirectoryEntries(dir1), ['file']);
+ assert_array_equals(await getSortedDirectoryEntries(dir2), []);
+ assert_equals(await getFileContents(handle), 'foo');
+
+ await handle.move(dir2);
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir1/', 'dir2/']);
+ assert_array_equals(await getSortedDirectoryEntries(dir1), []);
+ assert_array_equals(await getSortedDirectoryEntries(dir2), ['file']);
+ assert_equals(await getFileContents(handle), 'foo');
+
+ await handle.move(root);
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir1/', 'dir2/', 'file']);
+ assert_array_equals(await getSortedDirectoryEntries(dir1), []);
+ assert_array_equals(await getSortedDirectoryEntries(dir2), []);
+ assert_equals(await getFileContents(handle), 'foo');
+}, 'move(dir) can be called multiple times');
+
+directory_test(async (t, root) => {
+ const dir1 = await root.getDirectoryHandle('dir1', {create: true});
+ const dir2 = await root.getDirectoryHandle('dir2', {create: true});
+ const handle = await createFileWithContents(t, 'file', 'foo', root);
+
+ await handle.move(dir1, 'file-1');
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir1/', 'dir2/']);
+ assert_array_equals(await getSortedDirectoryEntries(dir1), ['file-1']);
+ assert_array_equals(await getSortedDirectoryEntries(dir2), []);
+ assert_equals(await getFileContents(handle), 'foo');
+
+ await handle.move(dir2, 'file-2');
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir1/', 'dir2/']);
+ assert_array_equals(await getSortedDirectoryEntries(dir1), []);
+ assert_array_equals(await getSortedDirectoryEntries(dir2), ['file-2']);
+ assert_equals(await getFileContents(handle), 'foo');
+
+ await handle.move(root, 'file-3');
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir1/', 'dir2/', 'file-3']);
+ assert_array_equals(await getSortedDirectoryEntries(dir1), []);
+ assert_array_equals(await getSortedDirectoryEntries(dir2), []);
+ assert_equals(await getFileContents(handle), 'foo');
+}, 'move(dir, name) can be called multiple times');
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(t, 'file-before', 'foo', root);
+ await promise_rejects_js(t, TypeError, handle.move(root, '..'));
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-before']);
+ assert_equals(await getFileContents(handle), 'foo');
+ assert_equals(await getFileSize(handle), 3);
+}, 'move(dir, name) with a name with invalid characters should fail');
+
+directory_test(async (t, root) => {
+ const dir_src = await root.getDirectoryHandle('dir-src', {create: true});
+ const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true});
+ const file = await createFileWithContents(t, 'file', 'abc', dir_src);
+
+ // Cannot move handle with an active writable.
+ const stream = await cleanup_writable(t, await file.createWritable());
+ await promise_rejects_dom(t, 'NoModificationAllowedError', file.move(dir_dest));
+
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']);
+ // Assert the file hasn't been moved to the destination directory.
+ assert_array_equals(await getSortedDirectoryEntries(dir_dest), []);
+
+ // Can move handle once the writable is closed.
+ await stream.close();
+ await file.move(dir_dest);
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']);
+ assert_array_equals(await getSortedDirectoryEntries(dir_src), []);
+ assert_array_equals(await getSortedDirectoryEntries(dir_dest), ['file']);
+}, 'move(dir) while the file has an open writable fails');
+
+directory_test(async (t, root) => {
+ const dir_src = await root.getDirectoryHandle('dir-src', {create: true});
+ const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true});
+ const file = await createFileWithContents(t, 'file-before', 'abc', dir_src);
+
+ // Cannot move handle with an active writable.
+ const stream = await cleanup_writable(t, await file.createWritable());
+ await promise_rejects_dom(t, 'NoModificationAllowedError', file.move(dir_dest));
+
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']);
+ // Assert the file hasn't been moved to the destination directory.
+ assert_array_equals(await getSortedDirectoryEntries(dir_dest), []);
+
+ // Can move handle once the writable is closed.
+ await stream.close();
+ await file.move(dir_dest, 'file-after');
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']);
+ assert_array_equals(await getSortedDirectoryEntries(dir_src), []);
+ assert_array_equals(
+ await getSortedDirectoryEntries(dir_dest), ['file-after']);
+}, 'move(dir, name) while the file has an open writable fails');
+
+directory_test(async (t, root) => {
+ const dir_src = await root.getDirectoryHandle('dir-src', {create: true});
+ const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true});
+ const file = await createFileWithContents(t, 'file', 'abc', dir_src);
+ const file_dest = await createFileWithContents(t, 'file', '123', dir_dest);
+
+ // Cannot overwrite handle with an active writable.
+ const stream = await cleanup_writable(t, await file_dest.createWritable());
+ await promise_rejects_dom(t, 'NoModificationAllowedError', file.move(dir_dest));
+
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']);
+ // Assert the file is still in the source directory.
+ assert_array_equals(await getSortedDirectoryEntries(dir_src), ['file']);
+
+ await stream.close();
+ assert_array_equals(await getSortedDirectoryEntries(dir_dest), ['file']);
+}, 'move(dir) while the destination file has an open writable fails');
+
+directory_test(async (t, root) => {
+ const dir_src = await root.getDirectoryHandle('dir-src', {create: true});
+ const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true});
+ const file = await createFileWithContents(t, 'file', 'abc', dir_src);
+ const file_dest = await createFileWithContents(t, 'file', '123', dir_dest);
+
+ await file.move(dir_dest);
+ assert_array_equals(await getSortedDirectoryEntries(dir_src), []);
+ assert_array_equals(await getSortedDirectoryEntries(dir_dest), ['file']);
+ assert_equals(await getFileContents(file), 'abc');
+ assert_equals(await getFileContents(file_dest), 'abc');
+}, 'move(dir) can overwrite an existing file');
+
+directory_test(async (t, root) => {
+ const dir_src = await root.getDirectoryHandle('dir-src', {create: true});
+ const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true});
+ const file = await createFileWithContents(t, 'file-src', 'abc', dir_src);
+ const file_dest =
+ await createFileWithContents(t, 'file-dest', '123', dir_dest);
+
+ // Cannot overwrite handle with an active writable.
+ const stream = await cleanup_writable(t, await file_dest.createWritable());
+ await promise_rejects_dom(
+ t, 'NoModificationAllowedError', file.move(dir_dest, 'file-dest'));
+
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']);
+ // Assert the file is still in the source directory.
+ assert_array_equals(await getSortedDirectoryEntries(dir_src), ['file-src']);
+
+ await stream.close();
+ assert_array_equals(await getSortedDirectoryEntries(dir_dest), ['file-dest']);
+}, 'move(dir, name) while the destination file has an open writable fails');
+
+directory_test(async (t, root) => {
+ const dir_src = await root.getDirectoryHandle('dir-src', {create: true});
+ const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true});
+ const file = await createFileWithContents(t, 'file-src', 'abc', dir_src);
+ const file_dest =
+ await createFileWithContents(t, 'file-dest', '123', dir_dest);
+
+ await file.move(dir_dest, 'file-dest');
+
+ // Assert the file has been moved to the destination directory and renamed.
+ assert_array_equals(await getSortedDirectoryEntries(dir_src), []);
+ assert_array_equals(await getSortedDirectoryEntries(dir_dest), ['file-dest']);
+ assert_equals(await getFileContents(file), 'abc');
+ assert_equals(await getFileContents(file_dest), 'abc');
+}, 'move(dir, name) can overwrite an existing file');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-move', '12345', root);
+ const handle2 = handle;
+
+ await handle.move('file-was-moved');
+
+ assert_equals(await getFileContents(handle), '12345');
+ assert_equals(await getFileSize(handle), 5);
+ assert_equals(await getFileContents(handle2), '12345');
+ assert_equals(await getFileSize(handle2), 5);
+
+ assert_array_equals(
+ await getSortedDirectoryEntries(root),
+ ['file-was-moved']);
+}, 'FileSystemFileHandles are references, not paths');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemObserver.js b/testing/web-platform/tests/fs/script-tests/FileSystemObserver.js
new file mode 100644
index 0000000000..2c8fd57f7c
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemObserver.js
@@ -0,0 +1,57 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /service-worker/resources/test-helpers.sub.js
+
+promise_test(async t => {
+ function dummyCallback(records, observer) {};
+ let success = true;
+ try {
+ const observer = new FileSystemObserver(dummyCallback);
+ } catch (error) {
+ success = false;
+ }
+ assert_true(success);
+}, 'Creating a FileSystemObserver from a window succeeds');
+
+promise_test(async t => {
+ const dedicated_worker =
+ create_dedicated_worker(t, kDedicatedWorkerMessageTarget);
+ dedicated_worker.postMessage({type: 'create-file-system-observer'});
+
+ const event_watcher = new EventWatcher(t, dedicated_worker, 'message');
+ const message_event = await event_watcher.wait_for('message');
+ const response = message_event.data;
+
+ assert_true(response.createObserverSuccess);
+}, 'Creating a FileSystemObserver from a dedicated worker succeeds');
+
+if (self.SharedWorker !== undefined) {
+ promise_test(async t => {
+ const shared_worker = new SharedWorker(kSharedWorkerMessageTarget);
+ shared_worker.port.start();
+ shared_worker.port.postMessage({type: 'create-file-system-observer'});
+
+ const event_watcher = new EventWatcher(t, shared_worker.port, 'message');
+ const message_event = await event_watcher.wait_for('message');
+ const response = message_event.data;
+
+ assert_true(response.createObserverSuccess);
+ }, 'Creating a FileSystemObserver from a shared worker succeeds');
+}
+
+promise_test(async t => {
+ const scope = `${kServiceWorkerMessageTarget}?create-observer`;
+ const registration =
+ await create_service_worker(t, kServiceWorkerMessageTarget, scope);
+ await wait_for_state(t, registration.installing, 'activated');
+
+ registration.active.postMessage({type: 'create-file-system-observer'});
+
+ const event_watcher = new EventWatcher(t, navigator.serviceWorker, 'message');
+ const message_event = await event_watcher.wait_for('message');
+ const response = message_event.data;
+
+ assert_false(response.createObserverSuccess);
+}, 'Creating a FileSystemObserver from a service worker fails');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemSyncAccessHandle-flush.js b/testing/web-platform/tests/fs/script-tests/FileSystemSyncAccessHandle-flush.js
new file mode 100644
index 0000000000..580da69cc9
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemSyncAccessHandle-flush.js
@@ -0,0 +1,8 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+
+sync_access_handle_test(async handle => {
+ await handle.flush();
+}, 'Test flush on an empty file.');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream-piped.js b/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream-piped.js
new file mode 100644
index 0000000000..79e4cfb4f2
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream-piped.js
@@ -0,0 +1,137 @@
+'use strict';
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'foo_string.txt', root);
+ const wfs = await cleanup_writable(t, await handle.createWritable());
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.enqueue('foo_string');
+ controller.close();
+ }
+ });
+
+ await rs.pipeTo(wfs, { preventCancel: true });
+ assert_equals(await getFileContents(handle), 'foo_string');
+ assert_equals(await getFileSize(handle), 10);
+}, 'can be piped to with a string');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'foo_arraybuf.txt', root);
+ const wfs = await cleanup_writable(t, await handle.createWritable());
+ const buf = new ArrayBuffer(3);
+ const intView = new Uint8Array(buf);
+ intView[0] = 0x66;
+ intView[1] = 0x6f;
+ intView[2] = 0x6f;
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.enqueue(buf);
+ controller.close();
+ }
+ });
+
+ await rs.pipeTo(wfs, { preventCancel: true });
+ assert_equals(await getFileContents(handle), 'foo');
+ assert_equals(await getFileSize(handle), 3);
+}, 'can be piped to with an ArrayBuffer');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'foo_blob.txt', root);
+ const wfs = await cleanup_writable(t, await handle.createWritable());
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.enqueue(new Blob(['foo']));
+ controller.close();
+ }
+ });
+
+ await rs.pipeTo(wfs, { preventCancel: true });
+ assert_equals(await getFileContents(handle), 'foo');
+ assert_equals(await getFileSize(handle), 3);
+}, 'can be piped to with a Blob');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'foo_write_param.txt', root);
+ const wfs = await cleanup_writable(t, await handle.createWritable());
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.enqueue({type: 'write', data: 'foobar'});
+ controller.close();
+ }
+ });
+
+ await rs.pipeTo(wfs, { preventCancel: true });
+ assert_equals(await getFileContents(handle), 'foobar');
+ assert_equals(await getFileSize(handle), 6);
+}, 'can be piped to with a param object with write command');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'foo_write_param.txt', root);
+ const wfs = await cleanup_writable(t, await handle.createWritable());
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.enqueue({type: 'write', data: 'foobar'});
+ controller.enqueue({type: 'truncate', size: 10});
+ controller.enqueue({type: 'write', position: 0, data: 'baz'});
+ controller.close();
+ }
+ });
+
+ await rs.pipeTo(wfs, { preventCancel: true });
+ assert_equals(await getFileContents(handle), 'bazbar\0\0\0\0');
+ assert_equals(await getFileSize(handle), 10);
+}, 'can be piped to with a param object with multiple commands');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'foo_write_queued.txt', root);
+ const wfs = await cleanup_writable(t, await handle.createWritable());
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.enqueue('foo');
+ controller.enqueue('bar');
+ controller.enqueue('baz');
+ controller.close();
+ }
+ });
+
+ await rs.pipeTo(wfs, { preventCancel: true });
+ assert_equals(await getFileContents(handle), 'foobarbaz');
+ assert_equals(await getFileSize(handle), 9);
+}, 'multiple operations can be queued');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'fetched.txt', root);
+ const wfs = await cleanup_writable(t, await handle.createWritable());
+
+ const response = await fetch('data:text/plain,fetched from far');
+ const body = await response.body;
+ await body.pipeTo(wfs, { preventCancel: true });
+ assert_equals(await getFileContents(handle), 'fetched from far');
+ assert_equals(await getFileSize(handle), 16);
+}, 'plays well with fetch');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'aborted should_be_empty.txt', root);
+ const wfs = await cleanup_writable(t, await handle.createWritable());
+
+ const response = await fetch('data:text/plain,fetched from far');
+ const body = await response.body;
+
+ const abortController = new AbortController();
+ const signal = abortController.signal;
+
+ const promise = body.pipeTo(wfs, { signal });
+ await abortController.abort();
+
+ await promise_rejects_dom(t, 'AbortError', promise, 'stream is aborted');
+ await promise_rejects_js(t, TypeError, wfs.close(), 'stream cannot be closed to flush writes');
+
+ assert_equals(await getFileContents(handle), '');
+ assert_equals(await getFileSize(handle), 0);
+}, 'abort() aborts write');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream-write.js b/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream-write.js
new file mode 100644
index 0000000000..246f420d0f
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream-write.js
@@ -0,0 +1,364 @@
+'use strict';
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'empty_blob', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write(new Blob([]));
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '');
+ assert_equals(await getFileSize(handle), 0);
+}, 'write() with an empty blob to an empty file');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'valid_blob', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write(new Blob(['1234567890']));
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '1234567890');
+ assert_equals(await getFileSize(handle), 10);
+}, 'write() a blob to an empty file');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'write_param_empty', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write({type: 'write', data: '1234567890'});
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '1234567890');
+ assert_equals(await getFileSize(handle), 10);
+}, 'write() with WriteParams without position to an empty file');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'string_zero_offset', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write({type: 'write', position: 0, data: '1234567890'});
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '1234567890');
+ assert_equals(await getFileSize(handle), 10);
+}, 'write() a string to an empty file with zero offset');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'blob_zero_offset', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write({type: 'write', position: 0, data: new Blob(['1234567890'])});
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '1234567890');
+ assert_equals(await getFileSize(handle), 10);
+}, 'write() a blob to an empty file with zero offset');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'write_appends', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write('12345');
+ await stream.write('67890');
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '1234567890');
+ assert_equals(await getFileSize(handle), 10);
+}, 'write() called consecutively appends');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'write_appends_object_string', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write('12345');
+ await stream.write({type: 'write', data: '67890'});
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '1234567890');
+ assert_equals(await getFileSize(handle), 10);
+}, 'write() WriteParams without position and string appends');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'write_appends_object_blob', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write('12345');
+ await stream.write({type: 'write', data: new Blob(['67890'])});
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '1234567890');
+ assert_equals(await getFileSize(handle), 10);
+}, 'write() WriteParams without position and blob appends');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'string_with_offset', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write('1234567890');
+ await stream.write({type: 'write', position: 4, data: 'abc'});
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '1234abc890');
+ assert_equals(await getFileSize(handle), 10);
+}, 'write() called with a string and a valid offset');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'write_string_with_offset_after_seek', root);
+ const stream = await handle.createWritable();
+
+ await stream.write('1234567890');
+ await stream.write({type: 'seek', position: 0});
+ await stream.write({type: 'write', position: 4, data: 'abc'});
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '1234abc890');
+ assert_equals(await getFileSize(handle), 10);
+}, 'write() called with a string and a valid offset after seek');
+
+directory_test(async (t, root) => {
+const handle = await createEmptyFile(t, 'blob_with_offset', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+await stream.write('1234567890');
+await stream.write({type: 'write', position: 4, data: new Blob(['abc'])});
+await stream.close();
+
+assert_equals(await getFileContents(handle), '1234abc890');
+assert_equals(await getFileSize(handle), 10);
+}, 'write() called with a blob and a valid offset');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'bad_offset', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write({type: 'write', position: 4, data: new Blob(['abc'])});
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '\0\0\0\0abc');
+ assert_equals(await getFileSize(handle), 7);
+}, 'write() called with an offset beyond the end of the file');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'empty_string', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write('');
+ await stream.close();
+ assert_equals(await getFileContents(handle), '');
+ assert_equals(await getFileSize(handle), 0);
+}, 'write() with an empty string to an empty file');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'valid_utf8_string', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write('foo🤘');
+ await stream.close();
+ assert_equals(await getFileContents(handle), 'foo🤘');
+ assert_equals(await getFileSize(handle), 7);
+}, 'write() with a valid utf-8 string');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'string_with_unix_line_ending', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write('foo\n');
+ await stream.close();
+ assert_equals(await getFileContents(handle), 'foo\n');
+ assert_equals(await getFileSize(handle), 4);
+}, 'write() with a string with unix line ending preserved');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createEmptyFile(t, 'string_with_windows_line_ending', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write('foo\r\n');
+ await stream.close();
+ assert_equals(await getFileContents(handle), 'foo\r\n');
+ assert_equals(await getFileSize(handle), 5);
+}, 'write() with a string with windows line ending preserved');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'empty_array_buffer', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ const buf = new ArrayBuffer(0);
+ await stream.write(buf);
+ await stream.close();
+ assert_equals(await getFileContents(handle), '');
+ assert_equals(await getFileSize(handle), 0);
+}, 'write() with an empty array buffer to an empty file');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createEmptyFile(t, 'valid_string_typed_byte_array', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ const buf = new ArrayBuffer(3);
+ const intView = new Uint8Array(buf);
+ intView[0] = 0x66;
+ intView[1] = 0x6f;
+ intView[2] = 0x6f;
+ await stream.write(buf);
+ await stream.close();
+ assert_equals(await getFileContents(handle), 'foo');
+ assert_equals(await getFileSize(handle), 3);
+}, 'write() with a valid typed array buffer');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'atomic_writes.txt', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+ await stream.write('foox');
+
+ const stream2 = await cleanup_writable(t, await handle.createWritable());
+ await stream2.write('bar');
+
+ assert_equals(await getFileSize(handle), 0);
+
+ await stream2.close();
+ assert_equals(await getFileContents(handle), 'bar');
+ assert_equals(await getFileSize(handle), 3);
+
+ await stream.close();
+ assert_equals(await getFileContents(handle), 'foox');
+ assert_equals(await getFileSize(handle), 4);
+}, 'atomic writes: writable file streams make atomic changes on close');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'atomic_write_after_close.txt', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+ await stream.write('foo');
+
+ await stream.close();
+ assert_equals(await getFileContents(handle), 'foo');
+ assert_equals(await getFileSize(handle), 3);
+
+ await promise_rejects_js(
+ t, TypeError, stream.write('abc'));
+}, 'atomic writes: write() after close() fails');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createEmptyFile(t, 'atomic_truncate_after_close.txt', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+ await stream.write('foo');
+
+ await stream.close();
+ assert_equals(await getFileContents(handle), 'foo');
+ assert_equals(await getFileSize(handle), 3);
+
+ await promise_rejects_js(t, TypeError, stream.truncate(0));
+}, 'atomic writes: truncate() after close() fails');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'atomic_close_after_close.txt', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+ await stream.write('foo');
+
+ await stream.close();
+ assert_equals(await getFileContents(handle), 'foo');
+ assert_equals(await getFileSize(handle), 3);
+
+ await promise_rejects_js(t, TypeError, stream.close());
+}, 'atomic writes: close() after close() fails');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'there_can_be_only_one.txt', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+ await stream.write('foo');
+
+ // This test might be flaky if there is a race condition allowing
+ // close() to be called multiple times.
+ const success_promises =
+ [...Array(100)].map(() => stream.close().then(() => 1).catch(() => 0));
+ const close_attempts = await Promise.all(success_promises);
+ const success_count = close_attempts.reduce((x, y) => x + y);
+ assert_equals(success_count, 1);
+}, 'atomic writes: only one close() operation may succeed');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'writer_written', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+ assert_false(stream.locked);
+ const writer = stream.getWriter();
+ assert_true(stream.locked);
+
+ await writer.write('foo');
+ await writer.write(new Blob(['bar']));
+ await writer.write({type: 'seek', position: 0});
+ await writer.write({type: 'write', data: 'baz'});
+ await writer.close();
+
+ assert_equals(await getFileContents(handle), 'bazbar');
+ assert_equals(await getFileSize(handle), 6);
+}, 'getWriter() can be used');
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(
+ t, 'content.txt', 'very long string', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await promise_rejects_dom(
+ t, 'SyntaxError', stream.write({type: 'truncate'}),
+ 'truncate without size');
+}, 'WriteParams: truncate missing size param');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'content.txt', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await promise_rejects_dom(
+ t, 'SyntaxError', stream.write({type: 'write'}), 'write without data');
+}, 'WriteParams: write missing data param');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'content.txt', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await promise_rejects_js(
+ t, TypeError, stream.write({type: 'write', data: null}),
+ 'write with null data');
+}, 'WriteParams: write null data param');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'content.txt', 'seekable', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await promise_rejects_dom(
+ t, 'SyntaxError', stream.write({type: 'seek'}), 'seek without position');
+}, 'WriteParams: seek missing position param');
+
+directory_test(async (t, root) => {
+ const source_file =
+ await createFileWithContents(t, 'source_file', 'source data', root);
+ const source_blob = await source_file.getFile();
+ await root.removeEntry(source_file.name);
+
+ const handle = await createEmptyFile(t, 'invalid_blob_test', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+ await promise_rejects_dom(t, "NotFoundError", stream.write(source_blob));
+ await promise_rejects_js(t, TypeError, stream.close());
+
+ assert_equals(await getFileContents(handle), '');
+ assert_equals(await getFileSize(handle), 0);
+}, 'write() with an invalid blob to an empty file should reject');
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(t, 'file.txt', 'contents', root);
+ const stream = await handle.createWritable({mode: 'exclusive'});
+
+ await stream.write('12345');
+ await promise_rejects_js(
+ t, TypeError, stream.write({type: 'write', data: null}),
+ 'write with null data');
+
+ // The file contents should not have been changed.
+ assert_equals(await getFileContents(handle), 'contents');
+
+ // The file's lock was released.
+ const newStream = await handle.createWritable({mode: 'exclusive'});
+ await newStream.close();
+}, 'an errored writable stream releases its lock');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream.js b/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream.js
new file mode 100644
index 0000000000..19dc6371cf
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream.js
@@ -0,0 +1,105 @@
+'use strict';
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'trunc_shrink', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write('1234567890');
+ await stream.truncate(5);
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '12345');
+ assert_equals(await getFileSize(handle), 5);
+}, 'truncate() to shrink a file');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'trunc_grow', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write('abc');
+ await stream.truncate(5);
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), 'abc\0\0');
+ assert_equals(await getFileSize(handle), 5);
+}, 'truncate() to grow a file');
+
+directory_test(async (t, root) => {
+ const dir = await createDirectory(t, 'parent_dir', root);
+ const file_name = 'create_writable_fails_when_dir_removed.txt';
+ const handle = await createEmptyFile(t, file_name, dir);
+
+ await root.removeEntry('parent_dir', {recursive: true});
+ await promise_rejects_dom(t, 'NotFoundError', cleanup_writable(t, handle.createWritable()));
+}, 'createWritable() fails when parent directory is removed');
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(
+ t, 'atomic_file_is_copied.txt', 'fooks', root);
+ const stream = await cleanup_writable(t, await handle.createWritable({keepExistingData: true}));
+
+ await stream.write('bar');
+ await stream.close();
+ assert_equals(await getFileContents(handle), 'barks');
+ assert_equals(await getFileSize(handle), 5);
+}, 'createWritable({keepExistingData: true}): atomic writable file stream initialized with source contents');
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(
+ t, 'atomic_file_is_not_copied.txt', 'very long string', root);
+ const stream = await cleanup_writable(t, await handle.createWritable({keepExistingData: false}));
+
+ await stream.write('bar');
+ assert_equals(await getFileContents(handle), 'very long string');
+ await stream.close();
+ assert_equals(await getFileContents(handle), 'bar');
+ assert_equals(await getFileSize(handle), 3);
+}, 'createWritable({keepExistingData: false}): atomic writable file stream initialized with empty file');
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(
+ t, 'trunc_smaller_offset.txt', '1234567890', root);
+ const stream = await cleanup_writable(t, await handle.createWritable({keepExistingData: true}));
+
+ await stream.truncate(5);
+ await stream.write('abc');
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), 'abc45');
+ assert_equals(await getFileSize(handle), 5);
+}, 'cursor position: truncate size > offset');
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(
+ t, 'trunc_bigger_offset.txt', '1234567890', root);
+ const stream = await cleanup_writable(t, await handle.createWritable({keepExistingData: true}));
+
+ await stream.seek(6);
+ await stream.truncate(5);
+ await stream.write('abc');
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '12345abc');
+ assert_equals(await getFileSize(handle), 8);
+}, 'cursor position: truncate size < offset');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'contents', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+ assert_false(stream.locked);
+
+ stream.write('abc');
+ assert_false(stream.locked);
+ stream.write('def');
+ assert_false(stream.locked);
+ stream.truncate(9);
+ assert_false(stream.locked);
+ stream.seek(0);
+ assert_false(stream.locked);
+ stream.write('xyz');
+ assert_false(stream.locked);
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), 'xyzdef\0\0\0');
+ assert_equals(await getFileSize(handle), 9);
+}, 'commands are queued, stream is unlocked after each operation');