/* 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/StorageAccessPermissionStatus.h"

#include "PermissionStatusSink.h"
#include "mozilla/AntiTrackingUtils.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/FeaturePolicyUtils.h"
#include "mozilla/dom/PermissionStatus.h"
#include "mozilla/dom/PermissionStatusBinding.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRef.h"
#include "nsGlobalWindowInner.h"
#include "nsIPermissionManager.h"

namespace mozilla::dom {

class StorageAccessPermissionStatusSink final : public PermissionStatusSink {
  Mutex mWorkerRefMutex;

  // Protected by mutex.
  // Created and released on worker-thread. Used also on main-thread.
  RefPtr<WeakWorkerRef> mWeakWorkerRef MOZ_GUARDED_BY(mWorkerRefMutex);

 public:
  StorageAccessPermissionStatusSink(PermissionStatus* aPermissionStatus,
                                    PermissionName aPermissionName,
                                    const nsACString& aPermissionType)
      : PermissionStatusSink(aPermissionStatus, aPermissionName,
                             aPermissionType),
        mWorkerRefMutex("StorageAccessPermissionStatusSink::mWorkerRefMutex") {}

  void Init() {
    if (!NS_IsMainThread()) {
      WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
      MOZ_ASSERT(workerPrivate);

      MutexAutoLock lock(mWorkerRefMutex);

      mWeakWorkerRef =
          WeakWorkerRef::Create(workerPrivate, [self = RefPtr(this)]() {
            MutexAutoLock lock(self->mWorkerRefMutex);
            self->mWeakWorkerRef = nullptr;
          });
    }
  }

 protected:
  bool MaybeUpdatedByOnMainThread(nsIPermission* aPermission) override {
    return false;
  }

  bool MaybeUpdatedByNotifyOnlyOnMainThread(
      nsPIDOMWindowInner* aInnerWindow) override {
    NS_ENSURE_TRUE(aInnerWindow, false);

    nsCOMPtr<nsPIDOMWindowInner> ownerWindow;

    if (mSerialEventTarget->IsOnCurrentThread()) {
      if (!GetPermissionStatus()) {
        return false;
      }
      ownerWindow = GetPermissionStatus()->GetOwnerWindow();
    } else {
      MutexAutoLock lock(mWorkerRefMutex);

      if (!mWeakWorkerRef) {
        return false;
      }

      // If we have mWeakWorkerRef, we haven't received the WorkerRef
      // notification yet.
      WorkerPrivate* workerPrivate = mWeakWorkerRef->GetUnsafePrivate();
      MOZ_ASSERT(workerPrivate);

      ownerWindow = workerPrivate->GetAncestorWindow();
    }

    NS_ENSURE_TRUE(ownerWindow, false);

    return ownerWindow->WindowID() == aInnerWindow->WindowID();
  }

  RefPtr<PermissionStatePromise> ComputeStateOnMainThread() override {
    if (mSerialEventTarget->IsOnCurrentThread()) {
      if (!GetPermissionStatus()) {
        return PermissionStatePromise::CreateAndReject(NS_ERROR_FAILURE,
                                                       __func__);
      }

      nsGlobalWindowInner* window = GetPermissionStatus()->GetOwnerWindow();
      if (NS_WARN_IF(!window)) {
        return PermissionStatePromise::CreateAndReject(NS_ERROR_FAILURE,
                                                       __func__);
      }

      WindowGlobalChild* wgc = window->GetWindowGlobalChild();
      if (NS_WARN_IF(!wgc)) {
        return PermissionStatePromise::CreateAndReject(NS_ERROR_FAILURE,
                                                       __func__);
      }

      // Perform a Permission Policy Request
      if (!FeaturePolicyUtils::IsFeatureAllowed(window->GetExtantDoc(),
                                                u"storage-access"_ns)) {
        return PermissionStatePromise::CreateAndResolve(
            nsIPermissionManager::PROMPT_ACTION, __func__);
      }

      return wgc->SendGetStorageAccessPermission(false)->Then(
          GetMainThreadSerialEventTarget(), __func__,
          [self = RefPtr(this)](uint32_t aAction) {
            // We never reveal PermissionState::Denied here
            return PermissionStatePromise::CreateAndResolve(
                aAction == nsIPermissionManager::ALLOW_ACTION
                    ? aAction
                    : nsIPermissionManager::PROMPT_ACTION,
                __func__);
          },
          [](mozilla::ipc::ResponseRejectReason aError) {
            return PermissionStatePromise::CreateAndReject(NS_ERROR_FAILURE,
                                                           __func__);
          });
    }

    // For workers we already have the correct value in workerPrivate.
    return InvokeAsync(mSerialEventTarget, __func__, [self = RefPtr(this)] {
      if (!self->GetPermissionStatus()) {
        return PermissionStatePromise::CreateAndReject(NS_ERROR_FAILURE,
                                                       __func__);
      }

      WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
      MOZ_ASSERT(workerPrivate);

      return PermissionStatePromise::CreateAndResolve(
          workerPrivate->StorageAccess() == StorageAccess::eAllow
              ? nsIPermissionManager::ALLOW_ACTION
              : nsIPermissionManager::PROMPT_ACTION,
          __func__);
    });
  }
};

StorageAccessPermissionStatus::StorageAccessPermissionStatus(
    nsIGlobalObject* aGlobal)
    : PermissionStatus(aGlobal, PermissionName::Storage_access) {}

already_AddRefed<PermissionStatusSink>
StorageAccessPermissionStatus::CreateSink() {
  RefPtr sink = MakeRefPtr<StorageAccessPermissionStatusSink>(
      this, Name(), GetPermissionType());
  sink->Init();
  return sink.forget();
}

}  // namespace mozilla::dom
