Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/dom/serviceworkers/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 114 kB image not shown  

Quelle  ServiceWorkerManager.cpp   Sprache: C

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

#include <algorithm>

#include "nsCOMPtr.h"
#include "nsICookieJarSettings.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsINamed.h"
#include "nsINetworkInterceptController.h"
#include "nsIMutableArray.h"
#include "nsIPrincipal.h"
#include "nsITimer.h"
#include "nsIUploadChannel2.h"
#include "nsServiceManagerUtils.h"
#include "nsDebug.h"
#include "nsIPermissionManager.h"
#include "nsXULAppAPI.h"

#include "jsapi.h"

#include "mozilla/AppShutdown.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ContentBlockingAllowList.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/ErrorNames.h"
#include "mozilla/LoadContext.h"
#include "mozilla/MozPromise.h"
#include "mozilla/Result.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/Telemetry.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/ClientHandle.h"
#include "mozilla/dom/ClientManager.h"
#include "mozilla/dom/ClientSource.h"
#include "mozilla/dom/ConsoleUtils.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/ErrorEvent.h"
#include "mozilla/dom/Headers.h"
#include "mozilla/dom/InternalHeaders.h"
#include "mozilla/dom/Navigator.h"
#include "mozilla/dom/NotificationEvent.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/Request.h"
#include "mozilla/dom/RootedDictionary.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/dom/SharedWorker.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/dom/WorkerScope.h"
#include "mozilla/extensions/WebExtensionPolicy.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "mozilla/dom/ScriptLoader.h"
#include "mozilla/PermissionManager.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_extensions.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "mozilla/StoragePrincipalHelper.h"
#include "mozilla/Unused.h"
#include "mozilla/EnumSet.h"

#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsIDUtils.h"
#include "nsNetUtil.h"
#include "nsProxyRelease.h"
#include "nsQueryObject.h"
#include "nsTArray.h"

#include "ServiceWorker.h"
#include "ServiceWorkerContainer.h"
#include "ServiceWorkerInfo.h"
#include "ServiceWorkerJobQueue.h"
#include "ServiceWorkerManagerChild.h"
#include "ServiceWorkerPrivate.h"
#include "ServiceWorkerRegisterJob.h"
#include "ServiceWorkerRegistrar.h"
#include "ServiceWorkerRegistration.h"
#include "ServiceWorkerScriptCache.h"
#include "ServiceWorkerShutdownBlocker.h"
#include "ServiceWorkerEvents.h"
#include "ServiceWorkerUnregisterJob.h"
#include "ServiceWorkerUpdateJob.h"
#include "ServiceWorkerUtils.h"
#include "ServiceWorkerQuotaUtils.h"

#ifdef PostMessage
#  undef PostMessage
#endif

mozilla::LazyLogModule sWorkerTelemetryLog("WorkerTelemetry");

#ifdef LOG
#  undef LOG
#endif
#define LOG(_args) MOZ_LOG(sWorkerTelemetryLog, LogLevel::Debug, _args);

using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::ipc;

namespace mozilla::dom {

static_assert(
    nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW ==
        static_cast<uint32_t>(RequestRedirect::Follow),
    "RequestRedirect enumeration value should make Necko Redirect mode value.");
static_assert(
    nsIHttpChannelInternal::REDIRECT_MODE_ERROR ==
        static_cast<uint32_t>(RequestRedirect::Error),
    "RequestRedirect enumeration value should make Necko Redirect mode value.");
static_assert(
    nsIHttpChannelInternal::REDIRECT_MODE_MANUAL ==
        static_cast<uint32_t>(RequestRedirect::Manual),
    "RequestRedirect enumeration value should make Necko Redirect mode value.");
static_assert(
    3 == ContiguousEnumSize<RequestRedirect>::value,
    "RequestRedirect enumeration value should make Necko Redirect mode value.");

static_assert(
    nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT ==
        static_cast<uint32_t>(RequestCache::Default),
    "RequestCache enumeration value should match Necko Cache mode value.");
static_assert(
    nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE ==
        static_cast<uint32_t>(RequestCache::No_store),
    "RequestCache enumeration value should match Necko Cache mode value.");
static_assert(
    nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD ==
        static_cast<uint32_t>(RequestCache::Reload),
    "RequestCache enumeration value should match Necko Cache mode value.");
static_assert(
    nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE ==
        static_cast<uint32_t>(RequestCache::No_cache),
    "RequestCache enumeration value should match Necko Cache mode value.");
static_assert(
    nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE ==
        static_cast<uint32_t>(RequestCache::Force_cache),
    "RequestCache enumeration value should match Necko Cache mode value.");
static_assert(
    nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED ==
        static_cast<uint32_t>(RequestCache::Only_if_cached),
    "RequestCache enumeration value should match Necko Cache mode value.");
static_assert(
    6 == ContiguousEnumSize<RequestCache>::value,
    "RequestCache enumeration value should match Necko Cache mode value.");

static_assert(static_cast<uint16_t>(ServiceWorkerUpdateViaCache::Imports) ==
                  nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
              "nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_*"
              " should match ServiceWorkerUpdateViaCache enumeration.");
static_assert(static_cast<uint16_t>(ServiceWorkerUpdateViaCache::All) ==
                  nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL,
              "nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_*"
              " should match ServiceWorkerUpdateViaCache enumeration.");
static_assert(static_cast<uint16_t>(ServiceWorkerUpdateViaCache::None) ==
                  nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_NONE,
              "nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_*"
              " should match ServiceWorkerUpdateViaCache enumeration.");

static StaticRefPtr<ServiceWorkerManager> gInstance;

namespace {

nsresult PopulateRegistrationData(
    nsIPrincipal* aPrincipal,
    const ServiceWorkerRegistrationInfo* aRegistration,
    ServiceWorkerRegistrationData& aData) {
  MOZ_ASSERT(aPrincipal);
  MOZ_ASSERT(aRegistration);

  if (NS_WARN_IF(!BasePrincipal::Cast(aPrincipal)->IsContentPrincipal())) {
    return NS_ERROR_FAILURE;
  }

  nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &aData.principal());
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  aData.scope() = aRegistration->Scope();

  // TODO: When bug 1426401 is implemented we will need to handle more
  //       than just the active worker here.
  RefPtr<ServiceWorkerInfo> active = aRegistration->GetActive();
  MOZ_ASSERT(active);
  if (NS_WARN_IF(!active)) {
    return NS_ERROR_FAILURE;
  }

  aData.currentWorkerURL() = active->ScriptSpec();
  aData.cacheName() = active->CacheName();
  aData.currentWorkerHandlesFetch() = active->HandlesFetch();

  aData.currentWorkerInstalledTime() = active->GetInstalledTime();
  aData.currentWorkerActivatedTime() = active->GetActivatedTime();

  aData.updateViaCache() =
      static_cast<uint32_t>(aRegistration->GetUpdateViaCache());

  aData.lastUpdateTime() = aRegistration->GetLastUpdateTime();

  aData.navigationPreloadState() = aRegistration->GetNavigationPreloadState();

  MOZ_ASSERT(ServiceWorkerRegistrationDataIsValid(aData));

  return NS_OK;
}

class TeardownRunnable final : public Runnable {
 public:
  explicit TeardownRunnable(ServiceWorkerManagerChild* aActor)
      : Runnable("dom::ServiceWorkerManager::TeardownRunnable"),
        mActor(aActor) {
    MOZ_ASSERT(mActor);
  }

  NS_IMETHOD Run() override {
    MOZ_ASSERT(mActor);
    PServiceWorkerManagerChild::Send__delete__(mActor);
    return NS_OK;
  }

 private:
  ~TeardownRunnable() = default;

  RefPtr<ServiceWorkerManagerChild> mActor;
};

constexpr char kFinishShutdownTopic[] = "profile-before-change-qm";

already_AddRefed<nsIAsyncShutdownClient> GetAsyncShutdownBarrier() {
  AssertIsOnMainThread();

  nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
  MOZ_ASSERT(svc);

  nsCOMPtr<nsIAsyncShutdownClient> barrier;
  DebugOnly<nsresult> rv =
      svc->GetProfileChangeTeardown(getter_AddRefs(barrier));
  MOZ_ASSERT(NS_SUCCEEDED(rv));

  return barrier.forget();
}

Result<nsCOMPtr<nsIPrincipal>, nsresult> ScopeToPrincipal(
    nsIURI* aScopeURI, const OriginAttributes& aOriginAttributes) {
  MOZ_ASSERT(aScopeURI);

  nsCOMPtr<nsIPrincipal> principal =
      BasePrincipal::CreateContentPrincipal(aScopeURI, aOriginAttributes);
  if (NS_WARN_IF(!principal)) {
    return Err(NS_ERROR_FAILURE);
  }

  return principal;
}

Result<nsCOMPtr<nsIPrincipal>, nsresult> ScopeToPrincipal(
    const nsACString& aScope, const OriginAttributes& aOriginAttributes) {
  MOZ_ASSERT(nsContentUtils::IsAbsoluteURL(aScope));

  nsCOMPtr<nsIURI> scopeURI;
  MOZ_TRY(NS_NewURI(getter_AddRefs(scopeURI), aScope));

  return ScopeToPrincipal(scopeURI, aOriginAttributes);
}

}  // namespace

