diff options
Diffstat (limited to 'dom/serviceworkers/ServiceWorkerRegistrationInfo.cpp')
-rw-r--r-- | dom/serviceworkers/ServiceWorkerRegistrationInfo.cpp | 907 |
1 files changed, 907 insertions, 0 deletions
diff --git a/dom/serviceworkers/ServiceWorkerRegistrationInfo.cpp b/dom/serviceworkers/ServiceWorkerRegistrationInfo.cpp new file mode 100644 index 0000000000..cf8c7543ee --- /dev/null +++ b/dom/serviceworkers/ServiceWorkerRegistrationInfo.cpp @@ -0,0 +1,907 @@ +/* -*- 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 "ServiceWorkerRegistrationInfo.h" + +#include "ServiceWorkerManager.h" +#include "ServiceWorkerPrivate.h" +#include "ServiceWorkerRegistrationListener.h" + +#include "mozilla/Preferences.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/StaticPrefs_dom.h" + +namespace mozilla::dom { + +namespace { + +class ContinueActivateRunnable final : public LifeCycleEventCallback { + nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration; + bool mSuccess; + + public: + explicit ContinueActivateRunnable( + const nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration) + : mRegistration(aRegistration), mSuccess(false) { + MOZ_ASSERT(NS_IsMainThread()); + } + + void SetResult(bool aResult) override { mSuccess = aResult; } + + NS_IMETHOD + Run() override { + MOZ_ASSERT(NS_IsMainThread()); + mRegistration->FinishActivate(mSuccess); + mRegistration = nullptr; + return NS_OK; + } +}; + +} // anonymous namespace + +void ServiceWorkerRegistrationInfo::ShutdownWorkers() { + ForEachWorker([](RefPtr<ServiceWorkerInfo>& aWorker) { + aWorker->WorkerPrivate()->NoteDeadServiceWorkerInfo(); + aWorker = nullptr; + }); +} + +void ServiceWorkerRegistrationInfo::Clear() { + ForEachWorker([](RefPtr<ServiceWorkerInfo>& aWorker) { + aWorker->UpdateState(ServiceWorkerState::Redundant); + aWorker->UpdateRedundantTime(); + }); + + // FIXME: Abort any inflight requests from installing worker. + + ShutdownWorkers(); + UpdateRegistrationState(); + NotifyChromeRegistrationListeners(); + NotifyCleared(); +} + +void ServiceWorkerRegistrationInfo::ClearAsCorrupt() { + mCorrupt = true; + Clear(); +} + +bool ServiceWorkerRegistrationInfo::IsCorrupt() const { return mCorrupt; } + +ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo( + const nsACString& aScope, nsIPrincipal* aPrincipal, + ServiceWorkerUpdateViaCache aUpdateViaCache, + IPCNavigationPreloadState&& aNavigationPreloadState) + : mPrincipal(aPrincipal), + mDescriptor(GetNextId(), GetNextVersion(), aPrincipal, aScope, + aUpdateViaCache), + mControlledClientsCounter(0), + mDelayMultiplier(0), + mUpdateState(NoUpdate), + mCreationTime(PR_Now()), + mCreationTimeStamp(TimeStamp::Now()), + mLastUpdateTime(0), + mUnregistered(false), + mCorrupt(false), + mNavigationPreloadState(std::move(aNavigationPreloadState)) { + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); +} + +ServiceWorkerRegistrationInfo::~ServiceWorkerRegistrationInfo() { + MOZ_DIAGNOSTIC_ASSERT(!IsControllingClients()); +} + +void ServiceWorkerRegistrationInfo::AddInstance( + ServiceWorkerRegistrationListener* aInstance, + const ServiceWorkerRegistrationDescriptor& aDescriptor) { + MOZ_DIAGNOSTIC_ASSERT(aInstance); + MOZ_ASSERT(!mInstanceList.Contains(aInstance)); + MOZ_DIAGNOSTIC_ASSERT(aDescriptor.Id() == mDescriptor.Id()); + MOZ_DIAGNOSTIC_ASSERT(aDescriptor.PrincipalInfo() == + mDescriptor.PrincipalInfo()); + MOZ_DIAGNOSTIC_ASSERT(aDescriptor.Scope() == mDescriptor.Scope()); + MOZ_DIAGNOSTIC_ASSERT(aDescriptor.Version() <= mDescriptor.Version()); + uint64_t lastVersion = aDescriptor.Version(); + for (auto& entry : mVersionList) { + if (lastVersion > entry->mDescriptor.Version()) { + continue; + } + lastVersion = entry->mDescriptor.Version(); + aInstance->UpdateState(entry->mDescriptor); + } + // Note, the mDescriptor may be contained in the version list. Since the + // version list is aged out, though, it may also not be in the version list. + // So always check for the mDescriptor update here. + if (lastVersion < mDescriptor.Version()) { + aInstance->UpdateState(mDescriptor); + } + mInstanceList.AppendElement(aInstance); +} + +void ServiceWorkerRegistrationInfo::RemoveInstance( + ServiceWorkerRegistrationListener* aInstance) { + MOZ_DIAGNOSTIC_ASSERT(aInstance); + DebugOnly<bool> removed = mInstanceList.RemoveElement(aInstance); + MOZ_ASSERT(removed); +} + +const nsCString& ServiceWorkerRegistrationInfo::Scope() const { + return mDescriptor.Scope(); +} + +nsIPrincipal* ServiceWorkerRegistrationInfo::Principal() const { + return mPrincipal; +} + +bool ServiceWorkerRegistrationInfo::IsUnregistered() const { + return mUnregistered; +} + +void ServiceWorkerRegistrationInfo::SetUnregistered() { +#ifdef DEBUG + MOZ_ASSERT(!mUnregistered); + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + MOZ_ASSERT(swm); + + RefPtr<ServiceWorkerRegistrationInfo> registration = + swm->GetRegistration(Principal(), Scope()); + MOZ_ASSERT(registration != this); +#endif + + mUnregistered = true; + NotifyChromeRegistrationListeners(); +} + +NS_IMPL_ISUPPORTS(ServiceWorkerRegistrationInfo, + nsIServiceWorkerRegistrationInfo) + +NS_IMETHODIMP +ServiceWorkerRegistrationInfo::GetPrincipal(nsIPrincipal** aPrincipal) { + MOZ_ASSERT(NS_IsMainThread()); + NS_ADDREF(*aPrincipal = mPrincipal); + return NS_OK; +} + +NS_IMETHODIMP ServiceWorkerRegistrationInfo::GetUnregistered( + bool* aUnregistered) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aUnregistered); + *aUnregistered = mUnregistered; + return NS_OK; +} + +NS_IMETHODIMP +ServiceWorkerRegistrationInfo::GetScope(nsAString& aScope) { + MOZ_ASSERT(NS_IsMainThread()); + CopyUTF8toUTF16(Scope(), aScope); + return NS_OK; +} + +NS_IMETHODIMP +ServiceWorkerRegistrationInfo::GetScriptSpec(nsAString& aScriptSpec) { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<ServiceWorkerInfo> newest = NewestIncludingEvaluating(); + if (newest) { + CopyUTF8toUTF16(newest->ScriptSpec(), aScriptSpec); + } + return NS_OK; +} + +NS_IMETHODIMP +ServiceWorkerRegistrationInfo::GetUpdateViaCache(uint16_t* aUpdateViaCache) { + *aUpdateViaCache = static_cast<uint16_t>(GetUpdateViaCache()); + return NS_OK; +} + +NS_IMETHODIMP +ServiceWorkerRegistrationInfo::GetLastUpdateTime(PRTime* _retval) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(_retval); + *_retval = mLastUpdateTime; + return NS_OK; +} + +NS_IMETHODIMP +ServiceWorkerRegistrationInfo::GetEvaluatingWorker( + nsIServiceWorkerInfo** aResult) { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<ServiceWorkerInfo> info = mEvaluatingWorker; + info.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +ServiceWorkerRegistrationInfo::GetInstallingWorker( + nsIServiceWorkerInfo** aResult) { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<ServiceWorkerInfo> info = mInstallingWorker; + info.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +ServiceWorkerRegistrationInfo::GetWaitingWorker( + nsIServiceWorkerInfo** aResult) { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<ServiceWorkerInfo> info = mWaitingWorker; + info.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +ServiceWorkerRegistrationInfo::GetActiveWorker(nsIServiceWorkerInfo** aResult) { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<ServiceWorkerInfo> info = mActiveWorker; + info.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +ServiceWorkerRegistrationInfo::GetQuotaUsageCheckCount( + int32_t* aQuotaUsageCheckCount) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aQuotaUsageCheckCount); + + // This value is actually stored on SWM's internal-only + // RegistrationDataPerPrincipal structure, but we expose it here for + // simplicity for our consumers, so we have to ask SWM to look it up for us. + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + MOZ_ASSERT(swm); + + *aQuotaUsageCheckCount = swm->GetPrincipalQuotaUsageCheckCount(mPrincipal); + + return NS_OK; +} + +NS_IMETHODIMP +ServiceWorkerRegistrationInfo::GetWorkerByID(uint64_t aID, + nsIServiceWorkerInfo** aResult) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aResult); + + RefPtr<ServiceWorkerInfo> info = GetServiceWorkerInfoById(aID); + // It is ok to return null for a missing service worker info. + info.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +ServiceWorkerRegistrationInfo::AddListener( + nsIServiceWorkerRegistrationInfoListener* aListener) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!aListener || mListeners.Contains(aListener)) { + return NS_ERROR_INVALID_ARG; + } + + mListeners.AppendElement(aListener); + + return NS_OK; +} + +NS_IMETHODIMP +ServiceWorkerRegistrationInfo::RemoveListener( + nsIServiceWorkerRegistrationInfoListener* aListener) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!aListener || !mListeners.Contains(aListener)) { + return NS_ERROR_INVALID_ARG; + } + + mListeners.RemoveElement(aListener); + + return NS_OK; +} + +NS_IMETHODIMP +ServiceWorkerRegistrationInfo::ForceShutdown() { + ClearInstalling(); + ShutdownWorkers(); + return NS_OK; +} + +already_AddRefed<ServiceWorkerInfo> +ServiceWorkerRegistrationInfo::GetServiceWorkerInfoById(uint64_t aId) { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<ServiceWorkerInfo> serviceWorker; + if (mEvaluatingWorker && mEvaluatingWorker->ID() == aId) { + serviceWorker = mEvaluatingWorker; + } else if (mInstallingWorker && mInstallingWorker->ID() == aId) { + serviceWorker = mInstallingWorker; + } else if (mWaitingWorker && mWaitingWorker->ID() == aId) { + serviceWorker = mWaitingWorker; + } else if (mActiveWorker && mActiveWorker->ID() == aId) { + serviceWorker = mActiveWorker; + } + + return serviceWorker.forget(); +} + +void ServiceWorkerRegistrationInfo::TryToActivateAsync( + TryToActivateCallback&& aCallback) { + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread( + NewRunnableMethod<StoreCopyPassByRRef<TryToActivateCallback>>( + "ServiceWorkerRegistrationInfo::TryToActivate", this, + &ServiceWorkerRegistrationInfo::TryToActivate, + std::move(aCallback)))); +} + +/* + * TryToActivate should not be called directly, use TryToActivateAsync instead. + */ +void ServiceWorkerRegistrationInfo::TryToActivate( + TryToActivateCallback&& aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + bool controlling = IsControllingClients(); + bool skipWaiting = mWaitingWorker && mWaitingWorker->SkipWaitingFlag(); + bool idle = IsIdle(); + if (idle && (!controlling || skipWaiting)) { + Activate(); + } + + if (aCallback) { + aCallback(); + } +} + +void ServiceWorkerRegistrationInfo::Activate() { + if (!mWaitingWorker) { + return; + } + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + if (!swm) { + // browser shutdown began during async activation step + return; + } + + TransitionWaitingToActive(); + + // FIXME(nsm): Unlink appcache if there is one. + + // "Queue a task to fire a simple event named controllerchange..." + MOZ_DIAGNOSTIC_ASSERT(mActiveWorker); + swm->UpdateClientControllers(this); + + nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> handle( + new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>( + "ServiceWorkerRegistrationInfoProxy", this)); + RefPtr<LifeCycleEventCallback> callback = + new ContinueActivateRunnable(handle); + + ServiceWorkerPrivate* workerPrivate = mActiveWorker->WorkerPrivate(); + MOZ_ASSERT(workerPrivate); + nsresult rv = workerPrivate->SendLifeCycleEvent(u"activate"_ns, callback); + if (NS_WARN_IF(NS_FAILED(rv))) { + nsCOMPtr<nsIRunnable> failRunnable = NewRunnableMethod<bool>( + "dom::ServiceWorkerRegistrationInfo::FinishActivate", this, + &ServiceWorkerRegistrationInfo::FinishActivate, false /* success */); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(failRunnable.forget())); + return; + } +} + +void ServiceWorkerRegistrationInfo::FinishActivate(bool aSuccess) { + if (mUnregistered || !mActiveWorker || + mActiveWorker->State() != ServiceWorkerState::Activating) { + return; + } + + // Activation never fails, so aSuccess is ignored. + mActiveWorker->UpdateState(ServiceWorkerState::Activated); + mActiveWorker->UpdateActivatedTime(); + + UpdateRegistrationState(); + NotifyChromeRegistrationListeners(); + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + if (!swm) { + // browser shutdown started during async activation completion step + return; + } + swm->StoreRegistration(mPrincipal, this); +} + +void ServiceWorkerRegistrationInfo::RefreshLastUpdateCheckTime() { + MOZ_ASSERT(NS_IsMainThread()); + + mLastUpdateTime = + mCreationTime + + static_cast<PRTime>( + (TimeStamp::Now() - mCreationTimeStamp).ToMicroseconds()); + NotifyChromeRegistrationListeners(); +} + +bool ServiceWorkerRegistrationInfo::IsLastUpdateCheckTimeOverOneDay() const { + MOZ_ASSERT(NS_IsMainThread()); + + // For testing. + if (Preferences::GetBool("dom.serviceWorkers.testUpdateOverOneDay")) { + return true; + } + + const int64_t kSecondsPerDay = 86400; + const int64_t nowMicros = + mCreationTime + + static_cast<PRTime>( + (TimeStamp::Now() - mCreationTimeStamp).ToMicroseconds()); + + // now < mLastUpdateTime if the system time is reset between storing + // and loading mLastUpdateTime from ServiceWorkerRegistrar. + if (nowMicros < mLastUpdateTime || + (nowMicros - mLastUpdateTime) / PR_USEC_PER_SEC > kSecondsPerDay) { + return true; + } + return false; +} + +void ServiceWorkerRegistrationInfo::UpdateRegistrationState() { + UpdateRegistrationState(mDescriptor.UpdateViaCache()); +} + +void ServiceWorkerRegistrationInfo::UpdateRegistrationState( + ServiceWorkerUpdateViaCache aUpdateViaCache) { + MOZ_ASSERT(NS_IsMainThread()); + + TimeStamp oldest = TimeStamp::Now() - TimeDuration::FromSeconds(30); + if (!mVersionList.IsEmpty() && mVersionList[0]->mTimeStamp < oldest) { + nsTArray<UniquePtr<VersionEntry>> list = std::move(mVersionList); + for (auto& entry : list) { + if (entry->mTimeStamp >= oldest) { + mVersionList.AppendElement(std::move(entry)); + } + } + } + mVersionList.AppendElement(MakeUnique<VersionEntry>(mDescriptor)); + + // We are going to modify the descriptor, so increase its version number. + mDescriptor.SetVersion(GetNextVersion()); + + // Note, this also sets the new version number on the ServiceWorkerInfo + // objects before we copy over their updated descriptors. + mDescriptor.SetWorkers(mInstallingWorker, mWaitingWorker, mActiveWorker); + + mDescriptor.SetUpdateViaCache(aUpdateViaCache); + + for (RefPtr<ServiceWorkerRegistrationListener> pinnedTarget : + mInstanceList.ForwardRange()) { + pinnedTarget->UpdateState(mDescriptor); + } +} + +void ServiceWorkerRegistrationInfo::NotifyChromeRegistrationListeners() { + nsTArray<nsCOMPtr<nsIServiceWorkerRegistrationInfoListener>> listeners( + mListeners.Clone()); + for (size_t index = 0; index < listeners.Length(); ++index) { + listeners[index]->OnChange(); + } +} + +void ServiceWorkerRegistrationInfo::MaybeScheduleTimeCheckAndUpdate() { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + if (!swm) { + // shutting down, do nothing + return; + } + + if (mUpdateState == NoUpdate) { + mUpdateState = NeedTimeCheckAndUpdate; + } + + swm->ScheduleUpdateTimer(mPrincipal, Scope()); +} + +void ServiceWorkerRegistrationInfo::MaybeScheduleUpdate() { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + if (!swm) { + // shutting down, do nothing + return; + } + + // When reach the navigation fault threshold, calling unregister instead of + // scheduling update. + if (mActiveWorker && !mUnregistered) { + uint32_t navigationFaultCount; + mActiveWorker->GetNavigationFaultCount(&navigationFaultCount); + const auto navigationFaultThreshold = StaticPrefs:: + dom_serviceWorkers_mitigations_navigation_fault_threshold(); + // Disable unregister mitigation when navigation fault threshold is 0. + if (navigationFaultThreshold <= navigationFaultCount && + navigationFaultThreshold != 0) { + CheckQuotaUsage(); + swm->Unregister(mPrincipal, nullptr, NS_ConvertUTF8toUTF16(Scope())); + return; + } + } + + mUpdateState = NeedUpdate; + + swm->ScheduleUpdateTimer(mPrincipal, Scope()); +} + +bool ServiceWorkerRegistrationInfo::CheckAndClearIfUpdateNeeded() { + MOZ_ASSERT(NS_IsMainThread()); + + bool result = + mUpdateState == NeedUpdate || (mUpdateState == NeedTimeCheckAndUpdate && + IsLastUpdateCheckTimeOverOneDay()); + + mUpdateState = NoUpdate; + + return result; +} + +ServiceWorkerInfo* ServiceWorkerRegistrationInfo::GetEvaluating() const { + MOZ_ASSERT(NS_IsMainThread()); + return mEvaluatingWorker; +} + +ServiceWorkerInfo* ServiceWorkerRegistrationInfo::GetInstalling() const { + MOZ_ASSERT(NS_IsMainThread()); + return mInstallingWorker; +} + +ServiceWorkerInfo* ServiceWorkerRegistrationInfo::GetWaiting() const { + MOZ_ASSERT(NS_IsMainThread()); + return mWaitingWorker; +} + +ServiceWorkerInfo* ServiceWorkerRegistrationInfo::GetActive() const { + MOZ_ASSERT(NS_IsMainThread()); + return mActiveWorker; +} + +ServiceWorkerInfo* ServiceWorkerRegistrationInfo::GetByDescriptor( + const ServiceWorkerDescriptor& aDescriptor) const { + if (mActiveWorker && mActiveWorker->Descriptor().Matches(aDescriptor)) { + return mActiveWorker; + } + if (mWaitingWorker && mWaitingWorker->Descriptor().Matches(aDescriptor)) { + return mWaitingWorker; + } + if (mInstallingWorker && + mInstallingWorker->Descriptor().Matches(aDescriptor)) { + return mInstallingWorker; + } + if (mEvaluatingWorker && + mEvaluatingWorker->Descriptor().Matches(aDescriptor)) { + return mEvaluatingWorker; + } + return nullptr; +} + +void ServiceWorkerRegistrationInfo::SetEvaluating( + ServiceWorkerInfo* aServiceWorker) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aServiceWorker); + MOZ_ASSERT(!mEvaluatingWorker); + MOZ_ASSERT(!mInstallingWorker); + MOZ_ASSERT(mWaitingWorker != aServiceWorker); + MOZ_ASSERT(mActiveWorker != aServiceWorker); + + mEvaluatingWorker = aServiceWorker; + + // We don't call UpdateRegistrationState() here because the evaluating worker + // is currently not exposed to content on the registration, so calling it here + // would produce redundant IPC traffic. + NotifyChromeRegistrationListeners(); +} + +void ServiceWorkerRegistrationInfo::ClearEvaluating() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mEvaluatingWorker) { + return; + } + + mEvaluatingWorker->UpdateState(ServiceWorkerState::Redundant); + // We don't update the redundant time for the sw here, since we've not expose + // evalutingWorker yet. + mEvaluatingWorker = nullptr; + + // As for SetEvaluating, UpdateRegistrationState() does not need to be called. + NotifyChromeRegistrationListeners(); +} + +void ServiceWorkerRegistrationInfo::ClearInstalling() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mInstallingWorker) { + return; + } + + RefPtr<ServiceWorkerInfo> installing = std::move(mInstallingWorker); + installing->UpdateState(ServiceWorkerState::Redundant); + installing->UpdateRedundantTime(); + + UpdateRegistrationState(); + NotifyChromeRegistrationListeners(); +} + +void ServiceWorkerRegistrationInfo::TransitionEvaluatingToInstalling() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mEvaluatingWorker); + MOZ_ASSERT(!mInstallingWorker); + + mInstallingWorker = std::move(mEvaluatingWorker); + mInstallingWorker->UpdateState(ServiceWorkerState::Installing); + + UpdateRegistrationState(); + NotifyChromeRegistrationListeners(); +} + +void ServiceWorkerRegistrationInfo::TransitionInstallingToWaiting() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mInstallingWorker); + + if (mWaitingWorker) { + MOZ_ASSERT(mInstallingWorker->CacheName() != mWaitingWorker->CacheName()); + mWaitingWorker->UpdateState(ServiceWorkerState::Redundant); + mWaitingWorker->UpdateRedundantTime(); + } + + mWaitingWorker = std::move(mInstallingWorker); + mWaitingWorker->UpdateState(ServiceWorkerState::Installed); + mWaitingWorker->UpdateInstalledTime(); + + UpdateRegistrationState(); + NotifyChromeRegistrationListeners(); + + // TODO: When bug 1426401 is implemented we will need to call + // StoreRegistration() here to persist the waiting worker. +} + +void ServiceWorkerRegistrationInfo::SetActive( + ServiceWorkerInfo* aServiceWorker) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aServiceWorker); + + // TODO: Assert installing, waiting, and active are nullptr once the SWM + // moves to the parent process. After that happens this code will + // only run for browser initialization and not for cross-process + // overrides. + MOZ_ASSERT(mInstallingWorker != aServiceWorker); + MOZ_ASSERT(mWaitingWorker != aServiceWorker); + MOZ_ASSERT(mActiveWorker != aServiceWorker); + + if (mActiveWorker) { + MOZ_ASSERT(aServiceWorker->CacheName() != mActiveWorker->CacheName()); + mActiveWorker->UpdateState(ServiceWorkerState::Redundant); + mActiveWorker->UpdateRedundantTime(); + } + + // The active worker is being overriden due to initial load or + // another process activating a worker. Move straight to the + // Activated state. + mActiveWorker = aServiceWorker; + mActiveWorker->SetActivateStateUncheckedWithoutEvent( + ServiceWorkerState::Activated); + + // We don't need to update activated time when we load registration from + // registrar. + UpdateRegistrationState(); + NotifyChromeRegistrationListeners(); +} + +void ServiceWorkerRegistrationInfo::TransitionWaitingToActive() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mWaitingWorker); + + if (mActiveWorker) { + MOZ_ASSERT(mWaitingWorker->CacheName() != mActiveWorker->CacheName()); + mActiveWorker->UpdateState(ServiceWorkerState::Redundant); + mActiveWorker->UpdateRedundantTime(); + } + + // We are transitioning from waiting to active normally, so go to + // the activating state. + mActiveWorker = std::move(mWaitingWorker); + mActiveWorker->UpdateState(ServiceWorkerState::Activating); + + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "ServiceWorkerRegistrationInfo::TransitionWaitingToActive", [] { + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + if (swm) { + swm->CheckPendingReadyPromises(); + } + }); + MOZ_ALWAYS_SUCCEEDS( + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); + + UpdateRegistrationState(); + NotifyChromeRegistrationListeners(); +} + +bool ServiceWorkerRegistrationInfo::IsIdle() const { + return !mActiveWorker || mActiveWorker->WorkerPrivate()->IsIdle(); +} + +ServiceWorkerUpdateViaCache ServiceWorkerRegistrationInfo::GetUpdateViaCache() + const { + return mDescriptor.UpdateViaCache(); +} + +void ServiceWorkerRegistrationInfo::SetUpdateViaCache( + ServiceWorkerUpdateViaCache aUpdateViaCache) { + UpdateRegistrationState(aUpdateViaCache); +} + +int64_t ServiceWorkerRegistrationInfo::GetLastUpdateTime() const { + return mLastUpdateTime; +} + +void ServiceWorkerRegistrationInfo::SetLastUpdateTime(const int64_t aTime) { + if (aTime == 0) { + return; + } + + mLastUpdateTime = aTime; +} + +const ServiceWorkerRegistrationDescriptor& +ServiceWorkerRegistrationInfo::Descriptor() const { + return mDescriptor; +} + +uint64_t ServiceWorkerRegistrationInfo::Id() const { return mDescriptor.Id(); } + +uint64_t ServiceWorkerRegistrationInfo::Version() const { + return mDescriptor.Version(); +} + +uint32_t ServiceWorkerRegistrationInfo::GetUpdateDelay( + const bool aWithMultiplier) { + uint32_t delay = Preferences::GetInt("dom.serviceWorkers.update_delay", 1000); + + if (!aWithMultiplier) { + return delay; + } + + // This can potentially happen if you spam registration->Update(). We don't + // want to wrap to a lower value. + if (mDelayMultiplier >= INT_MAX / (delay ? delay : 1)) { + return INT_MAX; + } + + delay *= mDelayMultiplier; + + if (!mControlledClientsCounter && mDelayMultiplier < (INT_MAX / 30)) { + mDelayMultiplier = (mDelayMultiplier ? mDelayMultiplier : 1) * 30; + } + + return delay; +} + +void ServiceWorkerRegistrationInfo::FireUpdateFound() { + for (RefPtr<ServiceWorkerRegistrationListener> pinnedTarget : + mInstanceList.ForwardRange()) { + pinnedTarget->FireUpdateFound(); + } +} + +void ServiceWorkerRegistrationInfo::NotifyCleared() { + for (RefPtr<ServiceWorkerRegistrationListener> pinnedTarget : + mInstanceList.ForwardRange()) { + pinnedTarget->RegistrationCleared(); + } +} + +void ServiceWorkerRegistrationInfo::ClearWhenIdle() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(IsUnregistered()); + MOZ_ASSERT(!IsControllingClients()); + MOZ_ASSERT(!IsIdle(), "Already idle!"); + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + MOZ_ASSERT(swm); + + swm->AddOrphanedRegistration(this); + + /** + * Although a Service Worker will transition to idle many times during its + * lifetime, the promise is only resolved once `GetIdlePromise` has been + * called, populating the `MozPromiseHolder`. Additionally, this is the only + * time this method will be called for the given ServiceWorker. This means we + * will be notified to the transition we are interested in, and there are no + * other callers to get confused. + * + * Note that because we are using `MozPromise`, our callback will be invoked + * as a separate task, so there is a small potential for races in the event + * code if things are still holding onto the ServiceWorker binding and using + * `postMessage()` or other mechanisms to schedule new events on it, which + * would make it non-idle. However, this is a race inherent in the spec which + * does not deal with the reality of multiple threads in "Try Clear + * Registration". + */ + GetActive()->WorkerPrivate()->GetIdlePromise()->Then( + GetCurrentSerialEventTarget(), __func__, + [self = RefPtr<ServiceWorkerRegistrationInfo>(this)]( + const GenericPromise::ResolveOrRejectValue& aResult) { + MOZ_ASSERT(aResult.IsResolve()); + // This registration was already unregistered and not controlling + // clients when `ClearWhenIdle` was called, so there should be no way + // that more clients were acquired. + MOZ_ASSERT(!self->IsControllingClients()); + MOZ_ASSERT(self->IsIdle()); + self->Clear(); + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + if (swm) { + swm->RemoveOrphanedRegistration(self); + } + }); +} + +const nsID& ServiceWorkerRegistrationInfo::AgentClusterId() const { + return mAgentClusterId; +} + +void ServiceWorkerRegistrationInfo::SetNavigationPreloadEnabled( + const bool& aEnabled) { + MOZ_ASSERT(NS_IsMainThread()); + mNavigationPreloadState.enabled() = aEnabled; +} + +void ServiceWorkerRegistrationInfo::SetNavigationPreloadHeader( + const nsCString& aHeader) { + MOZ_ASSERT(NS_IsMainThread()); + mNavigationPreloadState.headerValue() = aHeader; +} + +IPCNavigationPreloadState +ServiceWorkerRegistrationInfo::GetNavigationPreloadState() const { + MOZ_ASSERT(NS_IsMainThread()); + return mNavigationPreloadState; +} + +// static +uint64_t ServiceWorkerRegistrationInfo::GetNextId() { + MOZ_ASSERT(NS_IsMainThread()); + static uint64_t sNextId = 0; + return ++sNextId; +} + +// static +uint64_t ServiceWorkerRegistrationInfo::GetNextVersion() { + MOZ_ASSERT(NS_IsMainThread()); + static uint64_t sNextVersion = 0; + return ++sNextVersion; +} + +void ServiceWorkerRegistrationInfo::ForEachWorker( + void (*aFunc)(RefPtr<ServiceWorkerInfo>&)) { + if (mEvaluatingWorker) { + aFunc(mEvaluatingWorker); + } + + if (mInstallingWorker) { + aFunc(mInstallingWorker); + } + + if (mWaitingWorker) { + aFunc(mWaitingWorker); + } + + if (mActiveWorker) { + aFunc(mActiveWorker); + } +} + +void ServiceWorkerRegistrationInfo::CheckQuotaUsage() { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + MOZ_ASSERT(swm); + + swm->CheckPrincipalQuotaUsage(mPrincipal, Scope()); +} + +} // namespace mozilla::dom |