diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /dom/fs/api | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/fs/api')
-rw-r--r-- | dom/fs/api/FileSystemDirectoryHandle.cpp | 176 | ||||
-rw-r--r-- | dom/fs/api/FileSystemDirectoryHandle.h | 86 | ||||
-rw-r--r-- | dom/fs/api/FileSystemDirectoryIterator.cpp | 53 | ||||
-rw-r--r-- | dom/fs/api/FileSystemDirectoryIterator.h | 70 | ||||
-rw-r--r-- | dom/fs/api/FileSystemFileHandle.cpp | 122 | ||||
-rw-r--r-- | dom/fs/api/FileSystemFileHandle.h | 61 | ||||
-rw-r--r-- | dom/fs/api/FileSystemHandle.cpp | 319 | ||||
-rw-r--r-- | dom/fs/api/FileSystemHandle.h | 107 | ||||
-rw-r--r-- | dom/fs/api/FileSystemManager.cpp | 117 | ||||
-rw-r--r-- | dom/fs/api/FileSystemManager.h | 89 | ||||
-rw-r--r-- | dom/fs/api/FileSystemSyncAccessHandle.cpp | 627 | ||||
-rw-r--r-- | dom/fs/api/FileSystemSyncAccessHandle.h | 124 | ||||
-rw-r--r-- | dom/fs/api/FileSystemWritableFileStream.cpp | 706 | ||||
-rw-r--r-- | dom/fs/api/FileSystemWritableFileStream.h | 107 | ||||
-rw-r--r-- | dom/fs/api/moz.build | 34 |
15 files changed, 2798 insertions, 0 deletions
diff --git a/dom/fs/api/FileSystemDirectoryHandle.cpp b/dom/fs/api/FileSystemDirectoryHandle.cpp new file mode 100644 index 0000000000..3529f29e78 --- /dev/null +++ b/dom/fs/api/FileSystemDirectoryHandle.cpp @@ -0,0 +1,176 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileSystemDirectoryHandle.h" + +#include "FileSystemDirectoryIteratorFactory.h" +#include "fs/FileSystemRequestHandler.h" +#include "js/StructuredClone.h" +#include "js/TypeDecls.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/FileSystemDirectoryHandleBinding.h" +#include "mozilla/dom/FileSystemHandleBinding.h" +#include "mozilla/dom/FileSystemLog.h" +#include "mozilla/dom/FileSystemManager.h" +#include "mozilla/dom/PFileSystemManager.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/StorageManager.h" +#include "nsJSUtils.h" + +namespace mozilla::dom { + +FileSystemDirectoryHandle::FileSystemDirectoryHandle( + nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + const fs::FileSystemEntryMetadata& aMetadata, + fs::FileSystemRequestHandler* aRequestHandler) + : FileSystemHandle(aGlobal, aManager, aMetadata, aRequestHandler) {} + +FileSystemDirectoryHandle::FileSystemDirectoryHandle( + nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + const fs::FileSystemEntryMetadata& aMetadata) + : FileSystemDirectoryHandle(aGlobal, aManager, aMetadata, + new fs::FileSystemRequestHandler()) {} + +NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(FileSystemDirectoryHandle, + FileSystemHandle) +NS_IMPL_CYCLE_COLLECTION_INHERITED(FileSystemDirectoryHandle, FileSystemHandle) + +// WebIDL Boilerplate + +JSObject* FileSystemDirectoryHandle::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return FileSystemDirectoryHandle_Binding::Wrap(aCx, this, aGivenProto); +} + +// WebIDL Interface + +FileSystemHandleKind FileSystemDirectoryHandle::Kind() const { + return FileSystemHandleKind::Directory; +} + +void FileSystemDirectoryHandle::InitAsyncIteratorData( + IteratorData& aData, iterator_t::IteratorType aType, ErrorResult& aError) { + aData.mImpl = + fs::FileSystemDirectoryIteratorFactory::Create(mMetadata, aType); +} + +already_AddRefed<Promise> FileSystemDirectoryHandle::GetNextIterationResult( + FileSystemDirectoryHandle::iterator_t* aIterator, ErrorResult& aError) { + LOG_VERBOSE(("GetNextIterationResult")); + return aIterator->Data().mImpl->Next(mGlobal, mManager, aError); +} + +already_AddRefed<Promise> FileSystemDirectoryHandle::GetFileHandle( + const nsAString& aName, const FileSystemGetFileOptions& aOptions, + ErrorResult& aError) { + MOZ_ASSERT(!mMetadata.entryId().IsEmpty()); + + RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError); + if (aError.Failed()) { + return nullptr; + } + + fs::Name name(aName); + fs::FileSystemChildMetadata metadata(mMetadata.entryId(), name); + mRequestHandler->GetFileHandle(mManager, metadata, aOptions.mCreate, promise, + aError); + if (aError.Failed()) { + return nullptr; + } + + return promise.forget(); +} + +already_AddRefed<Promise> FileSystemDirectoryHandle::GetDirectoryHandle( + const nsAString& aName, const FileSystemGetDirectoryOptions& aOptions, + ErrorResult& aError) { + MOZ_ASSERT(!mMetadata.entryId().IsEmpty()); + + RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError); + if (aError.Failed()) { + return nullptr; + } + + fs::Name name(aName); + fs::FileSystemChildMetadata metadata(mMetadata.entryId(), name); + mRequestHandler->GetDirectoryHandle(mManager, metadata, aOptions.mCreate, + promise, aError); + if (aError.Failed()) { + return nullptr; + } + + return promise.forget(); +} + +already_AddRefed<Promise> FileSystemDirectoryHandle::RemoveEntry( + const nsAString& aName, const FileSystemRemoveOptions& aOptions, + ErrorResult& aError) { + MOZ_ASSERT(!mMetadata.entryId().IsEmpty()); + + RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError); + if (aError.Failed()) { + return nullptr; + } + + fs::Name name(aName); + fs::FileSystemChildMetadata metadata(mMetadata.entryId(), name); + + mRequestHandler->RemoveEntry(mManager, metadata, aOptions.mRecursive, promise, + aError); + if (aError.Failed()) { + return nullptr; + } + + return promise.forget(); +} + +already_AddRefed<Promise> FileSystemDirectoryHandle::Resolve( + FileSystemHandle& aPossibleDescendant, ErrorResult& aError) { + RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError); + if (aError.Failed()) { + return nullptr; + } + + LOG_VERBOSE(("Resolve")); + + fs::FileSystemEntryPair pair(mMetadata.entryId(), + aPossibleDescendant.GetId()); + mRequestHandler->Resolve(mManager, pair, promise, aError); + if (aError.Failed()) { + return nullptr; + } + + return promise.forget(); +} + +// [Serializable] implementation + +// static +already_AddRefed<FileSystemDirectoryHandle> +FileSystemDirectoryHandle::ReadStructuredClone( + JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader) { + uint32_t kind = static_cast<uint32_t>(FileSystemHandleKind::EndGuard_); + + if (!JS_ReadBytes(aReader, reinterpret_cast<void*>(&kind), + sizeof(uint32_t))) { + return nullptr; + } + + if (kind != static_cast<uint32_t>(FileSystemHandleKind::Directory)) { + return nullptr; + } + + RefPtr<FileSystemDirectoryHandle> result = + FileSystemHandle::ConstructDirectoryHandle(aCx, aGlobal, aReader); + if (!result) { + return nullptr; + } + + return result.forget(); +} + +} // namespace mozilla::dom diff --git a/dom/fs/api/FileSystemDirectoryHandle.h b/dom/fs/api/FileSystemDirectoryHandle.h new file mode 100644 index 0000000000..36ac7455c5 --- /dev/null +++ b/dom/fs/api/FileSystemDirectoryHandle.h @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_FS_FILESYSTEMDIRECTORYHANDLE_H_ +#define DOM_FS_FILESYSTEMDIRECTORYHANDLE_H_ + +#include "mozilla/dom/FileSystemDirectoryIterator.h" +#include "mozilla/dom/FileSystemHandle.h" +#include "mozilla/dom/IterableIterator.h" + +namespace mozilla { + +class ErrorResult; + +namespace dom { + +struct FileSystemGetFileOptions; +struct FileSystemGetDirectoryOptions; +struct FileSystemRemoveOptions; + +class FileSystemDirectoryHandle final : public FileSystemHandle { + public: + using iterator_t = AsyncIterableIterator<FileSystemDirectoryHandle>; + + FileSystemDirectoryHandle(nsIGlobalObject* aGlobal, + RefPtr<FileSystemManager>& aManager, + const fs::FileSystemEntryMetadata& aMetadata, + fs::FileSystemRequestHandler* aRequestHandler); + + FileSystemDirectoryHandle(nsIGlobalObject* aGlobal, + RefPtr<FileSystemManager>& aManager, + const fs::FileSystemEntryMetadata& aMetadata); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FileSystemDirectoryHandle, + FileSystemHandle) + + // WebIDL Boilerplate + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // WebIDL Interface + FileSystemHandleKind Kind() const override; + + struct IteratorData { + UniquePtr<FileSystemDirectoryIterator::Impl> mImpl; + }; + + void InitAsyncIteratorData(IteratorData& aData, + iterator_t::IteratorType aType, + ErrorResult& aError); + + [[nodiscard]] already_AddRefed<Promise> GetNextIterationResult( + iterator_t* aIterator, ErrorResult& aError); + + already_AddRefed<Promise> GetFileHandle( + const nsAString& aName, const FileSystemGetFileOptions& aOptions, + ErrorResult& aError); + + already_AddRefed<Promise> GetDirectoryHandle( + const nsAString& aName, const FileSystemGetDirectoryOptions& aOptions, + ErrorResult& aError); + + already_AddRefed<Promise> RemoveEntry(const nsAString& aName, + const FileSystemRemoveOptions& aOptions, + ErrorResult& aError); + + already_AddRefed<Promise> Resolve(FileSystemHandle& aPossibleDescendant, + ErrorResult& aError); + + // [Serializable] + static already_AddRefed<FileSystemDirectoryHandle> ReadStructuredClone( + JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader); + + private: + ~FileSystemDirectoryHandle() = default; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_FS_FILESYSTEMDIRECTORYHANDLE_H_ diff --git a/dom/fs/api/FileSystemDirectoryIterator.cpp b/dom/fs/api/FileSystemDirectoryIterator.cpp new file mode 100644 index 0000000000..45e7511e4a --- /dev/null +++ b/dom/fs/api/FileSystemDirectoryIterator.cpp @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileSystemDirectoryIterator.h" + +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/FileSystemDirectoryIteratorBinding.h" +#include "mozilla/dom/FileSystemManager.h" +#include "mozilla/dom/Promise.h" + +namespace mozilla::dom { + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileSystemDirectoryIterator) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END +NS_IMPL_CYCLE_COLLECTING_ADDREF(FileSystemDirectoryIterator); +NS_IMPL_CYCLE_COLLECTING_RELEASE(FileSystemDirectoryIterator); +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FileSystemDirectoryIterator, mGlobal); + +FileSystemDirectoryIterator::FileSystemDirectoryIterator( + nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + UniquePtr<Impl> aImpl) + : mGlobal(aGlobal), mManager(aManager), mImpl(std::move(aImpl)) {} + +// WebIDL Boilerplate + +nsIGlobalObject* FileSystemDirectoryIterator::GetParentObject() const { + return mGlobal; +} + +JSObject* FileSystemDirectoryIterator::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return FileSystemDirectoryIterator_Binding::Wrap(aCx, this, aGivenProto); +} + +// WebIDL Interface + +already_AddRefed<Promise> FileSystemDirectoryIterator::Next( + ErrorResult& aError) { + RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError); + if (aError.Failed()) { + return nullptr; + } + + MOZ_ASSERT(mImpl); + return mImpl->Next(mGlobal, mManager, aError); +} + +} // namespace mozilla::dom diff --git a/dom/fs/api/FileSystemDirectoryIterator.h b/dom/fs/api/FileSystemDirectoryIterator.h new file mode 100644 index 0000000000..d761ae6d2b --- /dev/null +++ b/dom/fs/api/FileSystemDirectoryIterator.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_FS_FILESYSTEMDIRECTORYITERATOR_H_ +#define DOM_FS_FILESYSTEMDIRECTORYITERATOR_H_ + +#include "mozilla/dom/IterableIterator.h" +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsWrapperCache.h" + +class nsIGlobalObject; + +namespace mozilla { + +class ErrorResult; + +namespace dom { + +class FileSystemManager; +class IterableIteratorBase; +class Promise; + +// XXX This class isn't used to support iteration anymore. `Impl` should be +// extracted elsewhere and `FileSystemDirectoryIterator` should be removed +// completely +class FileSystemDirectoryIterator : public nsISupports, public nsWrapperCache { + public: + class Impl { + public: + virtual already_AddRefed<Promise> Next(nsIGlobalObject* aGlobal, + RefPtr<FileSystemManager>& aManager, + ErrorResult& aError) = 0; + virtual ~Impl() = default; + }; + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FileSystemDirectoryIterator) + + explicit FileSystemDirectoryIterator(nsIGlobalObject* aGlobal, + RefPtr<FileSystemManager>& aManager, + UniquePtr<Impl> aImpl); + + // WebIDL Boilerplate + nsIGlobalObject* GetParentObject() const; + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // WebIDL Interface + already_AddRefed<Promise> Next(ErrorResult& aError); + + protected: + virtual ~FileSystemDirectoryIterator() = default; + + nsCOMPtr<nsIGlobalObject> mGlobal; + + RefPtr<FileSystemManager> mManager; + + private: + UniquePtr<Impl> mImpl; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_FS_FILESYSTEMDIRECTORYITERATOR_H_ diff --git a/dom/fs/api/FileSystemFileHandle.cpp b/dom/fs/api/FileSystemFileHandle.cpp new file mode 100644 index 0000000000..4d8306857f --- /dev/null +++ b/dom/fs/api/FileSystemFileHandle.cpp @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileSystemFileHandle.h" + +#include "fs/FileSystemRequestHandler.h" +#include "js/StructuredClone.h" +#include "js/TypeDecls.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/FileSystemFileHandleBinding.h" +#include "mozilla/dom/FileSystemHandleBinding.h" +#include "mozilla/dom/FileSystemManager.h" +#include "mozilla/dom/PFileSystemManager.h" +#include "mozilla/dom/Promise.h" + +namespace mozilla::dom { + +NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(FileSystemFileHandle, + FileSystemHandle) +NS_IMPL_CYCLE_COLLECTION_INHERITED(FileSystemFileHandle, FileSystemHandle) + +FileSystemFileHandle::FileSystemFileHandle( + nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + const fs::FileSystemEntryMetadata& aMetadata, + fs::FileSystemRequestHandler* aRequestHandler) + : FileSystemHandle(aGlobal, aManager, aMetadata, aRequestHandler) {} + +FileSystemFileHandle::FileSystemFileHandle( + nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + const fs::FileSystemEntryMetadata& aMetadata) + : FileSystemFileHandle(aGlobal, aManager, aMetadata, + new fs::FileSystemRequestHandler()) {} + +// WebIDL Boilerplate + +JSObject* FileSystemFileHandle::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return FileSystemFileHandle_Binding::Wrap(aCx, this, aGivenProto); +} + +// WebIDL Interface + +FileSystemHandleKind FileSystemFileHandle::Kind() const { + return FileSystemHandleKind::File; +} + +already_AddRefed<Promise> FileSystemFileHandle::GetFile(ErrorResult& aError) { + RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError); + if (aError.Failed()) { + return nullptr; + } + + mRequestHandler->GetFile(mManager, mMetadata, promise, aError); + if (aError.Failed()) { + return nullptr; + } + + return promise.forget(); +} + +already_AddRefed<Promise> FileSystemFileHandle::CreateWritable( + const FileSystemCreateWritableOptions& aOptions, ErrorResult& aError) { + RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError); + if (aError.Failed()) { + return nullptr; + } + + mRequestHandler->GetWritable(mManager, mMetadata, aOptions.mKeepExistingData, + promise, aError); + if (aError.Failed()) { + return nullptr; + } + + return promise.forget(); +} + +already_AddRefed<Promise> FileSystemFileHandle::CreateSyncAccessHandle( + ErrorResult& aError) { + RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError); + if (aError.Failed()) { + return nullptr; + } + + mRequestHandler->GetAccessHandle(mManager, mMetadata, promise, aError); + if (aError.Failed()) { + return nullptr; + } + + return promise.forget(); +} + +// [Serializable] implementation + +// static +already_AddRefed<FileSystemFileHandle> +FileSystemFileHandle::ReadStructuredClone(JSContext* aCx, + nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader) { + uint32_t kind = static_cast<uint32_t>(FileSystemHandleKind::EndGuard_); + + if (!JS_ReadBytes(aReader, reinterpret_cast<void*>(&kind), + sizeof(uint32_t))) { + return nullptr; + } + + if (kind != static_cast<uint32_t>(FileSystemHandleKind::File)) { + return nullptr; + } + + RefPtr<FileSystemFileHandle> result = + FileSystemHandle::ConstructFileHandle(aCx, aGlobal, aReader); + if (!result) { + return nullptr; + } + + return result.forget(); +} + +} // namespace mozilla::dom diff --git a/dom/fs/api/FileSystemFileHandle.h b/dom/fs/api/FileSystemFileHandle.h new file mode 100644 index 0000000000..c0606c85a6 --- /dev/null +++ b/dom/fs/api/FileSystemFileHandle.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_FS_FILESYSTEMFILEHANDLE_H_ +#define DOM_FS_FILESYSTEMFILEHANDLE_H_ + +#include "mozilla/dom/FileSystemHandle.h" + +namespace mozilla { + +class ErrorResult; + +namespace dom { + +struct FileSystemCreateWritableOptions; + +class FileSystemFileHandle final : public FileSystemHandle { + public: + FileSystemFileHandle(nsIGlobalObject* aGlobal, + RefPtr<FileSystemManager>& aManager, + const fs::FileSystemEntryMetadata& aMetadata, + fs::FileSystemRequestHandler* aRequestHandler); + + FileSystemFileHandle(nsIGlobalObject* aGlobal, + RefPtr<FileSystemManager>& aManager, + const fs::FileSystemEntryMetadata& aMetadata); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FileSystemFileHandle, + FileSystemHandle) + + // WebIDL Boilerplate + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // WebIDL interface + FileSystemHandleKind Kind() const override; + + already_AddRefed<Promise> GetFile(ErrorResult& aError); + + already_AddRefed<Promise> CreateWritable( + const FileSystemCreateWritableOptions& aOptions, ErrorResult& aError); + + already_AddRefed<Promise> CreateSyncAccessHandle(ErrorResult& aError); + + // [Serializable] + static already_AddRefed<FileSystemFileHandle> ReadStructuredClone( + JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader); + + private: + ~FileSystemFileHandle() = default; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_FS_FILESYSTEMFILEHANDLE_H_ diff --git a/dom/fs/api/FileSystemHandle.cpp b/dom/fs/api/FileSystemHandle.cpp new file mode 100644 index 0000000000..beca83d0bf --- /dev/null +++ b/dom/fs/api/FileSystemHandle.cpp @@ -0,0 +1,319 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileSystemHandle.h" + +#include "FileSystemDirectoryHandle.h" +#include "FileSystemFileHandle.h" +#include "fs/FileSystemRequestHandler.h" +#include "js/StructuredClone.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/FileSystemHandleBinding.h" +#include "mozilla/dom/FileSystemLog.h" +#include "mozilla/dom/FileSystemManager.h" +#include "mozilla/dom/Promise-inl.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/StorageManager.h" +#include "mozilla/dom/StructuredCloneHolder.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "nsJSPrincipals.h" +#include "nsString.h" +#include "prio.h" +#include "private/pprio.h" +#include "xpcpublic.h" + +namespace mozilla::dom { + +namespace { + +bool ConstructHandleMetadata(JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader, + const bool aDirectory, + fs::FileSystemEntryMetadata& aMetadata) { + using namespace mozilla::dom::fs; + + EntryId entryId; + if (!entryId.SetLength(32u, fallible)) { + return false; + } + + if (!JS_ReadBytes(aReader, static_cast<void*>(entryId.BeginWriting()), 32u)) { + return false; + } + + Name name; + if (!StructuredCloneHolder::ReadString(aReader, name)) { + return false; + } + + mozilla::ipc::PrincipalInfo storageKey; + if (!nsJSPrincipals::ReadPrincipalInfo(aReader, storageKey)) { + return false; + } + + QM_TRY_UNWRAP(auto hasEqualStorageKey, + aGlobal->HasEqualStorageKey(storageKey), false); + + if (!hasEqualStorageKey) { + LOG(("Blocking deserialization of %s due to cross-origin", + NS_ConvertUTF16toUTF8(name).get())); + return false; + } + + LOG_VERBOSE(("Deserializing %s", NS_ConvertUTF16toUTF8(name).get())); + + aMetadata = fs::FileSystemEntryMetadata(entryId, name, aDirectory); + return true; +} + +} // namespace + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileSystemHandle) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(FileSystemHandle) +NS_IMPL_CYCLE_COLLECTING_RELEASE(FileSystemHandle) + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FileSystemHandle) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FileSystemHandle) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal) + // Don't unlink mManager! + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FileSystemHandle) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mManager) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +FileSystemHandle::FileSystemHandle( + nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + const fs::FileSystemEntryMetadata& aMetadata, + fs::FileSystemRequestHandler* aRequestHandler) + : mGlobal(aGlobal), + mManager(aManager), + mMetadata(aMetadata), + mRequestHandler(aRequestHandler) { + MOZ_ASSERT(!mMetadata.entryId().IsEmpty()); +} + +// WebIDL Boilerplate + +nsIGlobalObject* FileSystemHandle::GetParentObject() const { return mGlobal; } + +JSObject* FileSystemHandle::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return FileSystemHandle_Binding::Wrap(aCx, this, aGivenProto); +} + +// WebIDL Interface + +void FileSystemHandle::GetName(nsAString& aResult) { + aResult = mMetadata.entryName(); +} + +already_AddRefed<Promise> FileSystemHandle::IsSameEntry( + FileSystemHandle& aOther, ErrorResult& aError) const { + RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError); + if (aError.Failed()) { + return nullptr; + } + + // Handles the case of "dir = createdir foo; removeEntry(foo); file = + // createfile foo; issameentry(dir, file)" + const bool result = mMetadata.entryId().Equals(aOther.mMetadata.entryId()) && + Kind() == aOther.Kind(); + promise->MaybeResolve(result); + + return promise.forget(); +} + +already_AddRefed<Promise> FileSystemHandle::Move(const nsAString& aName, + ErrorResult& aError) { + LOG(("Move %s to %s", NS_ConvertUTF16toUTF8(mMetadata.entryName()).get(), + NS_ConvertUTF16toUTF8(aName).get())); + + fs::EntryId parent; // empty means same directory + return Move(parent, aName, aError); +} + +already_AddRefed<Promise> FileSystemHandle::Move( + FileSystemDirectoryHandle& aParent, ErrorResult& aError) { + LOG(("Move %s to %s/%s", NS_ConvertUTF16toUTF8(mMetadata.entryName()).get(), + NS_ConvertUTF16toUTF8(aParent.mMetadata.entryName()).get(), + NS_ConvertUTF16toUTF8(mMetadata.entryName()).get())); + return Move(aParent, mMetadata.entryName(), aError); +} + +already_AddRefed<Promise> FileSystemHandle::Move( + FileSystemDirectoryHandle& aParent, const nsAString& aName, + ErrorResult& aError) { + LOG(("Move %s to %s/%s", NS_ConvertUTF16toUTF8(mMetadata.entryName()).get(), + NS_ConvertUTF16toUTF8(aParent.mMetadata.entryName()).get(), + NS_ConvertUTF16toUTF8(aName).get())); + return Move(aParent.mMetadata.entryId(), aName, aError); +} + +already_AddRefed<Promise> FileSystemHandle::Move(const fs::EntryId& aParentId, + const nsAString& aName, + ErrorResult& aError) { + RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError); + if (aError.Failed()) { + return nullptr; + } + + fs::Name name(aName); + if (!aParentId.IsEmpty()) { + fs::FileSystemChildMetadata newMetadata; + newMetadata.parentId() = aParentId; + newMetadata.childName() = aName; + mRequestHandler->MoveEntry(mManager, this, mMetadata, newMetadata, promise, + aError); + } else { + mRequestHandler->RenameEntry(mManager, this, mMetadata, name, promise, + aError); + } + if (aError.Failed()) { + return nullptr; + } + + // Other handles to this will be broken, and the spec is ok with this, but we + // need to update our EntryId and name + promise->AddCallbacksWithCycleCollectedArgs( + [name](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv, + FileSystemHandle* aHandle) { + // XXX Fix entryId! + LOG(("Changing FileSystemHandle name from %s to %s", + NS_ConvertUTF16toUTF8(aHandle->mMetadata.entryName()).get(), + NS_ConvertUTF16toUTF8(name).get())); + aHandle->mMetadata.entryName() = name; + }, + [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv, + FileSystemHandle* aHandle) { + LOG(("reject of move for %s", + NS_ConvertUTF16toUTF8(aHandle->mMetadata.entryName()).get())); + }, + RefPtr(this)); + + return promise.forget(); +} + +// [Serializable] implementation + +// static +already_AddRefed<FileSystemHandle> FileSystemHandle::ReadStructuredClone( + JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader) { + LOG_VERBOSE(("Reading File/DirectoryHandle")); + + uint32_t kind = static_cast<uint32_t>(FileSystemHandleKind::EndGuard_); + + if (!JS_ReadBytes(aReader, reinterpret_cast<void*>(&kind), + sizeof(uint32_t))) { + return nullptr; + } + + if (kind == static_cast<uint32_t>(FileSystemHandleKind::Directory)) { + RefPtr<FileSystemHandle> result = + FileSystemHandle::ConstructDirectoryHandle(aCx, aGlobal, aReader); + return result.forget(); + } + + if (kind == static_cast<uint32_t>(FileSystemHandleKind::File)) { + RefPtr<FileSystemHandle> result = + FileSystemHandle::ConstructFileHandle(aCx, aGlobal, aReader); + return result.forget(); + } + + return nullptr; +} + +bool FileSystemHandle::WriteStructuredClone( + JSContext* aCx, JSStructuredCloneWriter* aWriter) const { + LOG_VERBOSE(("Writing File/DirectoryHandle")); + MOZ_ASSERT(mMetadata.entryId().Length() == 32); + + auto kind = static_cast<uint32_t>(Kind()); + if (NS_WARN_IF(!JS_WriteBytes(aWriter, static_cast<void*>(&kind), + sizeof(uint32_t)))) { + return false; + } + + if (NS_WARN_IF(!JS_WriteBytes( + aWriter, static_cast<const void*>(mMetadata.entryId().get()), + mMetadata.entryId().Length()))) { + return false; + } + + if (!StructuredCloneHolder::WriteString(aWriter, mMetadata.entryName())) { + return false; + } + + // Needed to make sure the destination nsIGlobalObject is from the same + // origin/principal + QM_TRY_INSPECT(const auto& storageKey, mGlobal->GetStorageKey(), false); + + return nsJSPrincipals::WritePrincipalInfo(aWriter, storageKey); +} + +// static +already_AddRefed<FileSystemFileHandle> FileSystemHandle::ConstructFileHandle( + JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader) { + LOG(("Reading FileHandle")); + + fs::FileSystemEntryMetadata metadata; + if (!ConstructHandleMetadata(aCx, aGlobal, aReader, /* aDirectory */ false, + metadata)) { + return nullptr; + } + + RefPtr<StorageManager> storageManager = aGlobal->GetStorageManager(); + if (!storageManager) { + return nullptr; + } + + // Note that the actor may not exist or may not be connected yet. + RefPtr<FileSystemManager> fileSystemManager = + storageManager->GetFileSystemManager(); + + RefPtr<FileSystemFileHandle> fsHandle = + new FileSystemFileHandle(aGlobal, fileSystemManager, metadata); + + return fsHandle.forget(); +} + +// static +already_AddRefed<FileSystemDirectoryHandle> +FileSystemHandle::ConstructDirectoryHandle(JSContext* aCx, + nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader) { + LOG(("Reading DirectoryHandle")); + + fs::FileSystemEntryMetadata metadata; + if (!ConstructHandleMetadata(aCx, aGlobal, aReader, /* aDirectory */ true, + metadata)) { + return nullptr; + } + + RefPtr<StorageManager> storageManager = aGlobal->GetStorageManager(); + if (!storageManager) { + return nullptr; + } + + // Note that the actor may not exist or may not be connected yet. + RefPtr<FileSystemManager> fileSystemManager = + storageManager->GetFileSystemManager(); + + RefPtr<FileSystemDirectoryHandle> fsHandle = + new FileSystemDirectoryHandle(aGlobal, fileSystemManager, metadata); + + return fsHandle.forget(); +} + +} // namespace mozilla::dom diff --git a/dom/fs/api/FileSystemHandle.h b/dom/fs/api/FileSystemHandle.h new file mode 100644 index 0000000000..76ad66f5ea --- /dev/null +++ b/dom/fs/api/FileSystemHandle.h @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_FS_FILESYSTEMHANDLE_H_ +#define DOM_FS_FILESYSTEMHANDLE_H_ + +#include "mozilla/dom/PFileSystemManager.h" +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsWrapperCache.h" + +class nsIGlobalObject; + +namespace mozilla { + +class ErrorResult; + +namespace dom { + +class FileSystemDirectoryHandle; +class FileSystemFileHandle; +enum class FileSystemHandleKind : uint8_t; +class FileSystemManager; +class FileSystemManagerChild; +class Promise; + +namespace fs { +class FileSystemRequestHandler; +} // namespace fs + +class FileSystemHandle : public nsISupports, public nsWrapperCache { + public: + FileSystemHandle(nsIGlobalObject* aGlobal, + RefPtr<FileSystemManager>& aManager, + const fs::FileSystemEntryMetadata& aMetadata, + fs::FileSystemRequestHandler* aRequestHandler); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FileSystemHandle) + + const fs::EntryId& GetId() const { return mMetadata.entryId(); } + + // WebIDL Boilerplate + nsIGlobalObject* GetParentObject() const; + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // WebIDL Interface + virtual FileSystemHandleKind Kind() const = 0; + + void GetName(nsAString& aResult); + + already_AddRefed<Promise> IsSameEntry(FileSystemHandle& aOther, + ErrorResult& aError) const; + + // [Serializable] implementation + static already_AddRefed<FileSystemHandle> ReadStructuredClone( + JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader); + + virtual bool WriteStructuredClone(JSContext* aCx, + JSStructuredCloneWriter* aWriter) const; + + already_AddRefed<Promise> Move(const nsAString& aName, ErrorResult& aError); + + already_AddRefed<Promise> Move(FileSystemDirectoryHandle& aParent, + ErrorResult& aError); + + already_AddRefed<Promise> Move(FileSystemDirectoryHandle& aParent, + const nsAString& aName, ErrorResult& aError); + + already_AddRefed<Promise> Move(const fs::EntryId& aParentId, + const nsAString& aName, ErrorResult& aError); + + void UpdateMetadata(const fs::FileSystemEntryMetadata& aMetadata) { + mMetadata = aMetadata; + } + + protected: + virtual ~FileSystemHandle() = default; + + static already_AddRefed<FileSystemFileHandle> ConstructFileHandle( + JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader); + + static already_AddRefed<FileSystemDirectoryHandle> ConstructDirectoryHandle( + JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader); + + nsCOMPtr<nsIGlobalObject> mGlobal; + + RefPtr<FileSystemManager> mManager; + + // move() can change names/directories + fs::FileSystemEntryMetadata mMetadata; + + const UniquePtr<fs::FileSystemRequestHandler> mRequestHandler; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_FS_FILESYSTEMHANDLE_H_ diff --git a/dom/fs/api/FileSystemManager.cpp b/dom/fs/api/FileSystemManager.cpp new file mode 100644 index 0000000000..9c2b10f462 --- /dev/null +++ b/dom/fs/api/FileSystemManager.cpp @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/FileSystemManager.h" + +#include "FileSystemBackgroundRequestHandler.h" +#include "fs/FileSystemRequestHandler.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/FileSystemManagerChild.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/StorageManager.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "mozilla/dom/quota/ResultExtensions.h" + +namespace mozilla::dom { + +FileSystemManager::FileSystemManager( + nsIGlobalObject* aGlobal, RefPtr<StorageManager> aStorageManager, + RefPtr<FileSystemBackgroundRequestHandler> aBackgroundRequestHandler) + : mGlobal(aGlobal), + mStorageManager(std::move(aStorageManager)), + mBackgroundRequestHandler(std::move(aBackgroundRequestHandler)), + mRequestHandler(new fs::FileSystemRequestHandler()) {} + +FileSystemManager::FileSystemManager(nsIGlobalObject* aGlobal, + RefPtr<StorageManager> aStorageManager) + : FileSystemManager(aGlobal, std::move(aStorageManager), + MakeRefPtr<FileSystemBackgroundRequestHandler>()) {} + +FileSystemManager::~FileSystemManager() { MOZ_ASSERT(mShutdown); } + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileSystemManager) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END +NS_IMPL_CYCLE_COLLECTING_ADDREF(FileSystemManager); +NS_IMPL_CYCLE_COLLECTING_RELEASE(FileSystemManager); +NS_IMPL_CYCLE_COLLECTION(FileSystemManager, mGlobal, mStorageManager); + +void FileSystemManager::Shutdown() { + mShutdown.Flip(); + + if (mBackgroundRequestHandler->FileSystemManagerChildStrongRef()) { + // FileSystemAccessHandles prevent shutdown until they are full closed, so + // at this point, we should see no open FileSystemAccessHandles. + MOZ_ASSERT(mBackgroundRequestHandler->FileSystemManagerChildStrongRef() + ->AllSyncAccessHandlesClosed()); + + mBackgroundRequestHandler->FileSystemManagerChildStrongRef() + ->CloseAllWritableFileStreams(); + } + + mBackgroundRequestHandler->Shutdown(); + + mCreateFileSystemManagerChildPromiseRequestHolder.DisconnectIfExists(); +} + +void FileSystemManager::BeginRequest( + std::function<void(const RefPtr<FileSystemManagerChild>&)>&& aSuccess, + std::function<void(nsresult)>&& aFailure) { + MOZ_ASSERT(!mShutdown); + + MOZ_ASSERT(mGlobal); + + // Check if we're allowed to use storage + if (mGlobal->GetStorageAccess() < StorageAccess::eSessionScoped) { + aFailure(NS_ERROR_DOM_SECURITY_ERR); + return; + } + + if (mBackgroundRequestHandler->FileSystemManagerChildStrongRef()) { + aSuccess(mBackgroundRequestHandler->FileSystemManagerChildStrongRef()); + return; + } + + QM_TRY_INSPECT(const auto& principalInfo, mGlobal->GetStorageKey(), QM_VOID, + [&aFailure](nsresult rv) { aFailure(rv); }); + + mBackgroundRequestHandler->CreateFileSystemManagerChild(principalInfo) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [self = RefPtr<FileSystemManager>(this), + success = std::move(aSuccess), failure = std::move(aFailure)]( + const BoolPromise::ResolveOrRejectValue& aValue) { + self->mCreateFileSystemManagerChildPromiseRequestHolder.Complete(); + + if (aValue.IsResolve()) { + success(self->mBackgroundRequestHandler + ->FileSystemManagerChildStrongRef()); + } else { + failure(aValue.RejectValue()); + } + }) + ->Track(mCreateFileSystemManagerChildPromiseRequestHolder); +} + +already_AddRefed<Promise> FileSystemManager::GetDirectory(ErrorResult& aError) { + MOZ_ASSERT(mGlobal); + + RefPtr<Promise> promise = Promise::Create(mGlobal, aError); + if (NS_WARN_IF(aError.Failed())) { + return nullptr; + } + + MOZ_ASSERT(promise); + + mRequestHandler->GetRootHandle(this, promise, aError); + if (NS_WARN_IF(aError.Failed())) { + return nullptr; + } + + return promise.forget(); +} + +} // namespace mozilla::dom diff --git a/dom/fs/api/FileSystemManager.h b/dom/fs/api/FileSystemManager.h new file mode 100644 index 0000000000..c30d193467 --- /dev/null +++ b/dom/fs/api/FileSystemManager.h @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_FS_CHILD_FILESYSTEMMANAGER_H_ +#define DOM_FS_CHILD_FILESYSTEMMANAGER_H_ + +#include <functional> + +#include "mozilla/MozPromise.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/FlippedOnce.h" +#include "mozilla/dom/quota/ForwardDecls.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsISupports.h" + +class nsIGlobalObject; + +namespace mozilla { + +class ErrorResult; + +namespace dom { + +class FileSystemManagerChild; +class FileSystemBackgroundRequestHandler; +class StorageManager; + +namespace fs { +class FileSystemRequestHandler; +} // namespace fs + +// `FileSystemManager` is supposed to be held by `StorageManager` and thus +// there should always be only one `FileSystemManager` per `nsIGlobalObject`. +// `FileSystemManager` is responsible for creating and eventually caching +// `FileSystemManagerChild` which is required for communication with the parent +// process. `FileSystemHandle` is also expected to hold `FileSystemManager`, +// but it should never clear the strong reference during cycle collection's +// unlink phase to keep the actor alive. `FileSystemSyncAccessHandle` and +// `FileSystemWritableFileStream` are also expected to hold `FileSystemManager`, +// and they shouldn't clear the strong reference during cycle collection's +// unlink phase as well even though they have their own actor. Those actors +// are managed by the top level actor, so if the top level actor is destroyed, +// the whole chain of managed actors would be destroyed as well. +class FileSystemManager : public nsISupports { + public: + FileSystemManager( + nsIGlobalObject* aGlobal, RefPtr<StorageManager> aStorageManager, + RefPtr<FileSystemBackgroundRequestHandler> aBackgroundRequestHandler); + + FileSystemManager(nsIGlobalObject* aGlobal, + RefPtr<StorageManager> aStorageManager); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(FileSystemManager) + + bool IsShutdown() const { return mShutdown; } + + void Shutdown(); + + void BeginRequest( + std::function<void(const RefPtr<FileSystemManagerChild>&)>&& aSuccess, + std::function<void(nsresult)>&& aFailure); + + already_AddRefed<Promise> GetDirectory(ErrorResult& aError); + + private: + virtual ~FileSystemManager(); + + nsCOMPtr<nsIGlobalObject> mGlobal; + + RefPtr<StorageManager> mStorageManager; + + const RefPtr<FileSystemBackgroundRequestHandler> mBackgroundRequestHandler; + const UniquePtr<fs::FileSystemRequestHandler> mRequestHandler; + + MozPromiseRequestHolder<BoolPromise> + mCreateFileSystemManagerChildPromiseRequestHolder; + + FlippedOnce<false> mShutdown; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_FS_CHILD_FILESYSTEMMANAGER_H_ diff --git a/dom/fs/api/FileSystemSyncAccessHandle.cpp b/dom/fs/api/FileSystemSyncAccessHandle.cpp new file mode 100644 index 0000000000..12d9545332 --- /dev/null +++ b/dom/fs/api/FileSystemSyncAccessHandle.cpp @@ -0,0 +1,627 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileSystemSyncAccessHandle.h" + +#include "fs/FileSystemRequestHandler.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/FixedBufferOutputStream.h" +#include "mozilla/MozPromise.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/dom/FileSystemAccessHandleChild.h" +#include "mozilla/dom/FileSystemHandleBinding.h" +#include "mozilla/dom/FileSystemLog.h" +#include "mozilla/dom/FileSystemManager.h" +#include "mozilla/dom/FileSystemSyncAccessHandleBinding.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/UnionTypes.h" +#include "mozilla/dom/WorkerCommon.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRef.h" +#include "mozilla/dom/fs/TargetPtrHolder.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "mozilla/dom/quota/ResultExtensions.h" +#include "mozilla/ipc/RandomAccessStreamUtils.h" +#include "nsNetCID.h" +#include "nsStreamUtils.h" +#include "nsStringStream.h" + +namespace mozilla::dom { + +namespace { + +const uint32_t kStreamCopyBlockSize = 1024 * 1024; + +using SizePromise = Int64Promise; +const auto CreateAndRejectSizePromise = CreateAndRejectInt64Promise; + +nsresult AsyncCopy(nsIInputStream* aSource, nsIOutputStream* aSink, + nsISerialEventTarget* aIOTarget, const nsAsyncCopyMode aMode, + const bool aCloseSource, const bool aCloseSink, + std::function<void(uint32_t)>&& aProgressCallback, + MoveOnlyFunction<void(nsresult)>&& aCompleteCallback) { + struct CallbackClosure { + CallbackClosure(std::function<void(uint32_t)>&& aProgressCallback, + MoveOnlyFunction<void(nsresult)>&& aCompleteCallback) { + mProgressCallbackWrapper = MakeUnique<std::function<void(uint32_t)>>( + [progressCallback = std::move(aProgressCallback)](uint32_t count) { + progressCallback(count); + }); + + mCompleteCallbackWrapper = MakeUnique<MoveOnlyFunction<void(nsresult)>>( + [completeCallback = + std::move(aCompleteCallback)](nsresult rv) mutable { + auto callback = std::move(completeCallback); + callback(rv); + }); + } + + UniquePtr<std::function<void(uint32_t)>> mProgressCallbackWrapper; + UniquePtr<MoveOnlyFunction<void(nsresult)>> mCompleteCallbackWrapper; + }; + + auto* callbackClosure = new CallbackClosure(std::move(aProgressCallback), + std::move(aCompleteCallback)); + + QM_TRY( + MOZ_TO_RESULT(NS_AsyncCopy( + aSource, aSink, aIOTarget, aMode, kStreamCopyBlockSize, + [](void* aClosure, nsresult aRv) { + auto* callbackClosure = static_cast<CallbackClosure*>(aClosure); + (*callbackClosure->mCompleteCallbackWrapper)(aRv); + delete callbackClosure; + }, + callbackClosure, aCloseSource, aCloseSink, /* aCopierCtx */ nullptr, + [](void* aClosure, uint32_t aCount) { + auto* callbackClosure = static_cast<CallbackClosure*>(aClosure); + (*callbackClosure->mProgressCallbackWrapper)(aCount); + })), + [callbackClosure](nsresult rv) { + delete callbackClosure; + return rv; + }); + + return NS_OK; +} + +} // namespace + +FileSystemSyncAccessHandle::FileSystemSyncAccessHandle( + nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + RefPtr<FileSystemAccessHandleChild> aActor, RefPtr<TaskQueue> aIOTaskQueue, + mozilla::ipc::RandomAccessStreamParams&& aStreamParams, + const fs::FileSystemEntryMetadata& aMetadata) + : mGlobal(aGlobal), + mManager(aManager), + mActor(std::move(aActor)), + mIOTaskQueue(std::move(aIOTaskQueue)), + mStreamParams(std::move(aStreamParams)), + mMetadata(aMetadata), + mState(State::Initial) { + LOG(("Created SyncAccessHandle %p", this)); + + // Connect with the actor directly in the constructor. This way the actor + // can call `FileSystemSyncAccessHandle::ClearActor` when we call + // `PFileSystemAccessHandleChild::Send__delete__` even when + // FileSystemSyncAccessHandle::Create fails, in which case the not yet + // fully constructed FileSystemSyncAccessHandle is being destroyed. + mActor->SetAccessHandle(this); +} + +FileSystemSyncAccessHandle::~FileSystemSyncAccessHandle() { + MOZ_ASSERT(!mActor); + MOZ_ASSERT(IsClosed()); +} + +// static +Result<RefPtr<FileSystemSyncAccessHandle>, nsresult> +FileSystemSyncAccessHandle::Create( + nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + RefPtr<FileSystemAccessHandleChild> aActor, + mozilla::ipc::RandomAccessStreamParams&& aStreamParams, + const fs::FileSystemEntryMetadata& aMetadata) { + QM_TRY_UNWRAP(auto streamTransportService, + MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIEventTarget>, + MOZ_SELECT_OVERLOAD(do_GetService), + NS_STREAMTRANSPORTSERVICE_CONTRACTID)); + + RefPtr<TaskQueue> ioTaskQueue = TaskQueue::Create( + streamTransportService.forget(), "FileSystemSyncAccessHandle"); + QM_TRY(MOZ_TO_RESULT(ioTaskQueue)); + + RefPtr<FileSystemSyncAccessHandle> result = new FileSystemSyncAccessHandle( + aGlobal, aManager, std::move(aActor), std::move(ioTaskQueue), + std::move(aStreamParams), aMetadata); + + auto autoClose = MakeScopeExit([result] { + MOZ_ASSERT(result->mState == State::Initial); + result->mState = State::Closed; + result->mActor->SendClose(); + }); + + WorkerPrivate* const workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + + workerPrivate->AssertIsOnWorkerThread(); + + RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create( + workerPrivate, "FileSystemSyncAccessHandle", [result]() { + if (result->IsOpen()) { + // We don't need to use the result, we just need to begin the closing + // process. + Unused << result->BeginClose(); + } + }); + QM_TRY(MOZ_TO_RESULT(workerRef)); + + autoClose.release(); + + result->mWorkerRef = std::move(workerRef); + result->mState = State::Open; + + return result; +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileSystemSyncAccessHandle) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(FileSystemSyncAccessHandle) +NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(FileSystemSyncAccessHandle, + LastRelease()) + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FileSystemSyncAccessHandle) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FileSystemSyncAccessHandle) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal) + // Don't unlink mManager! + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER + if (tmp->IsOpen()) { + // We don't need to use the result, we just need to begin the closing + // process. + Unused << tmp->BeginClose(); + } +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FileSystemSyncAccessHandle) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mManager) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +void FileSystemSyncAccessHandle::LastRelease() { + // We can't call `FileSystemSyncAccessHandle::Close` here because it may need + // to keep FileSystemSyncAccessHandle object alive which isn't possible when + // the object is about to be deleted. There are other mechanisms which ensure + // that the object is correctly closed before destruction. For example the + // object unlinking and the worker shutdown (we get notified about it via the + // callback passed to `StrongWorkerRef`) are used to close the object if it + // hasn't been closed yet. + + if (mActor) { + PFileSystemAccessHandleChild::Send__delete__(mActor); + MOZ_ASSERT(!mActor); + } +} + +void FileSystemSyncAccessHandle::ClearActor() { + MOZ_ASSERT(mActor); + + mActor = nullptr; +} + +bool FileSystemSyncAccessHandle::IsOpen() const { + MOZ_ASSERT(mState != State::Initial); + + return mState == State::Open; +} + +bool FileSystemSyncAccessHandle::IsClosing() const { + MOZ_ASSERT(mState != State::Initial); + + return mState == State::Closing; +} + +bool FileSystemSyncAccessHandle::IsClosed() const { + MOZ_ASSERT(mState != State::Initial); + + return mState == State::Closed; +} + +RefPtr<BoolPromise> FileSystemSyncAccessHandle::BeginClose() { + MOZ_ASSERT(IsOpen()); + + mState = State::Closing; + + InvokeAsync(mIOTaskQueue, __func__, + [selfHolder = fs::TargetPtrHolder(this)]() { + QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()), + CreateAndRejectBoolPromise); + + LOG(("%p: Closing", selfHolder->mStream.get())); + + selfHolder->mStream->OutputStream()->Close(); + selfHolder->mStream = nullptr; + + return BoolPromise::CreateAndResolve(true, __func__); + }) + ->Then(mWorkerRef->Private()->ControlEventTarget(), __func__, + [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue&) { + return self->mIOTaskQueue->BeginShutdown(); + }) + ->Then( + mWorkerRef->Private()->ControlEventTarget(), __func__, + [self = RefPtr(this)](const ShutdownPromise::ResolveOrRejectValue&) { + if (self->mActor) { + self->mActor->SendClose(); + } + + self->mWorkerRef = nullptr; + + self->mState = State::Closed; + + self->mClosePromiseHolder.ResolveIfExists(true, __func__); + }); + + return OnClose(); +} + +RefPtr<BoolPromise> FileSystemSyncAccessHandle::OnClose() { + MOZ_ASSERT(mState == State::Closing); + + return mClosePromiseHolder.Ensure(__func__); +} + +// WebIDL Boilerplate + +nsIGlobalObject* FileSystemSyncAccessHandle::GetParentObject() const { + return mGlobal; +} + +JSObject* FileSystemSyncAccessHandle::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return FileSystemSyncAccessHandle_Binding::Wrap(aCx, this, aGivenProto); +} + +// WebIDL Interface + +uint64_t FileSystemSyncAccessHandle::Read( + const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer, + const FileSystemReadWriteOptions& aOptions, ErrorResult& aRv) { + return ReadOrWrite(aBuffer, aOptions, /* aRead */ true, aRv); +} + +uint64_t FileSystemSyncAccessHandle::Write( + const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer, + const FileSystemReadWriteOptions& aOptions, ErrorResult& aRv) { + return ReadOrWrite(aBuffer, aOptions, /* aRead */ false, aRv); +} + +void FileSystemSyncAccessHandle::Truncate(uint64_t aSize, ErrorResult& aError) { + if (!IsOpen()) { + aError.ThrowInvalidStateError("SyncAccessHandle is closed"); + return; + } + + MOZ_ASSERT(mWorkerRef); + + AutoSyncLoopHolder syncLoop(mWorkerRef->Private(), Canceling); + + nsCOMPtr<nsISerialEventTarget> syncLoopTarget = + syncLoop.GetSerialEventTarget(); + QM_TRY(MOZ_TO_RESULT(syncLoopTarget), [&aError](nsresult) { + aError.ThrowInvalidStateError("Worker is shutting down"); + }); + + InvokeAsync( + mIOTaskQueue, __func__, + [selfHolder = fs::TargetPtrHolder(this), aSize]() { + QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()), + CreateAndRejectBoolPromise); + + LOG(("%p: Truncate to %" PRIu64, selfHolder->mStream.get(), aSize)); + + QM_TRY(MOZ_TO_RESULT(selfHolder->mStream->Seek( + nsISeekableStream::NS_SEEK_SET, aSize)), + CreateAndRejectBoolPromise); + + QM_TRY(MOZ_TO_RESULT(selfHolder->mStream->SetEOF()), + CreateAndRejectBoolPromise); + + return BoolPromise::CreateAndResolve(true, __func__); + }) + ->Then(syncLoopTarget, __func__, + [this, &syncLoopTarget]( + const BoolPromise::ResolveOrRejectValue& aValue) { + MOZ_ASSERT(mWorkerRef); + + mWorkerRef->Private()->AssertIsOnWorkerThread(); + + mWorkerRef->Private()->StopSyncLoop( + syncLoopTarget, + aValue.IsResolve() ? NS_OK : aValue.RejectValue()); + }); + + QM_TRY(MOZ_TO_RESULT(syncLoop.Run()), + [&aError](const nsresult rv) { aError.Throw(rv); }); +} + +uint64_t FileSystemSyncAccessHandle::GetSize(ErrorResult& aError) { + if (!IsOpen()) { + aError.ThrowInvalidStateError("SyncAccessHandle is closed"); + return 0; + } + + MOZ_ASSERT(mWorkerRef); + + AutoSyncLoopHolder syncLoop(mWorkerRef->Private(), Canceling); + + nsCOMPtr<nsISerialEventTarget> syncLoopTarget = + syncLoop.GetSerialEventTarget(); + QM_TRY(MOZ_TO_RESULT(syncLoopTarget), [&aError](nsresult) { + aError.ThrowInvalidStateError("Worker is shutting down"); + return 0; + }); + + // XXX Could we somehow pass the size to `StopSyncLoop` and then get it via + // `QM_TRY_INSPECT(const auto& size, syncLoop.Run)` ? + // Could we use Result<UniquePtr<...>, nsresult> for that ? + int64_t size; + + InvokeAsync(mIOTaskQueue, __func__, + [selfHolder = fs::TargetPtrHolder(this)]() { + QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()), + CreateAndRejectSizePromise); + + nsCOMPtr<nsIFileMetadata> fileMetadata = + do_QueryInterface(selfHolder->mStream); + MOZ_ASSERT(fileMetadata); + + QM_TRY_INSPECT( + const auto& size, + MOZ_TO_RESULT_INVOKE_MEMBER(fileMetadata, GetSize), + CreateAndRejectSizePromise); + + LOG(("%p: GetSize %" PRIu64, selfHolder->mStream.get(), size)); + + return SizePromise::CreateAndResolve(size, __func__); + }) + ->Then(syncLoopTarget, __func__, + [this, &syncLoopTarget, + &size](const Int64Promise::ResolveOrRejectValue& aValue) { + MOZ_ASSERT(mWorkerRef); + + mWorkerRef->Private()->AssertIsOnWorkerThread(); + + if (aValue.IsResolve()) { + size = aValue.ResolveValue(); + + mWorkerRef->Private()->StopSyncLoop(syncLoopTarget, NS_OK); + } else { + mWorkerRef->Private()->StopSyncLoop(syncLoopTarget, + aValue.RejectValue()); + } + }); + + QM_TRY(MOZ_TO_RESULT(syncLoop.Run()), [&aError](const nsresult rv) { + aError.Throw(rv); + return 0; + }); + + return size; +} + +void FileSystemSyncAccessHandle::Flush(ErrorResult& aError) { + if (!IsOpen()) { + aError.ThrowInvalidStateError("SyncAccessHandle is closed"); + return; + } + + MOZ_ASSERT(mWorkerRef); + + AutoSyncLoopHolder syncLoop(mWorkerRef->Private(), Canceling); + + nsCOMPtr<nsISerialEventTarget> syncLoopTarget = + syncLoop.GetSerialEventTarget(); + QM_TRY(MOZ_TO_RESULT(syncLoopTarget), [&aError](nsresult) { + aError.ThrowInvalidStateError("Worker is shutting down"); + }); + + InvokeAsync(mIOTaskQueue, __func__, + [selfHolder = fs::TargetPtrHolder(this)]() { + QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()), + CreateAndRejectBoolPromise); + + LOG(("%p: Flush", selfHolder->mStream.get())); + + QM_TRY( + MOZ_TO_RESULT(selfHolder->mStream->OutputStream()->Flush()), + CreateAndRejectBoolPromise); + + return BoolPromise::CreateAndResolve(true, __func__); + }) + ->Then(syncLoopTarget, __func__, + [this, &syncLoopTarget]( + const BoolPromise::ResolveOrRejectValue& aValue) { + MOZ_ASSERT(mWorkerRef); + + mWorkerRef->Private()->AssertIsOnWorkerThread(); + + mWorkerRef->Private()->StopSyncLoop( + syncLoopTarget, + aValue.IsResolve() ? NS_OK : aValue.RejectValue()); + }); + + QM_TRY(MOZ_TO_RESULT(syncLoop.Run()), + [&aError](const nsresult rv) { aError.Throw(rv); }); +} + +void FileSystemSyncAccessHandle::Close() { + if (!(IsOpen() || IsClosing())) { + return; + } + + MOZ_ASSERT(mWorkerRef); + + // Normally mWorkerRef can be used directly for stopping the sync loop, but + // the async close is special because mWorkerRef is cleared as part of the + // operation. That's why we need to use this extra strong ref to the + // `StrongWorkerRef`. + RefPtr<StrongWorkerRef> workerRef = mWorkerRef; + + AutoSyncLoopHolder syncLoop(workerRef->Private(), Killing); + + nsCOMPtr<nsISerialEventTarget> syncLoopTarget = + syncLoop.GetSerialEventTarget(); + MOZ_ASSERT(syncLoopTarget); + + InvokeAsync(syncLoopTarget, __func__, [self = RefPtr(this)]() { + if (self->IsOpen()) { + return self->BeginClose(); + } + return self->OnClose(); + })->Then(syncLoopTarget, __func__, [&workerRef, &syncLoopTarget]() { + MOZ_ASSERT(workerRef); + + workerRef->Private()->AssertIsOnWorkerThread(); + + workerRef->Private()->StopSyncLoop(syncLoopTarget, NS_OK); + }); + + MOZ_ALWAYS_SUCCEEDS(syncLoop.Run()); +} + +uint64_t FileSystemSyncAccessHandle::ReadOrWrite( + const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer, + const FileSystemReadWriteOptions& aOptions, const bool aRead, + ErrorResult& aRv) { + if (!IsOpen()) { + aRv.ThrowInvalidStateError("SyncAccessHandle is closed"); + return 0; + } + + MOZ_ASSERT(mWorkerRef); + + auto throwAndReturn = [&aRv](const nsresult rv) { + aRv.Throw(rv); + return 0; + }; + + const auto dataSpan = [&aBuffer]() { + if (aBuffer.IsArrayBuffer()) { + const ArrayBuffer& buffer = aBuffer.GetAsArrayBuffer(); + buffer.ComputeState(); + return Span{buffer.Data(), buffer.Length()}; + } + MOZ_ASSERT(aBuffer.IsArrayBufferView()); + const ArrayBufferView& buffer = aBuffer.GetAsArrayBufferView(); + buffer.ComputeState(); + return Span{buffer.Data(), buffer.Length()}; + }(); + + // Handle seek before read ('at') + const auto at = [&aOptions]() -> uint64_t { + if (aOptions.mAt.WasPassed()) { + return aOptions.mAt.Value(); + } + // Spec says default for at is 0 (2.6) + return 0; + }(); + + const auto offset = CheckedInt<int64_t>(at); + QM_TRY(MOZ_TO_RESULT(offset.isValid()), throwAndReturn); + + AutoSyncLoopHolder syncLoop(mWorkerRef->Private(), Canceling); + + nsCOMPtr<nsISerialEventTarget> syncLoopTarget = + syncLoop.GetSerialEventTarget(); + QM_TRY(MOZ_TO_RESULT(syncLoopTarget), [&aRv](nsresult) { + aRv.ThrowInvalidStateError("Worker is shutting down"); + return 0; + }); + + uint64_t totalCount = 0; + + InvokeAsync( + mIOTaskQueue, __func__, + [selfHolder = fs::TargetPtrHolder(this), dataSpan, offset, aRead, + &totalCount]() { + QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()), + CreateAndRejectBoolPromise); + + LOG_VERBOSE(("%p: Seeking to %" PRIu64, selfHolder->mStream.get(), + offset.value())); + + QM_TRY(MOZ_TO_RESULT(selfHolder->mStream->Seek( + nsISeekableStream::NS_SEEK_SET, offset.value())), + CreateAndRejectBoolPromise); + + nsCOMPtr<nsIInputStream> inputStream; + nsCOMPtr<nsIOutputStream> outputStream; + + if (aRead) { + LOG_VERBOSE(("%p: Reading %zu bytes", selfHolder->mStream.get(), + dataSpan.Length())); + + inputStream = selfHolder->mStream->InputStream(); + + outputStream = + FixedBufferOutputStream::Create(AsWritableChars(dataSpan)); + } else { + LOG_VERBOSE(("%p: Writing %zu bytes", selfHolder->mStream.get(), + dataSpan.Length())); + + QM_TRY(MOZ_TO_RESULT(NS_NewByteInputStream( + getter_AddRefs(inputStream), AsChars(dataSpan), + NS_ASSIGNMENT_DEPEND)), + CreateAndRejectBoolPromise); + + outputStream = selfHolder->mStream->OutputStream(); + } + + auto promiseHolder = MakeUnique<MozPromiseHolder<BoolPromise>>(); + RefPtr<BoolPromise> promise = promiseHolder->Ensure(__func__); + + QM_TRY(MOZ_TO_RESULT(AsyncCopy( + inputStream, outputStream, GetCurrentSerialEventTarget(), + aRead ? NS_ASYNCCOPY_VIA_WRITESEGMENTS + : NS_ASYNCCOPY_VIA_READSEGMENTS, + /* aCloseSource */ !aRead, /* aCloseSink */ aRead, + [&totalCount](uint32_t count) { totalCount += count; }, + [promiseHolder = std::move(promiseHolder)](nsresult rv) { + promiseHolder->ResolveIfExists(true, __func__); + })), + CreateAndRejectBoolPromise); + + return promise; + }) + ->Then(syncLoopTarget, __func__, + [this, &syncLoopTarget]( + const BoolPromise::ResolveOrRejectValue& aValue) { + MOZ_ASSERT(mWorkerRef); + + mWorkerRef->Private()->AssertIsOnWorkerThread(); + + mWorkerRef->Private()->StopSyncLoop(syncLoopTarget, NS_OK); + }); + + MOZ_ALWAYS_SUCCEEDS(syncLoop.Run()); + + return totalCount; +} + +nsresult FileSystemSyncAccessHandle::EnsureStream() { + if (!mStream) { + QM_TRY_UNWRAP(mStream, DeserializeRandomAccessStream(mStreamParams), + NS_ERROR_FAILURE); + + mozilla::ipc::RandomAccessStreamParams streamParams( + std::move(mStreamParams)); + } + + return NS_OK; +} + +} // namespace mozilla::dom diff --git a/dom/fs/api/FileSystemSyncAccessHandle.h b/dom/fs/api/FileSystemSyncAccessHandle.h new file mode 100644 index 0000000000..ec2ca9bd76 --- /dev/null +++ b/dom/fs/api/FileSystemSyncAccessHandle.h @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_FS_FILESYSTEMSYNCACCESSHANDLE_H_ +#define DOM_FS_FILESYSTEMSYNCACCESSHANDLE_H_ + +#include "mozilla/dom/PFileSystemManager.h" +#include "mozilla/dom/quota/ForwardDecls.h" +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsWrapperCache.h" + +class nsIGlobalObject; + +namespace mozilla { + +class ErrorResult; +class TaskQueue; + +namespace dom { + +class FileSystemAccessHandleChild; +struct FileSystemReadWriteOptions; +class FileSystemManager; +class MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer; +class Promise; +class StrongWorkerRef; + +class FileSystemSyncAccessHandle final : public nsISupports, + public nsWrapperCache { + public: + enum struct State : uint8_t { Initial = 0, Open, Closing, Closed }; + + static Result<RefPtr<FileSystemSyncAccessHandle>, nsresult> Create( + nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + RefPtr<FileSystemAccessHandleChild> aActor, + mozilla::ipc::RandomAccessStreamParams&& aStreamParams, + const fs::FileSystemEntryMetadata& aMetadata); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FileSystemSyncAccessHandle) + + void LastRelease(); + + void ClearActor(); + + bool IsOpen() const; + + bool IsClosing() const; + + bool IsClosed() const; + + [[nodiscard]] RefPtr<BoolPromise> BeginClose(); + + [[nodiscard]] RefPtr<BoolPromise> OnClose(); + + // WebIDL Boilerplate + nsIGlobalObject* GetParentObject() const; + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // WebIDL Interface + uint64_t Read( + const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer, + const FileSystemReadWriteOptions& aOptions, ErrorResult& aRv); + + uint64_t Write( + const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer, + const FileSystemReadWriteOptions& aOptions, ErrorResult& aRv); + + void Truncate(uint64_t aSize, ErrorResult& aError); + + uint64_t GetSize(ErrorResult& aError); + + void Flush(ErrorResult& aError); + + void Close(); + + private: + FileSystemSyncAccessHandle( + nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + RefPtr<FileSystemAccessHandleChild> aActor, + RefPtr<TaskQueue> aIOTaskQueue, + mozilla::ipc::RandomAccessStreamParams&& aStreamParams, + const fs::FileSystemEntryMetadata& aMetadata); + + virtual ~FileSystemSyncAccessHandle(); + + uint64_t ReadOrWrite( + const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer, + const FileSystemReadWriteOptions& aOptions, const bool aRead, + ErrorResult& aRv); + + nsresult EnsureStream(); + + nsCOMPtr<nsIGlobalObject> mGlobal; + + RefPtr<FileSystemManager> mManager; + + RefPtr<FileSystemAccessHandleChild> mActor; + + RefPtr<TaskQueue> mIOTaskQueue; + + nsCOMPtr<nsIRandomAccessStream> mStream; + + RefPtr<StrongWorkerRef> mWorkerRef; + + MozPromiseHolder<BoolPromise> mClosePromiseHolder; + + mozilla::ipc::RandomAccessStreamParams mStreamParams; + + const fs::FileSystemEntryMetadata mMetadata; + + State mState; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_FS_FILESYSTEMSYNCACCESSHANDLE_H_ diff --git a/dom/fs/api/FileSystemWritableFileStream.cpp b/dom/fs/api/FileSystemWritableFileStream.cpp new file mode 100644 index 0000000000..69d1181c16 --- /dev/null +++ b/dom/fs/api/FileSystemWritableFileStream.cpp @@ -0,0 +1,706 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileSystemWritableFileStream.h" + +#include "mozilla/Buffer.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/Blob.h" +#include "mozilla/dom/FileSystemHandle.h" +#include "mozilla/dom/FileSystemLog.h" +#include "mozilla/dom/FileSystemManager.h" +#include "mozilla/dom/FileSystemWritableFileStreamBinding.h" +#include "mozilla/dom/FileSystemWritableFileStreamChild.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/WritableStreamDefaultController.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "mozilla/dom/quota/ResultExtensions.h" +#include "nsIInputStream.h" +#include "nsNetUtil.h" +#include "private/pprio.h" + +namespace mozilla::dom { + +namespace { + +class WritableFileStreamUnderlyingSinkAlgorithms final + : public UnderlyingSinkAlgorithmsBase { + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED( + WritableFileStreamUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase) + + explicit WritableFileStreamUnderlyingSinkAlgorithms( + FileSystemWritableFileStream& aStream) + : mStream(&aStream) {} + + // Streams algorithms + void StartCallback(JSContext* aCx, + WritableStreamDefaultController& aController, + JS::MutableHandle<JS::Value> aRetVal, + ErrorResult& aRv) override; + + MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> WriteCallback( + JSContext* aCx, JS::Handle<JS::Value> aChunk, + WritableStreamDefaultController& aController, ErrorResult& aRv) override; + + MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CloseCallback( + JSContext* aCx, ErrorResult& aRv) override; + + MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> AbortCallback( + JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason, + ErrorResult& aRv) override; + + void ReleaseObjects() override; + + private: + ~WritableFileStreamUnderlyingSinkAlgorithms() = default; + + RefPtr<FileSystemWritableFileStream> mStream; +}; + +/** + * TODO: Duplicated from netwerk/cache2/CacheFileIOManager.cpp + * Please remove after bug 1286601 is fixed, + * https://bugzilla.mozilla.org/show_bug.cgi?id=1286601 + */ +nsresult TruncFile(PRFileDesc* aFD, int64_t aEOF) { +#if defined(XP_UNIX) + if (ftruncate(PR_FileDesc2NativeHandle(aFD), aEOF) != 0) { + NS_ERROR("ftruncate failed"); + return NS_ERROR_FAILURE; + } +#elif defined(XP_WIN) + const int64_t currentOffset = PR_Seek64(aFD, 0, PR_SEEK_CUR); + if (currentOffset == -1) { + return NS_ERROR_FAILURE; + } + + int64_t cnt = PR_Seek64(aFD, aEOF, PR_SEEK_SET); + if (cnt == -1) { + return NS_ERROR_FAILURE; + } + + if (!SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(aFD))) { + NS_ERROR("SetEndOfFile failed"); + return NS_ERROR_FAILURE; + } + + if (PR_Seek64(aFD, currentOffset, PR_SEEK_SET) == -1) { + NS_ERROR("Restoring seek offset failed"); + return NS_ERROR_FAILURE; + } + +#else + MOZ_ASSERT(false, "Not implemented!"); + return NS_ERROR_NOT_IMPLEMENTED; +#endif + + return NS_OK; +} + +} // namespace + +FileSystemWritableFileStream::FileSystemWritableFileStream( + nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + RefPtr<FileSystemWritableFileStreamChild> aActor, + const ::mozilla::ipc::FileDescriptor& aFileDescriptor, + const fs::FileSystemEntryMetadata& aMetadata) + : WritableStream(aGlobal, HoldDropJSObjectsCaller::Explicit), + mManager(aManager), + mActor(std::move(aActor)), + mFileDesc(nullptr), + mMetadata(aMetadata), + mClosed(false) { + auto rawFD = aFileDescriptor.ClonePlatformHandle(); + mFileDesc = PR_ImportFile(PROsfd(rawFD.release())); + + LOG(("Created WritableFileStream %p for fd %p", this, mFileDesc)); + + // Connect with the actor directly in the constructor. This way the actor + // can call `FileSystemWritableFileStream::ClearActor` when we call + // `PFileSystemWritableFileStreamChild::Send__delete__` even when + // FileSystemWritableFileStream::Create fails, in which case the not yet + // fully constructed FileSystemWritableFileStream is being destroyed. + mActor->SetStream(this); + + mozilla::HoldJSObjects(this); +} + +FileSystemWritableFileStream::~FileSystemWritableFileStream() { + MOZ_ASSERT(!mActor); + MOZ_ASSERT(mClosed); + + mozilla::DropJSObjects(this); +} + +// https://streams.spec.whatwg.org/#writablestream-set-up +// * This is fallible because of OOM handling of JSAPI. See bug 1762233. +// * Consider extracting this as UnderlyingSinkAlgorithmsWrapper if more classes +// start subclassing WritableStream. +// For now this skips step 2 - 4 as they are not required here. +// XXX(krosylight): _BOUNDARY because SetUpWritableStreamDefaultController here +// can't run script because StartCallback here is no-op. Can we let the static +// check automatically detect this situation? +/* static */ +MOZ_CAN_RUN_SCRIPT_BOUNDARY already_AddRefed<FileSystemWritableFileStream> +FileSystemWritableFileStream::Create( + nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + RefPtr<FileSystemWritableFileStreamChild> aActor, + const ::mozilla::ipc::FileDescriptor& aFileDescriptor, + const fs::FileSystemEntryMetadata& aMetadata) { + AutoJSAPI jsapi; + if (!jsapi.Init(aGlobal)) { + return nullptr; + } + JSContext* cx = jsapi.cx(); + + // Step 5. Perform ! InitializeWritableStream(stream). + // (Done by the constructor) + RefPtr<FileSystemWritableFileStream> stream = + new FileSystemWritableFileStream(aGlobal, aManager, std::move(aActor), + aFileDescriptor, aMetadata); + + // Step 1 - 3 + auto algorithms = + MakeRefPtr<WritableFileStreamUnderlyingSinkAlgorithms>(*stream); + + // Step 6. Let controller be a new WritableStreamDefaultController. + auto controller = + MakeRefPtr<WritableStreamDefaultController>(aGlobal, *stream); + + // Step 7. Perform ! SetUpWritableStreamDefaultController(stream, controller, + // startAlgorithm, writeAlgorithm, closeAlgorithmWrapper, + // abortAlgorithmWrapper, highWaterMark, sizeAlgorithm). + IgnoredErrorResult rv; + SetUpWritableStreamDefaultController( + cx, stream, controller, algorithms, + // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream + // Step 6. Let highWaterMark be 1. + 1, + // Step 7. Let sizeAlgorithm be an algorithm that returns 1. + // (nullptr returns 1, See WritableStream::Constructor for details) + nullptr, rv); + if (rv.Failed()) { + return nullptr; + } + return stream.forget(); +} + +NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(FileSystemWritableFileStream, + WritableStream) + +NS_IMPL_CYCLE_COLLECTION_CLASS(FileSystemWritableFileStream) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FileSystemWritableFileStream, + WritableStream) + // Per the comment for the FileSystemManager class, don't unlink mManager! + tmp->Close(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FileSystemWritableFileStream, + WritableStream) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mManager) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +void FileSystemWritableFileStream::LastRelease() { + Close(); + + if (mActor) { + PFileSystemWritableFileStreamChild::Send__delete__(mActor); + MOZ_ASSERT(!mActor); + } +} + +void FileSystemWritableFileStream::ClearActor() { + MOZ_ASSERT(mActor); + + mActor = nullptr; +} + +void FileSystemWritableFileStream::Close() { + // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream + // Step 4. Let closeAlgorithm be these steps: + + if (mClosed) { + return; + } + + LOG(("%p: Closing", mFileDesc)); + + mClosed = true; + + PR_Close(mFileDesc); + mFileDesc = nullptr; + + if (mActor) { + mActor->SendClose(); + } +} + +already_AddRefed<Promise> FileSystemWritableFileStream::Write( + JSContext* aCx, JS::Handle<JS::Value> aChunk, ErrorResult& aError) { + MOZ_ASSERT(!mClosed); + + // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream + // Step 3. Let writeAlgorithm be an algorithm which takes a chunk argument + // and returns the result of running the write a chunk algorithm with stream + // and chunk. + + // https://fs.spec.whatwg.org/#write-a-chunk + // Step 1. Let input be the result of converting chunk to a + // FileSystemWriteChunkType. + + aError.MightThrowJSException(); + + ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams data; + if (!data.Init(aCx, aChunk)) { + aError.StealExceptionFromJSContext(aCx); + return nullptr; + } + + // Step 2. Let p be a new promise. + RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError); + if (aError.Failed()) { + return nullptr; + } + + // Step 3.3. Let command be input.type if input is a WriteParams, ... + if (data.IsWriteParams()) { + const WriteParams& params = data.GetAsWriteParams(); + switch (params.mType) { + // Step 3.4. If command is "write": + case WriteCommandType::Write: { + if (!params.mData.WasPassed()) { + promise->MaybeRejectWithSyntaxError("write() requires data"); + return promise.forget(); + } + + // Step 3.4.2. If data is undefined, reject p with a TypeError and + // abort. + if (params.mData.Value().IsNull()) { + promise->MaybeRejectWithTypeError("write() of null data"); + return promise.forget(); + } + + Maybe<uint64_t> position; + + if (params.mPosition.WasPassed()) { + if (params.mPosition.Value().IsNull()) { + promise->MaybeRejectWithTypeError("write() with null position"); + return promise.forget(); + } + + position = Some(params.mPosition.Value().Value()); + } + + Write(params.mData.Value().Value(), position, promise); + return promise.forget(); + } + + // Step 3.5. Otherwise, if command is "seek": + case WriteCommandType::Seek: + if (!params.mPosition.WasPassed()) { + promise->MaybeRejectWithSyntaxError("seek() requires a position"); + return promise.forget(); + } + + // Step 3.5.1. If chunk.position is undefined, reject p with a TypeError + // and abort. + if (params.mPosition.Value().IsNull()) { + promise->MaybeRejectWithTypeError("seek() with null position"); + return promise.forget(); + } + + Seek(params.mPosition.Value().Value(), promise); + return promise.forget(); + + // Step 3.6. Otherwise, if command is "truncate": + case WriteCommandType::Truncate: + if (!params.mSize.WasPassed()) { + promise->MaybeRejectWithSyntaxError("truncate() requires a size"); + return promise.forget(); + } + + // Step 3.6.1. If chunk.size is undefined, reject p with a TypeError + // and abort. + if (params.mSize.Value().IsNull()) { + promise->MaybeRejectWithTypeError("truncate() with null size"); + return promise.forget(); + } + + Truncate(params.mSize.Value().Value(), promise); + return promise.forget(); + + default: + MOZ_CRASH("Bad WriteParams value!"); + } + } + + // Step 3.3. ... and "write" otherwise. + // Step 3.4. If command is "write": + Write(data, Nothing(), promise); + return promise.forget(); +} + +// WebIDL Boilerplate + +JSObject* FileSystemWritableFileStream::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return FileSystemWritableFileStream_Binding::Wrap(aCx, this, aGivenProto); +} + +// WebIDL Interface + +already_AddRefed<Promise> FileSystemWritableFileStream::Write( + const ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams& aData, + ErrorResult& aError) { + // https://fs.spec.whatwg.org/#dom-filesystemwritablefilestream-write + // Step 1. Let writer be the result of getting a writer for this. + RefPtr<WritableStreamDefaultWriter> writer = GetWriter(aError); + if (aError.Failed()) { + return nullptr; + } + + // Step 2. Let result be the result of writing a chunk to writer given data. + AutoJSAPI jsapi; + if (!jsapi.Init(GetParentObject())) { + aError.ThrowUnknownError("Internal error"); + return nullptr; + } + + JSContext* cx = jsapi.cx(); + + JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx)); + + JS::Rooted<JS::Value> val(cx); + if (!aData.ToJSVal(cx, global, &val)) { + aError.ThrowUnknownError("Internal error"); + return nullptr; + } + + RefPtr<Promise> promise = writer->Write(cx, val, aError); + + // Step 3. Release writer. + { + IgnoredErrorResult error; + writer->ReleaseLock(cx, error); + } + + // Step 4. Return result. + return promise.forget(); +} + +already_AddRefed<Promise> FileSystemWritableFileStream::Seek( + uint64_t aPosition, ErrorResult& aError) { + // https://fs.spec.whatwg.org/#dom-filesystemwritablefilestream-seek + // Step 1. Let writer be the result of getting a writer for this. + RefPtr<WritableStreamDefaultWriter> writer = GetWriter(aError); + if (aError.Failed()) { + return nullptr; + } + + // Step 2. Let result be the result of writing a chunk to writer given + // «[ "type" → "seek", "position" → position ]». + AutoJSAPI jsapi; + if (!jsapi.Init(GetParentObject())) { + aError.ThrowUnknownError("Internal error"); + return nullptr; + } + + JSContext* cx = jsapi.cx(); + + RootedDictionary<WriteParams> writeParams(cx); + writeParams.mType = WriteCommandType::Seek; + writeParams.mPosition.Construct(aPosition); + + JS::Rooted<JS::Value> val(cx); + if (!ToJSValue(cx, writeParams, &val)) { + aError.ThrowUnknownError("Internal error"); + return nullptr; + } + + RefPtr<Promise> promise = writer->Write(cx, val, aError); + + // Step 3. Release writer. + { + IgnoredErrorResult error; + writer->ReleaseLock(cx, error); + } + + // Step 4. Return result. + return promise.forget(); +} + +already_AddRefed<Promise> FileSystemWritableFileStream::Truncate( + uint64_t aSize, ErrorResult& aError) { + // https://fs.spec.whatwg.org/#dom-filesystemwritablefilestream-truncate + // Step 1. Let writer be the result of getting a writer for this. + RefPtr<WritableStreamDefaultWriter> writer = GetWriter(aError); + if (aError.Failed()) { + return nullptr; + } + + // Step 2. Let result be the result of writing a chunk to writer given + // «[ "type" → "truncate", "size" → size ]». + AutoJSAPI jsapi; + if (!jsapi.Init(GetParentObject())) { + aError.ThrowUnknownError("Internal error"); + return nullptr; + } + + JSContext* cx = jsapi.cx(); + + RootedDictionary<WriteParams> writeParams(cx); + writeParams.mType = WriteCommandType::Truncate; + writeParams.mSize.Construct(aSize); + + JS::Rooted<JS::Value> val(cx); + if (!ToJSValue(cx, writeParams, &val)) { + aError.ThrowUnknownError("Internal error"); + return nullptr; + } + + RefPtr<Promise> promise = writer->Write(cx, val, aError); + + // Step 3. Release writer. + { + IgnoredErrorResult error; + writer->ReleaseLock(cx, error); + } + + // Step 4. Return result. + return promise.forget(); +} + +template <typename T> +void FileSystemWritableFileStream::Write(const T& aData, + const Maybe<uint64_t> aPosition, + RefPtr<Promise> aPromise) { + MOZ_ASSERT(!mClosed); + + auto rejectAndReturn = [&aPromise](const nsresult rv) { + if (rv == NS_ERROR_FILE_NOT_FOUND) { + aPromise->MaybeRejectWithNotFoundError("File not found"); + } else { + aPromise->MaybeReject(rv); + } + }; + + // https://fs.spec.whatwg.org/#write-a-chunk + // Step 3.4.6 If data is a BufferSource, let dataBytes be a copy of data. + if (aData.IsArrayBuffer() || aData.IsArrayBufferView()) { + const auto dataSpan = [&aData]() { + if (aData.IsArrayBuffer()) { + const ArrayBuffer& buffer = aData.GetAsArrayBuffer(); + buffer.ComputeState(); + return Span{buffer.Data(), buffer.Length()}; + } + MOZ_ASSERT(aData.IsArrayBufferView()); + const ArrayBufferView& buffer = aData.GetAsArrayBufferView(); + buffer.ComputeState(); + return Span{buffer.Data(), buffer.Length()}; + }(); + + nsCString dataBuffer; + QM_TRY(MOZ_TO_RESULT(dataBuffer.Assign( + AsChars(dataSpan).data(), dataSpan.Length(), mozilla::fallible)), + rejectAndReturn); + QM_TRY_INSPECT(const auto& written, WriteBuffer(dataBuffer, aPosition), + rejectAndReturn); + + LOG_VERBOSE(("WritableFileStream: Wrote %" PRId64, written)); + aPromise->MaybeResolve(written); + return; + } + + // Step 3.4.7 Otherwise, if data is a Blob ... + if (aData.IsBlob()) { + Blob& blob = aData.GetAsBlob(); + + nsCOMPtr<nsIInputStream> stream; + ErrorResult error; + blob.CreateInputStream(getter_AddRefs(stream), error); + QM_TRY((MOZ_TO_RESULT(!error.Failed()).mapErr([&error](const nsresult rv) { + return error.StealNSResult(); + })), + rejectAndReturn); + + QM_TRY_INSPECT(const auto& written, + WriteStream(std::move(stream), aPosition), rejectAndReturn); + + LOG_VERBOSE(("WritableFileStream: Wrote %" PRId64, written)); + aPromise->MaybeResolve(written); + return; + } + + // Step 3.4.8 Otherwise ... + MOZ_ASSERT(aData.IsUTF8String()); + + QM_TRY_INSPECT(const auto& written, + WriteBuffer(aData.GetAsUTF8String(), aPosition), + rejectAndReturn); + + LOG_VERBOSE(("WritableFileStream: Wrote %" PRId64, written)); + aPromise->MaybeResolve(written); +} + +void FileSystemWritableFileStream::Seek(uint64_t aPosition, + RefPtr<Promise> aPromise) { + MOZ_ASSERT(!mClosed); + + LOG_VERBOSE(("%p: Seeking to %" PRIu64, mFileDesc, aPosition)); + + QM_TRY(SeekPosition(aPosition), [&aPromise](const nsresult rv) { + aPromise->MaybeReject(rv); + return; + }); + + aPromise->MaybeResolveWithUndefined(); +} + +void FileSystemWritableFileStream::Truncate(uint64_t aSize, + RefPtr<Promise> aPromise) { + MOZ_ASSERT(!mClosed); + + // truncate filehandle (can extend with 0's) + LOG_VERBOSE(("%p: Truncate to %" PRIu64, mFileDesc, aSize)); + if (NS_WARN_IF(NS_FAILED(TruncFile(mFileDesc, aSize)))) { + aPromise->MaybeReject(NS_ErrorAccordingToNSPR()); + return; + } + + // We truncated; per non-normative text in the spec (2.5.3) we should + // adjust the cursor position to be within the new file size + int64_t where = PR_Seek(mFileDesc, 0, PR_SEEK_CUR); + if (where == -1) { + aPromise->MaybeReject(NS_ErrorAccordingToNSPR()); + return; + } + + if (where > (int64_t)aSize) { + where = PR_Seek(mFileDesc, 0, PR_SEEK_END); + if (where == -1) { + aPromise->MaybeReject(NS_ErrorAccordingToNSPR()); + return; + } + } + + aPromise->MaybeResolveWithUndefined(); +} + +Result<uint64_t, nsresult> FileSystemWritableFileStream::WriteBuffer( + const nsACString& aBuffer, const Maybe<uint64_t> aPosition) { + MOZ_ASSERT(!mClosed); + + const auto checkedLength = CheckedInt<PRInt32>(aBuffer.Length()); + QM_TRY(MOZ_TO_RESULT(checkedLength.isValid())); + + if (aPosition) { + QM_TRY(SeekPosition(*aPosition)); + } + + return PR_Write(mFileDesc, aBuffer.BeginReading(), checkedLength.value()); +} + +Result<uint64_t, nsresult> FileSystemWritableFileStream::WriteStream( + nsCOMPtr<nsIInputStream> aStream, const Maybe<uint64_t> aPosition) { + MOZ_ASSERT(aStream); + MOZ_ASSERT(!mClosed); + + nsCString rawBuffer; + QM_TRY(MOZ_TO_RESULT(NS_ReadInputStreamToString(aStream, rawBuffer, -1))); + QM_TRY_RETURN(WriteBuffer(rawBuffer, aPosition)); +} + +Result<Ok, nsresult> FileSystemWritableFileStream::SeekPosition( + uint64_t aPosition) { + MOZ_ASSERT(!mClosed); + + const auto checkedPosition = CheckedInt<int64_t>(aPosition); + QM_TRY(MOZ_TO_RESULT(checkedPosition.isValid())); + + int64_t cnt = PR_Seek64(mFileDesc, checkedPosition.value(), PR_SEEK_SET); + if (cnt == int64_t(-1)) { + LOG(("Failed to seek to %" PRIu64 " (errno %d)", aPosition, errno)); + return Err(NS_ErrorAccordingToNSPR()); + } + + if (cnt != checkedPosition.value()) { + LOG(("Failed to seek to %" PRIu64 " (errno %d), ended up at %" PRId64, + aPosition, errno, cnt)); + return Err(NS_ERROR_FAILURE); + } + + return Ok{}; +} + +NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0( + WritableFileStreamUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase) +NS_IMPL_CYCLE_COLLECTION_INHERITED(WritableFileStreamUnderlyingSinkAlgorithms, + UnderlyingSinkAlgorithmsBase, mStream) + +void WritableFileStreamUnderlyingSinkAlgorithms::StartCallback( + JSContext* aCx, WritableStreamDefaultController& aController, + JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv) { + // https://streams.spec.whatwg.org/#writablestream-set-up + // Step 1. Let startAlgorithm be an algorithm that returns undefined. + aRetVal.setUndefined(); +} + +already_AddRefed<Promise> +WritableFileStreamUnderlyingSinkAlgorithms::WriteCallback( + JSContext* aCx, JS::Handle<JS::Value> aChunk, + WritableStreamDefaultController& aController, ErrorResult& aRv) { + return mStream->Write(aCx, aChunk, aRv); +} + +already_AddRefed<Promise> +WritableFileStreamUnderlyingSinkAlgorithms::CloseCallback(JSContext* aCx, + ErrorResult& aRv) { + // https://streams.spec.whatwg.org/#writablestream-set-up + // Step 2. Let closeAlgorithmWrapper be an algorithm that runs these steps: + + RefPtr<Promise> promise = Promise::Create(mStream->GetParentObject(), aRv); + if (aRv.Failed()) { + return nullptr; + } + + if (mStream->IsClosed()) { + promise->MaybeRejectWithTypeError("WritableFileStream closed"); + return promise.forget(); + } + + // XXX The close should be async. For now we have to always fallback to the + // Step 2.3 below. + mStream->Close(); + + // Step 2.3. Return a promise resolved with undefined. + promise->MaybeResolveWithUndefined(); + return promise.forget(); +} + +already_AddRefed<Promise> +WritableFileStreamUnderlyingSinkAlgorithms::AbortCallback( + JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason, + ErrorResult& aRv) { + // https://streams.spec.whatwg.org/#writablestream-set-up + // Step 3. Let abortAlgorithmWrapper be an algorithm that runs these steps: + + // XXX The close or rather a dedicated abort should be async. For now we have + // to always fall back to the Step 3.3 below. + mStream->Close(); + + // Step 3.3. Return a promise resolved with undefined. + return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(), aRv); +} + +void WritableFileStreamUnderlyingSinkAlgorithms::ReleaseObjects() { + // XXX We shouldn't be calling close here. We should just release the lock. + // However, calling close here is not a big issue for now because we don't + // write to a temporary file which would atomically replace the real file + // during close. + mStream->Close(); +} + +} // namespace mozilla::dom diff --git a/dom/fs/api/FileSystemWritableFileStream.h b/dom/fs/api/FileSystemWritableFileStream.h new file mode 100644 index 0000000000..630dd9184f --- /dev/null +++ b/dom/fs/api/FileSystemWritableFileStream.h @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_FS_FILESYSTEMWRITABLEFILESTREAM_H_ +#define DOM_FS_FILESYSTEMWRITABLEFILESTREAM_H_ + +#include "mozilla/dom/PFileSystemManager.h" +#include "mozilla/dom/WritableStream.h" + +class nsIGlobalObject; + +namespace mozilla { + +template <typename T> +class Buffer; +class ErrorResult; + +namespace dom { + +class ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams; +class Blob; +class FileSystemManager; +class FileSystemWritableFileStreamChild; +class OwningArrayBufferViewOrArrayBufferOrBlobOrUSVString; +class Promise; + +class FileSystemWritableFileStream final : public WritableStream { + public: + static already_AddRefed<FileSystemWritableFileStream> Create( + nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + RefPtr<FileSystemWritableFileStreamChild> aActor, + const ::mozilla::ipc::FileDescriptor& aFileDescriptor, + const fs::FileSystemEntryMetadata& aMetadata); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FileSystemWritableFileStream, + WritableStream) + + void LastRelease() override; + + void ClearActor(); + + bool IsClosed() const { return mClosed; } + + void Close(); + + already_AddRefed<Promise> Write(JSContext* aCx, JS::Handle<JS::Value> aChunk, + ErrorResult& aError); + + // WebIDL Boilerplate + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // WebIDL Interface + MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Write( + const ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams& aData, + ErrorResult& aError); + + MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Seek(uint64_t aPosition, + ErrorResult& aError); + + MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Truncate(uint64_t aSize, + ErrorResult& aError); + + private: + FileSystemWritableFileStream( + nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + RefPtr<FileSystemWritableFileStreamChild> aActor, + const ::mozilla::ipc::FileDescriptor& aFileDescriptor, + const fs::FileSystemEntryMetadata& aMetadata); + + virtual ~FileSystemWritableFileStream(); + + template <typename T> + void Write(const T& aData, const Maybe<uint64_t> aPosition, + RefPtr<Promise> aPromise); + + void Seek(uint64_t aPosition, RefPtr<Promise> aPromise); + + void Truncate(uint64_t aSize, RefPtr<Promise> aPromise); + + Result<uint64_t, nsresult> WriteBuffer(const nsACString& aBuffer, + const Maybe<uint64_t> aPosition); + + Result<uint64_t, nsresult> WriteStream(nsCOMPtr<nsIInputStream> aStream, + const Maybe<uint64_t> aPosition); + + Result<Ok, nsresult> SeekPosition(uint64_t aPosition); + + RefPtr<FileSystemManager> mManager; + + RefPtr<FileSystemWritableFileStreamChild> mActor; + + PRFileDesc* mFileDesc; + + fs::FileSystemEntryMetadata mMetadata; + + bool mClosed; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_FS_FILESYSTEMWRITABLEFILESTREAM_H_ diff --git a/dom/fs/api/moz.build b/dom/fs/api/moz.build new file mode 100644 index 0000000000..09c0bc450a --- /dev/null +++ b/dom/fs/api/moz.build @@ -0,0 +1,34 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS.mozilla.dom += [ + "FileSystemDirectoryHandle.h", + "FileSystemDirectoryIterator.h", + "FileSystemFileHandle.h", + "FileSystemHandle.h", + "FileSystemManager.h", + "FileSystemSyncAccessHandle.h", + "FileSystemWritableFileStream.h", +] + +UNIFIED_SOURCES += [ + "FileSystemDirectoryHandle.cpp", + "FileSystemDirectoryIterator.cpp", + "FileSystemFileHandle.cpp", + "FileSystemHandle.cpp", + "FileSystemManager.cpp", + "FileSystemSyncAccessHandle.cpp", + "FileSystemWritableFileStream.cpp", +] + +LOCAL_INCLUDES += [ + "/dom/fs/child", + "/dom/fs/include", +] + +FINAL_LIBRARY = "xul" + +include("/ipc/chromium/chromium-config.mozbuild") |