struct ServiceWorkerManager::RegistrationDataPerPrincipal final {
  // Implements a container of keys for the "scope to registration map":
  // https://w3c.github.io/ServiceWorker/#dfn-scope-to-registration-map
  //
  // where each key is an absolute URL.
  //
  // The properties of this map that the spec uses are
  // 1) insertion,
  // 2) removal,
  // 3) iteration of scopes in FIFO order (excluding removed scopes),
  // 4) and finding, for a given path, the maximal length scope which is a
  //    prefix of the path.
  //
  // Additionally, because this is a container of keys for a map, there
  // shouldn't be duplicate scopes.
  //
  // The current implementation uses a dynamic array as the underlying
  // container, which is not optimal for unbounded container sizes (all
  // supported operations are in linear time) but may be superior for small
  // container sizes.
  //
  // If this is proven to be too slow, the underlying storage should be replaced
  // with a linked list of scopes in combination with an ordered map that maps
  // scopes to linked list elements/iterators. This would reduce all of the
  // above operations besides iteration (necessarily linear) to logarithmic
  // time.
  class ScopeContainer final : private nsTArray<nsCString> {
    using Base = nsTArray<nsCString>;

   public:
    using Base::Contains;
    using Base::IsEmpty;
    using Base::Length;

    // No using-declaration to avoid importing the non-const overload.
    decltype(autooperator[](Base::index_type aIndex) const {
      return Base::operator[](aIndex);
    }

    void InsertScope(const nsACString& aScope) {
      MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::IsAbsoluteURL(aScope));

      if (Contains(aScope)) {
        return;
      }

      AppendElement(aScope);
    }

    void RemoveScope(const nsACString& aScope) {
      MOZ_ALWAYS_TRUE(RemoveElement(aScope));
    }

    // Implements most of "Match Service Worker Registration":
    // https://w3c.github.io/ServiceWorker/#scope-match-algorithm
    Maybe<nsCString> MatchScope(const nsACString& aClientUrl) const {
      Maybe<nsCString> match;

      for (const nsCString& scope : *this) {
        if (StringBeginsWith(aClientUrl, scope)) {
          if (!match || scope.Length() > match->Length()) {
            match = Some(scope);
          }
        }
      }

      // Step 7.2:
      // "Assert: matchingScope’s origin and clientURL’s origin are same
      // origin."
      MOZ_DIAGNOSTIC_ASSERT_IF(match, IsSameOrigin(*match, aClientUrl));

      return match;
    }

   private:
    bool IsSameOrigin(const nsACString& aMatchingScope,
                      const nsACString& aClientUrl) const {
      auto parseResult = ScopeToPrincipal(aMatchingScope, OriginAttributes());

      if (NS_WARN_IF(parseResult.isErr())) {
        return false;
      }

      auto scopePrincipal = parseResult.unwrap();

      parseResult = ScopeToPrincipal(aClientUrl, OriginAttributes());

      if (NS_WARN_IF(parseResult.isErr())) {
        return false;
      }

      auto clientPrincipal = parseResult.unwrap();

      bool equals = false;

      if (NS_WARN_IF(
              NS_FAILED(scopePrincipal->Equals(clientPrincipal, &equals)))) {
        return false;
      }

      return equals;
    }
  };

  ScopeContainer mScopeContainer;

  // Scope to registration.
  // The scope should be a fully qualified valid URL.
  nsRefPtrHashtable<nsCStringHashKey, ServiceWorkerRegistrationInfo> mInfos;

  // Maps scopes to job queues.
  nsRefPtrHashtable<nsCStringHashKey, ServiceWorkerJobQueue> mJobQueues;

  // Map scopes to scheduled update timers.
  nsInterfaceHashtable<nsCStringHashKey, nsITimer> mUpdateTimers;

  // The number of times we have done a quota usage check for this origin for
  // mitigation purposes.  See the docs on nsIServiceWorkerRegistrationInfo,
  // where this value is exposed.
  int32_t mQuotaUsageCheckCount = 0;
};

//////////////////////////
// ServiceWorkerManager //
//////////////////////////

NS_IMPL_ADDREF(ServiceWorkerManager)
NS_IMPL_RELEASE(ServiceWorkerManager)

NS_INTERFACE_MAP_BEGIN(ServiceWorkerManager)
  NS_INTERFACE_MAP_ENTRY(nsIServiceWorkerManager)
  NS_INTERFACE_MAP_ENTRY(nsIObserver)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIServiceWorkerManager)
NS_INTERFACE_MAP_END

ServiceWorkerManager::ServiceWorkerManager()
    : mActor(nullptr), mShuttingDown(false) {}

ServiceWorkerManager::~ServiceWorkerManager() {
  // The map will assert if it is not empty when destroyed.
  mRegistrationInfos.Clear();

  // This can happen if the browser is started up in ProfileManager mode, in
  // which case XPCOM will startup and shutdown, but there won't be any
  // profile-* topic notifications. The shutdown blocker expects to be in a
  // NotAcceptingPromises state when it's destroyed, and this transition
  // normally happens in the "profile-change-teardown" notification callback
  // (which won't be called in ProfileManager mode).
  if (!mShuttingDown && mShutdownBlocker) {
    mShutdownBlocker->StopAcceptingPromises();
  }
}

void ServiceWorkerManager::BlockShutdownOn(GenericNonExclusivePromise* aPromise,
                                           uint32_t aShutdownStateId) {
  AssertIsOnMainThread();

  MOZ_ASSERT(mShutdownBlocker);
  MOZ_ASSERT(aPromise);

  mShutdownBlocker->WaitOnPromise(aPromise, aShutdownStateId);
}

void ServiceWorkerManager::Init(ServiceWorkerRegistrar* aRegistrar) {
  // ServiceWorkers now only support parent intercept.  In parent intercept
  // mode, only the parent process ServiceWorkerManager has any state or does
  // anything.
  //
  // It is our goal to completely eliminate support for content process
  // ServiceWorkerManager instances and make getting a SWM instance trigger a
  // fatal assertion.  But until we've reached that point, we make
  // initialization a no-op so that content process ServiceWorkerManager
  // instances will simply have no state and no registrations.
  if (!XRE_IsParentProcess()) {
    return;
  }

  nsCOMPtr<nsIAsyncShutdownClient> shutdownBarrier = GetAsyncShutdownBarrier();

  if (shutdownBarrier) {
    mShutdownBlocker = ServiceWorkerShutdownBlocker::CreateAndRegisterOn(
        *shutdownBarrier, *this);
    MOZ_ASSERT(mShutdownBlocker);
  }

  MOZ_DIAGNOSTIC_ASSERT(aRegistrar);

  PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread();
  if (NS_WARN_IF(!actorChild)) {
    MaybeStartShutdown();
    return;
  }

  PServiceWorkerManagerChild* actor =
      actorChild->SendPServiceWorkerManagerConstructor();
  if (!actor) {
    MaybeStartShutdown();
    return;
  }

  mActor = static_cast<ServiceWorkerManagerChild*>(actor);

  // mActor must be set before LoadRegistrations is called because it can purge
  // service workers if preferences are disabled.
  nsTArray<ServiceWorkerRegistrationData> data;
  aRegistrar->GetRegistrations(data);
  LoadRegistrations(data);

  mTelemetryLastChange = TimeStamp::Now();

  mETPPermissionObserver = new ETPPermissionObserver();
}

