summaryrefslogtreecommitdiffstats
path: root/dom/filesystem/FileSystemTaskBase.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/filesystem/FileSystemTaskBase.cpp')
-rw-r--r--dom/filesystem/FileSystemTaskBase.cpp379
1 files changed, 379 insertions, 0 deletions
diff --git a/dom/filesystem/FileSystemTaskBase.cpp b/dom/filesystem/FileSystemTaskBase.cpp
new file mode 100644
index 000000000..481002b7d
--- /dev/null
+++ b/dom/filesystem/FileSystemTaskBase.cpp
@@ -0,0 +1,379 @@
+/* -*- 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/FileSystemTaskBase.h"
+
+#include "nsNetCID.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FileSystemBase.h"
+#include "mozilla/dom/FileSystemRequestParent.h"
+#include "mozilla/dom/FileSystemUtils.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ipc/BlobParent.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/Unused.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+nsresult
+FileSystemErrorFromNsError(const nsresult& aErrorValue)
+{
+ uint16_t module = NS_ERROR_GET_MODULE(aErrorValue);
+ if (module == NS_ERROR_MODULE_DOM_FILESYSTEM ||
+ module == NS_ERROR_MODULE_DOM_FILE ||
+ module == NS_ERROR_MODULE_DOM) {
+ return aErrorValue;
+ }
+
+ switch (aErrorValue) {
+ case NS_OK:
+ return NS_OK;
+
+ case NS_ERROR_FILE_INVALID_PATH:
+ case NS_ERROR_FILE_UNRECOGNIZED_PATH:
+ return NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR;
+
+ case NS_ERROR_FILE_DESTINATION_NOT_DIR:
+ return NS_ERROR_DOM_FILESYSTEM_INVALID_MODIFICATION_ERR;
+
+ case NS_ERROR_FILE_ACCESS_DENIED:
+ case NS_ERROR_FILE_DIR_NOT_EMPTY:
+ return NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR;
+
+ case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
+ case NS_ERROR_NOT_AVAILABLE:
+ return NS_ERROR_DOM_FILE_NOT_FOUND_ERR;
+
+ case NS_ERROR_FILE_ALREADY_EXISTS:
+ return NS_ERROR_DOM_FILESYSTEM_PATH_EXISTS_ERR;
+
+ case NS_ERROR_FILE_NOT_DIRECTORY:
+ return NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR;
+
+ case NS_ERROR_UNEXPECTED:
+ default:
+ return NS_ERROR_DOM_FILESYSTEM_UNKNOWN_ERR;
+ }
+}
+
+nsresult
+DispatchToIOThread(nsIRunnable* aRunnable)
+{
+ MOZ_ASSERT(aRunnable);
+
+ nsCOMPtr<nsIEventTarget> target
+ = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ MOZ_ASSERT(target);
+
+ return target->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
+}
+
+// This runnable is used when an error value is set before doing any real
+// operation on the I/O thread. In this case we skip all and we directly
+// communicate the error.
+class ErrorRunnable final : public CancelableRunnable
+{
+public:
+ explicit ErrorRunnable(FileSystemTaskChildBase* aTask)
+ : mTask(aTask)
+ {
+ MOZ_ASSERT(aTask);
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mTask->HasError());
+
+ mTask->HandlerCallback();
+ return NS_OK;
+ }
+
+private:
+ RefPtr<FileSystemTaskChildBase> mTask;
+};
+
+} // anonymous namespace
+
+NS_IMPL_ISUPPORTS(FileSystemTaskChildBase, nsIIPCBackgroundChildCreateCallback)
+
+/**
+ * FileSystemTaskBase class
+ */
+
+FileSystemTaskChildBase::FileSystemTaskChildBase(FileSystemBase* aFileSystem)
+ : mErrorValue(NS_OK)
+ , mFileSystem(aFileSystem)
+{
+ MOZ_ASSERT(aFileSystem, "aFileSystem should not be null.");
+ aFileSystem->AssertIsOnOwningThread();
+}
+
+FileSystemTaskChildBase::~FileSystemTaskChildBase()
+{
+ mFileSystem->AssertIsOnOwningThread();
+}
+
+FileSystemBase*
+FileSystemTaskChildBase::GetFileSystem() const
+{
+ mFileSystem->AssertIsOnOwningThread();
+ return mFileSystem.get();
+}
+
+void
+FileSystemTaskChildBase::Start()
+{
+ mFileSystem->AssertIsOnOwningThread();
+
+ mozilla::ipc::PBackgroundChild* actor =
+ mozilla::ipc::BackgroundChild::GetForCurrentThread();
+ if (actor) {
+ ActorCreated(actor);
+ } else {
+ if (NS_WARN_IF(
+ !mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(this))) {
+ MOZ_CRASH();
+ }
+ }
+}
+
+void
+FileSystemTaskChildBase::ActorFailed()
+{
+ MOZ_CRASH("Failed to create a PBackgroundChild actor!");
+}
+
+void
+FileSystemTaskChildBase::ActorCreated(mozilla::ipc::PBackgroundChild* aActor)
+{
+ if (HasError()) {
+ // In this case we don't want to use IPC at all.
+ RefPtr<ErrorRunnable> runnable = new ErrorRunnable(this);
+ DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
+ return;
+ }
+
+ if (mFileSystem->IsShutdown()) {
+ return;
+ }
+
+ nsAutoString serialization;
+ mFileSystem->SerializeDOMPath(serialization);
+
+ ErrorResult rv;
+ FileSystemParams params = GetRequestParams(serialization, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return;
+ }
+
+ // Retain a reference so the task object isn't deleted without IPDL's
+ // knowledge. The reference will be released by
+ // mozilla::ipc::BackgroundChildImpl::DeallocPFileSystemRequestChild.
+ NS_ADDREF_THIS();
+
+ mozilla::ipc::PBackgroundChild* actor =
+ mozilla::ipc::BackgroundChild::GetForCurrentThread();
+ MOZ_ASSERT(actor);
+
+ actor->SendPFileSystemRequestConstructor(this, params);
+}
+
+void
+FileSystemTaskChildBase::SetRequestResult(const FileSystemResponseValue& aValue)
+{
+ mFileSystem->AssertIsOnOwningThread();
+
+ if (aValue.type() == FileSystemResponseValue::TFileSystemErrorResponse) {
+ FileSystemErrorResponse r = aValue;
+ mErrorValue = r.error();
+ } else {
+ ErrorResult rv;
+ SetSuccessRequestResult(aValue, rv);
+ mErrorValue = rv.StealNSResult();
+ }
+}
+
+bool
+FileSystemTaskChildBase::Recv__delete__(const FileSystemResponseValue& aValue)
+{
+ mFileSystem->AssertIsOnOwningThread();
+
+ SetRequestResult(aValue);
+ HandlerCallback();
+ return true;
+}
+
+void
+FileSystemTaskChildBase::SetError(const nsresult& aErrorValue)
+{
+ mErrorValue = FileSystemErrorFromNsError(aErrorValue);
+}
+
+/**
+ * FileSystemTaskParentBase class
+ */
+
+FileSystemTaskParentBase::FileSystemTaskParentBase(FileSystemBase* aFileSystem,
+ const FileSystemParams& aParam,
+ FileSystemRequestParent* aParent)
+ : mErrorValue(NS_OK)
+ , mFileSystem(aFileSystem)
+ , mRequestParent(aParent)
+ , mBackgroundEventTarget(NS_GetCurrentThread())
+{
+ MOZ_ASSERT(XRE_IsParentProcess(),
+ "Only call from parent process!");
+ MOZ_ASSERT(aFileSystem, "aFileSystem should not be null.");
+ MOZ_ASSERT(aParent);
+ MOZ_ASSERT(mBackgroundEventTarget);
+ AssertIsOnBackgroundThread();
+}
+
+FileSystemTaskParentBase::~FileSystemTaskParentBase()
+{
+ // This task can be released on different threads because we dispatch it (as
+ // runnable) to main-thread, I/O and then back to the PBackground thread.
+ NS_ProxyRelease(mBackgroundEventTarget, mFileSystem.forget());
+ NS_ProxyRelease(mBackgroundEventTarget, mRequestParent.forget());
+}
+
+void
+FileSystemTaskParentBase::Start()
+{
+ AssertIsOnBackgroundThread();
+ mFileSystem->AssertIsOnOwningThread();
+
+ if (NeedToGoToMainThread()) {
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
+ return;
+ }
+
+ DebugOnly<nsresult> rv = DispatchToIOThread(this);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchToIOThread failed");
+}
+
+void
+FileSystemTaskParentBase::HandleResult()
+{
+ AssertIsOnBackgroundThread();
+ mFileSystem->AssertIsOnOwningThread();
+
+ if (mFileSystem->IsShutdown()) {
+ return;
+ }
+
+ MOZ_ASSERT(mRequestParent);
+ Unused << mRequestParent->Send__delete__(mRequestParent, GetRequestResult());
+}
+
+FileSystemResponseValue
+FileSystemTaskParentBase::GetRequestResult() const
+{
+ AssertIsOnBackgroundThread();
+ mFileSystem->AssertIsOnOwningThread();
+
+ if (HasError()) {
+ return FileSystemErrorResponse(mErrorValue);
+ }
+
+ ErrorResult rv;
+ FileSystemResponseValue value = GetSuccessRequestResult(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return FileSystemErrorResponse(rv.StealNSResult());
+ }
+
+ return value;
+}
+
+void
+FileSystemTaskParentBase::SetError(const nsresult& aErrorValue)
+{
+ mErrorValue = FileSystemErrorFromNsError(aErrorValue);
+}
+
+bool
+FileSystemTaskParentBase::NeedToGoToMainThread() const
+{
+ return mFileSystem->NeedToGoToMainThread();
+}
+
+nsresult
+FileSystemTaskParentBase::MainThreadWork()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return mFileSystem->MainThreadWork();
+}
+
+NS_IMETHODIMP
+FileSystemTaskParentBase::Run()
+{
+ // This method can run in 3 different threads. Here why:
+ // 1. if we are on the main-thread it's because the task must do something
+ // here. If no errors are returned we go the step 2.
+ // 2. We can be here directly if the task doesn't have nothing to do on the
+ // main-thread. We are are on the I/O thread and we call IOWork().
+ // 3. Both step 1 (in case of error) and step 2 end up here where return the
+ // value back to the PBackground thread.
+ if (NS_IsMainThread()) {
+ MOZ_ASSERT(NeedToGoToMainThread());
+
+ nsresult rv = MainThreadWork();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SetError(rv);
+
+ // Something when wrong. Let's go to the Background thread directly
+ // skipping the I/O thread step.
+ rv = mBackgroundEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // Next step must happen on the I/O thread.
+ rv = DispatchToIOThread(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ // Run I/O thread tasks
+ if (!IsOnBackgroundThread()) {
+ nsresult rv = IOWork();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SetError(rv);
+ }
+
+ // Let's go back to PBackground thread to finish the work.
+ rv = mBackgroundEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ // If we are here, it's because the I/O work has been done and we have to
+ // handle the result back via IPC.
+ AssertIsOnBackgroundThread();
+ HandleResult();
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla