From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- dom/fs/api/FileSystemDirectoryHandle.cpp | 176 +++++ dom/fs/api/FileSystemDirectoryHandle.h | 86 +++ dom/fs/api/FileSystemDirectoryIterator.cpp | 53 ++ dom/fs/api/FileSystemDirectoryIterator.h | 74 ++ dom/fs/api/FileSystemFileHandle.cpp | 122 ++++ dom/fs/api/FileSystemFileHandle.h | 61 ++ dom/fs/api/FileSystemHandle.cpp | 318 ++++++++ dom/fs/api/FileSystemHandle.h | 107 +++ dom/fs/api/FileSystemManager.cpp | 153 ++++ dom/fs/api/FileSystemManager.h | 102 +++ dom/fs/api/FileSystemSyncAccessHandle.cpp | 645 +++++++++++++++++ dom/fs/api/FileSystemSyncAccessHandle.h | 133 ++++ dom/fs/api/FileSystemWritableFileStream.cpp | 1043 +++++++++++++++++++++++++++ dom/fs/api/FileSystemWritableFileStream.h | 158 ++++ dom/fs/api/moz.build | 38 + 15 files changed, 3269 insertions(+) create mode 100644 dom/fs/api/FileSystemDirectoryHandle.cpp create mode 100644 dom/fs/api/FileSystemDirectoryHandle.h create mode 100644 dom/fs/api/FileSystemDirectoryIterator.cpp create mode 100644 dom/fs/api/FileSystemDirectoryIterator.h create mode 100644 dom/fs/api/FileSystemFileHandle.cpp create mode 100644 dom/fs/api/FileSystemFileHandle.h create mode 100644 dom/fs/api/FileSystemHandle.cpp create mode 100644 dom/fs/api/FileSystemHandle.h create mode 100644 dom/fs/api/FileSystemManager.cpp create mode 100644 dom/fs/api/FileSystemManager.h create mode 100644 dom/fs/api/FileSystemSyncAccessHandle.cpp create mode 100644 dom/fs/api/FileSystemSyncAccessHandle.h create mode 100644 dom/fs/api/FileSystemWritableFileStream.cpp create mode 100644 dom/fs/api/FileSystemWritableFileStream.h create mode 100644 dom/fs/api/moz.build (limited to 'dom/fs/api') 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& aManager, + const fs::FileSystemEntryMetadata& aMetadata, + fs::FileSystemRequestHandler* aRequestHandler) + : FileSystemHandle(aGlobal, aManager, aMetadata, aRequestHandler) {} + +FileSystemDirectoryHandle::FileSystemDirectoryHandle( + nsIGlobalObject* aGlobal, RefPtr& 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 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 FileSystemDirectoryHandle::GetNextIterationResult( + FileSystemDirectoryHandle::iterator_t* aIterator, ErrorResult& aError) { + LOG_VERBOSE(("GetNextIterationResult")); + return aIterator->Data().mImpl->Next(mGlobal, mManager, aError); +} + +already_AddRefed FileSystemDirectoryHandle::GetFileHandle( + const nsAString& aName, const FileSystemGetFileOptions& aOptions, + ErrorResult& aError) { + MOZ_ASSERT(!mMetadata.entryId().IsEmpty()); + + RefPtr 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 FileSystemDirectoryHandle::GetDirectoryHandle( + const nsAString& aName, const FileSystemGetDirectoryOptions& aOptions, + ErrorResult& aError) { + MOZ_ASSERT(!mMetadata.entryId().IsEmpty()); + + RefPtr 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 FileSystemDirectoryHandle::RemoveEntry( + const nsAString& aName, const FileSystemRemoveOptions& aOptions, + ErrorResult& aError) { + MOZ_ASSERT(!mMetadata.entryId().IsEmpty()); + + RefPtr 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 FileSystemDirectoryHandle::Resolve( + FileSystemHandle& aPossibleDescendant, ErrorResult& aError) { + RefPtr 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::ReadStructuredClone( + JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader) { + uint32_t kind = static_cast(FileSystemHandleKind::EndGuard_); + + if (!JS_ReadBytes(aReader, reinterpret_cast(&kind), + sizeof(uint32_t))) { + return nullptr; + } + + if (kind != static_cast(FileSystemHandleKind::Directory)) { + return nullptr; + } + + RefPtr 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..1c7af3f52c --- /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(nsIGlobalObject* aGlobal, + RefPtr& aManager, + const fs::FileSystemEntryMetadata& aMetadata, + fs::FileSystemRequestHandler* aRequestHandler); + + FileSystemDirectoryHandle(nsIGlobalObject* aGlobal, + RefPtr& 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 aGivenProto) override; + + // WebIDL Interface + FileSystemHandleKind Kind() const override; + + struct IteratorData { + RefPtr mImpl; + }; + + void InitAsyncIteratorData(IteratorData& aData, + iterator_t::IteratorType aType, + ErrorResult& aError); + + [[nodiscard]] already_AddRefed GetNextIterationResult( + iterator_t* aIterator, ErrorResult& aError); + + already_AddRefed GetFileHandle( + const nsAString& aName, const FileSystemGetFileOptions& aOptions, + ErrorResult& aError); + + already_AddRefed GetDirectoryHandle( + const nsAString& aName, const FileSystemGetDirectoryOptions& aOptions, + ErrorResult& aError); + + already_AddRefed RemoveEntry(const nsAString& aName, + const FileSystemRemoveOptions& aOptions, + ErrorResult& aError); + + already_AddRefed Resolve(FileSystemHandle& aPossibleDescendant, + ErrorResult& aError); + + // [Serializable] + static already_AddRefed 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..663d5ecd57 --- /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& aManager, + RefPtr& aImpl) + : mGlobal(aGlobal), mManager(aManager), mImpl(aImpl) {} + +// WebIDL Boilerplate + +nsIGlobalObject* FileSystemDirectoryIterator::GetParentObject() const { + return mGlobal; +} + +JSObject* FileSystemDirectoryIterator::WrapObject( + JSContext* aCx, JS::Handle aGivenProto) { + return FileSystemDirectoryIterator_Binding::Wrap(aCx, this, aGivenProto); +} + +// WebIDL Interface + +already_AddRefed FileSystemDirectoryIterator::Next( + ErrorResult& aError) { + RefPtr 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..5357106e8b --- /dev/null +++ b/dom/fs/api/FileSystemDirectoryIterator.h @@ -0,0 +1,74 @@ +/* -*- 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: + NS_INLINE_DECL_REFCOUNTING(Impl) + + virtual already_AddRefed Next(nsIGlobalObject* aGlobal, + RefPtr& aManager, + ErrorResult& aError) = 0; + + protected: + virtual ~Impl() = default; + }; + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FileSystemDirectoryIterator) + + explicit FileSystemDirectoryIterator(nsIGlobalObject* aGlobal, + RefPtr& aManager, + RefPtr& aImpl); + + // WebIDL Boilerplate + nsIGlobalObject* GetParentObject() const; + + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + // WebIDL Interface + already_AddRefed Next(ErrorResult& aError); + + protected: + virtual ~FileSystemDirectoryIterator() = default; + + nsCOMPtr mGlobal; + + RefPtr mManager; + + private: + RefPtr 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& aManager, + const fs::FileSystemEntryMetadata& aMetadata, + fs::FileSystemRequestHandler* aRequestHandler) + : FileSystemHandle(aGlobal, aManager, aMetadata, aRequestHandler) {} + +FileSystemFileHandle::FileSystemFileHandle( + nsIGlobalObject* aGlobal, RefPtr& aManager, + const fs::FileSystemEntryMetadata& aMetadata) + : FileSystemFileHandle(aGlobal, aManager, aMetadata, + new fs::FileSystemRequestHandler()) {} + +// WebIDL Boilerplate + +JSObject* FileSystemFileHandle::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return FileSystemFileHandle_Binding::Wrap(aCx, this, aGivenProto); +} + +// WebIDL Interface + +FileSystemHandleKind FileSystemFileHandle::Kind() const { + return FileSystemHandleKind::File; +} + +already_AddRefed FileSystemFileHandle::GetFile(ErrorResult& aError) { + RefPtr 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 FileSystemFileHandle::CreateWritable( + const FileSystemCreateWritableOptions& aOptions, ErrorResult& aError) { + RefPtr 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 FileSystemFileHandle::CreateSyncAccessHandle( + ErrorResult& aError) { + RefPtr 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::ReadStructuredClone(JSContext* aCx, + nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader) { + uint32_t kind = static_cast(FileSystemHandleKind::EndGuard_); + + if (!JS_ReadBytes(aReader, reinterpret_cast(&kind), + sizeof(uint32_t))) { + return nullptr; + } + + if (kind != static_cast(FileSystemHandleKind::File)) { + return nullptr; + } + + RefPtr 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& aManager, + const fs::FileSystemEntryMetadata& aMetadata, + fs::FileSystemRequestHandler* aRequestHandler); + + FileSystemFileHandle(nsIGlobalObject* aGlobal, + RefPtr& 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 aGivenProto) override; + + // WebIDL interface + FileSystemHandleKind Kind() const override; + + already_AddRefed GetFile(ErrorResult& aError); + + already_AddRefed CreateWritable( + const FileSystemCreateWritableOptions& aOptions, ErrorResult& aError); + + already_AddRefed CreateSyncAccessHandle(ErrorResult& aError); + + // [Serializable] + static already_AddRefed 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..d0accdea00 --- /dev/null +++ b/dom/fs/api/FileSystemHandle.cpp @@ -0,0 +1,318 @@ +/* -*- 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(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& 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 aGivenProto) { + return FileSystemHandle_Binding::Wrap(aCx, this, aGivenProto); +} + +// WebIDL Interface + +void FileSystemHandle::GetName(nsAString& aResult) { + aResult = mMetadata.entryName(); +} + +already_AddRefed FileSystemHandle::IsSameEntry( + FileSystemHandle& aOther, ErrorResult& aError) const { + RefPtr 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 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 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 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 FileSystemHandle::Move(const fs::EntryId& aParentId, + const nsAString& aName, + ErrorResult& aError) { + RefPtr promise = Promise::Create(GetParentObject(), aError); + if (aError.Failed()) { + return nullptr; + } + + fs::FileSystemChildMetadata newMetadata; + newMetadata.parentId() = aParentId; + newMetadata.childName() = aName; + if (!aParentId.IsEmpty()) { + mRequestHandler->MoveEntry(mManager, this, &mMetadata, newMetadata, promise, + aError); + } else { + mRequestHandler->RenameEntry(mManager, this, &mMetadata, + newMetadata.childName(), 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( + [newMetadata](JSContext* aCx, JS::Handle aValue, + ErrorResult& aRv, FileSystemHandle* aHandle) { + // XXX Fix entryId! + LOG(("Changing FileSystemHandle name from %s to %s", + NS_ConvertUTF16toUTF8(aHandle->mMetadata.entryName()).get(), + NS_ConvertUTF16toUTF8(newMetadata.childName()).get())); + aHandle->mMetadata.entryName() = newMetadata.childName(); + }, + [](JSContext* aCx, JS::Handle 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::ReadStructuredClone( + JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader) { + LOG_VERBOSE(("Reading File/DirectoryHandle")); + + uint32_t kind = static_cast(FileSystemHandleKind::EndGuard_); + + if (!JS_ReadBytes(aReader, reinterpret_cast(&kind), + sizeof(uint32_t))) { + return nullptr; + } + + if (kind == static_cast(FileSystemHandleKind::Directory)) { + RefPtr result = + FileSystemHandle::ConstructDirectoryHandle(aCx, aGlobal, aReader); + return result.forget(); + } + + if (kind == static_cast(FileSystemHandleKind::File)) { + RefPtr 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(Kind()); + if (NS_WARN_IF(!JS_WriteBytes(aWriter, static_cast(&kind), + sizeof(uint32_t)))) { + return false; + } + + if (NS_WARN_IF(!JS_WriteBytes( + aWriter, static_cast(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 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 = aGlobal->GetStorageManager(); + if (!storageManager) { + return nullptr; + } + + // Note that the actor may not exist or may not be connected yet. + RefPtr fileSystemManager = + storageManager->GetFileSystemManager(); + + RefPtr fsHandle = + new FileSystemFileHandle(aGlobal, fileSystemManager, metadata); + + return fsHandle.forget(); +} + +// static +already_AddRefed +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 = aGlobal->GetStorageManager(); + if (!storageManager) { + return nullptr; + } + + // Note that the actor may not exist or may not be connected yet. + RefPtr fileSystemManager = + storageManager->GetFileSystemManager(); + + RefPtr 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& 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 aGivenProto) override; + + // WebIDL Interface + virtual FileSystemHandleKind Kind() const = 0; + + void GetName(nsAString& aResult); + + already_AddRefed IsSameEntry(FileSystemHandle& aOther, + ErrorResult& aError) const; + + // [Serializable] implementation + static already_AddRefed ReadStructuredClone( + JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader); + + virtual bool WriteStructuredClone(JSContext* aCx, + JSStructuredCloneWriter* aWriter) const; + + already_AddRefed Move(const nsAString& aName, ErrorResult& aError); + + already_AddRefed Move(FileSystemDirectoryHandle& aParent, + ErrorResult& aError); + + already_AddRefed Move(FileSystemDirectoryHandle& aParent, + const nsAString& aName, ErrorResult& aError); + + already_AddRefed 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 ConstructFileHandle( + JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader); + + static already_AddRefed ConstructDirectoryHandle( + JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader); + + nsCOMPtr mGlobal; + + RefPtr mManager; + + // move() can change names/directories + fs::FileSystemEntryMetadata mMetadata; + + const UniquePtr 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 aStorageManager, + RefPtr aBackgroundRequestHandler) + : mGlobal(aGlobal), + mStorageManager(std::move(aStorageManager)), + mBackgroundRequestHandler(std::move(aBackgroundRequestHandler)), + mRequestHandler(new fs::FileSystemRequestHandler()) {} + +FileSystemManager::FileSystemManager(nsIGlobalObject* aGlobal, + RefPtr aStorageManager) + : FileSystemManager(aGlobal, std::move(aStorageManager), + MakeRefPtr()) {} + +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> 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& FileSystemManager::ActorStrongRef() + const { + return mBackgroundRequestHandler->FileSystemManagerChildStrongRef(); +} + +void FileSystemManager::RegisterPromiseRequestHolder( + PromiseRequestHolder* aHolder) { + mPromiseRequestHolders.AppendElement(aHolder); +} + +void FileSystemManager::UnregisterPromiseRequestHolder( + PromiseRequestHolder* aHolder) { + mPromiseRequestHolders.RemoveElement(aHolder); +} + +void FileSystemManager::BeginRequest( + std::function&)>&& aSuccess, + std::function&& 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>(this); + + mBackgroundRequestHandler->CreateFileSystemManagerChild(principalInfo) + ->Then(GetCurrentSerialEventTarget(), __func__, + [self = RefPtr(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 FileSystemManager::GetDirectory(ErrorResult& aError) { + MOZ_ASSERT(mGlobal); + + RefPtr 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 + +#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 +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 + using PromiseRequestHolder = + fs::ManagedMozPromiseRequestHolder; + + FileSystemManager( + nsIGlobalObject* aGlobal, RefPtr aStorageManager, + RefPtr aBackgroundRequestHandler); + + FileSystemManager(nsIGlobalObject* aGlobal, + RefPtr aStorageManager); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(FileSystemManager) + + bool IsShutdown() const { return mShutdown; } + + void Shutdown(); + + const RefPtr& ActorStrongRef() const; + + void RegisterPromiseRequestHolder(PromiseRequestHolder* aHolder); + + void UnregisterPromiseRequestHolder( + PromiseRequestHolder* aHolder); + + void BeginRequest( + std::function&)>&& aSuccess, + std::function&& aFailure); + + already_AddRefed GetDirectory(ErrorResult& aError); + + private: + virtual ~FileSystemManager(); + + nsCOMPtr mGlobal; + + RefPtr mStorageManager; + + const RefPtr mBackgroundRequestHandler; + const UniquePtr mRequestHandler; + + nsTObserverArray*> mPromiseRequestHolders; + + FlippedOnce 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..0c7082905d --- /dev/null +++ b/dom/fs/api/FileSystemSyncAccessHandle.cpp @@ -0,0 +1,645 @@ +/* -*- 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& aManager, + mozilla::ipc::RandomAccessStreamParams&& aStreamParams, + RefPtr aActor, + RefPtr aControlActor, + RefPtr 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, nsresult> +FileSystemSyncAccessHandle::Create( + nsIGlobalObject* aGlobal, RefPtr& aManager, + mozilla::ipc::RandomAccessStreamParams&& aStreamParams, + mozilla::ipc::ManagedEndpoint&& + aAccessHandleChildEndpoint, + mozilla::ipc::Endpoint&& + aAccessHandleControlChildEndpoint, + const fs::FileSystemEntryMetadata& aMetadata) { + WorkerPrivate* const workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + + auto accessHandleChild = MakeRefPtr(); + + QM_TRY(MOZ_TO_RESULT( + aManager->ActorStrongRef()->BindPFileSystemAccessHandleEndpoint( + std::move(aAccessHandleChildEndpoint), accessHandleChild))); + + auto accessHandleControlChild = + MakeRefPtr(); + + aAccessHandleControlChildEndpoint.Bind(accessHandleControlChild, + workerPrivate->ControlEventTarget()); + + QM_TRY_UNWRAP(auto streamTransportService, + MOZ_TO_RESULT_GET_TYPED(nsCOMPtr, + MOZ_SELECT_OVERLOAD(do_GetService), + NS_STREAMTRANSPORTSERVICE_CONTRACTID)); + + RefPtr ioTaskQueue = TaskQueue::Create( + streamTransportService.forget(), "FileSystemSyncAccessHandle"); + QM_TRY(MOZ_TO_RESULT(ioTaskQueue)); + + RefPtr 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 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 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 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(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 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 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 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 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, nsresult> for that ? + int64_t size; + + InvokeAsync(mIOTaskQueue, __func__, + [selfHolder = fs::TargetPtrHolder(this)]() { + QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()), + CreateAndRejectSizePromise); + + nsCOMPtr 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 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 workerRef = mWorkerRef; + + AutoSyncLoopHolder syncLoop(workerRef->Private(), Killing); + + nsCOMPtr 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; + }; + + // 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(at); + QM_TRY(MOZ_TO_RESULT(offset.isValid()), throwAndReturn); + + AutoSyncLoopHolder syncLoop(mWorkerRef->Private(), Canceling); + + nsCOMPtr syncLoopTarget = + syncLoop.GetSerialEventTarget(); + QM_TRY(MOZ_TO_RESULT(syncLoopTarget), [&aRv](nsresult) { + aRv.ThrowInvalidStateError("Worker is shutting down"); + return 0; + }); + + uint64_t totalCount = 0; + + ProcessTypedArraysFixed(aBuffer, [&](const Span aData) { + InvokeAsync( + mIOTaskQueue, __func__, + [selfHolder = fs::TargetPtrHolder(this), aData, + use_offset = aOptions.mAt.WasPassed(), offset, aRead, syncLoopTarget, + &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 inputStream; + nsCOMPtr outputStream; + + if (aRead) { + LOG_VERBOSE(("%p: Reading %zu bytes", selfHolder->mStream.get(), + aData.Length())); + + inputStream = selfHolder->mStream->InputStream(); + outputStream = + FixedBufferOutputStream::Create(AsWritableChars(aData)); + } else { + LOG_VERBOSE(("%p: Writing %zu bytes", selfHolder->mStream.get(), + aData.Length())); + + QM_TRY(MOZ_TO_RESULT(NS_NewByteInputStream( + getter_AddRefs(inputStream), AsChars(aData), + NS_ASSIGNMENT_DEPEND)), + CreateAndRejectBoolPromise); + + outputStream = selfHolder->mStream->OutputStream(); + } + + auto promiseHolder = MakeUnique>(); + RefPtr 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, nsresult> Create( + nsIGlobalObject* aGlobal, RefPtr& aManager, + mozilla::ipc::RandomAccessStreamParams&& aStreamParams, + mozilla::ipc::ManagedEndpoint&& + aAccessHandleChildEndpoint, + mozilla::ipc::Endpoint&& + 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 BeginClose(); + + [[nodiscard]] RefPtr OnClose(); + + // WebIDL Boilerplate + nsIGlobalObject* GetParentObject() const; + + JSObject* WrapObject(JSContext* aCx, + JS::Handle 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& aManager, + mozilla::ipc::RandomAccessStreamParams&& aStreamParams, + RefPtr aActor, + RefPtr aControlActor, + RefPtr aIOTaskQueue, + const fs::FileSystemEntryMetadata& aMetadata); + + virtual ~FileSystemSyncAccessHandle(); + + uint64_t ReadOrWrite( + const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer, + const FileSystemReadWriteOptions& aOptions, const bool aRead, + ErrorResult& aRv); + + nsresult EnsureStream(); + + nsCOMPtr mGlobal; + + RefPtr mManager; + + RefPtr mActor; + + RefPtr mControlActor; + + RefPtr mIOTaskQueue; + + nsCOMPtr mStream; + + RefPtr mWorkerRef; + + MozPromiseHolder 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..ab133b2707 --- /dev/null +++ b/dom/fs/api/FileSystemWritableFileStream.cpp @@ -0,0 +1,1043 @@ +/* -*- 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 { + +CopyableErrorResult RejectWithConvertedErrors(nsresult aRv) { + CopyableErrorResult err; + switch (aRv) { + case NS_ERROR_DOM_FILE_NOT_FOUND_ERR: + [[fallthrough]]; + case NS_ERROR_FILE_NOT_FOUND: + err.ThrowNotFoundError("File not found"); + break; + case NS_ERROR_FILE_NO_DEVICE_SPACE: + err.ThrowQuotaExceededError("Quota exceeded"); + break; + default: + err.Throw(aRv); + } + + return err; +} + +RefPtr ResolvePromise( + const Int64Promise::ResolveOrRejectValue& aValue) { + MOZ_ASSERT(aValue.IsResolve()); + return FileSystemWritableFileStream::WriteDataPromise::CreateAndResolve( + Some(aValue.ResolveValue()), __func__); +} + +RefPtr ResolvePromise( + const BoolPromise::ResolveOrRejectValue& aValue) { + MOZ_ASSERT(aValue.IsResolve()); + return FileSystemWritableFileStream::WriteDataPromise::CreateAndResolve( + Nothing(), __func__); +} + +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 WriteCallback( + JSContext* aCx, JS::Handle aChunk, + WritableStreamDefaultController& aController, ErrorResult& aRv) override; + + already_AddRefed CloseCallbackImpl(JSContext* aCx, + ErrorResult& aRv) override; + + already_AddRefed AbortCallbackImpl( + JSContext* aCx, const Optional>& aReason, + ErrorResult& aRv) override; + + void ReleaseObjects() override; + + private: + ~WritableFileStreamUnderlyingSinkAlgorithms() = default; + + RefPtr mStream; +}; + +} // namespace + +class FileSystemWritableFileStream::Command { + public: + explicit Command(RefPtr aWritableFileStream) + : mWritableFileStream(std::move(aWritableFileStream)) { + MOZ_ASSERT(mWritableFileStream); + } + + NS_INLINE_DECL_REFCOUNTING(FileSystemWritableFileStream::Command) + + private: + ~Command() { mWritableFileStream->NoteFinishedCommand(); } + + RefPtr 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 not open and not closed? + */ + bool IsClosing() const { return State::Closing == 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 SetClosing() { + const bool isOpen = State::Open == mState; + + if (isOpen) { + mState = State::Closing; + } + + return isOpen; + } + + RefPtr GetClosePromise() const { + MOZ_ASSERT(State::Open != mState, + "Please call SetClosing 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 mShutdownBlocker; + + mutable MozPromiseHolder mClosePromiseHolder; + + State mState; +}; + +FileSystemWritableFileStream::FileSystemWritableFileStream( + const nsCOMPtr& aGlobal, + RefPtr& aManager, + mozilla::ipc::RandomAccessStreamParams&& aStreamParams, + RefPtr aActor, + already_AddRefed aTaskQueue, + const fs::FileSystemEntryMetadata& aMetadata) + : WritableStream(aGlobal, HoldDropJSObjectsCaller::Explicit), + mManager(aManager), + mActor(std::move(aActor)), + mTaskQueue(aTaskQueue), + mStreamParams(std::move(aStreamParams)), + mMetadata(std::move(aMetadata)), + mCloseHandler(MakeAndAddRef()), + 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(IsDone()); + + 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, nsresult> +FileSystemWritableFileStream::Create( + const nsCOMPtr& aGlobal, + RefPtr& aManager, + mozilla::ipc::RandomAccessStreamParams&& aStreamParams, + RefPtr aActor, + const fs::FileSystemEntryMetadata& aMetadata) { + MOZ_ASSERT(aGlobal); + + QM_TRY_UNWRAP(auto streamTransportService, + MOZ_TO_RESULT_GET_TYPED(nsCOMPtr, + MOZ_SELECT_OVERLOAD(do_GetService), + NS_STREAMTRANSPORTSERVICE_CONTRACTID)); + + RefPtr 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 stream = + new FileSystemWritableFileStream( + aGlobal, aManager, std::move(aStreamParams), std::move(aActor), + taskQueue.forget(), aMetadata); + + auto autoClose = MakeScopeExit([stream] { + stream->mCloseHandler->Close(); + stream->mActor->SendClose(/* aAbort */ true); + }); + + QM_TRY_UNWRAP( + RefPtr workerRef, + ([stream]() -> Result, nsresult> { + WorkerPrivate* const workerPrivate = GetCurrentThreadWorkerPrivate(); + if (!workerPrivate) { + return RefPtr(); + } + + RefPtr workerRef = StrongWorkerRef::Create( + workerPrivate, "FileSystemWritableFileStream::Create", [stream]() { + if (stream->IsOpen()) { + // We don't need the promise, we just + // begin the closing process. + Unused << stream->BeginAbort(); + } + }); + QM_TRY(MOZ_TO_RESULT(workerRef)); + + return workerRef; + }())); + + // Step 3 - 5 + auto algorithms = + MakeRefPtr(*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->BeginAbort(); + } +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::CreateCommand() { + MOZ_ASSERT(!mCommandActive); + + mCommandActive = true; + + return MakeRefPtr(this); +} + +bool FileSystemWritableFileStream::IsCommandActive() const { + return mCommandActive; +} + +void FileSystemWritableFileStream::ClearActor() { + MOZ_ASSERT(mActor); + + mActor = nullptr; +} + +bool FileSystemWritableFileStream::IsOpen() const { + return mCloseHandler->IsOpen(); +} + +bool FileSystemWritableFileStream::IsFinishing() const { + return mCloseHandler->IsClosing(); +} + +bool FileSystemWritableFileStream::IsDone() const { + return mCloseHandler->IsClosed(); +} + +RefPtr FileSystemWritableFileStream::BeginFinishing( + bool aShouldAbort) { + using ClosePromise = PFileSystemWritableFileStreamChild::ClosePromise; + MOZ_ASSERT(IsOpen()); + + if (mCloseHandler->SetClosing()) { + 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__, + [aShouldAbort, self = RefPtr(this)]( + const ShutdownPromise::ResolveOrRejectValue& /* aValue */) { + if (!self->mActor) { + return ClosePromise::CreateAndResolve(void_t(), __func__); + } + + return self->mActor->SendClose(aShouldAbort); + }) + ->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(); +} + +RefPtr FileSystemWritableFileStream::BeginClose() { + MOZ_ASSERT(IsOpen()); + return BeginFinishing(/* aShouldAbort */ false); +} + +RefPtr FileSystemWritableFileStream::BeginAbort() { + MOZ_ASSERT(IsOpen()); + return BeginFinishing(/* aShouldAbort */ true); +} + +RefPtr FileSystemWritableFileStream::OnDone() { + MOZ_ASSERT(!IsOpen()); + + return mCloseHandler->GetClosePromise(); +} + +already_AddRefed FileSystemWritableFileStream::Write( + JSContext* aCx, JS::Handle 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. + + aError.MightThrowJSException(); + + // https://fs.spec.whatwg.org/#write-a-chunk + // Step 1. Let input be the result of converting chunk to a + // FileSystemWriteChunkType. + + ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams data; + if (!data.Init(aCx, aChunk)) { + aError.StealExceptionFromJSContext(aCx); + return nullptr; + } + + // Step 2. Let p be a new promise. + RefPtr promise = Promise::Create(GetParentObject(), aError); + if (aError.Failed()) { + return nullptr; + } + + RefPtr innerPromise = Promise::Create(GetParentObject(), aError); + if (aError.Failed()) { + return nullptr; + } + + RefPtr command = CreateCommand(); + + // Step 3.3. + Write(data)->Then( + GetCurrentSerialEventTarget(), __func__, + [self = RefPtr{this}, command, + promise](const WriteDataPromise::ResolveOrRejectValue& aValue) { + MOZ_ASSERT(!aValue.IsNothing()); + if (aValue.IsResolve()) { + const Maybe& maybeWritten = aValue.ResolveValue(); + if (maybeWritten.isSome()) { + promise->MaybeResolve(maybeWritten.value()); + return; + } + + promise->MaybeResolveWithUndefined(); + return; + } + + CopyableErrorResult err = aValue.RejectValue(); + + if (self->IsOpen()) { + self->BeginAbort()->Then( + GetCurrentSerialEventTarget(), __func__, + [promise, err = std::move(err)]( + const BoolPromise::ResolveOrRejectValue&) mutable { + // Do not capture command to this context: + // close cannot proceed + promise->MaybeReject(std::move(err)); + }); + } else if (self->IsFinishing()) { + self->OnDone()->Then( + GetCurrentSerialEventTarget(), __func__, + [promise, err = std::move(err)]( + const BoolPromise::ResolveOrRejectValue&) mutable { + // Do not capture command to this context: + // close cannot proceed + promise->MaybeReject(std::move(err)); + }); + + } else { + promise->MaybeReject(std::move(err)); + } + }); + + return promise.forget(); +} + +RefPtr +FileSystemWritableFileStream::Write( + ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams& aData) { + auto rejectWithTypeError = [](const auto& aMessage) { + CopyableErrorResult err; + err.ThrowTypeError(aMessage); + return WriteDataPromise::CreateAndReject(err, __func__); + }; + + auto rejectWithSyntaxError = [](const auto& aMessage) { + CopyableErrorResult err; + err.ThrowSyntaxError(aMessage); + return WriteDataPromise::CreateAndReject(err, __func__); + }; + + if (!IsOpen()) { + return rejectWithTypeError("WritableFileStream closed"); + } + + auto tryResolve = [self = RefPtr{this}](const auto& aValue) + -> RefPtr { + MOZ_ASSERT(self->IsCommandActive()); + + if (aValue.IsResolve()) { + return ResolvePromise(aValue); + } + + MOZ_ASSERT(aValue.IsReject()); + return WriteDataPromise::CreateAndReject( + RejectWithConvertedErrors(aValue.RejectValue()), __func__); + }; + + auto tryResolveInt64 = + [tryResolve](const Int64Promise::ResolveOrRejectValue& aValue) { + return tryResolve(aValue); + }; + + auto tryResolveBool = + [tryResolve](const BoolPromise::ResolveOrRejectValue& aValue) { + return tryResolve(aValue); + }; + + // Step 3.3. Let command be input.type if input is a WriteParams, ... + if (aData.IsWriteParams()) { + const WriteParams& params = aData.GetAsWriteParams(); + switch (params.mType) { + // Step 3.4. If command is "write": + case WriteCommandType::Write: { + if (!params.mData.WasPassed()) { + return rejectWithSyntaxError("write() requires data"); + } + + // Step 3.4.2. If data is undefined, reject p with a TypeError and + // abort. + if (params.mData.Value().IsNull()) { + return rejectWithTypeError("write() of null data"); + } + + Maybe position; + + if (params.mPosition.WasPassed()) { + if (params.mPosition.Value().IsNull()) { + return rejectWithTypeError("write() with null position"); + } + + position = Some(params.mPosition.Value().Value()); + } + + return Write(params.mData.Value().Value(), position) + ->Then(GetCurrentSerialEventTarget(), __func__, + std::move(tryResolveInt64)); + } + + // Step 3.5. Otherwise, if command is "seek": + case WriteCommandType::Seek: + if (!params.mPosition.WasPassed()) { + return rejectWithSyntaxError("seek() requires a position"); + } + + // Step 3.5.1. If chunk.position is undefined, reject p with a + // TypeError and abort. + if (params.mPosition.Value().IsNull()) { + return rejectWithTypeError("seek() with null position"); + } + + return Seek(params.mPosition.Value().Value()) + ->Then(GetCurrentSerialEventTarget(), __func__, + std::move(tryResolveBool)); + + // Step 3.6. Otherwise, if command is "truncate": + case WriteCommandType::Truncate: + if (!params.mSize.WasPassed()) { + return rejectWithSyntaxError("truncate() requires a size"); + } + + // Step 3.6.1. If chunk.size is undefined, reject p with a TypeError + // and abort. + if (params.mSize.Value().IsNull()) { + return rejectWithTypeError("truncate() with null size"); + } + + return Truncate(params.mSize.Value().Value()) + ->Then(GetCurrentSerialEventTarget(), __func__, + std::move(tryResolveBool)); + + default: + MOZ_CRASH("Bad WriteParams value!"); + } + } + + // Step 3.3. ... and "write" otherwise. + // Step 3.4. If command is "write": + return Write(aData, Nothing()) + ->Then(GetCurrentSerialEventTarget(), __func__, + std::move(tryResolveInt64)); +} + +// WebIDL Boilerplate + +JSObject* FileSystemWritableFileStream::WrapObject( + JSContext* aCx, JS::Handle aGivenProto) { + return FileSystemWritableFileStream_Binding::Wrap(aCx, this, aGivenProto); +} + +// WebIDL Interface + +already_AddRefed 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 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 global(cx, JS::CurrentGlobalOrNull(cx)); + + JS::Rooted val(cx); + if (!aData.ToJSVal(cx, global, &val)) { + aError.ThrowUnknownError("Internal error"); + return nullptr; + } + + RefPtr promise = writer->Write(cx, val, aError); + + // Step 3. Release writer. + writer->ReleaseLock(cx); + + // Step 4. Return result. + return promise.forget(); +} + +already_AddRefed 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 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(cx); + writeParams.mType = WriteCommandType::Seek; + writeParams.mPosition.Construct(aPosition); + + JS::Rooted val(cx); + if (!ToJSValue(cx, writeParams, &val)) { + aError.ThrowUnknownError("Internal error"); + return nullptr; + } + + RefPtr promise = writer->Write(cx, val, aError); + + // Step 3. Release writer. + writer->ReleaseLock(cx); + + // Step 4. Return result. + return promise.forget(); +} + +already_AddRefed 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 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(cx); + writeParams.mType = WriteCommandType::Truncate; + writeParams.mSize.Construct(aSize); + + JS::Rooted val(cx); + if (!ToJSValue(cx, writeParams, &val)) { + aError.ThrowUnknownError("Internal error"); + return nullptr; + } + + RefPtr promise = writer->Write(cx, val, aError); + + // Step 3. Release writer. + writer->ReleaseLock(cx); + + // Step 4. Return result. + return promise.forget(); +} + +template +RefPtr FileSystemWritableFileStream::Write( + const T& aData, const Maybe aPosition) { + MOZ_ASSERT(IsOpen()); + + nsCOMPtr 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. + auto vectorFromTypedArray = CreateFromTypedArrayData>(aData); + if (vectorFromTypedArray.isSome()) { + Maybe>& maybeVector = vectorFromTypedArray.ref(); + QM_TRY(MOZ_TO_RESULT(maybeVector.isSome()), CreateAndRejectInt64Promise); + + // Here we copy + + size_t length = maybeVector->length(); + QM_TRY(MOZ_TO_RESULT(NS_NewByteInputStream( + getter_AddRefs(inputStream), + AsChars(Span(maybeVector->extractOrCopyRawBuffer(), length)), + NS_ASSIGNMENT_ADOPT)), + CreateAndRejectInt64Promise); + + return WriteImpl(std::move(inputStream), aPosition); + } + + // 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(); + })), + CreateAndRejectInt64Promise); + + return WriteImpl(std::move(inputStream), aPosition); + } + + // Step 3.4.8 Otherwise ... + MOZ_ASSERT(aData.IsUTF8String()); + + // Here we copy + nsCString dataString; + if (!dataString.Assign(aData.GetAsUTF8String(), mozilla::fallible)) { + return Int64Promise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__); + } + + // Input stream takes ownership + QM_TRY(MOZ_TO_RESULT(NS_NewCStringInputStream(getter_AddRefs(inputStream), + std::move(dataString))), + CreateAndRejectInt64Promise); + + return WriteImpl(std::move(inputStream), aPosition); +} + +RefPtr FileSystemWritableFileStream::WriteImpl( + nsCOMPtr aInputStream, const Maybe aPosition) { + return 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 streamSink = + selfHolder->mStreamOwner->OutputStream(); + + auto written = std::make_shared(0); + auto writingProgress = [written](uint32_t aDelta) { + *written += static_cast(aDelta); + }; + + auto promiseHolder = MakeUnique>(); + RefPtr 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; + }); +} + +RefPtr FileSystemWritableFileStream::Seek(uint64_t aPosition) { + MOZ_ASSERT(IsOpen()); + + LOG_VERBOSE(("%p: Seeking to %" PRIu64, mStreamOwner.get(), aPosition)); + + return 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__); + }); +} + +RefPtr FileSystemWritableFileStream::Truncate(uint64_t aSize) { + MOZ_ASSERT(IsOpen()); + + return 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__); + }); +} + +nsresult FileSystemWritableFileStream::EnsureStream() { + if (!mStreamOwner) { + QM_TRY_UNWRAP(MovingNotNull> stream, + DeserializeRandomAccessStream(mStreamParams), + NS_ERROR_FAILURE); + + mozilla::ipc::RandomAccessStreamParams streamParams( + std::move(mStreamParams)); + + mStreamOwner = MakeRefPtr( + this, std::move(stream)); + } + + return NS_OK; +} + +void FileSystemWritableFileStream::NoteFinishedCommand() { + MOZ_ASSERT(mCommandActive); + + mCommandActive = false; + + mFinishPromiseHolder.ResolveIfExists(true, __func__); +} + +RefPtr 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 +WritableFileStreamUnderlyingSinkAlgorithms::WriteCallback( + JSContext* aCx, JS::Handle 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 +WritableFileStreamUnderlyingSinkAlgorithms::CloseCallbackImpl( + JSContext* aCx, ErrorResult& aRv) { + RefPtr 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 +WritableFileStreamUnderlyingSinkAlgorithms::AbortCallbackImpl( + JSContext* aCx, const Optional>& /* 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. + + RefPtr promise = Promise::Create(mStream->GetParentObject(), aRv); + if (aRv.Failed()) { + return nullptr; + } + + if (!mStream->IsOpen()) { + promise->MaybeRejectWithTypeError("WritableFileStream closed"); + return promise.forget(); + } + + mStream->BeginAbort()->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(); +} + +void WritableFileStreamUnderlyingSinkAlgorithms::ReleaseObjects() { + // WritableStream transitions to errored state whenever a rejected promise is + // returned. At the end of the transition, ReleaseObjects is called. + // Because there is no way to release the locks synchronously, + // we assume this has been initiated before the rejected promise is returned. + MOZ_ASSERT(!mStream->IsOpen()); +} + +} // namespace mozilla::dom diff --git a/dom/fs/api/FileSystemWritableFileStream.h b/dom/fs/api/FileSystemWritableFileStream.h new file mode 100644 index 0000000000..e681840962 --- /dev/null +++ b/dom/fs/api/FileSystemWritableFileStream.h @@ -0,0 +1,158 @@ +/* -*- 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 +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: + using WriteDataPromise = + MozPromise, CopyableErrorResult, /* IsExclusive */ true>; + + static Result, nsresult> Create( + const nsCOMPtr& aGlobal, + RefPtr& aManager, + mozilla::ipc::RandomAccessStreamParams&& aStreamParams, + RefPtr 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 CreateCommand(); + + bool IsCommandActive() const; + + bool IsOpen() const; + + bool IsFinishing() const; + + bool IsDone() const; + + [[nodiscard]] RefPtr BeginAbort(); + + [[nodiscard]] RefPtr BeginClose(); + + [[nodiscard]] RefPtr OnDone(); + + already_AddRefed Write(JSContext* aCx, JS::Handle aChunk, + ErrorResult& aError); + + // WebIDL Boilerplate + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + // WebIDL Interface + MOZ_CAN_RUN_SCRIPT already_AddRefed Write( + const ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams& aData, + ErrorResult& aError); + + MOZ_CAN_RUN_SCRIPT already_AddRefed Seek(uint64_t aPosition, + ErrorResult& aError); + + MOZ_CAN_RUN_SCRIPT already_AddRefed Truncate(uint64_t aSize, + ErrorResult& aError); + + private: + class CloseHandler; + + FileSystemWritableFileStream( + const nsCOMPtr& aGlobal, + RefPtr& aManager, + mozilla::ipc::RandomAccessStreamParams&& aStreamParams, + RefPtr aActor, + already_AddRefed aTaskQueue, + const fs::FileSystemEntryMetadata& aMetadata); + + virtual ~FileSystemWritableFileStream(); + + [[nodiscard]] RefPtr BeginFinishing(bool aShouldAbort); + + RefPtr Write( + ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams& aData); + + template + RefPtr Write(const T& aData, const Maybe aPosition); + + RefPtr WriteImpl(nsCOMPtr aInputStream, + const Maybe aPosition); + + RefPtr Seek(uint64_t aPosition); + + RefPtr Truncate(uint64_t aSize); + + nsresult EnsureStream(); + + void NoteFinishedCommand(); + + [[nodiscard]] RefPtr Finish(); + + RefPtr mManager; + + RefPtr mActor; + + RefPtr mTaskQueue; + + RefPtr mStreamOwner; + + RefPtr mWorkerRef; + + mozilla::ipc::RandomAccessStreamParams mStreamParams; + + fs::FileSystemEntryMetadata mMetadata; + + RefPtr mCloseHandler; + + MozPromiseHolder 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") -- cgit v1.2.3