void ServiceWorkerManager::RecordTelemetry(uint32_t aNumber, uint32_t aFetch) {
  // Submit N value pairs to Telemetry for the time we were at those values
  auto now = TimeStamp::Now();
  // round down, with a minimum of 1 repeat.  In theory this gives
  // inaccuracy if there are frequent changes, but that's uncommon.
  uint32_t repeats = (uint32_t)((now - mTelemetryLastChange).ToMilliseconds()) /
                     mTelemetryPeriodMs;
  mTelemetryLastChange = now;
  if (repeats == 0) {
    repeats = 1;
  }
  nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
      "ServiceWorkerTelemetryRunnable", [aNumber, aFetch, repeats]() {
        LOG(("ServiceWorkers running: %u samples of %u/%u", repeats, aNumber,
             aFetch));
        // Don't allocate infinitely huge arrays if someone visits a SW site
        // after a few months running. 1 month is about 500K repeats @ 5s
        // sampling
        uint32_t num_repeats = std::min(repeats, 1000000U);  // 4MB max
        nsTArray<uint32_t> values;

        uint32_t* array = values.AppendElements(num_repeats);
        for (uint32_t i = 0; i < num_repeats; i++) {
          array[i] = aNumber;
        }
        Telemetry::Accumulate(Telemetry::SERVICE_WORKER_RUNNING, "All"_ns,
                              values);

        for (uint32_t i = 0; i < num_repeats; i++) {
          array[i] = aFetch;
        }
        Telemetry::Accumulate(Telemetry::SERVICE_WORKER_RUNNING, "Fetch"_ns,
                              values);
      });
  NS_DispatchBackgroundTask(runnable.forget(), nsIEventTarget::DISPATCH_NORMAL);
}

RefPtr<GenericErrorResultPromise> ServiceWorkerManager::StartControllingClient(
    const ClientInfo& aClientInfo,
    ServiceWorkerRegistrationInfo* aRegistrationInfo,
    bool aControlClientHandle) {
  MOZ_DIAGNOSTIC_ASSERT(aRegistrationInfo->GetActive());

  // XXX We can't use a generic lambda (accepting auto&& entry) like elsewhere
  // with WithEntryHandle, since we get linker errors then using clang+lld. This
  // might be a toolchain issue?
  return mControlledClients.WithEntryHandle(
      aClientInfo.Id(),
      [&](decltype(mControlledClients)::EntryHandle&& entry)
          -> RefPtr<GenericErrorResultPromise> {
        const RefPtr<ServiceWorkerManager> self = this;

        const ServiceWorkerDescriptor& active =
            aRegistrationInfo->GetActive()->Descriptor();

        if (entry) {
          const RefPtr<ServiceWorkerRegistrationInfo> old =
              std::move(entry.Data()->mRegistrationInfo);

          const RefPtr<GenericErrorResultPromise> promise =
              aControlClientHandle
                  ? entry.Data()->mClientHandle->Control(active)
                  : GenericErrorResultPromise::CreateAndResolve(false,
                                                                __func__);

          entry.Data()->mRegistrationInfo = aRegistrationInfo;

          if (old != aRegistrationInfo) {
            StopControllingRegistration(old);
            aRegistrationInfo->StartControllingClient();
          }

          // Always check to see if we failed to actually control the client. In
          // that case remove the client from our list of controlled clients.
          return promise->Then(
              GetMainThreadSerialEventTarget(), __func__,
              [](bool) {
                // do nothing on success
                return GenericErrorResultPromise::CreateAndResolve(true,
                                                                   __func__);
              },
              [self, aClientInfo](const CopyableErrorResult& aRv) {
                // failed to control, forget about this client
                self->StopControllingClient(aClientInfo);
                return GenericErrorResultPromise::CreateAndReject(aRv,
                                                                  __func__);
              });
        }

        RefPtr<ClientHandle> clientHandle = ClientManager::CreateHandle(
            aClientInfo, GetMainThreadSerialEventTarget());

        const RefPtr<GenericErrorResultPromise> promise =
            aControlClientHandle
                ? clientHandle->Control(active)
                : GenericErrorResultPromise::CreateAndResolve(false, __func__);

        aRegistrationInfo->StartControllingClient();

        entry.Insert(
            MakeUnique<ControlledClientData>(clientHandle, aRegistrationInfo));

        clientHandle->OnDetach()->Then(
            GetMainThreadSerialEventTarget(), __func__,
            [self, aClientInfo] { self->StopControllingClient(aClientInfo); });

        // Always check to see if we failed to actually control the client.  In
        // that case removed the client from our list of controlled clients.
        return promise->Then(
            GetMainThreadSerialEventTarget(), __func__,
            [](bool) {
              // do nothing on success
              return GenericErrorResultPromise::CreateAndResolve(true,
                                                                 __func__);
            },
            [self, aClientInfo](const CopyableErrorResult& aRv) {
              // failed to control, forget about this client
              self->StopControllingClient(aClientInfo);
              return GenericErrorResultPromise::CreateAndReject(aRv, __func__);
            });
      });
}

void ServiceWorkerManager::StopControllingClient(
    const ClientInfo& aClientInfo) {
  auto entry = mControlledClients.Lookup(aClientInfo.Id());
  if (!entry) {
    return;
  }

  RefPtr<ServiceWorkerRegistrationInfo> reg =
      std::move(entry.Data()->mRegistrationInfo);

  entry.Remove();

  StopControllingRegistration(reg);
}

void ServiceWorkerManager::MaybeStartShutdown() {
  MOZ_ASSERT(NS_IsMainThread());

  if (mShuttingDown) {
    return;
  }

  mShuttingDown = true;

  for (const auto& dataPtr : mRegistrationInfos.Values()) {
    for (const auto& timerEntry : dataPtr->mUpdateTimers.Values()) {
      timerEntry->Cancel();
    }
    dataPtr->mUpdateTimers.Clear();

    for (const auto& queueEntry : dataPtr->mJobQueues.Values()) {
      queueEntry->CancelAll();
    }
    dataPtr->mJobQueues.Clear();

    for (const auto& registrationEntry : dataPtr->mInfos.Values()) {
      registrationEntry->ShutdownWorkers();
    }

    // ServiceWorkerCleanup may try to unregister registrations, so don't clear
    // mInfos.
  }

  for (const auto& entry : mControlledClients.Values()) {
    entry->mRegistrationInfo->ShutdownWorkers();
  }

  for (auto iter = mOrphanedRegistrations.iter(); !iter.done(); iter.next()) {
    iter.get()->ShutdownWorkers();
  }

  if (mShutdownBlocker) {
    mShutdownBlocker->StopAcceptingPromises();
  }

  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
  if (obs) {
    obs->AddObserver(this, kFinishShutdownTopic, false);
    return;
  }

  MaybeFinishShutdown();
}

void ServiceWorkerManager::MaybeFinishShutdown() {
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
  if (obs) {
    obs->RemoveObserver(this, kFinishShutdownTopic);
  }

  if (!mActor) {
    return;
  }

  mActor->ManagerShuttingDown();

  RefPtr<TeardownRunnable> runnable = new TeardownRunnable(mActor);
  nsresult rv = NS_DispatchToMainThread(runnable);
  Unused << NS_WARN_IF(NS_FAILED(rv));
  mActor = nullptr;
  mETPPermissionObserver = nullptr;

  // This also submits final telemetry
  ServiceWorkerPrivate::RunningShutdown();
}

class ServiceWorkerResolveWindowPromiseOnRegisterCallback final
    : public ServiceWorkerJob::Callback {
 public:
  NS_INLINE_DECL_REFCOUNTING(
      ServiceWorkerResolveWindowPromiseOnRegisterCallback, override)

  virtual void JobFinished(ServiceWorkerJob* aJob,
                           ErrorResult& aStatus) override {
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(aJob);

    if (aStatus.Failed()) {
      mPromiseHolder.Reject(CopyableErrorResult(aStatus), __func__);
      return;
    }

    MOZ_ASSERT(aJob->GetType() == ServiceWorkerJob::Type::Register);
    RefPtr<ServiceWorkerRegisterJob> registerJob =
        static_cast<ServiceWorkerRegisterJob*>(aJob);
    RefPtr<ServiceWorkerRegistrationInfo> reg = registerJob->GetRegistration();

    mPromiseHolder.Resolve(reg->Descriptor(), __func__);
  }

  virtual void JobDiscarded(ErrorResult& aStatus) override {
    MOZ_ASSERT(NS_IsMainThread());

    mPromiseHolder.Reject(CopyableErrorResult(aStatus), __func__);
  }

  RefPtr<ServiceWorkerRegistrationPromise> Promise() {
    MOZ_ASSERT(NS_IsMainThread());
    return mPromiseHolder.Ensure(__func__);
  }

 private:
  ~ServiceWorkerResolveWindowPromiseOnRegisterCallback() = default;

  MozPromiseHolder<ServiceWorkerRegistrationPromise> mPromiseHolder;
};

