diff options
Diffstat (limited to 'dom/filesystem/GetFilesHelper.cpp')
-rw-r--r-- | dom/filesystem/GetFilesHelper.cpp | 506 |
1 files changed, 506 insertions, 0 deletions
diff --git a/dom/filesystem/GetFilesHelper.cpp b/dom/filesystem/GetFilesHelper.cpp new file mode 100644 index 0000000000..5c91b4cfe5 --- /dev/null +++ b/dom/filesystem/GetFilesHelper.cpp @@ -0,0 +1,506 @@ +/* -*- 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 "GetFilesHelper.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Directory.h" +#include "mozilla/dom/FileBlobImpl.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/UnionTypes.h" +#include "mozilla/dom/IPCBlobUtils.h" +#include "mozilla/ipc/IPCStreamUtils.h" +#include "FileSystemUtils.h" +#include "nsNetCID.h" +#include "nsProxyRelease.h" + +namespace mozilla::dom { + +namespace { + +// This class is used in the DTOR of GetFilesHelper to release resources in the +// correct thread. +class ReleaseRunnable final : public Runnable { + public: + static void MaybeReleaseOnMainThread( + nsTArray<RefPtr<Promise>>&& aPromises, + nsTArray<RefPtr<GetFilesCallback>>&& aCallbacks) { + if (NS_IsMainThread()) { + return; + } + + RefPtr<ReleaseRunnable> runnable = + new ReleaseRunnable(std::move(aPromises), std::move(aCallbacks)); + FileSystemUtils::DispatchRunnable(nullptr, runnable.forget()); + } + + NS_IMETHOD + Run() override { + MOZ_ASSERT(NS_IsMainThread()); + + mPromises.Clear(); + mCallbacks.Clear(); + + return NS_OK; + } + + private: + ReleaseRunnable(nsTArray<RefPtr<Promise>>&& aPromises, + nsTArray<RefPtr<GetFilesCallback>>&& aCallbacks) + : Runnable("dom::ReleaseRunnable"), + mPromises(std::move(aPromises)), + mCallbacks(std::move(aCallbacks)) {} + + nsTArray<RefPtr<Promise>> mPromises; + nsTArray<RefPtr<GetFilesCallback>> mCallbacks; +}; + +} // namespace + +/////////////////////////////////////////////////////////////////////////////// +// GetFilesHelper Base class + +already_AddRefed<GetFilesHelper> GetFilesHelper::Create( + const nsTArray<OwningFileOrDirectory>& aFilesOrDirectory, + bool aRecursiveFlag, ErrorResult& aRv) { + RefPtr<GetFilesHelper> helper; + + if (XRE_IsParentProcess()) { + helper = new GetFilesHelper(aRecursiveFlag); + } else { + helper = new GetFilesHelperChild(aRecursiveFlag); + } + + nsAutoString directoryPath; + + for (uint32_t i = 0; i < aFilesOrDirectory.Length(); ++i) { + const OwningFileOrDirectory& data = aFilesOrDirectory[i]; + if (data.IsFile()) { + if (!helper->mTargetBlobImplArray.AppendElement(data.GetAsFile()->Impl(), + fallible)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + } else { + MOZ_ASSERT(data.IsDirectory()); + + // We support the upload of only 1 top-level directory from our + // directory picker. This means that we cannot have more than 1 + // Directory object in aFilesOrDirectory array. + MOZ_ASSERT(directoryPath.IsEmpty()); + + RefPtr<Directory> directory = data.GetAsDirectory(); + MOZ_ASSERT(directory); + + aRv = directory->GetFullRealPath(directoryPath); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + } + } + + // No directories to explore. + if (directoryPath.IsEmpty()) { + helper->mListingCompleted = true; + return helper.forget(); + } + + MOZ_ASSERT(helper->mTargetBlobImplArray.IsEmpty()); + helper->SetDirectoryPath(directoryPath); + + helper->Work(aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return helper.forget(); +} + +GetFilesHelper::GetFilesHelper(bool aRecursiveFlag) + : Runnable("GetFilesHelper"), + GetFilesHelperBase(aRecursiveFlag), + mListingCompleted(false), + mErrorResult(NS_OK), + mMutex("GetFilesHelper::mMutex"), + mCanceled(false) {} + +GetFilesHelper::~GetFilesHelper() { + ReleaseRunnable::MaybeReleaseOnMainThread(std::move(mPromises), + std::move(mCallbacks)); +} + +void GetFilesHelper::AddPromise(Promise* aPromise) { + MOZ_ASSERT(aPromise); + + // Still working. + if (!mListingCompleted) { + mPromises.AppendElement(aPromise); + return; + } + + MOZ_ASSERT(mPromises.IsEmpty()); + ResolveOrRejectPromise(aPromise); +} + +void GetFilesHelper::AddCallback(GetFilesCallback* aCallback) { + MOZ_ASSERT(aCallback); + + // Still working. + if (!mListingCompleted) { + mCallbacks.AppendElement(aCallback); + return; + } + + MOZ_ASSERT(mCallbacks.IsEmpty()); + RunCallback(aCallback); +} + +void GetFilesHelper::Unlink() { + mPromises.Clear(); + mCallbacks.Clear(); + + { + MutexAutoLock lock(mMutex); + mCanceled = true; + } + + Cancel(); +} + +void GetFilesHelper::Traverse(nsCycleCollectionTraversalCallback& cb) { + GetFilesHelper* tmp = this; + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromises); +} + +void GetFilesHelper::Work(ErrorResult& aRv) { + nsCOMPtr<nsIEventTarget> target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(target); + + aRv = target->Dispatch(this, NS_DISPATCH_NORMAL); +} + +NS_IMETHODIMP +GetFilesHelper::Run() { + MOZ_ASSERT(!mDirectoryPath.IsEmpty()); + MOZ_ASSERT(!mListingCompleted); + + // First step is to retrieve the list of file paths. + // This happens in the I/O thread. + if (!NS_IsMainThread()) { + RunIO(); + + // If this operation has been canceled, we don't have to go back to + // main-thread. + if (IsCanceled()) { + return NS_OK; + } + + RefPtr<Runnable> runnable = this; + return FileSystemUtils::DispatchRunnable(nullptr, runnable.forget()); + } + + // We are here, but we should not do anything on this thread because, in the + // meantime, the operation has been canceled. + if (IsCanceled()) { + return NS_OK; + } + + OperationCompleted(); + return NS_OK; +} + +void GetFilesHelper::OperationCompleted() { + // We mark the operation as completed here. + mListingCompleted = true; + + // Let's process the pending promises. + nsTArray<RefPtr<Promise>> promises = std::move(mPromises); + + for (uint32_t i = 0; i < promises.Length(); ++i) { + ResolveOrRejectPromise(promises[i]); + } + + // Let's process the pending callbacks. + nsTArray<RefPtr<GetFilesCallback>> callbacks = std::move(mCallbacks); + + for (uint32_t i = 0; i < callbacks.Length(); ++i) { + RunCallback(callbacks[i]); + } +} + +void GetFilesHelper::RunIO() { + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(!mDirectoryPath.IsEmpty()); + MOZ_ASSERT(!mListingCompleted); + + nsCOMPtr<nsIFile> file; + mErrorResult = NS_NewLocalFile(mDirectoryPath, true, getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(mErrorResult))) { + return; + } + + nsAutoString leafName; + mErrorResult = file->GetLeafName(leafName); + if (NS_WARN_IF(NS_FAILED(mErrorResult))) { + return; + } + + nsAutoString domPath; + domPath.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL); + domPath.Append(leafName); + + mErrorResult = ExploreDirectory(domPath, file); +} + +nsresult GetFilesHelperBase::ExploreDirectory(const nsAString& aDOMPath, + nsIFile* aFile) { + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aFile); + + // We check if this operation has to be terminated at each recursion. + if (IsCanceled()) { + return NS_OK; + } + + nsCOMPtr<nsIDirectoryEnumerator> entries; + nsresult rv = aFile->GetDirectoryEntries(getter_AddRefs(entries)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + for (;;) { + nsCOMPtr<nsIFile> currFile; + if (NS_WARN_IF(NS_FAILED(entries->GetNextFile(getter_AddRefs(currFile)))) || + !currFile) { + break; + } + bool isLink, isSpecial, isFile, isDir; + if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) || + NS_FAILED(currFile->IsSpecial(&isSpecial))) || + isSpecial || + // Although we allow explicit individual selection of symlinks via the + // file picker, we do not process symlinks in directory traversal. Our + // specific policy decision is documented at + // https://bugzilla.mozilla.org/show_bug.cgi?id=1813299#c20 + isLink) { + continue; + } + + if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) || + NS_FAILED(currFile->IsDirectory(&isDir))) || + !(isFile || isDir)) { + continue; + } + + // The new domPath + nsAutoString domPath; + domPath.Assign(aDOMPath); + if (!aDOMPath.EqualsLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL)) { + domPath.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL); + } + + nsAutoString leafName; + if (NS_WARN_IF(NS_FAILED(currFile->GetLeafName(leafName)))) { + continue; + } + domPath.Append(leafName); + + if (isFile) { + RefPtr<BlobImpl> blobImpl = new FileBlobImpl(currFile); + blobImpl->SetDOMPath(domPath); + + if (!mTargetBlobImplArray.AppendElement(blobImpl, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + continue; + } + + MOZ_ASSERT(isDir); + if (!mRecursiveFlag) { + continue; + } + + // Recursive. + rv = ExploreDirectory(domPath, currFile); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; +} + +void GetFilesHelper::ResolveOrRejectPromise(Promise* aPromise) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mListingCompleted); + MOZ_ASSERT(aPromise); + + Sequence<RefPtr<File>> files; + + if (NS_SUCCEEDED(mErrorResult)) { + for (uint32_t i = 0; i < mTargetBlobImplArray.Length(); ++i) { + RefPtr<File> domFile = + File::Create(aPromise->GetParentObject(), mTargetBlobImplArray[i]); + if (NS_WARN_IF(!domFile)) { + mErrorResult = NS_ERROR_FAILURE; + files.Clear(); + break; + } + + if (!files.AppendElement(domFile, fallible)) { + mErrorResult = NS_ERROR_OUT_OF_MEMORY; + files.Clear(); + break; + } + } + } + + // Error propagation. + if (NS_FAILED(mErrorResult)) { + aPromise->MaybeReject(mErrorResult); + return; + } + + aPromise->MaybeResolve(files); +} + +void GetFilesHelper::RunCallback(GetFilesCallback* aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mListingCompleted); + MOZ_ASSERT(aCallback); + + aCallback->Callback(mErrorResult, mTargetBlobImplArray); +} + +/////////////////////////////////////////////////////////////////////////////// +// GetFilesHelperChild class + +void GetFilesHelperChild::Work(ErrorResult& aRv) { + ContentChild* cc = ContentChild::GetSingleton(); + if (NS_WARN_IF(!cc)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + aRv = nsID::GenerateUUIDInPlace(mUUID); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + mPendingOperation = true; + cc->CreateGetFilesRequest(mDirectoryPath, mRecursiveFlag, mUUID, this); +} + +void GetFilesHelperChild::Cancel() { + if (!mPendingOperation) { + return; + } + + ContentChild* cc = ContentChild::GetSingleton(); + if (NS_WARN_IF(!cc)) { + return; + } + + mPendingOperation = false; + cc->DeleteGetFilesRequest(mUUID, this); +} + +bool GetFilesHelperChild::AppendBlobImpl(BlobImpl* aBlobImpl) { + MOZ_ASSERT(mPendingOperation); + MOZ_ASSERT(aBlobImpl); + MOZ_ASSERT(aBlobImpl->IsFile()); + + return mTargetBlobImplArray.AppendElement(aBlobImpl, fallible); +} + +void GetFilesHelperChild::Finished(nsresult aError) { + MOZ_ASSERT(mPendingOperation); + MOZ_ASSERT(NS_SUCCEEDED(mErrorResult)); + + mPendingOperation = false; + mErrorResult = aError; + + OperationCompleted(); +} + +/////////////////////////////////////////////////////////////////////////////// +// GetFilesHelperParent class + +class GetFilesHelperParentCallback final : public GetFilesCallback { + public: + explicit GetFilesHelperParentCallback(GetFilesHelperParent* aParent) + : mParent(aParent) { + MOZ_ASSERT(aParent); + } + + void Callback(nsresult aStatus, + const FallibleTArray<RefPtr<BlobImpl>>& aBlobImpls) override { + if (NS_FAILED(aStatus)) { + mParent->mContentParent->SendGetFilesResponseAndForget( + mParent->mUUID, GetFilesResponseFailure(aStatus)); + return; + } + + GetFilesResponseSuccess success; + + nsTArray<IPCBlob>& ipcBlobs = success.blobs(); + ipcBlobs.SetLength(aBlobImpls.Length()); + + for (uint32_t i = 0; i < aBlobImpls.Length(); ++i) { + nsresult rv = IPCBlobUtils::Serialize(aBlobImpls[i], ipcBlobs[i]); + if (NS_WARN_IF(NS_FAILED(rv))) { + mParent->mContentParent->SendGetFilesResponseAndForget( + mParent->mUUID, GetFilesResponseFailure(NS_ERROR_OUT_OF_MEMORY)); + return; + } + } + + mParent->mContentParent->SendGetFilesResponseAndForget(mParent->mUUID, + success); + } + + private: + // Raw pointer because this callback is kept alive by this parent object. + GetFilesHelperParent* mParent; +}; + +GetFilesHelperParent::GetFilesHelperParent(const nsID& aUUID, + ContentParent* aContentParent, + bool aRecursiveFlag) + : GetFilesHelper(aRecursiveFlag), + mContentParent(aContentParent), + mUUID(aUUID) {} + +GetFilesHelperParent::~GetFilesHelperParent() { + NS_ReleaseOnMainThread("GetFilesHelperParent::mContentParent", + mContentParent.forget()); +} + +/* static */ +already_AddRefed<GetFilesHelperParent> GetFilesHelperParent::Create( + const nsID& aUUID, const nsAString& aDirectoryPath, bool aRecursiveFlag, + ContentParent* aContentParent, ErrorResult& aRv) { + MOZ_ASSERT(aContentParent); + + RefPtr<GetFilesHelperParent> helper = + new GetFilesHelperParent(aUUID, aContentParent, aRecursiveFlag); + helper->SetDirectoryPath(aDirectoryPath); + + helper->Work(aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + RefPtr<GetFilesHelperParentCallback> callback = + new GetFilesHelperParentCallback(helper); + helper->AddCallback(callback); + + return helper.forget(); +} + +} // namespace mozilla::dom |