/* -*- 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 mRegistration; bool mSuccess; public: explicit ContinueActivateRunnable( const nsMainThreadPtrHandle& 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& aWorker) { aWorker->WorkerPrivate()->NoteDeadServiceWorkerInfo(); aWorker = nullptr; }); } void ServiceWorkerRegistrationInfo::Clear() { ForEachWorker([](RefPtr& 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 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 swm = ServiceWorkerManager::GetInstance(); MOZ_ASSERT(swm); RefPtr 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 newest = NewestIncludingEvaluating(); if (newest) { CopyUTF8toUTF16(newest->ScriptSpec(), aScriptSpec); } return NS_OK; } NS_IMETHODIMP ServiceWorkerRegistrationInfo::GetUpdateViaCache(uint16_t* aUpdateViaCache) { *aUpdateViaCache = static_cast(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 info = mEvaluatingWorker; info.forget(aResult); return NS_OK; } NS_IMETHODIMP ServiceWorkerRegistrationInfo::GetInstallingWorker( nsIServiceWorkerInfo** aResult) { MOZ_ASSERT(NS_IsMainThread()); RefPtr info = mInstallingWorker; info.forget(aResult); return NS_OK; } NS_IMETHODIMP ServiceWorkerRegistrationInfo::GetWaitingWorker( nsIServiceWorkerInfo** aResult) { MOZ_ASSERT(NS_IsMainThread()); RefPtr info = mWaitingWorker; info.forget(aResult); return NS_OK; } NS_IMETHODIMP ServiceWorkerRegistrationInfo::GetActiveWorker(nsIServiceWorkerInfo** aResult) { MOZ_ASSERT(NS_IsMainThread()); RefPtr 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 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 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 ServiceWorkerRegistrationInfo::GetServiceWorkerInfoById(uint64_t aId) { MOZ_ASSERT(NS_IsMainThread()); RefPtr 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>( "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 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 handle( new nsMainThreadPtrHolder( "ServiceWorkerRegistrationInfoProxy", this)); RefPtr 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 failRunnable = NewRunnableMethod( "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 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( (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( (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> list = std::move(mVersionList); for (auto& entry : list) { if (entry->mTimeStamp >= oldest) { mVersionList.AppendElement(std::move(entry)); } } } mVersionList.AppendElement(MakeUnique(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 pinnedTarget : mInstanceList.ForwardRange()) { pinnedTarget->UpdateState(mDescriptor); } } void ServiceWorkerRegistrationInfo::NotifyChromeRegistrationListeners() { nsTArray> listeners( mListeners.Clone()); for (size_t index = 0; index < listeners.Length(); ++index) { listeners[index]->OnChange(); } } void ServiceWorkerRegistrationInfo::MaybeScheduleTimeCheckAndUpdate() { MOZ_ASSERT(NS_IsMainThread()); RefPtr 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 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 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 r = NS_NewRunnableFunction( "ServiceWorkerRegistrationInfo::TransitionWaitingToActive", [] { RefPtr 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 pinnedTarget : mInstanceList.ForwardRange()) { pinnedTarget->FireUpdateFound(); } } void ServiceWorkerRegistrationInfo::NotifyCleared() { for (RefPtr pinnedTarget : mInstanceList.ForwardRange()) { pinnedTarget->RegistrationCleared(); } } void ServiceWorkerRegistrationInfo::ClearWhenIdle() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(IsUnregistered()); MOZ_ASSERT(!IsControllingClients()); MOZ_ASSERT(!IsIdle(), "Already idle!"); RefPtr 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(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 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&)) { if (mEvaluatingWorker) { aFunc(mEvaluatingWorker); } if (mInstallingWorker) { aFunc(mInstallingWorker); } if (mWaitingWorker) { aFunc(mWaitingWorker); } if (mActiveWorker) { aFunc(mActiveWorker); } } void ServiceWorkerRegistrationInfo::CheckQuotaUsage() { MOZ_ASSERT(NS_IsMainThread()); RefPtr swm = ServiceWorkerManager::GetInstance(); MOZ_ASSERT(swm); swm->CheckPrincipalQuotaUsage(mPrincipal, Scope()); } } // namespace mozilla::dom