NS_IMETHODIMP
ServiceWorkerManager::RegisterForTest(nsIPrincipal* aPrincipal,
                                      const nsAString& aScopeURL,
                                      const nsAString& aScriptURL,
                                      JSContext* aCx,
                                      mozilla::dom::Promise** aPromise) {
  nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
  if (NS_WARN_IF(!global)) {
    return NS_ERROR_FAILURE;
  }

  ErrorResult erv;
  RefPtr<Promise> outer = Promise::Create(global, erv);
  if (NS_WARN_IF(erv.Failed())) {
    return erv.StealNSResult();
  }

  if (!StaticPrefs::dom_serviceWorkers_testing_enabled()) {
    outer->MaybeRejectWithAbortError(
        "registerForTest only allowed when dom.serviceWorkers.testing.enabled "
        "is true");
    outer.forget(aPromise);
    return NS_OK;
  }

  if (aPrincipal == nullptr) {
    outer->MaybeRejectWithAbortError("Missing principal");
    outer.forget(aPromise);
    return NS_OK;
  }

  if (aScriptURL.IsEmpty()) {
    outer->MaybeRejectWithAbortError("Missing script url");
    outer.forget(aPromise);
    return NS_OK;
  }

  if (aScopeURL.IsEmpty()) {
    outer->MaybeRejectWithAbortError("Missing scope url");
    outer.forget(aPromise);
    return NS_OK;
  }

  // The ClientType isn't really used here, but ClientType::Window
  // is the least bad choice since this is happening on the main thread.
  Maybe<ClientInfo> clientInfo =
      dom::ClientManager::CreateInfo(ClientType::Window, aPrincipal);

  if (!clientInfo.isSome()) {
    outer->MaybeRejectWithUnknownError("Error creating clientInfo");
    outer.forget(aPromise);
    return NS_OK;
  }

  auto scope = NS_ConvertUTF16toUTF8(aScopeURL);
  auto scriptURL = NS_ConvertUTF16toUTF8(aScriptURL);

  auto regPromise = Register(clientInfo.ref(), scope, scriptURL,
                             dom::ServiceWorkerUpdateViaCache::Imports);
  const RefPtr<ServiceWorkerManager> self(this);
  const nsCOMPtr<nsIPrincipal> principal(aPrincipal);
  regPromise->Then(
      GetMainThreadSerialEventTarget(), __func__,
      [self, outer, principal,
       scope](const ServiceWorkerRegistrationDescriptor& regDesc) {
        RefPtr<ServiceWorkerRegistrationInfo> registration =
            self->GetRegistration(principal, NS_ConvertUTF16toUTF8(scope));
        if (registration) {
          outer->MaybeResolve(registration);
        } else {
          outer->MaybeRejectWithUnknownError(
              "Failed to retrieve ServiceWorkerRegistrationInfo");
        }
      },
      [outer](const mozilla::CopyableErrorResult& err) {
        CopyableErrorResult result(err);
        outer->MaybeReject(std::move(result));
      });

  outer.forget(aPromise);

  return NS_OK;
}

RefPtr<ServiceWorkerRegistrationPromise> ServiceWorkerManager::Register(
    const ClientInfo& aClientInfo, const nsACString& aScopeURL,
    const nsACString& aScriptURL, ServiceWorkerUpdateViaCache aUpdateViaCache) {
  AUTO_PROFILER_MARKER_TEXT("SWM Register", DOM, {}, ""_ns);

  nsCOMPtr<nsIURI> scopeURI;
  nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScopeURL);
  if (NS_FAILED(rv)) {
    // Odd, since it was serialiazed from an nsIURI.
    CopyableErrorResult err;
    err.ThrowInvalidStateError("Scope URL cannot be parsed");
    return ServiceWorkerRegistrationPromise::CreateAndReject(err, __func__);
  }

  nsCOMPtr<nsIURI> scriptURI;
  rv = NS_NewURI(getter_AddRefs(scriptURI), aScriptURL);
  if (NS_FAILED(rv)) {
    // Odd, since it was serialiazed from an nsIURI.
    CopyableErrorResult err;
    err.ThrowInvalidStateError("Script URL cannot be parsed");
    return ServiceWorkerRegistrationPromise::CreateAndReject(err, __func__);
  }

  IgnoredErrorResult err;
  ServiceWorkerScopeAndScriptAreValid(aClientInfo, scopeURI, scriptURI, err);
  if (err.Failed()) {
    return ServiceWorkerRegistrationPromise::CreateAndReject(
        CopyableErrorResult(std::move(err)), __func__);
  }

  // If the previous validation step passed then we must have a principal.
  auto principalOrErr = aClientInfo.GetPrincipal();

  if (NS_WARN_IF(principalOrErr.isErr())) {
    return ServiceWorkerRegistrationPromise::CreateAndReject(
        CopyableErrorResult(principalOrErr.unwrapErr()), __func__);
  }

  nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
  nsAutoCString scopeKey;
  rv = PrincipalToScopeKey(principal, scopeKey);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return ServiceWorkerRegistrationPromise::CreateAndReject(
        CopyableErrorResult(rv), __func__);
  }

  RefPtr<ServiceWorkerJobQueue> queue =
      GetOrCreateJobQueue(scopeKey, aScopeURL);

  RefPtr<ServiceWorkerResolveWindowPromiseOnRegisterCallback> cb =
      new ServiceWorkerResolveWindowPromiseOnRegisterCallback();

  auto lifetime = DetermineLifetimeForClient(aClientInfo);

  RefPtr<ServiceWorkerRegisterJob> job = new ServiceWorkerRegisterJob(
      principal, aScopeURL, aScriptURL,
      static_cast<ServiceWorkerUpdateViaCache>(aUpdateViaCache), lifetime);

  job->AppendResultCallback(cb);
  queue->ScheduleJob(job);

  MOZ_ASSERT(NS_IsMainThread());

  return cb->Promise();
}

/*
 * Implements the async aspects of the getRegistrations algorithm.
 */

class GetRegistrationsRunnable final : public Runnable {
  const ClientInfo mClientInfo;
  RefPtr<ServiceWorkerRegistrationListPromise::Private> mPromise;

 public:
  explicit GetRegistrationsRunnable(const ClientInfo& aClientInfo)
      : Runnable("dom::ServiceWorkerManager::GetRegistrationsRunnable"),
        mClientInfo(aClientInfo),
        mPromise(new ServiceWorkerRegistrationListPromise::Private(__func__)) {}

  RefPtr<ServiceWorkerRegistrationListPromise> Promise() const {
    return mPromise;
  }

  NS_IMETHOD
  Run() override {
    auto scopeExit = MakeScopeExit(
        [&] { mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__); });

    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
    if (!swm) {
      return NS_OK;
    }

    auto principalOrErr = mClientInfo.GetPrincipal();
    if (NS_WARN_IF(principalOrErr.isErr())) {
      return NS_OK;
    }

    nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();

    nsTArray<ServiceWorkerRegistrationDescriptor> array;

    if (NS_WARN_IF(!BasePrincipal::Cast(principal)->IsContentPrincipal())) {
      return NS_OK;
    }

    nsAutoCString scopeKey;
    nsresult rv = swm->PrincipalToScopeKey(principal, scopeKey);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    ServiceWorkerManager::RegistrationDataPerPrincipal* data;
    if (!swm->mRegistrationInfos.Get(scopeKey, &data)) {
      scopeExit.release();
      mPromise->Resolve(array, __func__);
      return NS_OK;
    }

    for (uint32_t i = 0; i < data->mScopeContainer.Length(); ++i) {
      RefPtr<ServiceWorkerRegistrationInfo> info =
          data->mInfos.GetWeak(data->mScopeContainer[i]);

      NS_ConvertUTF8toUTF16 scope(data->mScopeContainer[i]);

      nsCOMPtr<nsIURI> scopeURI;
      nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), scope);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        break;
      }

      // Unfortunately we don't seem to have an obvious window id here; in
      // particular ClientInfo does not have one, and neither do service worker
      // registrations, as far as I can tell.
      rv = principal->CheckMayLoadWithReporting(
          scopeURI, false /* allowIfInheritsPrincipal */,
          0 /* innerWindowID */);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        continue;
      }

      array.AppendElement(info->Descriptor());
    }

    scopeExit.release();
    mPromise->Resolve(array, __func__);

    return NS_OK;
  }
};

RefPtr<ServiceWorkerRegistrationListPromise>
ServiceWorkerManager::GetRegistrations(const ClientInfo& aClientInfo) const {
  RefPtr<GetRegistrationsRunnable> runnable =
      new GetRegistrationsRunnable(aClientInfo);
  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(runnable));
  return runnable->Promise();
}

/*
 * Implements the async aspects of the getRegistration algorithm.
 */

class GetRegistrationRunnable final : public Runnable {
  const ClientInfo mClientInfo;
  RefPtr<ServiceWorkerRegistrationPromise::Private> mPromise;
  nsCString mURL;

 public:
  GetRegistrationRunnable(const ClientInfo& aClientInfo, const nsACString& aURL)
      : Runnable("dom::ServiceWorkerManager::GetRegistrationRunnable"),
        mClientInfo(aClientInfo),
        mPromise(new ServiceWorkerRegistrationPromise::Private(__func__)),
        mURL(aURL) {}

