summaryrefslogtreecommitdiffstats
path: root/dom/fs/api
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /dom/fs/api
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
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.cpp117
-rw-r--r--dom/fs/api/FileSystemManager.h89
-rw-r--r--dom/fs/api/FileSystemSyncAccessHandle.cpp627
-rw-r--r--dom/fs/api/FileSystemSyncAccessHandle.h124
-rw-r--r--dom/fs/api/FileSystemWritableFileStream.cpp706
-rw-r--r--dom/fs/api/FileSystemWritableFileStream.h107
-rw-r--r--dom/fs/api/moz.build34
15 files changed, 2798 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..9c2b10f462
--- /dev/null
+++ b/dom/fs/api/FileSystemManager.cpp
@@ -0,0 +1,117 @@
+/* -*- 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/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();
+
+ if (mBackgroundRequestHandler->FileSystemManagerChildStrongRef()) {
+ // FileSystemAccessHandles prevent shutdown until they are full closed, so
+ // at this point, we should see no open FileSystemAccessHandles.
+ MOZ_ASSERT(mBackgroundRequestHandler->FileSystemManagerChildStrongRef()
+ ->AllSyncAccessHandlesClosed());
+
+ mBackgroundRequestHandler->FileSystemManagerChildStrongRef()
+ ->CloseAllWritableFileStreams();
+ }
+
+ mBackgroundRequestHandler->Shutdown();
+
+ mCreateFileSystemManagerChildPromiseRequestHolder.DisconnectIfExists();
+}
+
+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); });
+
+ mBackgroundRequestHandler->CreateFileSystemManagerChild(principalInfo)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr<FileSystemManager>(this),
+ success = std::move(aSuccess), failure = std::move(aFailure)](
+ const BoolPromise::ResolveOrRejectValue& aValue) {
+ self->mCreateFileSystemManagerChildPromiseRequestHolder.Complete();
+
+ if (aValue.IsResolve()) {
+ success(self->mBackgroundRequestHandler
+ ->FileSystemManagerChildStrongRef());
+ } else {
+ failure(aValue.RejectValue());
+ }
+ })
+ ->Track(mCreateFileSystemManagerChildPromiseRequestHolder);
+}
+
+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..c30d193467
--- /dev/null
+++ b/dom/fs/api/FileSystemManager.h
@@ -0,0 +1,89 @@
+/* -*- 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"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class FileSystemManagerChild;
+class FileSystemBackgroundRequestHandler;
+class StorageManager;
+
+namespace fs {
+class FileSystemRequestHandler;
+} // 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:
+ 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();
+
+ 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;
+
+ MozPromiseRequestHolder<BoolPromise>
+ mCreateFileSystemManagerChildPromiseRequestHolder;
+
+ 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..12d9545332
--- /dev/null
+++ b/dom/fs/api/FileSystemSyncAccessHandle.cpp
@@ -0,0 +1,627 @@
+/* -*- 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/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/FileSystemHandleBinding.h"
+#include "mozilla/dom/FileSystemLog.h"
+#include "mozilla/dom/FileSystemManager.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/TargetPtrHolder.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "mozilla/ipc/RandomAccessStreamUtils.h"
+#include "nsNetCID.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+const uint32_t kStreamCopyBlockSize = 1024 * 1024;
+
+using SizePromise = Int64Promise;
+const auto CreateAndRejectSizePromise = CreateAndRejectInt64Promise;
+
+nsresult AsyncCopy(nsIInputStream* aSource, nsIOutputStream* aSink,
+ nsISerialEventTarget* aIOTarget, const nsAsyncCopyMode aMode,
+ const bool aCloseSource, const bool aCloseSink,
+ std::function<void(uint32_t)>&& aProgressCallback,
+ MoveOnlyFunction<void(nsresult)>&& aCompleteCallback) {
+ struct CallbackClosure {
+ CallbackClosure(std::function<void(uint32_t)>&& aProgressCallback,
+ MoveOnlyFunction<void(nsresult)>&& aCompleteCallback) {
+ mProgressCallbackWrapper = MakeUnique<std::function<void(uint32_t)>>(
+ [progressCallback = std::move(aProgressCallback)](uint32_t count) {
+ progressCallback(count);
+ });
+
+ mCompleteCallbackWrapper = MakeUnique<MoveOnlyFunction<void(nsresult)>>(
+ [completeCallback =
+ std::move(aCompleteCallback)](nsresult rv) mutable {
+ auto callback = std::move(completeCallback);
+ callback(rv);
+ });
+ }
+
+ UniquePtr<std::function<void(uint32_t)>> mProgressCallbackWrapper;
+ UniquePtr<MoveOnlyFunction<void(nsresult)>> mCompleteCallbackWrapper;
+ };
+
+ auto* callbackClosure = new CallbackClosure(std::move(aProgressCallback),
+ std::move(aCompleteCallback));
+
+ QM_TRY(
+ MOZ_TO_RESULT(NS_AsyncCopy(
+ aSource, aSink, aIOTarget, aMode, kStreamCopyBlockSize,
+ [](void* aClosure, nsresult aRv) {
+ auto* callbackClosure = static_cast<CallbackClosure*>(aClosure);
+ (*callbackClosure->mCompleteCallbackWrapper)(aRv);
+ delete callbackClosure;
+ },
+ callbackClosure, aCloseSource, aCloseSink, /* aCopierCtx */ nullptr,
+ [](void* aClosure, uint32_t aCount) {
+ auto* callbackClosure = static_cast<CallbackClosure*>(aClosure);
+ (*callbackClosure->mProgressCallbackWrapper)(aCount);
+ })),
+ [callbackClosure](nsresult rv) {
+ delete callbackClosure;
+ return rv;
+ });
+
+ return NS_OK;
+}
+
+} // namespace
+
+FileSystemSyncAccessHandle::FileSystemSyncAccessHandle(
+ nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+ RefPtr<FileSystemAccessHandleChild> aActor, RefPtr<TaskQueue> aIOTaskQueue,
+ mozilla::ipc::RandomAccessStreamParams&& aStreamParams,
+ const fs::FileSystemEntryMetadata& aMetadata)
+ : mGlobal(aGlobal),
+ mManager(aManager),
+ mActor(std::move(aActor)),
+ 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);
+}
+
+FileSystemSyncAccessHandle::~FileSystemSyncAccessHandle() {
+ MOZ_ASSERT(!mActor);
+ MOZ_ASSERT(IsClosed());
+}
+
+// static
+Result<RefPtr<FileSystemSyncAccessHandle>, nsresult>
+FileSystemSyncAccessHandle::Create(
+ nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+ RefPtr<FileSystemAccessHandleChild> aActor,
+ mozilla::ipc::RandomAccessStreamParams&& aStreamParams,
+ const fs::FileSystemEntryMetadata& aMetadata) {
+ 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(aActor), std::move(ioTaskQueue),
+ std::move(aStreamParams), aMetadata);
+
+ auto autoClose = MakeScopeExit([result] {
+ MOZ_ASSERT(result->mState == State::Initial);
+ result->mState = State::Closed;
+ result->mActor->SendClose();
+ });
+
+ WorkerPrivate* const workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ 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);
+ MOZ_ASSERT(!mActor);
+ }
+}
+
+void FileSystemSyncAccessHandle::ClearActor() {
+ MOZ_ASSERT(mActor);
+
+ mActor = 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)]() {
+ QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()),
+ CreateAndRejectBoolPromise);
+
+ LOG(("%p: Closing", selfHolder->mStream.get()));
+
+ selfHolder->mStream->OutputStream()->Close();
+ selfHolder->mStream = nullptr;
+
+ 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->mActor) {
+ self->mActor->SendClose();
+ }
+
+ 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));
+
+ QM_TRY(MOZ_TO_RESULT(selfHolder->mStream->Seek(
+ nsISeekableStream::NS_SEEK_SET, aSize)),
+ CreateAndRejectBoolPromise);
+
+ QM_TRY(MOZ_TO_RESULT(selfHolder->mStream->SetEOF()),
+ 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()};
+ }();
+
+ // Handle seek before read ('at')
+ const auto at = [&aOptions]() -> uint64_t {
+ if (aOptions.mAt.WasPassed()) {
+ return aOptions.mAt.Value();
+ }
+ // Spec says default for at is 0 (2.6)
+ return 0;
+ }();
+
+ const auto offset = CheckedInt<int64_t>(at);
+ 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, offset, aRead,
+ &totalCount]() {
+ QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()),
+ CreateAndRejectBoolPromise);
+
+ 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(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..ec2ca9bd76
--- /dev/null
+++ b/dom/fs/api/FileSystemSyncAccessHandle.h
@@ -0,0 +1,124 @@
+/* -*- 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;
+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,
+ RefPtr<FileSystemAccessHandleChild> aActor,
+ mozilla::ipc::RandomAccessStreamParams&& aStreamParams,
+ const fs::FileSystemEntryMetadata& aMetadata);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FileSystemSyncAccessHandle)
+
+ void LastRelease();
+
+ void ClearActor();
+
+ 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,
+ RefPtr<FileSystemAccessHandleChild> aActor,
+ RefPtr<TaskQueue> aIOTaskQueue,
+ mozilla::ipc::RandomAccessStreamParams&& aStreamParams,
+ 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<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..69d1181c16
--- /dev/null
+++ b/dom/fs/api/FileSystemWritableFileStream.cpp
@@ -0,0 +1,706 @@
+/* -*- 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 "mozilla/Buffer.h"
+#include "mozilla/ErrorResult.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/WritableStreamDefaultController.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "nsIInputStream.h"
+#include "nsNetUtil.h"
+#include "private/pprio.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+class WritableFileStreamUnderlyingSinkAlgorithms final
+ : public UnderlyingSinkAlgorithmsBase {
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
+ WritableFileStreamUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase)
+
+ explicit WritableFileStreamUnderlyingSinkAlgorithms(
+ FileSystemWritableFileStream& aStream)
+ : mStream(&aStream) {}
+
+ // Streams algorithms
+ void StartCallback(JSContext* aCx,
+ WritableStreamDefaultController& aController,
+ JS::MutableHandle<JS::Value> aRetVal,
+ ErrorResult& aRv) override;
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> WriteCallback(
+ JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ WritableStreamDefaultController& aController, ErrorResult& aRv) override;
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CloseCallback(
+ JSContext* aCx, ErrorResult& aRv) override;
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> AbortCallback(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) override;
+
+ void ReleaseObjects() override;
+
+ private:
+ ~WritableFileStreamUnderlyingSinkAlgorithms() = default;
+
+ RefPtr<FileSystemWritableFileStream> mStream;
+};
+
+/**
+ * TODO: Duplicated from netwerk/cache2/CacheFileIOManager.cpp
+ * Please remove after bug 1286601 is fixed,
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1286601
+ */
+nsresult TruncFile(PRFileDesc* aFD, int64_t aEOF) {
+#if defined(XP_UNIX)
+ if (ftruncate(PR_FileDesc2NativeHandle(aFD), aEOF) != 0) {
+ NS_ERROR("ftruncate failed");
+ return NS_ERROR_FAILURE;
+ }
+#elif defined(XP_WIN)
+ const int64_t currentOffset = PR_Seek64(aFD, 0, PR_SEEK_CUR);
+ if (currentOffset == -1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int64_t cnt = PR_Seek64(aFD, aEOF, PR_SEEK_SET);
+ if (cnt == -1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(aFD))) {
+ NS_ERROR("SetEndOfFile failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (PR_Seek64(aFD, currentOffset, PR_SEEK_SET) == -1) {
+ NS_ERROR("Restoring seek offset failed");
+ return NS_ERROR_FAILURE;
+ }
+
+#else
+ MOZ_ASSERT(false, "Not implemented!");
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+
+ return NS_OK;
+}
+
+} // namespace
+
+FileSystemWritableFileStream::FileSystemWritableFileStream(
+ nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+ RefPtr<FileSystemWritableFileStreamChild> aActor,
+ const ::mozilla::ipc::FileDescriptor& aFileDescriptor,
+ const fs::FileSystemEntryMetadata& aMetadata)
+ : WritableStream(aGlobal, HoldDropJSObjectsCaller::Explicit),
+ mManager(aManager),
+ mActor(std::move(aActor)),
+ mFileDesc(nullptr),
+ mMetadata(aMetadata),
+ mClosed(false) {
+ auto rawFD = aFileDescriptor.ClonePlatformHandle();
+ mFileDesc = PR_ImportFile(PROsfd(rawFD.release()));
+
+ LOG(("Created WritableFileStream %p for fd %p", this, mFileDesc));
+
+ // 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(!mActor);
+ MOZ_ASSERT(mClosed);
+
+ mozilla::DropJSObjects(this);
+}
+
+// https://streams.spec.whatwg.org/#writablestream-set-up
+// * This is fallible because of OOM handling of JSAPI. See bug 1762233.
+// * Consider extracting this as UnderlyingSinkAlgorithmsWrapper if more classes
+// start subclassing WritableStream.
+// For now this skips step 2 - 4 as they are not required here.
+// XXX(krosylight): _BOUNDARY because SetUpWritableStreamDefaultController 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 already_AddRefed<FileSystemWritableFileStream>
+FileSystemWritableFileStream::Create(
+ nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+ RefPtr<FileSystemWritableFileStreamChild> aActor,
+ const ::mozilla::ipc::FileDescriptor& aFileDescriptor,
+ const fs::FileSystemEntryMetadata& aMetadata) {
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(aGlobal)) {
+ return nullptr;
+ }
+ JSContext* cx = jsapi.cx();
+
+ // Step 5. Perform ! InitializeWritableStream(stream).
+ // (Done by the constructor)
+ RefPtr<FileSystemWritableFileStream> stream =
+ new FileSystemWritableFileStream(aGlobal, aManager, std::move(aActor),
+ aFileDescriptor, aMetadata);
+
+ // Step 1 - 3
+ auto algorithms =
+ MakeRefPtr<WritableFileStreamUnderlyingSinkAlgorithms>(*stream);
+
+ // Step 6. Let controller be a new WritableStreamDefaultController.
+ auto controller =
+ MakeRefPtr<WritableStreamDefaultController>(aGlobal, *stream);
+
+ // Step 7. Perform ! SetUpWritableStreamDefaultController(stream, controller,
+ // startAlgorithm, writeAlgorithm, closeAlgorithmWrapper,
+ // abortAlgorithmWrapper, highWaterMark, sizeAlgorithm).
+ IgnoredErrorResult rv;
+ SetUpWritableStreamDefaultController(
+ cx, stream, controller, algorithms,
+ // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream
+ // Step 6. Let highWaterMark be 1.
+ 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 nullptr;
+ }
+ return stream.forget();
+}
+
+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!
+ tmp->Close();
+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() {
+ Close();
+
+ if (mActor) {
+ PFileSystemWritableFileStreamChild::Send__delete__(mActor);
+ MOZ_ASSERT(!mActor);
+ }
+}
+
+void FileSystemWritableFileStream::ClearActor() {
+ MOZ_ASSERT(mActor);
+
+ mActor = nullptr;
+}
+
+void FileSystemWritableFileStream::Close() {
+ // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream
+ // Step 4. Let closeAlgorithm be these steps:
+
+ if (mClosed) {
+ return;
+ }
+
+ LOG(("%p: Closing", mFileDesc));
+
+ mClosed = true;
+
+ PR_Close(mFileDesc);
+ mFileDesc = nullptr;
+
+ if (mActor) {
+ mActor->SendClose();
+ }
+}
+
+already_AddRefed<Promise> FileSystemWritableFileStream::Write(
+ JSContext* aCx, JS::Handle<JS::Value> aChunk, ErrorResult& aError) {
+ MOZ_ASSERT(!mClosed);
+
+ // 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;
+ }
+
+ // 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.
+ {
+ IgnoredErrorResult error;
+ writer->ReleaseLock(cx, error);
+ }
+
+ // 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.
+ {
+ IgnoredErrorResult error;
+ writer->ReleaseLock(cx, error);
+ }
+
+ // 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.
+ {
+ IgnoredErrorResult error;
+ writer->ReleaseLock(cx, error);
+ }
+
+ // Step 4. Return result.
+ return promise.forget();
+}
+
+template <typename T>
+void FileSystemWritableFileStream::Write(const T& aData,
+ const Maybe<uint64_t> aPosition,
+ RefPtr<Promise> aPromise) {
+ MOZ_ASSERT(!mClosed);
+
+ auto rejectAndReturn = [&aPromise](const nsresult rv) {
+ if (rv == NS_ERROR_FILE_NOT_FOUND) {
+ aPromise->MaybeRejectWithNotFoundError("File not found");
+ } else {
+ aPromise->MaybeReject(rv);
+ }
+ };
+
+ // 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]() {
+ if (aData.IsArrayBuffer()) {
+ const ArrayBuffer& buffer = aData.GetAsArrayBuffer();
+ buffer.ComputeState();
+ return Span{buffer.Data(), buffer.Length()};
+ }
+ MOZ_ASSERT(aData.IsArrayBufferView());
+ const ArrayBufferView& buffer = aData.GetAsArrayBufferView();
+ buffer.ComputeState();
+ return Span{buffer.Data(), buffer.Length()};
+ }();
+
+ nsCString dataBuffer;
+ QM_TRY(MOZ_TO_RESULT(dataBuffer.Assign(
+ AsChars(dataSpan).data(), dataSpan.Length(), mozilla::fallible)),
+ rejectAndReturn);
+ QM_TRY_INSPECT(const auto& written, WriteBuffer(dataBuffer, aPosition),
+ rejectAndReturn);
+
+ LOG_VERBOSE(("WritableFileStream: Wrote %" PRId64, written));
+ aPromise->MaybeResolve(written);
+ return;
+ }
+
+ // Step 3.4.7 Otherwise, if data is a Blob ...
+ if (aData.IsBlob()) {
+ Blob& blob = aData.GetAsBlob();
+
+ nsCOMPtr<nsIInputStream> stream;
+ ErrorResult error;
+ blob.CreateInputStream(getter_AddRefs(stream), error);
+ QM_TRY((MOZ_TO_RESULT(!error.Failed()).mapErr([&error](const nsresult rv) {
+ return error.StealNSResult();
+ })),
+ rejectAndReturn);
+
+ QM_TRY_INSPECT(const auto& written,
+ WriteStream(std::move(stream), aPosition), rejectAndReturn);
+
+ LOG_VERBOSE(("WritableFileStream: Wrote %" PRId64, written));
+ aPromise->MaybeResolve(written);
+ return;
+ }
+
+ // Step 3.4.8 Otherwise ...
+ MOZ_ASSERT(aData.IsUTF8String());
+
+ QM_TRY_INSPECT(const auto& written,
+ WriteBuffer(aData.GetAsUTF8String(), aPosition),
+ rejectAndReturn);
+
+ LOG_VERBOSE(("WritableFileStream: Wrote %" PRId64, written));
+ aPromise->MaybeResolve(written);
+}
+
+void FileSystemWritableFileStream::Seek(uint64_t aPosition,
+ RefPtr<Promise> aPromise) {
+ MOZ_ASSERT(!mClosed);
+
+ LOG_VERBOSE(("%p: Seeking to %" PRIu64, mFileDesc, aPosition));
+
+ QM_TRY(SeekPosition(aPosition), [&aPromise](const nsresult rv) {
+ aPromise->MaybeReject(rv);
+ return;
+ });
+
+ aPromise->MaybeResolveWithUndefined();
+}
+
+void FileSystemWritableFileStream::Truncate(uint64_t aSize,
+ RefPtr<Promise> aPromise) {
+ MOZ_ASSERT(!mClosed);
+
+ // truncate filehandle (can extend with 0's)
+ LOG_VERBOSE(("%p: Truncate to %" PRIu64, mFileDesc, aSize));
+ if (NS_WARN_IF(NS_FAILED(TruncFile(mFileDesc, aSize)))) {
+ aPromise->MaybeReject(NS_ErrorAccordingToNSPR());
+ return;
+ }
+
+ // We truncated; per non-normative text in the spec (2.5.3) we should
+ // adjust the cursor position to be within the new file size
+ int64_t where = PR_Seek(mFileDesc, 0, PR_SEEK_CUR);
+ if (where == -1) {
+ aPromise->MaybeReject(NS_ErrorAccordingToNSPR());
+ return;
+ }
+
+ if (where > (int64_t)aSize) {
+ where = PR_Seek(mFileDesc, 0, PR_SEEK_END);
+ if (where == -1) {
+ aPromise->MaybeReject(NS_ErrorAccordingToNSPR());
+ return;
+ }
+ }
+
+ aPromise->MaybeResolveWithUndefined();
+}
+
+Result<uint64_t, nsresult> FileSystemWritableFileStream::WriteBuffer(
+ const nsACString& aBuffer, const Maybe<uint64_t> aPosition) {
+ MOZ_ASSERT(!mClosed);
+
+ const auto checkedLength = CheckedInt<PRInt32>(aBuffer.Length());
+ QM_TRY(MOZ_TO_RESULT(checkedLength.isValid()));
+
+ if (aPosition) {
+ QM_TRY(SeekPosition(*aPosition));
+ }
+
+ return PR_Write(mFileDesc, aBuffer.BeginReading(), checkedLength.value());
+}
+
+Result<uint64_t, nsresult> FileSystemWritableFileStream::WriteStream(
+ nsCOMPtr<nsIInputStream> aStream, const Maybe<uint64_t> aPosition) {
+ MOZ_ASSERT(aStream);
+ MOZ_ASSERT(!mClosed);
+
+ nsCString rawBuffer;
+ QM_TRY(MOZ_TO_RESULT(NS_ReadInputStreamToString(aStream, rawBuffer, -1)));
+ QM_TRY_RETURN(WriteBuffer(rawBuffer, aPosition));
+}
+
+Result<Ok, nsresult> FileSystemWritableFileStream::SeekPosition(
+ uint64_t aPosition) {
+ MOZ_ASSERT(!mClosed);
+
+ const auto checkedPosition = CheckedInt<int64_t>(aPosition);
+ QM_TRY(MOZ_TO_RESULT(checkedPosition.isValid()));
+
+ int64_t cnt = PR_Seek64(mFileDesc, checkedPosition.value(), PR_SEEK_SET);
+ if (cnt == int64_t(-1)) {
+ LOG(("Failed to seek to %" PRIu64 " (errno %d)", aPosition, errno));
+ return Err(NS_ErrorAccordingToNSPR());
+ }
+
+ if (cnt != checkedPosition.value()) {
+ LOG(("Failed to seek to %" PRIu64 " (errno %d), ended up at %" PRId64,
+ aPosition, errno, cnt));
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ return Ok{};
+}
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(
+ WritableFileStreamUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(WritableFileStreamUnderlyingSinkAlgorithms,
+ UnderlyingSinkAlgorithmsBase, mStream)
+
+void WritableFileStreamUnderlyingSinkAlgorithms::StartCallback(
+ JSContext* aCx, WritableStreamDefaultController& aController,
+ JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv) {
+ // https://streams.spec.whatwg.org/#writablestream-set-up
+ // Step 1. Let startAlgorithm be an algorithm that returns undefined.
+ aRetVal.setUndefined();
+}
+
+already_AddRefed<Promise>
+WritableFileStreamUnderlyingSinkAlgorithms::WriteCallback(
+ JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ WritableStreamDefaultController& aController, ErrorResult& aRv) {
+ return mStream->Write(aCx, aChunk, aRv);
+}
+
+already_AddRefed<Promise>
+WritableFileStreamUnderlyingSinkAlgorithms::CloseCallback(JSContext* aCx,
+ ErrorResult& aRv) {
+ // https://streams.spec.whatwg.org/#writablestream-set-up
+ // Step 2. Let closeAlgorithmWrapper be an algorithm that runs these steps:
+
+ RefPtr<Promise> promise = Promise::Create(mStream->GetParentObject(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if (mStream->IsClosed()) {
+ promise->MaybeRejectWithTypeError("WritableFileStream closed");
+ return promise.forget();
+ }
+
+ // XXX The close should be async. For now we have to always fallback to the
+ // Step 2.3 below.
+ mStream->Close();
+
+ // Step 2.3. Return a promise resolved with undefined.
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+}
+
+already_AddRefed<Promise>
+WritableFileStreamUnderlyingSinkAlgorithms::AbortCallback(
+ 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:
+
+ // XXX The close or rather a dedicated abort should be async. For now we have
+ // to always fall back to the Step 3.3 below.
+ mStream->Close();
+
+ // Step 3.3. Return a promise resolved with undefined.
+ return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(), 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.
+ mStream->Close();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fs/api/FileSystemWritableFileStream.h b/dom/fs/api/FileSystemWritableFileStream.h
new file mode 100644
index 0000000000..630dd9184f
--- /dev/null
+++ b/dom/fs/api/FileSystemWritableFileStream.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_FILESYSTEMWRITABLEFILESTREAM_H_
+#define DOM_FS_FILESYSTEMWRITABLEFILESTREAM_H_
+
+#include "mozilla/dom/PFileSystemManager.h"
+#include "mozilla/dom/WritableStream.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+
+template <typename T>
+class Buffer;
+class ErrorResult;
+
+namespace dom {
+
+class ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams;
+class Blob;
+class FileSystemManager;
+class FileSystemWritableFileStreamChild;
+class OwningArrayBufferViewOrArrayBufferOrBlobOrUSVString;
+class Promise;
+
+class FileSystemWritableFileStream final : public WritableStream {
+ public:
+ static already_AddRefed<FileSystemWritableFileStream> Create(
+ nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+ RefPtr<FileSystemWritableFileStreamChild> aActor,
+ const ::mozilla::ipc::FileDescriptor& aFileDescriptor,
+ const fs::FileSystemEntryMetadata& aMetadata);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FileSystemWritableFileStream,
+ WritableStream)
+
+ void LastRelease() override;
+
+ void ClearActor();
+
+ bool IsClosed() const { return mClosed; }
+
+ void Close();
+
+ 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:
+ FileSystemWritableFileStream(
+ nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+ RefPtr<FileSystemWritableFileStreamChild> aActor,
+ const ::mozilla::ipc::FileDescriptor& aFileDescriptor,
+ const fs::FileSystemEntryMetadata& aMetadata);
+
+ virtual ~FileSystemWritableFileStream();
+
+ template <typename T>
+ void Write(const T& aData, const Maybe<uint64_t> aPosition,
+ RefPtr<Promise> aPromise);
+
+ void Seek(uint64_t aPosition, RefPtr<Promise> aPromise);
+
+ void Truncate(uint64_t aSize, RefPtr<Promise> aPromise);
+
+ Result<uint64_t, nsresult> WriteBuffer(const nsACString& aBuffer,
+ const Maybe<uint64_t> aPosition);
+
+ Result<uint64_t, nsresult> WriteStream(nsCOMPtr<nsIInputStream> aStream,
+ const Maybe<uint64_t> aPosition);
+
+ Result<Ok, nsresult> SeekPosition(uint64_t aPosition);
+
+ RefPtr<FileSystemManager> mManager;
+
+ RefPtr<FileSystemWritableFileStreamChild> mActor;
+
+ PRFileDesc* mFileDesc;
+
+ fs::FileSystemEntryMetadata mMetadata;
+
+ bool mClosed;
+};
+
+} // 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..09c0bc450a
--- /dev/null
+++ b/dom/fs/api/moz.build
@@ -0,0 +1,34 @@
+# -*- 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",
+]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")