summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/fs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /testing/web-platform/tests/fs
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/fs')
-rw-r--r--testing/web-platform/tests/fs/FileSystemBaseHandle-IndexedDB.https.any.js6
-rw-r--r--testing/web-platform/tests/fs/FileSystemBaseHandle-buckets.https.any.js4
-rw-r--r--testing/web-platform/tests/fs/FileSystemBaseHandle-getUniqueId.https.any.js3
-rw-r--r--testing/web-platform/tests/fs/FileSystemBaseHandle-isSameEntry.https.any.js3
-rw-r--r--testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-BroadcastChannel.https.window.js7
-rw-r--r--testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-Error.https.window.js9
-rw-r--r--testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-MessagePort-frames.https.window.js7
-rw-r--r--testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-MessagePort-windows.https.window.js7
-rw-r--r--testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-MessagePort-workers.https.window.js8
-rw-r--r--testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-frames.https.window.js7
-rw-r--r--testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-windows.https.window.js7
-rw-r--r--testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-workers.https.window.js8
-rw-r--r--testing/web-platform/tests/fs/FileSystemBaseHandle-remove.https.any.js3
-rw-r--r--testing/web-platform/tests/fs/FileSystemDirectoryHandle-getDirectoryHandle.https.any.js3
-rw-r--r--testing/web-platform/tests/fs/FileSystemDirectoryHandle-getFileHandle.https.any.js3
-rw-r--r--testing/web-platform/tests/fs/FileSystemDirectoryHandle-iteration.https.any.js4
-rw-r--r--testing/web-platform/tests/fs/FileSystemDirectoryHandle-removeEntry.https.any.js3
-rw-r--r--testing/web-platform/tests/fs/FileSystemDirectoryHandle-resolve.https.any.js3
-rw-r--r--testing/web-platform/tests/fs/FileSystemFileHandle-create-sync-access-handle.https.tentative.window.js4
-rw-r--r--testing/web-platform/tests/fs/FileSystemFileHandle-getFile.https.any.js3
-rw-r--r--testing/web-platform/tests/fs/FileSystemFileHandle-move.https.any.js3
-rw-r--r--testing/web-platform/tests/fs/FileSystemFileHandle-sync-access-handle-writable-lock.https.tentative.worker.js93
-rw-r--r--testing/web-platform/tests/fs/FileSystemSyncAccessHandle-close.https.tentative.worker.js40
-rw-r--r--testing/web-platform/tests/fs/FileSystemSyncAccessHandle-flush.https.tentative.worker.js29
-rw-r--r--testing/web-platform/tests/fs/FileSystemSyncAccessHandle-getSize.https.tentative.worker.js21
-rw-r--r--testing/web-platform/tests/fs/FileSystemSyncAccessHandle-read-write.https.tentative.worker.js302
-rw-r--r--testing/web-platform/tests/fs/FileSystemSyncAccessHandle-truncate.https.tentative.worker.js70
-rw-r--r--testing/web-platform/tests/fs/FileSystemWritableFileStream-piped.https.any.js4
-rw-r--r--testing/web-platform/tests/fs/FileSystemWritableFileStream-write.https.any.js3
-rw-r--r--testing/web-platform/tests/fs/FileSystemWritableFileStream.https.any.js3
-rw-r--r--testing/web-platform/tests/fs/META.yml3
-rw-r--r--testing/web-platform/tests/fs/README.md2
-rw-r--r--testing/web-platform/tests/fs/idlharness.https.any.js17
-rw-r--r--testing/web-platform/tests/fs/opaque-origin.https.window.js75
-rw-r--r--testing/web-platform/tests/fs/resources/message-target-dedicated-worker.js9
-rw-r--r--testing/web-platform/tests/fs/resources/message-target-service-worker.js9
-rw-r--r--testing/web-platform/tests/fs/resources/message-target-shared-worker.js14
-rw-r--r--testing/web-platform/tests/fs/resources/message-target.html22
-rw-r--r--testing/web-platform/tests/fs/resources/message-target.js158
-rw-r--r--testing/web-platform/tests/fs/resources/messaging-blob-helpers.js51
-rw-r--r--testing/web-platform/tests/fs/resources/messaging-helpers.js187
-rw-r--r--testing/web-platform/tests/fs/resources/messaging-serialize-helpers.js210
-rw-r--r--testing/web-platform/tests/fs/resources/opaque-origin-sandbox.html27
-rw-r--r--testing/web-platform/tests/fs/resources/sandboxed-fs-test-helpers.js28
-rw-r--r--testing/web-platform/tests/fs/resources/sync-access-handle-test.js17
-rw-r--r--testing/web-platform/tests/fs/resources/test-helpers.js116
-rw-r--r--testing/web-platform/tests/fs/root-name.https.any.js6
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-IndexedDB.js145
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-buckets.js42
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-getUniqueId.js89
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-isSameEntry.js107
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-BroadcastChannel.js82
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-Error.js244
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-frames.js44
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-windows.js35
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-workers.js40
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-frames.js40
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-windows.js31
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-workers.js35
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-remove.js105
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getDirectoryHandle.js117
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getFileHandle.js145
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-iteration.js100
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-removeEntry.js222
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-resolve.js27
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-create-sync-access-handle.js27
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-getFile.js52
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-move.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.js347
-rw-r--r--testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream.js105
72 files changed, 4316 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fs/FileSystemBaseHandle-IndexedDB.https.any.js b/testing/web-platform/tests/fs/FileSystemBaseHandle-IndexedDB.https.any.js
new file mode 100644
index 0000000000..9e67fe8802
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemBaseHandle-IndexedDB.https.any.js
@@ -0,0 +1,6 @@
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=resources/messaging-helpers.js
+// META: script=resources/messaging-serialize-helpers.js
+// META: script=/IndexedDB/resources/support-promises.js
+// META: script=script-tests/FileSystemBaseHandle-IndexedDB.js
diff --git a/testing/web-platform/tests/fs/FileSystemBaseHandle-buckets.https.any.js b/testing/web-platform/tests/fs/FileSystemBaseHandle-buckets.https.any.js
new file mode 100644
index 0000000000..cd78c5a950
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemBaseHandle-buckets.https.any.js
@@ -0,0 +1,4 @@
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=/storage/buckets/resources/util.js
+// META: script=script-tests/FileSystemBaseHandle-buckets.js
diff --git a/testing/web-platform/tests/fs/FileSystemBaseHandle-getUniqueId.https.any.js b/testing/web-platform/tests/fs/FileSystemBaseHandle-getUniqueId.https.any.js
new file mode 100644
index 0000000000..24ea20244c
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemBaseHandle-getUniqueId.https.any.js
@@ -0,0 +1,3 @@
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=script-tests/FileSystemBaseHandle-getUniqueId.js
diff --git a/testing/web-platform/tests/fs/FileSystemBaseHandle-isSameEntry.https.any.js b/testing/web-platform/tests/fs/FileSystemBaseHandle-isSameEntry.https.any.js
new file mode 100644
index 0000000000..67d36dfae8
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemBaseHandle-isSameEntry.https.any.js
@@ -0,0 +1,3 @@
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=script-tests/FileSystemBaseHandle-isSameEntry.js
diff --git a/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-BroadcastChannel.https.window.js b/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-BroadcastChannel.https.window.js
new file mode 100644
index 0000000000..ca25b548cb
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-BroadcastChannel.https.window.js
@@ -0,0 +1,7 @@
+// META: script=/service-workers/service-worker/resources/test-helpers.sub.js
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=resources/messaging-helpers.js
+// META: script=resources/messaging-serialize-helpers.js
+// META: script=script-tests/FileSystemBaseHandle-postMessage-BroadcastChannel.js
+// META: timeout=long
diff --git a/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-Error.https.window.js b/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-Error.https.window.js
new file mode 100644
index 0000000000..16a7002a2a
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-Error.https.window.js
@@ -0,0 +1,9 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=/service-workers/service-worker/resources/test-helpers.sub.js
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=resources/messaging-helpers.js
+// META: script=resources/messaging-blob-helpers.js
+// META: script=resources/messaging-serialize-helpers.js
+// META: script=script-tests/FileSystemBaseHandle-postMessage-Error.js
+// META: timeout=long
diff --git a/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-MessagePort-frames.https.window.js b/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-MessagePort-frames.https.window.js
new file mode 100644
index 0000000000..612c823295
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-MessagePort-frames.https.window.js
@@ -0,0 +1,7 @@
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=resources/messaging-helpers.js
+// META: script=resources/messaging-blob-helpers.js
+// META: script=resources/messaging-serialize-helpers.js
+// META: script=script-tests/FileSystemBaseHandle-postMessage-MessagePort-frames.js
+// META: timeout=long
diff --git a/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-MessagePort-windows.https.window.js b/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-MessagePort-windows.https.window.js
new file mode 100644
index 0000000000..28cec810ee
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-MessagePort-windows.https.window.js
@@ -0,0 +1,7 @@
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=resources/messaging-helpers.js
+// META: script=resources/messaging-blob-helpers.js
+// META: script=resources/messaging-serialize-helpers.js
+// META: script=script-tests/FileSystemBaseHandle-postMessage-MessagePort-windows.js
+// META: timeout=long
diff --git a/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-MessagePort-workers.https.window.js b/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-MessagePort-workers.https.window.js
new file mode 100644
index 0000000000..1599ba969d
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-MessagePort-workers.https.window.js
@@ -0,0 +1,8 @@
+// META: script=/service-workers/service-worker/resources/test-helpers.sub.js
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=resources/messaging-helpers.js
+// META: script=resources/messaging-blob-helpers.js
+// META: script=resources/messaging-serialize-helpers.js
+// META: script=script-tests/FileSystemBaseHandle-postMessage-MessagePort-workers.js
+// META: timeout=long
diff --git a/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-frames.https.window.js b/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-frames.https.window.js
new file mode 100644
index 0000000000..a0e41c51b1
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-frames.https.window.js
@@ -0,0 +1,7 @@
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=resources/messaging-helpers.js
+// META: script=resources/messaging-blob-helpers.js
+// META: script=resources/messaging-serialize-helpers.js
+// META: script=script-tests/FileSystemBaseHandle-postMessage-frames.js
+// META: timeout=long
diff --git a/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-windows.https.window.js b/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-windows.https.window.js
new file mode 100644
index 0000000000..1e3de1ea39
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-windows.https.window.js
@@ -0,0 +1,7 @@
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=resources/messaging-helpers.js
+// META: script=resources/messaging-blob-helpers.js
+// META: script=resources/messaging-serialize-helpers.js
+// META: script=script-tests/FileSystemBaseHandle-postMessage-windows.js
+// META: timeout=long
diff --git a/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-workers.https.window.js b/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-workers.https.window.js
new file mode 100644
index 0000000000..e690682b6f
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemBaseHandle-postMessage-workers.https.window.js
@@ -0,0 +1,8 @@
+// META: script=/service-workers/service-worker/resources/test-helpers.sub.js
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=resources/messaging-helpers.js
+// META: script=resources/messaging-blob-helpers.js
+// META: script=resources/messaging-serialize-helpers.js
+// META: script=script-tests/FileSystemBaseHandle-postMessage-workers.js
+// META: timeout=long
diff --git a/testing/web-platform/tests/fs/FileSystemBaseHandle-remove.https.any.js b/testing/web-platform/tests/fs/FileSystemBaseHandle-remove.https.any.js
new file mode 100644
index 0000000000..c3c1776784
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemBaseHandle-remove.https.any.js
@@ -0,0 +1,3 @@
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=script-tests/FileSystemBaseHandle-remove.js
diff --git a/testing/web-platform/tests/fs/FileSystemDirectoryHandle-getDirectoryHandle.https.any.js b/testing/web-platform/tests/fs/FileSystemDirectoryHandle-getDirectoryHandle.https.any.js
new file mode 100644
index 0000000000..69ca2bf367
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemDirectoryHandle-getDirectoryHandle.https.any.js
@@ -0,0 +1,3 @@
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=script-tests/FileSystemDirectoryHandle-getDirectoryHandle.js
diff --git a/testing/web-platform/tests/fs/FileSystemDirectoryHandle-getFileHandle.https.any.js b/testing/web-platform/tests/fs/FileSystemDirectoryHandle-getFileHandle.https.any.js
new file mode 100644
index 0000000000..afe362e757
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemDirectoryHandle-getFileHandle.https.any.js
@@ -0,0 +1,3 @@
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=script-tests/FileSystemDirectoryHandle-getFileHandle.js
diff --git a/testing/web-platform/tests/fs/FileSystemDirectoryHandle-iteration.https.any.js b/testing/web-platform/tests/fs/FileSystemDirectoryHandle-iteration.https.any.js
new file mode 100644
index 0000000000..b337d61d76
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemDirectoryHandle-iteration.https.any.js
@@ -0,0 +1,4 @@
+// META: script=/common/gc.js
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=script-tests/FileSystemDirectoryHandle-iteration.js
diff --git a/testing/web-platform/tests/fs/FileSystemDirectoryHandle-removeEntry.https.any.js b/testing/web-platform/tests/fs/FileSystemDirectoryHandle-removeEntry.https.any.js
new file mode 100644
index 0000000000..a4be8bd267
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemDirectoryHandle-removeEntry.https.any.js
@@ -0,0 +1,3 @@
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=script-tests/FileSystemDirectoryHandle-removeEntry.js
diff --git a/testing/web-platform/tests/fs/FileSystemDirectoryHandle-resolve.https.any.js b/testing/web-platform/tests/fs/FileSystemDirectoryHandle-resolve.https.any.js
new file mode 100644
index 0000000000..6ee3270930
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemDirectoryHandle-resolve.https.any.js
@@ -0,0 +1,3 @@
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=script-tests/FileSystemDirectoryHandle-resolve.js
diff --git a/testing/web-platform/tests/fs/FileSystemFileHandle-create-sync-access-handle.https.tentative.window.js b/testing/web-platform/tests/fs/FileSystemFileHandle-create-sync-access-handle.https.tentative.window.js
new file mode 100644
index 0000000000..6e20b57cb7
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemFileHandle-create-sync-access-handle.https.tentative.window.js
@@ -0,0 +1,4 @@
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=resources/messaging-helpers.js
+// META: script=script-tests/FileSystemFileHandle-create-sync-access-handle.js
diff --git a/testing/web-platform/tests/fs/FileSystemFileHandle-getFile.https.any.js b/testing/web-platform/tests/fs/FileSystemFileHandle-getFile.https.any.js
new file mode 100644
index 0000000000..fb93858fe7
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemFileHandle-getFile.https.any.js
@@ -0,0 +1,3 @@
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=script-tests/FileSystemFileHandle-getFile.js
diff --git a/testing/web-platform/tests/fs/FileSystemFileHandle-move.https.any.js b/testing/web-platform/tests/fs/FileSystemFileHandle-move.https.any.js
new file mode 100644
index 0000000000..1f9678a041
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemFileHandle-move.https.any.js
@@ -0,0 +1,3 @@
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=script-tests/FileSystemFileHandle-move.js
diff --git a/testing/web-platform/tests/fs/FileSystemFileHandle-sync-access-handle-writable-lock.https.tentative.worker.js b/testing/web-platform/tests/fs/FileSystemFileHandle-sync-access-handle-writable-lock.https.tentative.worker.js
new file mode 100644
index 0000000000..169364cb44
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemFileHandle-sync-access-handle-writable-lock.https.tentative.worker.js
@@ -0,0 +1,93 @@
+importScripts('/resources/testharness.js');
+importScripts('resources/test-helpers.js');
+importScripts('resources/sandboxed-fs-test-helpers.js');
+importScripts('resources/test-helpers.js');
+
+'use strict';
+
+directory_test(async (t, root_dir) => {
+ const fileHandle = await root_dir.getFileHandle('OPFS.test', {create: true});
+
+ const syncHandle1 = await fileHandle.createSyncAccessHandle();
+ t.add_cleanup(() => syncHandle1.close());
+ await promise_rejects_dom(
+ t, 'NoModificationAllowedError', fileHandle.createSyncAccessHandle());
+
+ syncHandle1.close();
+ const syncHandle2 = await fileHandle.createSyncAccessHandle();
+ syncHandle2.close();
+}, 'There can only be one open access handle at any given time');
+
+directory_test(async (t, root_dir) => {
+ const fooFileHandle = await root_dir.getFileHandle('foo.test', {create: true});
+ const barFileHandle = await root_dir.getFileHandle('bar.test', {create: true});
+
+ const fooSyncHandle = await fooFileHandle.createSyncAccessHandle();
+ t.add_cleanup(() => fooSyncHandle.close());
+
+ const barSyncHandle1 = await barFileHandle.createSyncAccessHandle();
+ t.add_cleanup(() => barSyncHandle1.close());
+ await promise_rejects_dom(
+ t, 'NoModificationAllowedError', barFileHandle.createSyncAccessHandle());
+
+ barSyncHandle1.close();
+ const barSyncHandle2 = await barFileHandle.createSyncAccessHandle();
+ barSyncHandle2.close();
+}, 'An access handle from one file does not interfere with the creation of an' +
+ ' access handle on another file');
+
+directory_test(async (t, root_dir) => {
+ const fooFileHandle = await root_dir.getFileHandle('foo.test', {create: true});
+ const barFileHandle = await root_dir.getFileHandle('bar.test', {create: true});
+
+ const fooWritable = await cleanup_writable(t, await fooFileHandle.createWritable());
+ t.add_cleanup(() => fooWritable.close());
+
+ const barSyncHandle = await barFileHandle.createSyncAccessHandle();
+ t.add_cleanup(() => barSyncHandle.close());
+}, 'A writable stream from one file does not interfere with the creation of an' +
+ ' access handle on another file');
+
+directory_test(async (t, root_dir) => {
+ const fooFileHandle = await root_dir.getFileHandle('foo.test', {create: true});
+ const barFileHandle = await root_dir.getFileHandle('bar.test', {create: true});
+
+ const fooSyncHandle = await fooFileHandle.createSyncAccessHandle();
+ t.add_cleanup(() => fooSyncHandle.close());
+
+ const barWritable = await cleanup_writable(t, await barFileHandle.createWritable());
+ t.add_cleanup(() => barWritable.close());
+}, 'An access handle from one file does not interfere with the creation of a' +
+ ' writable stream on another file');
+
+directory_test(async (t, root_dir) => {
+ const fileHandle = await root_dir.getFileHandle('OPFS.test', {create: true});
+
+ const syncHandle = await fileHandle.createSyncAccessHandle();
+ t.add_cleanup(() => { syncHandle.close(); });
+ await promise_rejects_dom(
+ t, 'NoModificationAllowedError', cleanup_writable(t, fileHandle.createWritable()));
+
+ syncHandle.close();
+ const writable = await cleanup_writable(t, await fileHandle.createWritable());
+ await writable.close();
+}, 'Writable streams cannot be created if there is an open access handle');
+
+directory_test(async (t, root_dir) => {
+ const fileHandle = await root_dir.getFileHandle('OPFS.test', {create: true});
+
+ const writable1 = await cleanup_writable(t, await fileHandle.createWritable());
+ const writable2 = await cleanup_writable(t, await fileHandle.createWritable());
+ await promise_rejects_dom(
+ t, 'NoModificationAllowedError', fileHandle.createSyncAccessHandle());
+
+ await writable1.close();
+ await promise_rejects_dom(
+ t, 'NoModificationAllowedError', fileHandle.createSyncAccessHandle());
+
+ await writable2.close();
+ const syncHandle = await fileHandle.createSyncAccessHandle();
+ syncHandle.close();
+}, 'Access handles cannot be created if there are open Writable streams');
+
+done();
diff --git a/testing/web-platform/tests/fs/FileSystemSyncAccessHandle-close.https.tentative.worker.js b/testing/web-platform/tests/fs/FileSystemSyncAccessHandle-close.https.tentative.worker.js
new file mode 100644
index 0000000000..1c6aaf38a3
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemSyncAccessHandle-close.https.tentative.worker.js
@@ -0,0 +1,40 @@
+importScripts("/resources/testharness.js");
+importScripts('resources/sync-access-handle-test.js');
+
+'use strict';
+sync_access_handle_test((t, handle) => {
+ assert_equals(handle.close(), undefined);
+ assert_equals(handle.close(), undefined);
+}, 'SyncAccessHandle.close is idempotent');
+
+sync_access_handle_test((t, handle) => {
+ assert_equals(handle.close(), undefined);
+ const readBuffer = new Uint8Array(4);
+ assert_throws_dom(
+ 'InvalidStateError', () => handle.read(readBuffer, {at: 0}));
+}, 'SyncAccessHandle.read fails after SyncAccessHandle.close');
+
+sync_access_handle_test((t, handle) => {
+ assert_equals(handle.close(), undefined);
+ const writeBuffer = new Uint8Array(4);
+ writeBuffer.set([96, 97, 98, 99]);
+ assert_throws_dom(
+ 'InvalidStateError', () => handle.write(writeBuffer, {at: 0}));
+}, 'SyncAccessHandle.write fails after SyncAccessHandle.close');
+
+sync_access_handle_test((t, handle) => {
+ assert_equals(handle.close(), undefined);
+ assert_throws_dom('InvalidStateError', () => handle.flush());
+}, 'SyncAccessHandle.flush fails after SyncAccessHandle.close');
+
+sync_access_handle_test((t, handle) => {
+ assert_equals(handle.close(), undefined);
+ assert_throws_dom('InvalidStateError', () => handle.getSize());
+}, 'SyncAccessHandle.getSize fails after SyncAccessHandle.close');
+
+sync_access_handle_test((t, handle) => {
+ assert_equals(handle.close(), undefined);
+ assert_throws_dom('InvalidStateError', () => handle.truncate(4));
+}, 'SyncAccessHandle.truncate fails after SyncAccessHandle.handle.close');
+
+done(); \ No newline at end of file
diff --git a/testing/web-platform/tests/fs/FileSystemSyncAccessHandle-flush.https.tentative.worker.js b/testing/web-platform/tests/fs/FileSystemSyncAccessHandle-flush.https.tentative.worker.js
new file mode 100644
index 0000000000..96953a88f2
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemSyncAccessHandle-flush.https.tentative.worker.js
@@ -0,0 +1,29 @@
+importScripts('/resources/testharness.js');
+importScripts('resources/sync-access-handle-test.js');
+
+'use strict';
+
+sync_access_handle_test((t, handle) => {
+ handle.flush();
+}, 'Test flush on an empty file.');
+
+sync_access_handle_test((t, handle) => {
+ if (!('TextEncoder' in self)) {
+ return;
+ }
+ const encoder = new TextEncoder();
+ const decoder = new TextDecoder();
+
+ const text = 'Hello Storage Foundation';
+ const writeBuffer = new TextEncoder().encode(text);
+ handle.write(writeBuffer, {at: 0});
+ handle.flush();
+ let readBuffer = new Uint8Array(text.length);
+ handle.read(readBuffer, {at: 0});
+ assert_equals(
+ text, new TextDecoder().decode(readBuffer),
+ 'Check that the written bytes and the read bytes match');
+}, 'SyncAccessHandle.read returns bytes written by SyncAccessHandle.write' +
+ ' after SyncAccessHandle.flush');
+
+done();
diff --git a/testing/web-platform/tests/fs/FileSystemSyncAccessHandle-getSize.https.tentative.worker.js b/testing/web-platform/tests/fs/FileSystemSyncAccessHandle-getSize.https.tentative.worker.js
new file mode 100644
index 0000000000..4b62b280b9
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemSyncAccessHandle-getSize.https.tentative.worker.js
@@ -0,0 +1,21 @@
+importScripts("/resources/testharness.js");
+importScripts('resources/sync-access-handle-test.js');
+
+'use strict';
+
+sync_access_handle_test((t, handle) => {
+ assert_equals(handle.getSize(), 0);
+ const bufferSize = 4;
+ const writeBuffer = new Uint8Array(bufferSize);
+ writeBuffer.set([96, 97, 98, 99]);
+ handle.write(writeBuffer, {at: 0});
+ assert_equals(handle.getSize(), bufferSize);
+ let offset = 3;
+ handle.write(writeBuffer, {at: offset});
+ assert_equals(handle.getSize(), bufferSize + offset);
+ offset = 10;
+ handle.write(writeBuffer, {at: offset});
+ assert_equals(handle.getSize(), bufferSize + offset);
+}, 'test SyncAccessHandle.getSize after SyncAccessHandle.write');
+
+done();
diff --git a/testing/web-platform/tests/fs/FileSystemSyncAccessHandle-read-write.https.tentative.worker.js b/testing/web-platform/tests/fs/FileSystemSyncAccessHandle-read-write.https.tentative.worker.js
new file mode 100644
index 0000000000..a22608a2c7
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemSyncAccessHandle-read-write.https.tentative.worker.js
@@ -0,0 +1,302 @@
+importScripts("/resources/testharness.js");
+importScripts('resources/sync-access-handle-test.js');
+
+'use strict';
+
+sync_access_handle_test((t, handle) => {
+ const readBuffer = new Uint8Array(24);
+ const readBytes = handle.read(readBuffer, {at: 0});
+ assert_equals(0, readBytes, 'Check that no bytes were read');
+}, 'Test reading an empty file through a sync access handle.');
+
+sync_access_handle_test((t, handle) => {
+ if (!('TextEncoder' in self)) {
+ return;
+ }
+
+ const decoder = new TextDecoder();
+
+ const text = 'Hello Storage Foundation';
+ const writeBuffer = new TextEncoder().encode(text);
+ const writtenBytes = handle.write(writeBuffer, {at: 0});
+ assert_equals(
+ writeBuffer.byteLength, writtenBytes,
+ 'Check that all bytes were written.');
+ let readBuffer = new Uint8Array(writtenBytes);
+ let readBytes = handle.read(readBuffer, {at: 0});
+ assert_equals(writtenBytes, readBytes, 'Check that all bytes were read');
+ assert_equals(
+ text, decoder.decode(readBuffer),
+ 'Check that the written bytes and the read bytes match');
+
+ // Test a read of less bytes than available.
+ const expected = 'Storage';
+ readBuffer = new Uint8Array(expected.length);
+ readBytes = handle.read(readBuffer, {at: text.indexOf(expected)});
+ assert_equals(readBuffer.length, readBytes, 'Check that all bytes were read');
+ const actual = decoder.decode(readBuffer);
+ assert_equals(
+ expected, actual,
+ 'Partial read returned unexpected contents');
+}, 'Test writing and reading through a sync access handle.');
+
+sync_access_handle_test((t, handle) => {
+ if (!('TextEncoder' in self)) {
+ return;
+ }
+
+ const encoder = new TextEncoder();
+ const decoder = new TextDecoder();
+
+ for (text of ['Hello', 'Longer Text']) {
+ const writeBuffer = encoder.encode(text);
+ const writtenBytes = handle.write(writeBuffer, {at: 0});
+ assert_equals(
+ writeBuffer.byteLength, writtenBytes,
+ 'Check that all bytes were written.');
+ const readBuffer = new Uint8Array(writtenBytes);
+ const readBytes = handle.read(readBuffer, {at: 0});
+ assert_equals(writtenBytes, readBytes, 'Check that all bytes were read');
+ assert_equals(
+ text, decoder.decode(readBuffer),
+ 'Check that the written bytes and the read bytes match');
+ }
+}, 'Test second write that is bigger than the first write');
+
+sync_access_handle_test((t, handle) => {
+ if (!('TextEncoder' in self)) {
+ return;
+ }
+
+ const encoder = new TextEncoder();
+ const decoder = new TextDecoder();
+
+ for (tuple
+ of [{input: 'Hello World', expected: 'Hello World'},
+ {input: 'foobar', expected: 'foobarWorld'}]) {
+ const text = tuple.input;
+ const expected = tuple.expected;
+ const writeBuffer = encoder.encode(text);
+ const writtenBytes = handle.write(writeBuffer, {at: 0});
+ assert_equals(
+ writeBuffer.byteLength, writtenBytes,
+ 'Check that all bytes were written.');
+ const readBuffer = new Uint8Array(expected.length);
+ const readBytes = handle.read(readBuffer, {at: 0});
+ assert_equals(expected.length, readBytes, 'Check that all bytes were read');
+ assert_equals(
+ expected, decoder.decode(readBuffer),
+ 'Check that the written bytes and the read bytes match');
+ }
+}, 'Test second write that is smaller than the first write');
+
+sync_access_handle_test((t, handle) => {
+ const expected = 17;
+ const writeBuffer = new Uint8Array(1);
+ writeBuffer[0] = expected;
+ const offset = 5;
+ const writtenBytes = handle.write(writeBuffer, {at: offset});
+ assert_equals(
+ writeBuffer.byteLength, writtenBytes,
+ 'Check that all bytes were written.');
+ const fileLength = writeBuffer.byteLength + offset;
+ const readBuffer = new Uint8Array(fileLength);
+ const readBytes = handle.read(readBuffer, {at: 0});
+ assert_equals(fileLength, readBytes, 'Check that all bytes were read');
+ for (let i = 0; i < offset; ++i) {
+ assert_equals(
+ readBuffer[i], 0,
+ `Gaps in the file should be filled with 0, but got ${readBuffer[i]}.`);
+ }
+
+ assert_equals(
+ readBuffer[offset], expected,
+ 'Gaps in the file should be filled with 0.');
+}, 'Test initial write with an offset');
+
+sync_access_handle_test((t, handle) => {
+ if (!('TextEncoder' in self)) {
+ return;
+ }
+
+ const encoder = new TextEncoder();
+ const decoder = new TextDecoder();
+
+ for (tuple
+ of [{input: 'Hello World', expected: 'Hello World', offset: 0},
+ {input: 'foobar', expected: 'Hello foobar', offset: 6}]) {
+ const text = tuple.input;
+ const expected = tuple.expected;
+ const offset = tuple.offset;
+ const writeBuffer = encoder.encode(text);
+ const writtenBytes = handle.write(writeBuffer, {at: offset});
+ assert_equals(
+ writeBuffer.byteLength, writtenBytes,
+ 'Check that all bytes were written.');
+ const readBuffer = new Uint8Array(expected.length);
+ const readBytes = handle.read(readBuffer, {at: 0});
+ assert_equals(expected.length, readBytes, 'Check that all bytes were read');
+ const actual = decoder.decode(readBuffer);
+ assert_equals(
+ expected, actual,
+ 'Check content read from the handle');
+ }
+}, 'Test overwriting the file at an offset');
+
+sync_access_handle_test((t, handle) => {
+ if (!('TextEncoder' in self)) {
+ return;
+ }
+
+ const decoder = new TextDecoder();
+
+ const text = 'Hello Storage Foundation';
+ const writeBuffer = new TextEncoder().encode(text);
+ const writtenBytes = handle.write(writeBuffer, {at: 0});
+ assert_equals(
+ writeBuffer.byteLength, writtenBytes,
+ 'Check that all bytes were written.');
+ const bufferLength = text.length;
+ for (tuple
+ of [{offset: 0, expected: text},
+ {offset: 6, expected: text.substring(6)}]) {
+ const offset = tuple.offset;
+ const expected = tuple.expected;
+
+ const readBuffer = new Uint8Array(bufferLength);
+ const readBytes = handle.read(readBuffer, {at: offset});
+ assert_equals(expected.length, readBytes, 'Check that all bytes were read');
+ const actual = decoder.decode(readBuffer);
+ assert_true(
+ actual.startsWith(expected),
+ `Expected to read ${expected} but the actual value was ${actual}.`);
+ }
+
+ const readBuffer = new Uint8Array(bufferLength);
+ // Offset is greater than the file length.
+ const readBytes = handle.read(readBuffer, {at: bufferLength + 1});
+ assert_equals(0, readBytes, 'Check that no bytes were read');
+ for (let i = 0; i < readBuffer.byteLength; ++i) {
+ assert_equals(0, readBuffer[i], 'Check that the read buffer is unchanged.');
+ }
+}, 'Test read at an offset');
+
+sync_access_handle_test((t, handle) => {
+ if (!('TextEncoder' in self)) {
+ return;
+ }
+
+ const expected = 'Hello Storage Foundation';
+ const writeBuffer = new TextEncoder().encode(expected);
+ const writtenBytes = handle.write(writeBuffer, {at: 0});
+ assert_equals(
+ writeBuffer.byteLength, writtenBytes,
+ 'Check that all bytes were written.');
+
+ const bufferLength = expected.length;
+ const readBuffer = new Uint8Array(expected.length);
+ // No options parameter provided, should read at offset 0.
+ const readBytes = handle.read(readBuffer, {at: 0});
+ assert_equals(expected.length, readBytes, 'Check that all bytes were read');
+ const actual = new TextDecoder().decode(readBuffer);
+ assert_equals(
+ expected, actual,
+ `Expected to read ${expected} but the actual value was ${actual}.`);
+}, 'Test read with default options');
+
+sync_access_handle_test((t, handle) => {
+ if (!('TextEncoder' in self)) {
+ return;
+ }
+
+ const expected = 'Hello Storage Foundation';
+ const writeBuffer = new TextEncoder().encode(expected);
+ // No options parameter provided, should write at offset 0.
+ const writtenBytes = handle.write(writeBuffer);
+ assert_equals(
+ writeBuffer.byteLength, writtenBytes,
+ 'Check that all bytes were written.');
+
+ const bufferLength = expected.length;
+ const readBuffer = new Uint8Array(expected.length);
+ const readBytes = handle.read(readBuffer, {at: 0});
+ assert_equals(expected.length, readBytes, 'Check that all bytes were read');
+ const actual = new TextDecoder().decode(readBuffer);
+ assert_equals(
+ expected, actual,
+ `Expected to read ${expected} but the actual value was ${actual}.`);
+}, 'Test write with default options');
+
+sync_access_handle_test((t, handle) => {
+ const readBuffer = new Uint8Array(24);
+ assert_throws_js(TypeError, () => handle.read(readBuffer, {at: -1}));
+}, 'Test reading at a negative offset fails.');
+
+sync_access_handle_test((t, handle) => {
+ const text = 'foobar';
+ const writeBuffer = new TextEncoder().encode(text);
+ assert_throws_js(TypeError, () => handle.write(writeBuffer, {at: -1}));
+
+ const readBuffer = new Uint8Array(24);
+ const readBytes = handle.read(readBuffer, {at: 0});
+
+ assert_equals(0, readBytes, 'Check that no bytes were written');
+}, 'Test writing at a negative offset fails.');
+
+sync_access_handle_test((t, handle) => {
+ if (!('TextEncoder' in self)) {
+ return;
+ }
+
+ const encoder = new TextEncoder();
+ const decoder = new TextDecoder();
+
+ let writeBuffer = encoder.encode("Hello ");
+ let writtenBytes = handle.write(writeBuffer);
+ writeBuffer = encoder.encode("World");
+ writtenBytes += handle.write(writeBuffer);
+ let readBuffer = new Uint8Array(256);
+ let readBytes = handle.read(readBuffer, {at: 0});
+ assert_equals(readBytes, "Hello World".length, 'Check that all bytes were read');
+ let actual = decoder.decode(readBuffer).substring(0, readBytes);
+ assert_equals(
+ actual, "Hello World",
+ 'Check content read from the handle');
+
+ readBuffer = new Uint8Array(5);
+ readBytes = handle.read(readBuffer, {at: 0});
+ assert_equals(readBytes, 5, 'Check that all bytes were read');
+ actual = decoder.decode(readBuffer).substring(0, readBytes);
+ assert_equals(
+ actual, "Hello",
+ 'Check content read from the handle');
+
+ readBuffer = new Uint8Array(256);
+ readBytes = handle.read(readBuffer);
+ assert_equals(readBytes, "Hello World".length - 5, 'Check that all bytes were read');
+ actual = decoder.decode(readBuffer).substring(0, readBytes);
+ assert_equals(
+ actual, " World",
+ 'Check content read from the handle');
+
+ readBuffer = new Uint8Array(5);
+ readBytes = handle.read(readBuffer, {at: 0});
+ assert_equals(readBytes, 5, 'Check that all bytes were read');
+ actual = decoder.decode(readBuffer);
+ assert_equals(
+ actual, "Hello",
+ 'Check content read from the handle');
+ writeBuffer = encoder.encode(" X");
+ writtenBytes = handle.write(writeBuffer);
+ assert_equals(writtenBytes, 2, 'Check overwrite length');
+
+ readBuffer = new Uint8Array(256);
+ readBytes = handle.read(readBuffer, {at: 0});
+ assert_equals(readBytes, "Hello Xorld".length, 'Check that all bytes were read');
+ actual = decoder.decode(readBuffer).substring(0, readBytes);
+ assert_equals(
+ actual, "Hello Xorld",
+ 'Check content read from the handle');
+}, 'Test reading and writing a file using the cursor');
+
+done();
diff --git a/testing/web-platform/tests/fs/FileSystemSyncAccessHandle-truncate.https.tentative.worker.js b/testing/web-platform/tests/fs/FileSystemSyncAccessHandle-truncate.https.tentative.worker.js
new file mode 100644
index 0000000000..e5f557e070
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemSyncAccessHandle-truncate.https.tentative.worker.js
@@ -0,0 +1,70 @@
+importScripts("/resources/testharness.js");
+importScripts('resources/sync-access-handle-test.js');
+
+'use strict';
+
+sync_access_handle_test((t, handle) => {
+ // Without this assertion, the test passes even if truncate is not defined.
+ assert_implements(handle.truncate,
+ "SyncAccessHandle.truncate is not implemented.");
+
+ handle.truncate(4);
+ assert_equals(handle.getSize(), 4);
+ handle.truncate(2);
+ assert_equals(handle.getSize(), 2);
+ handle.truncate(7);
+ assert_equals(handle.getSize(), 7);
+ handle.truncate(0);
+ assert_equals(handle.getSize(), 0);
+ assert_throws_js(TypeError, () => handle.truncate(-4));
+}, 'test SyncAccessHandle.truncate with different sizes');
+
+sync_access_handle_test((t, handle) => {
+ const writeBuffer = new Uint8Array(4);
+ writeBuffer.set([96, 97, 98, 99]);
+ handle.write(writeBuffer, {at: 0});
+
+ handle.truncate(2);
+ let readBuffer = new Uint8Array(6);
+ assert_equals(2, handle.read(readBuffer, {at: 0}));
+ let expected = new Uint8Array(6);
+ expected.set([96, 97, 0, 0, 0, 0]);
+ assert_array_equals(expected, readBuffer);
+
+ // Resize the file to 6, expect that everything beyond the old size is '0'.
+ handle.truncate(6);
+ assert_equals(6, handle.read(readBuffer, {at: 0}));
+ assert_array_equals(expected, readBuffer);
+}, 'test SyncAccessHandle.truncate after SyncAccessHandle.write');
+
+sync_access_handle_test((t, handle) => {
+ const writeBuffer = new Uint8Array(4);
+ writeBuffer.set([96, 97, 98, 99]);
+ handle.write(writeBuffer, {at: 0});
+
+ // Moves cursor to 2
+ handle.truncate(2);
+ let readBuffer = new Uint8Array(256);
+ assert_equals(handle.read(readBuffer), 0);
+
+ writeBuffer.set([100, 101, 102, 103]);
+ handle.write(writeBuffer);
+
+ assert_equals(handle.read(readBuffer, {at: 0}), 6);
+ let expected = new Uint8Array(256);
+ expected.set([96, 97, 100, 101, 102, 103]);
+ assert_array_equals(readBuffer, expected);
+
+ // Resize the file to 10, expect that everything beyond the old size is '0'.
+ handle.truncate(10); // file cursor should still be at 6
+ // overwrite two bytes
+ const writeBuffer2 = new Uint8Array(2);
+ writeBuffer2.set([110, 111]);
+ handle.write(writeBuffer2);
+ expected = new Uint8Array(256);
+ expected.set([96, 97, 100, 101, 102, 103, 110, 111, 0, 0]);
+ assert_equals(handle.read(readBuffer, {at: 0}), 10);
+ assert_array_equals(readBuffer, expected);
+}, 'Test truncate effect on cursor');
+
+done();
diff --git a/testing/web-platform/tests/fs/FileSystemWritableFileStream-piped.https.any.js b/testing/web-platform/tests/fs/FileSystemWritableFileStream-piped.https.any.js
new file mode 100644
index 0000000000..eed6a561dc
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemWritableFileStream-piped.https.any.js
@@ -0,0 +1,4 @@
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=../streams/resources/recording-streams.js
+// META: script=script-tests/FileSystemWritableFileStream-piped.js
diff --git a/testing/web-platform/tests/fs/FileSystemWritableFileStream-write.https.any.js b/testing/web-platform/tests/fs/FileSystemWritableFileStream-write.https.any.js
new file mode 100644
index 0000000000..7ef0ea0ef8
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemWritableFileStream-write.https.any.js
@@ -0,0 +1,3 @@
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=script-tests/FileSystemWritableFileStream-write.js
diff --git a/testing/web-platform/tests/fs/FileSystemWritableFileStream.https.any.js b/testing/web-platform/tests/fs/FileSystemWritableFileStream.https.any.js
new file mode 100644
index 0000000000..16dbbe6a80
--- /dev/null
+++ b/testing/web-platform/tests/fs/FileSystemWritableFileStream.https.any.js
@@ -0,0 +1,3 @@
+// META: script=resources/test-helpers.js
+// META: script=resources/sandboxed-fs-test-helpers.js
+// META: script=script-tests/FileSystemWritableFileStream.js
diff --git a/testing/web-platform/tests/fs/META.yml b/testing/web-platform/tests/fs/META.yml
new file mode 100644
index 0000000000..23d7765cdf
--- /dev/null
+++ b/testing/web-platform/tests/fs/META.yml
@@ -0,0 +1,3 @@
+spec: https://fs.spec.whatwg.org/
+suggested_reviewers:
+ - mkruisselbrink \ No newline at end of file
diff --git a/testing/web-platform/tests/fs/README.md b/testing/web-platform/tests/fs/README.md
new file mode 100644
index 0000000000..8b99a0140d
--- /dev/null
+++ b/testing/web-platform/tests/fs/README.md
@@ -0,0 +1,2 @@
+This directory contains tests for the
+[File System](https://fs.spec.whatwg.org/) specification.
diff --git a/testing/web-platform/tests/fs/idlharness.https.any.js b/testing/web-platform/tests/fs/idlharness.https.any.js
new file mode 100644
index 0000000000..508beccc36
--- /dev/null
+++ b/testing/web-platform/tests/fs/idlharness.https.any.js
@@ -0,0 +1,17 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+// META: timeout=long
+
+'use strict';
+
+idl_test(
+ ['fs'],
+ ['storage', 'streams'],
+ idl_array => {
+ idl_array.add_objects({
+ // TODO: Add instances of FileSystemHandle, FileSystemFileHandle,
+ // FileSystemDirectoryHandle, FileSystemWritableFileStream, and
+ // StorageManager.
+ });
+ }
+);
diff --git a/testing/web-platform/tests/fs/opaque-origin.https.window.js b/testing/web-platform/tests/fs/opaque-origin.https.window.js
new file mode 100644
index 0000000000..94b4cd7978
--- /dev/null
+++ b/testing/web-platform/tests/fs/opaque-origin.https.window.js
@@ -0,0 +1,75 @@
+'use strict';
+
+const kSandboxWindowUrl = 'resources/opaque-origin-sandbox.html';
+
+function add_iframe(test, src, sandbox) {
+ const iframe = document.createElement('iframe');
+ iframe.src = src;
+ if (sandbox !== undefined) {
+ iframe.sandbox = sandbox;
+ }
+ document.body.appendChild(iframe);
+ test.add_cleanup(() => {
+ iframe.remove();
+ });
+}
+
+// Creates a data URI iframe that uses postMessage() to provide its parent
+// with the test result. The iframe checks for the existence of
+// |property_name| on the window.
+async function verify_does_exist_in_data_uri_iframe(
+ test, property_name) {
+ const iframe_content =
+ '<script>' +
+ ' const is_property_name_defined = ' +
+ ` (self.${property_name} !== undefined);` +
+ ' parent.postMessage({is_property_name_defined}, "*")' +
+ '</script>';
+
+ const data_uri = `data:text/html,${encodeURIComponent(iframe_content)}`;
+ add_iframe(test, data_uri);
+
+ const event_watcher = new EventWatcher(test, self, 'message');
+ const message_event = await event_watcher.wait_for('message')
+
+ assert_true(message_event.data.is_property_name_defined,
+ `Data URI iframes must define '${property_name}'.`);
+}
+
+// |kSandboxWindowUrl| sends the result of navigator.storage.getDirectory() to
+// this window. For windows using sandbox='allow-scripts', this must produce a
+// rejected promise.
+async function verify_results_from_sandboxed_child_window(test) {
+ const event_watcher = new EventWatcher(test, self, 'message');
+
+ const message_event = await event_watcher.wait_for('message');
+ assert_equals(message_event.data,
+ 'navigator.storage.getDirectory(): REJECTED: SecurityError');
+}
+
+promise_test(async test => {
+ await verify_does_exist_in_data_uri_iframe(
+ test, 'FileSystemDirectoryHandle');
+}, 'FileSystemDirectoryHandle must be defined for data URI iframes.');
+
+promise_test(
+ async test => {
+ add_iframe(test, kSandboxWindowUrl, /*sandbox=*/ 'allow-scripts');
+ await verify_results_from_sandboxed_child_window(test);
+ },
+ 'navigator.storage.getDirectory() must reject in a sandboxed iframe.');
+
+promise_test(
+ async test => {
+ const child_window_url = kSandboxWindowUrl +
+ '?pipe=header(Content-Security-Policy, sandbox allow-scripts)';
+
+ const child_window = window.open(child_window_url);
+ test.add_cleanup(() => {
+ child_window.close();
+ });
+
+ await verify_results_from_sandboxed_child_window(test);
+ },
+ 'navigator.storage.getDirectory() ' +
+ 'must reject in a sandboxed opened window.');
diff --git a/testing/web-platform/tests/fs/resources/message-target-dedicated-worker.js b/testing/web-platform/tests/fs/resources/message-target-dedicated-worker.js
new file mode 100644
index 0000000000..26ff23ef8a
--- /dev/null
+++ b/testing/web-platform/tests/fs/resources/message-target-dedicated-worker.js
@@ -0,0 +1,9 @@
+'use strict';
+
+importScripts(
+ 'test-helpers.js',
+ 'messaging-serialize-helpers.js',
+ 'message-target.js'
+);
+
+add_message_event_handlers(/*receiver=*/self, /*target=*/self);
diff --git a/testing/web-platform/tests/fs/resources/message-target-service-worker.js b/testing/web-platform/tests/fs/resources/message-target-service-worker.js
new file mode 100644
index 0000000000..4a6174ae3b
--- /dev/null
+++ b/testing/web-platform/tests/fs/resources/message-target-service-worker.js
@@ -0,0 +1,9 @@
+'use strict';
+
+importScripts(
+ 'test-helpers.js',
+ 'messaging-serialize-helpers.js',
+ 'message-target.js'
+);
+
+add_message_event_handlers(/*receiver=*/self); \ No newline at end of file
diff --git a/testing/web-platform/tests/fs/resources/message-target-shared-worker.js b/testing/web-platform/tests/fs/resources/message-target-shared-worker.js
new file mode 100644
index 0000000000..6829c61d4c
--- /dev/null
+++ b/testing/web-platform/tests/fs/resources/message-target-shared-worker.js
@@ -0,0 +1,14 @@
+'use strict';
+
+importScripts(
+ 'test-helpers.js',
+ 'messaging-serialize-helpers.js',
+ 'message-target.js'
+);
+
+self.addEventListener('connect', connect_event => {
+ const message_port = connect_event.ports[0];
+ add_message_event_handlers(
+ /*receiver=*/message_port, /*target=*/message_port);
+ message_port.start();
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/fs/resources/message-target.html b/testing/web-platform/tests/fs/resources/message-target.html
new file mode 100644
index 0000000000..cdd86bcc66
--- /dev/null
+++ b/testing/web-platform/tests/fs/resources/message-target.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<script src='test-helpers.js'></script>
+<script src='messaging-serialize-helpers.js'></script>
+<script src='message-target.js'></script>
+<script id="inline_script">
+ 'use strict'
+
+ if (window.parent !== null && window.parent != window) {
+ window.parent.postMessage('LOADED', { targetOrigin: '*' });
+ }
+
+ if (window.opener !== null) {
+ window.opener.postMessage('LOADED', { targetOrigin: '*' });
+ }
+
+ // Use an undefined message target to send responses to
+ // MessageEvent::source instead.
+ const target = undefined;
+
+ add_message_event_handlers(
+ /*receiver=*/self, target, /*target_origin=*/'*');
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/fs/resources/message-target.js b/testing/web-platform/tests/fs/resources/message-target.js
new file mode 100644
index 0000000000..7c29afb799
--- /dev/null
+++ b/testing/web-platform/tests/fs/resources/message-target.js
@@ -0,0 +1,158 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /fs/resources/test-helpers.js
+
+// add_message_event_handlers() is the helper function used to setup all
+// message targets, including iframes and workers.
+//
+// Adds a message event handler and a message error handler to |receiver|.
+// The 'data' property from received MessageEvents must include a 'type'
+// property. The 'type' selects the test logic to run. Most message type
+// handlers use postMessage() to respond to the sender with test results.
+// The sender then validates the test results after receiving the response.
+//
+// Both |target| and |target_origin| are optional. |target| is used
+// to send message responses back to the sender. When omitted, the
+// 'source' from received MessageEvents is used instead.
+//
+// For window messaging, |target_origin| specifies the origin to receive
+// responses. Most window tests use '*' for the |target_origin|. Worker
+// and message port tests must use undefined for |target_origin| to avoid
+// exceptions.
+function add_message_event_handlers(receiver, target, target_origin) {
+ receiver.addEventListener('message', async function (message_event) {
+ const message_data = message_event.data;
+
+ // Reply to the sender using the 'source' from the received MessageEvent.
+ let message_source = message_event.source;
+ if (message_source === null) {
+ // However, some message senders, like DedicatedWorkers, don't include
+ // a source. Fallback to the target when the source is null.
+ message_source = target;
+ }
+
+ try {
+ switch (message_data.type) {
+ case 'receive-message-port':
+ // Receive a MessagePort to use as a message target for testing.
+ add_message_event_handlers(
+ /*receiver=*/message_data.message_port,
+ /*target=*/message_data.message_port);
+ message_data.message_port.start();
+ break;
+
+ case 'create-broadcast-channel':
+ // Create a BroadcastChannel to use as a message target for testing.
+ const broadcast_channel =
+ new BroadcastChannel(message_data.broadcast_channel_name);
+ add_message_event_handlers(
+ /*receiver=*/broadcast_channel,
+ /*target=*/broadcast_channel);
+ message_source.postMessage(
+ { type: 'broadcast-channel-created' },
+ { targetOrigin: target_origin });
+ break;
+
+ case 'receive-file-system-handles':
+ // Receive a list of cloned FileSystemFileHandles. Access the
+ // properties of each FileSystemFileHandle by serializing the
+ // handle to a JavaScript object. Then respond with the serialized
+ // results, enabling the sender to verify that the cloned handle
+ // produced the expected property values from this execution context.
+ const serialized_handles = [];
+ const cloned_handles = message_data.cloned_handles;
+ for (let i = 0; i < cloned_handles.length; ++i) {
+ const serialized = await serialize_handle(cloned_handles[i]);
+ serialized_handles.push(serialized);
+ }
+ message_source.postMessage({
+ type: 'receive-serialized-file-system-handles',
+ serialized_handles,
+ // Respond with the cloned handles to create new clones for
+ // the sender to verify.
+ cloned_handles,
+ }, { targetOrigin: target_origin });
+ break;
+
+ case 'receive-serialized-file-system-handles':
+ // Do nothing. This message is meant for test runner validation.
+ // Other message targets may receive this message while testing
+ // broadcast channels.
+ break;
+
+ case 'create-file':
+ // Create a new file and then respond to the sender with it.
+ const directory = await navigator.storage.getDirectory();
+ const file_handle =
+ await directory.getFileHandle('temp-file', { create: true });
+ message_source.postMessage(
+ { type: 'receive-file', file_handle },
+ { targetOrigin: target_origin });
+ break;
+
+ case 'create-directory':
+ // Create a new directory and then respond to the sender with it.
+ const parent_directory = await navigator.storage.getDirectory();
+ const directory_handle =
+ await parent_directory.getDirectoryHandle('temp-directory',
+ { create: true });
+ message_source.postMessage(
+ { type: 'receive-directory', directory_handle },
+ { targetOrigin: target_origin });
+ break;
+
+ case 'create-sync-access-handle':
+ // Receive a file and create a sync access handle out of it. Report
+ // success to the sender.
+ let success = true;
+ try {
+ const access_handle = await message_data.file_handle
+ .createSyncAccessHandle({mode: "in-place"});
+ access_handle.close();
+ } catch (error) {
+ success = false;
+ }
+
+ message_source.postMessage(
+ { type: 'receive-sync-access-handle-result', success },
+ { targetOrigin: target_origin });
+ break;
+
+ default:
+ throw `Unknown message type: '${message_data.type}'`;
+ }
+ } catch (error) {
+ // Respond with an error to trigger a failure in the sender's
+ // test runner.
+ message_source.postMessage(`ERROR: ${error}`,
+ { targetOrigin: target_origin });
+ }
+ });
+
+ receiver.addEventListener('messageerror', async function (message_event) {
+ // Select the target for message responses (see comment in 'message' event
+ // listener above).
+ let message_source = message_event.source;
+ if (message_source === null) {
+ message_source = target;
+ }
+
+ try {
+ // Respond with the MessageEvent's property values, enabling the sender
+ // to verify results.
+ const serialized_message_error_event =
+ serialize_message_error_event(message_event);
+ message_source.postMessage({
+ type: 'serialized-message-error',
+ serialized_message_error_event
+ }, { targetOrigin: target_origin });
+ } catch (error) {
+ // Respond with an error to trigger a failure in the sender's
+ // test runner.
+ message_source.postMessage(`ERROR: ${error}`,
+ { targetOrigin: target_origin });
+ }
+ });
+}
diff --git a/testing/web-platform/tests/fs/resources/messaging-blob-helpers.js b/testing/web-platform/tests/fs/resources/messaging-blob-helpers.js
new file mode 100644
index 0000000000..852f2e2d32
--- /dev/null
+++ b/testing/web-platform/tests/fs/resources/messaging-blob-helpers.js
@@ -0,0 +1,51 @@
+'use strict';
+
+// Creates a blob URL with the contents of 'message-target.html'. Use the
+// blob as an iframe src or a window.open() URL, which creates a same origin
+// message target.
+async function create_message_target_blob_url(test) {
+ const html = await create_message_target_html_without_subresources(test);
+ const blob = new Blob([html], { type: 'text/html' });
+ return URL.createObjectURL(blob);
+}
+
+// Creates a data URI with the contents of 'message-target.html'. Use the
+// data URI as an iframe src, which creates a cross origin message target.
+async function create_message_target_data_uri(test) {
+ const iframe_html =
+ await create_message_target_html_without_subresources(test);
+ return `data:text/html,${encodeURIComponent(iframe_html)}`;
+}
+
+// Constructs a version of 'message-target.html' without any subresources.
+// Enables the creation of blob URLs, data URIs and iframe srcdocs re-using
+// the contents of 'message-target.html'.
+async function create_message_target_html_without_subresources(test) {
+ const test_helpers_script = await fetch_text('resources/test-helpers.js');
+
+ const messaging_helpers_script =
+ await fetch_text('resources/messaging-helpers.js');
+
+ const messaging_serialize_helpers_script =
+ await fetch_text('resources/messaging-serialize-helpers.js');
+
+ const message_target_script =
+ await fetch_text('resources/message-target.js');
+
+ // Get the inline script code from 'message-target.html'.
+ const iframe = await add_iframe(test, { src: 'resources/message-target.html' });
+ const iframe_script =
+ iframe.contentWindow.document.getElementById('inline_script').outerHTML;
+ iframe.remove();
+
+ return '<!DOCTYPE html>' +
+ `<script>${test_helpers_script}</script>` +
+ `<script>${messaging_serialize_helpers_script}</script>` +
+ `<script>${message_target_script}</script>` +
+ `${iframe_script}`;
+}
+
+async function fetch_text(url) {
+ const response = await fetch(url);
+ return await response.text();
+}
diff --git a/testing/web-platform/tests/fs/resources/messaging-helpers.js b/testing/web-platform/tests/fs/resources/messaging-helpers.js
new file mode 100644
index 0000000000..776c0c50d5
--- /dev/null
+++ b/testing/web-platform/tests/fs/resources/messaging-helpers.js
@@ -0,0 +1,187 @@
+'use strict';
+
+// This script depends on the following script:
+// /fs/resources/test-helpers.js
+// /service-workers/service-worker/resources/test-helpers.sub.js
+
+// Define the URL constants used for each type of message target, including
+// iframes and workers.
+const kDocumentMessageTarget = 'resources/message-target.html';
+const kSharedWorkerMessageTarget = 'resources/message-target-shared-worker.js';
+const kServiceWorkerMessageTarget =
+ 'resources/message-target-service-worker.js';
+const kDedicatedWorkerMessageTarget =
+ 'resources/message-target-dedicated-worker.js';
+
+function create_dedicated_worker(test, url) {
+ const dedicated_worker = new Worker(url);
+ test.add_cleanup(() => {
+ dedicated_worker.terminate();
+ });
+ return dedicated_worker;
+}
+
+async function create_service_worker(test, script_url, scope) {
+ const registration = await service_worker_unregister_and_register(
+ test, script_url, scope);
+ test.add_cleanup(() => {
+ return registration.unregister();
+ });
+ return registration;
+}
+
+// Creates an iframe and waits to receive a message from the iframe.
+// Valid |options| include src, srcdoc and sandbox, which mirror the
+// corresponding iframe element properties.
+async function add_iframe(test, options) {
+ const iframe = document.createElement('iframe');
+
+ if (options.sandbox !== undefined) {
+ iframe.sandbox = options.sandbox;
+ }
+
+ if (options.src !== undefined) {
+ iframe.src = options.src;
+ }
+
+ if (options.srcdoc !== undefined) {
+ iframe.srcdoc = options.srcdoc;
+ }
+
+ document.body.appendChild(iframe);
+ test.add_cleanup(() => {
+ iframe.remove();
+ });
+
+ await wait_for_loaded_message(self);
+ return iframe;
+}
+
+// Creates a child window using window.open() and waits to receive a message
+// from the child window.
+async function open_window(test, url) {
+ const child_window = window.open(url);
+ test.add_cleanup(() => {
+ child_window.close();
+ });
+ await wait_for_loaded_message(self);
+ return child_window;
+}
+
+// Wait until |receiver| gets a message event with the data set to 'LOADED'.
+// The postMessage() tests use messaging instead of the loaded event because
+// cross-origin child windows from window.open() do not dispatch the loaded
+// event to the parent window.
+async function wait_for_loaded_message(receiver) {
+ const message_promise = new Promise((resolve, reject) => {
+ receiver.addEventListener('message', message_event => {
+ if (message_event.data === 'LOADED') {
+ resolve();
+ } else {
+ reject('The message target must receive a "LOADED" message response.');
+ }
+ });
+ });
+ await message_promise;
+}
+
+// Sets up a new message channel. Sends one port to |target| and then returns
+// the other port.
+function create_message_channel(target, target_origin) {
+ const message_channel = new MessageChannel();
+
+ const message_data =
+ { type: 'receive-message-port', message_port: message_channel.port2 };
+ target.postMessage(
+ message_data,
+ {
+ transfer: [message_channel.port2],
+ targetOrigin: target_origin
+ });
+ message_channel.port1.start();
+ return message_channel.port1;
+}
+
+// Creates a variety of different FileSystemFileHandles for testing.
+async function create_file_system_handles(test, root) {
+ // Create some files to use with postMessage().
+ const empty_file = await createEmptyFile(test, 'empty-file', root);
+ const first_file = await createFileWithContents(
+ test, 'first-file-with-contents', 'first-text-content', root);
+ const second_file = await createFileWithContents(
+ test, 'second-file-with-contents', 'second-text-content', root);
+
+ // Create an empty directory to use with postMessage().
+ const empty_directory = await createDirectory(test, 'empty-directory', root);
+
+ // Create a directory containing both files and subdirectories to use
+ // with postMessage().
+ const directory_with_files =
+ await createDirectory(test, 'directory-with-files', root);
+ await createFileWithContents(test, 'first-file-in-directory',
+ 'first-directory-text-content', directory_with_files);
+ await createFileWithContents(test, 'second-file-in-directory',
+ 'second-directory-text-content', directory_with_files);
+ const subdirectory =
+ await createDirectory(test, 'subdirectory', directory_with_files);
+ await createFileWithContents(test, 'first-file-in-subdirectory',
+ 'first-subdirectory-text-content', subdirectory);
+
+ return [
+ empty_file,
+ first_file,
+ second_file,
+ // Include the same FileSystemFileHandle twice.
+ second_file,
+ empty_directory,
+ // Include the Same FileSystemDirectoryHandle object twice.
+ empty_directory,
+ directory_with_files
+ ];
+}
+
+// Tests sending an array of FileSystemHandles to |target| with postMessage().
+// The array includes both FileSystemFileHandles and FileSystemDirectoryHandles.
+// After receiving the message, |target| accesses all cloned handles by
+// serializing the properties of each handle to a JavaScript object.
+//
+// |target| then responds with the resulting array of serialized handles. The
+// response also includes the array of cloned handles, which creates more
+// clones. After receiving the response, this test runner verifies that both
+// the serialized handles and the cloned handles contain the expected properties.
+async function do_post_message_test(
+ test, root_dir, receiver, target, target_origin) {
+ // Create and send the handles to |target|.
+ const handles =
+ await create_file_system_handles(test, root_dir, target, target_origin);
+ target.postMessage(
+ { type: 'receive-file-system-handles', cloned_handles: handles },
+ { targetOrigin: target_origin });
+
+ // Wait for |target| to respond with results.
+ const event_watcher = new EventWatcher(test, receiver, 'message');
+ const message_event = await event_watcher.wait_for('message');
+ const response = message_event.data;
+
+ assert_equals(response.type, 'receive-serialized-file-system-handles',
+ 'The test runner must receive a "serialized-file-system-handles" ' +
+ `message response. Actual response: ${response}`);
+
+ // Verify the results.
+ const expected_serialized_handles = await serialize_handles(handles);
+
+ assert_equals_serialized_handles(
+ response.serialized_handles, expected_serialized_handles);
+
+ await assert_equals_cloned_handles(response.cloned_handles, handles);
+}
+
+// Runs the same test as do_post_message_test(), but uses a MessagePort.
+// This test starts by establishing a message channel between the test runner
+// and |target|. Afterwards, the test sends FileSystemHandles through the
+// message port channel.
+async function do_message_port_test(test, root_dir, target, target_origin) {
+ const message_port = create_message_channel(target, target_origin);
+ await do_post_message_test(
+ test, root_dir, /*receiver=*/ message_port, /*target=*/ message_port);
+}
diff --git a/testing/web-platform/tests/fs/resources/messaging-serialize-helpers.js b/testing/web-platform/tests/fs/resources/messaging-serialize-helpers.js
new file mode 100644
index 0000000000..c7dfc0436e
--- /dev/null
+++ b/testing/web-platform/tests/fs/resources/messaging-serialize-helpers.js
@@ -0,0 +1,210 @@
+'use strict';
+
+// This script depends on the following script:
+// /fs/resources/test-helpers.js
+
+// Serializes an array of FileSystemHandles where each element can be either a
+// FileSystemFileHandle or FileSystemDirectoryHandle.
+async function serialize_handles(handle_array) {
+ const serialized_handle_array = [];
+ for (let i = 0; i < handle_array.length; ++i) {
+ serialized_handle_array.push(await serialize_handle(handle_array[i]));
+ }
+ return serialized_handle_array;
+}
+
+// Serializes either a FileSystemFileHandle or FileSystemDirectoryHandle.
+async function serialize_handle(handle) {
+ switch (handle.kind) {
+ case 'directory':
+ return await serialize_file_system_directory_handle(handle);
+ case 'file':
+ return await serialize_file_system_file_handle(handle);
+ default:
+ throw 'Object is not a FileSystemFileHandle or ' +
+ `FileSystemDirectoryHandle ${handle}`;
+ }
+}
+
+// Creates a dictionary for a FileSystemHandle base, which contains
+// serialized properties shared by both FileSystemFileHandle and
+// FileSystemDirectoryHandle.
+async function serialize_file_system_handle(handle) {
+ return {
+ kind: handle.kind,
+ name: handle.name
+ };
+}
+
+// Create a dictionary with each property value in FileSystemFileHandle.
+// Also, reads the contents of the file to include with the returned
+// dictionary. Example output:
+// {
+// kind: "file",
+// name: "example-file-name",
+// contents: "example-file-contents"
+// }
+async function serialize_file_system_file_handle(file_handle) {
+ const contents = await getFileContents(file_handle);
+
+ const serialized_file_system_handle =
+ await serialize_file_system_handle(file_handle);
+
+ return Object.assign(serialized_file_system_handle, { contents });
+}
+
+// Create a dictionary with each property value in FileSystemDirectoryHandle.
+// Example output:
+// {
+// kind: "directory",
+// name: "example-directory-name",
+// files: [<first serialized file>, ...]
+// directories: [<first serialized subdirectory>, ...]
+// }
+async function serialize_file_system_directory_handle(directory_handle) {
+ // Serialize the contents of the directory.
+ const serialized_files = [];
+ const serialized_directories = [];
+ for await (const child_handle of directory_handle.values()) {
+ const serialized_child_handle = await serialize_handle(child_handle);
+ if (child_handle.kind === "directory") {
+ serialized_directories.push(serialized_child_handle);
+ } else {
+ serialized_files.push(serialized_child_handle);
+ }
+ }
+
+ // Order the serialized contents of the directory by name.
+ serialized_files.sort((left, right) => {
+ return left.name.localeCompare(right.name);
+ });
+ serialized_directories.sort((left, right) => {
+ return left.name.localeCompare(right.name);
+ });
+
+ // Serialize the directory's common properties shared by all
+ // FileSystemHandles.
+ const serialized_file_system_handle =
+ await serialize_file_system_handle(directory_handle);
+
+ return Object.assign(
+ serialized_file_system_handle,
+ { files: serialized_files, directories: serialized_directories });
+}
+
+// Verifies |left_array| is a clone of |right_array| where each element
+// is a cloned FileSystemHandle with the same properties and contents.
+async function assert_equals_cloned_handles(left_array, right_array) {
+ assert_equals(left_array.length, right_array.length,
+ 'Each array of FileSystemHandles must have the same length');
+
+ for (let i = 0; i < left_array.length; ++i) {
+ assert_not_equals(left_array[i], right_array[i],
+ 'Clones must create new FileSystemHandle instances.');
+
+ const left_serialized = await serialize_handle(left_array[i]);
+ const right_serialized = await serialize_handle(right_array[i]);
+ assert_equals_serialized_handle(left_serialized, right_serialized);
+ }
+}
+
+// Verifies |left_array| is the same as |right_array| where each element
+// is a serialized FileSystemHandle with the same properties.
+function assert_equals_serialized_handles(left_array, right_array) {
+ assert_equals(left_array.length, right_array.length,
+ 'Each array of serialized handles must have the same length');
+
+ for (let i = 0; i < left_array.length; ++i) {
+ assert_equals_serialized_handle(left_array[i], right_array[i]);
+ }
+}
+
+// Verifies each property of a serialized FileSystemFileHandle or
+// FileSystemDirectoryHandle.
+function assert_equals_serialized_handle(left, right) {
+ switch (left.kind) {
+ case 'directory':
+ assert_equals_serialized_file_system_directory_handle(left, right);
+ break;
+ case 'file':
+ assert_equals_serialized_file_system_file_handle(left, right);
+ break;
+ default:
+ throw 'Object is not a FileSystemFileHandle or ' +
+ `FileSystemDirectoryHandle ${left}`;
+ }
+}
+
+// Compares the output of serialize_file_system_handle() for
+// two FileSystemHandles.
+function assert_equals_serialized_file_system_handle(left, right) {
+ assert_equals(left.kind, right.kind,
+ 'Each FileSystemHandle instance must use the expected "kind".');
+
+ assert_equals(left.name, right.name,
+ 'Each FileSystemHandle instance must use the expected "name" ' +
+ ' property.');
+}
+
+// Compares the output of serialize_file_system_file_handle()
+// for two FileSystemFileHandle.
+function assert_equals_serialized_file_system_file_handle(left, right) {
+ assert_equals_serialized_file_system_handle(left, right);
+ assert_equals(left.contents, right.contents,
+ 'Each FileSystemFileHandle instance must have the same contents.');
+}
+
+// Compares the output of serialize_file_system_directory_handle()
+// for two FileSystemDirectoryHandles.
+function assert_equals_serialized_file_system_directory_handle(left, right) {
+ assert_equals_serialized_file_system_handle(left, right);
+
+ assert_equals(left.files.length, right.files.length,
+ 'Each FileSystemDirectoryHandle must contain the same number of ' +
+ 'file children');
+
+ for (let i = 0; i < left.files.length; ++i) {
+ assert_equals_serialized_file_system_file_handle(
+ left.files[i], right.files[i]);
+ }
+
+ assert_equals(left.directories.length, right.directories.length,
+ 'Each FileSystemDirectoryHandle must contain the same number of ' +
+ 'directory children');
+
+ for (let i = 0; i < left.directories.length; ++i) {
+ assert_equals_serialized_file_system_directory_handle(
+ left.directories[i], right.directories[i]);
+ }
+}
+
+// Creates a dictionary with interesting property values from MessageEvent.
+function serialize_message_error_event(message_error_event) {
+ return {
+ data: message_error_event.data,
+ origin: message_error_event.origin,
+ last_event_id: message_error_event.lastEventId,
+ has_source: (message_error_event.source !== null),
+ ports_length: message_error_event.ports.length
+ };
+}
+
+// Compares the output of serialize_message_error_event() with an
+// expected result.
+function assert_equals_serialized_message_error_event(
+ serialized_event, expected_origin, expected_has_source) {
+ assert_equals(serialized_event.data, null,
+ 'The message error event must set the "data" property to null.');
+
+ assert_equals(serialized_event.origin, expected_origin,
+ 'The message error event must have the expected "origin" property.');
+
+ assert_equals(serialized_event.last_event_id, "",
+ 'The message error event must set the "lastEventId" property to the empty string.');
+
+ assert_equals(serialized_event.has_source, expected_has_source,
+ 'The message error event must have the expected "source" property.');
+
+ assert_equals(serialized_event.ports_length, 0,
+ 'The message error event must not contain any message ports.');
+}
diff --git a/testing/web-platform/tests/fs/resources/opaque-origin-sandbox.html b/testing/web-platform/tests/fs/resources/opaque-origin-sandbox.html
new file mode 100644
index 0000000000..b2582ca4c2
--- /dev/null
+++ b/testing/web-platform/tests/fs/resources/opaque-origin-sandbox.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<script>
+ 'use strict'
+
+ // Sends a message containing the result of navigator.storage.getDirectory()
+ // to its creator.
+
+ function post_message(data) {
+ if (window.parent !== null) {
+ window.parent.postMessage(data, { targetOrigin: '*' });
+ }
+ if (window.opener !== null) {
+ window.opener.postMessage(data, { targetOrigin: '*' });
+ }
+ }
+
+ try {
+ navigator.storage.getDirectory()
+ .then(() => {
+ post_message('navigator.storage.getDirectory(): FULFILLED');
+ }).catch(error => {
+ post_message(`navigator.storage.getDirectory(): REJECTED: ${error.name}`);
+ });
+ } catch (error) {
+ post_message(`navigator.storage.getDirectory(): EXCEPTION: ${error.name}`);
+ }
+</script>
diff --git a/testing/web-platform/tests/fs/resources/sandboxed-fs-test-helpers.js b/testing/web-platform/tests/fs/resources/sandboxed-fs-test-helpers.js
new file mode 100644
index 0000000000..400b2c507b
--- /dev/null
+++ b/testing/web-platform/tests/fs/resources/sandboxed-fs-test-helpers.js
@@ -0,0 +1,28 @@
+// This file defines a directory_test() function that can be used to define
+// tests that require a FileSystemDirectoryHandle. The implementation of that
+// function in this file will return an empty directory in the sandboxed file
+// system.
+//
+// Another implementation of this function exists in
+// file-system-access/local-fs-test-helpers.js, where that version uses the
+// local file system instead.
+
+function getFileSystemType() {
+ return 'sandboxed';
+}
+
+async function cleanupSandboxedFileSystem() {
+ const dir = await navigator.storage.getDirectory();
+ for await (let entry of dir.values())
+ await dir.removeEntry(entry.name, {recursive: entry.kind === 'directory'});
+}
+
+function directory_test(func, description) {
+ promise_test(async t => {
+ // To be extra resilient against bad tests, cleanup before every test.
+ await cleanupSandboxedFileSystem();
+
+ const dir = await navigator.storage.getDirectory();
+ await func(t, dir);
+ }, description);
+}
diff --git a/testing/web-platform/tests/fs/resources/sync-access-handle-test.js b/testing/web-platform/tests/fs/resources/sync-access-handle-test.js
new file mode 100644
index 0000000000..46c5d3072c
--- /dev/null
+++ b/testing/web-platform/tests/fs/resources/sync-access-handle-test.js
@@ -0,0 +1,17 @@
+async function cleanupSandboxedFileSystem() {
+ const dir = await navigator.storage.getDirectory();
+ for await (let entry of dir.values())
+ await dir.removeEntry(entry.name, {recursive: entry.kind === 'directory'});
+}
+
+function sync_access_handle_test(test, description) {
+ promise_test(async t => {
+ // To be extra resilient against bad tests, cleanup before every test.
+ await cleanupSandboxedFileSystem();
+ const dir = await navigator.storage.getDirectory();
+ const fileHandle = await dir.getFileHandle('OPFS.test', {create: true});
+ const syncHandle = await fileHandle.createSyncAccessHandle();
+ test(t, syncHandle);
+ syncHandle.close();
+ }, description);
+}
diff --git a/testing/web-platform/tests/fs/resources/test-helpers.js b/testing/web-platform/tests/fs/resources/test-helpers.js
new file mode 100644
index 0000000000..01593165a0
--- /dev/null
+++ b/testing/web-platform/tests/fs/resources/test-helpers.js
@@ -0,0 +1,116 @@
+// A special path component meaning "this directory."
+const kCurrentDirectory = '.';
+
+// A special path component meaning "the parent directory."
+const kParentDirectory = '..';
+
+// Array of separators used to separate components in hierarchical paths.
+let kPathSeparators;
+if (navigator.userAgent.includes('Windows NT')) {
+ // Windows uses both '/' and '\' as path separators.
+ kPathSeparators = ['/', '\\'];
+} else {
+ kPathSeparators = ['/'];
+}
+
+async function getFileSize(handle) {
+ const file = await handle.getFile();
+ return file.size;
+}
+
+async function getFileContents(handle) {
+ const file = await handle.getFile();
+ return new Response(file).text();
+}
+
+async function getDirectoryEntryCount(handle) {
+ let result = 0;
+ for await (let entry of handle) {
+ result++;
+ }
+ return result;
+}
+
+async function getSortedDirectoryEntries(handle) {
+ let result = [];
+ for await (let entry of handle.values()) {
+ if (entry.kind === 'directory') {
+ result.push(entry.name + '/');
+ } else {
+ result.push(entry.name);
+ }
+ }
+ result.sort();
+ return result;
+}
+
+async function createDirectory(test, name, parent) {
+ const new_dir_handle = await parent.getDirectoryHandle(name, {create: true});
+ cleanup(test, new_dir_handle, async () => {
+ try {
+ await parent.removeEntry(name, {recursive: true});
+ } catch (e) {
+ // Ignore any errors when removing directories, as tests might
+ // have already removed the directory.
+ }
+ });
+ return new_dir_handle;
+}
+
+async function createEmptyFile(test, name, parent) {
+ const handle = await parent.getFileHandle(name, {create: true});
+ cleanup(test, handle, async () => {
+ try {
+ await parent.removeEntry(name);
+ } catch (e) {
+ // Ignore any errors when removing files, as tests might already remove
+ // the file.
+ }
+ });
+ // Make sure the file is empty.
+ assert_equals(await getFileSize(handle), 0);
+ return handle;
+}
+
+async function createFileWithContents(test, name, contents, parent) {
+ const handle = await createEmptyFile(test, name, parent);
+ const writer = await handle.createWritable();
+ await writer.write(new Blob([contents]));
+ await writer.close();
+ return handle;
+}
+
+var fs_cleanups = [];
+
+async function cleanup(test, value, cleanup_func) {
+ if (fs_cleanups.length === 0) {
+ // register to get called back once from cleanup
+ test.add_cleanup(async () => {
+ // Cleanup in LIFO order to ensure locks are released correctly relative
+ // to thinks like removeEntry(). Do so in a serialized form, not in parallel!
+ fs_cleanups.reverse();
+ for (let cleanup of fs_cleanups) {
+ try {
+ await cleanup();
+ } catch (e) {
+ // Ignore any errors when removing files, as tests might already remove
+ // the file.
+ }
+ }
+ fs_cleanups.length = 0;
+ });
+ }
+ fs_cleanups.push(cleanup_func);
+ return value;
+}
+
+async function cleanup_writable(test, value) {
+ return cleanup(test, value, async () => {
+ try {
+ return (await value).close();
+ } catch (e) {
+ // Ignore any errors when closing writables, since attempting to close
+ // aborted or closed writables will error.
+ }
+ });
+}
diff --git a/testing/web-platform/tests/fs/root-name.https.any.js b/testing/web-platform/tests/fs/root-name.https.any.js
new file mode 100644
index 0000000000..650a7a64ee
--- /dev/null
+++ b/testing/web-platform/tests/fs/root-name.https.any.js
@@ -0,0 +1,6 @@
+'use strict';
+
+promise_test(async test => {
+ let root = await navigator.storage.getDirectory();
+ assert_equals(root.name, '');
+}, 'getDirectory returns a directory whose name is the empty string');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-IndexedDB.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-IndexedDB.js
new file mode 100644
index 0000000000..9e114619bf
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-IndexedDB.js
@@ -0,0 +1,145 @@
+'use strict';
+
+directory_test(async (t, root_dir) => {
+ const handles = await create_file_system_handles(t, root_dir);
+
+ const db = await createDatabase(t, db => {
+ const store = db.createObjectStore('store');
+ });
+
+ const value = handles;
+
+ const tx = db.transaction('store', 'readwrite');
+ const store = tx.objectStore('store');
+ await promiseForRequest(t, store.put(value, 'key'));
+ const result = await promiseForRequest(t, store.get('key'));
+
+ await promiseForTransaction(t, tx);
+
+ assert_true(Array.isArray(result), 'Result should be an array');
+ assert_equals(result.length, value.length);
+ await assert_equals_cloned_handles(result, value);
+}, 'Store handle in IndexedDB and read from pending transaction.');
+
+directory_test(async (t, root_dir) => {
+ const handles = await create_file_system_handles(t, root_dir);
+
+ const db = await createDatabase(t, db => {
+ const store = db.createObjectStore('store');
+ });
+
+ const value = handles;
+
+ let tx = db.transaction('store', 'readwrite');
+ let store = tx.objectStore('store');
+ await promiseForRequest(t, store.put(value, 'key'));
+ await promiseForTransaction(t, tx);
+
+ tx = db.transaction('store', 'readonly');
+ store = tx.objectStore('store');
+ const result = await promiseForRequest(t, store.get('key'));
+ await promiseForTransaction(t, tx);
+
+ assert_true(Array.isArray(result), 'Result should be an array');
+ assert_equals(result.length, value.length);
+ await assert_equals_cloned_handles(result, value);
+}, 'Store handle in IndexedDB and read from new transaction.');
+
+directory_test(async (t, root_dir) => {
+ const handles = await create_file_system_handles(t, root_dir);
+
+ const db = await createDatabase(t, db => {
+ const store = db.createObjectStore('store');
+ });
+
+ const value = {handles, blob: new Blob(["foobar"])};
+
+ let tx = db.transaction('store', 'readwrite');
+ let store = tx.objectStore('store');
+ await promiseForRequest(t, store.put(value, 'key'));
+ await promiseForTransaction(t, tx);
+
+ tx = db.transaction('store', 'readonly');
+ store = tx.objectStore('store');
+ const result = await promiseForRequest(t, store.get('key'));
+ await promiseForTransaction(t, tx);
+
+ assert_true(Array.isArray(result.handles), 'Result should be an array');
+ assert_equals(result.handles.length, value.handles.length);
+ await assert_equals_cloned_handles(result.handles, value.handles);
+
+ assert_equals(await result.blob.text(), await value.blob.text());
+}, 'Store handles and blobs in IndexedDB.');
+
+directory_test(async (t, root_dir) => {
+ const handles = await create_file_system_handles(t, root_dir);
+
+ const db = await createDatabase(t, db => {
+ const store = db.createObjectStore('store');
+ });
+
+ const value = handles;
+
+ let tx = db.transaction('store', 'readwrite');
+ let store = tx.objectStore('store');
+ await promiseForRequest(t, store.put(value, 'key'));
+ await promiseForTransaction(t, tx);
+
+ tx = db.transaction('store', 'readonly');
+ store = tx.objectStore('store');
+ let cursor_request = store.openCursor();
+ await requestWatcher(t, cursor_request).wait_for('success');
+ const result = cursor_request.result.value;
+ await promiseForTransaction(t, tx);
+
+ assert_true(Array.isArray(result), 'Result should be an array');
+ assert_equals(result.length, value.length);
+ await assert_equals_cloned_handles(result, value);
+}, 'Store handle in IndexedDB and read using a cursor.');
+
+directory_test(async (t, root_dir) => {
+ const handles = await create_file_system_handles(t, root_dir);
+
+ const db = await createDatabase(t, db => {
+ const store = db.createObjectStore('store', {keyPath: 'key'});
+ });
+
+ const value = handles;
+ let tx = db.transaction('store', 'readwrite');
+ let store = tx.objectStore('store');
+ await promiseForRequest(t, store.put({key: 'key', value}));
+ await promiseForTransaction(t, tx);
+
+ tx = db.transaction('store', 'readonly');
+ store = tx.objectStore('store');
+ const result = await promiseForRequest(t, store.get('key'));
+ await promiseForTransaction(t, tx);
+
+ assert_true(Array.isArray(result.value), 'Result should be an array');
+ assert_equals(result.value.length, value.length);
+ await assert_equals_cloned_handles(result.value, value);
+}, 'Store handle in IndexedDB using inline keys.');
+
+directory_test(async (t, root_dir) => {
+ const expected_root_name = '';
+ assert_equals(root_dir.name, expected_root_name);
+
+ const db = await createDatabase(t, db => {
+ const store = db.createObjectStore('store', {keyPath: 'key'});
+ });
+
+ const value = [ root_dir ];
+ let tx = db.transaction('store', 'readwrite');
+ let store = tx.objectStore('store');
+ await promiseForRequest(t, store.put({key: 'key', value}));
+ await promiseForTransaction(t, tx);
+
+ tx = db.transaction('store', 'readonly');
+ store = tx.objectStore('store');
+ const result = await promiseForRequest(t, store.get('key'));
+ await promiseForTransaction(t, tx);
+
+ const actual = result.value[ 0 ];
+ assert_equals(actual.name, expected_root_name);
+ assert_true(await root_dir.isSameEntry(actual));
+}, 'Store and retrieve the root directory from IndexedDB.');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-buckets.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-buckets.js
new file mode 100644
index 0000000000..c06e940d7e
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-buckets.js
@@ -0,0 +1,42 @@
+'use strict';
+
+directory_test(async (t, root_dir) => {
+ await prepareForBucketTest(t);
+
+ const inboxBucket = await navigator.storageBuckets.open('inbox');
+ const inboxRootDir = await inboxBucket.getDirectory();
+
+ assert_false(await inboxRootDir.isSameEntry(root_dir));
+
+ const handle1 = await createEmptyFile(t, 'mtime.txt', inboxRootDir);
+ const handle2 = await inboxRootDir.getFileHandle('mtime.txt');
+ assert_true(await handle1.isSameEntry(handle2));
+}, 'isSameEntry works as expected with buckets');
+
+directory_test(async (t, root_dir) => {
+ await prepareForBucketTest(t);
+
+ const inboxBucket = await navigator.storageBuckets.open('inbox');
+ await navigator.storageBuckets.delete('inbox');
+ const directoryPromise = inboxBucket.getDirectory();
+ await promise_rejects_dom(t, 'InvalidStateError', directoryPromise);
+}, 'getDirectory promise rejects if bucket has been deleted');
+
+directory_test(async (t, root_dir) => {
+ await prepareForBucketTest(t);
+
+ const inboxBucket = await navigator.storageBuckets.open('inbox', {quota: 500});
+ const inboxRootDir = await inboxBucket.getDirectory();
+
+ // Short file succeeds.
+ const file =
+ await createFileWithContents(t, 'mtime.txt', 'short file', inboxRootDir);
+
+ // Longer file fails.
+ return promise_rejects_dom(
+ t, 'QuotaExceededError',
+ createFileWithContents(
+ t, 'mtime2.txt',
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum',
+ inboxRootDir));
+}, 'Bucket quota restricts the size of a file that can be created');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-getUniqueId.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-getUniqueId.js
new file mode 100644
index 0000000000..ee0cd5e349
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-getUniqueId.js
@@ -0,0 +1,89 @@
+'use strict';
+
+directory_test(async (t, root_dir) => {
+ assert_equals(await root_dir.getUniqueId(), await root_dir.getUniqueId());
+
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+ assert_equals(await subdir.getUniqueId(), await subdir.getUniqueId());
+}, 'identical directory handles return the same ID');
+
+directory_test(async (t, root_dir) => {
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+
+ assert_not_equals(await root_dir.getUniqueId(), await subdir.getUniqueId());
+}, 'different directories return different IDs');
+
+directory_test(async (t, root_dir) => {
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+ const subdir2 = await root_dir.getDirectoryHandle('subdir-name');
+
+ assert_equals(await subdir.getUniqueId(), await subdir2.getUniqueId());
+}, 'different handles for the same directory return the same ID');
+
+directory_test(async (t, root_dir) => {
+ const handle = await createEmptyFile(t, 'foo.txt', root_dir);
+
+ assert_equals(await handle.getUniqueId(), await handle.getUniqueId());
+}, 'identical file handles return the same unique ID');
+
+directory_test(async (t, root_dir) => {
+ const handle1 = await createEmptyFile(t, 'foo.txt', root_dir);
+ const handle2 = await createEmptyFile(t, 'bar.txt', root_dir);
+
+ assert_not_equals(await handle1.getUniqueId(), await handle2.getUniqueId());
+}, 'different files return different IDs');
+
+directory_test(async (t, root_dir) => {
+ const handle1 = await createEmptyFile(t, 'foo.txt', root_dir);
+ const handle2 = await root_dir.getFileHandle('foo.txt');
+
+ assert_equals(await handle1.getUniqueId(), await handle2.getUniqueId());
+}, 'different handles for the same file return the same ID');
+
+directory_test(async (t, root_dir) => {
+ const handle1 = await createEmptyFile(t, 'foo.txt', root_dir);
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+ const handle2 = await createEmptyFile(t, 'foo.txt', subdir);
+
+ assert_not_equals(await handle1.getUniqueId(), await handle2.getUniqueId());
+}, 'two files of the same name in different directories return different IDs');
+
+directory_test(async (t, root_dir) => {
+ const handle1 = await createEmptyFile(t, 'foo.txt', root_dir);
+ const handle2 = await createDirectory(t, 'subdir-name', root_dir);
+
+ assert_not_equals(await handle1.getUniqueId(), await handle2.getUniqueId());
+}, 'a file and a directory return different IDs');
+
+directory_test(async (t, root_dir) => {
+ const file_handle = await createEmptyFile(t, 'foo', root_dir);
+ const file_id = await file_handle.getUniqueId();
+
+ // Remove the file.
+ await root_dir.removeEntry('foo');
+
+ // Create a directory of the same name and path.
+ const dir_handle = await createDirectory(t, 'foo', root_dir);
+ assert_not_equals(await dir_handle.getUniqueId(), file_id);
+}, 'a file and a directory of the same path return different IDs');
+
+directory_test(async (t, root_dir) => {
+ const handle = await createEmptyFile(t, 'foo.txt', root_dir);
+ const id_before = await handle.getUniqueId();
+
+ // Write to the file. The unique ID should not change.
+ const writable = await cleanup_writable(t, await handle.createWritable());
+ await writable.write("blah");
+ await writable.close();
+
+ assert_equals(await handle.getUniqueId(), id_before);
+}, 'unique ID of a file handle does not change after writes');
+
+directory_test(async (t, root_dir) => {
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+
+ const UUIDRegex =
+ /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/
+ assert_true(UUIDRegex.test(await root_dir.getUniqueId()));
+ assert_true(UUIDRegex.test(await subdir.getUniqueId()));
+}, 'unique ID is in GUID version 4 format');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-isSameEntry.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-isSameEntry.js
new file mode 100644
index 0000000000..e3b6d1891e
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-isSameEntry.js
@@ -0,0 +1,107 @@
+'use strict';
+
+directory_test(async (t, root_dir) => {
+ assert_true(await root_dir.isSameEntry(root_dir));
+
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+ assert_true(await subdir.isSameEntry(subdir));
+}, 'isSameEntry for identical directory handles returns true');
+
+directory_test(async (t, root_dir) => {
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+
+ assert_false(await root_dir.isSameEntry(subdir));
+ assert_false(await subdir.isSameEntry(root_dir));
+}, 'isSameEntry for different directories returns false');
+
+directory_test(async (t, root_dir) => {
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+ const subdir2 = await root_dir.getDirectoryHandle('subdir-name');
+
+ assert_true(await subdir.isSameEntry(subdir2));
+ assert_true(await subdir2.isSameEntry(subdir));
+}, 'isSameEntry for different handles for the same directory');
+
+directory_test(async (t, root_dir) => {
+ const handle = await createEmptyFile(t, 'mtime.txt', root_dir);
+
+ assert_true(await handle.isSameEntry(handle));
+}, 'isSameEntry for identical file handles returns true');
+
+directory_test(async (t, root_dir) => {
+ const handle1 = await createEmptyFile(t, 'mtime.txt', root_dir);
+ const handle2 = await createEmptyFile(t, 'foo.txt', root_dir);
+
+ assert_false(await handle1.isSameEntry(handle2));
+ assert_false(await handle2.isSameEntry(handle1));
+}, 'isSameEntry for different files returns false');
+
+directory_test(async (t, root_dir) => {
+ const handle1 = await createEmptyFile(t, 'mtime.txt', root_dir);
+ const handle2 = await root_dir.getFileHandle('mtime.txt');
+
+ assert_true(await handle1.isSameEntry(handle2));
+ assert_true(await handle2.isSameEntry(handle1));
+}, 'isSameEntry for different handles for the same file');
+
+directory_test(async (t, root_dir) => {
+ const handle1 = await createEmptyFile(t, 'mtime.txt', root_dir);
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+ const handle2 = await createEmptyFile(t, 'mtime.txt', subdir);
+
+ assert_false(await handle1.isSameEntry(handle2));
+ assert_false(await handle2.isSameEntry(handle1));
+}, 'isSameEntry comparing a file to a file in a different directory returns false');
+
+directory_test(async (t, root_dir) => {
+ const handle1 = await createEmptyFile(t, 'mtime.txt', root_dir);
+ const handle2 = await createDirectory(t, 'subdir-name', root_dir);
+
+ assert_false(await handle1.isSameEntry(handle2));
+ assert_false(await handle2.isSameEntry(handle1));
+}, 'isSameEntry comparing a file to a directory returns false');
+
+directory_test(async (t, root_dir) => {
+ const filename = 'foo';
+ const handle1 = await createEmptyFile(t, filename, root_dir);
+ // Remove the file and create a new file of the same path.
+ await root_dir.removeEntry(filename);
+ const handle2 = await createEmptyFile(t, filename, root_dir);
+
+ assert_true(
+ await handle1.isSameEntry(handle2),
+ 'two file handles pointing at the same path should be considered the same entry');
+ assert_true(
+ await handle2.isSameEntry(handle1),
+ 'two file handles pointing at the same path should be considered the same entry');
+}, 'isSameEntry comparing two files pointing to the same path returns true');
+
+directory_test(async (t, root_dir) => {
+ const filename = 'foo';
+ const handle1 = await createDirectory(t, filename, root_dir);
+ // Remove the directory and create a new directory of the same path.
+ await root_dir.removeEntry(filename);
+ const handle2 = await createDirectory(t, filename, root_dir);
+
+ assert_true(
+ await handle1.isSameEntry(handle2),
+ 'two directory handles pointing at the same path should be considered the same entry');
+ assert_true(
+ await handle2.isSameEntry(handle1),
+ 'two directory handles pointing at the same path should be considered the same entry');
+}, 'isSameEntry comparing two directories pointing to the same path returns true');
+
+directory_test(async (t, root_dir) => {
+ const filename = 'foo';
+ const dir_handle = await createDirectory(t, filename, root_dir);
+ // Remove the directory and create a file of the same path.
+ await root_dir.removeEntry(filename);
+ const file_handle = await createEmptyFile(t, filename, root_dir);
+
+ assert_false(
+ await dir_handle.isSameEntry(file_handle),
+ 'a file and directory handle pointing at the same path should not be considered the same entry');
+ assert_false(
+ await file_handle.isSameEntry(dir_handle),
+ 'a file and directory handle pointing at the same path should not be considered the same entry');
+}, 'isSameEntry comparing a file to a directory of the same path returns false');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-BroadcastChannel.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-BroadcastChannel.js
new file mode 100644
index 0000000000..681037db2f
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-BroadcastChannel.js
@@ -0,0 +1,82 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /fs/resources/messaging-serialize-helpers.js
+// /fs/resources/test-helpers.js
+// /service-workers/service-worker/resources/test-helpers.sub.js
+
+// Sets up a new broadcast channel in |target|. Posts a message instructing
+// |target| to open the broadcast channel using |broadcast_channel_name|.
+async function create_broadcast_channel(
+ test, broadcast_channel_name, receiver, target, target_origin) {
+ target.postMessage(
+ { type: 'create-broadcast-channel', broadcast_channel_name },
+ { targetOrigin: target_origin });
+ const event_watcher = new EventWatcher(test, receiver, 'message');
+
+ // Wait until |target| is listening to the broad cast channel.
+ const message_event = await event_watcher.wait_for('message');
+ assert_equals(message_event.data.type, 'broadcast-channel-created',
+ 'The message target must receive a "broadcast-channel-created" message ' +
+ 'response.');
+}
+
+// This test is very similar to 'FileSystemBaseHandle-postMessage.js'. It
+// starts by creating three message targets for the broadcast channel:
+// an iframe, dedicated worker and a service worker. After setup, an array
+// of FileSystemHandles is sent across the broadcast channel. The test
+// expects three responses -- one from each message target.
+directory_test(async (t, root) => {
+ const broadcast_channel_name = 'file-system-file-handle-channel';
+ const broadcast_channel = new BroadcastChannel(broadcast_channel_name);
+ const broadcast_channel_event_watcher =
+ new EventWatcher(t, broadcast_channel, 'message');
+
+ const iframe = await add_iframe(t, { src: kDocumentMessageTarget });
+ await create_broadcast_channel(
+ t, broadcast_channel_name, self, iframe.contentWindow, '*');
+
+ const scope = `${kServiceWorkerMessageTarget}` +
+ '?post-message-to-broadcast-channel-with-file-handle';
+
+ const registration = await create_service_worker(
+ t, kServiceWorkerMessageTarget, scope);
+
+ await create_broadcast_channel(
+ t, broadcast_channel_name,
+ navigator.serviceWorker, registration.installing);
+
+ const dedicated_worker =
+ create_dedicated_worker(t, kDedicatedWorkerMessageTarget);
+
+ await create_broadcast_channel(
+ t, broadcast_channel_name, dedicated_worker, dedicated_worker);
+
+ const handles = await create_file_system_handles(t, root);
+
+ broadcast_channel.postMessage(
+ { type: 'receive-file-system-handles', cloned_handles: handles });
+
+ const expected_response_count = 3;
+ const responses = [];
+ for (let i = 0; i < expected_response_count; ++i) {
+ const message_event =
+ await broadcast_channel_event_watcher.wait_for('message');
+ responses.push(message_event.data);
+ }
+
+ const expected_serialized_handles = await serialize_handles(handles);
+
+ for (let i = 0; i < responses.length; ++i) {
+ assert_equals(responses[i].type, 'receive-serialized-file-system-handles',
+ 'The test runner must receive a "serialized-file-system-handles" ' +
+ `message response. Actual response: ${responses[i]}`);
+
+ assert_equals_serialized_handles(
+ responses[i].serialized_handles, expected_serialized_handles);
+
+ await assert_equals_cloned_handles(responses[i].cloned_handles, handles);
+ }
+}, 'Send and receive messages using a broadcast channel in an iframe, ' +
+'dedicated worker and service worker.'); \ No newline at end of file
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-Error.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-Error.js
new file mode 100644
index 0000000000..7c97a7da48
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-Error.js
@@ -0,0 +1,244 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /fs/resources/messaging-blob-helpers.js
+// /fs/resources/messaging-serialize-helpers.js
+// /fs/resources/test-helpers.js
+// /common/get-host-info.sub.js
+// /service-workers/service-worker/resources/test-helpers.sub.js
+
+// Define URL constants for cross origin windows.
+const kRemoteOrigin = get_host_info().HTTPS_REMOTE_ORIGIN;
+const kRemoteOriginDocumentMessageTarget = `${kRemoteOrigin}${base_path()}` +
+ kDocumentMessageTarget;
+
+// Sending a FileSystemHandle to a cross origin |target| through postMessage()
+// must dispatch the 'messageerror' event.
+//
+// This test sends a FileSystemHandle to |target|. |target| responds with a
+// serialized MessageEvent from the 'messageerror' event, allowing the test
+// runner to verify MessageEvent properties.
+async function do_send_message_error_test(
+ test,
+ root_dir,
+ receiver,
+ target,
+ target_origin,
+ // False when the MessageEvent's source is null.
+ expected_has_source,
+ // The origin of MessageEvents received by |target|.
+ expected_origin) {
+ const message_watcher = new EventWatcher(test, receiver, 'message');
+
+ // Send a file to |target|.
+ const file = await createFileWithContents(
+ test, 'test-error-file', 'test-error-file-contents', root_dir);
+ target.postMessage(
+ { type: 'receive-file-system-handles', cloned_file_system_handles: [file] },
+ { targetOrigin: target_origin });
+
+ // Wait for |target| to respond with results.
+ let message_event = await message_watcher.wait_for('message');
+ const first_response = message_event.data;
+ assert_equals(first_response.type, 'serialized-message-error',
+ 'The test runner must receive a "serialized-message-error" message ' +
+ 'in response to a FileSystemFileHandle message.');
+
+ // Verify the results.
+ assert_equals_serialized_message_error_event(
+ first_response.serialized_message_error_event,
+ expected_origin, expected_has_source);
+
+ // Send a directory to |target|.
+ const directory = await createDirectory(
+ test, 'test-error-directory', root_dir);
+
+ target.postMessage(
+ {
+ type: 'receive-file-system-handles',
+ cloned_file_system_handles: [directory]
+ }, { targetOrigin: target_origin });
+
+ // Wait for |target| to respond with results.
+ message_event = await message_watcher.wait_for('message');
+ const second_response = message_event.data;
+ assert_equals(second_response.type, 'serialized-message-error',
+ 'The test runner must receive a "serialized-message-error" message ' +
+ 'response to a FileSystemDirectoryHandle message.');
+
+ // Verify the results.
+ assert_equals_serialized_message_error_event(
+ second_response.serialized_message_error_event,
+ expected_origin, expected_has_source);
+}
+
+// This test receives a FileSystemHandle from |target|. This test runner
+// must dispatch the 'messageerror' event after receiving a handle from target.
+async function do_receive_message_error_test(
+ test,
+ receiver,
+ target,
+ target_origin,
+ // False when the MessageEvent's source is null.
+ expected_has_source,
+ // The origin of MessageEvents received by this test runner.
+ expected_origin) {
+ const error_watcher = new EventWatcher(test, receiver, 'messageerror');
+
+ // Receive a file from |target|.
+ target.postMessage(
+ { type: 'create-file' }, { targetOrigin: target_origin });
+ const first_error = await error_watcher.wait_for('messageerror');
+ const serialized_first_error = serialize_message_error_event(first_error);
+ assert_equals_serialized_message_error_event(
+ serialized_first_error, expected_origin, expected_has_source);
+
+ // Receive a directory from |target|.
+ target.postMessage(
+ { type: 'create-directory' }, { targetOrigin: target_origin });
+ const second_error = await error_watcher.wait_for('messageerror');
+ const serialized_second_error = serialize_message_error_event(second_error);
+ assert_equals_serialized_message_error_event(
+ serialized_second_error, expected_origin, expected_has_source);
+}
+
+// Performs the send message error test followed by the receive message error
+// test.
+async function do_send_and_receive_message_error_test(
+ test,
+ root_dir,
+ receiver,
+ target,
+ target_origin,
+ // False when the MessageEvent's source is null.
+ expected_has_source,
+ // The origin of MessageEvents received by |target|.
+ expected_origin,
+ // The origin of MessageEvents received by this test runner.
+ expected_remote_origin) {
+ await do_send_message_error_test(
+ test, root_dir, receiver, target, target_origin, expected_has_source,
+ expected_origin);
+ await do_receive_message_error_test(
+ test, receiver, target, target_origin, expected_has_source,
+ expected_remote_origin);
+}
+
+// Runs the same test as do_send_message_error_test(), but uses a MessagePort.
+// This test starts by establishing a message channel between the test runner
+// and |target|.
+async function do_send_message_port_error_test(
+ test, root_dir, target, target_origin) {
+ const message_port = create_message_channel(target, target_origin);
+ await do_send_message_error_test(
+ test, root_dir, /*receiver=*/message_port, /*target=*/message_port,
+ /*target_origin=*/undefined, /*expected_has_source=*/false,
+ /*expected_origin=*/'', /*expected_remote_origin=*/'');
+}
+
+// Runs the same test as do_receive_message_error_test(), but uses a MessagePort.
+async function do_receive_message_port_error_test(
+ test, target, target_origin) {
+ const message_port = create_message_channel(target, target_origin);
+ await do_receive_message_error_test(
+ test, /*receiver=*/message_port, /*target=*/message_port,
+ /*target_origin=*/undefined, /*expected_has_source=*/false,
+ /*expected_origin=*/'');
+}
+
+// Runs the same test as do_send_and_receive_message_error_test(), but uses a
+// MessagePort.
+async function do_send_and_receive_message_port_error_test(
+ test, root_dir, target, target_origin) {
+ await do_send_message_port_error_test(
+ test, root_dir, target, target_origin);
+ await do_receive_message_port_error_test(
+ test, target, target_origin);
+}
+
+directory_test(async (t, root_dir) => {
+ const iframe = await add_iframe(
+ t, { src: kRemoteOriginDocumentMessageTarget });
+ await do_send_and_receive_message_error_test(
+ t, root_dir, /*receiver=*/self, /*target=*/iframe.contentWindow,
+ /*target_origin=*/'*', /*expected_has_source=*/true,
+ /*expected_origin=*/location.origin,
+ /*expected_remote_origin=*/kRemoteOrigin);
+}, 'Fail to send and receive messages using a cross origin iframe.');
+
+directory_test(async (t, root_dir) => {
+ const iframe = await add_iframe(t, { src: kRemoteOriginDocumentMessageTarget });
+ await do_send_and_receive_message_port_error_test(
+ t, root_dir, /*target=*/iframe.contentWindow, /*target_origin=*/'*');
+}, 'Fail to send and receive messages using a cross origin message port in ' +
+'an iframe.');
+
+directory_test(async (t, root_dir) => {
+ const iframe = await add_iframe(
+ t, { src: kDocumentMessageTarget, sandbox: 'allow-scripts' });
+
+ await do_send_message_error_test(
+ t, root_dir, /*receiver=*/self, /*target=*/iframe.contentWindow,
+ /*target_origin=*/'*', /*expected_has_source*/true,
+ /*expected_origin=*/location.origin);
+}, 'Fail to send to a sandboxed iframe.');
+
+directory_test(async (t, root_dir) => {
+ const iframe = await add_iframe(
+ t, { src: kDocumentMessageTarget, sandbox: 'allow-scripts' });
+ await do_send_message_port_error_test(
+ t, root_dir, /*target=*/iframe.contentWindow, /*target_origin=*/'*');
+}, 'Fail to send messages using a message port to a sandboxed ' +
+'iframe.');
+
+directory_test(async (t, root_dir) => {
+ const iframe_data_uri = await create_message_target_data_uri(t);
+ const iframe = await add_iframe(t, { src: iframe_data_uri });
+ await do_send_message_error_test(t, root_dir, /*receiver=*/self,
+ /*target=*/iframe.contentWindow, /*target_origin=*/'*',
+ /*expected_has_source*/true, /*expected_origin=*/location.origin);
+ // Do not test receiving FileSystemHandles from the data URI iframe. Data URI
+ // iframes are insecure and do not expose the File System APIs.
+}, 'Fail to send messages to a data URI iframe.');
+
+directory_test(async (t, root_dir) => {
+ const iframe_data_uri = await create_message_target_data_uri(t);
+ const iframe = await add_iframe(t, { src: iframe_data_uri });
+ await do_send_message_port_error_test(
+ t, root_dir, /*target=*/iframe.contentWindow, /*target_origin=*/'*');
+}, 'Fail to send messages using a message port in a data URI iframe.');
+
+directory_test(async (t, root_dir) => {
+ const child_window = await open_window(t, kRemoteOriginDocumentMessageTarget);
+ await do_send_and_receive_message_error_test(
+ t, root_dir, /*receiver=*/self, /*target=*/child_window, /*target_origin=*/'*',
+ /*expected_has_source=*/true, /*expected_origin=*/location.origin,
+ /*expected_remote_origin=*/kRemoteOrigin);
+}, 'Fail to send and receive messages using a cross origin window.');
+
+directory_test(async (t, root_dir) => {
+ const child_window = await open_window(t, kRemoteOriginDocumentMessageTarget);
+ await do_send_message_port_error_test(
+ t, root_dir, /*target=*/child_window, /*target_origin=*/'*');
+}, 'Fail to send and receive messages using a cross origin message port in ' +
+'a window.');
+
+directory_test(async (t, root_dir) => {
+ const url = `${kDocumentMessageTarget}?pipe=header(Content-Security-Policy` +
+ ', sandbox allow-scripts)';
+ const child_window = await open_window(t, url);
+ await do_send_message_error_test(
+ t, root_dir, /*receiver=*/self, /*target=*/child_window,
+ /*target_origin=*/'*', /*expected_has_source*/true,
+ /*expected_origin=*/location.origin);
+}, 'Fail to send messages to a sandboxed window.');
+
+directory_test(async (t, root_dir) => {
+ const url = `${kDocumentMessageTarget}?pipe=header(Content-Security-Policy` +
+ ', sandbox allow-scripts)';
+ const child_window = await open_window(t, url);
+ await do_send_message_port_error_test(
+ t, root_dir, /*target=*/child_window, /*target_origin=*/'*');
+}, 'Fail to send messages using a message port to a sandboxed ' +
+'window.');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-frames.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-frames.js
new file mode 100644
index 0000000000..b70b2992c6
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-frames.js
@@ -0,0 +1,44 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /fs/resources/messaging-blob-helpers.js
+// /fs/resources/messaging-serialize-helpers.js
+// /fs/resources/test-helpers.js
+
+directory_test(
+ async (t, root_dir) => {
+ const iframe = await add_iframe(t, {src: kDocumentMessageTarget});
+ await do_message_port_test(
+ t, root_dir, /*target=*/ iframe.contentWindow,
+ /*target_origin=*/ '*');
+ },
+ 'Send and receive messages using a message port in a same origin ' +
+ 'iframe.');
+
+directory_test(
+ async (t, root_dir) => {
+ const iframe = await add_iframe(t, {
+ src: kDocumentMessageTarget,
+ sandbox: 'allow-scripts allow-same-origin'
+ });
+ await do_message_port_test(
+ t, root_dir, /*target=*/ iframe.contentWindow,
+ /*target_origin=*/ '*');
+ },
+ 'Send and receive messages using a message port in a sandboxed same ' +
+ 'origin iframe.');
+
+directory_test(async (t, root_dir) => {
+ const blob_url = await create_message_target_blob_url(t);
+ const iframe = await add_iframe(t, {src: blob_url});
+ await do_message_port_test(
+ t, root_dir, /*target=*/ iframe.contentWindow, /*target_origin=*/ '*');
+}, 'Send and receive messages using a message port in a blob iframe.');
+
+directory_test(async (t, root_dir) => {
+ const iframe_html = await create_message_target_html_without_subresources(t);
+ const iframe = await add_iframe(t, {srcdoc: iframe_html});
+ await do_message_port_test(
+ t, root_dir, /*target=*/ iframe.contentWindow, /*target_origin=*/ '*');
+}, 'Send and receive messages using a message port in an iframe srcdoc.');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-windows.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-windows.js
new file mode 100644
index 0000000000..dceb250ebb
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-windows.js
@@ -0,0 +1,35 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /fs/resources/messaging-blob-helpers.js
+// /fs/resources/messaging-serialize-helpers.js
+// /fs/resources/test-helpers.js
+
+directory_test(
+ async (t, root_dir) => {
+ const child_window = await open_window(t, kDocumentMessageTarget);
+ await do_message_port_test(
+ t, root_dir, /*target=*/ child_window, /*target_origin=*/ '*');
+ },
+ 'Send and receive messages using a message port in a same origin ' +
+ 'window.');
+
+directory_test(async (t, root_dir) => {
+ const blob_url = await create_message_target_blob_url(t);
+ const child_window = await open_window(t, blob_url);
+ await do_message_port_test(
+ t, root_dir, /*target=*/ child_window, /*target_origin=*/ '*');
+}, 'Send and receive messages using a message port in a blob window.');
+
+directory_test(
+ async (t, root_dir) => {
+ const url =
+ `${kDocumentMessageTarget}?pipe=header(Content-Security-Policy` +
+ ', sandbox allow-scripts allow-same-origin)';
+ const child_window = await open_window(t, url);
+ await do_message_port_test(
+ t, root_dir, /*target=*/ child_window, /*target_origin=*/ '*');
+ },
+ 'Send and receive messages using a message port in a sandboxed same ' +
+ 'origin window.');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-workers.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-workers.js
new file mode 100644
index 0000000000..b386527dbd
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-workers.js
@@ -0,0 +1,40 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /fs/resources/messaging-blob-helpers.js
+// /fs/resources/messaging-serialize-helpers.js
+// /fs/resources/test-helpers.js
+// /service-workers/service-worker/resources/test-helpers.sub.js
+
+directory_test(
+ async (t, root_dir) => {
+ const dedicated_worker =
+ create_dedicated_worker(t, kDedicatedWorkerMessageTarget);
+ await do_message_port_test(t, root_dir, /*target=*/ dedicated_worker);
+ },
+ 'Send and receive messages using a message port in a dedicated ' +
+ 'worker.');
+
+directory_test(
+ async (t, root_dir) => {
+ const scope = `${kServiceWorkerMessageTarget}` +
+ '?post-message-to-message-port-with-file-handle';
+ const registration =
+ await create_service_worker(t, kServiceWorkerMessageTarget, scope);
+ await do_message_port_test(
+ t, root_dir, /*target=*/ registration.installing);
+ },
+ 'Send and receive messages using a message port in a service ' +
+ 'worker.');
+
+if (self.SharedWorker !== undefined) {
+ directory_test(
+ async (t, root_dir) => {
+ const shared_worker = new SharedWorker(kSharedWorkerMessageTarget);
+ shared_worker.port.start();
+ await do_message_port_test(t, root_dir, /*target=*/ shared_worker.port);
+ },
+ 'Send and receive messages using a message port in a shared ' +
+ ' worker.');
+}
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-frames.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-frames.js
new file mode 100644
index 0000000000..1e77b89d77
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-frames.js
@@ -0,0 +1,40 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /fs/resources/messaging-blob-helpers.js
+// /fs/resources/messaging-serialize-helpers.js
+// /fs/resources/test-helpers.js
+
+directory_test(async (t, root_dir) => {
+ const iframe = await add_iframe(t, {src: kDocumentMessageTarget});
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ self, /*target=*/ iframe.contentWindow,
+ /*target_origin=*/ '*');
+}, 'Send and receive messages using a same origin iframe.');
+
+directory_test(async (t, root_dir) => {
+ const iframe = await add_iframe(t, {
+ src: kDocumentMessageTarget,
+ sandbox: 'allow-scripts allow-same-origin'
+ });
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ self, /*target=*/ iframe.contentWindow,
+ /*target_origin=*/ '*');
+}, 'Send and receive messages using a sandboxed same origin iframe.');
+
+directory_test(async (t, root_dir) => {
+ const blob_url = await create_message_target_blob_url(t);
+ const iframe = await add_iframe(t, {src: blob_url});
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ self, /*target=*/ iframe.contentWindow,
+ /*target_origin=*/ '*');
+}, 'Send and receive messages using a blob iframe.');
+
+directory_test(async (t, root_dir) => {
+ const iframe_html = await create_message_target_html_without_subresources(t);
+ const iframe = await add_iframe(t, {srcdoc: iframe_html});
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ self, /*target=*/ iframe.contentWindow,
+ /*target_origin=*/ '*');
+}, 'Send and receive messages using an iframe srcdoc.');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-windows.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-windows.js
new file mode 100644
index 0000000000..798d458534
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-windows.js
@@ -0,0 +1,31 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /fs/resources/messaging-blob-helpers.js
+// /fs/resources/messaging-serialize-helpers.js
+// /fs/resources/test-helpers.js
+
+directory_test(async (t, root_dir) => {
+ const child_window = await open_window(t, kDocumentMessageTarget);
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ self, /*target=*/ child_window,
+ /*target_origin=*/ '*');
+}, 'Send and receive messages using a same origin window.');
+
+directory_test(async (t, root_dir) => {
+ const blob_url = await create_message_target_blob_url(t);
+ const child_window = await open_window(t, blob_url);
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ self, /*target=*/ child_window,
+ /*target_origin=*/ '*');
+}, 'Send and receive messages using a blob window.');
+
+directory_test(async (t, root_dir) => {
+ const url = `${kDocumentMessageTarget}?pipe=header(Content-Security-Policy` +
+ ', sandbox allow-scripts allow-same-origin)';
+ const child_window = await open_window(t, url);
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ self, /*target=*/ child_window,
+ /*target_origin=*/ '*');
+}, 'Send and receive messages using a sandboxed same origin window.');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-workers.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-workers.js
new file mode 100644
index 0000000000..dbd8e5754d
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-workers.js
@@ -0,0 +1,35 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /fs/resources/messaging-blob-helpers.js
+// /fs/resources/messaging-serialize-helpers.js
+// /fs/resources/test-helpers.js
+// /service-workers/service-worker/resources/test-helpers.sub.js
+
+directory_test(async (t, root_dir) => {
+ const dedicated_worker =
+ create_dedicated_worker(t, kDedicatedWorkerMessageTarget);
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ dedicated_worker,
+ /*target=*/ dedicated_worker);
+}, 'Send and receive messages using a dedicated worker.');
+
+directory_test(async (t, root_dir) => {
+ const scope = `${kServiceWorkerMessageTarget}?post-message-with-file-handle`;
+ const registration =
+ await create_service_worker(t, kServiceWorkerMessageTarget, scope);
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ navigator.serviceWorker,
+ /*target=*/ registration.installing);
+}, 'Send and receive messages using a service worker.');
+
+if (self.SharedWorker !== undefined) {
+ directory_test(async (t, root_dir) => {
+ const shared_worker = new SharedWorker(kSharedWorkerMessageTarget);
+ shared_worker.port.start();
+ await do_post_message_test(
+ t, root_dir, /*receiver=*/ shared_worker.port,
+ /*target=*/ shared_worker.port);
+ }, 'Send and receive messages using a shared worker.');
+}
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-remove.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-remove.js
new file mode 100644
index 0000000000..021576310b
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-remove.js
@@ -0,0 +1,105 @@
+'use strict';
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await handle.remove();
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+ await promise_rejects_dom(t, 'NotFoundError', getFileContents(handle));
+}, 'remove() to remove a file');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await handle.remove();
+
+ await promise_rejects_dom(t, 'NotFoundError', handle.remove());
+}, 'remove() on an already removed file should fail');
+
+directory_test(async (t, root) => {
+ const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await dir.remove();
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+ await promise_rejects_dom(t, 'NotFoundError', getSortedDirectoryEntries(dir));
+}, 'remove() to remove an empty directory');
+
+directory_test(async (t, root) => {
+ const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+ await dir.remove();
+
+ await promise_rejects_dom(t, 'NotFoundError', dir.remove());
+}, 'remove() on an already removed directory should fail');
+
+directory_test(async (t, root) => {
+ const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+ t.add_cleanup(() => root.removeEntry('dir-to-remove', {recursive: true}));
+ await createEmptyFile(t, 'file-in-dir', dir);
+
+ await promise_rejects_dom(t, 'InvalidModificationError', dir.remove());
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir-to-remove/']);
+ assert_array_equals(await getSortedDirectoryEntries(dir), ['file-in-dir']);
+}, 'remove() on a non-empty directory should fail');
+
+directory_test(async (t, root) => {
+ // root
+ // ├──file-to-keep
+ // ├──dir-to-remove
+ // ├── file0
+ // ├── dir1-in-dir
+ // │   └── file1
+ // └── dir2
+ const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await createEmptyFile(t, 'file0', dir);
+ const dir1_in_dir = await createDirectory(t, 'dir1-in-dir', dir);
+ await createEmptyFile(t, 'file1', dir1_in_dir);
+ await createDirectory(t, 'dir2-in-dir', dir);
+
+ await dir.remove({recursive: true});
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+}, 'remove() on a directory recursively should delete all sub-items');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await handle.remove({recursive: true});
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+ await promise_rejects_dom(t, 'NotFoundError', getFileContents(handle));
+}, 'remove() on a file should ignore the recursive option');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+
+ const writable = await cleanup_writable(t, await handle.createWritable());
+ await promise_rejects_dom(t, 'NoModificationAllowedError', handle.remove());
+
+ await writable.close();
+ assert_array_equals(
+ await getSortedDirectoryEntries(root),
+ ['file-to-keep', 'file-to-remove']);
+
+ await handle.remove();
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+ await promise_rejects_dom(t, 'NotFoundError', getFileContents(handle));
+}, 'remove() while the file has an open writable fails');
+
+promise_test(async (t) => {
+ const root = await navigator.storage.getDirectory();
+ await root.getFileHandle('file.txt', {create: true});
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file.txt']);
+
+ await root.remove();
+
+ // Creates a fresh sandboxed file system.
+ const newRoot = await navigator.storage.getDirectory();
+ assert_array_equals(await getSortedDirectoryEntries(newRoot), []);
+}, 'can remove the root of a sandbox file system');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getDirectoryHandle.js b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getDirectoryHandle.js
new file mode 100644
index 0000000000..6a63edecc5
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getDirectoryHandle.js
@@ -0,0 +1,117 @@
+'use strict';
+
+directory_test(async (t, root) => {
+ await promise_rejects_dom(
+ t, 'NotFoundError', root.getDirectoryHandle('non-existing-dir'));
+}, 'getDirectoryHandle(create=false) rejects for non-existing directories');
+
+directory_test(async (t, root) => {
+ const handle =
+ await root.getDirectoryHandle('non-existing-dir', {create: true});
+ t.add_cleanup(() => root.removeEntry('non-existing-dir', {recursive: true}));
+
+ assert_equals(handle.kind, 'directory');
+ assert_equals(handle.name, 'non-existing-dir');
+ assert_equals(await getDirectoryEntryCount(handle), 0);
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['non-existing-dir/']);
+}, 'getDirectoryHandle(create=true) creates an empty directory');
+
+directory_test(async (t, root) => {
+ const existing_handle =
+ await root.getDirectoryHandle('dir-with-contents', {create: true});
+ t.add_cleanup(() => root.removeEntry('dir-with-contents', {recursive: true}));
+ const file_handle = await createEmptyFile(t, 'test-file', existing_handle);
+
+ const handle =
+ await root.getDirectoryHandle('dir-with-contents', {create: false});
+
+ assert_equals(handle.kind, 'directory');
+ assert_equals(handle.name, 'dir-with-contents');
+ assert_array_equals(await getSortedDirectoryEntries(handle), ['test-file']);
+}, 'getDirectoryHandle(create=false) returns existing directories');
+
+directory_test(async (t, root) => {
+ const existing_handle =
+ await root.getDirectoryHandle('dir-with-contents', {create: true});
+ t.add_cleanup(() => root.removeEntry('dir-with-contents', {recursive: true}));
+ const file_handle =
+ await existing_handle.getFileHandle('test-file', {create: true});
+
+ const handle =
+ await root.getDirectoryHandle('dir-with-contents', {create: true});
+
+ assert_equals(handle.kind, 'directory');
+ assert_equals(handle.name, 'dir-with-contents');
+ assert_array_equals(await getSortedDirectoryEntries(handle), ['test-file']);
+}, 'getDirectoryHandle(create=true) returns existing directories without erasing');
+
+directory_test(async (t, root) => {
+ await createEmptyFile(t, 'file-name', root);
+
+ await promise_rejects_dom(
+ t, 'TypeMismatchError', root.getDirectoryHandle('file-name'));
+ await promise_rejects_dom(
+ t, 'TypeMismatchError',
+ root.getDirectoryHandle('file-name', {create: false}));
+ await promise_rejects_dom(
+ t, 'TypeMismatchError',
+ root.getDirectoryHandle('file-name', {create: true}));
+}, 'getDirectoryHandle() when a file already exists with the same name');
+
+directory_test(async (t, dir) => {
+ await promise_rejects_js(
+ t, TypeError, dir.getDirectoryHandle('', {create: true}));
+ await promise_rejects_js(
+ t, TypeError, dir.getDirectoryHandle('', {create: false}));
+}, 'getDirectoryHandle() with empty name');
+
+directory_test(async (t, dir) => {
+ await promise_rejects_js(
+ t, TypeError, dir.getDirectoryHandle(kCurrentDirectory));
+ await promise_rejects_js(
+ t, TypeError, dir.getDirectoryHandle(kCurrentDirectory, {create: true}));
+}, `getDirectoryHandle() with "${kCurrentDirectory}" name`);
+
+directory_test(async (t, dir) => {
+ const subdir = await createDirectory(t, 'subdir-name', /*parent=*/ dir);
+
+ await promise_rejects_js(
+ t, TypeError, subdir.getDirectoryHandle(kParentDirectory));
+ await promise_rejects_js(
+ t, TypeError,
+ subdir.getDirectoryHandle(kParentDirectory, {create: true}));
+}, `getDirectoryHandle() with "${kParentDirectory}" name`);
+
+directory_test(async (t, dir) => {
+ const first_subdir_name = 'first-subdir-name';
+ const first_subdir =
+ await createDirectory(t, first_subdir_name, /*parent=*/ dir);
+
+ const second_subdir_name = 'second-subdir-name';
+ const second_subdir =
+ await createDirectory(t, second_subdir_name, /*parent=*/ first_subdir);
+
+ for (let i = 0; i < kPathSeparators.length; ++i) {
+ const path_with_separator =
+ `${first_subdir_name}${kPathSeparators[i]}${second_subdir_name}`;
+ await promise_rejects_js(
+ t, TypeError, dir.getDirectoryHandle(path_with_separator),
+ `getDirectoryHandle() must reject names containing "${
+ kPathSeparators[i]}"`);
+ }
+}, 'getDirectoryHandle(create=false) with a path separator when the directory exists');
+
+directory_test(async (t, dir) => {
+ const subdir_name = 'subdir-name';
+ const subdir = await createDirectory(t, subdir_name, /*parent=*/ dir);
+
+ for (let i = 0; i < kPathSeparators.length; ++i) {
+ const path_with_separator = `${subdir_name}${kPathSeparators[i]}file_name`;
+ await promise_rejects_js(
+ t, TypeError,
+ dir.getDirectoryHandle(path_with_separator, {create: true}),
+ `getDirectoryHandle(true) must reject names containing "${
+ kPathSeparators[i]}"`);
+ }
+}, 'getDirectoryHandle(create=true) with a path separator');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getFileHandle.js b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getFileHandle.js
new file mode 100644
index 0000000000..840e85b436
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getFileHandle.js
@@ -0,0 +1,145 @@
+'use strict';
+
+directory_test(async (t, dir) => {
+ await promise_rejects_dom(
+ t, 'NotFoundError', dir.getFileHandle('non-existing-file'));
+}, 'getFileHandle(create=false) rejects for non-existing files');
+
+directory_test(async (t, dir) => {
+ const handle = await dir.getFileHandle('non-existing-file', {create: true});
+ t.add_cleanup(() => dir.removeEntry('non-existing-file'));
+
+ assert_equals(handle.kind, 'file');
+ assert_equals(handle.name, 'non-existing-file');
+ assert_equals(await getFileSize(handle), 0);
+ assert_equals(await getFileContents(handle), '');
+}, 'getFileHandle(create=true) creates an empty file for non-existing files');
+
+directory_test(async (t, dir) => {
+ var name = '';
+ // test the ascii characters -- start after the non-character ASCII values, exclude DEL
+ for (let i = 32; i < 127; i++) {
+ // Path separators are disallowed
+ let disallow = false;
+ for (let j = 0; j < kPathSeparators.length; ++j) {
+ if (String.fromCharCode(i) == kPathSeparators[j]) {
+ disallow = true;
+ }
+ }
+ if (!disallow) {
+ name += String.fromCharCode(i);
+ }
+ }
+ // Add in CR, LF, FF, Tab, Vertical Tab
+ for (let i = 9; i < 14; i++) {
+ name += String.fromCharCode(i);
+ }
+ const handle = await dir.getFileHandle(name, {create: true});
+ t.add_cleanup(() => dir.removeEntry(name));
+
+ assert_equals(handle.kind, 'file');
+ assert_equals(handle.name, name);
+ assert_equals(await getFileSize(handle), 0);
+ assert_equals(await getFileContents(handle), '');
+}, 'getFileHandle(create=true) creates an empty file with all valid ASCII characters in the name');
+
+directory_test(async (t, dir) => {
+ var name;
+ // A non-ASCII name
+ name = 'Funny cat \u{1F639}'
+ const handle = await dir.getFileHandle(name, {create: true});
+ t.add_cleanup(() => dir.removeEntry(name));
+
+ assert_equals(handle.kind, 'file');
+ assert_equals(handle.name, name);
+ assert_equals(await getFileSize(handle), 0);
+ assert_equals(await getFileContents(handle), '');
+}, 'getFileHandle(create=true) creates an empty file with non-ASCII characters in the name');
+
+directory_test(async (t, dir) => {
+ const existing_handle = await createFileWithContents(
+ t, 'existing-file', '1234567890', /*parent=*/ dir);
+ const handle = await dir.getFileHandle('existing-file');
+
+ assert_equals(handle.kind, 'file');
+ assert_equals(handle.name, 'existing-file');
+ assert_equals(await getFileSize(handle), 10);
+ assert_equals(await getFileContents(handle), '1234567890');
+}, 'getFileHandle(create=false) returns existing files');
+
+directory_test(async (t, dir) => {
+ const existing_handle = await createFileWithContents(
+ t, 'file-with-contents', '1234567890', /*parent=*/ dir);
+ const handle = await dir.getFileHandle('file-with-contents', {create: true});
+
+ assert_equals(handle.kind, 'file');
+ assert_equals(handle.name, 'file-with-contents');
+ assert_equals(await getFileSize(handle), 10);
+ assert_equals(await getFileContents(handle), '1234567890');
+}, 'getFileHandle(create=true) returns existing files without erasing');
+
+directory_test(async (t, dir) => {
+ const dir_handle = await dir.getDirectoryHandle('dir-name', {create: true});
+ t.add_cleanup(() => dir.removeEntry('dir-name', {recursive: true}));
+
+ await promise_rejects_dom(
+ t, 'TypeMismatchError', dir.getFileHandle('dir-name'));
+}, 'getFileHandle(create=false) when a directory already exists with the same name');
+
+directory_test(async (t, dir) => {
+ const dir_handle = await dir.getDirectoryHandle('dir-name', {create: true});
+ t.add_cleanup(() => dir.removeEntry('dir-name', {recursive: true}));
+
+ await promise_rejects_dom(
+ t, 'TypeMismatchError', dir.getFileHandle('dir-name', {create: true}));
+}, 'getFileHandle(create=true) when a directory already exists with the same name');
+
+directory_test(async (t, dir) => {
+ await promise_rejects_js(t, TypeError, dir.getFileHandle('', {create: true}));
+ await promise_rejects_js(
+ t, TypeError, dir.getFileHandle('', {create: false}));
+}, 'getFileHandle() with empty name');
+
+directory_test(async (t, dir) => {
+ await promise_rejects_js(t, TypeError, dir.getFileHandle(kCurrentDirectory));
+ await promise_rejects_js(
+ t, TypeError, dir.getFileHandle(kCurrentDirectory, {create: true}));
+}, `getFileHandle() with "${kCurrentDirectory}" name`);
+
+directory_test(async (t, dir) => {
+ const subdir = await createDirectory(t, 'subdir-name', /*parent=*/ dir);
+
+ await promise_rejects_js(
+ t, TypeError, subdir.getFileHandle(kParentDirectory));
+ await promise_rejects_js(
+ t, TypeError, subdir.getFileHandle(kParentDirectory, {create: true}));
+}, `getFileHandle() with "${kParentDirectory}" name`);
+
+directory_test(async (t, dir) => {
+ const subdir_name = 'subdir-name';
+ const subdir = await createDirectory(t, subdir_name, /*parent=*/ dir);
+
+ const file_name = 'file-name';
+ await createEmptyFile(t, file_name, /*parent=*/ subdir);
+
+ for (let i = 0; i < kPathSeparators.length; ++i) {
+ const path_with_separator =
+ `${subdir_name}${kPathSeparators[i]}${file_name}`;
+ await promise_rejects_js(
+ t, TypeError, dir.getFileHandle(path_with_separator),
+ `getFileHandle() must reject names containing "${kPathSeparators[i]}"`);
+ }
+}, 'getFileHandle(create=false) with a path separator when the file exists.');
+
+directory_test(async (t, dir) => {
+ const subdir_name = 'subdir-name';
+ const subdir = await createDirectory(t, subdir_name, /*parent=*/ dir);
+
+ for (let i = 0; i < kPathSeparators.length; ++i) {
+ const path_with_separator = `${subdir_name}${kPathSeparators[i]}file_name`;
+ await promise_rejects_js(
+ t, TypeError, dir.getFileHandle(path_with_separator, {create: true}),
+ `getFileHandle(create=true) must reject names containing "${
+ kPathSeparators[i]}"`);
+ }
+}, 'getFileHandle(create=true) with a path separator');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-iteration.js b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-iteration.js
new file mode 100644
index 0000000000..815ae21936
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-iteration.js
@@ -0,0 +1,100 @@
+'use strict';
+
+directory_test(async (t, root) => {
+ const file_name1 = 'foo1.txt';
+ const file_name2 = 'foo2.txt';
+ await createFileWithContents(t, file_name1, 'contents', /*parent=*/ root);
+ await createFileWithContents(t, file_name2, 'contents', /*parent=*/ root);
+
+ for await (let entry of root) {
+ break;
+ }
+
+}, 'returning early from an iteration doesn\'t crash');
+
+directory_test(async (t, root) => {
+ const file_name1 = 'foo1.txt';
+ const file_name2 = 'foo2.txt';
+ await createFileWithContents(t, file_name1, 'contents', /*parent=*/ root);
+ await createFileWithContents(t, file_name2, 'contents', /*parent=*/ root);
+
+ let names = [];
+ for await (let entry of root) {
+ assert_true(Array.isArray(entry));
+ assert_equals(entry.length, 2);
+ assert_equals(typeof entry[0], 'string');
+ assert_true(entry[1] instanceof FileSystemFileHandle);
+ assert_equals(entry[0], entry[1].name);
+ names.push(entry[0]);
+ }
+ names.sort();
+ assert_array_equals(names, [file_name1, file_name2]);
+
+}, '@@asyncIterator: full iteration works');
+
+directory_test(async (t, root) => {
+ const file_name1 = 'foo1.txt';
+ const file_name2 = 'foo2.txt';
+ await createFileWithContents(t, file_name1, 'contents', /*parent=*/ root);
+ await createFileWithContents(t, file_name2, 'contents', /*parent=*/ root);
+
+ let names = [];
+ for await (let entry of root.entries()) {
+ assert_true(Array.isArray(entry));
+ assert_equals(entry.length, 2);
+ assert_equals(typeof entry[0], 'string');
+ assert_true(entry[1] instanceof FileSystemFileHandle);
+ assert_equals(entry[0], entry[1].name);
+ names.push(entry[0]);
+ }
+ names.sort();
+ assert_array_equals(names, [file_name1, file_name2]);
+}, 'entries: full iteration works');
+
+directory_test(async (t, root) => {
+ const file_name1 = 'foo1.txt';
+ const file_name2 = 'foo2.txt';
+ await createFileWithContents(t, file_name1, 'contents', /*parent=*/ root);
+ await createFileWithContents(t, file_name2, 'contents', /*parent=*/ root);
+
+ let names = [];
+ for await (let entry of root.values()) {
+ assert_true(entry instanceof FileSystemFileHandle);
+ names.push(entry.name);
+ }
+ names.sort();
+ assert_array_equals(names, [file_name1, file_name2]);
+}, 'values: full iteration works');
+
+directory_test(async (t, root) => {
+ const file_name1 = 'foo1.txt';
+ const file_name2 = 'foo2.txt';
+ await createFileWithContents(t, file_name1, 'contents', /*parent=*/ root);
+ await createFileWithContents(t, file_name2, 'contents', /*parent=*/ root);
+
+ let names = [];
+ for await (let entry of root.keys()) {
+ assert_equals(typeof entry, 'string');
+ names.push(entry);
+ }
+ names.sort();
+ assert_array_equals(names, [file_name1, file_name2]);
+}, 'keys: full iteration works');
+
+directory_test(async (t, root) => {
+ const file_name1 = 'foo1.txt';
+ await createFileWithContents(t, file_name1, 'contents', /*parent=*/ root);
+
+ const next = (() => {
+ const iterator = root.entries();
+ return iterator.next();
+ })();
+ garbageCollect();
+ let entry = await next;
+ assert_false(entry.done);
+ assert_true(Array.isArray(entry.value));
+ assert_equals(entry.value.length, 2);
+ assert_equals(entry.value[0], file_name1);
+ assert_true(entry.value[1] instanceof FileSystemFileHandle);
+ assert_equals(entry.value[1].name, file_name1);
+}, 'iteration while iterator gets garbage collected');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-removeEntry.js b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-removeEntry.js
new file mode 100644
index 0000000000..07d26e9011
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-removeEntry.js
@@ -0,0 +1,222 @@
+'use strict';
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await root.removeEntry('file-to-remove');
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+ await promise_rejects_dom(t, 'NotFoundError', getFileContents(handle));
+}, 'removeEntry() to remove a file');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await root.removeEntry('file-to-remove');
+
+ await promise_rejects_dom(
+ t, 'NotFoundError', root.removeEntry('file-to-remove'));
+}, 'removeEntry() on an already removed file should fail');
+
+directory_test(async (t, root) => {
+ const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await root.removeEntry('dir-to-remove');
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+}, 'removeEntry() to remove an empty directory');
+
+directory_test(async (t, root) => {
+ const dir = await createDirectory(t, 'dir-to-remove', root);
+ await createFileWithContents(t, 'file-in-dir', 'abc', dir);
+
+ await promise_rejects_dom(
+ t, 'InvalidModificationError', root.removeEntry('dir-to-remove'));
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir-to-remove/']);
+ assert_array_equals(await getSortedDirectoryEntries(dir), ['file-in-dir']);
+}, 'removeEntry() on a non-empty directory should fail');
+
+directory_test(async (t, root) => {
+ // root
+ // ├──file-to-keep
+ // ├──dir-to-remove
+ // ├── file0
+ // ├── dir1-in-dir
+ // │   └── file1
+ // └── dir2
+ const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await createEmptyFile(t, 'file0', dir);
+ const dir1_in_dir = await createDirectory(t, 'dir1-in-dir', dir);
+ await createEmptyFile(t, 'file1', dir1_in_dir);
+ await createDirectory(t, 'dir2-in-dir', dir);
+
+ await root.removeEntry('dir-to-remove', {recursive: true});
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+}, 'removeEntry() on a directory recursively should delete all sub-items');
+
+directory_test(async (t, root) => {
+ const dir = await createDirectory(t, 'dir', root);
+ await promise_rejects_js(t, TypeError, dir.removeEntry(''));
+}, 'removeEntry() with empty name should fail');
+
+directory_test(async (t, root) => {
+ const dir = await createDirectory(t, 'dir', root);
+ await promise_rejects_js(t, TypeError, dir.removeEntry(kCurrentDirectory));
+}, `removeEntry() with "${kCurrentDirectory}" name should fail`);
+
+directory_test(async (t, root) => {
+ const dir = await createDirectory(t, 'dir', root);
+ await promise_rejects_js(t, TypeError, dir.removeEntry(kParentDirectory));
+}, `removeEntry() with "${kParentDirectory}" name should fail`);
+
+directory_test(async (t, root) => {
+ const dir_name = 'dir-name';
+ const dir = await createDirectory(t, dir_name, root);
+
+ const file_name = 'file-name';
+ await createEmptyFile(t, file_name, dir);
+
+ for (let i = 0; i < kPathSeparators.length; ++i) {
+ const path_with_separator = `${dir_name}${kPathSeparators[i]}${file_name}`;
+ await promise_rejects_js(
+ t, TypeError, root.removeEntry(path_with_separator),
+ `removeEntry() must reject names containing "${kPathSeparators[i]}"`);
+ }
+}, 'removeEntry() with a path separator should fail.');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await root.removeEntry('file-to-remove');
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+ await promise_rejects_dom(t, 'NotFoundError', getFileContents(handle));
+}, 'removeEntry() to remove a file');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await root.removeEntry('file-to-remove');
+
+ await promise_rejects_dom(t, 'NotFoundError', root.removeEntry('file-to-remove'));
+}, 'removeEntry() on an already removed file should fail');
+
+directory_test(async (t, root) => {
+ const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await root.removeEntry('dir-to-remove');
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+}, 'removeEntry() to remove an empty directory');
+
+directory_test(async (t, root) => {
+ const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+ t.add_cleanup(() => root.removeEntry('dir-to-remove', {recursive: true}));
+ await createEmptyFile(t, 'file-in-dir', dir);
+
+ await promise_rejects_dom(
+ t, 'InvalidModificationError', root.removeEntry('dir-to-remove'));
+ assert_array_equals(
+ await getSortedDirectoryEntries(root), ['dir-to-remove/']);
+ assert_array_equals(await getSortedDirectoryEntries(dir), ['file-in-dir']);
+}, 'removeEntry() on a non-empty directory should fail');
+
+directory_test(async (t, root) => {
+ // root
+ // ├──file-to-keep
+ // ├──dir-to-remove
+ // ├── file0
+ // ├── dir1-in-dir
+ // │   └── file1
+ // └── dir2
+ const dir = await root.getDirectoryHandle('dir-to-remove', {create: true});
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+ await createEmptyFile(t, 'file0', dir);
+ const dir1_in_dir = await createDirectory(t, 'dir1-in-dir', dir);
+ await createEmptyFile(t, 'file1', dir1_in_dir);
+ await createDirectory(t, 'dir2-in-dir', dir);
+
+ await root.removeEntry('dir-to-remove', {recursive: true});
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+}, 'removeEntry() on a directory recursively should delete all sub-items');
+
+directory_test(async (t, root) => {
+ const dir = await createDirectory(t, 'dir', root);
+ await promise_rejects_js(t, TypeError, dir.removeEntry(''));
+}, 'removeEntry() with empty name should fail');
+
+directory_test(async (t, root) => {
+ const dir = await createDirectory(t, 'dir', root);
+ await promise_rejects_js(t, TypeError, dir.removeEntry(kCurrentDirectory));
+}, `removeEntry() with "${kCurrentDirectory}" name should fail`);
+
+directory_test(async (t, root) => {
+ const dir = await createDirectory(t, 'dir', root);
+ await promise_rejects_js(t, TypeError, dir.removeEntry(kParentDirectory));
+}, `removeEntry() with "${kParentDirectory}" name should fail`);
+
+directory_test(async (t, root) => {
+ const dir_name = 'dir-name';
+ const dir = await createDirectory(t, dir_name, root);
+
+ const file_name = 'file-name';
+ await createEmptyFile(t, file_name, dir);
+
+ for (let i = 0; i < kPathSeparators.length; ++i) {
+ const path_with_separator = `${dir_name}${kPathSeparators[i]}${file_name}`;
+ await promise_rejects_js(
+ t, TypeError, root.removeEntry(path_with_separator),
+ `removeEntry() must reject names containing "${kPathSeparators[i]}"`);
+ }
+}, 'removeEntry() with a path separator should fail.');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await createFileWithContents(t, 'file-to-keep', 'abc', root);
+
+ const writable = await cleanup_writable(t, await handle.createWritable());
+ await promise_rejects_dom(
+ t, 'NoModificationAllowedError', root.removeEntry('file-to-remove'));
+
+ await writable.close();
+ await root.removeEntry('file-to-remove');
+
+ assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']);
+}, 'removeEntry() while the file has an open writable fails');
+
+directory_test(async (t, root) => {
+ const dir_name = 'dir-name';
+ const dir = await createDirectory(t, dir_name, root);
+
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', dir);
+ await createFileWithContents(t, 'file-to-keep', 'abc', dir);
+
+ const writable = await cleanup_writable(t, await handle.createWritable());
+ await promise_rejects_dom(
+ t, 'NoModificationAllowedError', root.removeEntry(dir_name));
+
+ await writable.close();
+ assert_array_equals(
+ await getSortedDirectoryEntries(dir), ['file-to-keep', 'file-to-remove']);
+
+ await dir.removeEntry('file-to-remove');
+ assert_array_equals(await getSortedDirectoryEntries(dir), ['file-to-keep']);
+}, 'removeEntry() of a directory while a containing file has an open writable fails');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'file-to-remove', '12345', root);
+ await root.removeEntry('file-to-remove');
+
+ await promise_rejects_dom(t, 'NotFoundError', cleanup_writable(t, handle.createWritable({keepExistingData: true})));
+
+ assert_array_equals(
+ await getSortedDirectoryEntries(root),
+ []);
+}, 'createWritable after removeEntry succeeds but doesnt recreate the file');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-resolve.js b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-resolve.js
new file mode 100644
index 0000000000..a8900f97e5
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-resolve.js
@@ -0,0 +1,27 @@
+'use strict';
+
+directory_test(async (t, root_dir) => {
+ assert_array_equals(await root_dir.resolve(root_dir), []);
+}, 'Resolve returns empty array for same directory');
+
+directory_test(async (t, root_dir) => {
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+ const file = await createEmptyFile(t, 'file-name', subdir);
+
+ assert_array_equals(await root_dir.resolve(file), ['subdir-name', 'file-name']);
+}, 'Resolve returns correct path');
+
+directory_test(async (t, root_dir) => {
+ const subdir = await createDirectory(t, 'subdir😊', root_dir);
+ const file = await createEmptyFile(t, 'file-name', subdir);
+
+ assert_array_equals(await root_dir.resolve(file), ['subdir😊', 'file-name']);
+ assert_array_equals(await root_dir.resolve(subdir), ['subdir😊']);
+}, 'Resolve returns correct path with non-ascii characters');
+
+directory_test(async (t, root_dir) => {
+ const subdir = await createDirectory(t, 'subdir-name', root_dir);
+ const file = await createEmptyFile(t, 'file-name', root_dir);
+
+ assert_equals(await subdir.resolve(file), null);
+}, 'Resolve returns null when entry is not a child');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-create-sync-access-handle.js b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-create-sync-access-handle.js
new file mode 100644
index 0000000000..b9eafaf0d8
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-create-sync-access-handle.js
@@ -0,0 +1,27 @@
+'use strict';
+
+// This script depends on the following scripts:
+// /fs/resources/messaging-helpers.js
+// /fs/resources/test-helpers.js
+
+directory_test(async (t, root_dir) => {
+ const fileSystemType = getFileSystemType();
+ assert_true(
+ fileSystemType == 'sandboxed' || fileSystemType == 'local',
+ 'File system type should be sandboxed or local.');
+ const expect_success = fileSystemType == 'sandboxed';
+
+ const dedicated_worker =
+ create_dedicated_worker(t, kDedicatedWorkerMessageTarget);
+ const file_handle =
+ await root_dir.getFileHandle('sync-access-handle-file', {create: true});
+
+ dedicated_worker.postMessage(
+ {type: 'create-sync-access-handle', file_handle});
+
+ const event_watcher = new EventWatcher(t, dedicated_worker, 'message');
+ const message_event = await event_watcher.wait_for('message');
+ const response = message_event.data;
+
+ assert_equals(response.success, expect_success);
+}, 'Attempt to create a sync access handle.');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-getFile.js b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-getFile.js
new file mode 100644
index 0000000000..be9fbcca6e
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-getFile.js
@@ -0,0 +1,52 @@
+'use strict';
+
+directory_test(async (t, root) => {
+ const fileContents = 'awesome content';
+ let handle = await createFileWithContents(t, 'foo.txt', fileContents, /*parent=*/ root);
+ let file = await handle.getFile();
+ let slice = file.slice(1, file.size);
+ let actualContents = await slice.text();
+ assert_equals(actualContents, fileContents.slice(1, fileContents.length));
+}, 'getFile() provides a file that can be sliced');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'mtime.txt', root);
+ let file = await handle.getFile();
+ const first_mtime = file.lastModified;
+
+ // We wait for 2s here to ensure that the files do not have the
+ // same modification time. Some filesystems have low resolutions
+ // for modification timestamps.
+ let timeout = new Promise(resolve => {
+ t.step_timeout(resolve, 2000);
+ });
+ await timeout;
+
+ const writer = await cleanup_writable(t, await handle.createWritable({keepExistingData: false}));
+ await writer.write(new Blob(['foo']));
+ await writer.close();
+
+ file = await handle.getFile();
+ const second_mtime = file.lastModified;
+
+ // We wait for 5 ms here to ensure that `lastModified`
+ // from the File objects is stable between getFile invocations.
+ timeout = new Promise(resolve => {
+ t.step_timeout(resolve, 5);
+ });
+ await timeout;
+ let fileReplica = await handle.getFile();
+ assert_equals(second_mtime, fileReplica.lastModified);
+
+ assert_less_than(first_mtime, second_mtime);
+}, 'getFile() returns last modified time');
+
+directory_test(async (t, root) => {
+ const fileName = "fileAttributesTest.txt";
+
+ const fileHandle = await createEmptyFile(t, fileName, root);
+ assert_equals(fileHandle.name, fileName);
+
+ const file = await fileHandle.getFile();
+ assert_equals(file.name, fileName);
+}, 'getFile() returns expected name');
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-move.js b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-move.js
new file mode 100644
index 0000000000..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..a853a260b5
--- /dev/null
+++ b/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream-write.js
@@ -0,0 +1,347 @@
+'use strict';
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'empty_blob', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write(new Blob([]));
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '');
+ assert_equals(await getFileSize(handle), 0);
+}, 'write() with an empty blob to an empty file');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'valid_blob', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write(new Blob(['1234567890']));
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '1234567890');
+ assert_equals(await getFileSize(handle), 10);
+}, 'write() a blob to an empty file');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'write_param_empty', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write({type: 'write', data: '1234567890'});
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '1234567890');
+ assert_equals(await getFileSize(handle), 10);
+}, 'write() with WriteParams without position to an empty file');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'string_zero_offset', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write({type: 'write', position: 0, data: '1234567890'});
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '1234567890');
+ assert_equals(await getFileSize(handle), 10);
+}, 'write() a string to an empty file with zero offset');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'blob_zero_offset', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write({type: 'write', position: 0, data: new Blob(['1234567890'])});
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '1234567890');
+ assert_equals(await getFileSize(handle), 10);
+}, 'write() a blob to an empty file with zero offset');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'write_appends', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write('12345');
+ await stream.write('67890');
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '1234567890');
+ assert_equals(await getFileSize(handle), 10);
+}, 'write() called consecutively appends');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'write_appends_object_string', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write('12345');
+ await stream.write({type: 'write', data: '67890'});
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '1234567890');
+ assert_equals(await getFileSize(handle), 10);
+}, 'write() WriteParams without position and string appends');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'write_appends_object_blob', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write('12345');
+ await stream.write({type: 'write', data: new Blob(['67890'])});
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '1234567890');
+ assert_equals(await getFileSize(handle), 10);
+}, 'write() WriteParams without position and blob appends');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'string_with_offset', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write('1234567890');
+ await stream.write({type: 'write', position: 4, data: 'abc'});
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '1234abc890');
+ assert_equals(await getFileSize(handle), 10);
+}, 'write() called with a string and a valid offset');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'write_string_with_offset_after_seek', root);
+ const stream = await handle.createWritable();
+
+ await stream.write('1234567890');
+ await stream.write({type: 'seek', position: 0});
+ await stream.write({type: 'write', position: 4, data: 'abc'});
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '1234abc890');
+ assert_equals(await getFileSize(handle), 10);
+}, 'write() called with a string and a valid offset after seek');
+
+directory_test(async (t, root) => {
+const handle = await createEmptyFile(t, 'blob_with_offset', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+await stream.write('1234567890');
+await stream.write({type: 'write', position: 4, data: new Blob(['abc'])});
+await stream.close();
+
+assert_equals(await getFileContents(handle), '1234abc890');
+assert_equals(await getFileSize(handle), 10);
+}, 'write() called with a blob and a valid offset');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'bad_offset', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write({type: 'write', position: 4, data: new Blob(['abc'])});
+ await stream.close();
+
+ assert_equals(await getFileContents(handle), '\0\0\0\0abc');
+ assert_equals(await getFileSize(handle), 7);
+}, 'write() called with an offset beyond the end of the file');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'empty_string', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write('');
+ await stream.close();
+ assert_equals(await getFileContents(handle), '');
+ assert_equals(await getFileSize(handle), 0);
+}, 'write() with an empty string to an empty file');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'valid_utf8_string', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write('foo🤘');
+ await stream.close();
+ assert_equals(await getFileContents(handle), 'foo🤘');
+ assert_equals(await getFileSize(handle), 7);
+}, 'write() with a valid utf-8 string');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'string_with_unix_line_ending', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write('foo\n');
+ await stream.close();
+ assert_equals(await getFileContents(handle), 'foo\n');
+ assert_equals(await getFileSize(handle), 4);
+}, 'write() with a string with unix line ending preserved');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createEmptyFile(t, 'string_with_windows_line_ending', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await stream.write('foo\r\n');
+ await stream.close();
+ assert_equals(await getFileContents(handle), 'foo\r\n');
+ assert_equals(await getFileSize(handle), 5);
+}, 'write() with a string with windows line ending preserved');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'empty_array_buffer', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ const buf = new ArrayBuffer(0);
+ await stream.write(buf);
+ await stream.close();
+ assert_equals(await getFileContents(handle), '');
+ assert_equals(await getFileSize(handle), 0);
+}, 'write() with an empty array buffer to an empty file');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createEmptyFile(t, 'valid_string_typed_byte_array', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ const buf = new ArrayBuffer(3);
+ const intView = new Uint8Array(buf);
+ intView[0] = 0x66;
+ intView[1] = 0x6f;
+ intView[2] = 0x6f;
+ await stream.write(buf);
+ await stream.close();
+ assert_equals(await getFileContents(handle), 'foo');
+ assert_equals(await getFileSize(handle), 3);
+}, 'write() with a valid typed array buffer');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'atomic_writes.txt', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+ await stream.write('foox');
+
+ const stream2 = await cleanup_writable(t, await handle.createWritable());
+ await stream2.write('bar');
+
+ assert_equals(await getFileSize(handle), 0);
+
+ await stream2.close();
+ assert_equals(await getFileContents(handle), 'bar');
+ assert_equals(await getFileSize(handle), 3);
+
+ await stream.close();
+ assert_equals(await getFileContents(handle), 'foox');
+ assert_equals(await getFileSize(handle), 4);
+}, 'atomic writes: writable file streams make atomic changes on close');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'atomic_write_after_close.txt', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+ await stream.write('foo');
+
+ await stream.close();
+ assert_equals(await getFileContents(handle), 'foo');
+ assert_equals(await getFileSize(handle), 3);
+
+ await promise_rejects_js(
+ t, TypeError, stream.write('abc'));
+}, 'atomic writes: write() after close() fails');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createEmptyFile(t, 'atomic_truncate_after_close.txt', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+ await stream.write('foo');
+
+ await stream.close();
+ assert_equals(await getFileContents(handle), 'foo');
+ assert_equals(await getFileSize(handle), 3);
+
+ await promise_rejects_js(t, TypeError, stream.truncate(0));
+}, 'atomic writes: truncate() after close() fails');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'atomic_close_after_close.txt', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+ await stream.write('foo');
+
+ await stream.close();
+ assert_equals(await getFileContents(handle), 'foo');
+ assert_equals(await getFileSize(handle), 3);
+
+ await promise_rejects_js(t, TypeError, stream.close());
+}, 'atomic writes: close() after close() fails');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'there_can_be_only_one.txt', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+ await stream.write('foo');
+
+ // This test might be flaky if there is a race condition allowing
+ // close() to be called multiple times.
+ const success_promises =
+ [...Array(100)].map(() => stream.close().then(() => 1).catch(() => 0));
+ const close_attempts = await Promise.all(success_promises);
+ const success_count = close_attempts.reduce((x, y) => x + y);
+ assert_equals(success_count, 1);
+}, 'atomic writes: only one close() operation may succeed');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'writer_written', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+ assert_false(stream.locked);
+ const writer = stream.getWriter();
+ assert_true(stream.locked);
+
+ await writer.write('foo');
+ await writer.write(new Blob(['bar']));
+ await writer.write({type: 'seek', position: 0});
+ await writer.write({type: 'write', data: 'baz'});
+ await writer.close();
+
+ assert_equals(await getFileContents(handle), 'bazbar');
+ assert_equals(await getFileSize(handle), 6);
+}, 'getWriter() can be used');
+
+directory_test(async (t, root) => {
+ const handle = await createFileWithContents(
+ t, 'content.txt', 'very long string', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await promise_rejects_dom(
+ t, 'SyntaxError', stream.write({type: 'truncate'}),
+ 'truncate without size');
+}, 'WriteParams: truncate missing size param');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'content.txt', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await promise_rejects_dom(
+ t, 'SyntaxError', stream.write({type: 'write'}), 'write without data');
+}, 'WriteParams: write missing data param');
+
+directory_test(async (t, root) => {
+ const handle = await createEmptyFile(t, 'content.txt', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await promise_rejects_js(
+ t, TypeError, stream.write({type: 'write', data: null}),
+ 'write with null data');
+}, 'WriteParams: write null data param');
+
+directory_test(async (t, root) => {
+ const handle =
+ await createFileWithContents(t, 'content.txt', 'seekable', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+
+ await promise_rejects_dom(
+ t, 'SyntaxError', stream.write({type: 'seek'}), 'seek without position');
+}, 'WriteParams: seek missing position param');
+
+directory_test(async (t, root) => {
+ const source_file =
+ await createFileWithContents(t, 'source_file', 'source data', root);
+ const source_blob = await source_file.getFile();
+ await root.removeEntry(source_file.name);
+
+ const handle = await createEmptyFile(t, 'invalid_blob_test', root);
+ const stream = await cleanup_writable(t, await handle.createWritable());
+ await promise_rejects_dom(t, "NotFoundError", stream.write(source_blob));
+ await promise_rejects_js(t, TypeError, stream.close());
+
+ assert_equals(await getFileContents(handle), '');
+ assert_equals(await getFileSize(handle), 0);
+}, 'write() with an invalid blob to an empty file should reject');
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');