  RefPtr<ServiceWorkerRegistrationPromise> Promise() const { return mPromise; }

  NS_IMETHOD
  Run() override {
    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
    if (!swm) {
      mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
      return NS_OK;
    }

    auto principalOrErr = mClientInfo.GetPrincipal();
    if (NS_WARN_IF(principalOrErr.isErr())) {
      mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
      return NS_OK;
    }

    nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
    nsCOMPtr<nsIURI> uri;
    nsresult rv = NS_NewURI(getter_AddRefs(uri), mURL);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      mPromise->Reject(rv, __func__);
      return NS_OK;
    }

    // Unfortunately we don't seem to have an obvious window id here; in
    // particular ClientInfo does not have one, and neither do service worker
    // registrations, as far as I can tell.
    rv = principal->CheckMayLoadWithReporting(
        uri, false /* allowIfInheritsPrincipal */, 0 /* innerWindowID */);
    if (NS_FAILED(rv)) {
      mPromise->Reject(NS_ERROR_DOM_SECURITY_ERR, __func__);
      return NS_OK;
    }

    RefPtr<ServiceWorkerRegistrationInfo> registration =
        swm->GetServiceWorkerRegistrationInfo(principal, uri);

    if (!registration) {
      // Reject with NS_OK means "not found".
      mPromise->Reject(NS_OK, __func__);
      return NS_OK;
    }

    mPromise->Resolve(registration->Descriptor(), __func__);

    return NS_OK;
  }
};

RefPtr<ServiceWorkerRegistrationPromise> ServiceWorkerManager::GetRegistration(
    const ClientInfo& aClientInfo, const nsACString& aURL) const {
  MOZ_ASSERT(NS_IsMainThread());

  RefPtr<GetRegistrationRunnable> runnable =
      new GetRegistrationRunnable(aClientInfo, aURL);
  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(runnable));

  return runnable->Promise();
}

NS_IMETHODIMP
ServiceWorkerManager::SendPushEvent(const nsACString& aOriginAttributes,
                                    const nsACString& aScope,
                                    const nsTArray<uint8_t>& aDataBytes,
                                    uint8_t optional_argc) {
  if (optional_argc == 1) {
    // This does one copy here (while constructing the Maybe) and another when
    // we end up copying into the SendPushEventRunnable.  We could fix that to
    // only do one copy by making things between here and there take
    // Maybe<nsTArray<uint8_t>>&&, but then we'd need to copy before we know
    // whether we really need to in PushMessageDispatcher::NotifyWorkers.  Since
    // in practice this only affects JS callers that pass data, and we don't
    // have any right now, let's not worry about it.
    return SendPushEvent(aOriginAttributes, aScope, u""_ns,
                         Some(aDataBytes.Clone()));
  }
  MOZ_ASSERT(optional_argc == 0);
  return SendPushEvent(aOriginAttributes, aScope, u""_ns, Nothing());
}

nsresult ServiceWorkerManager::SendPushEvent(
    const nsACString& aOriginAttributes, const nsACString& aScope,
    const nsAString& aMessageId, const Maybe<nsTArray<uint8_t>>& aData) {
  OriginAttributes attrs;
  if (!attrs.PopulateFromSuffix(aOriginAttributes)) {
    return NS_ERROR_INVALID_ARG;
  }

  nsCOMPtr<nsIPrincipal> principal;
  MOZ_TRY_VAR(principal, ScopeToPrincipal(aScope, attrs));

  // The registration handling a push notification must have an exact scope
  // match. This will try to find an exact match, unlike how fetch may find the
  // registration with the longest scope that's a prefix of the fetched URL.
  RefPtr<ServiceWorkerRegistrationInfo> registration =
      GetRegistration(principal, aScope);
  if (NS_WARN_IF(!registration)) {
    return NS_ERROR_FAILURE;
  }

  MOZ_DIAGNOSTIC_ASSERT(registration->Scope().Equals(aScope));

  ServiceWorkerInfo* serviceWorker = registration->GetActive();
  if (NS_WARN_IF(!serviceWorker)) {
    return NS_ERROR_FAILURE;
  }

  return serviceWorker->WorkerPrivate()->SendPushEvent(aMessageId, aData,
                                                       registration);
}

NS_IMETHODIMP
ServiceWorkerManager::SendPushSubscriptionChangeEvent(
    const nsACString& aOriginAttributes, const nsACString& aScope) {
  OriginAttributes attrs;
  if (!attrs.PopulateFromSuffix(aOriginAttributes)) {
    return NS_ERROR_INVALID_ARG;
  }

  ServiceWorkerInfo* info = GetActiveWorkerInfoForScope(attrs, aScope);
  if (!info) {
    return NS_ERROR_FAILURE;
  }
  return info->WorkerPrivate()->SendPushSubscriptionChangeEvent();
}

nsresult ServiceWorkerManager::SendNotificationEvent(
    const nsAString& aEventName, const nsACString& aOriginSuffix,
    const nsAString& aScope, const nsAString& aID, const nsAString& aTitle,
    const nsAString& aDir, const nsAString& aLang, const nsAString& aBody,
    const nsAString& aTag, const nsAString& aIcon, const nsAString& aData) {
  OriginAttributes attrs;
  if (!attrs.PopulateFromSuffix(aOriginSuffix)) {
    return NS_ERROR_INVALID_ARG;
  }

  ServiceWorkerInfo* info =
      GetActiveWorkerInfoForScope(attrs, NS_ConvertUTF16toUTF8(aScope));
  if (!info) {
    return NS_ERROR_FAILURE;
  }

  ServiceWorkerPrivate* workerPrivate = info->WorkerPrivate();
  return workerPrivate->SendNotificationEvent(
      aEventName, aID, aTitle, aDir, aLang, aBody, aTag, aIcon, aData, aScope);
}

NS_IMETHODIMP
ServiceWorkerManager::SendNotificationClickEvent(
    const nsACString& aOriginSuffix, const nsAString& aScope,
    const nsAString& aID, const nsAString& aTitle, const nsAString& aDir,
    const nsAString& aLang, const nsAString& aBody, const nsAString& aTag,
    const nsAString& aIcon, const nsAString& aData) {
  return SendNotificationEvent(nsLiteralString(NOTIFICATION_CLICK_EVENT_NAME),
                               aOriginSuffix, aScope, aID, aTitle, aDir, aLang,
                               aBody, aTag, aIcon, aData);
}

NS_IMETHODIMP
ServiceWorkerManager::SendNotificationCloseEvent(
    const nsACString& aOriginSuffix, const nsAString& aScope,
    const nsAString& aID, const nsAString& aTitle, const nsAString& aDir,
    const nsAString& aLang, const nsAString& aBody, const nsAString& aTag,
    const nsAString& aIcon, const nsAString& aData) {
  return SendNotificationEvent(nsLiteralString(NOTIFICATION_CLOSE_EVENT_NAME),
                               aOriginSuffix, aScope, aID, aTitle, aDir, aLang,
                               aBody, aTag, aIcon, aData);
}

RefPtr<ServiceWorkerRegistrationPromise> ServiceWorkerManager::WhenReady(
    const ClientInfo& aClientInfo) {
  AssertIsOnMainThread();

  for (auto& prd : mPendingReadyList) {
    if (prd->mClientHandle->Info().Id() == aClientInfo.Id() &&
        prd->mClientHandle->Info().PrincipalInfo() ==
            aClientInfo.PrincipalInfo()) {
      return prd->mPromise;
    }
  }

  RefPtr<ServiceWorkerRegistrationInfo> reg =
      GetServiceWorkerRegistrationInfo(aClientInfo);
  if (reg && reg->GetActive()) {
    return ServiceWorkerRegistrationPromise::CreateAndResolve(reg->Descriptor(),
                                                              __func__);
  }

  nsCOMPtr<nsISerialEventTarget> target = GetMainThreadSerialEventTarget();

  RefPtr<ClientHandle> handle =
      ClientManager::CreateHandle(aClientInfo, target);
  mPendingReadyList.AppendElement(MakeUnique<PendingReadyData>(handle));

  RefPtr<ServiceWorkerManager> self(this);
  handle->OnDetach()->Then(target, __func__,
                           [self = std::move(self), aClientInfo] {
                             self->RemovePendingReadyPromise(aClientInfo);
                           });

  return mPendingReadyList.LastElement()->mPromise;
}

