diff options
Diffstat (limited to 'dom/filesystem/GetFilesHelper.cpp')
-rw-r--r-- | dom/filesystem/GetFilesHelper.cpp | 643 |
1 files changed, 643 insertions, 0 deletions
diff --git a/dom/filesystem/GetFilesHelper.cpp b/dom/filesystem/GetFilesHelper.cpp new file mode 100644 index 000000000..21d228a60 --- /dev/null +++ b/dom/filesystem/GetFilesHelper.cpp @@ -0,0 +1,643 @@ +/* -*- 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 "nsProxyRelease.h" + +namespace mozilla { +namespace 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, + Sequence<RefPtr<File>>& aFiles, + already_AddRefed<nsIGlobalObject> aGlobal) + { + nsCOMPtr<nsIGlobalObject> global(aGlobal); + if (NS_IsMainThread()) { + return; + } + + RefPtr<ReleaseRunnable> runnable = + new ReleaseRunnable(aPromises, aCallbacks, aFiles, global.forget()); + NS_DispatchToMainThread(runnable); + } + + NS_IMETHOD + Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + + mPromises.Clear(); + mCallbacks.Clear(); + mFiles.Clear(); + mGlobal = nullptr; + + return NS_OK; + } + +private: + ReleaseRunnable(nsTArray<RefPtr<Promise>>& aPromises, + nsTArray<RefPtr<GetFilesCallback>>& aCallbacks, + Sequence<RefPtr<File>>& aFiles, + already_AddRefed<nsIGlobalObject> aGlobal) + { + mPromises.SwapElements(aPromises); + mCallbacks.SwapElements(aCallbacks); + mFiles.SwapElements(aFiles); + mGlobal = aGlobal; + } + + nsTArray<RefPtr<Promise>> mPromises; + nsTArray<RefPtr<GetFilesCallback>> mCallbacks; + Sequence<RefPtr<File>> mFiles; + nsCOMPtr<nsIGlobalObject> mGlobal; +}; + +} // anonymous + +/////////////////////////////////////////////////////////////////////////////// +// GetFilesHelper Base class + +already_AddRefed<GetFilesHelper> +GetFilesHelper::Create(nsIGlobalObject* aGlobal, + const nsTArray<OwningFileOrDirectory>& aFilesOrDirectory, + bool aRecursiveFlag, ErrorResult& aRv) +{ + RefPtr<GetFilesHelper> helper; + + if (XRE_IsParentProcess()) { + helper = new GetFilesHelper(aGlobal, aRecursiveFlag); + } else { + helper = new GetFilesHelperChild(aGlobal, aRecursiveFlag); + } + + nsAutoString directoryPath; + + for (uint32_t i = 0; i < aFilesOrDirectory.Length(); ++i) { + const OwningFileOrDirectory& data = aFilesOrDirectory[i]; + if (data.IsFile()) { + if (!helper->mFiles.AppendElement(data.GetAsFile(), 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->mFiles.IsEmpty()); + helper->SetDirectoryPath(directoryPath); + + helper->Work(aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return helper.forget(); +} + +GetFilesHelper::GetFilesHelper(nsIGlobalObject* aGlobal, bool aRecursiveFlag) + : GetFilesHelperBase(aRecursiveFlag) + , mGlobal(aGlobal) + , mListingCompleted(false) + , mErrorResult(NS_OK) + , mMutex("GetFilesHelper::mMutex") + , mCanceled(false) +{ +} + +GetFilesHelper::~GetFilesHelper() +{ + ReleaseRunnable::MaybeReleaseOnMainThread(mPromises, mCallbacks, mFiles, + mGlobal.forget()); +} + +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() +{ + mGlobal = nullptr; + mFiles.Clear(); + mPromises.Clear(); + mCallbacks.Clear(); + + { + MutexAutoLock lock(mMutex); + mCanceled = true; + } + + Cancel(); +} + +void +GetFilesHelper::Traverse(nsCycleCollectionTraversalCallback &cb) +{ + GetFilesHelper* tmp = this; + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal); + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFiles); + 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; + } + + return NS_DispatchToMainThread(this); + } + + // 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; + } + + RunMainThread(); + + 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; + promises.SwapElements(mPromises); + + for (uint32_t i = 0; i < promises.Length(); ++i) { + ResolveOrRejectPromise(promises[i]); + } + + // Let's process the pending callbacks. + nsTArray<RefPtr<GetFilesCallback>> callbacks; + callbacks.SwapElements(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); +} + +void +GetFilesHelper::RunMainThread() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mDirectoryPath.IsEmpty()); + MOZ_ASSERT(!mListingCompleted); + + // If there is an error, do nothing. + if (NS_FAILED(mErrorResult)) { + return; + } + + // Create the sequence of Files. + for (uint32_t i = 0; i < mTargetBlobImplArray.Length(); ++i) { + RefPtr<File> domFile = File::Create(mGlobal, mTargetBlobImplArray[i]); + MOZ_ASSERT(domFile); + + if (!mFiles.AppendElement(domFile, fallible)) { + mErrorResult = NS_ERROR_OUT_OF_MEMORY; + mFiles.Clear(); + return; + } + } +} + +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; + } + + nsresult rv = AddExploredDirectory(aFile); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsISimpleEnumerator> entries; + rv = aFile->GetDirectoryEntries(getter_AddRefs(entries)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + for (;;) { + bool hasMore = false; + if (NS_WARN_IF(NS_FAILED(entries->HasMoreElements(&hasMore))) || !hasMore) { + break; + } + + nsCOMPtr<nsISupports> supp; + if (NS_WARN_IF(NS_FAILED(entries->GetNext(getter_AddRefs(supp))))) { + break; + } + + nsCOMPtr<nsIFile> currFile = do_QueryInterface(supp); + MOZ_ASSERT(currFile); + + bool isLink, isSpecial, isFile, isDir; + if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) || + NS_FAILED(currFile->IsSpecial(&isSpecial))) || + isSpecial) { + continue; + } + + if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) || + NS_FAILED(currFile->IsDirectory(&isDir))) || + !(isFile || isDir)) { + continue; + } + + // We don't want to explore loops of links. + if (isDir && isLink && !ShouldFollowSymLink(currFile)) { + 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 BlobImplFile(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; +} + +nsresult +GetFilesHelperBase::AddExploredDirectory(nsIFile* aDir) +{ + nsresult rv; + +#ifdef DEBUG + bool isDir; + rv = aDir->IsDirectory(&isDir); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(isDir, "Why are we here?"); +#endif + + bool isLink; + rv = aDir->IsSymlink(&isLink); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoCString path; + + if (!isLink) { + nsAutoString path16; + rv = aDir->GetPath(path16); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + path = NS_ConvertUTF16toUTF8(path16); + } else { + rv = aDir->GetNativeTarget(path); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + mExploredDirectories.PutEntry(path); + return NS_OK; +} + +bool +GetFilesHelperBase::ShouldFollowSymLink(nsIFile* aDir) +{ +#ifdef DEBUG + bool isLink, isDir; + if (NS_WARN_IF(NS_FAILED(aDir->IsSymlink(&isLink)) || + NS_FAILED(aDir->IsDirectory(&isDir)))) { + return false; + } + + MOZ_ASSERT(isLink && isDir, "Why are we here?"); +#endif + + nsAutoCString targetPath; + if (NS_WARN_IF(NS_FAILED(aDir->GetNativeTarget(targetPath)))) { + return false; + } + + return !mExploredDirectories.Contains(targetPath); +} + +void +GetFilesHelper::ResolveOrRejectPromise(Promise* aPromise) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mListingCompleted); + MOZ_ASSERT(aPromise); + + // Error propagation. + if (NS_FAILED(mErrorResult)) { + aPromise->MaybeReject(mErrorResult); + return; + } + + aPromise->MaybeResolve(mFiles); +} + +void +GetFilesHelper::RunCallback(GetFilesCallback* aCallback) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mListingCompleted); + MOZ_ASSERT(aCallback); + + aCallback->Callback(mErrorResult, mFiles); +} + +/////////////////////////////////////////////////////////////////////////////// +// GetFilesHelperChild class + +void +GetFilesHelperChild::Work(ErrorResult& aRv) +{ + ContentChild* cc = ContentChild::GetSingleton(); + if (NS_WARN_IF(!cc)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + aRv = nsContentUtils::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()); + + RefPtr<File> file = File::Create(mGlobal, aBlobImpl); + MOZ_ASSERT(file); + + return mFiles.AppendElement(file, 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 Sequence<RefPtr<File>>& aFiles) override + { + if (NS_FAILED(aStatus)) { + mParent->mContentParent->SendGetFilesResponseAndForget(mParent->mUUID, + GetFilesResponseFailure(aStatus)); + return; + } + + GetFilesResponseSuccess success; + nsTArray<PBlobParent*>& blobsParent = success.blobsParent(); + blobsParent.SetLength(aFiles.Length()); + + for (uint32_t i = 0; i < aFiles.Length(); ++i) { + blobsParent[i] = + mParent->mContentParent->GetOrCreateActorForBlob(aFiles[i]); + if (!blobsParent[i]) { + 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(nullptr, aRecursiveFlag) + , mContentParent(aContentParent) + , mUUID(aUUID) +{} + +GetFilesHelperParent::~GetFilesHelperParent() +{ + NS_ReleaseOnMainThread(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(); +} + +} // dom namespace +} // mozilla namespace |