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