summaryrefslogtreecommitdiffstats
path: root/dom/fs/test/common
diff options
context:
space:
mode:
Diffstat (limited to 'dom/fs/test/common')
-rw-r--r--dom/fs/test/common/.eslintrc.js14
-rw-r--r--dom/fs/test/common/dummy.js0
-rw-r--r--dom/fs/test/common/mochitest.toml15
-rw-r--r--dom/fs/test/common/moz.build21
-rw-r--r--dom/fs/test/common/nsresult.js9
-rw-r--r--dom/fs/test/common/test_basics.js375
-rw-r--r--dom/fs/test/common/test_fileSystemDirectoryHandle.js196
-rw-r--r--dom/fs/test/common/test_syncAccessHandle.js237
-rw-r--r--dom/fs/test/common/test_writableFileStream.js153
-rw-r--r--dom/fs/test/common/xpcshell.toml10
10 files changed, 1030 insertions, 0 deletions
diff --git a/dom/fs/test/common/.eslintrc.js b/dom/fs/test/common/.eslintrc.js
new file mode 100644
index 0000000000..d805cf6e0f
--- /dev/null
+++ b/dom/fs/test/common/.eslintrc.js
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.exports = {
+ globals: {
+ Assert: true,
+ exported_symbols: true,
+ require_module: true,
+ Utils: true,
+ },
+};
diff --git a/dom/fs/test/common/dummy.js b/dom/fs/test/common/dummy.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/fs/test/common/dummy.js
diff --git a/dom/fs/test/common/mochitest.toml b/dom/fs/test/common/mochitest.toml
new file mode 100644
index 0000000000..3da39b96b8
--- /dev/null
+++ b/dom/fs/test/common/mochitest.toml
@@ -0,0 +1,15 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+[DEFAULT]
+support-files = [
+ "nsresult.js",
+ "test_basics.js",
+ "test_fileSystemDirectoryHandle.js",
+ "test_syncAccessHandle.js",
+ "test_writableFileStream.js",
+]
+
+["dummy.js"]
+skip-if = ["true"]
diff --git a/dom/fs/test/common/moz.build b/dom/fs/test/common/moz.build
new file mode 100644
index 0000000000..65d62c9cda
--- /dev/null
+++ b/dom/fs/test/common/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MOCHITEST_MANIFESTS += [
+ "mochitest.toml",
+]
+
+XPCSHELL_TESTS_MANIFESTS += [
+ "xpcshell.toml",
+]
+
+TESTING_JS_MODULES.dom.fs.test.common += [
+ "nsresult.js",
+ "test_basics.js",
+ "test_fileSystemDirectoryHandle.js",
+ "test_syncAccessHandle.js",
+ "test_writableFileStream.js",
+]
diff --git a/dom/fs/test/common/nsresult.js b/dom/fs/test/common/nsresult.js
new file mode 100644
index 0000000000..6e59b947a1
--- /dev/null
+++ b/dom/fs/test/common/nsresult.js
@@ -0,0 +1,9 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const nsresult = {
+ NS_ERROR_NOT_IMPLEMENTED: Cr.NS_ERROR_NOT_IMPLEMENTED,
+};
+exported_symbols.nsresult = nsresult;
diff --git a/dom/fs/test/common/test_basics.js b/dom/fs/test/common/test_basics.js
new file mode 100644
index 0000000000..f1cb1c222e
--- /dev/null
+++ b/dom/fs/test/common/test_basics.js
@@ -0,0 +1,375 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This test must be first, since we need the actor not to be created already.
+exported_symbols.testGetDirectoryTwice = async function () {
+ const promise1 = navigator.storage.getDirectory();
+ const promise2 = navigator.storage.getDirectory();
+
+ await Promise.all([promise1, promise2]);
+
+ Assert.ok(true, "Should not have thrown");
+};
+
+exported_symbols.testGetDirectoryDoesNotThrow = async function () {
+ await navigator.storage.getDirectory();
+
+ Assert.ok(true, "Should not have thrown");
+};
+
+exported_symbols.testGetDirectoryKindIsDirectory = async function () {
+ const root = await navigator.storage.getDirectory();
+
+ Assert.equal(root.kind, "directory");
+};
+
+exported_symbols.testDirectoryHandleStringConversion = async function () {
+ const root = await navigator.storage.getDirectory();
+
+ Assert.equal(
+ "" + root,
+ "[object FileSystemDirectoryHandle]",
+ "Is directoryHandle convertible to string?"
+ );
+};
+
+exported_symbols.testNewDirectoryHandleFromPrototype = async function () {
+ const root = await navigator.storage.getDirectory();
+
+ try {
+ Object.create(root.prototype);
+ Assert.ok(false, "Should have thrown");
+ } catch (ex) {
+ Assert.ok(true, "Should have thrown");
+ Assert.ok(ex instanceof TypeError, "Threw the right error type");
+ }
+};
+
+exported_symbols.testIsSameEntryRoot = async function () {
+ const root = await navigator.storage.getDirectory();
+ try {
+ await root.move(root);
+ Assert.ok(false, "root should not be movable");
+ } catch (ex) {
+ Assert.ok(true, "root isn't movable");
+ }
+};
+
+exported_symbols.testDirectoryHandleSupportsKeysIterator = async function () {
+ const root = await navigator.storage.getDirectory();
+
+ const it = await root.keys();
+ Assert.ok(!!it, "Does root support keys iterator?");
+};
+
+exported_symbols.testKeysIteratorNextIsCallable = async function () {
+ const root = await navigator.storage.getDirectory();
+
+ const it = await root.keys();
+ Assert.ok(!!it, "Does root support keys iterator?");
+
+ const item = await it.next();
+ Assert.ok(!!item, "Should return an item");
+};
+
+exported_symbols.testDirectoryHandleSupportsValuesIterator = async function () {
+ const root = await navigator.storage.getDirectory();
+
+ const it = await root.values();
+ Assert.ok(!!it, "Does root support values iterator?");
+};
+
+exported_symbols.testValuesIteratorNextIsCallable = async function () {
+ const root = await navigator.storage.getDirectory();
+
+ const it = await root.values();
+ Assert.ok(!!it, "Does root support values iterator?");
+
+ const item = await it.next();
+ Assert.ok(!!item, "Should return an item");
+};
+
+exported_symbols.testDirectoryHandleSupportsEntriesIterator =
+ async function () {
+ const root = await navigator.storage.getDirectory();
+
+ const it = await root.entries();
+ Assert.ok(!!it, "Does root support entries iterator?");
+ };
+
+exported_symbols.testEntriesIteratorNextIsCallable = async function () {
+ const root = await navigator.storage.getDirectory();
+
+ const it = await root.entries();
+ Assert.ok(!!it, "Does root support entries iterator?");
+
+ const item = await it.next();
+ Assert.ok(!!item, "Should return an item");
+};
+
+exported_symbols.testGetFileHandleIsCallable = async function () {
+ const root = await navigator.storage.getDirectory();
+ const allowCreate = { create: true };
+
+ const item = await root.getFileHandle("fileName", allowCreate);
+ Assert.ok(!!item, "Should return an item");
+
+ await root.removeEntry("fileName");
+};
+
+exported_symbols.testGetDirectoryHandleIsCallable = async function () {
+ const root = await navigator.storage.getDirectory();
+ const allowCreate = { create: true };
+
+ const item = await root.getDirectoryHandle("dirName", allowCreate);
+ Assert.ok(!!item, "Should return an item");
+
+ await root.removeEntry("dirName");
+};
+
+exported_symbols.testRemoveEntryIsCallable = async function () {
+ const root = await navigator.storage.getDirectory();
+ const removeOptions = { recursive: true };
+ const allowCreate = { create: true };
+
+ // Ensure file and directory items exists
+ await root.getFileHandle("fileName", allowCreate);
+ await root.getDirectoryHandle("dirName", allowCreate);
+ await root.removeEntry("fileName", removeOptions);
+ await root.removeEntry("dirName", removeOptions);
+ try {
+ await root.removeEntry("doesNotExist", removeOptions);
+ Assert.ok(false, "Should have thrown");
+ } catch (ex) {
+ Assert.ok(true, "Should have thrown");
+ Assert.equal(
+ ex.message,
+ "Entry not found",
+ "Threw the right error message"
+ );
+ }
+};
+
+exported_symbols.testResolveIsCallable = async function () {
+ const root = await navigator.storage.getDirectory();
+ const allowCreate = { create: true };
+ const item = await root.getFileHandle("fileName", allowCreate);
+
+ let path = await root.resolve(item);
+ Assert.equal(path.length, 1);
+ Assert.equal(path[0], "fileName", "Resolve got the right path");
+
+ await root.removeEntry("fileName");
+};
+
+exported_symbols.testFileType = async function () {
+ const root = await navigator.storage.getDirectory();
+ const allowCreate = { create: true };
+ const nameStem = "testFileType";
+ const empty = "";
+
+ const extensions = [
+ "txt",
+ "jS",
+ "JSON",
+ "css",
+ "html",
+ "htm",
+ "xhtml",
+ "xml",
+ "xhtml+xml",
+ "png",
+ "apng",
+ "jPg",
+ "Jpeg",
+ "pdF",
+ "out",
+ "sh",
+ "ExE",
+ "psid",
+ "EXE ",
+ " EXE",
+ "EX\uff65",
+ "\udbff\udbff\udbff",
+ // XXX: Invalid surrogate combos like "\udc00\udc00\udc00" may map to the same names impacting cleanup.
+ "js\udbff",
+ "\udc00js",
+ "???",
+ "\root",
+ empty,
+ "AXS",
+ "dll",
+ "ocx",
+ "1",
+ "ps1",
+ "cmd",
+ "xpi",
+ "swf",
+ ];
+
+ const expectedTypes = [
+ "text/plain",
+ "application/javascript",
+ "application/json",
+ "text/css",
+ "text/html",
+ "text/html",
+ "application/xhtml+xml",
+ "text/xml",
+ empty,
+ "image/png",
+ "image/apng",
+ "image/jpeg",
+ "image/jpeg",
+ "application/pdf",
+ empty,
+ "application/x-sh",
+ "application/octet-stream",
+ empty,
+ empty,
+ empty,
+ empty,
+ empty,
+ empty,
+ empty,
+ empty,
+ empty,
+ empty,
+ "application/olescript",
+ "application/x-msdownload",
+ "application/octet-stream",
+ empty,
+ empty,
+ "text/plain",
+ "application/x-xpinstall",
+ "application/x-shockwave-flash",
+ ];
+
+ Assert.equal(extensions.length, expectedTypes.length);
+
+ await Promise.all(
+ extensions.map(async (ext, i) => {
+ const fileName = nameStem + "." + ext;
+ const fileHandle = await root.getFileHandle(fileName, allowCreate);
+ const fileObject = await fileHandle.getFile();
+ Assert.equal(fileObject.name, fileHandle.name);
+ Assert.equal(fileObject.type, expectedTypes[i]);
+ await root.removeEntry(fileName);
+ })
+ );
+};
+
+exported_symbols.testContentTypeChangesOnFileMove = async function () {
+ const allowCreate = { create: true };
+ const root = await navigator.storage.getDirectory();
+ const oldName = "testFile.txt";
+ const oldType = "text/plain";
+ const subdir = await root.getDirectoryHandle("subdir", allowCreate);
+
+ const fileHandle = await root.getFileHandle(oldName, allowCreate);
+
+ async function checkMove(newName, newType) {
+ Assert.equal(fileHandle.name, newName, "Has filename changed?");
+ {
+ const fileObject = await fileHandle.getFile();
+ Assert.equal(fileObject.name, newName, "Is the fileobject renamed?");
+ Assert.equal(fileObject.type, newType, "Is the fileobject type updated?");
+ }
+ }
+
+ async function restoreTest() {
+ await fileHandle.move(root, oldName);
+ await checkMove(oldName, oldType);
+ }
+
+ // No name change
+ await checkMove(oldName, oldType);
+ await fileHandle.move(subdir);
+ await checkMove(oldName, oldType);
+ await restoreTest();
+
+ // With name change
+
+ async function testMoveWithParams(testName, testType) {
+ async function testFileMoveCall(...combo) {
+ await fileHandle.move(...combo);
+ await checkMove(testName, testType);
+ await restoreTest();
+ }
+
+ await testFileMoveCall(subdir, testName);
+ await testFileMoveCall(root, testName);
+ await testFileMoveCall(testName);
+ }
+
+ const testParams = {
+ "testFile.json": "application/json",
+ testFile: oldType,
+ "testFile.äüö": "",
+ };
+
+ for (const [aName, aType] of Object.entries(testParams)) {
+ await testMoveWithParams(aName, aType);
+ }
+};
+
+exported_symbols.testContentTypeChangesOnDirMove = async function () {
+ const allowCreate = { create: true };
+ const root = await navigator.storage.getDirectory();
+ const oldName = "testFile.txt";
+ const oldType = "text/plain";
+ const subDirOrig = await root.getDirectoryHandle("subDirOrig", allowCreate);
+ const subDirOther = await root.getDirectoryHandle("subDirOther", allowCreate);
+ const subSubDir = await subDirOrig.getDirectoryHandle(
+ "subSubDir",
+ allowCreate
+ );
+
+ const testName = "testFile.json";
+ const testType = "application/json";
+
+ async function checkMove(newName, newType) {
+ const fileHandle = await subSubDir.getFileHandle(newName, allowCreate);
+
+ Assert.equal(fileHandle.name, newName, "Has filename changed?");
+ {
+ const fileObject = await fileHandle.getFile();
+ Assert.equal(fileObject.name, newName, "Is the fileobject renamed?");
+ Assert.equal(fileObject.type, newType, "Is the fileobject type updated?");
+ }
+ }
+
+ async function restoreTest() {
+ await subSubDir.move(subDirOrig, "subSubDir");
+ await checkMove(oldName, oldType);
+ }
+
+ await checkMove(oldName, oldType);
+
+ // No name change
+ await subSubDir.move(subDirOther, "other");
+ await checkMove(oldName, oldType);
+ await restoreTest();
+
+ // With name change
+
+ async function testDirMoveCall(...combo) {
+ await subSubDir.move(...combo);
+ await checkMove(testName, testType);
+ await restoreTest();
+ }
+
+ await testDirMoveCall(subDirOther);
+ await testDirMoveCall(subDirOther, testName);
+ await testDirMoveCall(subDirOrig, testName);
+ await testDirMoveCall(subDirOrig);
+};
+
+for (const [key, value] of Object.entries(exported_symbols)) {
+ Object.defineProperty(value, "name", {
+ value: key,
+ writable: false,
+ });
+}
diff --git a/dom/fs/test/common/test_fileSystemDirectoryHandle.js b/dom/fs/test/common/test_fileSystemDirectoryHandle.js
new file mode 100644
index 0000000000..9142e47380
--- /dev/null
+++ b/dom/fs/test/common/test_fileSystemDirectoryHandle.js
@@ -0,0 +1,196 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+exported_symbols.smokeTest = async function smokeTest() {
+ const storage = navigator.storage;
+ const subdirectoryNames = new Set(["Documents", "Downloads", "Music"]);
+ const allowCreate = { create: true };
+
+ {
+ let root = await storage.getDirectory();
+ Assert.ok(root, "Can we access the root directory?");
+
+ let it = await root.values();
+ Assert.ok(!!it, "Does root have values iterator?");
+
+ let elem = await it.next();
+ Assert.ok(elem.done, "Is root directory empty?");
+
+ for (let dirName of subdirectoryNames) {
+ await root.getDirectoryHandle(dirName, allowCreate);
+ Assert.ok(true, "Was it possible to add subdirectory " + dirName + "?");
+ }
+ }
+
+ {
+ let root = await storage.getDirectory();
+ Assert.ok(root, "Can we refresh the root directory?");
+
+ let it = await root.values();
+ Assert.ok(!!it, "Does root have values iterator?");
+
+ let hasElements = false;
+ let hangGuard = 0;
+ for await (let [key, elem] of root.entries()) {
+ Assert.ok(elem, "Is element not non-empty?");
+ Assert.equal("directory", elem.kind, "Is found item a directory?");
+ Assert.ok(
+ elem.name.length >= 1 && elem.name.match("^[A-Za-z]{1,64}"),
+ "Are names of the elements strings?"
+ );
+ Assert.equal(key, elem.name);
+ Assert.ok(subdirectoryNames.has(elem.name), "Is name among known names?");
+ hasElements = true;
+ ++hangGuard;
+ if (hangGuard == 10) {
+ break; // Exit if there is a hang
+ }
+ }
+
+ Assert.ok(hasElements, "Is values container now non-empty?");
+ Assert.equal(3, hangGuard, "Do we only have three elements?");
+
+ {
+ it = await root.values();
+ Assert.ok(!!it, "Does root have values iterator?");
+ let elem = await it.next();
+
+ await elem.value.getDirectoryHandle("Trash", allowCreate);
+ let subit = elem.value.values();
+ Assert.ok(!!elem, "Is element not non-empty?");
+ let subdirResult = await subit.next();
+ let subdir = subdirResult.value;
+ Assert.ok(!!subdir, "Is element not non-empty?");
+ Assert.equal("directory", subdir.kind, "Is found item a directory?");
+ Assert.equal("Trash", subdir.name, "Is found item a directory?");
+ }
+
+ const wipeEverything = { recursive: true };
+ for (let dirName of subdirectoryNames) {
+ await root.removeEntry(dirName, wipeEverything);
+ Assert.ok(
+ true,
+ "Was it possible to remove subdirectory " + dirName + "?"
+ );
+ }
+ }
+
+ {
+ let root = await storage.getDirectory();
+ Assert.ok(root, "Can we refresh the root directory?");
+
+ let it = root.values();
+ Assert.ok(!!it, "Does root have values iterator?");
+
+ let elem = await it.next();
+ Assert.ok(elem.done, "Is root directory empty?");
+ }
+};
+
+exported_symbols.quotaTest = async function () {
+ const storage = navigator.storage;
+ const allowCreate = { create: true };
+
+ {
+ let root = await storage.getDirectory();
+ Assert.ok(root, "Can we access the root directory?");
+
+ const fileHandle = await root.getFileHandle("test.txt", allowCreate);
+ Assert.ok(!!fileHandle, "Can we get file handle?");
+
+ const usageAtStart = await Utils.getCachedOriginUsage();
+ Assert.ok(true, "usageAtStart: " + usageAtStart);
+
+ const writable = await fileHandle.createWritable();
+ Assert.ok(!!writable, "Can we create writable file stream?");
+
+ const usageAtWritableCreated = await Utils.getCachedOriginUsage();
+ Assert.equal(
+ usageAtWritableCreated - usageAtStart,
+ 0,
+ "Did usage increase when writable was created?"
+ );
+
+ const buffer = new ArrayBuffer(42);
+ Assert.ok(!!buffer, "Can we create array buffer?");
+
+ const result = await writable.write(buffer);
+ Assert.equal(result, undefined, "Can we write entire buffer?");
+
+ const usageAtWriteDone = await Utils.getCachedOriginUsage();
+ // Note: Usage should change only on close after 1824305
+ Assert.equal(
+ usageAtWriteDone - usageAtWritableCreated,
+ buffer.byteLength,
+ "Is write immediately reflected in usage?"
+ );
+
+ await writable.close();
+
+ const usageAtWritableClosed = await Utils.getCachedOriginUsage();
+
+ Assert.equal(
+ usageAtWritableClosed - usageAtWritableCreated,
+ buffer.byteLength,
+ "Did usage increase by the amount of bytes written?"
+ );
+
+ await root.removeEntry("test.txt");
+
+ const usageAtFileDeleted = await Utils.getCachedOriginUsage();
+
+ Assert.equal(
+ usageAtFileDeleted,
+ usageAtWritableCreated,
+ "Is usage back to the value before any writing when the file is removed?"
+ );
+ }
+};
+
+exported_symbols.pagedIterationTest = async function () {
+ const root = await navigator.storage.getDirectory();
+
+ for await (let contentItem of root.keys()) {
+ await root.removeEntry(contentItem, { recursive: true });
+ }
+
+ const allowCreate = { create: true };
+
+ // When half of the buffer is iterated, a request for the second half is sent.
+ // We test that the this boundary is crossed smoothly.
+ // After the buffer is filled, a request for more items is sent. The
+ // items are placed in the first half of the buffer.
+ // This boundary should also be crossed without problems.
+ // Currently, the buffer is half-filled at 1024.
+ const itemBatch = 3 + 2 * 1024;
+ for (let i = 0; i <= itemBatch; ++i) {
+ await root.getDirectoryHandle("" + i, allowCreate);
+ }
+
+ let result = 0;
+ let sum = 0;
+ const handles = new Set();
+ let isUnique = true;
+ for await (let [key, elem] of root.entries()) {
+ result += key.length;
+ sum += parseInt(elem.name);
+ if (handles.has(key)) {
+ // Asserting here is slow and verbose
+ isUnique = false;
+ break;
+ }
+ handles.add(key);
+ }
+ Assert.ok(isUnique);
+ Assert.equal(result, 7098);
+ Assert.equal(sum, (itemBatch * (itemBatch + 1)) / 2);
+};
+
+for (const [key, value] of Object.entries(exported_symbols)) {
+ Object.defineProperty(value, "name", {
+ value: key,
+ writable: false,
+ });
+}
diff --git a/dom/fs/test/common/test_syncAccessHandle.js b/dom/fs/test/common/test_syncAccessHandle.js
new file mode 100644
index 0000000000..ac7e0ef769
--- /dev/null
+++ b/dom/fs/test/common/test_syncAccessHandle.js
@@ -0,0 +1,237 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const allowCreate = { create: true };
+
+exported_symbols.test0 = async function () {
+ let root = await navigator.storage.getDirectory();
+ Assert.ok(root, "Can we access the root directory?");
+
+ try {
+ await root.getFileHandle("test.txt");
+ Assert.ok(false, "Opened file that shouldn't exist");
+ } catch (e) {
+ dump("caught exception when we tried to open a non-existant file\n");
+ }
+};
+
+exported_symbols.test1 = async function () {
+ let root = await navigator.storage.getDirectory();
+ Assert.ok(root, "Can we access the root directory?");
+
+ const testFile = await root.getFileHandle("test.txt", allowCreate);
+ Assert.ok(!!testFile, "Can't create file");
+ let handle = await testFile.createSyncAccessHandle();
+ Assert.ok(!!handle, "Can't create SyncAccessHandle");
+ await handle.close();
+ handle = await testFile.createSyncAccessHandle();
+ Assert.ok(!!handle, "Can't create second SyncAccessHandle to same file");
+ await handle.close();
+};
+
+exported_symbols.test2 = async function () {
+ let root = await navigator.storage.getDirectory();
+ Assert.ok(root, "Can we access the root directory?");
+
+ const testFile = await root.getFileHandle("test.txt", allowCreate);
+ Assert.ok(!!testFile, "Can't open file");
+ let handle = await testFile.createSyncAccessHandle();
+ Assert.ok(!!handle, "Can't create SyncAccessHandle");
+ await handle.close();
+
+ await root.removeEntry("test.txt");
+ try {
+ handle = await testFile.createSyncAccessHandle();
+ Assert.ok(!!handle, "Didn't remove file!");
+ if (handle) {
+ await handle.close();
+ }
+ } catch (e) {
+ dump("Caught exception trying to create accesshandle to deleted file\n");
+ }
+};
+
+exported_symbols.test3 = async function () {
+ let root = await navigator.storage.getDirectory();
+ Assert.ok(!!root, "Can we access the root directory?");
+
+ let dir = await root.getDirectoryHandle("dir", allowCreate);
+ Assert.ok(!!dir, "Can we create a directory?");
+
+ // XXX not implemented yet
+ //const path = await root.resolve(dir);
+ //Assert.ok(path == ["dir"], "Wrong path: " + path);
+
+ let dir2 = await dir.getDirectoryHandle("dir", allowCreate);
+ Assert.ok(!!dir, "Can we create dir/dir?");
+
+ // XXX not implemented yet
+ //const path = await root.resolve(dir2);
+ //Assert.ok(path == ["dir", "dir"], "Wrong path: " + path);
+
+ let dir3 = await dir.getDirectoryHandle("bar", allowCreate);
+ Assert.ok(!!dir3, "Can we create dir/bar?");
+
+ // This should fail
+ try {
+ await root.getDirectoryHandle("bar");
+ Assert.ok(!dir, "we shouldn't be able to get bar unless we create it");
+ } catch (e) {
+ dump("caught exception when we tried to get a non-existant dir\n");
+ }
+
+ const testFile = await dir2.getFileHandle("test.txt", allowCreate);
+ Assert.ok(!!testFile, "Can't create file in dir2");
+ let handle = await testFile.createSyncAccessHandle();
+ Assert.ok(!!handle, "Can't create SyncAccessHandle in dir2");
+ await handle.close();
+};
+
+exported_symbols.test4 = async function () {
+ let root = await navigator.storage.getDirectory();
+ Assert.ok(!!root, "Can we access the root directory?");
+
+ const testFile = await root.getFileHandle("test.txt", allowCreate);
+ Assert.ok(!!testFile, "Can't access existing file");
+ let handle = await testFile.createSyncAccessHandle();
+ Assert.ok(!!handle, "Can't create SyncAccessHandle to existing file");
+
+ // Write a sentence to the end of the file.
+ const encoder = new TextEncoder();
+ const writeBuffer = encoder.encode("Thank you for reading this.");
+ const writeSize = handle.write(writeBuffer);
+ Assert.ok(!!writeSize);
+
+ // Read it back
+ // Get size of the file.
+ let fileSize = await handle.getSize();
+ Assert.ok(fileSize == writeBuffer.byteLength);
+ // Read file content to a buffer.
+ const readBuffer = new ArrayBuffer(fileSize);
+ const readSize = handle.read(readBuffer, { at: 0 });
+ Assert.ok(!!readSize);
+ //Assert.ok(readBuffer == writeBuffer);
+
+ await handle.truncate(5);
+ fileSize = await handle.getSize();
+ Assert.ok(fileSize == 5);
+
+ await handle.flush();
+ await handle.close();
+};
+
+exported_symbols.test5 = async function () {
+ let root = await navigator.storage.getDirectory();
+ Assert.ok(!!root, "Can we access the root directory?");
+
+ const testFile = await root.getFileHandle("test.txt", allowCreate);
+ Assert.ok(!!testFile, "Can't create file");
+ let handle = await testFile.createSyncAccessHandle();
+ Assert.ok(!!handle, "Can't create SyncAccessHandle");
+
+ try {
+ const testFile2 = await root.getFileHandle("test2.txt", allowCreate);
+ let handle2 = await testFile2.createSyncAccessHandle();
+ Assert.ok(!!handle2, "can't create SyncAccessHandle to second file!");
+ if (handle2) {
+ await handle2.close();
+ }
+ } catch (e) {
+ Assert.ok(false, "Failed to create second file");
+ }
+
+ await handle.close();
+};
+
+exported_symbols.test6 = async function () {
+ let root = await navigator.storage.getDirectory();
+ Assert.ok(root, "Can we access the root directory?");
+
+ const testFile = await root.getFileHandle("test.txt", allowCreate);
+ Assert.ok(!!testFile, "Can't get file");
+ let handle = await testFile.createSyncAccessHandle();
+ Assert.ok(!!handle, "Can't create SyncAccessHandle");
+
+ try {
+ let handle2 = await testFile.createSyncAccessHandle();
+ Assert.ok(!handle2, "Shouldn't create SyncAccessHandle!");
+ if (handle2) {
+ await handle2.close();
+ }
+ } catch (e) {
+ // should always happen
+ dump("caught exception when we tried to get 2 SyncAccessHandles\n");
+ }
+
+ // test that locks work across multiple connections for an origin
+ try {
+ let root2 = await navigator.storage.getDirectory();
+ Assert.ok(root2, "Can we access the root2 directory?");
+
+ const testFile2 = await root2.getFileHandle("test.txt");
+ Assert.ok(!!testFile2, "Can't get file");
+ let handle2 = await testFile2.createSyncAccessHandle();
+ Assert.ok(!handle2, "Shouldn't create SyncAccessHandle (2)!");
+ if (handle2) {
+ await handle2.close();
+ }
+ } catch (e) {
+ // should always happen
+ dump("caught exception when we tried to get 2 SyncAccessHandles\n");
+ }
+
+ if (handle) {
+ await handle.close();
+ }
+};
+
+exported_symbols.quotaTest = async function () {
+ const shrinkedStorageSizeKB = 5 * 1024;
+ const defaultDatabaseSize = 491520;
+
+ // Shrink storage size to 5MB.
+ await Utils.shrinkStorageSize(shrinkedStorageSizeKB);
+
+ let root = await navigator.storage.getDirectory();
+ Assert.ok(root, "Can we access the root directory?");
+
+ // Fill entire storage.
+ const fileHandle = await root.getFileHandle("test.txt", allowCreate);
+ Assert.ok(!!fileHandle, "Can we get file handle?");
+
+ const accessHandle = await fileHandle.createSyncAccessHandle();
+ Assert.ok(!!accessHandle, "Can we create sync access handle?");
+
+ const buffer = new ArrayBuffer(
+ shrinkedStorageSizeKB * 1024 - defaultDatabaseSize
+ );
+ Assert.ok(!!buffer, "Can we create array buffer?");
+
+ const written = accessHandle.write(buffer);
+ Assert.equal(written, buffer.byteLength, "Can we write entire buffer?");
+
+ // Try to write one more byte.
+ const fileHandle2 = await root.getFileHandle("test2.txt", allowCreate);
+ Assert.ok(!!fileHandle2, "Can we get file handle?");
+
+ const accessHandle2 = await fileHandle2.createSyncAccessHandle();
+ Assert.ok(!!accessHandle2, "Can we create sync access handle?");
+
+ const buffer2 = new ArrayBuffer(1);
+ Assert.ok(!!buffer2, "Can we create array buffer?");
+
+ const written2 = accessHandle2.write(buffer2);
+ Assert.equal(written2, 0, "Can we write beyond the limit?");
+
+ await accessHandle.close();
+ await accessHandle2.close();
+
+ await Utils.restoreStorageSize();
+};
+
+for (const [key, value] of Object.entries(exported_symbols)) {
+ Object.defineProperty(value, "name", {
+ value: key,
+ writable: false,
+ });
+}
diff --git a/dom/fs/test/common/test_writableFileStream.js b/dom/fs/test/common/test_writableFileStream.js
new file mode 100644
index 0000000000..016c53bf3b
--- /dev/null
+++ b/dom/fs/test/common/test_writableFileStream.js
@@ -0,0 +1,153 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const allowCreate = { create: true };
+
+exported_symbols.test0 = async function () {
+ let root = await navigator.storage.getDirectory();
+ Assert.ok(!!root, "Can we access the root directory?");
+
+ const testFile = await root.getFileHandle("test.txt", allowCreate);
+ Assert.ok(!!testFile, "Can't access existing file");
+ let writable = await testFile.createWritable();
+ Assert.ok(!!writable, "Can't create WritableFileStream to existing file");
+
+ // Write a sentence to the end of the file.
+ const encoder = new TextEncoder();
+ const writeBuffer = encoder.encode("Thank you for reading this.");
+ try {
+ dump("Trying to write...\n");
+ await writable.write(writeBuffer);
+ dump("closing...\n");
+ await writable.close();
+ } catch (e) {
+ Assert.ok(false, "Couldn't write to WritableFileStream: " + e);
+ }
+
+ // Read it back
+ // Get size of the file.
+ let file = await testFile.getFile();
+ Assert.ok(
+ !!file,
+ "Can't create File to file written with WritableFileStream"
+ );
+ let fileSize = file.size;
+ Assert.ok(fileSize == writeBuffer.byteLength);
+};
+
+exported_symbols.quotaTest = async function () {
+ const shrinkedStorageSizeKB = 5 * 1024;
+ const defaultDatabaseSize = 491547;
+
+ // Shrink storage size to 5MB.
+ await Utils.shrinkStorageSize(shrinkedStorageSizeKB);
+
+ let root = await navigator.storage.getDirectory();
+ Assert.ok(root, "Can we access the root directory?");
+
+ // Fill entire storage.
+ const fileHandle = await root.getFileHandle("test.txt", allowCreate);
+ Assert.ok(!!fileHandle, "Can we get file handle?");
+
+ const writable = await fileHandle.createWritable();
+ Assert.ok(!!writable, "Can we create writable file stream?");
+
+ const buffer = new ArrayBuffer(
+ shrinkedStorageSizeKB * 1024 - defaultDatabaseSize
+ );
+ Assert.ok(!!buffer, "Can we create array buffer?");
+
+ const result = await writable.write(buffer);
+ Assert.equal(result, undefined, "Can we write entire buffer?");
+
+ // Try to write one more byte.
+ const fileHandle2 = await root.getFileHandle("test2.txt", allowCreate);
+ Assert.ok(!!fileHandle2, "Can we get file handle?");
+
+ const writable2 = await fileHandle2.createWritable();
+ Assert.ok(!!writable2, "Can we create writable file stream?");
+
+ const buffer2 = new ArrayBuffer(1);
+ Assert.ok(!!buffer2, "Can we create array buffer?");
+
+ try {
+ await writable2.write(buffer2);
+ Assert.ok(false, "Should have thrown");
+ } catch (ex) {
+ Assert.ok(true, "Did throw");
+ Assert.ok(DOMException.isInstance(ex), "Threw DOMException");
+ Assert.equal(ex.name, "QuotaExceededError", "Threw right DOMException");
+ }
+
+ await writable.close();
+ // writable2 is already closed because of the failed write above
+
+ await Utils.restoreStorageSize();
+};
+
+exported_symbols.bug1823445 = async function () {
+ const root = await navigator.storage.getDirectory();
+ const testFileName = "test1823445.txt";
+ let handle = await root.getFileHandle(testFileName, allowCreate);
+ let writable = await handle.createWritable();
+ await writable.write("abcdefghijklmnop");
+ await writable.close();
+
+ handle = await root.getFileHandle(testFileName);
+ writable = await handle.createWritable({ keepExistingData: false });
+ await writable.write("12345");
+ await writable.close();
+
+ handle = await root.getFileHandle(testFileName);
+ const file = await handle.getFile();
+ const text = await file.text();
+ Assert.equal(text, "12345");
+};
+
+exported_symbols.bug1824993 = async function () {
+ const root = await navigator.storage.getDirectory();
+ const testFileName = "test1824993.txt";
+ const handle = await root.getFileHandle(testFileName, allowCreate);
+ {
+ const writable = await handle.createWritable();
+ await writable.write("test");
+
+ {
+ const file = await handle.getFile();
+ const contents = await file.text();
+ Assert.equal(contents, "");
+ }
+
+ await writable.abort();
+ }
+
+ const file = await handle.getFile();
+ const contents = await file.text();
+ Assert.equal(contents, "");
+};
+
+exported_symbols.bug1825018 = async function () {
+ const root = await navigator.storage.getDirectory();
+ const testFileName = "test1825018.txt";
+ const handle = await root.getFileHandle(testFileName, allowCreate);
+ const writable = await handle.createWritable();
+ try {
+ await writable.write({ type: "truncate" });
+ } catch (e) {
+ // Called write without size throws an error as expected
+ }
+
+ try {
+ await writable.abort();
+ await root.removeEntry(testFileName);
+ } catch (e) {
+ Assert.ok(false, e.message);
+ }
+};
+
+for (const [key, value] of Object.entries(exported_symbols)) {
+ Object.defineProperty(value, "name", {
+ value: key,
+ writable: false,
+ });
+}
diff --git a/dom/fs/test/common/xpcshell.toml b/dom/fs/test/common/xpcshell.toml
new file mode 100644
index 0000000000..9f086b65fe
--- /dev/null
+++ b/dom/fs/test/common/xpcshell.toml
@@ -0,0 +1,10 @@
+[DEFAULT]
+support-files = [
+ "nsresult.js",
+ "test_basics.js",
+ "test_fileSystemDirectoryHandle.js",
+ "test_writableFileStream.js",
+]
+
+["dummy.js"]
+skip-if = ["true"]