void ServiceWorkerManager::CheckPendingReadyPromises() {
  nsTArray<UniquePtr<PendingReadyData>> pendingReadyList =
      std::move(mPendingReadyList);
  for (uint32_t i = 0; i < pendingReadyList.Length(); ++i) {
    UniquePtr<PendingReadyData> prd(std::move(pendingReadyList[i]));

    RefPtr<ServiceWorkerRegistrationInfo> reg =
        GetServiceWorkerRegistrationInfo(prd->mClientHandle->Info());

    if (reg && reg->GetActive()) {
      prd->mPromise->Resolve(reg->Descriptor(), __func__);
    } else {
      mPendingReadyList.AppendElement(std::move(prd));
    }
  }
}

void ServiceWorkerManager::RemovePendingReadyPromise(
    const ClientInfo& aClientInfo) {
  nsTArray<UniquePtr<PendingReadyData>> pendingReadyList =
      std::move(mPendingReadyList);
  for (uint32_t i = 0; i < pendingReadyList.Length(); ++i) {
    UniquePtr<PendingReadyData> prd(std::move(pendingReadyList[i]));

    if (prd->mClientHandle->Info().Id() == aClientInfo.Id() &&
        prd->mClientHandle->Info().PrincipalInfo() ==
            aClientInfo.PrincipalInfo()) {
      prd->mPromise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
    } else {
      mPendingReadyList.AppendElement(std::move(prd));
    }
  }
}

void ServiceWorkerManager::NoteInheritedController(
    const ClientInfo& aClientInfo, const ServiceWorkerDescriptor& aController) {
  MOZ_ASSERT(NS_IsMainThread());

  auto principalOrErr = PrincipalInfoToPrincipal(aController.PrincipalInfo());

  if (NS_WARN_IF(principalOrErr.isErr())) {
    return;
  }

  nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
  nsCOMPtr<nsIURI> scope;
  nsresult rv = NS_NewURI(getter_AddRefs(scope), aController.Scope());
  NS_ENSURE_SUCCESS_VOID(rv);

  RefPtr<ServiceWorkerRegistrationInfo> registration =
      GetServiceWorkerRegistrationInfo(principal, scope);
  NS_ENSURE_TRUE_VOID(registration);
  NS_ENSURE_TRUE_VOID(registration->GetActive());

  StartControllingClient(aClientInfo, registration,
                         false /* aControlClientHandle */);
}

ServiceWorkerInfo* ServiceWorkerManager::GetActiveWorkerInfoForScope(
    const OriginAttributes& aOriginAttributes, const nsACString& aScope) {
  MOZ_ASSERT(NS_IsMainThread());

  nsCOMPtr<nsIURI> scopeURI;
  nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope);
  if (NS_FAILED(rv)) {
    return nullptr;
  }

  auto result = ScopeToPrincipal(scopeURI, aOriginAttributes);
  if (NS_WARN_IF(result.isErr())) {
    return nullptr;
  }

  auto principal = result.unwrap();

  RefPtr<ServiceWorkerRegistrationInfo> registration =
      GetServiceWorkerRegistrationInfo(principal, scopeURI);
  if (!registration) {
    return nullptr;
  }

  return registration->GetActive();
}

ServiceWorkerInfo* ServiceWorkerManager::GetServiceWorkerByClientInfo(
    const ClientInfo& aClientInfo) const {
  MOZ_ASSERT(NS_IsMainThread());

  auto principalOrErr = aClientInfo.GetPrincipal();
  if (NS_WARN_IF(principalOrErr.isErr())) {
    return nullptr;
  }

  nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();

  nsAutoCString scopeKey;
  nsresult rv = PrincipalToScopeKey(principal, scopeKey);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return nullptr;
  }

  RegistrationDataPerPrincipal* data;
  if (!mRegistrationInfos.Get(scopeKey, &data)) {
    return nullptr;
  }

  // If the ClientInfo has CSP data populated, we need to normalize that off, so
  // make a copy and only propagate the non-CSP fields.
  ClientInfo normalized = ClientInfo(
      aClientInfo.Id(), aClientInfo.AgentClusterId(), aClientInfo.Type(),
      aClientInfo.PrincipalInfo(), aClientInfo.CreationTime(),
      aClientInfo.URL(), aClientInfo.FrameType());

  for (const auto& registration : data->mInfos.Values()) {
    ServiceWorkerInfo* info = registration->GetByClientInfo(normalized);
    if (info) {
      return info;
    }
  }

  return nullptr;
}

ServiceWorkerInfo* ServiceWorkerManager::GetServiceWorkerByDescriptor(
    const ServiceWorkerDescriptor& aServiceWorker) const {
  auto principalOrErr = aServiceWorker.GetPrincipal();
  if (NS_WARN_IF(principalOrErr.isErr())) {
    return nullptr;
  }

  nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();

  RefPtr<ServiceWorkerRegistrationInfo> registration =
      GetRegistration(principal, aServiceWorker.Scope());
  if (NS_WARN_IF(!registration)) {
    return nullptr;
  }

  return registration->GetByDescriptor(aServiceWorker);
}

namespace {

class UnregisterJobCallback final : public ServiceWorkerJob::Callback {
  nsCOMPtr<nsIServiceWorkerUnregisterCallback> mCallback;

  ~UnregisterJobCallback() { MOZ_ASSERT(!mCallback); }

 public:
  explicit UnregisterJobCallback(nsIServiceWorkerUnregisterCallback* aCallback)
      : mCallback(aCallback) {
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(mCallback);
  }

  void JobFinished(ServiceWorkerJob* aJob, ErrorResult& aStatus) override {
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(aJob);
    MOZ_ASSERT(mCallback);

    auto scopeExit = MakeScopeExit([&]() { mCallback = nullptr; });

    if (aStatus.Failed()) {
      mCallback->UnregisterFailed();
      return;
    }

    MOZ_ASSERT(aJob->GetType() == ServiceWorkerJob::Type::Unregister);
    RefPtr<ServiceWorkerUnregisterJob> unregisterJob =
        static_cast<ServiceWorkerUnregisterJob*>(aJob);
    mCallback->UnregisterSucceeded(unregisterJob->GetResult());
  }

  void JobDiscarded(ErrorResult&) override {
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(mCallback);

    mCallback->UnregisterFailed();
    mCallback = nullptr;
  }

  NS_INLINE_DECL_REFCOUNTING(UnregisterJobCallback, override)
};

}  // anonymous namespace

NS_IMETHODIMP
ServiceWorkerManager::Unregister(nsIPrincipal* aPrincipal,
                                 nsIServiceWorkerUnregisterCallback* aCallback,
                                 const nsAString& aScope) {
  MOZ_ASSERT(NS_IsMainThread());

  if (!aPrincipal) {
    return NS_ERROR_FAILURE;
  }

  nsresult rv;

// This is not accessible by content, and callers should always ensure scope is
// a correct URI, so this is wrapped in DEBUG
#ifdef DEBUG
  nsCOMPtr<nsIURI> scopeURI;
  rv = NS_NewURI(getter_AddRefs(scopeURI), aScope);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return NS_ERROR_DOM_SECURITY_ERR;
  }
#endif

  nsAutoCString scopeKey;
  rv = PrincipalToScopeKey(aPrincipal, scopeKey);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  NS_ConvertUTF16toUTF8 scope(aScope);
  RefPtr<ServiceWorkerJobQueue> queue = GetOrCreateJobQueue(scopeKey, scope);

  RefPtr<ServiceWorkerUnregisterJob> job =
      new ServiceWorkerUnregisterJob(aPrincipal, scope);

  if (aCallback) {
    RefPtr<UnregisterJobCallback> cb = new UnregisterJobCallback(aCallback);
    job->AppendResultCallback(cb);
  }

  queue->ScheduleJob(job);
  return NS_OK;
}

void ServiceWorkerManager::WorkerIsIdle(ServiceWorkerInfo* aWorker) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_DIAGNOSTIC_ASSERT(aWorker);

  RefPtr<ServiceWorkerRegistrationInfo> reg =
      GetRegistration(aWorker->Principal(), aWorker->Scope());
  if (!reg) {
    return;
  }

  // We only care about the active worker becoming idle because it means we
  // can now promote a waiting worker to active.
  if (reg->GetActive() != aWorker) {
    return;
  }

  // The active worker becoming idle is not a reason to extend a waiting SW's
  // lifetime (or spawn it) on its own because that potentially enables
  // unlimited lifetime extension.  `TryToActivate` will handle upgrading the
  // lifetime extension to a full extension if the registration is controlling
  // pages (and skipWaiting was used).  It's fine for the ServiceWorker to
  // receive its activation message prior to its next functional event.
  reg->TryToActivateAsync(
      ServiceWorkerLifetimeExtension(NoLifetimeExtension{}));
}

