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

#include "ServiceWorkerUtils.h"
#include "ServiceWorkerPrivate.h"
#include "ServiceWorkerScriptCache.h"
#include "mozilla/dom/ClientIPCTypes.h"
#include "mozilla/dom/ClientState.h"
#include "mozilla/dom/RemoteWorkerTypes.h"
#include "mozilla/dom/WorkerPrivate.h"

namespace mozilla::dom {

using mozilla::ipc::PrincipalInfo;

static_assert(nsIServiceWorkerInfo::STATE_PARSED ==
                  static_cast<uint16_t>(ServiceWorkerState::Parsed),
              "ServiceWorkerState enumeration value should match state values "
              "from nsIServiceWorkerInfo.");
static_assert(nsIServiceWorkerInfo::STATE_INSTALLING ==
                  static_cast<uint16_t>(ServiceWorkerState::Installing),
              "ServiceWorkerState enumeration value should match state values "
              "from nsIServiceWorkerInfo.");
static_assert(nsIServiceWorkerInfo::STATE_INSTALLED ==
                  static_cast<uint16_t>(ServiceWorkerState::Installed),
              "ServiceWorkerState enumeration value should match state values "
              "from nsIServiceWorkerInfo.");
static_assert(nsIServiceWorkerInfo::STATE_ACTIVATING ==
                  static_cast<uint16_t>(ServiceWorkerState::Activating),
              "ServiceWorkerState enumeration value should match state values "
              "from nsIServiceWorkerInfo.");
static_assert(nsIServiceWorkerInfo::STATE_ACTIVATED ==
                  static_cast<uint16_t>(ServiceWorkerState::Activated),
              "ServiceWorkerState enumeration value should match state values "
              "from nsIServiceWorkerInfo.");
static_assert(nsIServiceWorkerInfo::STATE_REDUNDANT ==
                  static_cast<uint16_t>(ServiceWorkerState::Redundant),
              "ServiceWorkerState enumeration value should match state values "
              "from nsIServiceWorkerInfo.");
static_assert(nsIServiceWorkerInfo::STATE_UNKNOWN ==
                  ServiceWorkerStateValues::Count,
              "ServiceWorkerState enumeration value should match state values "
              "from nsIServiceWorkerInfo.");

NS_IMPL_ISUPPORTS(ServiceWorkerInfo, nsIServiceWorkerInfo)

NS_IMETHODIMP
ServiceWorkerInfo::GetId(nsAString& aId) {
  MOZ_ASSERT(NS_IsMainThread());
  aId = mWorkerPrivateId;
  return NS_OK;
}

NS_IMETHODIMP
ServiceWorkerInfo::GetScriptSpec(nsAString& aScriptSpec) {
  MOZ_ASSERT(NS_IsMainThread());
  CopyUTF8toUTF16(mDescriptor.ScriptURL(), aScriptSpec);
  return NS_OK;
}

NS_IMETHODIMP
ServiceWorkerInfo::GetCacheName(nsAString& aCacheName) {
  MOZ_ASSERT(NS_IsMainThread());
  aCacheName = mCacheName;
  return NS_OK;
}

NS_IMETHODIMP
ServiceWorkerInfo::GetState(uint16_t* aState) {
  MOZ_ASSERT(aState);
  MOZ_ASSERT(NS_IsMainThread());
  *aState = static_cast<uint16_t>(State());
  return NS_OK;
}

NS_IMETHODIMP
ServiceWorkerInfo::GetDebugger(nsIWorkerDebugger** aResult) {
  if (NS_WARN_IF(!aResult)) {
    return NS_ERROR_FAILURE;
  }

  return mServiceWorkerPrivate->GetDebugger(aResult);
}

NS_IMETHODIMP
ServiceWorkerInfo::GetHandlesFetchEvents(bool* aValue) {
  MOZ_ASSERT(aValue);
  MOZ_ASSERT(NS_IsMainThread());

  if (mHandlesFetch == Unknown) {
    return NS_ERROR_FAILURE;
  }

  *aValue = HandlesFetch();
  return NS_OK;
}

NS_IMETHODIMP
ServiceWorkerInfo::GetInstalledTime(PRTime* _retval) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(_retval);
  *_retval = mInstalledTime;
  return NS_OK;
}

NS_IMETHODIMP
ServiceWorkerInfo::GetActivatedTime(PRTime* _retval) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(_retval);
  *_retval = mActivatedTime;
  return NS_OK;
}

NS_IMETHODIMP
ServiceWorkerInfo::GetRedundantTime(PRTime* _retval) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(_retval);
  *_retval = mRedundantTime;
  return NS_OK;
}

NS_IMETHODIMP
ServiceWorkerInfo::GetNavigationFaultCount(uint32_t* aNavigationFaultCount) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(aNavigationFaultCount);
  *aNavigationFaultCount = mNavigationFaultCount;
  return NS_OK;
}

NS_IMETHODIMP
ServiceWorkerInfo::GetTestingInjectCancellation(
    nsresult* aTestingInjectCancellation) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(aTestingInjectCancellation);
  *aTestingInjectCancellation = mTestingInjectCancellation;
  return NS_OK;
}

NS_IMETHODIMP
ServiceWorkerInfo::SetTestingInjectCancellation(
    nsresult aTestingInjectCancellation) {
  MOZ_ASSERT(NS_IsMainThread());
  mTestingInjectCancellation = aTestingInjectCancellation;
  return NS_OK;
}

NS_IMETHODIMP
ServiceWorkerInfo::AttachDebugger() {
  return mServiceWorkerPrivate->AttachDebugger();
}

