summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/fs/script-tests
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/fs/script-tests')
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-IndexedDB.js121
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-buckets.js19
-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.js26
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-getFile.js42
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-move.js369
-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.js334
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream.js105
25 files changed, 2624 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..15a0c2a3b5
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-IndexedDB.js
@@ -0,0 +1,121 @@
+'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.');
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..98261995f9
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-buckets.js
@@ -0,0 +1,19 @@
+'use strict';
+
+directory_test(async (t, root_dir) => {
+ 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) => {
+ 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');
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..302d4a5a7a
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-create-sync-access-handle.js
@@ -0,0 +1,26 @@
+'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) => {
+ assert_true(
+ file_system_type == 'sandboxed' || file_system_type == 'local',
+ 'File system type should be sandboxed or local.');
+ const expect_success = file_system_type == '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..b7ceb5ee7a
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-getFile.js
@@ -0,0 +1,42 @@
+'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');
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..dd848626e4
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-move.js
@@ -0,0 +1,369 @@
+// 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 dir = await root.getDirectoryHandle('dir', {create: true});
+ const handle = await createFileWithContents(t, 'file-before', 'foo', dir);
+ await promise_rejects_js(t, TypeError, handle.move('Lorem.'));
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['dir/']);
+ assert_array_equals(await getSortedDirectoryEntries(dir), ['file-before']);
+ assert_equals(await getFileContents(handle), 'foo');
+ assert_equals(await getFileSize(handle), 3);
+}, 'move(name) with a name with a trailing period should fail');
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(t, 'file-before', 'foo', root);
+ 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 invalid characters 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/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..66d6a944a5
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream-write.js
@@ -0,0 +1,334 @@
+'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, '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');
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');