already_AddRefed<ServiceWorkerJobQueue>
ServiceWorkerManager::GetOrCreateJobQueue(const nsACString& aKey,
                                          const nsACString& aScope) {
  MOZ_ASSERT(!aKey.IsEmpty());
  ServiceWorkerManager::RegistrationDataPerPrincipal* data;
  // XXX we could use WithEntryHandle here to avoid a hashtable lookup, except
  // that leads to a false positive assertion, see bug 1370674 comment 7.
  if (!mRegistrationInfos.Get(aKey, &data)) {
    data = mRegistrationInfos
               .InsertOrUpdate(aKey, MakeUnique<RegistrationDataPerPrincipal>())
               .get();
  }

  RefPtr queue = data->mJobQueues.GetOrInsertNew(aScope);
  return queue.forget();
}

/* static */
already_AddRefed<ServiceWorkerManager> ServiceWorkerManager::GetInstance() {
  if (!gInstance) {
    RefPtr<ServiceWorkerRegistrar> swr;

    // XXX: Substitute this with an assertion. See comment in Init.
    if (XRE_IsParentProcess()) {
      // Don't (re-)create the ServiceWorkerManager if we are already shutting
      // down.
      if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
        return nullptr;
      }
      // Don't create the ServiceWorkerManager until the ServiceWorkerRegistrar
      // is initialized.
      swr = ServiceWorkerRegistrar::Get();
      if (!swr) {
        return nullptr;
      }
    }

    MOZ_ASSERT(NS_IsMainThread());

    gInstance = new ServiceWorkerManager();
    gInstance->Init(swr);
    ClearOnShutdown(&gInstance);
  }
  RefPtr<ServiceWorkerManager> copy = gInstance.get();
  return copy.forget();
}

void ServiceWorkerManager::ReportToAllClients(
    const nsCString& aScope, const nsString& aMessage,
    const nsCString& aFilename, const nsString& aLine, uint32_t aLineNumber,
    uint32_t aColumnNumber, uint32_t aFlags) {
  ConsoleUtils::ReportForServiceWorkerScope(
      NS_ConvertUTF8toUTF16(aScope), aMessage, aFilename, aLineNumber,
      aColumnNumber, ConsoleUtils::eError);
}

/* static */
void ServiceWorkerManager::LocalizeAndReportToAllClients(
    const nsCString& aScope, const char* aStringKey,
    const nsTArray<nsString>& aParamArray, uint32_t aFlags,
    const nsCString& aFilename, const nsString& aLine, uint32_t aLineNumber,
    uint32_t aColumnNumber) {
  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
  if (!swm) {
    return;
  }

  nsresult rv;
  nsAutoString message;
  rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
                                             aStringKey, aParamArray, message);
  if (NS_SUCCEEDED(rv)) {
    swm->ReportToAllClients(aScope, message, aFilename, aLine, aLineNumber,
                            aColumnNumber, aFlags);
  } else {
    NS_WARNING("Failed to format and therefore report localized error.");
  }
}

void ServiceWorkerManager::HandleError(
    JSContext* aCx, nsIPrincipal* aPrincipal, const nsCString& aScope,
    const nsCString& aWorkerURL, const nsString& aMessage,
    const nsCString& aFilename, const nsString& aLine, uint32_t aLineNumber,
    uint32_t aColumnNumber, uint32_t aFlags, JSExnType aExnType) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(aPrincipal);

  nsAutoCString scopeKey;
  nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }

  ServiceWorkerManager::RegistrationDataPerPrincipal* data;
  if (NS_WARN_IF(!mRegistrationInfos.Get(scopeKey, &data))) {
    return;
  }

  // Always report any uncaught exceptions or errors to the console of
  // each client.
  ReportToAllClients(aScope, aMessage, aFilename, aLine, aLineNumber,
                     aColumnNumber, aFlags);
}

void ServiceWorkerManager::PurgeServiceWorker(
    const ServiceWorkerRegistrationData& aRegistration,
    nsIPrincipal* aPrincipal) {
  MOZ_ASSERT(mActor);
  serviceWorkerScriptCache::PurgeCache(aPrincipal, aRegistration.cacheName());
  MaybeSendUnregister(aPrincipal, aRegistration.scope());
}

void ServiceWorkerManager::LoadRegistration(
    const ServiceWorkerRegistrationData& aRegistration) {
  MOZ_ASSERT(NS_IsMainThread());

  auto principalOrErr = PrincipalInfoToPrincipal(aRegistration.principal());
  if (NS_WARN_IF(principalOrErr.isErr())) {
    return;
  }
  nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();

  if (!StaticPrefs::dom_serviceWorkers_enabled()) {
    // If service workers are disabled, remove the registration from disk
    // instead of loading.
    PurgeServiceWorker(aRegistration, principal);
    return;
  }

  // Purge extensions registrations if they are disabled by prefs.
  if (!StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup()) {
    nsCOMPtr<nsIURI> uri = principal->GetURI();

    // We do check the URI scheme here because when this is going to run
    // the extension may not have been loaded yet and the WebExtensionPolicy
    // may not exist yet.
    if (uri->SchemeIs("moz-extension")) {
      PurgeServiceWorker(aRegistration, principal);
      return;
    }
  }

  RefPtr<ServiceWorkerRegistrationInfo> registration =
      GetRegistration(principal, aRegistration.scope());
  if (!registration) {
    registration =
        CreateNewRegistration(aRegistration.scope(), principal,
                              static_cast<ServiceWorkerUpdateViaCache>(
                                  aRegistration.updateViaCache()),
                              aRegistration.navigationPreloadState());
  } else {
    // If active worker script matches our expectations for a "current worker",
    // then we are done. Since scripts with the same URL might have different
    // contents such as updated scripts or scripts with different LoadFlags, we
    // use the CacheName to judge whether the two scripts are identical, where
    // the CacheName is an UUID generated when a new script is found.
    if (registration->GetActive() &&
        registration->GetActive()->CacheName() == aRegistration.cacheName()) {
      // No needs for updates.
      return;
    }
  }

  registration->SetLastUpdateTime(aRegistration.lastUpdateTime());

  nsLoadFlags importsLoadFlags = nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
  if (aRegistration.updateViaCache() !=
      static_cast<uint16_t>(ServiceWorkerUpdateViaCache::None)) {
    importsLoadFlags |= nsIRequest::VALIDATE_ALWAYS;
  }

  const nsCString& currentWorkerURL = aRegistration.currentWorkerURL();
  if (!currentWorkerURL.IsEmpty()) {
    registration->SetActive(new ServiceWorkerInfo(
        registration->Principal(), registration->Scope(), registration->Id(),
        registration->Version(), currentWorkerURL, aRegistration.cacheName(),
        importsLoadFlags));
    registration->GetActive()->SetHandlesFetch(
        aRegistration.currentWorkerHandlesFetch());
    registration->GetActive()->SetInstalledTime(
        aRegistration.currentWorkerInstalledTime());
    registration->GetActive()->SetActivatedTime(
        aRegistration.currentWorkerActivatedTime());
  }
}

void ServiceWorkerManager::LoadRegistrations(
    const nsTArray<ServiceWorkerRegistrationData>& aRegistrations) {
  MOZ_ASSERT(NS_IsMainThread());
  for (uint32_t i = 0, len = aRegistrations.Length(); i < len; ++i) {
    LoadRegistration(aRegistrations[i]);
  }
}

void ServiceWorkerManager::StoreRegistration(
    nsIPrincipal* aPrincipal, ServiceWorkerRegistrationInfo* aRegistration) {
  MOZ_ASSERT(aPrincipal);
  MOZ_ASSERT(aRegistration);

  if (mShuttingDown) {
    return;
  }

  // Do not store a registration for addons that are not installed, not enabled
  // or installed temporarily.
  //
  // If the dom.serviceWorkers.testing.persistTemporaryInstalledAddons is set
  // to true, the registration for a temporary installed addon will still be
  // persisted (only meant to be used to make it easier to test some particular
  // scenario with a temporary installed addon which doesn't need to be signed
  // to be installed on release channel builds).
  if (aPrincipal->SchemeIs("moz-extension")) {
    RefPtr<extensions::WebExtensionPolicy> addonPolicy =
        BasePrincipal::Cast(aPrincipal)->AddonPolicy();
    if (!addonPolicy || !addonPolicy->Active() ||
        (addonPolicy->TemporarilyInstalled() &&
         !StaticPrefs::
             dom_serviceWorkers_testing_persistTemporarilyInstalledAddons())) {
      return;
    }
  }

  ServiceWorkerRegistrationData data;
  nsresult rv = PopulateRegistrationData(aPrincipal, aRegistration, data);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }

  PrincipalInfo principalInfo;
  if (NS_WARN_IF(
          NS_FAILED(PrincipalToPrincipalInfo(aPrincipal, &principalInfo)))) {
    return;
  }

  mActor->SendRegister(data);
}

