diff --git a/dom/serviceworkers/ServiceWorkerShutdownBlocker.cpp b/dom/serviceworkers/ServiceWorkerShutdownBlocker.cpp new file mode 100644 index 000000000000..534f83094200 --- /dev/null +++ b/dom/serviceworkers/ServiceWorkerShutdownBlocker.cpp @@ -0,0 +1,174 @@ +/* -*- 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 "ServiceWorkerShutdownBlocker.h" + +#include + +#include "MainThreadUtils.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsIWritablePropertyBag2.h" +#include "nsThreadUtils.h" + +#include "mozilla/Assertions.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_ISUPPORTS(ServiceWorkerShutdownBlocker, nsIAsyncShutdownBlocker) + +NS_IMETHODIMP ServiceWorkerShutdownBlocker::GetName(nsAString& aNameOut) { + aNameOut = NS_LITERAL_STRING( + "ServiceWorkerShutdownBlocker: shutting down Service Workers"); + return NS_OK; +} + +NS_IMETHODIMP +ServiceWorkerShutdownBlocker::BlockShutdown(nsIAsyncShutdownClient* aClient) { + AssertIsOnMainThread(); + MOZ_ASSERT(!mShutdownClient); + + mShutdownClient = aClient; + MaybeUnblockShutdown(); + + return NS_OK; +} + +NS_IMETHODIMP ServiceWorkerShutdownBlocker::GetState(nsIPropertyBag** aBagOut) { + AssertIsOnMainThread(); + MOZ_ASSERT(aBagOut); + + nsCOMPtr propertyBag = + do_CreateInstance("@mozilla.org/hash-property-bag;1"); + + if (NS_WARN_IF(!propertyBag)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = propertyBag->SetPropertyAsBool( + NS_LITERAL_STRING("acceptingPromises"), IsAcceptingPromises()); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = propertyBag->SetPropertyAsUint32(NS_LITERAL_STRING("pendingPromises"), + GetPendingPromises()); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + propertyBag.forget(aBagOut); + + return NS_OK; +} + +/* static */ already_AddRefed +ServiceWorkerShutdownBlocker::CreateAndRegisterOn( + nsIAsyncShutdownClient* aShutdownBarrier) { + AssertIsOnMainThread(); + MOZ_ASSERT(aShutdownBarrier); + + RefPtr blocker = + new ServiceWorkerShutdownBlocker(); + + nsresult rv = aShutdownBarrier->AddBlocker( + blocker.get(), NS_LITERAL_STRING(__FILE__), __LINE__, + NS_LITERAL_STRING("Service Workers shutdown")); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + return blocker.forget(); +} + +void ServiceWorkerShutdownBlocker::WaitOnPromise( + GenericNonExclusivePromise* aPromise) { + AssertIsOnMainThread(); + MOZ_DIAGNOSTIC_ASSERT(IsAcceptingPromises()); + MOZ_ASSERT(aPromise); + + ++mState.as().mPendingPromises; + + RefPtr self = this; + + aPromise->Then(GetCurrentThreadSerialEventTarget(), __func__, + [self = std::move(self)]( + const GenericNonExclusivePromise::ResolveOrRejectValue&) { + if (!self->PromiseSettled()) { + self->MaybeUnblockShutdown(); + } + }); +} + +void ServiceWorkerShutdownBlocker::StopAcceptingPromises() { + AssertIsOnMainThread(); + MOZ_ASSERT(IsAcceptingPromises()); + + mState = AsVariant(NotAcceptingPromises(mState.as())); +} + +ServiceWorkerShutdownBlocker::ServiceWorkerShutdownBlocker() + : mState(VariantType()) { + AssertIsOnMainThread(); +} + +ServiceWorkerShutdownBlocker::~ServiceWorkerShutdownBlocker() { + MOZ_ASSERT(!IsAcceptingPromises()); + MOZ_ASSERT(!GetPendingPromises()); + MOZ_ASSERT(!mShutdownClient); +} + +void ServiceWorkerShutdownBlocker::MaybeUnblockShutdown() { + AssertIsOnMainThread(); + + if (!mShutdownClient || IsAcceptingPromises() || GetPendingPromises()) { + return; + } + + mShutdownClient->RemoveBlocker(this); + mShutdownClient = nullptr; +} + +uint32_t ServiceWorkerShutdownBlocker::PromiseSettled() { + AssertIsOnMainThread(); + MOZ_ASSERT(GetPendingPromises()); + + if (IsAcceptingPromises()) { + return --mState.as().mPendingPromises; + } + + return --mState.as().mPendingPromises; +} + +bool ServiceWorkerShutdownBlocker::IsAcceptingPromises() const { + AssertIsOnMainThread(); + + return mState.is(); +} + +uint32_t ServiceWorkerShutdownBlocker::GetPendingPromises() const { + AssertIsOnMainThread(); + + if (IsAcceptingPromises()) { + return mState.as().mPendingPromises; + } + + return mState.as().mPendingPromises; +} + +ServiceWorkerShutdownBlocker::NotAcceptingPromises::NotAcceptingPromises( + AcceptingPromises aPreviousState) + : mPendingPromises(aPreviousState.mPendingPromises) { + AssertIsOnMainThread(); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/serviceworkers/ServiceWorkerShutdownBlocker.h b/dom/serviceworkers/ServiceWorkerShutdownBlocker.h new file mode 100644 index 000000000000..87373da762e0 --- /dev/null +++ b/dom/serviceworkers/ServiceWorkerShutdownBlocker.h @@ -0,0 +1,92 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_serviceworkershutdownblocker_h__ +#define mozilla_dom_serviceworkershutdownblocker_h__ + +#include "nsCOMPtr.h" +#include "nsIAsyncShutdown.h" +#include "nsISupportsImpl.h" + +#include "mozilla/MozPromise.h" + +namespace mozilla { +namespace dom { + +/** + * Main thread only. + */ +class ServiceWorkerShutdownBlocker final : public nsIAsyncShutdownBlocker { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIASYNCSHUTDOWNBLOCKER + + /** + * Returns the registered shutdown blocker if registration succeeded and + * nullptr otherwise. + */ + static already_AddRefed CreateAndRegisterOn( + nsIAsyncShutdownClient* aShutdownBarrier); + + /** + * Blocks shutdown until `aPromise` settles. + * + * Can be called multiple times, and shutdown will be blocked until all the + * calls' promises settle, but all of these calls must happen before + * `StopAcceptingPromises()` is called (assertions will enforce this). + */ + void WaitOnPromise(GenericNonExclusivePromise* aPromise); + + /** + * Once this is called, shutdown will be blocked until all promises + * passed to `WaitOnPromise()` settle, and there must be no more calls to + * `WaitOnPromise()` (assertions will enforce this). + */ + void StopAcceptingPromises(); + + private: + ServiceWorkerShutdownBlocker(); + + ~ServiceWorkerShutdownBlocker(); + + /** + * No-op if any of the following are true: + * 1) `BlockShutdown()` hasn't been called yet, or + * 2) `StopAcceptingPromises()` hasn't been called yet, or + * 3) `StopAcceptingPromises()` HAS been called, but there are still pending + * promises. + */ + void MaybeUnblockShutdown(); + + /** + * Returns the remaining pending promise count (i.e. excluding the promise + * that just settled). + */ + uint32_t PromiseSettled(); + + bool IsAcceptingPromises() const; + + uint32_t GetPendingPromises() const; + + struct AcceptingPromises { + uint32_t mPendingPromises = 0; + }; + + struct NotAcceptingPromises { + explicit NotAcceptingPromises(AcceptingPromises aPreviousState); + + uint32_t mPendingPromises = 0; + }; + + Variant mState; + + nsCOMPtr mShutdownClient; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_serviceworkershutdownblocker_h__ diff --git a/dom/serviceworkers/moz.build b/dom/serviceworkers/moz.build index d60784941c4b..4f711f902c4c 100644 --- a/dom/serviceworkers/moz.build +++ b/dom/serviceworkers/moz.build @@ -65,6 +65,7 @@ UNIFIED_SOURCES += [ 'ServiceWorkerRegistrationParent.cpp', 'ServiceWorkerRegistrationProxy.cpp', 'ServiceWorkerScriptCache.cpp', + 'ServiceWorkerShutdownBlocker.cpp', 'ServiceWorkerUnregisterCallback.cpp', 'ServiceWorkerUnregisterJob.cpp', 'ServiceWorkerUpdateJob.cpp',