NS_IMETHODIMP
ServiceWorkerInfo::DetachDebugger() {
  return mServiceWorkerPrivate->DetachDebugger();
}

void ServiceWorkerInfo::UpdateState(ServiceWorkerState aState) {
  MOZ_ASSERT(NS_IsMainThread());
#ifdef DEBUG
  // Any state can directly transition to redundant, but everything else is
  // ordered.
  if (aState != ServiceWorkerState::Redundant) {
    MOZ_ASSERT_IF(State() == ServiceWorkerState::EndGuard_,
                  aState == ServiceWorkerState::Installing);
    MOZ_ASSERT_IF(State() == ServiceWorkerState::Installing,
                  aState == ServiceWorkerState::Installed);
    MOZ_ASSERT_IF(State() == ServiceWorkerState::Installed,
                  aState == ServiceWorkerState::Activating);
    MOZ_ASSERT_IF(State() == ServiceWorkerState::Activating,
                  aState == ServiceWorkerState::Activated);
  }
  // Activated can only go to redundant.
  MOZ_ASSERT_IF(State() == ServiceWorkerState::Activated,
                aState == ServiceWorkerState::Redundant);
#endif
  // Flush any pending functional events to the worker when it transitions to
  // the activated state.
  // TODO: Do we care that these events will race with the propagation of the
  //       state change?
  if (State() != aState) {
    mServiceWorkerPrivate->UpdateState(aState);
  }
  mDescriptor.SetState(aState);
  if (State() == ServiceWorkerState::Redundant) {
    serviceWorkerScriptCache::PurgeCache(mPrincipal, mCacheName);
    mServiceWorkerPrivate->NoteDeadServiceWorkerInfo();
  }
}

ServiceWorkerInfo::ServiceWorkerInfo(nsIPrincipal* aPrincipal,
                                     const nsACString& aScope,
                                     uint64_t aRegistrationId,
                                     uint64_t aRegistrationVersion,
                                     const nsACString& aScriptSpec,
                                     const nsAString& aCacheName,
                                     nsLoadFlags aImportsLoadFlags)
    : mPrincipal(aPrincipal),
      mDescriptor(GetNextID(), aRegistrationId, aRegistrationVersion,
                  aPrincipal, aScope, aScriptSpec, ServiceWorkerState::Parsed),
      mCacheName(aCacheName),
      mWorkerPrivateId(ComputeWorkerPrivateId()),
      mImportsLoadFlags(aImportsLoadFlags),
      mCreationTime(PR_Now()),
      mCreationTimeStamp(TimeStamp::Now()),
      mInstalledTime(0),
      mActivatedTime(0),
      mRedundantTime(0),
      mServiceWorkerPrivate(new ServiceWorkerPrivate(this)),
      mSkipWaitingFlag(false),
      mHandlesFetch(Unknown),
      mNavigationFaultCount(0),
      mTestingInjectCancellation(NS_OK) {
  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
  MOZ_ASSERT(mPrincipal);
  // cache origin attributes so we can use them off main thread
  mOriginAttributes = mPrincipal->OriginAttributesRef();
  MOZ_ASSERT(!mDescriptor.ScriptURL().IsEmpty());
  MOZ_ASSERT(!mCacheName.IsEmpty());
  MOZ_ASSERT(!mWorkerPrivateId.IsEmpty());

  // Scripts of a service worker should always be loaded bypass service workers.
  // Otherwise, we might not be able to update a service worker correctly, if
  // there is a service worker generating the script.
  MOZ_DIAGNOSTIC_ASSERT(mImportsLoadFlags &
                        nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
}

ServiceWorkerInfo::~ServiceWorkerInfo() {
  MOZ_ASSERT(mServiceWorkerPrivate);
  mServiceWorkerPrivate->NoteDeadServiceWorkerInfo();
}

static uint64_t gServiceWorkerInfoCurrentID = 0;

uint64_t ServiceWorkerInfo::GetNextID() const {
  return ++gServiceWorkerInfoCurrentID;
}

void ServiceWorkerInfo::PostMessage(RefPtr<ServiceWorkerCloneData>&& aData,
                                    const ClientInfo& aClientInfo,
                                    const ClientState& aClientState) {
  mServiceWorkerPrivate->SendMessageEvent(
      std::move(aData),
      ClientInfoAndState(aClientInfo.ToIPC(), aClientState.ToIPC()));
}

void ServiceWorkerInfo::UpdateInstalledTime() {
  MOZ_ASSERT(State() == ServiceWorkerState::Installed);
  MOZ_ASSERT(mInstalledTime == 0);

  mInstalledTime =
      mCreationTime +
      static_cast<PRTime>(
          (TimeStamp::Now() - mCreationTimeStamp).ToMicroseconds());
}

void ServiceWorkerInfo::UpdateActivatedTime() {
  MOZ_ASSERT(State() == ServiceWorkerState::Activated);
  MOZ_ASSERT(mActivatedTime == 0);

  mActivatedTime =
      mCreationTime +
      static_cast<PRTime>(
          (TimeStamp::Now() - mCreationTimeStamp).ToMicroseconds());
}

void ServiceWorkerInfo::UpdateRedundantTime() {
  MOZ_ASSERT(State() == ServiceWorkerState::Redundant);
  MOZ_ASSERT(mRedundantTime == 0);

  mRedundantTime =
      mCreationTime +
      static_cast<PRTime>(
          (TimeStamp::Now() - mCreationTimeStamp).ToMicroseconds());
}

void ServiceWorkerInfo::SetRegistrationVersion(uint64_t aVersion) {
  mDescriptor.SetRegistrationVersion(aVersion);
}

}  // namespace mozilla::dom
