Bug 1663089 [wpt PR 25399] - idle-detection: Implement requestPermission() method...
[gecko.git] / dom / serviceworkers / ServiceWorkerShutdownBlocker.cpp
blob42656a53242820b682452cd8afb88adc9869413a
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ServiceWorkerShutdownBlocker.h"
9 #include <chrono>
10 #include <utility>
12 #include "MainThreadUtils.h"
13 #include "nsDebug.h"
14 #include "nsError.h"
15 #include "nsIWritablePropertyBag2.h"
16 #include "nsThreadUtils.h"
18 #include "mozilla/Assertions.h"
19 #include "mozilla/RefPtr.h"
21 namespace mozilla {
22 namespace dom {
24 NS_IMPL_ISUPPORTS(ServiceWorkerShutdownBlocker, nsIAsyncShutdownBlocker)
26 NS_IMETHODIMP ServiceWorkerShutdownBlocker::GetName(nsAString& aNameOut) {
27 aNameOut = nsLiteralString(
28 u"ServiceWorkerShutdownBlocker: shutting down Service Workers");
29 return NS_OK;
32 NS_IMETHODIMP
33 ServiceWorkerShutdownBlocker::BlockShutdown(nsIAsyncShutdownClient* aClient) {
34 AssertIsOnMainThread();
35 MOZ_ASSERT(!mShutdownClient);
37 mShutdownClient = aClient;
39 MaybeUnblockShutdown();
40 MaybeInitUnblockShutdownTimer();
42 return NS_OK;
45 NS_IMETHODIMP ServiceWorkerShutdownBlocker::GetState(nsIPropertyBag** aBagOut) {
46 AssertIsOnMainThread();
47 MOZ_ASSERT(aBagOut);
49 nsCOMPtr<nsIWritablePropertyBag2> propertyBag =
50 do_CreateInstance("@mozilla.org/hash-property-bag;1");
52 if (NS_WARN_IF(!propertyBag)) {
53 return NS_ERROR_OUT_OF_MEMORY;
56 nsresult rv = propertyBag->SetPropertyAsBool(u"acceptingPromises"_ns,
57 IsAcceptingPromises());
59 if (NS_WARN_IF(NS_FAILED(rv))) {
60 return rv;
63 rv = propertyBag->SetPropertyAsUint32(u"pendingPromises"_ns,
64 GetPendingPromises());
66 if (NS_WARN_IF(NS_FAILED(rv))) {
67 return rv;
70 nsAutoCString shutdownStates;
72 for (auto iter = mShutdownStates.iter(); !iter.done(); iter.next()) {
73 shutdownStates.Append(iter.get().value().GetProgressString());
74 shutdownStates.Append(", ");
77 rv = propertyBag->SetPropertyAsACString(u"shutdownStates"_ns, shutdownStates);
79 if (NS_WARN_IF(NS_FAILED(rv))) {
80 return rv;
83 propertyBag.forget(aBagOut);
85 return NS_OK;
88 /* static */ already_AddRefed<ServiceWorkerShutdownBlocker>
89 ServiceWorkerShutdownBlocker::CreateAndRegisterOn(
90 nsIAsyncShutdownClient* aShutdownBarrier) {
91 AssertIsOnMainThread();
92 MOZ_ASSERT(aShutdownBarrier);
94 RefPtr<ServiceWorkerShutdownBlocker> blocker =
95 new ServiceWorkerShutdownBlocker();
97 nsresult rv = aShutdownBarrier->AddBlocker(
98 blocker.get(), NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__,
99 u"Service Workers shutdown"_ns);
101 if (NS_WARN_IF(NS_FAILED(rv))) {
102 return nullptr;
105 return blocker.forget();
108 void ServiceWorkerShutdownBlocker::WaitOnPromise(
109 GenericNonExclusivePromise* aPromise, uint32_t aShutdownStateId) {
110 AssertIsOnMainThread();
111 MOZ_DIAGNOSTIC_ASSERT(IsAcceptingPromises());
112 MOZ_ASSERT(aPromise);
113 MOZ_ASSERT(mShutdownStates.has(aShutdownStateId));
115 ++mState.as<AcceptingPromises>().mPendingPromises;
117 RefPtr<ServiceWorkerShutdownBlocker> self = this;
119 aPromise->Then(GetCurrentSerialEventTarget(), __func__,
120 [self = std::move(self), shutdownStateId = aShutdownStateId](
121 const GenericNonExclusivePromise::ResolveOrRejectValue&) {
122 // Progress reporting might race with aPromise settling.
123 self->mShutdownStates.remove(shutdownStateId);
125 if (!self->PromiseSettled()) {
126 self->MaybeUnblockShutdown();
131 void ServiceWorkerShutdownBlocker::StopAcceptingPromises() {
132 AssertIsOnMainThread();
133 MOZ_ASSERT(IsAcceptingPromises());
135 mState = AsVariant(NotAcceptingPromises(mState.as<AcceptingPromises>()));
137 MaybeUnblockShutdown();
138 MaybeInitUnblockShutdownTimer();
141 uint32_t ServiceWorkerShutdownBlocker::CreateShutdownState() {
142 AssertIsOnMainThread();
144 static uint32_t nextShutdownStateId = 1;
146 MOZ_ALWAYS_TRUE(mShutdownStates.putNew(nextShutdownStateId,
147 ServiceWorkerShutdownState()));
149 return nextShutdownStateId++;
152 void ServiceWorkerShutdownBlocker::ReportShutdownProgress(
153 uint32_t aShutdownStateId, Progress aProgress) {
154 AssertIsOnMainThread();
155 MOZ_RELEASE_ASSERT(aShutdownStateId != kInvalidShutdownStateId);
157 auto lookup = mShutdownStates.lookup(aShutdownStateId);
159 // Progress reporting might race with the promise that WaitOnPromise is called
160 // with settling.
161 if (!lookup) {
162 return;
165 // This will check for a valid progress transition with assertions.
166 lookup->value().SetProgress(aProgress);
168 if (aProgress == Progress::ShutdownCompleted) {
169 mShutdownStates.remove(lookup);
173 ServiceWorkerShutdownBlocker::ServiceWorkerShutdownBlocker()
174 : mState(VariantType<AcceptingPromises>()) {
175 AssertIsOnMainThread();
178 ServiceWorkerShutdownBlocker::~ServiceWorkerShutdownBlocker() {
179 MOZ_ASSERT(!IsAcceptingPromises());
180 MOZ_ASSERT(!GetPendingPromises());
181 MOZ_ASSERT(!mShutdownClient);
184 void ServiceWorkerShutdownBlocker::MaybeUnblockShutdown() {
185 AssertIsOnMainThread();
187 if (!mShutdownClient || IsAcceptingPromises() || GetPendingPromises()) {
188 return;
191 UnblockShutdown();
194 void ServiceWorkerShutdownBlocker::UnblockShutdown() {
195 MOZ_ASSERT(mShutdownClient);
197 mShutdownClient->RemoveBlocker(this);
198 mShutdownClient = nullptr;
200 if (mTimer) {
201 mTimer->Cancel();
205 uint32_t ServiceWorkerShutdownBlocker::PromiseSettled() {
206 AssertIsOnMainThread();
207 MOZ_ASSERT(GetPendingPromises());
209 if (IsAcceptingPromises()) {
210 return --mState.as<AcceptingPromises>().mPendingPromises;
213 return --mState.as<NotAcceptingPromises>().mPendingPromises;
216 bool ServiceWorkerShutdownBlocker::IsAcceptingPromises() const {
217 AssertIsOnMainThread();
219 return mState.is<AcceptingPromises>();
222 uint32_t ServiceWorkerShutdownBlocker::GetPendingPromises() const {
223 AssertIsOnMainThread();
225 if (IsAcceptingPromises()) {
226 return mState.as<AcceptingPromises>().mPendingPromises;
229 return mState.as<NotAcceptingPromises>().mPendingPromises;
232 ServiceWorkerShutdownBlocker::NotAcceptingPromises::NotAcceptingPromises(
233 AcceptingPromises aPreviousState)
234 : mPendingPromises(aPreviousState.mPendingPromises) {
235 AssertIsOnMainThread();
238 NS_IMETHODIMP ServiceWorkerShutdownBlocker::Notify(nsITimer*) {
239 // TODO: this method being called indicates that there are ServiceWorkers
240 // that did not complete shutdown before the timer expired - there should be
241 // a telemetry ping or some other way of recording the state of when this
242 // happens (e.g. what's returned by GetState()).
243 UnblockShutdown();
244 return NS_OK;
247 void ServiceWorkerShutdownBlocker::MaybeInitUnblockShutdownTimer() {
248 #ifdef RELEASE_OR_BETA
249 AssertIsOnMainThread();
250 MOZ_ASSERT(!mTimer);
252 if (!mShutdownClient || IsAcceptingPromises()) {
253 return;
256 MOZ_ASSERT(GetPendingPromises(),
257 "Shouldn't be blocking shutdown with zero pending promises.");
259 using namespace std::chrono_literals;
261 static constexpr auto delay =
262 std::chrono::duration_cast<std::chrono::milliseconds>(10s);
264 mTimer = NS_NewTimer();
266 mTimer->InitWithCallback(this, delay.count(), nsITimer::TYPE_ONE_SHOT);
267 #endif
270 } // namespace dom
271 } // namespace mozilla