/* -*- 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 "ServiceWorkerRegistrationProxy.h" #include "mozilla/SchedulerGroup.h" #include "mozilla/ScopeExit.h" #include "mozilla/ipc/BackgroundParent.h" #include "ServiceWorkerManager.h" #include "ServiceWorkerRegistrationParent.h" #include "ServiceWorkerUnregisterCallback.h" namespace mozilla::dom { using mozilla::ipc::AssertIsOnBackgroundThread; class ServiceWorkerRegistrationProxy::DelayedUpdate final : public nsITimerCallback, public nsINamed { RefPtr mProxy; RefPtr mPromise; nsCOMPtr mTimer; nsCString mNewestWorkerScriptUrl; ~DelayedUpdate() = default; public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSITIMERCALLBACK NS_DECL_NSINAMED DelayedUpdate(RefPtr&& aProxy, RefPtr&& aPromise, nsCString&& aNewestWorkerScriptUrl, uint32_t delay); void ChainTo(RefPtr aPromise); void Reject(); void SetNewestWorkerScriptUrl(nsCString&& aNewestWorkerScriptUrl); }; ServiceWorkerRegistrationProxy::~ServiceWorkerRegistrationProxy() { // Any thread MOZ_DIAGNOSTIC_ASSERT(!mActor); MOZ_DIAGNOSTIC_ASSERT(!mReg); } void ServiceWorkerRegistrationProxy::MaybeShutdownOnBGThread() { AssertIsOnBackgroundThread(); if (!mActor) { return; } mActor->MaybeSendDelete(); } void ServiceWorkerRegistrationProxy::UpdateStateOnBGThread( const ServiceWorkerRegistrationDescriptor& aDescriptor) { AssertIsOnBackgroundThread(); if (!mActor) { return; } Unused << mActor->SendUpdateState(aDescriptor.ToIPC()); } void ServiceWorkerRegistrationProxy::FireUpdateFoundOnBGThread() { AssertIsOnBackgroundThread(); if (!mActor) { return; } Unused << mActor->SendFireUpdateFound(); } void ServiceWorkerRegistrationProxy::InitOnMainThread() { AssertIsOnMainThread(); auto scopeExit = MakeScopeExit([&] { MaybeShutdownOnMainThread(); }); RefPtr swm = ServiceWorkerManager::GetInstance(); NS_ENSURE_TRUE_VOID(swm); RefPtr reg = swm->GetRegistration(mDescriptor.PrincipalInfo(), mDescriptor.Scope()); NS_ENSURE_TRUE_VOID(reg); if (reg->Id() != mDescriptor.Id()) { // This registration has already been replaced by another one. return; } scopeExit.release(); mReg = new nsMainThreadPtrHolder( "ServiceWorkerRegistrationProxy::mInfo", reg); mReg->AddInstance(this, mDescriptor); } void ServiceWorkerRegistrationProxy::MaybeShutdownOnMainThread() { AssertIsOnMainThread(); if (mDelayedUpdate) { mDelayedUpdate->Reject(); mDelayedUpdate = nullptr; } nsCOMPtr r = NewRunnableMethod( __func__, this, &ServiceWorkerRegistrationProxy::MaybeShutdownOnBGThread); MOZ_ALWAYS_SUCCEEDS(mEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL)); } void ServiceWorkerRegistrationProxy::StopListeningOnMainThread() { AssertIsOnMainThread(); if (!mReg) { return; } mReg->RemoveInstance(this); mReg = nullptr; } void ServiceWorkerRegistrationProxy::UpdateState( const ServiceWorkerRegistrationDescriptor& aDescriptor) { AssertIsOnMainThread(); if (mDescriptor == aDescriptor) { return; } mDescriptor = aDescriptor; nsCOMPtr r = NewRunnableMethod( __func__, this, &ServiceWorkerRegistrationProxy::UpdateStateOnBGThread, aDescriptor); MOZ_ALWAYS_SUCCEEDS(mEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL)); } void ServiceWorkerRegistrationProxy::FireUpdateFound() { AssertIsOnMainThread(); nsCOMPtr r = NewRunnableMethod( __func__, this, &ServiceWorkerRegistrationProxy::FireUpdateFoundOnBGThread); MOZ_ALWAYS_SUCCEEDS(mEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL)); } void ServiceWorkerRegistrationProxy::RegistrationCleared() { MaybeShutdownOnMainThread(); } void ServiceWorkerRegistrationProxy::GetScope(nsAString& aScope) const { CopyUTF8toUTF16(mDescriptor.Scope(), aScope); } bool ServiceWorkerRegistrationProxy::MatchesDescriptor( const ServiceWorkerRegistrationDescriptor& aDescriptor) { AssertIsOnMainThread(); return aDescriptor.Id() == mDescriptor.Id() && aDescriptor.PrincipalInfo() == mDescriptor.PrincipalInfo() && aDescriptor.Scope() == mDescriptor.Scope(); } ServiceWorkerRegistrationProxy::ServiceWorkerRegistrationProxy( const ServiceWorkerRegistrationDescriptor& aDescriptor) : mEventTarget(GetCurrentSerialEventTarget()), mDescriptor(aDescriptor) {} void ServiceWorkerRegistrationProxy::Init( ServiceWorkerRegistrationParent* aActor) { AssertIsOnBackgroundThread(); MOZ_DIAGNOSTIC_ASSERT(aActor); MOZ_DIAGNOSTIC_ASSERT(!mActor); MOZ_DIAGNOSTIC_ASSERT(mEventTarget); mActor = aActor; // Note, this must be done from a separate Init() method and not in // the constructor. If done from the constructor the runnable can // execute, complete, and release its reference before the constructor // returns. nsCOMPtr r = NewRunnableMethod("ServiceWorkerRegistrationProxy::Init", this, &ServiceWorkerRegistrationProxy::InitOnMainThread); MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget())); } void ServiceWorkerRegistrationProxy::RevokeActor( ServiceWorkerRegistrationParent* aActor) { AssertIsOnBackgroundThread(); MOZ_DIAGNOSTIC_ASSERT(mActor); MOZ_DIAGNOSTIC_ASSERT(mActor == aActor); mActor = nullptr; nsCOMPtr r = NewRunnableMethod( __func__, this, &ServiceWorkerRegistrationProxy::StopListeningOnMainThread); MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget())); } RefPtr ServiceWorkerRegistrationProxy::Unregister() { AssertIsOnBackgroundThread(); RefPtr self = this; RefPtr promise = new GenericPromise::Private(__func__); nsCOMPtr r = NS_NewRunnableFunction(__func__, [self, promise]() mutable { nsresult rv = NS_ERROR_DOM_INVALID_STATE_ERR; auto scopeExit = MakeScopeExit([&] { promise->Reject(rv, __func__); }); NS_ENSURE_TRUE_VOID(self->mReg); RefPtr swm = ServiceWorkerManager::GetInstance(); NS_ENSURE_TRUE_VOID(swm); RefPtr cb = new UnregisterCallback(promise); rv = swm->Unregister(self->mReg->Principal(), cb, NS_ConvertUTF8toUTF16(self->mReg->Scope())); NS_ENSURE_SUCCESS_VOID(rv); scopeExit.release(); }); MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget())); return promise; } namespace { class UpdateCallback final : public ServiceWorkerUpdateFinishCallback { RefPtr mPromise; ~UpdateCallback() = default; public: explicit UpdateCallback( RefPtr&& aPromise) : mPromise(std::move(aPromise)) { MOZ_DIAGNOSTIC_ASSERT(mPromise); } void UpdateSucceeded(ServiceWorkerRegistrationInfo* aInfo) override { mPromise->Resolve(aInfo->Descriptor(), __func__); } void UpdateFailed(ErrorResult& aResult) override { mPromise->Reject(CopyableErrorResult(aResult), __func__); } }; } // anonymous namespace NS_IMPL_ISUPPORTS(ServiceWorkerRegistrationProxy::DelayedUpdate, nsITimerCallback, nsINamed) ServiceWorkerRegistrationProxy::DelayedUpdate::DelayedUpdate( RefPtr&& aProxy, RefPtr&& aPromise, nsCString&& aNewestWorkerScriptUrl, uint32_t delay) : mProxy(std::move(aProxy)), mPromise(std::move(aPromise)), mNewestWorkerScriptUrl(std::move(aNewestWorkerScriptUrl)) { MOZ_DIAGNOSTIC_ASSERT(mProxy); MOZ_DIAGNOSTIC_ASSERT(mPromise); MOZ_ASSERT(!mNewestWorkerScriptUrl.IsEmpty()); mProxy->mDelayedUpdate = this; Result, nsresult> result = NS_NewTimerWithCallback(this, delay, nsITimer::TYPE_ONE_SHOT); mTimer = result.unwrapOr(nullptr); MOZ_DIAGNOSTIC_ASSERT(mTimer); } void ServiceWorkerRegistrationProxy::DelayedUpdate::ChainTo( RefPtr aPromise) { AssertIsOnMainThread(); MOZ_ASSERT(mProxy->mDelayedUpdate == this); MOZ_ASSERT(mPromise); mPromise->ChainTo(aPromise.forget(), __func__); } void ServiceWorkerRegistrationProxy::DelayedUpdate::Reject() { MOZ_DIAGNOSTIC_ASSERT(mPromise); if (mTimer) { mTimer->Cancel(); mTimer = nullptr; } mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__); } void ServiceWorkerRegistrationProxy::DelayedUpdate::SetNewestWorkerScriptUrl( nsCString&& aNewestWorkerScriptUrl) { MOZ_ASSERT(NS_IsMainThread()); mNewestWorkerScriptUrl = std::move(aNewestWorkerScriptUrl); } NS_IMETHODIMP ServiceWorkerRegistrationProxy::DelayedUpdate::Notify(nsITimer* aTimer) { // Already shutting down. if (mProxy->mDelayedUpdate != this) { return NS_OK; } auto scopeExit = MakeScopeExit( [&] { mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__); }); NS_ENSURE_TRUE(mProxy->mReg, NS_ERROR_FAILURE); RefPtr swm = ServiceWorkerManager::GetInstance(); NS_ENSURE_TRUE(swm, NS_ERROR_FAILURE); RefPtr cb = new UpdateCallback(std::move(mPromise)); swm->Update(mProxy->mReg->Principal(), mProxy->mReg->Scope(), std::move(mNewestWorkerScriptUrl), cb); mTimer = nullptr; mProxy->mDelayedUpdate = nullptr; scopeExit.release(); return NS_OK; } NS_IMETHODIMP ServiceWorkerRegistrationProxy::DelayedUpdate::GetName(nsACString& aName) { aName.AssignLiteral("ServiceWorkerRegistrationProxy::DelayedUpdate"); return NS_OK; } RefPtr ServiceWorkerRegistrationProxy::Update( const nsACString& aNewestWorkerScriptUrl) { AssertIsOnBackgroundThread(); RefPtr self = this; RefPtr promise = new ServiceWorkerRegistrationPromise::Private(__func__); nsCOMPtr r = NS_NewRunnableFunction( __func__, [self, promise, newestWorkerScriptUrl = nsCString(aNewestWorkerScriptUrl)]() mutable { auto scopeExit = MakeScopeExit( [&] { promise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__); }); // Get the delay value for the update NS_ENSURE_TRUE_VOID(self->mReg); uint32_t delay = self->mReg->GetUpdateDelay(false); // If the delay value does not equal to 0, create a timer and a timer // callback to perform the delayed update. Otherwise, update directly. if (delay) { if (self->mDelayedUpdate) { // NOTE: if we `ChainTo(),` there will ultimately be a single // update, and this update will resolve all promises that were // issued while the update's timer was ticking down. self->mDelayedUpdate->ChainTo(std::move(promise)); // Use the "newest newest worker"'s script URL. self->mDelayedUpdate->SetNewestWorkerScriptUrl( std::move(newestWorkerScriptUrl)); } else { RefPtr du = new ServiceWorkerRegistrationProxy::DelayedUpdate( std::move(self), std::move(promise), std::move(newestWorkerScriptUrl), delay); } } else { RefPtr swm = ServiceWorkerManager::GetInstance(); NS_ENSURE_TRUE_VOID(swm); RefPtr cb = new UpdateCallback(std::move(promise)); swm->Update(self->mReg->Principal(), self->mReg->Scope(), std::move(newestWorkerScriptUrl), cb); } scopeExit.release(); }); MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget())); return promise; } RefPtr ServiceWorkerRegistrationProxy::SetNavigationPreloadEnabled( const bool& aEnabled) { AssertIsOnBackgroundThread(); RefPtr self = this; RefPtr promise = new GenericPromise::Private(__func__); nsCOMPtr r = NS_NewRunnableFunction(__func__, [aEnabled, self, promise]() mutable { nsresult rv = NS_ERROR_DOM_INVALID_STATE_ERR; auto scopeExit = MakeScopeExit([&] { promise->Reject(rv, __func__); }); NS_ENSURE_TRUE_VOID(self->mReg); NS_ENSURE_TRUE_VOID(self->mReg->GetActive()); auto reg = self->mReg; reg->SetNavigationPreloadEnabled(aEnabled); RefPtr swm = ServiceWorkerManager::GetInstance(); NS_ENSURE_TRUE_VOID(swm); swm->StoreRegistration(reg->Principal(), reg); scopeExit.release(); promise->Resolve(true, __func__); }); MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget())); return promise; } RefPtr ServiceWorkerRegistrationProxy::SetNavigationPreloadHeader( const nsACString& aHeader) { AssertIsOnBackgroundThread(); RefPtr self = this; RefPtr promise = new GenericPromise::Private(__func__); nsCOMPtr r = NS_NewRunnableFunction( __func__, [aHeader = nsCString(aHeader), self, promise]() mutable { nsresult rv = NS_ERROR_DOM_INVALID_STATE_ERR; auto scopeExit = MakeScopeExit([&] { promise->Reject(rv, __func__); }); NS_ENSURE_TRUE_VOID(self->mReg); NS_ENSURE_TRUE_VOID(self->mReg->GetActive()); auto reg = self->mReg; reg->SetNavigationPreloadHeader(aHeader); RefPtr swm = ServiceWorkerManager::GetInstance(); NS_ENSURE_TRUE_VOID(swm); swm->StoreRegistration(reg->Principal(), reg); scopeExit.release(); promise->Resolve(true, __func__); }); MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget())); return promise; } RefPtr ServiceWorkerRegistrationProxy::GetNavigationPreloadState() { AssertIsOnBackgroundThread(); RefPtr self = this; RefPtr promise = new NavigationPreloadStatePromise::Private(__func__); nsCOMPtr r = NS_NewRunnableFunction(__func__, [self, promise]() mutable { nsresult rv = NS_ERROR_DOM_INVALID_STATE_ERR; auto scopeExit = MakeScopeExit([&] { promise->Reject(rv, __func__); }); NS_ENSURE_TRUE_VOID(self->mReg); scopeExit.release(); promise->Resolve(self->mReg->GetNavigationPreloadState(), __func__); }); MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget())); return promise; } } // namespace mozilla::dom