summaryrefslogtreecommitdiffstats
path: root/dom/fs/api
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/fs/api
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/fs/api')
-rw-r--r--dom/fs/api/FileSystemDirectoryHandle.cpp176
-rw-r--r--dom/fs/api/FileSystemDirectoryHandle.h86
-rw-r--r--dom/fs/api/FileSystemDirectoryIterator.cpp53
-rw-r--r--dom/fs/api/FileSystemDirectoryIterator.h70
-rw-r--r--dom/fs/api/FileSystemFileHandle.cpp122
-rw-r--r--dom/fs/api/FileSystemFileHandle.h61
-rw-r--r--dom/fs/api/FileSystemHandle.cpp319
-rw-r--r--dom/fs/api/FileSystemHandle.h107
-rw-r--r--dom/fs/api/FileSystemManager.cpp153
-rw-r--r--dom/fs/api/FileSystemManager.h102
-rw-r--r--dom/fs/api/FileSystemSyncAccessHandle.cpp650
-rw-r--r--dom/fs/api/FileSystemSyncAccessHandle.h133
-rw-r--r--dom/fs/api/FileSystemWritableFileStream.cpp951
-rw-r--r--dom/fs/api/FileSystemWritableFileStream.h146
-rw-r--r--dom/fs/api/moz.build38
15 files changed, 3167 insertions, 0 deletions
diff --git a/dom/fs/api/FileSystemDirectoryHandle.cpp b/dom/fs/api/FileSystemDirectoryHandle.cpp
new file mode 100644
index 0000000000..3529f29e78
--- /dev/null
+++ b/dom/fs/api/FileSystemDirectoryHandle.cpp
@@ -0,0 +1,176 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "FileSystemDirectoryHandle.h"
+
+#include "FileSystemDirectoryIteratorFactory.h"
+#include "fs/FileSystemRequestHandler.h"
+#include "js/StructuredClone.h"
+#include "js/TypeDecls.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/FileSystemDirectoryHandleBinding.h"
+#include "mozilla/dom/FileSystemHandleBinding.h"
+#include "mozilla/dom/FileSystemLog.h"
+#include "mozilla/dom/FileSystemManager.h"
+#include "mozilla/dom/PFileSystemManager.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/StorageManager.h"
+#include "nsJSUtils.h"
+
+namespace mozilla::dom {
+
+FileSystemDirectoryHandle::FileSystemDirectoryHandle(
+ nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+ const fs::FileSystemEntryMetadata& aMetadata,
+ fs::FileSystemRequestHandler* aRequestHandler)
+ : FileSystemHandle(aGlobal, aManager, aMetadata, aRequestHandler) {}
+
+FileSystemDirectoryHandle::FileSystemDirectoryHandle(
+ nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+ const fs::FileSystemEntryMetadata& aMetadata)
+ : FileSystemDirectoryHandle(aGlobal, aManager, aMetadata,
+ new fs::FileSystemRequestHandler()) {}
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(FileSystemDirectoryHandle,
+ FileSystemHandle)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(FileSystemDirectoryHandle, FileSystemHandle)
+
+// WebIDL Boilerplate
+
+JSObject* FileSystemDirectoryHandle::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return FileSystemDirectoryHandle_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// WebIDL Interface
+
+FileSystemHandleKind FileSystemDirectoryHandle::Kind() const {
+ return FileSystemHandleKind::Directory;
+}
+
+void FileSystemDirectoryHandle::InitAsyncIteratorData(
+ IteratorData& aData, iterator_t::IteratorType aType, ErrorResult& aError) {
+ aData.mImpl =
+ fs::FileSystemDirectoryIteratorFactory::Create(mMetadata, aType);
+}
+
+already_AddRefed<Promise> FileSystemDirectoryHandle::GetNextIterationResult(
+ FileSystemDirectoryHandle::iterator_t* aIterator, ErrorResult& aError) {
+ LOG_VERBOSE(("GetNextIterationResult"));
+ return aIterator->Data().mImpl->Next(mGlobal, mManager, aError);
+}
+
+already_AddRefed<Promise> FileSystemDirectoryHandle::GetFileHandle(
+ const nsAString& aName, const FileSystemGetFileOptions& aOptions,
+ ErrorResult& aError) {
+ MOZ_ASSERT(!mMetadata.entryId().IsEmpty());
+
+ RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ fs::Name name(aName);
+ fs::FileSystemChildMetadata metadata(mMetadata.entryId(), name);
+ mRequestHandler->GetFileHandle(mManager, metadata, aOptions.mCreate, promise,
+ aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise> FileSystemDirectoryHandle::GetDirectoryHandle(
+ const nsAString& aName, const FileSystemGetDirectoryOptions& aOptions,
+ ErrorResult& aError) {
+ MOZ_ASSERT(!mMetadata.entryId().IsEmpty());
+
+ RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ fs::Name name(aName);
+ fs::FileSystemChildMetadata metadata(mMetadata.entryId(), name);
+ mRequestHandler->GetDirectoryHandle(mManager, metadata, aOptions.mCreate,
+ promise, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise> FileSystemDirectoryHandle::RemoveEntry(
+ const nsAString& aName, const FileSystemRemoveOptions& aOptions,
+ ErrorResult& aError) {
+ MOZ_ASSERT(!mMetadata.entryId().IsEmpty());
+
+ RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ fs::Name name(aName);
+ fs::FileSystemChildMetadata metadata(mMetadata.entryId(), name);
+
+ mRequestHandler->RemoveEntry(mManager, metadata, aOptions.mRecursive, promise,
+ aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise> FileSystemDirectoryHandle::Resolve(
+ FileSystemHandle& aPossibleDescendant, ErrorResult& aError) {
+ RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ LOG_VERBOSE(("Resolve"));
+
+ fs::FileSystemEntryPair pair(mMetadata.entryId(),
+ aPossibleDescendant.GetId());
+ mRequestHandler->Resolve(mManager, pair, promise, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ return promise.forget();
+}
+
+// [Serializable] implementation
+
+// static
+already_AddRefed<FileSystemDirectoryHandle>
+FileSystemDirectoryHandle::ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader) {
+ uint32_t kind = static_cast<uint32_t>(FileSystemHandleKind::EndGuard_);
+
+ if (!JS_ReadBytes(aReader, reinterpret_cast<void*>(&kind),
+ sizeof(uint32_t))) {
+ return nullptr;
+ }
+
+ if (kind != static_cast<uint32_t>(FileSystemHandleKind::Directory)) {
+ return nullptr;
+ }
+
+ RefPtr<FileSystemDirectoryHandle> result =
+ FileSystemHandle::ConstructDirectoryHandle(aCx, aGlobal, aReader);
+ if (!result) {
+ return nullptr;
+ }
+
+ return result.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fs/api/FileSystemDirectoryHandle.h b/dom/fs/api/FileSystemDirectoryHandle.h
new file mode 100644
index 0000000000..36ac7455c5
--- /dev/null
+++ b/dom/fs/api/FileSystemDirectoryHandle.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef DOM_FS_FILESYSTEMDIRECTORYHANDLE_H_
+#define DOM_FS_FILESYSTEMDIRECTORYHANDLE_H_
+
+#include "mozilla/dom/FileSystemDirectoryIterator.h"
+#include "mozilla/dom/FileSystemHandle.h"
+#include "mozilla/dom/IterableIterator.h"
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+struct FileSystemGetFileOptions;
+struct FileSystemGetDirectoryOptions;
+struct FileSystemRemoveOptions;
+
+class FileSystemDirectoryHandle final : public FileSystemHandle {
+ public:
+ using iterator_t = AsyncIterableIterator<FileSystemDirectoryHandle>;
+
+ FileSystemDirectoryHandle(nsIGlobalObject* aGlobal,
+ RefPtr<FileSystemManager>& aManager,
+ const fs::FileSystemEntryMetadata& aMetadata,
+ fs::FileSystemRequestHandler* aRequestHandler);
+
+ FileSystemDirectoryHandle(nsIGlobalObject* aGlobal,
+ RefPtr<FileSystemManager>& aManager,
+ const fs::FileSystemEntryMetadata& aMetadata);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FileSystemDirectoryHandle,
+ FileSystemHandle)
+
+ // WebIDL Boilerplate
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL Interface
+ FileSystemHandleKind Kind() const override;
+
+ struct IteratorData {
+ UniquePtr<FileSystemDirectoryIterator::Impl> mImpl;
+ };
+
+ void InitAsyncIteratorData(IteratorData& aData,
+ iterator_t::IteratorType aType,
+ ErrorResult& aError);
+
+ [[nodiscard]] already_AddRefed<Promise> GetNextIterationResult(
+ iterator_t* aIterator, ErrorResult& aError);
+
+ already_AddRefed<Promise> GetFileHandle(
+ const nsAString& aName, const FileSystemGetFileOptions& aOptions,
+ ErrorResult& aError);
+
+ already_AddRefed<Promise> GetDirectoryHandle(
+ const nsAString& aName, const FileSystemGetDirectoryOptions& aOptions,
+ ErrorResult& aError);
+
+ already_AddRefed<Promise> RemoveEntry(const nsAString& aName,
+ const FileSystemRemoveOptions& aOptions,
+ ErrorResult& aError);
+
+ already_AddRefed<Promise> Resolve(FileSystemHandle& aPossibleDescendant,
+ ErrorResult& aError);
+
+ // [Serializable]
+ static already_AddRefed<FileSystemDirectoryHandle> ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader);
+
+ private:
+ ~FileSystemDirectoryHandle() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // DOM_FS_FILESYSTEMDIRECTORYHANDLE_H_
diff --git a/dom/fs/api/FileSystemDirectoryIterator.cpp b/dom/fs/api/FileSystemDirectoryIterator.cpp
new file mode 100644
index 0000000000..45e7511e4a
--- /dev/null
+++ b/dom/fs/api/FileSystemDirectoryIterator.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "FileSystemDirectoryIterator.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/FileSystemDirectoryIteratorBinding.h"
+#include "mozilla/dom/FileSystemManager.h"
+#include "mozilla/dom/Promise.h"
+
+namespace mozilla::dom {
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileSystemDirectoryIterator)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FileSystemDirectoryIterator);
+NS_IMPL_CYCLE_COLLECTING_RELEASE(FileSystemDirectoryIterator);
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FileSystemDirectoryIterator, mGlobal);
+
+FileSystemDirectoryIterator::FileSystemDirectoryIterator(
+ nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+ UniquePtr<Impl> aImpl)
+ : mGlobal(aGlobal), mManager(aManager), mImpl(std::move(aImpl)) {}
+
+// WebIDL Boilerplate
+
+nsIGlobalObject* FileSystemDirectoryIterator::GetParentObject() const {
+ return mGlobal;
+}
+
+JSObject* FileSystemDirectoryIterator::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return FileSystemDirectoryIterator_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// WebIDL Interface
+
+already_AddRefed<Promise> FileSystemDirectoryIterator::Next(
+ ErrorResult& aError) {
+ RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(mImpl);
+ return mImpl->Next(mGlobal, mManager, aError);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fs/api/FileSystemDirectoryIterator.h b/dom/fs/api/FileSystemDirectoryIterator.h
new file mode 100644
index 0000000000..d761ae6d2b
--- /dev/null
+++ b/dom/fs/api/FileSystemDirectoryIterator.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef DOM_FS_FILESYSTEMDIRECTORYITERATOR_H_
+#define DOM_FS_FILESYSTEMDIRECTORYITERATOR_H_
+
+#include "mozilla/dom/IterableIterator.h"
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class FileSystemManager;
+class IterableIteratorBase;
+class Promise;
+
+// XXX This class isn't used to support iteration anymore. `Impl` should be
+// extracted elsewhere and `FileSystemDirectoryIterator` should be removed
+// completely
+class FileSystemDirectoryIterator : public nsISupports, public nsWrapperCache {
+ public:
+ class Impl {
+ public:
+ virtual already_AddRefed<Promise> Next(nsIGlobalObject* aGlobal,
+ RefPtr<FileSystemManager>& aManager,
+ ErrorResult& aError) = 0;
+ virtual ~Impl() = default;
+ };
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FileSystemDirectoryIterator)
+
+ explicit FileSystemDirectoryIterator(nsIGlobalObject* aGlobal,
+ RefPtr<FileSystemManager>& aManager,
+ UniquePtr<Impl> aImpl);
+
+ // WebIDL Boilerplate
+ nsIGlobalObject* GetParentObject() const;
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL Interface
+ already_AddRefed<Promise> Next(ErrorResult& aError);
+
+ protected:
+ virtual ~FileSystemDirectoryIterator() = default;
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+
+ RefPtr<FileSystemManager> mManager;
+
+ private:
+ UniquePtr<Impl> mImpl;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // DOM_FS_FILESYSTEMDIRECTORYITERATOR_H_
diff --git a/dom/fs/api/FileSystemFileHandle.cpp b/dom/fs/api/FileSystemFileHandle.cpp
new file mode 100644
index 0000000000..4d8306857f
--- /dev/null
+++ b/dom/fs/api/FileSystemFileHandle.cpp
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "FileSystemFileHandle.h"
+
+#include "fs/FileSystemRequestHandler.h"
+#include "js/StructuredClone.h"
+#include "js/TypeDecls.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/FileSystemFileHandleBinding.h"
+#include "mozilla/dom/FileSystemHandleBinding.h"
+#include "mozilla/dom/FileSystemManager.h"
+#include "mozilla/dom/PFileSystemManager.h"
+#include "mozilla/dom/Promise.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(FileSystemFileHandle,
+ FileSystemHandle)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(FileSystemFileHandle, FileSystemHandle)
+
+FileSystemFileHandle::FileSystemFileHandle(
+ nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+ const fs::FileSystemEntryMetadata& aMetadata,
+ fs::FileSystemRequestHandler* aRequestHandler)
+ : FileSystemHandle(aGlobal, aManager, aMetadata, aRequestHandler) {}
+
+FileSystemFileHandle::FileSystemFileHandle(
+ nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+ const fs::FileSystemEntryMetadata& aMetadata)
+ : FileSystemFileHandle(aGlobal, aManager, aMetadata,
+ new fs::FileSystemRequestHandler()) {}
+
+// WebIDL Boilerplate
+
+JSObject* FileSystemFileHandle::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return FileSystemFileHandle_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// WebIDL Interface
+
+FileSystemHandleKind FileSystemFileHandle::Kind() const {
+ return FileSystemHandleKind::File;
+}
+
+already_AddRefed<Promise> FileSystemFileHandle::GetFile(ErrorResult& aError) {
+ RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ mRequestHandler->GetFile(mManager, mMetadata, promise, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise> FileSystemFileHandle::CreateWritable(
+ const FileSystemCreateWritableOptions& aOptions, ErrorResult& aError) {
+ RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ mRequestHandler->GetWritable(mManager, mMetadata, aOptions.mKeepExistingData,
+ promise, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise> FileSystemFileHandle::CreateSyncAccessHandle(
+ ErrorResult& aError) {
+ RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ mRequestHandler->GetAccessHandle(mManager, mMetadata, promise, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ return promise.forget();
+}
+
+// [Serializable] implementation
+
+// static
+already_AddRefed<FileSystemFileHandle>
+FileSystemFileHandle::ReadStructuredClone(JSContext* aCx,
+ nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader) {
+ uint32_t kind = static_cast<uint32_t>(FileSystemHandleKind::EndGuard_);
+
+ if (!JS_ReadBytes(aReader, reinterpret_cast<void*>(&kind),
+ sizeof(uint32_t))) {
+ return nullptr;
+ }
+
+ if (kind != static_cast<uint32_t>(FileSystemHandleKind::File)) {
+ return nullptr;
+ }
+
+ RefPtr<FileSystemFileHandle> result =
+ FileSystemHandle::ConstructFileHandle(aCx, aGlobal, aReader);
+ if (!result) {
+ return nullptr;
+ }
+
+ return result.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fs/api/FileSystemFileHandle.h b/dom/fs/api/FileSystemFileHandle.h
new file mode 100644
index 0000000000..c0606c85a6
--- /dev/null
+++ b/dom/fs/api/FileSystemFileHandle.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef DOM_FS_FILESYSTEMFILEHANDLE_H_
+#define DOM_FS_FILESYSTEMFILEHANDLE_H_
+
+#include "mozilla/dom/FileSystemHandle.h"
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+struct FileSystemCreateWritableOptions;
+
+class FileSystemFileHandle final : public FileSystemHandle {
+ public:
+ FileSystemFileHandle(nsIGlobalObject* aGlobal,
+ RefPtr<FileSystemManager>& aManager,
+ const fs::FileSystemEntryMetadata& aMetadata,
+ fs::FileSystemRequestHandler* aRequestHandler);
+
+ FileSystemFileHandle(nsIGlobalObject* aGlobal,
+ RefPtr<FileSystemManager>& aManager,
+ const fs::FileSystemEntryMetadata& aMetadata);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FileSystemFileHandle,
+ FileSystemHandle)
+
+ // WebIDL Boilerplate
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL interface
+ FileSystemHandleKind Kind() const override;
+
+ already_AddRefed<Promise> GetFile(ErrorResult& aError);
+
+ already_AddRefed<Promise> CreateWritable(
+ const FileSystemCreateWritableOptions& aOptions, ErrorResult& aError);
+
+ already_AddRefed<Promise> CreateSyncAccessHandle(ErrorResult& aError);
+
+ // [Serializable]
+ static already_AddRefed<FileSystemFileHandle> ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader);
+
+ private:
+ ~FileSystemFileHandle() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // DOM_FS_FILESYSTEMFILEHANDLE_H_
diff --git a/dom/fs/api/FileSystemHandle.cpp b/dom/fs/api/FileSystemHandle.cpp
new file mode 100644
index 0000000000..beca83d0bf
--- /dev/null
+++ b/dom/fs/api/FileSystemHandle.cpp
@@ -0,0 +1,319 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "FileSystemHandle.h"
+
+#include "FileSystemDirectoryHandle.h"
+#include "FileSystemFileHandle.h"
+#include "fs/FileSystemRequestHandler.h"
+#include "js/StructuredClone.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/FileSystemHandleBinding.h"
+#include "mozilla/dom/FileSystemLog.h"
+#include "mozilla/dom/FileSystemManager.h"
+#include "mozilla/dom/Promise-inl.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/StorageManager.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "nsJSPrincipals.h"
+#include "nsString.h"
+#include "prio.h"
+#include "private/pprio.h"
+#include "xpcpublic.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+bool ConstructHandleMetadata(JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader,
+ const bool aDirectory,
+ fs::FileSystemEntryMetadata& aMetadata) {
+ using namespace mozilla::dom::fs;
+
+ EntryId entryId;
+ if (!entryId.SetLength(32u, fallible)) {
+ return false;
+ }
+
+ if (!JS_ReadBytes(aReader, static_cast<void*>(entryId.BeginWriting()), 32u)) {
+ return false;
+ }
+
+ Name name;
+ if (!StructuredCloneHolder::ReadString(aReader, name)) {
+ return false;
+ }
+
+ mozilla::ipc::PrincipalInfo storageKey;
+ if (!nsJSPrincipals::ReadPrincipalInfo(aReader, storageKey)) {
+ return false;
+ }
+
+ QM_TRY_UNWRAP(auto hasEqualStorageKey,
+ aGlobal->HasEqualStorageKey(storageKey), false);
+
+ if (!hasEqualStorageKey) {
+ LOG(("Blocking deserialization of %s due to cross-origin",
+ NS_ConvertUTF16toUTF8(name).get()));
+ return false;
+ }
+
+ LOG_VERBOSE(("Deserializing %s", NS_ConvertUTF16toUTF8(name).get()));
+
+ aMetadata = fs::FileSystemEntryMetadata(entryId, name, aDirectory);
+ return true;
+}
+
+} // namespace
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileSystemHandle)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FileSystemHandle)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(FileSystemHandle)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FileSystemHandle)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FileSystemHandle)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
+ // Don't unlink mManager!
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FileSystemHandle)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mManager)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+FileSystemHandle::FileSystemHandle(
+ nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+ const fs::FileSystemEntryMetadata& aMetadata,
+ fs::FileSystemRequestHandler* aRequestHandler)
+ : mGlobal(aGlobal),
+ mManager(aManager),
+ mMetadata(aMetadata),
+ mRequestHandler(aRequestHandler) {
+ MOZ_ASSERT(!mMetadata.entryId().IsEmpty());
+}
+
+// WebIDL Boilerplate
+
+nsIGlobalObject* FileSystemHandle::GetParentObject() const { return mGlobal; }
+
+JSObject* FileSystemHandle::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return FileSystemHandle_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// WebIDL Interface
+
+void FileSystemHandle::GetName(nsAString& aResult) {
+ aResult = mMetadata.entryName();
+}
+
+already_AddRefed<Promise> FileSystemHandle::IsSameEntry(
+ FileSystemHandle& aOther, ErrorResult& aError) const {
+ RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ // Handles the case of "dir = createdir foo; removeEntry(foo); file =
+ // createfile foo; issameentry(dir, file)"
+ const bool result = mMetadata.entryId().Equals(aOther.mMetadata.entryId()) &&
+ Kind() == aOther.Kind();
+ promise->MaybeResolve(result);
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise> FileSystemHandle::Move(const nsAString& aName,
+ ErrorResult& aError) {
+ LOG(("Move %s to %s", NS_ConvertUTF16toUTF8(mMetadata.entryName()).get(),
+ NS_ConvertUTF16toUTF8(aName).get()));
+
+ fs::EntryId parent; // empty means same directory
+ return Move(parent, aName, aError);
+}
+
+already_AddRefed<Promise> FileSystemHandle::Move(
+ FileSystemDirectoryHandle& aParent, ErrorResult& aError) {
+ LOG(("Move %s to %s/%s", NS_ConvertUTF16toUTF8(mMetadata.entryName()).get(),
+ NS_ConvertUTF16toUTF8(aParent.mMetadata.entryName()).get(),
+ NS_ConvertUTF16toUTF8(mMetadata.entryName()).get()));
+ return Move(aParent, mMetadata.entryName(), aError);
+}
+
+already_AddRefed<Promise> FileSystemHandle::Move(
+ FileSystemDirectoryHandle& aParent, const nsAString& aName,
+ ErrorResult& aError) {
+ LOG(("Move %s to %s/%s", NS_ConvertUTF16toUTF8(mMetadata.entryName()).get(),
+ NS_ConvertUTF16toUTF8(aParent.mMetadata.entryName()).get(),
+ NS_ConvertUTF16toUTF8(aName).get()));
+ return Move(aParent.mMetadata.entryId(), aName, aError);
+}
+
+already_AddRefed<Promise> FileSystemHandle::Move(const fs::EntryId& aParentId,
+ const nsAString& aName,
+ ErrorResult& aError) {
+ RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ fs::Name name(aName);
+ if (!aParentId.IsEmpty()) {
+ fs::FileSystemChildMetadata newMetadata;
+ newMetadata.parentId() = aParentId;
+ newMetadata.childName() = aName;
+ mRequestHandler->MoveEntry(mManager, this, mMetadata, newMetadata, promise,
+ aError);
+ } else {
+ mRequestHandler->RenameEntry(mManager, this, mMetadata, name, promise,
+ aError);
+ }
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ // Other handles to this will be broken, and the spec is ok with this, but we
+ // need to update our EntryId and name
+ promise->AddCallbacksWithCycleCollectedArgs(
+ [name](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ FileSystemHandle* aHandle) {
+ // XXX Fix entryId!
+ LOG(("Changing FileSystemHandle name from %s to %s",
+ NS_ConvertUTF16toUTF8(aHandle->mMetadata.entryName()).get(),
+ NS_ConvertUTF16toUTF8(name).get()));
+ aHandle->mMetadata.entryName() = name;
+ },
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ FileSystemHandle* aHandle) {
+ LOG(("reject of move for %s",
+ NS_ConvertUTF16toUTF8(aHandle->mMetadata.entryName()).get()));
+ },
+ RefPtr(this));
+
+ return promise.forget();
+}
+
+// [Serializable] implementation
+
+// static
+already_AddRefed<FileSystemHandle> FileSystemHandle::ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader) {
+ LOG_VERBOSE(("Reading File/DirectoryHandle"));
+
+ uint32_t kind = static_cast<uint32_t>(FileSystemHandleKind::EndGuard_);
+
+ if (!JS_ReadBytes(aReader, reinterpret_cast<void*>(&kind),
+ sizeof(uint32_t))) {
+ return nullptr;
+ }
+
+ if (kind == static_cast<uint32_t>(FileSystemHandleKind::Directory)) {
+ RefPtr<FileSystemHandle> result =
+ FileSystemHandle::ConstructDirectoryHandle(aCx, aGlobal, aReader);
+ return result.forget();
+ }
+
+ if (kind == static_cast<uint32_t>(FileSystemHandleKind::File)) {
+ RefPtr<FileSystemHandle> result =
+ FileSystemHandle::ConstructFileHandle(aCx, aGlobal, aReader);
+ return result.forget();
+ }
+
+ return nullptr;
+}
+
+bool FileSystemHandle::WriteStructuredClone(
+ JSContext* aCx, JSStructuredCloneWriter* aWriter) const {
+ LOG_VERBOSE(("Writing File/DirectoryHandle"));
+ MOZ_ASSERT(mMetadata.entryId().Length() == 32);
+
+ auto kind = static_cast<uint32_t>(Kind());
+ if (NS_WARN_IF(!JS_WriteBytes(aWriter, static_cast<void*>(&kind),
+ sizeof(uint32_t)))) {
+ return false;
+ }
+
+ if (NS_WARN_IF(!JS_WriteBytes(
+ aWriter, static_cast<const void*>(mMetadata.entryId().get()),
+ mMetadata.entryId().Length()))) {
+ return false;
+ }
+
+ if (!StructuredCloneHolder::WriteString(aWriter, mMetadata.entryName())) {
+ return false;
+ }
+
+ // Needed to make sure the destination nsIGlobalObject is from the same
+ // origin/principal
+ QM_TRY_INSPECT(const auto& storageKey, mGlobal->GetStorageKey(), false);
+
+ return nsJSPrincipals::WritePrincipalInfo(aWriter, storageKey);
+}
+
+// static
+already_AddRefed<FileSystemFileHandle> FileSystemHandle::ConstructFileHandle(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader) {
+ LOG(("Reading FileHandle"));
+
+ fs::FileSystemEntryMetadata metadata;
+ if (!ConstructHandleMetadata(aCx, aGlobal, aReader, /* aDirectory */ false,
+ metadata)) {
+ return nullptr;
+ }
+
+ RefPtr<StorageManager> storageManager = aGlobal->GetStorageManager();
+ if (!storageManager) {
+ return nullptr;
+ }
+
+ // Note that the actor may not exist or may not be connected yet.
+ RefPtr<FileSystemManager> fileSystemManager =
+ storageManager->GetFileSystemManager();
+
+ RefPtr<FileSystemFileHandle> fsHandle =
+ new FileSystemFileHandle(aGlobal, fileSystemManager, metadata);
+
+ return fsHandle.forget();
+}
+
+// static
+already_AddRefed<FileSystemDirectoryHandle>
+FileSystemHandle::ConstructDirectoryHandle(JSContext* aCx,
+ nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader) {
+ LOG(("Reading DirectoryHandle"));
+
+ fs::FileSystemEntryMetadata metadata;
+ if (!ConstructHandleMetadata(aCx, aGlobal, aReader, /* aDirectory */ true,
+ metadata)) {
+ return nullptr;
+ }
+
+ RefPtr<StorageManager> storageManager = aGlobal->GetStorageManager();
+ if (!storageManager) {
+ return nullptr;
+ }
+
+ // Note that the actor may not exist or may not be connected yet.
+ RefPtr<FileSystemManager> fileSystemManager =
+ storageManager->GetFileSystemManager();
+
+ RefPtr<FileSystemDirectoryHandle> fsHandle =
+ new FileSystemDirectoryHandle(aGlobal, fileSystemManager, metadata);
+
+ return fsHandle.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fs/api/FileSystemHandle.h b/dom/fs/api/FileSystemHandle.h
new file mode 100644
index 0000000000..76ad66f5ea
--- /dev/null
+++ b/dom/fs/api/FileSystemHandle.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef DOM_FS_FILESYSTEMHANDLE_H_
+#define DOM_FS_FILESYSTEMHANDLE_H_
+
+#include "mozilla/dom/PFileSystemManager.h"
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class FileSystemDirectoryHandle;
+class FileSystemFileHandle;
+enum class FileSystemHandleKind : uint8_t;
+class FileSystemManager;
+class FileSystemManagerChild;
+class Promise;
+
+namespace fs {
+class FileSystemRequestHandler;
+} // namespace fs
+
+class FileSystemHandle : public nsISupports, public nsWrapperCache {
+ public:
+ FileSystemHandle(nsIGlobalObject* aGlobal,
+ RefPtr<FileSystemManager>& aManager,
+ const fs::FileSystemEntryMetadata& aMetadata,
+ fs::FileSystemRequestHandler* aRequestHandler);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FileSystemHandle)
+
+ const fs::EntryId& GetId() const { return mMetadata.entryId(); }
+
+ // WebIDL Boilerplate
+ nsIGlobalObject* GetParentObject() const;
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL Interface
+ virtual FileSystemHandleKind Kind() const = 0;
+
+ void GetName(nsAString& aResult);
+
+ already_AddRefed<Promise> IsSameEntry(FileSystemHandle& aOther,
+ ErrorResult& aError) const;
+
+ // [Serializable] implementation
+ static already_AddRefed<FileSystemHandle> ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader);
+
+ virtual bool WriteStructuredClone(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter) const;
+
+ already_AddRefed<Promise> Move(const nsAString& aName, ErrorResult& aError);
+
+ already_AddRefed<Promise> Move(FileSystemDirectoryHandle& aParent,
+ ErrorResult& aError);
+
+ already_AddRefed<Promise> Move(FileSystemDirectoryHandle& aParent,
+ const nsAString& aName, ErrorResult& aError);
+
+ already_AddRefed<Promise> Move(const fs::EntryId& aParentId,
+ const nsAString& aName, ErrorResult& aError);
+
+ void UpdateMetadata(const fs::FileSystemEntryMetadata& aMetadata) {
+ mMetadata = aMetadata;
+ }
+
+ protected:
+ virtual ~FileSystemHandle() = default;
+
+ static already_AddRefed<FileSystemFileHandle> ConstructFileHandle(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader);
+
+ static already_AddRefed<FileSystemDirectoryHandle> ConstructDirectoryHandle(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader);
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+
+ RefPtr<FileSystemManager> mManager;
+
+ // move() can change names/directories
+ fs::FileSystemEntryMetadata mMetadata;
+
+ const UniquePtr<fs::FileSystemRequestHandler> mRequestHandler;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // DOM_FS_FILESYSTEMHANDLE_H_
diff --git a/dom/fs/api/FileSystemManager.cpp b/dom/fs/api/FileSystemManager.cpp
new file mode 100644
index 0000000000..099739f8a1
--- /dev/null
+++ b/dom/fs/api/FileSystemManager.cpp
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "mozilla/dom/FileSystemManager.h"
+
+#include "FileSystemBackgroundRequestHandler.h"
+#include "fs/FileSystemRequestHandler.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/FileSystemManagerChild.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/StorageManager.h"
+#include "mozilla/dom/fs/ManagedMozPromiseRequestHolder.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+
+namespace mozilla::dom {
+
+FileSystemManager::FileSystemManager(
+ nsIGlobalObject* aGlobal, RefPtr<StorageManager> aStorageManager,
+ RefPtr<FileSystemBackgroundRequestHandler> aBackgroundRequestHandler)
+ : mGlobal(aGlobal),
+ mStorageManager(std::move(aStorageManager)),
+ mBackgroundRequestHandler(std::move(aBackgroundRequestHandler)),
+ mRequestHandler(new fs::FileSystemRequestHandler()) {}
+
+FileSystemManager::FileSystemManager(nsIGlobalObject* aGlobal,
+ RefPtr<StorageManager> aStorageManager)
+ : FileSystemManager(aGlobal, std::move(aStorageManager),
+ MakeRefPtr<FileSystemBackgroundRequestHandler>()) {}
+
+FileSystemManager::~FileSystemManager() { MOZ_ASSERT(mShutdown); }
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileSystemManager)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FileSystemManager);
+NS_IMPL_CYCLE_COLLECTING_RELEASE(FileSystemManager);
+NS_IMPL_CYCLE_COLLECTION(FileSystemManager, mGlobal, mStorageManager);
+
+void FileSystemManager::Shutdown() {
+ mShutdown.Flip();
+
+ auto shutdownAndDisconnect = [self = RefPtr(this)]() {
+ self->mBackgroundRequestHandler->Shutdown();
+
+ for (RefPtr<PromiseRequestHolder<BoolPromise>> holder :
+ self->mPromiseRequestHolders.ForwardRange()) {
+ holder->DisconnectIfExists();
+ }
+ };
+
+ if (NS_IsMainThread()) {
+ if (mBackgroundRequestHandler->FileSystemManagerChildStrongRef()) {
+ mBackgroundRequestHandler->FileSystemManagerChildStrongRef()
+ ->CloseAllWritables(
+ [shutdownAndDisconnect = std::move(shutdownAndDisconnect)]() {
+ shutdownAndDisconnect();
+ });
+ } else {
+ shutdownAndDisconnect();
+ }
+ } else {
+ if (mBackgroundRequestHandler->FileSystemManagerChildStrongRef()) {
+ // FileSystemAccessHandles and FileSystemWritableFileStreams prevent
+ // shutdown until they are full closed, so at this point, they all should
+ // be closed.
+ MOZ_ASSERT(mBackgroundRequestHandler->FileSystemManagerChildStrongRef()
+ ->AllSyncAccessHandlesClosed());
+ MOZ_ASSERT(mBackgroundRequestHandler->FileSystemManagerChildStrongRef()
+ ->AllWritableFileStreamsClosed());
+ }
+
+ shutdownAndDisconnect();
+ }
+}
+
+const RefPtr<FileSystemManagerChild>& FileSystemManager::ActorStrongRef()
+ const {
+ return mBackgroundRequestHandler->FileSystemManagerChildStrongRef();
+}
+
+void FileSystemManager::RegisterPromiseRequestHolder(
+ PromiseRequestHolder<BoolPromise>* aHolder) {
+ mPromiseRequestHolders.AppendElement(aHolder);
+}
+
+void FileSystemManager::UnregisterPromiseRequestHolder(
+ PromiseRequestHolder<BoolPromise>* aHolder) {
+ mPromiseRequestHolders.RemoveElement(aHolder);
+}
+
+void FileSystemManager::BeginRequest(
+ std::function<void(const RefPtr<FileSystemManagerChild>&)>&& aSuccess,
+ std::function<void(nsresult)>&& aFailure) {
+ MOZ_ASSERT(!mShutdown);
+
+ MOZ_ASSERT(mGlobal);
+
+ // Check if we're allowed to use storage
+ if (mGlobal->GetStorageAccess() < StorageAccess::eSessionScoped) {
+ aFailure(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ if (mBackgroundRequestHandler->FileSystemManagerChildStrongRef()) {
+ aSuccess(mBackgroundRequestHandler->FileSystemManagerChildStrongRef());
+ return;
+ }
+
+ QM_TRY_INSPECT(const auto& principalInfo, mGlobal->GetStorageKey(), QM_VOID,
+ [&aFailure](nsresult rv) { aFailure(rv); });
+
+ auto holder = MakeRefPtr<PromiseRequestHolder<BoolPromise>>(this);
+
+ mBackgroundRequestHandler->CreateFileSystemManagerChild(principalInfo)
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr<FileSystemManager>(this), holder,
+ success = std::move(aSuccess), failure = std::move(aFailure)](
+ const BoolPromise::ResolveOrRejectValue& aValue) {
+ holder->Complete();
+
+ if (aValue.IsResolve()) {
+ success(self->mBackgroundRequestHandler
+ ->FileSystemManagerChildStrongRef());
+ } else {
+ failure(aValue.RejectValue());
+ }
+ })
+ ->Track(*holder);
+}
+
+already_AddRefed<Promise> FileSystemManager::GetDirectory(ErrorResult& aError) {
+ MOZ_ASSERT(mGlobal);
+
+ RefPtr<Promise> promise = Promise::Create(mGlobal, aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(promise);
+
+ mRequestHandler->GetRootHandle(this, promise, aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ return nullptr;
+ }
+
+ return promise.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fs/api/FileSystemManager.h b/dom/fs/api/FileSystemManager.h
new file mode 100644
index 0000000000..b910eacea2
--- /dev/null
+++ b/dom/fs/api/FileSystemManager.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef DOM_FS_CHILD_FILESYSTEMMANAGER_H_
+#define DOM_FS_CHILD_FILESYSTEMMANAGER_H_
+
+#include <functional>
+
+#include "mozilla/MozPromise.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/FlippedOnce.h"
+#include "mozilla/dom/quota/ForwardDecls.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsTObserverArray.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class FileSystemManagerChild;
+class FileSystemBackgroundRequestHandler;
+class StorageManager;
+
+namespace fs {
+class FileSystemRequestHandler;
+template <typename Manager, typename PromiseType>
+class ManagedMozPromiseRequestHolder;
+} // namespace fs
+
+// `FileSystemManager` is supposed to be held by `StorageManager` and thus
+// there should always be only one `FileSystemManager` per `nsIGlobalObject`.
+// `FileSystemManager` is responsible for creating and eventually caching
+// `FileSystemManagerChild` which is required for communication with the parent
+// process. `FileSystemHandle` is also expected to hold `FileSystemManager`,
+// but it should never clear the strong reference during cycle collection's
+// unlink phase to keep the actor alive. `FileSystemSyncAccessHandle` and
+// `FileSystemWritableFileStream` are also expected to hold `FileSystemManager`,
+// and they shouldn't clear the strong reference during cycle collection's
+// unlink phase as well even though they have their own actor. Those actors
+// are managed by the top level actor, so if the top level actor is destroyed,
+// the whole chain of managed actors would be destroyed as well.
+class FileSystemManager : public nsISupports {
+ public:
+ template <typename PromiseType>
+ using PromiseRequestHolder =
+ fs::ManagedMozPromiseRequestHolder<FileSystemManager, PromiseType>;
+
+ FileSystemManager(
+ nsIGlobalObject* aGlobal, RefPtr<StorageManager> aStorageManager,
+ RefPtr<FileSystemBackgroundRequestHandler> aBackgroundRequestHandler);
+
+ FileSystemManager(nsIGlobalObject* aGlobal,
+ RefPtr<StorageManager> aStorageManager);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(FileSystemManager)
+
+ bool IsShutdown() const { return mShutdown; }
+
+ void Shutdown();
+
+ const RefPtr<FileSystemManagerChild>& ActorStrongRef() const;
+
+ void RegisterPromiseRequestHolder(PromiseRequestHolder<BoolPromise>* aHolder);
+
+ void UnregisterPromiseRequestHolder(
+ PromiseRequestHolder<BoolPromise>* aHolder);
+
+ void BeginRequest(
+ std::function<void(const RefPtr<FileSystemManagerChild>&)>&& aSuccess,
+ std::function<void(nsresult)>&& aFailure);
+
+ already_AddRefed<Promise> GetDirectory(ErrorResult& aError);
+
+ private:
+ virtual ~FileSystemManager();
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+
+ RefPtr<StorageManager> mStorageManager;
+
+ const RefPtr<FileSystemBackgroundRequestHandler> mBackgroundRequestHandler;
+ const UniquePtr<fs::FileSystemRequestHandler> mRequestHandler;
+
+ nsTObserverArray<PromiseRequestHolder<BoolPromise>*> mPromiseRequestHolders;
+
+ FlippedOnce<false> mShutdown;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // DOM_FS_CHILD_FILESYSTEMMANAGER_H_
diff --git a/dom/fs/api/FileSystemSyncAccessHandle.cpp b/dom/fs/api/FileSystemSyncAccessHandle.cpp
new file mode 100644
index 0000000000..c04f8e11a5
--- /dev/null
+++ b/dom/fs/api/FileSystemSyncAccessHandle.cpp
@@ -0,0 +1,650 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "FileSystemSyncAccessHandle.h"
+
+#include "fs/FileSystemAsyncCopy.h"
+#include "fs/FileSystemRequestHandler.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/FixedBufferOutputStream.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/dom/FileSystemAccessHandleChild.h"
+#include "mozilla/dom/FileSystemAccessHandleControlChild.h"
+#include "mozilla/dom/FileSystemHandleBinding.h"
+#include "mozilla/dom/FileSystemLog.h"
+#include "mozilla/dom/FileSystemManager.h"
+#include "mozilla/dom/FileSystemManagerChild.h"
+#include "mozilla/dom/FileSystemSyncAccessHandleBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/fs/IPCRejectReporter.h"
+#include "mozilla/dom/fs/TargetPtrHolder.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "mozilla/ipc/RandomAccessStreamUtils.h"
+#include "nsNetCID.h"
+#include "nsStringStream.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+using SizePromise = Int64Promise;
+const auto CreateAndRejectSizePromise = CreateAndRejectInt64Promise;
+
+} // namespace
+
+FileSystemSyncAccessHandle::FileSystemSyncAccessHandle(
+ nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+ mozilla::ipc::RandomAccessStreamParams&& aStreamParams,
+ RefPtr<FileSystemAccessHandleChild> aActor,
+ RefPtr<FileSystemAccessHandleControlChild> aControlActor,
+ RefPtr<TaskQueue> aIOTaskQueue,
+ const fs::FileSystemEntryMetadata& aMetadata)
+ : mGlobal(aGlobal),
+ mManager(aManager),
+ mActor(std::move(aActor)),
+ mControlActor(std::move(aControlActor)),
+ mIOTaskQueue(std::move(aIOTaskQueue)),
+ mStreamParams(std::move(aStreamParams)),
+ mMetadata(aMetadata),
+ mState(State::Initial) {
+ LOG(("Created SyncAccessHandle %p", this));
+
+ // Connect with the actor directly in the constructor. This way the actor
+ // can call `FileSystemSyncAccessHandle::ClearActor` when we call
+ // `PFileSystemAccessHandleChild::Send__delete__` even when
+ // FileSystemSyncAccessHandle::Create fails, in which case the not yet
+ // fully constructed FileSystemSyncAccessHandle is being destroyed.
+ mActor->SetAccessHandle(this);
+
+ mControlActor->SetAccessHandle(this);
+}
+
+FileSystemSyncAccessHandle::~FileSystemSyncAccessHandle() {
+ MOZ_ASSERT(!mActor);
+ MOZ_ASSERT(IsClosed());
+}
+
+// static
+Result<RefPtr<FileSystemSyncAccessHandle>, nsresult>
+FileSystemSyncAccessHandle::Create(
+ nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+ mozilla::ipc::RandomAccessStreamParams&& aStreamParams,
+ mozilla::ipc::ManagedEndpoint<PFileSystemAccessHandleChild>&&
+ aAccessHandleChildEndpoint,
+ mozilla::ipc::Endpoint<PFileSystemAccessHandleControlChild>&&
+ aAccessHandleControlChildEndpoint,
+ const fs::FileSystemEntryMetadata& aMetadata) {
+ WorkerPrivate* const workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ auto accessHandleChild = MakeRefPtr<FileSystemAccessHandleChild>();
+
+ QM_TRY(MOZ_TO_RESULT(
+ aManager->ActorStrongRef()->BindPFileSystemAccessHandleEndpoint(
+ std::move(aAccessHandleChildEndpoint), accessHandleChild)));
+
+ auto accessHandleControlChild =
+ MakeRefPtr<FileSystemAccessHandleControlChild>();
+
+ aAccessHandleControlChildEndpoint.Bind(accessHandleControlChild,
+ workerPrivate->ControlEventTarget());
+
+ QM_TRY_UNWRAP(auto streamTransportService,
+ MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIEventTarget>,
+ MOZ_SELECT_OVERLOAD(do_GetService),
+ NS_STREAMTRANSPORTSERVICE_CONTRACTID));
+
+ RefPtr<TaskQueue> ioTaskQueue = TaskQueue::Create(
+ streamTransportService.forget(), "FileSystemSyncAccessHandle");
+ QM_TRY(MOZ_TO_RESULT(ioTaskQueue));
+
+ RefPtr<FileSystemSyncAccessHandle> result = new FileSystemSyncAccessHandle(
+ aGlobal, aManager, std::move(aStreamParams), std::move(accessHandleChild),
+ std::move(accessHandleControlChild), std::move(ioTaskQueue), aMetadata);
+
+ auto autoClose = MakeScopeExit([result] {
+ MOZ_ASSERT(result->mState == State::Initial);
+ result->mState = State::Closed;
+ result->mActor->SendClose();
+ });
+
+ workerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
+ workerPrivate, "FileSystemSyncAccessHandle", [result]() {
+ if (result->IsOpen()) {
+ // We don't need to use the result, we just need to begin the closing
+ // process.
+ Unused << result->BeginClose();
+ }
+ });
+ QM_TRY(MOZ_TO_RESULT(workerRef));
+
+ autoClose.release();
+
+ result->mWorkerRef = std::move(workerRef);
+ result->mState = State::Open;
+
+ return result;
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileSystemSyncAccessHandle)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FileSystemSyncAccessHandle)
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(FileSystemSyncAccessHandle,
+ LastRelease())
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FileSystemSyncAccessHandle)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FileSystemSyncAccessHandle)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
+ // Don't unlink mManager!
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ if (tmp->IsOpen()) {
+ // We don't need to use the result, we just need to begin the closing
+ // process.
+ Unused << tmp->BeginClose();
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FileSystemSyncAccessHandle)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mManager)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+void FileSystemSyncAccessHandle::LastRelease() {
+ // We can't call `FileSystemSyncAccessHandle::Close` here because it may need
+ // to keep FileSystemSyncAccessHandle object alive which isn't possible when
+ // the object is about to be deleted. There are other mechanisms which ensure
+ // that the object is correctly closed before destruction. For example the
+ // object unlinking and the worker shutdown (we get notified about it via the
+ // callback passed to `StrongWorkerRef`) are used to close the object if it
+ // hasn't been closed yet.
+
+ if (mActor) {
+ PFileSystemAccessHandleChild::Send__delete__(mActor);
+
+ // `PFileSystemAccessHandleChild::Send__delete__` is supposed to call
+ // `FileSystemAccessHandleChild::ActorDestroy` which in turn calls
+ // `FileSystemSyncAccessHandle::ClearActor`, so `mActor` should be be null
+ // at this point.
+ MOZ_ASSERT(!mActor);
+ }
+
+ if (mControlActor) {
+ mControlActor->Close();
+
+ // `FileSystemAccessHandleControlChild::Close` is supposed to call
+ // `FileSystemAccessHandleControlChild::ActorDestroy` which in turn calls
+ // `FileSystemSyncAccessHandle::ClearControlActor`, so `mControlActor`
+ // should be be null at this point.
+ MOZ_ASSERT(!mControlActor);
+ }
+}
+
+void FileSystemSyncAccessHandle::ClearActor() {
+ MOZ_ASSERT(mActor);
+
+ mActor = nullptr;
+}
+
+void FileSystemSyncAccessHandle::ClearControlActor() {
+ // `mControlActor` is initialized in the constructor and this method is
+ // supposed to be called only once.
+ MOZ_ASSERT(mControlActor);
+
+ mControlActor = nullptr;
+}
+
+bool FileSystemSyncAccessHandle::IsOpen() const {
+ MOZ_ASSERT(mState != State::Initial);
+
+ return mState == State::Open;
+}
+
+bool FileSystemSyncAccessHandle::IsClosing() const {
+ MOZ_ASSERT(mState != State::Initial);
+
+ return mState == State::Closing;
+}
+
+bool FileSystemSyncAccessHandle::IsClosed() const {
+ MOZ_ASSERT(mState != State::Initial);
+
+ return mState == State::Closed;
+}
+
+RefPtr<BoolPromise> FileSystemSyncAccessHandle::BeginClose() {
+ MOZ_ASSERT(IsOpen());
+
+ mState = State::Closing;
+
+ InvokeAsync(mIOTaskQueue, __func__,
+ [selfHolder = fs::TargetPtrHolder(this)]() {
+ if (selfHolder->mStream) {
+ LOG(("%p: Closing", selfHolder->mStream.get()));
+
+ selfHolder->mStream->OutputStream()->Close();
+ selfHolder->mStream = nullptr;
+ } else {
+ LOG(("Closing (no stream)"));
+
+ // If the stream was not deserialized, `mStreamParams` still
+ // contains a pre-opened file descriptor which needs to be
+ // closed here by moving `mStreamParams` to a local variable
+ // (the file descriptor will be closed for real when
+ // `streamParams` goes out of scope).
+
+ mozilla::ipc::RandomAccessStreamParams streamParams(
+ std::move(selfHolder->mStreamParams));
+ }
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(mWorkerRef->Private()->ControlEventTarget(), __func__,
+ [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue&) {
+ return self->mIOTaskQueue->BeginShutdown();
+ })
+ ->Then(
+ mWorkerRef->Private()->ControlEventTarget(), __func__,
+ [self = RefPtr(this)](const ShutdownPromise::ResolveOrRejectValue&) {
+ if (self->mControlActor) {
+ RefPtr<BoolPromise::Private> promise =
+ new BoolPromise::Private(__func__);
+
+ self->mControlActor->SendClose(
+ [promise](void_t&&) { promise->Resolve(true, __func__); },
+ [promise](const mozilla::ipc::ResponseRejectReason& aReason) {
+ fs::IPCRejectReporter(aReason);
+
+ promise->Reject(NS_ERROR_FAILURE, __func__);
+ });
+
+ return RefPtr<BoolPromise>(promise);
+ }
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(mWorkerRef->Private()->ControlEventTarget(), __func__,
+ [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue&) {
+ self->mWorkerRef = nullptr;
+
+ self->mState = State::Closed;
+
+ self->mClosePromiseHolder.ResolveIfExists(true, __func__);
+ });
+
+ return OnClose();
+}
+
+RefPtr<BoolPromise> FileSystemSyncAccessHandle::OnClose() {
+ MOZ_ASSERT(mState == State::Closing);
+
+ return mClosePromiseHolder.Ensure(__func__);
+}
+
+// WebIDL Boilerplate
+
+nsIGlobalObject* FileSystemSyncAccessHandle::GetParentObject() const {
+ return mGlobal;
+}
+
+JSObject* FileSystemSyncAccessHandle::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return FileSystemSyncAccessHandle_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// WebIDL Interface
+
+uint64_t FileSystemSyncAccessHandle::Read(
+ const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer,
+ const FileSystemReadWriteOptions& aOptions, ErrorResult& aRv) {
+ return ReadOrWrite(aBuffer, aOptions, /* aRead */ true, aRv);
+}
+
+uint64_t FileSystemSyncAccessHandle::Write(
+ const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer,
+ const FileSystemReadWriteOptions& aOptions, ErrorResult& aRv) {
+ return ReadOrWrite(aBuffer, aOptions, /* aRead */ false, aRv);
+}
+
+void FileSystemSyncAccessHandle::Truncate(uint64_t aSize, ErrorResult& aError) {
+ if (!IsOpen()) {
+ aError.ThrowInvalidStateError("SyncAccessHandle is closed");
+ return;
+ }
+
+ MOZ_ASSERT(mWorkerRef);
+
+ AutoSyncLoopHolder syncLoop(mWorkerRef->Private(), Canceling);
+
+ nsCOMPtr<nsISerialEventTarget> syncLoopTarget =
+ syncLoop.GetSerialEventTarget();
+ QM_TRY(MOZ_TO_RESULT(syncLoopTarget), [&aError](nsresult) {
+ aError.ThrowInvalidStateError("Worker is shutting down");
+ });
+
+ InvokeAsync(
+ mIOTaskQueue, __func__,
+ [selfHolder = fs::TargetPtrHolder(this), aSize]() {
+ QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()),
+ CreateAndRejectBoolPromise);
+
+ LOG(("%p: Truncate to %" PRIu64, selfHolder->mStream.get(), aSize));
+ int64_t offset = 0;
+ QM_TRY(MOZ_TO_RESULT(selfHolder->mStream->Tell(&offset)),
+ CreateAndRejectBoolPromise);
+ QM_TRY(MOZ_TO_RESULT(selfHolder->mStream->Seek(
+ nsISeekableStream::NS_SEEK_SET, aSize)),
+ CreateAndRejectBoolPromise);
+
+ QM_TRY(MOZ_TO_RESULT(selfHolder->mStream->SetEOF()),
+ CreateAndRejectBoolPromise);
+ // restore cursor position (clamp to end of file)
+ QM_TRY(MOZ_TO_RESULT(selfHolder->mStream->Seek(
+ nsISeekableStream::NS_SEEK_SET,
+ std::min((uint64_t)offset, aSize))),
+ CreateAndRejectBoolPromise);
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(syncLoopTarget, __func__,
+ [this, &syncLoopTarget](
+ const BoolPromise::ResolveOrRejectValue& aValue) {
+ MOZ_ASSERT(mWorkerRef);
+
+ mWorkerRef->Private()->AssertIsOnWorkerThread();
+
+ mWorkerRef->Private()->StopSyncLoop(
+ syncLoopTarget,
+ aValue.IsResolve() ? NS_OK : aValue.RejectValue());
+ });
+
+ QM_TRY(MOZ_TO_RESULT(syncLoop.Run()),
+ [&aError](const nsresult rv) { aError.Throw(rv); });
+}
+
+uint64_t FileSystemSyncAccessHandle::GetSize(ErrorResult& aError) {
+ if (!IsOpen()) {
+ aError.ThrowInvalidStateError("SyncAccessHandle is closed");
+ return 0;
+ }
+
+ MOZ_ASSERT(mWorkerRef);
+
+ AutoSyncLoopHolder syncLoop(mWorkerRef->Private(), Canceling);
+
+ nsCOMPtr<nsISerialEventTarget> syncLoopTarget =
+ syncLoop.GetSerialEventTarget();
+ QM_TRY(MOZ_TO_RESULT(syncLoopTarget), [&aError](nsresult) {
+ aError.ThrowInvalidStateError("Worker is shutting down");
+ return 0;
+ });
+
+ // XXX Could we somehow pass the size to `StopSyncLoop` and then get it via
+ // `QM_TRY_INSPECT(const auto& size, syncLoop.Run)` ?
+ // Could we use Result<UniquePtr<...>, nsresult> for that ?
+ int64_t size;
+
+ InvokeAsync(mIOTaskQueue, __func__,
+ [selfHolder = fs::TargetPtrHolder(this)]() {
+ QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()),
+ CreateAndRejectSizePromise);
+
+ nsCOMPtr<nsIFileMetadata> fileMetadata =
+ do_QueryInterface(selfHolder->mStream);
+ MOZ_ASSERT(fileMetadata);
+
+ QM_TRY_INSPECT(
+ const auto& size,
+ MOZ_TO_RESULT_INVOKE_MEMBER(fileMetadata, GetSize),
+ CreateAndRejectSizePromise);
+
+ LOG(("%p: GetSize %" PRIu64, selfHolder->mStream.get(), size));
+
+ return SizePromise::CreateAndResolve(size, __func__);
+ })
+ ->Then(syncLoopTarget, __func__,
+ [this, &syncLoopTarget,
+ &size](const Int64Promise::ResolveOrRejectValue& aValue) {
+ MOZ_ASSERT(mWorkerRef);
+
+ mWorkerRef->Private()->AssertIsOnWorkerThread();
+
+ if (aValue.IsResolve()) {
+ size = aValue.ResolveValue();
+
+ mWorkerRef->Private()->StopSyncLoop(syncLoopTarget, NS_OK);
+ } else {
+ mWorkerRef->Private()->StopSyncLoop(syncLoopTarget,
+ aValue.RejectValue());
+ }
+ });
+
+ QM_TRY(MOZ_TO_RESULT(syncLoop.Run()), [&aError](const nsresult rv) {
+ aError.Throw(rv);
+ return 0;
+ });
+
+ return size;
+}
+
+void FileSystemSyncAccessHandle::Flush(ErrorResult& aError) {
+ if (!IsOpen()) {
+ aError.ThrowInvalidStateError("SyncAccessHandle is closed");
+ return;
+ }
+
+ MOZ_ASSERT(mWorkerRef);
+
+ AutoSyncLoopHolder syncLoop(mWorkerRef->Private(), Canceling);
+
+ nsCOMPtr<nsISerialEventTarget> syncLoopTarget =
+ syncLoop.GetSerialEventTarget();
+ QM_TRY(MOZ_TO_RESULT(syncLoopTarget), [&aError](nsresult) {
+ aError.ThrowInvalidStateError("Worker is shutting down");
+ });
+
+ InvokeAsync(mIOTaskQueue, __func__,
+ [selfHolder = fs::TargetPtrHolder(this)]() {
+ QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()),
+ CreateAndRejectBoolPromise);
+
+ LOG(("%p: Flush", selfHolder->mStream.get()));
+
+ QM_TRY(
+ MOZ_TO_RESULT(selfHolder->mStream->OutputStream()->Flush()),
+ CreateAndRejectBoolPromise);
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(syncLoopTarget, __func__,
+ [this, &syncLoopTarget](
+ const BoolPromise::ResolveOrRejectValue& aValue) {
+ MOZ_ASSERT(mWorkerRef);
+
+ mWorkerRef->Private()->AssertIsOnWorkerThread();
+
+ mWorkerRef->Private()->StopSyncLoop(
+ syncLoopTarget,
+ aValue.IsResolve() ? NS_OK : aValue.RejectValue());
+ });
+
+ QM_TRY(MOZ_TO_RESULT(syncLoop.Run()),
+ [&aError](const nsresult rv) { aError.Throw(rv); });
+}
+
+void FileSystemSyncAccessHandle::Close() {
+ if (!(IsOpen() || IsClosing())) {
+ return;
+ }
+
+ MOZ_ASSERT(mWorkerRef);
+
+ // Normally mWorkerRef can be used directly for stopping the sync loop, but
+ // the async close is special because mWorkerRef is cleared as part of the
+ // operation. That's why we need to use this extra strong ref to the
+ // `StrongWorkerRef`.
+ RefPtr<StrongWorkerRef> workerRef = mWorkerRef;
+
+ AutoSyncLoopHolder syncLoop(workerRef->Private(), Killing);
+
+ nsCOMPtr<nsISerialEventTarget> syncLoopTarget =
+ syncLoop.GetSerialEventTarget();
+ MOZ_ASSERT(syncLoopTarget);
+
+ InvokeAsync(syncLoopTarget, __func__, [self = RefPtr(this)]() {
+ if (self->IsOpen()) {
+ return self->BeginClose();
+ }
+ return self->OnClose();
+ })->Then(syncLoopTarget, __func__, [&workerRef, &syncLoopTarget]() {
+ MOZ_ASSERT(workerRef);
+
+ workerRef->Private()->AssertIsOnWorkerThread();
+
+ workerRef->Private()->StopSyncLoop(syncLoopTarget, NS_OK);
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(syncLoop.Run());
+}
+
+uint64_t FileSystemSyncAccessHandle::ReadOrWrite(
+ const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer,
+ const FileSystemReadWriteOptions& aOptions, const bool aRead,
+ ErrorResult& aRv) {
+ if (!IsOpen()) {
+ aRv.ThrowInvalidStateError("SyncAccessHandle is closed");
+ return 0;
+ }
+
+ MOZ_ASSERT(mWorkerRef);
+
+ auto throwAndReturn = [&aRv](const nsresult rv) {
+ aRv.Throw(rv);
+ return 0;
+ };
+
+ const auto dataSpan = [&aBuffer]() {
+ if (aBuffer.IsArrayBuffer()) {
+ const ArrayBuffer& buffer = aBuffer.GetAsArrayBuffer();
+ buffer.ComputeState();
+ return Span{buffer.Data(), buffer.Length()};
+ }
+ MOZ_ASSERT(aBuffer.IsArrayBufferView());
+ const ArrayBufferView& buffer = aBuffer.GetAsArrayBufferView();
+ buffer.ComputeState();
+ return Span{buffer.Data(), buffer.Length()};
+ }();
+
+ CheckedInt<int64_t> offset = 0;
+ if (aOptions.mAt.WasPassed()) {
+ // Handle seek before read ('at')
+ offset = CheckedInt<int64_t>(aOptions.mAt.Value());
+ QM_TRY(MOZ_TO_RESULT(offset.isValid()), throwAndReturn);
+ }
+ AutoSyncLoopHolder syncLoop(mWorkerRef->Private(), Canceling);
+
+ nsCOMPtr<nsISerialEventTarget> syncLoopTarget =
+ syncLoop.GetSerialEventTarget();
+ QM_TRY(MOZ_TO_RESULT(syncLoopTarget), [&aRv](nsresult) {
+ aRv.ThrowInvalidStateError("Worker is shutting down");
+ return 0;
+ });
+
+ uint64_t totalCount = 0;
+
+ InvokeAsync(
+ mIOTaskQueue, __func__,
+ [selfHolder = fs::TargetPtrHolder(this), dataSpan,
+ use_offset = aOptions.mAt.WasPassed(), offset, aRead, &totalCount]() {
+ QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()),
+ CreateAndRejectBoolPromise);
+
+ if (use_offset) {
+ LOG_VERBOSE(("%p: Seeking to %" PRIu64, selfHolder->mStream.get(),
+ offset.value()));
+
+ QM_TRY(MOZ_TO_RESULT(selfHolder->mStream->Seek(
+ nsISeekableStream::NS_SEEK_SET, offset.value())),
+ CreateAndRejectBoolPromise);
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCOMPtr<nsIOutputStream> outputStream;
+
+ if (aRead) {
+ LOG_VERBOSE(("%p: Reading %zu bytes", selfHolder->mStream.get(),
+ dataSpan.Length()));
+
+ inputStream = selfHolder->mStream->InputStream();
+
+ outputStream =
+ FixedBufferOutputStream::Create(AsWritableChars(dataSpan));
+ } else {
+ LOG_VERBOSE(("%p: Writing %zu bytes", selfHolder->mStream.get(),
+ dataSpan.Length()));
+
+ QM_TRY(MOZ_TO_RESULT(NS_NewByteInputStream(
+ getter_AddRefs(inputStream), AsChars(dataSpan),
+ NS_ASSIGNMENT_DEPEND)),
+ CreateAndRejectBoolPromise);
+
+ outputStream = selfHolder->mStream->OutputStream();
+ }
+
+ auto promiseHolder = MakeUnique<MozPromiseHolder<BoolPromise>>();
+ RefPtr<BoolPromise> promise = promiseHolder->Ensure(__func__);
+
+ QM_TRY(MOZ_TO_RESULT(fs::AsyncCopy(
+ inputStream, outputStream, GetCurrentSerialEventTarget(),
+ aRead ? NS_ASYNCCOPY_VIA_WRITESEGMENTS
+ : NS_ASYNCCOPY_VIA_READSEGMENTS,
+ /* aCloseSource */ !aRead, /* aCloseSink */ aRead,
+ [&totalCount](uint32_t count) { totalCount += count; },
+ [promiseHolder = std::move(promiseHolder)](nsresult rv) {
+ promiseHolder->ResolveIfExists(true, __func__);
+ })),
+ CreateAndRejectBoolPromise);
+
+ return promise;
+ })
+ ->Then(syncLoopTarget, __func__,
+ [this, &syncLoopTarget](
+ const BoolPromise::ResolveOrRejectValue& aValue) {
+ MOZ_ASSERT(mWorkerRef);
+
+ mWorkerRef->Private()->AssertIsOnWorkerThread();
+
+ mWorkerRef->Private()->StopSyncLoop(syncLoopTarget, NS_OK);
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(syncLoop.Run());
+
+ return totalCount;
+}
+
+nsresult FileSystemSyncAccessHandle::EnsureStream() {
+ if (!mStream) {
+ QM_TRY_UNWRAP(mStream, DeserializeRandomAccessStream(mStreamParams),
+ NS_ERROR_FAILURE);
+
+ mozilla::ipc::RandomAccessStreamParams streamParams(
+ std::move(mStreamParams));
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fs/api/FileSystemSyncAccessHandle.h b/dom/fs/api/FileSystemSyncAccessHandle.h
new file mode 100644
index 0000000000..49f470b60b
--- /dev/null
+++ b/dom/fs/api/FileSystemSyncAccessHandle.h
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef DOM_FS_FILESYSTEMSYNCACCESSHANDLE_H_
+#define DOM_FS_FILESYSTEMSYNCACCESSHANDLE_H_
+
+#include "mozilla/dom/PFileSystemManager.h"
+#include "mozilla/dom/quota/ForwardDecls.h"
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+
+class ErrorResult;
+class TaskQueue;
+
+namespace dom {
+
+class FileSystemAccessHandleChild;
+class FileSystemAccessHandleControlChild;
+struct FileSystemReadWriteOptions;
+class FileSystemManager;
+class MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer;
+class Promise;
+class StrongWorkerRef;
+
+class FileSystemSyncAccessHandle final : public nsISupports,
+ public nsWrapperCache {
+ public:
+ enum struct State : uint8_t { Initial = 0, Open, Closing, Closed };
+
+ static Result<RefPtr<FileSystemSyncAccessHandle>, nsresult> Create(
+ nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+ mozilla::ipc::RandomAccessStreamParams&& aStreamParams,
+ mozilla::ipc::ManagedEndpoint<PFileSystemAccessHandleChild>&&
+ aAccessHandleChildEndpoint,
+ mozilla::ipc::Endpoint<PFileSystemAccessHandleControlChild>&&
+ aAccessHandleControlChildEndpoint,
+ const fs::FileSystemEntryMetadata& aMetadata);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FileSystemSyncAccessHandle)
+
+ void LastRelease();
+
+ void ClearActor();
+
+ void ClearControlActor();
+
+ bool IsOpen() const;
+
+ bool IsClosing() const;
+
+ bool IsClosed() const;
+
+ [[nodiscard]] RefPtr<BoolPromise> BeginClose();
+
+ [[nodiscard]] RefPtr<BoolPromise> OnClose();
+
+ // WebIDL Boilerplate
+ nsIGlobalObject* GetParentObject() const;
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL Interface
+ uint64_t Read(
+ const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer,
+ const FileSystemReadWriteOptions& aOptions, ErrorResult& aRv);
+
+ uint64_t Write(
+ const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer,
+ const FileSystemReadWriteOptions& aOptions, ErrorResult& aRv);
+
+ void Truncate(uint64_t aSize, ErrorResult& aError);
+
+ uint64_t GetSize(ErrorResult& aError);
+
+ void Flush(ErrorResult& aError);
+
+ void Close();
+
+ private:
+ FileSystemSyncAccessHandle(
+ nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+ mozilla::ipc::RandomAccessStreamParams&& aStreamParams,
+ RefPtr<FileSystemAccessHandleChild> aActor,
+ RefPtr<FileSystemAccessHandleControlChild> aControlActor,
+ RefPtr<TaskQueue> aIOTaskQueue,
+ const fs::FileSystemEntryMetadata& aMetadata);
+
+ virtual ~FileSystemSyncAccessHandle();
+
+ uint64_t ReadOrWrite(
+ const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer,
+ const FileSystemReadWriteOptions& aOptions, const bool aRead,
+ ErrorResult& aRv);
+
+ nsresult EnsureStream();
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+
+ RefPtr<FileSystemManager> mManager;
+
+ RefPtr<FileSystemAccessHandleChild> mActor;
+
+ RefPtr<FileSystemAccessHandleControlChild> mControlActor;
+
+ RefPtr<TaskQueue> mIOTaskQueue;
+
+ nsCOMPtr<nsIRandomAccessStream> mStream;
+
+ RefPtr<StrongWorkerRef> mWorkerRef;
+
+ MozPromiseHolder<BoolPromise> mClosePromiseHolder;
+
+ mozilla::ipc::RandomAccessStreamParams mStreamParams;
+
+ const fs::FileSystemEntryMetadata mMetadata;
+
+ State mState;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // DOM_FS_FILESYSTEMSYNCACCESSHANDLE_H_
diff --git a/dom/fs/api/FileSystemWritableFileStream.cpp b/dom/fs/api/FileSystemWritableFileStream.cpp
new file mode 100644
index 0000000000..b19a0c9fe9
--- /dev/null
+++ b/dom/fs/api/FileSystemWritableFileStream.cpp
@@ -0,0 +1,951 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "FileSystemWritableFileStream.h"
+
+#include "fs/FileSystemAsyncCopy.h"
+#include "fs/FileSystemShutdownBlocker.h"
+#include "fs/FileSystemThreadSafeStreamOwner.h"
+#include "mozilla/Buffer.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/InputStreamLengthHelper.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/dom/Blob.h"
+#include "mozilla/dom/FileSystemHandle.h"
+#include "mozilla/dom/FileSystemLog.h"
+#include "mozilla/dom/FileSystemManager.h"
+#include "mozilla/dom/FileSystemWritableFileStreamBinding.h"
+#include "mozilla/dom/FileSystemWritableFileStreamChild.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WritableStreamDefaultController.h"
+#include "mozilla/dom/fs/TargetPtrHolder.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "mozilla/ipc/RandomAccessStreamUtils.h"
+#include "nsAsyncStreamCopier.h"
+#include "nsIInputStream.h"
+#include "nsIRequestObserver.h"
+#include "nsISupportsImpl.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+constexpr bool IsFileNotFoundError(const nsresult aRv) {
+ return NS_ERROR_DOM_FILE_NOT_FOUND_ERR == aRv ||
+ NS_ERROR_FILE_NOT_FOUND == aRv;
+}
+
+class WritableFileStreamUnderlyingSinkAlgorithms final
+ : public UnderlyingSinkAlgorithmsWrapper {
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
+ WritableFileStreamUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase)
+
+ explicit WritableFileStreamUnderlyingSinkAlgorithms(
+ FileSystemWritableFileStream& aStream)
+ : mStream(&aStream) {}
+
+ already_AddRefed<Promise> WriteCallback(
+ JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ WritableStreamDefaultController& aController, ErrorResult& aRv) override;
+
+ already_AddRefed<Promise> CloseCallbackImpl(JSContext* aCx,
+ ErrorResult& aRv) override;
+
+ already_AddRefed<Promise> AbortCallbackImpl(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) override;
+
+ void ReleaseObjects() override;
+
+ private:
+ ~WritableFileStreamUnderlyingSinkAlgorithms() = default;
+
+ RefPtr<FileSystemWritableFileStream> mStream;
+};
+
+} // namespace
+
+class FileSystemWritableFileStream::Command {
+ public:
+ explicit Command(RefPtr<FileSystemWritableFileStream> aWritableFileStream)
+ : mWritableFileStream(std::move(aWritableFileStream)) {
+ MOZ_ASSERT(mWritableFileStream);
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(FileSystemWritableFileStream::Command)
+
+ private:
+ ~Command() { mWritableFileStream->NoteFinishedCommand(); }
+
+ RefPtr<FileSystemWritableFileStream> mWritableFileStream;
+};
+
+class FileSystemWritableFileStream::CloseHandler {
+ enum struct State : uint8_t { Initial = 0, Open, Closing, Closed };
+
+ public:
+ CloseHandler()
+ : mShutdownBlocker(fs::FileSystemShutdownBlocker::CreateForWritable()),
+ mClosePromiseHolder(),
+ mState(State::Initial) {}
+
+ NS_INLINE_DECL_REFCOUNTING(FileSystemWritableFileStream::CloseHandler)
+
+ /**
+ * @brief Are we not yet closing?
+ */
+ bool IsOpen() const { return State::Open == mState; }
+
+ /**
+ * @brief Are we already fully closed?
+ */
+ bool IsClosed() const { return State::Closed == mState; }
+
+ /**
+ * @brief Transition from open to closing state
+ *
+ * @return true if the state was open and became closing after the call
+ * @return false in all the other cases the previous state is preserved
+ */
+ bool TestAndSetClosing() {
+ const bool isOpen = State::Open == mState;
+
+ if (isOpen) {
+ mState = State::Closing;
+ }
+
+ return isOpen;
+ }
+
+ RefPtr<BoolPromise> GetClosePromise() const {
+ MOZ_ASSERT(State::Open != mState,
+ "Please call TestAndSetClosing before GetClosePromise");
+
+ if (State::Closing == mState) {
+ return mClosePromiseHolder.Ensure(__func__);
+ }
+
+ // Instant resolve for initial state due to early shutdown or closed state
+ return BoolPromise::CreateAndResolve(true, __func__);
+ }
+
+ /**
+ * @brief Transition from initial to open state. In initial state
+ *
+ */
+ void Open() {
+ MOZ_ASSERT(State::Initial == mState);
+ mShutdownBlocker->Block();
+
+ mState = State::Open;
+ }
+
+ /**
+ * @brief Transition to closed state and resolve all pending promises.
+ *
+ */
+ void Close() {
+ mShutdownBlocker->Unblock();
+ mState = State::Closed;
+ mClosePromiseHolder.ResolveIfExists(true, __func__);
+ }
+
+ protected:
+ virtual ~CloseHandler() = default;
+
+ private:
+ RefPtr<fs::FileSystemShutdownBlocker> mShutdownBlocker;
+
+ mutable MozPromiseHolder<BoolPromise> mClosePromiseHolder;
+
+ State mState;
+};
+
+FileSystemWritableFileStream::FileSystemWritableFileStream(
+ const nsCOMPtr<nsIGlobalObject>& aGlobal,
+ RefPtr<FileSystemManager>& aManager,
+ mozilla::ipc::RandomAccessStreamParams&& aStreamParams,
+ RefPtr<FileSystemWritableFileStreamChild> aActor,
+ already_AddRefed<TaskQueue> aTaskQueue,
+ const fs::FileSystemEntryMetadata& aMetadata)
+ : WritableStream(aGlobal, HoldDropJSObjectsCaller::Explicit),
+ mManager(aManager),
+ mActor(std::move(aActor)),
+ mTaskQueue(aTaskQueue),
+ mWorkerRef(),
+ mStreamParams(std::move(aStreamParams)),
+ mMetadata(std::move(aMetadata)),
+ mCloseHandler(MakeAndAddRef<CloseHandler>()),
+ mCommandActive(false) {
+ LOG(("Created WritableFileStream %p", this));
+
+ // Connect with the actor directly in the constructor. This way the actor
+ // can call `FileSystemWritableFileStream::ClearActor` when we call
+ // `PFileSystemWritableFileStreamChild::Send__delete__` even when
+ // FileSystemWritableFileStream::Create fails, in which case the not yet
+ // fully constructed FileSystemWritableFileStream is being destroyed.
+ mActor->SetStream(this);
+
+ mozilla::HoldJSObjects(this);
+}
+
+FileSystemWritableFileStream::~FileSystemWritableFileStream() {
+ MOZ_ASSERT(!mCommandActive);
+ MOZ_ASSERT(IsClosed());
+
+ mozilla::DropJSObjects(this);
+}
+
+// https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream
+// * This is fallible because of OOM handling of JSAPI. See bug 1762233.
+// XXX(krosylight): _BOUNDARY because SetUpNative here can't run script because
+// StartCallback here is no-op. Can we let the static check automatically detect
+// this situation?
+/* static */
+MOZ_CAN_RUN_SCRIPT_BOUNDARY
+Result<RefPtr<FileSystemWritableFileStream>, nsresult>
+FileSystemWritableFileStream::Create(
+ const nsCOMPtr<nsIGlobalObject>& aGlobal,
+ RefPtr<FileSystemManager>& aManager,
+ mozilla::ipc::RandomAccessStreamParams&& aStreamParams,
+ RefPtr<FileSystemWritableFileStreamChild> aActor,
+ const fs::FileSystemEntryMetadata& aMetadata) {
+ MOZ_ASSERT(aGlobal);
+
+ QM_TRY_UNWRAP(auto streamTransportService,
+ MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIEventTarget>,
+ MOZ_SELECT_OVERLOAD(do_GetService),
+ NS_STREAMTRANSPORTSERVICE_CONTRACTID));
+
+ RefPtr<TaskQueue> taskQueue =
+ TaskQueue::Create(streamTransportService.forget(), "WritableStreamQueue");
+ MOZ_ASSERT(taskQueue);
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(aGlobal)) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ JSContext* cx = jsapi.cx();
+
+ // Step 1. Let stream be a new FileSystemWritableFileStream in realm.
+ // Step 2. Set stream.[[file]] to file. (Covered by the constructor)
+ RefPtr<FileSystemWritableFileStream> stream =
+ new FileSystemWritableFileStream(
+ aGlobal, aManager, std::move(aStreamParams), std::move(aActor),
+ taskQueue.forget(), aMetadata);
+
+ auto autoClose = MakeScopeExit([stream] {
+ stream->mCloseHandler->Close();
+ stream->mActor->SendClose();
+ });
+
+ QM_TRY_UNWRAP(
+ RefPtr<StrongWorkerRef> workerRef,
+ ([stream]() -> Result<RefPtr<StrongWorkerRef>, nsresult> {
+ WorkerPrivate* const workerPrivate = GetCurrentThreadWorkerPrivate();
+ if (!workerPrivate) {
+ return RefPtr<StrongWorkerRef>();
+ }
+
+ RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
+ workerPrivate, "FileSystemWritableFileStream::Create", [stream]() {
+ if (stream->IsOpen()) {
+ // We don't need the promise, we just
+ // begin the closing process.
+ Unused << stream->BeginClose();
+ }
+ });
+ QM_TRY(MOZ_TO_RESULT(workerRef));
+
+ return workerRef;
+ }()));
+
+ // Step 3 - 5
+ auto algorithms =
+ MakeRefPtr<WritableFileStreamUnderlyingSinkAlgorithms>(*stream);
+
+ // Step 8: Set up stream with writeAlgorithm set to writeAlgorithm,
+ // closeAlgorithm set to closeAlgorithm, abortAlgorithm set to
+ // abortAlgorithm, highWaterMark set to highWaterMark, and
+ // sizeAlgorithm set to sizeAlgorithm.
+ IgnoredErrorResult rv;
+ stream->SetUpNative(cx, *algorithms,
+ // Step 6. Let highWaterMark be 1.
+ Some(1),
+ // Step 7. Let sizeAlgorithm be an algorithm
+ // that returns 1. (nullptr returns 1, See
+ // WritableStream::Constructor for details)
+ nullptr, rv);
+ if (rv.Failed()) {
+ return Err(rv.StealNSResult());
+ }
+
+ autoClose.release();
+
+ stream->mWorkerRef = std::move(workerRef);
+ stream->mCloseHandler->Open();
+
+ // Step 9: Return stream.
+ return stream;
+}
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(FileSystemWritableFileStream,
+ WritableStream)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(FileSystemWritableFileStream)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FileSystemWritableFileStream,
+ WritableStream)
+ // Per the comment for the FileSystemManager class, don't unlink mManager!
+ if (tmp->IsOpen()) {
+ Unused << tmp->BeginClose();
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FileSystemWritableFileStream,
+ WritableStream)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mManager)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+void FileSystemWritableFileStream::LastRelease() {
+ // We can't call `FileSystemWritableFileStream::Close` here because it may
+ // need to keep FileSystemWritableFileStream object alive which isn't possible
+ // when the object is about to be deleted. There are other mechanisms which
+ // ensure that the object is correctly closed before destruction. For example
+ // the object unlinking and the worker shutdown (we get notified about it via
+ // the callback passed to `StrongWorkerRef`) are used to close the object if
+ // it hasn't been closed yet.
+
+ if (mActor) {
+ PFileSystemWritableFileStreamChild::Send__delete__(mActor);
+ MOZ_ASSERT(!mActor);
+ }
+}
+
+RefPtr<FileSystemWritableFileStream::Command>
+FileSystemWritableFileStream::CreateCommand() {
+ MOZ_ASSERT(!mCommandActive);
+
+ mCommandActive = true;
+
+ return MakeRefPtr<Command>(this);
+}
+
+bool FileSystemWritableFileStream::IsCommandActive() const {
+ return mCommandActive;
+}
+
+void FileSystemWritableFileStream::ClearActor() {
+ MOZ_ASSERT(mActor);
+
+ mActor = nullptr;
+}
+
+bool FileSystemWritableFileStream::IsOpen() const {
+ return mCloseHandler->IsOpen();
+}
+
+bool FileSystemWritableFileStream::IsClosed() const {
+ return mCloseHandler->IsClosed();
+}
+
+RefPtr<BoolPromise> FileSystemWritableFileStream::BeginClose() {
+ using ClosePromise = PFileSystemWritableFileStreamChild::ClosePromise;
+ if (mCloseHandler->TestAndSetClosing()) {
+ Finish()
+ ->Then(mTaskQueue, __func__,
+ [selfHolder = fs::TargetPtrHolder(this)]() mutable {
+ if (selfHolder->mStreamOwner) {
+ selfHolder->mStreamOwner->Close();
+ } else {
+ // If the stream was not deserialized, `mStreamParams` still
+ // contains a pre-opened file descriptor which needs to be
+ // closed here by moving `mStreamParams` to a local variable
+ // (the file descriptor will be closed for real when
+ // `streamParams` goes out of scope).
+
+ mozilla::ipc::RandomAccessStreamParams streamParams(
+ std::move(selfHolder->mStreamParams));
+ }
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue&) {
+ return self->mTaskQueue->BeginShutdown();
+ })
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr(this)](
+ const ShutdownPromise::ResolveOrRejectValue& /* aValue */) {
+ if (!self->mActor) {
+ return ClosePromise::CreateAndResolve(void_t(), __func__);
+ }
+
+ return self->mActor->SendClose();
+ })
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr(this)](
+ const ClosePromise::ResolveOrRejectValue& aValue) {
+ self->mWorkerRef = nullptr;
+ self->mCloseHandler->Close();
+
+ QM_TRY(OkIf(aValue.IsResolve()), QM_VOID);
+ });
+ }
+
+ return mCloseHandler->GetClosePromise();
+}
+
+already_AddRefed<Promise> FileSystemWritableFileStream::Write(
+ JSContext* aCx, JS::Handle<JS::Value> aChunk, ErrorResult& aError) {
+ // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream
+ // Step 3. Let writeAlgorithm be an algorithm which takes a chunk argument
+ // and returns the result of running the write a chunk algorithm with stream
+ // and chunk.
+
+ // https://fs.spec.whatwg.org/#write-a-chunk
+ // Step 1. Let input be the result of converting chunk to a
+ // FileSystemWriteChunkType.
+
+ aError.MightThrowJSException();
+
+ ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams data;
+ if (!data.Init(aCx, aChunk)) {
+ aError.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+
+ // Step 2. Let p be a new promise.
+ RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ if (!IsOpen()) {
+ promise->MaybeRejectWithTypeError("WritableFileStream closed");
+ return promise.forget();
+ }
+
+ // Step 3.3. Let command be input.type if input is a WriteParams, ...
+ if (data.IsWriteParams()) {
+ const WriteParams& params = data.GetAsWriteParams();
+ switch (params.mType) {
+ // Step 3.4. If command is "write":
+ case WriteCommandType::Write: {
+ if (!params.mData.WasPassed()) {
+ promise->MaybeRejectWithSyntaxError("write() requires data");
+ return promise.forget();
+ }
+
+ // Step 3.4.2. If data is undefined, reject p with a TypeError and
+ // abort.
+ if (params.mData.Value().IsNull()) {
+ promise->MaybeRejectWithTypeError("write() of null data");
+ return promise.forget();
+ }
+
+ Maybe<uint64_t> position;
+
+ if (params.mPosition.WasPassed()) {
+ if (params.mPosition.Value().IsNull()) {
+ promise->MaybeRejectWithTypeError("write() with null position");
+ return promise.forget();
+ }
+
+ position = Some(params.mPosition.Value().Value());
+ }
+
+ Write(params.mData.Value().Value(), position, promise);
+ return promise.forget();
+ }
+
+ // Step 3.5. Otherwise, if command is "seek":
+ case WriteCommandType::Seek:
+ if (!params.mPosition.WasPassed()) {
+ promise->MaybeRejectWithSyntaxError("seek() requires a position");
+ return promise.forget();
+ }
+
+ // Step 3.5.1. If chunk.position is undefined, reject p with a
+ // TypeError and abort.
+ if (params.mPosition.Value().IsNull()) {
+ promise->MaybeRejectWithTypeError("seek() with null position");
+ return promise.forget();
+ }
+
+ Seek(params.mPosition.Value().Value(), promise);
+ return promise.forget();
+
+ // Step 3.6. Otherwise, if command is "truncate":
+ case WriteCommandType::Truncate:
+ if (!params.mSize.WasPassed()) {
+ promise->MaybeRejectWithSyntaxError("truncate() requires a size");
+ return promise.forget();
+ }
+
+ // Step 3.6.1. If chunk.size is undefined, reject p with a TypeError
+ // and abort.
+ if (params.mSize.Value().IsNull()) {
+ promise->MaybeRejectWithTypeError("truncate() with null size");
+ return promise.forget();
+ }
+
+ Truncate(params.mSize.Value().Value(), promise);
+ return promise.forget();
+
+ default:
+ MOZ_CRASH("Bad WriteParams value!");
+ }
+ }
+
+ // Step 3.3. ... and "write" otherwise.
+ // Step 3.4. If command is "write":
+ Write(data, Nothing(), promise);
+ return promise.forget();
+}
+
+// WebIDL Boilerplate
+
+JSObject* FileSystemWritableFileStream::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return FileSystemWritableFileStream_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// WebIDL Interface
+
+already_AddRefed<Promise> FileSystemWritableFileStream::Write(
+ const ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams& aData,
+ ErrorResult& aError) {
+ // https://fs.spec.whatwg.org/#dom-filesystemwritablefilestream-write
+ // Step 1. Let writer be the result of getting a writer for this.
+ RefPtr<WritableStreamDefaultWriter> writer = GetWriter(aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ // Step 2. Let result be the result of writing a chunk to writer given data.
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(GetParentObject())) {
+ aError.ThrowUnknownError("Internal error");
+ return nullptr;
+ }
+
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
+
+ JS::Rooted<JS::Value> val(cx);
+ if (!aData.ToJSVal(cx, global, &val)) {
+ aError.ThrowUnknownError("Internal error");
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise = writer->Write(cx, val, aError);
+
+ // Step 3. Release writer.
+ writer->ReleaseLock(cx);
+
+ // Step 4. Return result.
+ return promise.forget();
+}
+
+already_AddRefed<Promise> FileSystemWritableFileStream::Seek(
+ uint64_t aPosition, ErrorResult& aError) {
+ // https://fs.spec.whatwg.org/#dom-filesystemwritablefilestream-seek
+ // Step 1. Let writer be the result of getting a writer for this.
+ RefPtr<WritableStreamDefaultWriter> writer = GetWriter(aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ // Step 2. Let result be the result of writing a chunk to writer given
+ // «[ "type" → "seek", "position" → position ]».
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(GetParentObject())) {
+ aError.ThrowUnknownError("Internal error");
+ return nullptr;
+ }
+
+ JSContext* cx = jsapi.cx();
+
+ RootedDictionary<WriteParams> writeParams(cx);
+ writeParams.mType = WriteCommandType::Seek;
+ writeParams.mPosition.Construct(aPosition);
+
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, writeParams, &val)) {
+ aError.ThrowUnknownError("Internal error");
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise = writer->Write(cx, val, aError);
+
+ // Step 3. Release writer.
+ writer->ReleaseLock(cx);
+
+ // Step 4. Return result.
+ return promise.forget();
+}
+
+already_AddRefed<Promise> FileSystemWritableFileStream::Truncate(
+ uint64_t aSize, ErrorResult& aError) {
+ // https://fs.spec.whatwg.org/#dom-filesystemwritablefilestream-truncate
+ // Step 1. Let writer be the result of getting a writer for this.
+ RefPtr<WritableStreamDefaultWriter> writer = GetWriter(aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ // Step 2. Let result be the result of writing a chunk to writer given
+ // «[ "type" → "truncate", "size" → size ]».
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(GetParentObject())) {
+ aError.ThrowUnknownError("Internal error");
+ return nullptr;
+ }
+
+ JSContext* cx = jsapi.cx();
+
+ RootedDictionary<WriteParams> writeParams(cx);
+ writeParams.mType = WriteCommandType::Truncate;
+ writeParams.mSize.Construct(aSize);
+
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, writeParams, &val)) {
+ aError.ThrowUnknownError("Internal error");
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise = writer->Write(cx, val, aError);
+
+ // Step 3. Release writer.
+ writer->ReleaseLock(cx);
+
+ // Step 4. Return result.
+ return promise.forget();
+}
+
+template <typename T>
+void FileSystemWritableFileStream::Write(const T& aData,
+ const Maybe<uint64_t> aPosition,
+ const RefPtr<Promise>& aPromise) {
+ MOZ_ASSERT(IsOpen());
+
+ auto rejectAndReturn = [&aPromise](const nsresult rv) {
+ if (IsFileNotFoundError(rv)) {
+ aPromise->MaybeRejectWithNotFoundError("File not found");
+ return;
+ }
+ aPromise->MaybeReject(rv);
+ };
+
+ nsCOMPtr<nsIInputStream> inputStream;
+
+ // https://fs.spec.whatwg.org/#write-a-chunk
+ // Step 3.4.6 If data is a BufferSource, let dataBytes be a copy of data.
+ if (aData.IsArrayBuffer() || aData.IsArrayBufferView()) {
+ const auto dataSpan = [&aData]() -> mozilla::Span<uint8_t> {
+ if (aData.IsArrayBuffer()) {
+ const ArrayBuffer& buffer = aData.GetAsArrayBuffer();
+ buffer.ComputeState();
+ return Span{buffer.Data(), buffer.Length()};
+ }
+
+ const ArrayBufferView& buffer = aData.GetAsArrayBufferView();
+ buffer.ComputeState();
+ return Span{buffer.Data(), buffer.Length()};
+ }();
+
+ // Here we copy
+
+ QM_TRY(MOZ_TO_RESULT(NS_NewByteInputStream(getter_AddRefs(inputStream),
+ AsChars(dataSpan),
+ NS_ASSIGNMENT_COPY)),
+ rejectAndReturn);
+
+ WriteImpl(std::move(inputStream), aPosition, aPromise);
+ return;
+ }
+
+ // Step 3.4.7 Otherwise, if data is a Blob ...
+ if (aData.IsBlob()) {
+ Blob& blob = aData.GetAsBlob();
+
+ ErrorResult error;
+ blob.CreateInputStream(getter_AddRefs(inputStream), error);
+ QM_TRY((MOZ_TO_RESULT(!error.Failed()).mapErr([&error](const nsresult rv) {
+ return error.StealNSResult();
+ })),
+ rejectAndReturn);
+
+ WriteImpl(std::move(inputStream), aPosition, aPromise);
+ return;
+ }
+
+ // Step 3.4.8 Otherwise ...
+ MOZ_ASSERT(aData.IsUTF8String());
+
+ // Here we copy
+ nsCString dataString;
+ if (!dataString.Assign(aData.GetAsUTF8String(), mozilla::fallible)) {
+ rejectAndReturn(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ // Input stream takes ownership
+ QM_TRY(MOZ_TO_RESULT(NS_NewCStringInputStream(getter_AddRefs(inputStream),
+ std::move(dataString))),
+ rejectAndReturn);
+
+ WriteImpl(std::move(inputStream), aPosition, aPromise);
+}
+
+void FileSystemWritableFileStream::WriteImpl(
+ nsCOMPtr<nsIInputStream> aInputStream, const Maybe<uint64_t> aPosition,
+ const RefPtr<Promise>& aPromise) {
+ auto command = CreateCommand();
+
+ InvokeAsync(
+ mTaskQueue, __func__,
+ [selfHolder = fs::TargetPtrHolder(this),
+ inputStream = std::move(aInputStream), aPosition]() {
+ QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()),
+ CreateAndRejectInt64Promise);
+
+ if (aPosition.isSome()) {
+ LOG(("%p: Seeking to %" PRIu64, selfHolder->mStreamOwner.get(),
+ aPosition.value()));
+
+ QM_TRY(
+ MOZ_TO_RESULT(selfHolder->mStreamOwner->Seek(aPosition.value())),
+ CreateAndRejectInt64Promise);
+ }
+
+ nsCOMPtr<nsIOutputStream> streamSink =
+ selfHolder->mStreamOwner->OutputStream();
+
+ auto written = std::make_shared<int64_t>(0);
+ auto writingProgress = [written](uint32_t aDelta) {
+ *written += static_cast<int64_t>(aDelta);
+ };
+
+ auto promiseHolder = MakeUnique<MozPromiseHolder<Int64Promise>>();
+ RefPtr<Int64Promise> promise = promiseHolder->Ensure(__func__);
+
+ auto writingCompletion =
+ [written,
+ promiseHolder = std::move(promiseHolder)](nsresult aStatus) {
+ if (NS_SUCCEEDED(aStatus)) {
+ promiseHolder->ResolveIfExists(*written, __func__);
+ return;
+ }
+
+ promiseHolder->RejectIfExists(aStatus, __func__);
+ };
+
+ QM_TRY(MOZ_TO_RESULT(fs::AsyncCopy(
+ inputStream, streamSink, selfHolder->mTaskQueue,
+ nsAsyncCopyMode::NS_ASYNCCOPY_VIA_READSEGMENTS,
+ /* aCloseSource */ true, /* aCloseSink */ false,
+ std::move(writingProgress), std::move(writingCompletion))),
+ CreateAndRejectInt64Promise);
+
+ return promise;
+ })
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [command,
+ aPromise](const Int64Promise::ResolveOrRejectValue& aValue) {
+ if (aValue.IsResolve()) {
+ aPromise->MaybeResolve(aValue.ResolveValue());
+ return;
+ }
+
+ if (IsFileNotFoundError(aValue.RejectValue())) {
+ aPromise->MaybeRejectWithNotFoundError("File not found");
+ } else if (aValue.RejectValue() == NS_ERROR_FILE_NO_DEVICE_SPACE) {
+ aPromise->MaybeRejectWithQuotaExceededError("Quota exceeded");
+ } else {
+ aPromise->MaybeReject(aValue.RejectValue());
+ }
+
+ aPromise->MaybeReject(aValue.RejectValue());
+ });
+}
+
+void FileSystemWritableFileStream::Seek(uint64_t aPosition,
+ const RefPtr<Promise>& aPromise) {
+ MOZ_ASSERT(IsOpen());
+
+ LOG_VERBOSE(("%p: Seeking to %" PRIu64, mStreamOwner.get(), aPosition));
+
+ auto command = CreateCommand();
+
+ InvokeAsync(mTaskQueue, __func__,
+ [selfHolder = fs::TargetPtrHolder(this), aPosition]() mutable {
+ QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()),
+ CreateAndRejectBoolPromise);
+
+ QM_TRY(MOZ_TO_RESULT(selfHolder->mStreamOwner->Seek(aPosition)),
+ CreateAndRejectBoolPromise);
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [command, aPromise](const BoolPromise::ResolveOrRejectValue& aValue) {
+ if (aValue.IsReject()) {
+ auto rv = aValue.RejectValue();
+ if (IsFileNotFoundError(rv)) {
+ aPromise->MaybeRejectWithNotFoundError("File not found");
+ return;
+ }
+ aPromise->MaybeReject(rv);
+ return;
+ }
+ MOZ_ASSERT(aValue.IsResolve());
+ aPromise->MaybeResolveWithUndefined();
+ });
+}
+
+void FileSystemWritableFileStream::Truncate(uint64_t aSize,
+ const RefPtr<Promise>& aPromise) {
+ MOZ_ASSERT(IsOpen());
+
+ auto command = CreateCommand();
+
+ InvokeAsync(mTaskQueue, __func__,
+ [selfHolder = fs::TargetPtrHolder(this), aSize]() mutable {
+ QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()),
+ CreateAndRejectBoolPromise);
+
+ QM_TRY(MOZ_TO_RESULT(selfHolder->mStreamOwner->Truncate(aSize)),
+ CreateAndRejectBoolPromise);
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [command, aPromise](const BoolPromise::ResolveOrRejectValue& aValue) {
+ if (aValue.IsReject()) {
+ aPromise->MaybeReject(aValue.RejectValue());
+ return;
+ }
+
+ aPromise->MaybeResolveWithUndefined();
+ });
+}
+
+nsresult FileSystemWritableFileStream::EnsureStream() {
+ if (!mStreamOwner) {
+ QM_TRY_UNWRAP(MovingNotNull<nsCOMPtr<nsIRandomAccessStream>> stream,
+ DeserializeRandomAccessStream(mStreamParams),
+ NS_ERROR_FAILURE);
+
+ mozilla::ipc::RandomAccessStreamParams streamParams(
+ std::move(mStreamParams));
+
+ mStreamOwner = MakeRefPtr<fs::FileSystemThreadSafeStreamOwner>(
+ this, std::move(stream));
+ }
+
+ return NS_OK;
+}
+
+void FileSystemWritableFileStream::NoteFinishedCommand() {
+ MOZ_ASSERT(mCommandActive);
+
+ mCommandActive = false;
+
+ mFinishPromiseHolder.ResolveIfExists(true, __func__);
+}
+
+RefPtr<BoolPromise> FileSystemWritableFileStream::Finish() {
+ if (!mCommandActive) {
+ return BoolPromise::CreateAndResolve(true, __func__);
+ }
+
+ return mFinishPromiseHolder.Ensure(__func__);
+}
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(
+ WritableFileStreamUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(WritableFileStreamUnderlyingSinkAlgorithms,
+ UnderlyingSinkAlgorithmsBase, mStream)
+
+// Step 3 of
+// https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream
+already_AddRefed<Promise>
+WritableFileStreamUnderlyingSinkAlgorithms::WriteCallback(
+ JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ WritableStreamDefaultController& aController, ErrorResult& aRv) {
+ return mStream->Write(aCx, aChunk, aRv);
+}
+
+// Step 4 of
+// https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream
+already_AddRefed<Promise>
+WritableFileStreamUnderlyingSinkAlgorithms::CloseCallbackImpl(
+ JSContext* aCx, ErrorResult& aRv) {
+ RefPtr<Promise> promise = Promise::Create(mStream->GetParentObject(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if (!mStream->IsOpen()) {
+ promise->MaybeRejectWithTypeError("WritableFileStream closed");
+ return promise.forget();
+ }
+
+ mStream->BeginClose()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise](const BoolPromise::ResolveOrRejectValue& aValue) {
+ // Step 2.3. Return a promise resolved with undefined.
+ if (aValue.IsResolve()) {
+ promise->MaybeResolveWithUndefined();
+ return;
+ }
+ promise->MaybeRejectWithAbortError(
+ "Internal error closing file stream");
+ });
+
+ return promise.forget();
+}
+
+// Step 5 of
+// https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream
+already_AddRefed<Promise>
+WritableFileStreamUnderlyingSinkAlgorithms::AbortCallbackImpl(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& /* aReason */,
+ ErrorResult& aRv) {
+ // https://streams.spec.whatwg.org/#writablestream-set-up
+ // Step 3. Let abortAlgorithmWrapper be an algorithm that runs these steps:
+ // Step 3.3. Return a promise resolved with undefined.
+
+ return CloseCallbackImpl(aCx, aRv);
+}
+
+void WritableFileStreamUnderlyingSinkAlgorithms::ReleaseObjects() {
+ // XXX We shouldn't be calling close here. We should just release the lock.
+ // However, calling close here is not a big issue for now because we don't
+ // write to a temporary file which would atomically replace the real file
+ // during close.
+ if (mStream->IsOpen()) {
+ Unused << mStream->BeginClose();
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fs/api/FileSystemWritableFileStream.h b/dom/fs/api/FileSystemWritableFileStream.h
new file mode 100644
index 0000000000..ac8b8b03f3
--- /dev/null
+++ b/dom/fs/api/FileSystemWritableFileStream.h
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef DOM_FS_FILESYSTEMWRITABLEFILESTREAM_H_
+#define DOM_FS_FILESYSTEMWRITABLEFILESTREAM_H_
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/dom/FlippedOnce.h"
+#include "mozilla/dom/PFileSystemManager.h"
+#include "mozilla/dom/WritableStream.h"
+#include "mozilla/dom/quota/ForwardDecls.h"
+
+class nsIGlobalObject;
+class nsIRandomAccessStream;
+
+namespace mozilla {
+
+template <typename T>
+class Buffer;
+class ErrorResult;
+class TaskQueue;
+
+namespace ipc {
+class RandomAccessStreamParams;
+}
+
+namespace dom {
+
+class ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams;
+class Blob;
+class FileSystemManager;
+class FileSystemWritableFileStreamChild;
+class OwningArrayBufferViewOrArrayBufferOrBlobOrUSVString;
+class Promise;
+class StrongWorkerRef;
+
+namespace fs {
+class FileSystemThreadSafeStreamOwner;
+}
+
+class FileSystemWritableFileStream final : public WritableStream {
+ public:
+ static Result<RefPtr<FileSystemWritableFileStream>, nsresult> Create(
+ const nsCOMPtr<nsIGlobalObject>& aGlobal,
+ RefPtr<FileSystemManager>& aManager,
+ mozilla::ipc::RandomAccessStreamParams&& aStreamParams,
+ RefPtr<FileSystemWritableFileStreamChild> aActor,
+ const fs::FileSystemEntryMetadata& aMetadata);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FileSystemWritableFileStream,
+ WritableStream)
+
+ void LastRelease() override;
+
+ void ClearActor();
+
+ class Command;
+ RefPtr<Command> CreateCommand();
+
+ bool IsCommandActive() const;
+
+ bool IsOpen() const;
+
+ bool IsClosed() const;
+
+ [[nodiscard]] RefPtr<BoolPromise> BeginClose();
+
+ already_AddRefed<Promise> Write(JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ ErrorResult& aError);
+
+ // WebIDL Boilerplate
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL Interface
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Write(
+ const ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams& aData,
+ ErrorResult& aError);
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Seek(uint64_t aPosition,
+ ErrorResult& aError);
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Truncate(uint64_t aSize,
+ ErrorResult& aError);
+
+ private:
+ class CloseHandler;
+
+ FileSystemWritableFileStream(
+ const nsCOMPtr<nsIGlobalObject>& aGlobal,
+ RefPtr<FileSystemManager>& aManager,
+ mozilla::ipc::RandomAccessStreamParams&& aStreamParams,
+ RefPtr<FileSystemWritableFileStreamChild> aActor,
+ already_AddRefed<TaskQueue> aTaskQueue,
+ const fs::FileSystemEntryMetadata& aMetadata);
+
+ virtual ~FileSystemWritableFileStream();
+
+ template <typename T>
+ void Write(const T& aData, const Maybe<uint64_t> aPosition,
+ const RefPtr<Promise>& aPromise);
+
+ void WriteImpl(nsCOMPtr<nsIInputStream> aInputStream,
+ const Maybe<uint64_t> aPosition,
+ const RefPtr<Promise>& aPromise);
+
+ void Seek(uint64_t aPosition, const RefPtr<Promise>& aPromise);
+
+ void Truncate(uint64_t aSize, const RefPtr<Promise>& aPromise);
+
+ nsresult EnsureStream();
+
+ void NoteFinishedCommand();
+
+ [[nodiscard]] RefPtr<BoolPromise> Finish();
+
+ RefPtr<FileSystemManager> mManager;
+
+ RefPtr<FileSystemWritableFileStreamChild> mActor;
+
+ RefPtr<TaskQueue> mTaskQueue;
+
+ RefPtr<fs::FileSystemThreadSafeStreamOwner> mStreamOwner;
+
+ RefPtr<StrongWorkerRef> mWorkerRef;
+
+ mozilla::ipc::RandomAccessStreamParams mStreamParams;
+
+ fs::FileSystemEntryMetadata mMetadata;
+
+ RefPtr<CloseHandler> mCloseHandler;
+
+ MozPromiseHolder<BoolPromise> mFinishPromiseHolder;
+
+ bool mCommandActive;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // DOM_FS_FILESYSTEMWRITABLEFILESTREAM_H_
diff --git a/dom/fs/api/moz.build b/dom/fs/api/moz.build
new file mode 100644
index 0000000000..1b9a64996d
--- /dev/null
+++ b/dom/fs/api/moz.build
@@ -0,0 +1,38 @@
+# -*- 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/.
+
+EXPORTS.mozilla.dom += [
+ "FileSystemDirectoryHandle.h",
+ "FileSystemDirectoryIterator.h",
+ "FileSystemFileHandle.h",
+ "FileSystemHandle.h",
+ "FileSystemManager.h",
+ "FileSystemSyncAccessHandle.h",
+ "FileSystemWritableFileStream.h",
+]
+
+UNIFIED_SOURCES += [
+ "FileSystemDirectoryHandle.cpp",
+ "FileSystemDirectoryIterator.cpp",
+ "FileSystemFileHandle.cpp",
+ "FileSystemHandle.cpp",
+ "FileSystemManager.cpp",
+ "FileSystemSyncAccessHandle.cpp",
+ "FileSystemWritableFileStream.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/dom/fs/child",
+ "/dom/fs/include",
+ "/netwerk/base",
+]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+# Add libFuzzer configuration directives
+include("/tools/fuzzing/libfuzzer-config.mozbuild")