/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "nsProxyRelease.h" BEGIN_WORKERS_NAMESPACE namespace { class ContinueActivateRunnable final : public LifeCycleEventCallback { nsMainThreadPtrHandle mRegistration; bool mSuccess; public: explicit ContinueActivateRunnable(const nsMainThreadPtrHandle& aRegistration) : mRegistration(aRegistration) , mSuccess(false) { AssertIsOnMainThread(); } void SetResult(bool aResult) override { mSuccess = aResult; } NS_IMETHOD Run() override { AssertIsOnMainThread(); mRegistration->FinishActivate(mSuccess); mRegistration = nullptr; return NS_OK; } }; } // anonymous namespace void ServiceWorkerRegistrationInfo::Clear() { if (mEvaluatingWorker) { mEvaluatingWorker = nullptr; } if (mInstallingWorker) { mInstallingWorker->UpdateState(ServiceWorkerState::Redundant); mInstallingWorker->WorkerPrivate()->NoteDeadServiceWorkerInfo(); mInstallingWorker = nullptr; // FIXME(nsm): Abort any inflight requests from installing worker. } if (mWaitingWorker) { mWaitingWorker->UpdateState(ServiceWorkerState::Redundant); mWaitingWorker->WorkerPrivate()->NoteDeadServiceWorkerInfo(); mWaitingWorker = nullptr; } if (mActiveWorker) { mActiveWorker->UpdateState(ServiceWorkerState::Redundant); mActiveWorker->WorkerPrivate()->NoteDeadServiceWorkerInfo(); mActiveWorker = nullptr; } NotifyListenersOnChange(WhichServiceWorker::INSTALLING_WORKER | WhichServiceWorker::WAITING_WORKER | WhichServiceWorker::ACTIVE_WORKER); } ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo(const nsACString& aScope, nsIPrincipal* aPrincipal) : mControlledDocumentsCounter(0) , mUpdateState(NoUpdate) , mLastUpdateCheckTime(0) , mScope(aScope) , mPrincipal(aPrincipal) , mPendingUninstall(false) {} ServiceWorkerRegistrationInfo::~ServiceWorkerRegistrationInfo() { if (IsControllingDocuments()) { NS_WARNING("ServiceWorkerRegistrationInfo is still controlling documents. This can be a bug or a leak in ServiceWorker API or in any other API that takes the document alive."); } } NS_IMPL_ISUPPORTS(ServiceWorkerRegistrationInfo, nsIServiceWorkerRegistrationInfo) NS_IMETHODIMP ServiceWorkerRegistrationInfo::GetPrincipal(nsIPrincipal** aPrincipal) { AssertIsOnMainThread(); NS_ADDREF(*aPrincipal = mPrincipal); return NS_OK; } NS_IMETHODIMP ServiceWorkerRegistrationInfo::GetScope(nsAString& aScope) { AssertIsOnMainThread(); CopyUTF8toUTF16(mScope, aScope); return NS_OK; } NS_IMETHODIMP ServiceWorkerRegistrationInfo::GetScriptSpec(nsAString& aScriptSpec) { AssertIsOnMainThread(); RefPtr newest = Newest(); if (newest) { CopyUTF8toUTF16(newest->ScriptSpec(), aScriptSpec); } return NS_OK; } NS_IMETHODIMP ServiceWorkerRegistrationInfo::GetInstallingWorker(nsIServiceWorkerInfo **aResult) { AssertIsOnMainThread(); nsCOMPtr info = do_QueryInterface(mInstallingWorker); info.forget(aResult); return NS_OK; } NS_IMETHODIMP ServiceWorkerRegistrationInfo::GetWaitingWorker(nsIServiceWorkerInfo **aResult) { AssertIsOnMainThread(); nsCOMPtr info = do_QueryInterface(mWaitingWorker); info.forget(aResult); return NS_OK; } NS_IMETHODIMP ServiceWorkerRegistrationInfo::GetActiveWorker(nsIServiceWorkerInfo **aResult) { AssertIsOnMainThread(); nsCOMPtr info = do_QueryInterface(mActiveWorker); info.forget(aResult); return NS_OK; } NS_IMETHODIMP ServiceWorkerRegistrationInfo::GetWorkerByID(uint64_t aID, nsIServiceWorkerInfo **aResult) { AssertIsOnMainThread(); 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) { AssertIsOnMainThread(); if (!aListener || mListeners.Contains(aListener)) { return NS_ERROR_INVALID_ARG; } mListeners.AppendElement(aListener); return NS_OK; } NS_IMETHODIMP ServiceWorkerRegistrationInfo::RemoveListener( nsIServiceWorkerRegistrationInfoListener *aListener) { AssertIsOnMainThread(); if (!aListener || !mListeners.Contains(aListener)) { return NS_ERROR_INVALID_ARG; } mListeners.RemoveElement(aListener); return NS_OK; } already_AddRefed ServiceWorkerRegistrationInfo::GetServiceWorkerInfoById(uint64_t aId) { AssertIsOnMainThread(); 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() { MOZ_ALWAYS_SUCCEEDS( NS_DispatchToMainThread(NewRunnableMethod(this, &ServiceWorkerRegistrationInfo::TryToActivate))); } /* * TryToActivate should not be called directly, use TryToActivateAsync instead. */ void ServiceWorkerRegistrationInfo::TryToActivate() { AssertIsOnMainThread(); bool controlling = IsControllingDocuments(); bool skipWaiting = mWaitingWorker && mWaitingWorker->SkipWaitingFlag(); bool idle = IsIdle(); if (idle && (!controlling || skipWaiting)) { Activate(); } } 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. swm->CheckPendingReadyPromises(); // "Queue a task to fire a simple event named controllerchange..." nsCOMPtr controllerChangeRunnable = NewRunnableMethod>( swm, &ServiceWorkerManager::FireControllerChange, this); NS_DispatchToMainThread(controllerChangeRunnable); nsCOMPtr failRunnable = NewRunnableMethod(this, &ServiceWorkerRegistrationInfo::FinishActivate, false /* success */); nsMainThreadPtrHandle handle( new nsMainThreadPtrHolder(this)); RefPtr callback = new ContinueActivateRunnable(handle); ServiceWorkerPrivate* workerPrivate = mActiveWorker->WorkerPrivate(); MOZ_ASSERT(workerPrivate); nsresult rv = workerPrivate->SendLifeCycleEvent(NS_LITERAL_STRING("activate"), callback, failRunnable); if (NS_WARN_IF(NS_FAILED(rv))) { MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(failRunnable)); return; } } void ServiceWorkerRegistrationInfo::FinishActivate(bool aSuccess) { if (mPendingUninstall || !mActiveWorker || mActiveWorker->State() != ServiceWorkerState::Activating) { return; } // Activation never fails, so aSuccess is ignored. mActiveWorker->UpdateState(ServiceWorkerState::Activated); RefPtr swm = ServiceWorkerManager::GetInstance(); if (!swm) { // browser shutdown started during async activation completion step return; } swm->StoreRegistration(mPrincipal, this); } void ServiceWorkerRegistrationInfo::RefreshLastUpdateCheckTime() { AssertIsOnMainThread(); mLastUpdateCheckTime = PR_IntervalNow() / PR_MSEC_PER_SEC; } bool ServiceWorkerRegistrationInfo::IsLastUpdateCheckTimeOverOneDay() const { AssertIsOnMainThread(); // For testing. if (Preferences::GetBool("dom.serviceWorkers.testUpdateOverOneDay")) { return true; } const uint64_t kSecondsPerDay = 86400; const uint64_t now = PR_IntervalNow() / PR_MSEC_PER_SEC; if ((now - mLastUpdateCheckTime) > kSecondsPerDay) { return true; } return false; } void ServiceWorkerRegistrationInfo::NotifyListenersOnChange(WhichServiceWorker aChangedWorkers) { AssertIsOnMainThread(); MOZ_ASSERT(aChangedWorkers & (WhichServiceWorker::INSTALLING_WORKER | WhichServiceWorker::WAITING_WORKER | WhichServiceWorker::ACTIVE_WORKER)); RefPtr swm = ServiceWorkerManager::GetInstance(); if (!swm) { // browser shutdown started return; } swm->InvalidateServiceWorkerRegistrationWorker(this, aChangedWorkers); nsTArray> listeners(mListeners); for (size_t index = 0; index < listeners.Length(); ++index) { listeners[index]->OnChange(); } } void ServiceWorkerRegistrationInfo::MaybeScheduleTimeCheckAndUpdate() { AssertIsOnMainThread(); RefPtr swm = ServiceWorkerManager::GetInstance(); if (!swm) { // shutting down, do nothing return; } if (mUpdateState == NoUpdate) { mUpdateState = NeedTimeCheckAndUpdate; } swm->ScheduleUpdateTimer(mPrincipal, mScope); } void ServiceWorkerRegistrationInfo::MaybeScheduleUpdate() { AssertIsOnMainThread(); RefPtr swm = ServiceWorkerManager::GetInstance(); if (!swm) { // shutting down, do nothing return; } mUpdateState = NeedUpdate; swm->ScheduleUpdateTimer(mPrincipal, mScope); } bool ServiceWorkerRegistrationInfo::CheckAndClearIfUpdateNeeded() { AssertIsOnMainThread(); bool result = mUpdateState == NeedUpdate || (mUpdateState == NeedTimeCheckAndUpdate && IsLastUpdateCheckTimeOverOneDay()); mUpdateState = NoUpdate; return result; } ServiceWorkerInfo* ServiceWorkerRegistrationInfo::GetEvaluating() const { AssertIsOnMainThread(); return mEvaluatingWorker; } ServiceWorkerInfo* ServiceWorkerRegistrationInfo::GetInstalling() const { AssertIsOnMainThread(); return mInstallingWorker; } ServiceWorkerInfo* ServiceWorkerRegistrationInfo::GetWaiting() const { AssertIsOnMainThread(); return mWaitingWorker; } ServiceWorkerInfo* ServiceWorkerRegistrationInfo::GetActive() const { AssertIsOnMainThread(); return mActiveWorker; } void ServiceWorkerRegistrationInfo::SetEvaluating(ServiceWorkerInfo* aServiceWorker) { AssertIsOnMainThread(); MOZ_ASSERT(aServiceWorker); MOZ_ASSERT(!mEvaluatingWorker); MOZ_ASSERT(!mInstallingWorker); MOZ_ASSERT(mWaitingWorker != aServiceWorker); MOZ_ASSERT(mActiveWorker != aServiceWorker); mEvaluatingWorker = aServiceWorker; } void ServiceWorkerRegistrationInfo::ClearEvaluating() { AssertIsOnMainThread(); if (!mEvaluatingWorker) { return; } mEvaluatingWorker->UpdateState(ServiceWorkerState::Redundant); mEvaluatingWorker = nullptr; } void ServiceWorkerRegistrationInfo::ClearInstalling() { AssertIsOnMainThread(); if (!mInstallingWorker) { return; } mInstallingWorker->UpdateState(ServiceWorkerState::Redundant); mInstallingWorker = nullptr; NotifyListenersOnChange(WhichServiceWorker::INSTALLING_WORKER); } void ServiceWorkerRegistrationInfo::TransitionEvaluatingToInstalling() { AssertIsOnMainThread(); MOZ_ASSERT(mEvaluatingWorker); MOZ_ASSERT(!mInstallingWorker); mInstallingWorker = mEvaluatingWorker.forget(); mInstallingWorker->UpdateState(ServiceWorkerState::Installing); NotifyListenersOnChange(WhichServiceWorker::INSTALLING_WORKER); } void ServiceWorkerRegistrationInfo::TransitionInstallingToWaiting() { AssertIsOnMainThread(); MOZ_ASSERT(mInstallingWorker); if (mWaitingWorker) { MOZ_ASSERT(mInstallingWorker->CacheName() != mWaitingWorker->CacheName()); mWaitingWorker->UpdateState(ServiceWorkerState::Redundant); } mWaitingWorker = mInstallingWorker.forget(); mWaitingWorker->UpdateState(ServiceWorkerState::Installed); NotifyListenersOnChange(WhichServiceWorker::INSTALLING_WORKER | WhichServiceWorker::WAITING_WORKER); RefPtr swm = ServiceWorkerManager::GetInstance(); if (!swm) { // browser shutdown began return; } swm->StoreRegistration(mPrincipal, this); } void ServiceWorkerRegistrationInfo::SetActive(ServiceWorkerInfo* aServiceWorker) { AssertIsOnMainThread(); 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); } // 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); NotifyListenersOnChange(WhichServiceWorker::ACTIVE_WORKER); } void ServiceWorkerRegistrationInfo::TransitionWaitingToActive() { AssertIsOnMainThread(); MOZ_ASSERT(mWaitingWorker); if (mActiveWorker) { MOZ_ASSERT(mWaitingWorker->CacheName() != mActiveWorker->CacheName()); mActiveWorker->UpdateState(ServiceWorkerState::Redundant); } // We are transitioning from waiting to active normally, so go to // the activating state. mActiveWorker = mWaitingWorker.forget(); mActiveWorker->UpdateState(ServiceWorkerState::Activating); NotifyListenersOnChange(WhichServiceWorker::WAITING_WORKER | WhichServiceWorker::ACTIVE_WORKER); } bool ServiceWorkerRegistrationInfo::IsIdle() const { return !mActiveWorker || mActiveWorker->WorkerPrivate()->IsIdle(); } END_WORKERS_NAMESPACE