/* -*- 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 "ServiceWorkerRegistrationImpl.h" #include "ipc/ErrorIPCUtils.h" #include "mozilla/dom/DOMMozPromiseRequestHolder.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/PromiseWorkerProxy.h" #include "mozilla/dom/PushManagerBinding.h" #include "mozilla/dom/PushManager.h" #include "mozilla/dom/ServiceWorkerRegistrationBinding.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/dom/WorkerRef.h" #include "mozilla/dom/WorkerRunnable.h" #include "mozilla/dom/WorkerScope.h" #include "mozilla/Services.h" #include "mozilla/Unused.h" #include "nsCycleCollectionParticipant.h" #include "nsIPrincipal.h" #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" #include "ServiceWorker.h" #include "ServiceWorkerManager.h" #include "ServiceWorkerPrivate.h" #include "ServiceWorkerRegistration.h" #include "ServiceWorkerUnregisterCallback.h" #include "mozilla/dom/Document.h" #include "nsIServiceWorkerManager.h" #include "nsPIDOMWindow.h" #include "nsContentUtils.h" namespace mozilla { namespace dom { //////////////////////////////////////////////////// // Main Thread implementation ServiceWorkerRegistrationMainThread::ServiceWorkerRegistrationMainThread( const ServiceWorkerRegistrationDescriptor& aDescriptor) : mOuter(nullptr), mDescriptor(aDescriptor), mScope(NS_ConvertUTF8toUTF16(aDescriptor.Scope())), mListeningForEvents(false) { MOZ_ASSERT(NS_IsMainThread()); } ServiceWorkerRegistrationMainThread::~ServiceWorkerRegistrationMainThread() { MOZ_DIAGNOSTIC_ASSERT(!mListeningForEvents); MOZ_DIAGNOSTIC_ASSERT(!mOuter); } // XXXnsm, maybe this can be optimized to only add when a event handler is // registered. void ServiceWorkerRegistrationMainThread::StartListeningForEvents() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!mListeningForEvents); MOZ_DIAGNOSTIC_ASSERT(!mInfo); RefPtr swm = ServiceWorkerManager::GetInstance(); NS_ENSURE_TRUE_VOID(swm); mInfo = swm->GetRegistration(mDescriptor.PrincipalInfo(), mDescriptor.Scope()); NS_ENSURE_TRUE_VOID(mInfo); mInfo->AddInstance(this, mDescriptor); mListeningForEvents = true; } void ServiceWorkerRegistrationMainThread::StopListeningForEvents() { MOZ_ASSERT(NS_IsMainThread()); if (!mListeningForEvents) { return; } MOZ_DIAGNOSTIC_ASSERT(mInfo); mInfo->RemoveInstance(this); mInfo = nullptr; mListeningForEvents = false; } void ServiceWorkerRegistrationMainThread::RegistrationClearedInternal() { MOZ_ASSERT(NS_IsMainThread()); // Its possible for the binding object to be collected while we the // runnable to call this method is in the event queue. Double check // whether there is still anything to do here. if (mOuter) { mOuter->RegistrationCleared(); } StopListeningForEvents(); } // NB: These functions use NS_ENSURE_TRUE_VOID to be noisy about preconditions // that would otherwise cause things to silently not happen if they were false. void ServiceWorkerRegistrationMainThread::UpdateState( const ServiceWorkerRegistrationDescriptor& aDescriptor) { NS_ENSURE_TRUE_VOID(mOuter); nsIGlobalObject* global = mOuter->GetParentObject(); NS_ENSURE_TRUE_VOID(global); RefPtr self = this; nsCOMPtr r = NS_NewRunnableFunction("ServiceWorkerRegistrationMainThread::UpdateState", [self, desc = std::move(aDescriptor)]() mutable { self->mDescriptor = std::move(desc); NS_ENSURE_TRUE_VOID(self->mOuter); self->mOuter->UpdateState(self->mDescriptor); }); Unused << global->EventTargetFor(TaskCategory::Other) ->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } void ServiceWorkerRegistrationMainThread::FireUpdateFound() { NS_ENSURE_TRUE_VOID(mOuter); nsIGlobalObject* global = mOuter->GetParentObject(); NS_ENSURE_TRUE_VOID(global); RefPtr self = this; nsCOMPtr r = NS_NewRunnableFunction( "ServiceWorkerRegistrationMainThread::FireUpdateFound", [self]() mutable { NS_ENSURE_TRUE_VOID(self->mOuter); self->mOuter->MaybeDispatchUpdateFoundRunnable(); }); Unused << global->EventTargetFor(TaskCategory::Other) ->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } void ServiceWorkerRegistrationMainThread::RegistrationCleared() { NS_ENSURE_TRUE_VOID(mOuter); nsIGlobalObject* global = mOuter->GetParentObject(); NS_ENSURE_TRUE_VOID(global); // Queue a runnable to clean up the registration. This is necessary // because there may be runnables in the event queue already to // update the registration state. We want to let those run // if possible before clearing our mOuter reference. nsCOMPtr r = NewRunnableMethod( "ServiceWorkerRegistrationMainThread::RegistrationCleared", this, &ServiceWorkerRegistrationMainThread::RegistrationClearedInternal); Unused << global->EventTargetFor(TaskCategory::Other) ->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } bool ServiceWorkerRegistrationMainThread::MatchesDescriptor( const ServiceWorkerRegistrationDescriptor& aDescriptor) { return mOuter->MatchesDescriptor(aDescriptor); } void ServiceWorkerRegistrationMainThread::SetServiceWorkerRegistration( ServiceWorkerRegistration* aReg) { MOZ_DIAGNOSTIC_ASSERT(aReg); MOZ_DIAGNOSTIC_ASSERT(!mOuter); mOuter = aReg; StartListeningForEvents(); } void ServiceWorkerRegistrationMainThread::ClearServiceWorkerRegistration( ServiceWorkerRegistration* aReg) { MOZ_ASSERT_IF(mOuter, mOuter == aReg); StopListeningForEvents(); mOuter = nullptr; } namespace { void UpdateInternal(nsIPrincipal* aPrincipal, const nsACString& aScope, nsCString aNewestWorkerScriptUrl, ServiceWorkerUpdateFinishCallback* aCallback) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aPrincipal); MOZ_ASSERT(aCallback); RefPtr swm = ServiceWorkerManager::GetInstance(); if (!swm) { // browser shutdown return; } swm->Update(aPrincipal, aScope, std::move(aNewestWorkerScriptUrl), aCallback); } class MainThreadUpdateCallback final : public ServiceWorkerUpdateFinishCallback { RefPtr mPromise; ~MainThreadUpdateCallback() { mPromise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__); } public: MainThreadUpdateCallback() : mPromise(new ServiceWorkerRegistrationPromise::Private(__func__)) {} void UpdateSucceeded(ServiceWorkerRegistrationInfo* aRegistration) override { mPromise->Resolve(aRegistration->Descriptor(), __func__); } void UpdateFailed(ErrorResult& aStatus) override { mPromise->Reject(std::move(aStatus), __func__); } RefPtr Promise() const { return mPromise; } }; class WorkerThreadUpdateCallback final : public ServiceWorkerUpdateFinishCallback { RefPtr mWorkerRef; RefPtr mPromise; ~WorkerThreadUpdateCallback() = default; public: WorkerThreadUpdateCallback( RefPtr&& aWorkerRef, ServiceWorkerRegistrationPromise::Private* aPromise) : mWorkerRef(std::move(aWorkerRef)), mPromise(aPromise) { MOZ_ASSERT(NS_IsMainThread()); } void UpdateSucceeded(ServiceWorkerRegistrationInfo* aRegistration) override { mPromise->Resolve(aRegistration->Descriptor(), __func__); mWorkerRef = nullptr; } void UpdateFailed(ErrorResult& aStatus) override { mPromise->Reject(std::move(aStatus), __func__); mWorkerRef = nullptr; } }; class SWRUpdateRunnable final : public Runnable { class TimerCallback final : public nsITimerCallback { RefPtr mPrivate; RefPtr mRunnable; public: TimerCallback(ServiceWorkerPrivate* aPrivate, Runnable* aRunnable) : mPrivate(aPrivate), mRunnable(aRunnable) { MOZ_ASSERT(mPrivate); MOZ_ASSERT(aRunnable); } NS_IMETHOD Notify(nsITimer* aTimer) override { mRunnable->Run(); mPrivate->RemoveISupports(aTimer); return NS_OK; } NS_DECL_THREADSAFE_ISUPPORTS private: ~TimerCallback() = default; }; public: SWRUpdateRunnable(StrongWorkerRef* aWorkerRef, ServiceWorkerRegistrationPromise::Private* aPromise, const ServiceWorkerDescriptor& aDescriptor, const nsCString& aNewestWorkerScriptUrl) : Runnable("dom::SWRUpdateRunnable"), mMutex("SWRUpdateRunnable"), mWorkerRef(new ThreadSafeWorkerRef(aWorkerRef)), mPromise(aPromise), mDescriptor(aDescriptor), mDelayed(false), mNewestWorkerScriptUrl(aNewestWorkerScriptUrl) { MOZ_DIAGNOSTIC_ASSERT(mWorkerRef); MOZ_DIAGNOSTIC_ASSERT(mPromise); } NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); ErrorResult result; auto principalOrErr = mDescriptor.GetPrincipal(); if (NS_WARN_IF(principalOrErr.isErr())) { mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__); return NS_OK; } RefPtr swm = ServiceWorkerManager::GetInstance(); if (NS_WARN_IF(!swm)) { mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__); return NS_OK; } nsCOMPtr principal = principalOrErr.unwrap(); // This will delay update jobs originating from a service worker thread. // We don't currently handle ServiceWorkerRegistration.update() from other // worker types. Also, we assume this registration matches self.registration // on the service worker global. This is ok for now because service worker // globals are the only worker contexts where we expose // ServiceWorkerRegistration. RefPtr registration = swm->GetRegistration(principal, mDescriptor.Scope()); if (NS_WARN_IF(!registration)) { return NS_OK; } RefPtr worker = registration->GetByDescriptor(mDescriptor); uint32_t delay = registration->GetUpdateDelay(); // if we have a timer object, it means we've already been delayed once. if (delay && !mDelayed) { nsCOMPtr cb = new TimerCallback(worker->WorkerPrivate(), this); Result, nsresult> result = NS_NewTimerWithCallback(cb, delay, nsITimer::TYPE_ONE_SHOT); nsCOMPtr timer = result.unwrapOr(nullptr); if (NS_WARN_IF(!timer)) { return NS_OK; } mDelayed = true; // We're storing the timer object on the calling service worker's private. // ServiceWorkerPrivate will drop the reference if the worker terminates, // which will cancel the timer. if (!worker->WorkerPrivate()->MaybeStoreISupports(timer)) { // The worker thread is already shutting down. Just cancel the timer // and let the update runnable be destroyed. timer->Cancel(); return NS_OK; } return NS_OK; } RefPtr promise; { MutexAutoLock lock(mMutex); promise.swap(mPromise); } RefPtr cb = new WorkerThreadUpdateCallback(std::move(mWorkerRef), promise); UpdateInternal(principal, mDescriptor.Scope(), std::move(mNewestWorkerScriptUrl), cb); return NS_OK; } private: ~SWRUpdateRunnable() { MutexAutoLock lock(mMutex); if (mPromise) { mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__); } } // Protects promise access across threads Mutex mMutex; RefPtr mWorkerRef; RefPtr mPromise; const ServiceWorkerDescriptor mDescriptor; bool mDelayed; nsCString mNewestWorkerScriptUrl; }; NS_IMPL_ISUPPORTS(SWRUpdateRunnable::TimerCallback, nsITimerCallback) class WorkerUnregisterCallback final : public nsIServiceWorkerUnregisterCallback { RefPtr mWorkerRef; RefPtr mPromise; public: NS_DECL_ISUPPORTS WorkerUnregisterCallback(RefPtr&& aWorkerRef, RefPtr&& aPromise) : mWorkerRef(std::move(aWorkerRef)), mPromise(std::move(aPromise)) { MOZ_DIAGNOSTIC_ASSERT(mWorkerRef); MOZ_DIAGNOSTIC_ASSERT(mPromise); } NS_IMETHOD UnregisterSucceeded(bool aState) override { mPromise->Resolve(aState, __func__); mWorkerRef = nullptr; return NS_OK; } NS_IMETHOD UnregisterFailed() override { mPromise->Reject(NS_ERROR_DOM_SECURITY_ERR, __func__); mWorkerRef = nullptr; return NS_OK; } private: ~WorkerUnregisterCallback() = default; }; NS_IMPL_ISUPPORTS(WorkerUnregisterCallback, nsIServiceWorkerUnregisterCallback); /* * If the worker goes away, we still continue to unregister, but we don't try to * resolve the worker Promise (which doesn't exist by that point). */ class StartUnregisterRunnable final : public Runnable { // The promise is protected by the mutex. Mutex mMutex; RefPtr mWorkerRef; RefPtr mPromise; const ServiceWorkerRegistrationDescriptor mDescriptor; ~StartUnregisterRunnable() { MutexAutoLock lock(mMutex); if (mPromise) { mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__); } } public: StartUnregisterRunnable( StrongWorkerRef* aWorkerRef, GenericPromise::Private* aPromise, const ServiceWorkerRegistrationDescriptor& aDescriptor) : Runnable("dom::StartUnregisterRunnable"), mMutex("StartUnregisterRunnable"), mWorkerRef(new ThreadSafeWorkerRef(aWorkerRef)), mPromise(aPromise), mDescriptor(aDescriptor) { MOZ_DIAGNOSTIC_ASSERT(mWorkerRef); MOZ_DIAGNOSTIC_ASSERT(mPromise); } NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); auto principalOrErr = mDescriptor.GetPrincipal(); if (NS_WARN_IF(principalOrErr.isErr())) { mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__); return NS_OK; } nsCOMPtr principal = principalOrErr.unwrap(); nsCOMPtr swm = mozilla::services::GetServiceWorkerManager(); if (!swm) { mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__); return NS_OK; } RefPtr promise; { MutexAutoLock lock(mMutex); promise = std::move(mPromise); } RefPtr cb = new WorkerUnregisterCallback(std::move(mWorkerRef), std::move(promise)); nsresult rv = swm->Unregister(principal, cb, NS_ConvertUTF8toUTF16(mDescriptor.Scope())); if (NS_WARN_IF(NS_FAILED(rv))) { mPromise->Reject(rv, __func__); return NS_OK; } return NS_OK; } }; } // namespace void ServiceWorkerRegistrationMainThread::Update( const nsCString& aNewestWorkerScriptUrl, ServiceWorkerRegistrationCallback&& aSuccessCB, ServiceWorkerFailureCallback&& aFailureCB) { MOZ_ASSERT(NS_IsMainThread()); MOZ_DIAGNOSTIC_ASSERT(mOuter); nsIGlobalObject* global = mOuter->GetParentObject(); if (!global) { aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); return; } auto principalOrErr = mDescriptor.GetPrincipal(); if (NS_WARN_IF(principalOrErr.isErr())) { aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); return; } nsCOMPtr principal = principalOrErr.unwrap(); RefPtr cb = new MainThreadUpdateCallback(); UpdateInternal(principal, NS_ConvertUTF16toUTF8(mScope), aNewestWorkerScriptUrl, cb); auto holder = MakeRefPtr>( global); cb->Promise() ->Then( global->EventTargetFor(TaskCategory::Other), __func__, [successCB = std::move(aSuccessCB), holder](const ServiceWorkerRegistrationDescriptor& aDescriptor) { holder->Complete(); successCB(aDescriptor); }, [failureCB = std::move(aFailureCB), holder](const CopyableErrorResult& aRv) { holder->Complete(); failureCB(CopyableErrorResult(aRv)); }) ->Track(*holder); } void ServiceWorkerRegistrationMainThread::Unregister( ServiceWorkerBoolCallback&& aSuccessCB, ServiceWorkerFailureCallback&& aFailureCB) { MOZ_ASSERT(NS_IsMainThread()); MOZ_DIAGNOSTIC_ASSERT(mOuter); nsIGlobalObject* global = mOuter->GetParentObject(); if (!global) { aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); return; } nsCOMPtr swm = mozilla::services::GetServiceWorkerManager(); if (!swm) { aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); return; } auto principalOrErr = mDescriptor.GetPrincipal(); if (NS_WARN_IF(principalOrErr.isErr())) { aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); return; } nsCOMPtr principal = principalOrErr.unwrap(); RefPtr cb = new UnregisterCallback(); nsresult rv = swm->Unregister(principal, cb, NS_ConvertUTF8toUTF16(mDescriptor.Scope())); if (NS_FAILED(rv)) { aFailureCB(CopyableErrorResult(rv)); return; } auto holder = MakeRefPtr>(global); cb->Promise() ->Then( global->EventTargetFor(TaskCategory::Other), __func__, [successCB = std::move(aSuccessCB), holder](bool aResult) { holder->Complete(); successCB(aResult); }, [failureCB = std::move(aFailureCB), holder](nsresult aRv) { holder->Complete(); failureCB(CopyableErrorResult(aRv)); }) ->Track(*holder); } //////////////////////////////////////////////////// // Worker Thread implementation class WorkerListener final : public ServiceWorkerRegistrationListener { ServiceWorkerRegistrationDescriptor mDescriptor; nsMainThreadPtrHandle mInfo; nsCOMPtr mEventTarget; bool mListeningForEvents; // Set and unset on worker thread, used on main-thread and protected by mutex. ServiceWorkerRegistrationWorkerThread* mRegistration; Mutex mMutex; public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WorkerListener, override) WorkerListener(ServiceWorkerRegistrationWorkerThread* aReg, const ServiceWorkerRegistrationDescriptor& aDescriptor, nsISerialEventTarget* aEventTarget) : mDescriptor(aDescriptor), mEventTarget(aEventTarget), mListeningForEvents(false), mRegistration(aReg), mMutex("WorkerListener::mMutex") { MOZ_ASSERT(IsCurrentThreadRunningWorker()); MOZ_ASSERT(mEventTarget); MOZ_ASSERT(mRegistration); } void StartListeningForEvents() { MOZ_ASSERT(NS_IsMainThread()); MOZ_DIAGNOSTIC_ASSERT(!mListeningForEvents); MOZ_DIAGNOSTIC_ASSERT(!mInfo); RefPtr swm = ServiceWorkerManager::GetInstance(); NS_ENSURE_TRUE_VOID(swm); RefPtr info = swm->GetRegistration(mDescriptor.PrincipalInfo(), mDescriptor.Scope()); NS_ENSURE_TRUE_VOID(info); mInfo = new nsMainThreadPtrHolder( "WorkerListener::mInfo", info); mInfo->AddInstance(this, mDescriptor); mListeningForEvents = true; } void StopListeningForEvents() { MOZ_ASSERT(NS_IsMainThread()); if (!mListeningForEvents) { return; } MOZ_DIAGNOSTIC_ASSERT(mInfo); mInfo->RemoveInstance(this); mListeningForEvents = false; } // ServiceWorkerRegistrationListener void UpdateState( const ServiceWorkerRegistrationDescriptor& aDescriptor) override { MOZ_ASSERT(NS_IsMainThread()); mDescriptor = aDescriptor; nsCOMPtr r = NewCancelableRunnableMethod( "WorkerListener::UpdateState", this, &WorkerListener::UpdateStateOnWorkerThread, aDescriptor); Unused << mEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } void FireUpdateFound() override { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr r = NewCancelableRunnableMethod( "WorkerListener::FireUpdateFound", this, &WorkerListener::FireUpdateFoundOnWorkerThread); Unused << mEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } void UpdateStateOnWorkerThread( const ServiceWorkerRegistrationDescriptor& aDescriptor) { MOZ_ASSERT(IsCurrentThreadRunningWorker()); if (mRegistration) { mRegistration->UpdateState(aDescriptor); } } void FireUpdateFoundOnWorkerThread() { MOZ_ASSERT(IsCurrentThreadRunningWorker()); if (mRegistration) { mRegistration->FireUpdateFound(); } } void RegistrationCleared() override; void GetScope(nsAString& aScope) const override { CopyUTF8toUTF16(mDescriptor.Scope(), aScope); } bool MatchesDescriptor( const ServiceWorkerRegistrationDescriptor& aDescriptor) override { // TODO: Not implemented return false; } void ClearRegistration() { MOZ_ASSERT(IsCurrentThreadRunningWorker()); MutexAutoLock lock(mMutex); mRegistration = nullptr; } private: ~WorkerListener() { MOZ_ASSERT(!mListeningForEvents); } }; ServiceWorkerRegistrationWorkerThread::ServiceWorkerRegistrationWorkerThread( const ServiceWorkerRegistrationDescriptor& aDescriptor) : mOuter(nullptr), mDescriptor(aDescriptor), mScope(NS_ConvertUTF8toUTF16(aDescriptor.Scope())) {} ServiceWorkerRegistrationWorkerThread:: ~ServiceWorkerRegistrationWorkerThread() { MOZ_DIAGNOSTIC_ASSERT(!mListener); MOZ_DIAGNOSTIC_ASSERT(!mOuter); } void ServiceWorkerRegistrationWorkerThread::RegistrationCleared() { // The SWM notifying us that the registration was removed on the MT may // race with ClearServiceWorkerRegistration() on the worker thread. So // double-check that mOuter is still valid. if (mOuter) { mOuter->RegistrationCleared(); } } void ServiceWorkerRegistrationWorkerThread::SetServiceWorkerRegistration( ServiceWorkerRegistration* aReg) { MOZ_DIAGNOSTIC_ASSERT(aReg); MOZ_DIAGNOSTIC_ASSERT(!mOuter); mOuter = aReg; InitListener(); } void ServiceWorkerRegistrationWorkerThread::ClearServiceWorkerRegistration( ServiceWorkerRegistration* aReg) { MOZ_ASSERT_IF(mOuter, mOuter == aReg); ReleaseListener(); mOuter = nullptr; } void ServiceWorkerRegistrationWorkerThread::Update( const nsCString& aNewestWorkerScriptUrl, ServiceWorkerRegistrationCallback&& aSuccessCB, ServiceWorkerFailureCallback&& aFailureCB) { if (NS_WARN_IF(!mWorkerRef->GetPrivate())) { aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); return; } RefPtr workerRef = StrongWorkerRef::Create( mWorkerRef->GetPrivate(), "ServiceWorkerRegistration::Update"); if (NS_WARN_IF(!workerRef)) { aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); return; } nsIGlobalObject* global = workerRef->Private()->GlobalScope(); if (NS_WARN_IF(!global)) { aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); return; } // Eventually we need to support all workers, but for right now this // code assumes we're on a service worker global as self.registration. if (NS_WARN_IF(!workerRef->Private()->IsServiceWorker())) { aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); return; } // Avoid infinite update loops by ignoring update() calls during top // level script evaluation. See: // https://github.com/slightlyoff/ServiceWorker/issues/800 if (workerRef->Private()->IsLoadingWorkerScript()) { aSuccessCB(mDescriptor); return; } auto promise = MakeRefPtr(__func__); auto holder = MakeRefPtr>( global); promise ->Then( global->EventTargetFor(TaskCategory::Other), __func__, [successCB = std::move(aSuccessCB), holder](const ServiceWorkerRegistrationDescriptor& aDescriptor) { holder->Complete(); successCB(aDescriptor); }, [failureCB = std::move(aFailureCB), holder](const CopyableErrorResult& aRv) { holder->Complete(); failureCB(CopyableErrorResult(aRv)); }) ->Track(*holder); RefPtr r = new SWRUpdateRunnable( workerRef, promise, workerRef->Private()->GetServiceWorkerDescriptor(), aNewestWorkerScriptUrl); nsresult rv = workerRef->Private()->DispatchToMainThread(r.forget()); if (NS_FAILED(rv)) { promise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__); return; } } void ServiceWorkerRegistrationWorkerThread::Unregister( ServiceWorkerBoolCallback&& aSuccessCB, ServiceWorkerFailureCallback&& aFailureCB) { if (NS_WARN_IF(!mWorkerRef->GetPrivate())) { aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); return; } RefPtr workerRef = StrongWorkerRef::Create(mWorkerRef->GetPrivate(), __func__); if (NS_WARN_IF(!workerRef)) { aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); return; } // Eventually we need to support all workers, but for right now this // code assumes we're on a service worker global as self.registration. if (NS_WARN_IF(!workerRef->Private()->IsServiceWorker())) { aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); return; } nsIGlobalObject* global = workerRef->Private()->GlobalScope(); if (!global) { aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); return; } auto promise = MakeRefPtr(__func__); auto holder = MakeRefPtr>(global); promise ->Then( global->EventTargetFor(TaskCategory::Other), __func__, [successCB = std::move(aSuccessCB), holder](bool aResult) { holder->Complete(); successCB(aResult); }, [failureCB = std::move(aFailureCB), holder](nsresult aRv) { holder->Complete(); failureCB(CopyableErrorResult(aRv)); }) ->Track(*holder); RefPtr r = new StartUnregisterRunnable(workerRef, promise, mDescriptor); nsresult rv = workerRef->Private()->DispatchToMainThread(r); if (NS_FAILED(rv)) { promise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__); return; } } void ServiceWorkerRegistrationWorkerThread::InitListener() { MOZ_ASSERT(!mListener); WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(worker); worker->AssertIsOnWorkerThread(); RefPtr self = this; mWorkerRef = WeakWorkerRef::Create(worker, [self]() { self->ReleaseListener(); // Break the ref-cycle immediately when the worker thread starts to // teardown. We must make sure its GC'd before the worker RuntimeService is // destroyed. The WorkerListener may not be able to post a runnable // clearing this value after shutdown begins and thus delaying cleanup too // late. self->mOuter = nullptr; }); if (NS_WARN_IF(!mWorkerRef)) { return; } mListener = new WorkerListener(this, mDescriptor, worker->HybridEventTarget()); nsCOMPtr r = NewRunnableMethod("dom::WorkerListener::StartListeningForEvents", mListener, &WorkerListener::StartListeningForEvents); MOZ_ALWAYS_SUCCEEDS(worker->DispatchToMainThread(r.forget())); } void ServiceWorkerRegistrationWorkerThread::ReleaseListener() { if (!mListener) { return; } MOZ_ASSERT(IsCurrentThreadRunningWorker()); mListener->ClearRegistration(); nsCOMPtr r = NewCancelableRunnableMethod( "dom::WorkerListener::StopListeningForEvents", mListener, &WorkerListener::StopListeningForEvents); // Calling GetPrivate() is safe because this method is called when the // WorkerRef is notified. MOZ_ALWAYS_SUCCEEDS( mWorkerRef->GetPrivate()->DispatchToMainThread(r.forget())); mListener = nullptr; mWorkerRef = nullptr; } void ServiceWorkerRegistrationWorkerThread::UpdateState( const ServiceWorkerRegistrationDescriptor& aDescriptor) { if (mOuter) { mOuter->UpdateState(aDescriptor); } } void ServiceWorkerRegistrationWorkerThread::FireUpdateFound() { if (mOuter) { mOuter->MaybeDispatchUpdateFoundRunnable(); } } class RegistrationClearedWorkerRunnable final : public WorkerRunnable { RefPtr mListener; public: RegistrationClearedWorkerRunnable(WorkerPrivate* aWorkerPrivate, WorkerListener* aListener) : WorkerRunnable(aWorkerPrivate), mListener(aListener) { // Need this assertion for now since runnables which modify busy count can // only be dispatched from parent thread to worker thread and we don't deal // with nested workers. SW threads can't be nested. MOZ_ASSERT(aWorkerPrivate->IsServiceWorker()); } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); mListener->RegistrationCleared(); return true; } }; void WorkerListener::RegistrationCleared() { MutexAutoLock lock(mMutex); if (!mRegistration) { return; } if (NS_IsMainThread()) { RefPtr r = new RegistrationClearedWorkerRunnable( mRegistration->GetWorkerPrivate(lock), this); Unused << r->Dispatch(); StopListeningForEvents(); return; } mRegistration->RegistrationCleared(); } WorkerPrivate* ServiceWorkerRegistrationWorkerThread::GetWorkerPrivate( const MutexAutoLock& aProofOfLock) { // In this case, calling GetUnsafePrivate() is ok because we have a proof of // mutex lock. MOZ_ASSERT(mWorkerRef && mWorkerRef->GetUnsafePrivate()); return mWorkerRef->GetUnsafePrivate(); } } // namespace dom } // namespace mozilla