already_AddRefed<ServiceWorkerRegistrationInfo>
ServiceWorkerManager::GetServiceWorkerRegistrationInfo(
    const ClientInfo& aClientInfo) const {
  auto principalOrErr = aClientInfo.GetPrincipal();
  if (NS_WARN_IF(principalOrErr.isErr())) {
    return nullptr;
  }

  nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
  nsCOMPtr<nsIURI> uri;
  nsresult rv = NS_NewURI(getter_AddRefs(uri), aClientInfo.URL());
  NS_ENSURE_SUCCESS(rv, nullptr);

  return GetServiceWorkerRegistrationInfo(principal, uri);
}

already_AddRefed<ServiceWorkerRegistrationInfo>
ServiceWorkerManager::GetServiceWorkerRegistrationInfo(nsIPrincipal* aPrincipal,
                                                       nsIURI* aURI) const {
  MOZ_ASSERT(aPrincipal);
  MOZ_ASSERT(aURI);

  nsAutoCString scopeKey;
  nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
  if (NS_FAILED(rv)) {
    return nullptr;
  }

  return GetServiceWorkerRegistrationInfo(scopeKey, aURI);
}

already_AddRefed<ServiceWorkerRegistrationInfo>
ServiceWorkerManager::GetServiceWorkerRegistrationInfo(
    const nsACString& aScopeKey, nsIURI* aURI) const {
  MOZ_ASSERT(aURI);

  nsAutoCString spec;
  nsresult rv = aURI->GetSpec(spec);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return nullptr;
  }

  nsAutoCString scope;
  RegistrationDataPerPrincipal* data;
  if (!FindScopeForPath(aScopeKey, spec, &data, scope)) {
    return nullptr;
  }

  MOZ_ASSERT(data);

  RefPtr<ServiceWorkerRegistrationInfo> registration;
  data->mInfos.Get(scope, getter_AddRefs(registration));
  // ordered scopes and registrations better be in sync.
  MOZ_ASSERT(registration);

#ifdef DEBUG
  nsAutoCString origin;
  rv = registration->Principal()->GetOrigin(origin);
  MOZ_ASSERT(NS_SUCCEEDED(rv));
  MOZ_ASSERT(origin.Equals(aScopeKey));
#endif

  return registration.forget();
}

/* static */
nsresult ServiceWorkerManager::PrincipalToScopeKey(nsIPrincipal* aPrincipal,
                                                   nsACString& aKey) {
  MOZ_ASSERT(aPrincipal);

  if (!BasePrincipal::Cast(aPrincipal)->IsContentPrincipal()) {
    return NS_ERROR_FAILURE;
  }

  nsresult rv = aPrincipal->GetOrigin(aKey);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  return NS_OK;
}

/* static */
nsresult ServiceWorkerManager::PrincipalInfoToScopeKey(
    const PrincipalInfo& aPrincipalInfo, nsACString& aKey) {
  if (aPrincipalInfo.type() != PrincipalInfo::TContentPrincipalInfo) {
    return NS_ERROR_FAILURE;
  }

  auto content = aPrincipalInfo.get_ContentPrincipalInfo();

  nsAutoCString suffix;
  content.attrs().CreateSuffix(suffix);

  aKey = content.originNoSuffix();
  aKey.Append(suffix);

  return NS_OK;
}

/* static */
void ServiceWorkerManager::AddScopeAndRegistration(
    const nsACString& aScope, ServiceWorkerRegistrationInfo* aInfo) {
  MOZ_ASSERT(aInfo);
  MOZ_ASSERT(aInfo->Principal());
  MOZ_ASSERT(!aInfo->IsUnregistered());

  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
  if (!swm) {
    // browser shutdown
    return;
  }

  nsAutoCString scopeKey;
  nsresult rv = swm->PrincipalToScopeKey(aInfo->Principal(), scopeKey);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }

  MOZ_ASSERT(!scopeKey.IsEmpty());

  autoconst data = swm->mRegistrationInfos.GetOrInsertNew(scopeKey);
  data->mScopeContainer.InsertScope(aScope);
  data->mInfos.InsertOrUpdate(aScope, RefPtr{aInfo});
  swm->NotifyListenersOnRegister(aInfo);
}

/* static */
bool ServiceWorkerManager::FindScopeForPath(
    const nsACString& aScopeKey, const nsACString& aPath,
    RegistrationDataPerPrincipal** aData, nsACString& aMatch) {
  MOZ_ASSERT(aData);

  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();

  if (!swm || !swm->mRegistrationInfos.Get(aScopeKey, aData)) {
    return false;
  }

  Maybe<nsCString> scope = (*aData)->mScopeContainer.MatchScope(aPath);

  if (scope) {
    // scope.isSome() will still truen true after this; we are just moving the
    // string inside the Maybe, so the Maybe will contain an empty string.
    aMatch = std::move(*scope);
  }

  return scope.isSome();
}

/* static */
bool ServiceWorkerManager::HasScope(nsIPrincipal* aPrincipal,
                                    const nsACString& aScope) {
  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
  if (!swm) {
    return false;
  }

  nsAutoCString scopeKey;
  nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return false;
  }

  RegistrationDataPerPrincipal* data;
  if (!swm->mRegistrationInfos.Get(scopeKey, &data)) {
    return false;
  }

  return data->mScopeContainer.Contains(aScope);
}

/* static */
void ServiceWorkerManager::RemoveScopeAndRegistration(
    ServiceWorkerRegistrationInfo* aRegistration) {
  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
  if (!swm) {
    return;
  }

  nsAutoCString scopeKey;
  nsresult rv = swm->PrincipalToScopeKey(aRegistration->Principal(), scopeKey);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }

  RegistrationDataPerPrincipal* data;
  if (!swm->mRegistrationInfos.Get(scopeKey, &data)) {
    return;
  }

  if (auto entry = data->mUpdateTimers.Lookup(aRegistration->Scope())) {
    entry.Data()->Cancel();
    entry.Remove();
  }

  // Verify there are no controlled clients for the purged registration.
  for (auto iter = swm->mControlledClients.Iter(); !iter.Done(); iter.Next()) {
    auto& reg = iter.UserData()->mRegistrationInfo;
    if (reg->Scope().Equals(aRegistration->Scope()) &&
        reg->Principal()->Equals(aRegistration->Principal()) &&
        reg->IsCorrupt()) {
      iter.Remove();
    }
  }

  RefPtr<ServiceWorkerRegistrationInfo> info;
  data->mInfos.Remove(aRegistration->Scope(), getter_AddRefs(info));
  aRegistration->SetUnregistered();
  data->mScopeContainer.RemoveScope(aRegistration->Scope());
  swm->NotifyListenersOnUnregister(info);

  swm->MaybeRemoveRegistrationInfo(scopeKey);
}

void ServiceWorkerManager::MaybeRemoveRegistrationInfo(
    const nsACString& aScopeKey) {
  if (auto entry = mRegistrationInfos.Lookup(aScopeKey)) {
    if (entry.Data()->mScopeContainer.IsEmpty() &&
        entry.Data()->mJobQueues.Count() == 0) {
      entry.Remove();

      // Need to reset the mQuotaUsageCheckCount, if
      // RegistrationDataPerPrincipal:: mScopeContainer is empty. This
      // RegistrationDataPerPrincipal might be reused, such that quota usage
      // mitigation can be triggered for the new added registration.
    } else if (entry.Data()->mScopeContainer.IsEmpty() &&
               entry.Data()->mQuotaUsageCheckCount) {
      entry.Data()->mQuotaUsageCheckCount = 0;
    }
  }
}

bool ServiceWorkerManager::StartControlling(
    const ClientInfo& aClientInfo,
    const ServiceWorkerDescriptor& aServiceWorker) {
  MOZ_ASSERT(NS_IsMainThread());

  auto principalOrErr =
      PrincipalInfoToPrincipal(aServiceWorker.PrincipalInfo());

  if (NS_WARN_IF(principalOrErr.isErr())) {
    return false;
  }

  nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();

  nsCOMPtr<nsIURI> scope;
  nsresult rv = NS_NewURI(getter_AddRefs(scope), aServiceWorker.Scope());
  NS_ENSURE_SUCCESS(rv, false);

  RefPtr<ServiceWorkerRegistrationInfo> registration =
      GetServiceWorkerRegistrationInfo(principal, scope);
  NS_ENSURE_TRUE(registration, false);
  NS_ENSURE_TRUE(registration->GetActive(), false);

  StartControllingClient(aClientInfo, registration);

  return true;
}

void ServiceWorkerManager::MaybeCheckNavigationUpdate(
    const ClientInfo& aClientInfo) {
--> --------------------

--> maximum size reached

--> --------------------

95%


¤ Dauer der Verarbeitung: 0.29 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.