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/. */
6
7 #include "ServiceWorkerPrivate.h"
8
9 #include <utility>
10
11 #include "ServiceWorkerCloneData.h"
12 #include "ServiceWorkerManager.h"
13 #include "ServiceWorkerPrivateImpl.h"
14 #include "ServiceWorkerUtils.h"
15 #include "nsContentUtils.h"
16 #include "nsICacheInfoChannel.h"
17 #include "nsIHttpChannel.h"
18 #include "nsIHttpChannelInternal.h"
19 #include "nsIHttpHeaderVisitor.h"
20 #include "nsINamed.h"
21 #include "nsINetworkInterceptController.h"
22 #include "nsIPushErrorReporter.h"
23 #include "nsISupportsImpl.h"
24 #include "nsIUploadChannel2.h"
25 #include "nsNetUtil.h"
26 #include "nsProxyRelease.h"
27 #include "nsQueryObject.h"
28 #include "nsStreamUtils.h"
29 #include "nsStringStream.h"
30 #include "mozilla/Assertions.h"
31 #include "mozilla/CycleCollectedJSContext.h" // for MicroTaskRunnable
32 #include "mozilla/JSObjectHolder.h"
33 #include "mozilla/Preferences.h"
34 #include "mozilla/dom/Client.h"
35 #include "mozilla/dom/ClientIPCTypes.h"
36 #include "mozilla/dom/FetchUtil.h"
37 #include "mozilla/dom/IndexedDatabaseManager.h"
38 #include "mozilla/dom/InternalHeaders.h"
39 #include "mozilla/dom/NotificationEvent.h"
40 #include "mozilla/dom/PromiseNativeHandler.h"
41 #include "mozilla/dom/PushEventBinding.h"
42 #include "mozilla/dom/RequestBinding.h"
43 #include "mozilla/dom/WorkerDebugger.h"
44 #include "mozilla/dom/WorkerRef.h"
45 #include "mozilla/dom/WorkerRunnable.h"
46 #include "mozilla/dom/WorkerScope.h"
47 #include "mozilla/dom/ipc/StructuredCloneData.h"
48 #include "mozilla/ipc/BackgroundUtils.h"
49 #include "mozilla/net/CookieJarSettings.h"
50 #include "mozilla/net/NeckoChannelParams.h"
51 #include "mozilla/Services.h"
52 #include "mozilla/StoragePrincipalHelper.h"
53 #include "mozilla/Telemetry.h"
54 #include "mozilla/DebugOnly.h"
55 #include "mozilla/StaticPrefs_dom.h"
56 #include "mozilla/StaticPrefs_privacy.h"
57 #include "mozilla/Unused.h"
58 #include "nsIReferrerInfo.h"
59
60 using namespace mozilla;
61 using namespace mozilla::dom;
62
63 namespace mozilla {
64 namespace dom {
65
66 using mozilla::ipc::PrincipalInfo;
67
68 NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(ServiceWorkerPrivate)
69 NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(ServiceWorkerPrivate)
70 NS_IMPL_CYCLE_COLLECTION(ServiceWorkerPrivate, mSupportsArray)
71 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(ServiceWorkerPrivate, AddRef)
72 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(ServiceWorkerPrivate, Release)
73
74 // Tracks the "dom.serviceWorkers.disable_open_click_delay" preference. Modified
75 // on main thread, read on worker threads.
76 // It is updated every time a "notificationclick" event is dispatched. While
77 // this is done without synchronization, at the worst, the thread will just get
78 // an older value within which a popup is allowed to be displayed, which will
79 // still be a valid value since it was set prior to dispatching the runnable.
80 Atomic<uint32_t> gDOMDisableOpenClickDelay(0);
81
KeepAliveToken(ServiceWorkerPrivate * aPrivate)82 KeepAliveToken::KeepAliveToken(ServiceWorkerPrivate* aPrivate)
83 : mPrivate(aPrivate) {
84 MOZ_ASSERT(NS_IsMainThread());
85 MOZ_ASSERT(aPrivate);
86 mPrivate->AddToken();
87 }
88
~KeepAliveToken()89 KeepAliveToken::~KeepAliveToken() {
90 MOZ_ASSERT(NS_IsMainThread());
91 mPrivate->ReleaseToken();
92 }
93
NS_IMPL_ISUPPORTS0(KeepAliveToken)94 NS_IMPL_ISUPPORTS0(KeepAliveToken)
95
96 ServiceWorkerPrivate::ServiceWorkerPrivate(ServiceWorkerInfo* aInfo)
97 : mInfo(aInfo), mDebuggerCount(0), mTokenCount(0) {
98 MOZ_ASSERT(NS_IsMainThread());
99 MOZ_ASSERT(aInfo);
100
101 mIdleWorkerTimer = NS_NewTimer();
102 MOZ_ASSERT(mIdleWorkerTimer);
103
104 RefPtr<ServiceWorkerPrivateImpl> inner = new ServiceWorkerPrivateImpl(this);
105
106 // Assert in all debug builds as well as non-debug Nightly and Dev Edition.
107 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
108 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(inner->Initialize()));
109 #else
110 MOZ_ALWAYS_SUCCEEDS(inner->Initialize());
111 #endif
112
113 mInner = std::move(inner);
114 }
115
~ServiceWorkerPrivate()116 ServiceWorkerPrivate::~ServiceWorkerPrivate() {
117 MOZ_ASSERT(!mWorkerPrivate);
118 MOZ_ASSERT(!mTokenCount);
119 MOZ_ASSERT(!mInner);
120 MOZ_ASSERT(!mInfo);
121 MOZ_ASSERT(mSupportsArray.IsEmpty());
122 MOZ_ASSERT(mIdlePromiseHolder.IsEmpty());
123
124 mIdleWorkerTimer->Cancel();
125 }
126
127 namespace {
128
129 class CheckScriptEvaluationWithCallback final : public WorkerDebuggeeRunnable {
130 nsMainThreadPtrHandle<ServiceWorkerPrivate> mServiceWorkerPrivate;
131 nsMainThreadPtrHandle<KeepAliveToken> mKeepAliveToken;
132
133 // The script evaluation result must be reported even if the runnable
134 // is cancelled.
135 RefPtr<LifeCycleEventCallback> mScriptEvaluationCallback;
136
137 #ifdef DEBUG
138 bool mDone;
139 #endif
140
141 public:
CheckScriptEvaluationWithCallback(WorkerPrivate * aWorkerPrivate,ServiceWorkerPrivate * aServiceWorkerPrivate,KeepAliveToken * aKeepAliveToken,LifeCycleEventCallback * aScriptEvaluationCallback)142 CheckScriptEvaluationWithCallback(
143 WorkerPrivate* aWorkerPrivate,
144 ServiceWorkerPrivate* aServiceWorkerPrivate,
145 KeepAliveToken* aKeepAliveToken,
146 LifeCycleEventCallback* aScriptEvaluationCallback)
147 : WorkerDebuggeeRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount),
148 mServiceWorkerPrivate(new nsMainThreadPtrHolder<ServiceWorkerPrivate>(
149 "CheckScriptEvaluationWithCallback::mServiceWorkerPrivate",
150 aServiceWorkerPrivate)),
151 mKeepAliveToken(new nsMainThreadPtrHolder<KeepAliveToken>(
152 "CheckScriptEvaluationWithCallback::mKeepAliveToken",
153 aKeepAliveToken)),
154 mScriptEvaluationCallback(aScriptEvaluationCallback)
155 #ifdef DEBUG
156 ,
157 mDone(false)
158 #endif
159 {
160 MOZ_ASSERT(NS_IsMainThread());
161 }
162
~CheckScriptEvaluationWithCallback()163 ~CheckScriptEvaluationWithCallback() { MOZ_ASSERT(mDone); }
164
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)165 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
166 aWorkerPrivate->AssertIsOnWorkerThread();
167
168 bool fetchHandlerWasAdded = aWorkerPrivate->FetchHandlerWasAdded();
169 nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<bool>(
170 "dom::CheckScriptEvaluationWithCallback::ReportFetchFlag", this,
171 &CheckScriptEvaluationWithCallback::ReportFetchFlag,
172 fetchHandlerWasAdded);
173 aWorkerPrivate->DispatchToMainThread(runnable.forget());
174
175 ReportScriptEvaluationResult(
176 aWorkerPrivate->WorkerScriptExecutedSuccessfully());
177
178 return true;
179 }
180
ReportFetchFlag(bool aFetchHandlerWasAdded)181 void ReportFetchFlag(bool aFetchHandlerWasAdded) {
182 MOZ_ASSERT(NS_IsMainThread());
183 mServiceWorkerPrivate->SetHandlesFetch(aFetchHandlerWasAdded);
184 }
185
Cancel()186 nsresult Cancel() override {
187 // We need to check first if cancel is permitted
188 nsresult rv = WorkerRunnable::Cancel();
189 NS_ENSURE_SUCCESS(rv, rv);
190
191 ReportScriptEvaluationResult(false);
192 return NS_OK;
193 }
194
195 private:
ReportScriptEvaluationResult(bool aScriptEvaluationResult)196 void ReportScriptEvaluationResult(bool aScriptEvaluationResult) {
197 #ifdef DEBUG
198 mDone = true;
199 #endif
200 mScriptEvaluationCallback->SetResult(aScriptEvaluationResult);
201 MOZ_ALWAYS_SUCCEEDS(
202 mWorkerPrivate->DispatchToMainThread(mScriptEvaluationCallback));
203 }
204 };
205
206 } // anonymous namespace
207
CheckScriptEvaluation(LifeCycleEventCallback * aScriptEvaluationCallback)208 nsresult ServiceWorkerPrivate::CheckScriptEvaluation(
209 LifeCycleEventCallback* aScriptEvaluationCallback) {
210 MOZ_ASSERT(NS_IsMainThread());
211
212 if (mInner) {
213 return mInner->CheckScriptEvaluation(aScriptEvaluationCallback);
214 }
215
216 nsresult rv = SpawnWorkerIfNeeded(LifeCycleEvent);
217 NS_ENSURE_SUCCESS(rv, rv);
218
219 RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
220 RefPtr<WorkerRunnable> r = new CheckScriptEvaluationWithCallback(
221 mWorkerPrivate, this, token, aScriptEvaluationCallback);
222 if (NS_WARN_IF(!r->Dispatch())) {
223 return NS_ERROR_FAILURE;
224 }
225
226 return NS_OK;
227 }
228
229 namespace {
230
231 class KeepAliveHandler final : public ExtendableEvent::ExtensionsHandler,
232 public PromiseNativeHandler {
233 // This class manages lifetime extensions added by calling WaitUntil()
234 // or RespondWith(). We allow new extensions as long as we still hold
235 // |mKeepAliveToken|. Once the last promise was settled, we queue a microtask
236 // which releases the token and prevents further extensions. By doing this,
237 // we give other pending microtasks a chance to continue adding extensions.
238
239 RefPtr<StrongWorkerRef> mWorkerRef;
240 nsMainThreadPtrHandle<KeepAliveToken> mKeepAliveToken;
241
242 // We start holding a self reference when the first extension promise is
243 // added. As far as I can tell, the only case where this is useful is when
244 // we're waiting indefinitely on a promise that's no longer reachable
245 // and will never be settled.
246 // The cycle is broken when the last promise was settled or when the
247 // worker is shutting down.
248 RefPtr<KeepAliveHandler> mSelfRef;
249
250 // Called when the last promise was settled.
251 RefPtr<ExtendableEventCallback> mCallback;
252
253 uint32_t mPendingPromisesCount;
254
255 // We don't actually care what values the promises resolve to, only whether
256 // any of them were rejected.
257 bool mRejected;
258
259 public:
260 NS_DECL_ISUPPORTS
261
KeepAliveHandler(const nsMainThreadPtrHandle<KeepAliveToken> & aKeepAliveToken,ExtendableEventCallback * aCallback)262 explicit KeepAliveHandler(
263 const nsMainThreadPtrHandle<KeepAliveToken>& aKeepAliveToken,
264 ExtendableEventCallback* aCallback)
265 : mKeepAliveToken(aKeepAliveToken),
266 mCallback(aCallback),
267 mPendingPromisesCount(0),
268 mRejected(false) {
269 MOZ_ASSERT(mKeepAliveToken);
270 }
271
Init()272 bool Init() {
273 MOZ_ASSERT(IsCurrentThreadRunningWorker());
274
275 RefPtr<KeepAliveHandler> self = this;
276 mWorkerRef = StrongWorkerRef::Create(GetCurrentThreadWorkerPrivate(),
277 "KeepAliveHandler",
278 [self]() { self->MaybeCleanup(); });
279
280 if (NS_WARN_IF(!mWorkerRef)) {
281 return false;
282 }
283
284 return true;
285 }
286
WaitOnPromise(Promise & aPromise)287 bool WaitOnPromise(Promise& aPromise) override {
288 if (!mKeepAliveToken) {
289 MOZ_ASSERT(!GetDispatchFlag());
290 MOZ_ASSERT(!mSelfRef, "We shouldn't be holding a self reference!");
291 return false;
292 }
293 if (!mSelfRef) {
294 MOZ_ASSERT(!mPendingPromisesCount);
295 mSelfRef = this;
296 }
297
298 ++mPendingPromisesCount;
299 aPromise.AppendNativeHandler(this);
300
301 return true;
302 }
303
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue,ErrorResult & aRv)304 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
305 ErrorResult& aRv) override {
306 RemovePromise(Resolved);
307 }
308
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue,ErrorResult & aRv)309 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
310 ErrorResult& aRv) override {
311 RemovePromise(Rejected);
312 }
313
MaybeDone()314 void MaybeDone() {
315 MOZ_ASSERT(IsCurrentThreadRunningWorker());
316 MOZ_ASSERT(!GetDispatchFlag());
317
318 if (mPendingPromisesCount || !mKeepAliveToken) {
319 return;
320 }
321 if (mCallback) {
322 mCallback->FinishedWithResult(mRejected ? Rejected : Resolved);
323 }
324
325 MaybeCleanup();
326 }
327
328 private:
~KeepAliveHandler()329 ~KeepAliveHandler() { MaybeCleanup(); }
330
MaybeCleanup()331 void MaybeCleanup() {
332 MOZ_ASSERT(IsCurrentThreadRunningWorker());
333
334 if (!mKeepAliveToken) {
335 return;
336 }
337
338 mWorkerRef = nullptr;
339 mKeepAliveToken = nullptr;
340 mSelfRef = nullptr;
341 }
342
343 class MaybeDoneRunner : public MicroTaskRunnable {
344 public:
MaybeDoneRunner(KeepAliveHandler * aHandler)345 explicit MaybeDoneRunner(KeepAliveHandler* aHandler) : mHandler(aHandler) {}
Run(AutoSlowOperation & aAso)346 virtual void Run(AutoSlowOperation& aAso) override {
347 mHandler->MaybeDone();
348 }
349
350 RefPtr<KeepAliveHandler> mHandler;
351 };
352
RemovePromise(ExtendableEventResult aResult)353 void RemovePromise(ExtendableEventResult aResult) {
354 MOZ_ASSERT(IsCurrentThreadRunningWorker());
355 MOZ_DIAGNOSTIC_ASSERT(mPendingPromisesCount > 0);
356
357 // Note: mSelfRef and mKeepAliveToken can be nullptr here
358 // if MaybeCleanup() was called just before a promise
359 // settled. This can happen, for example, if the
360 // worker thread is being terminated for running too
361 // long, browser shutdown, etc.
362
363 mRejected |= (aResult == Rejected);
364
365 --mPendingPromisesCount;
366 if (mPendingPromisesCount || GetDispatchFlag()) {
367 return;
368 }
369
370 CycleCollectedJSContext* cx = CycleCollectedJSContext::Get();
371 MOZ_ASSERT(cx);
372
373 RefPtr<MaybeDoneRunner> r = new MaybeDoneRunner(this);
374 cx->DispatchToMicroTask(r.forget());
375 }
376 };
377
378 NS_IMPL_ISUPPORTS0(KeepAliveHandler)
379
380 class RegistrationUpdateRunnable : public Runnable {
381 nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
382 const bool mNeedTimeCheck;
383
384 public:
RegistrationUpdateRunnable(nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> & aRegistration,bool aNeedTimeCheck)385 RegistrationUpdateRunnable(
386 nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
387 bool aNeedTimeCheck)
388 : Runnable("dom::RegistrationUpdateRunnable"),
389 mRegistration(aRegistration),
390 mNeedTimeCheck(aNeedTimeCheck) {
391 MOZ_DIAGNOSTIC_ASSERT(mRegistration);
392 }
393
394 NS_IMETHOD
Run()395 Run() override {
396 if (mNeedTimeCheck) {
397 mRegistration->MaybeScheduleTimeCheckAndUpdate();
398 } else {
399 mRegistration->MaybeScheduleUpdate();
400 }
401 return NS_OK;
402 }
403 };
404
405 class ExtendableEventWorkerRunnable : public WorkerRunnable {
406 protected:
407 nsMainThreadPtrHandle<KeepAliveToken> mKeepAliveToken;
408
409 public:
ExtendableEventWorkerRunnable(WorkerPrivate * aWorkerPrivate,KeepAliveToken * aKeepAliveToken)410 ExtendableEventWorkerRunnable(WorkerPrivate* aWorkerPrivate,
411 KeepAliveToken* aKeepAliveToken)
412 : WorkerRunnable(aWorkerPrivate) {
413 MOZ_ASSERT(NS_IsMainThread());
414 MOZ_ASSERT(aWorkerPrivate);
415 MOZ_ASSERT(aKeepAliveToken);
416
417 mKeepAliveToken = new nsMainThreadPtrHolder<KeepAliveToken>(
418 "ExtendableEventWorkerRunnable::mKeepAliveToken", aKeepAliveToken);
419 }
420
DispatchExtendableEventOnWorkerScope(JSContext * aCx,WorkerGlobalScope * aWorkerScope,ExtendableEvent * aEvent,ExtendableEventCallback * aCallback)421 nsresult DispatchExtendableEventOnWorkerScope(
422 JSContext* aCx, WorkerGlobalScope* aWorkerScope, ExtendableEvent* aEvent,
423 ExtendableEventCallback* aCallback) {
424 MOZ_ASSERT(aWorkerScope);
425 MOZ_ASSERT(aEvent);
426 nsCOMPtr<nsIGlobalObject> sgo = aWorkerScope;
427 WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
428
429 RefPtr<KeepAliveHandler> keepAliveHandler =
430 new KeepAliveHandler(mKeepAliveToken, aCallback);
431 if (NS_WARN_IF(!keepAliveHandler->Init())) {
432 return NS_ERROR_FAILURE;
433 }
434
435 // This must always be set *before* dispatching the event, otherwise
436 // waitUntil calls will fail.
437 aEvent->SetKeepAliveHandler(keepAliveHandler);
438
439 ErrorResult result;
440 aWorkerScope->DispatchEvent(*aEvent, result);
441 if (NS_WARN_IF(result.Failed())) {
442 result.SuppressException();
443 return NS_ERROR_FAILURE;
444 }
445
446 // [[ If e’s extend lifetime promises is empty, unset e’s extensions allowed
447 // flag and abort these steps. ]]
448 keepAliveHandler->MaybeDone();
449
450 // We don't block the event when getting an exception but still report the
451 // error message.
452 // Report exception message. Note: This will not stop the event.
453 if (internalEvent->mFlags.mExceptionWasRaised) {
454 result.SuppressException();
455 return NS_ERROR_XPC_JS_THREW_EXCEPTION;
456 }
457
458 return NS_OK;
459 }
460 };
461
462 class SendMessageEventRunnable final : public ExtendableEventWorkerRunnable {
463 const ClientInfoAndState mClientInfoAndState;
464 RefPtr<ServiceWorkerCloneData> mData;
465
466 public:
SendMessageEventRunnable(WorkerPrivate * aWorkerPrivate,KeepAliveToken * aKeepAliveToken,const ClientInfoAndState & aClientInfoAndState,RefPtr<ServiceWorkerCloneData> && aData)467 SendMessageEventRunnable(WorkerPrivate* aWorkerPrivate,
468 KeepAliveToken* aKeepAliveToken,
469 const ClientInfoAndState& aClientInfoAndState,
470 RefPtr<ServiceWorkerCloneData>&& aData)
471 : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken),
472 mClientInfoAndState(aClientInfoAndState),
473 mData(std::move(aData)) {
474 MOZ_ASSERT(NS_IsMainThread());
475 MOZ_DIAGNOSTIC_ASSERT(mData);
476 }
477
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)478 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
479 JS::Rooted<JS::Value> messageData(aCx);
480 nsCOMPtr<nsIGlobalObject> sgo = aWorkerPrivate->GlobalScope();
481 ErrorResult rv;
482 mData->Read(aCx, &messageData, rv);
483
484 // If deserialization fails, we will fire a messageerror event
485 bool deserializationFailed = rv.Failed();
486
487 if (!deserializationFailed && NS_WARN_IF(rv.Failed())) {
488 rv.SuppressException();
489 return true;
490 }
491
492 Sequence<OwningNonNull<MessagePort>> ports;
493 if (!mData->TakeTransferredPortsAsSequence(ports)) {
494 return true;
495 }
496
497 RootedDictionary<ExtendableMessageEventInit> init(aCx);
498
499 init.mBubbles = false;
500 init.mCancelable = false;
501
502 // On a messageerror event, we disregard ports:
503 // https://w3c.github.io/ServiceWorker/#service-worker-postmessage
504 if (!deserializationFailed) {
505 init.mData = messageData;
506 init.mPorts = std::move(ports);
507 }
508
509 init.mSource.SetValue().SetAsClient() =
510 new Client(sgo, mClientInfoAndState);
511
512 rv.SuppressException();
513 RefPtr<EventTarget> target = aWorkerPrivate->GlobalScope();
514 RefPtr<ExtendableMessageEvent> extendableEvent =
515 ExtendableMessageEvent::Constructor(
516 target, deserializationFailed ? u"messageerror"_ns : u"message"_ns,
517 init);
518
519 extendableEvent->SetTrusted(true);
520
521 return NS_SUCCEEDED(DispatchExtendableEventOnWorkerScope(
522 aCx, aWorkerPrivate->GlobalScope(), extendableEvent, nullptr));
523 }
524 };
525
526 } // anonymous namespace
527
SendMessageEvent(RefPtr<ServiceWorkerCloneData> && aData,const ClientInfoAndState & aClientInfoAndState)528 nsresult ServiceWorkerPrivate::SendMessageEvent(
529 RefPtr<ServiceWorkerCloneData>&& aData,
530 const ClientInfoAndState& aClientInfoAndState) {
531 MOZ_ASSERT(NS_IsMainThread());
532
533 if (mInner) {
534 return mInner->SendMessageEvent(std::move(aData), aClientInfoAndState);
535 }
536
537 nsresult rv = SpawnWorkerIfNeeded(MessageEvent);
538 NS_ENSURE_SUCCESS(rv, rv);
539
540 RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
541 RefPtr<SendMessageEventRunnable> runnable = new SendMessageEventRunnable(
542 mWorkerPrivate, token, aClientInfoAndState, std::move(aData));
543
544 if (!runnable->Dispatch()) {
545 return NS_ERROR_FAILURE;
546 }
547
548 return NS_OK;
549 }
550
551 namespace {
552
553 // Handle functional event
554 // 9.9.7 If the time difference in seconds calculated by the current time minus
555 // registration's last update check time is greater than 86400, invoke Soft
556 // Update algorithm.
557 class ExtendableFunctionalEventWorkerRunnable
558 : public ExtendableEventWorkerRunnable {
559 protected:
560 nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
561
562 public:
ExtendableFunctionalEventWorkerRunnable(WorkerPrivate * aWorkerPrivate,KeepAliveToken * aKeepAliveToken,nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> & aRegistration)563 ExtendableFunctionalEventWorkerRunnable(
564 WorkerPrivate* aWorkerPrivate, KeepAliveToken* aKeepAliveToken,
565 nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration)
566 : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken),
567 mRegistration(aRegistration) {
568 MOZ_DIAGNOSTIC_ASSERT(aRegistration);
569 }
570
PostRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate,bool aRunResult)571 void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
572 bool aRunResult) override {
573 // Sub-class PreRun() or WorkerRun() methods could clear our mRegistration.
574 if (mRegistration) {
575 nsCOMPtr<nsIRunnable> runnable =
576 new RegistrationUpdateRunnable(mRegistration, true /* time check */);
577 aWorkerPrivate->DispatchToMainThread(runnable.forget());
578 }
579
580 ExtendableEventWorkerRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
581 }
582 };
583
584 /*
585 * Fires 'install' event on the ServiceWorkerGlobalScope. Modifies busy count
586 * since it fires the event. This is ok since there can't be nested
587 * ServiceWorkers, so the parent thread -> worker thread requirement for
588 * runnables is satisfied.
589 */
590 class LifecycleEventWorkerRunnable : public ExtendableEventWorkerRunnable {
591 nsString mEventName;
592 RefPtr<LifeCycleEventCallback> mCallback;
593
594 public:
LifecycleEventWorkerRunnable(WorkerPrivate * aWorkerPrivate,KeepAliveToken * aToken,const nsAString & aEventName,LifeCycleEventCallback * aCallback)595 LifecycleEventWorkerRunnable(WorkerPrivate* aWorkerPrivate,
596 KeepAliveToken* aToken,
597 const nsAString& aEventName,
598 LifeCycleEventCallback* aCallback)
599 : ExtendableEventWorkerRunnable(aWorkerPrivate, aToken),
600 mEventName(aEventName),
601 mCallback(aCallback) {
602 MOZ_ASSERT(NS_IsMainThread());
603 }
604
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)605 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
606 MOZ_ASSERT(aWorkerPrivate);
607 return DispatchLifecycleEvent(aCx, aWorkerPrivate);
608 }
609
Cancel()610 nsresult Cancel() override {
611 // We need to check first if cancel is permitted
612 nsresult rv = WorkerRunnable::Cancel();
613 NS_ENSURE_SUCCESS(rv, rv);
614
615 mCallback->SetResult(false);
616 MOZ_ALWAYS_SUCCEEDS(mWorkerPrivate->DispatchToMainThread(mCallback));
617
618 return NS_OK;
619 }
620
621 private:
622 bool DispatchLifecycleEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate);
623 };
624
625 /*
626 * Used to handle ExtendableEvent::waitUntil() and catch abnormal worker
627 * termination during the execution of life cycle events. It is responsible
628 * with advancing the job queue for install/activate tasks.
629 */
630 class LifeCycleEventWatcher final : public ExtendableEventCallback {
631 RefPtr<StrongWorkerRef> mWorkerRef;
632 RefPtr<LifeCycleEventCallback> mCallback;
633
~LifeCycleEventWatcher()634 ~LifeCycleEventWatcher() {
635 // XXXcatalinb: If all the promises passed to waitUntil go out of scope,
636 // the resulting Promise.all will be cycle collected and it will drop its
637 // native handlers (including this object). Instead of waiting for a timeout
638 // we report the failure now.
639 ReportResult(false);
640 }
641
642 public:
NS_INLINE_DECL_REFCOUNTING(LifeCycleEventWatcher,override)643 NS_INLINE_DECL_REFCOUNTING(LifeCycleEventWatcher, override)
644
645 explicit LifeCycleEventWatcher(LifeCycleEventCallback* aCallback)
646 : mCallback(aCallback) {
647 MOZ_ASSERT(IsCurrentThreadRunningWorker());
648 }
649
Init()650 bool Init() {
651 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
652 MOZ_ASSERT(workerPrivate);
653
654 // We need to listen for worker termination in case the event handler
655 // never completes or never resolves the waitUntil promise. There are
656 // two possible scenarios:
657 // 1. The keepAlive token expires and the worker is terminated, in which
658 // case the registration/update promise will be rejected
659 // 2. A new service worker is registered which will terminate the current
660 // installing worker.
661 RefPtr<LifeCycleEventWatcher> self = this;
662 mWorkerRef =
663 StrongWorkerRef::Create(workerPrivate, "LifeCycleEventWatcher",
664 [self]() { self->ReportResult(false); });
665 if (NS_WARN_IF(!mWorkerRef)) {
666 mCallback->SetResult(false);
667 // Using DispatchToMainThreadForMessaging so that state update on
668 // the main thread doesn't happen too soon.
669 nsresult rv = workerPrivate->DispatchToMainThreadForMessaging(mCallback);
670 Unused << NS_WARN_IF(NS_FAILED(rv));
671 return false;
672 }
673
674 return true;
675 }
676
ReportResult(bool aResult)677 void ReportResult(bool aResult) {
678 MOZ_ASSERT(IsCurrentThreadRunningWorker());
679
680 if (!mWorkerRef) {
681 return;
682 }
683
684 mCallback->SetResult(aResult);
685 // Using DispatchToMainThreadForMessaging so that state update on
686 // the main thread doesn't happen too soon.
687 nsresult rv =
688 mWorkerRef->Private()->DispatchToMainThreadForMessaging(mCallback);
689 if (NS_WARN_IF(NS_FAILED(rv))) {
690 MOZ_CRASH("Failed to dispatch life cycle event handler.");
691 }
692
693 mWorkerRef = nullptr;
694 }
695
FinishedWithResult(ExtendableEventResult aResult)696 void FinishedWithResult(ExtendableEventResult aResult) override {
697 MOZ_ASSERT(IsCurrentThreadRunningWorker());
698 ReportResult(aResult == Resolved);
699
700 // Note, all WaitUntil() rejections are reported to client consoles
701 // by the WaitUntilHandler in ServiceWorkerEvents. This ensures that
702 // errors in non-lifecycle events like FetchEvent and PushEvent are
703 // reported properly.
704 }
705 };
706
DispatchLifecycleEvent(JSContext * aCx,WorkerPrivate * aWorkerPrivate)707 bool LifecycleEventWorkerRunnable::DispatchLifecycleEvent(
708 JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
709 aWorkerPrivate->AssertIsOnWorkerThread();
710 MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
711
712 RefPtr<ExtendableEvent> event;
713 RefPtr<EventTarget> target = aWorkerPrivate->GlobalScope();
714
715 if (mEventName.EqualsASCII("install") || mEventName.EqualsASCII("activate")) {
716 ExtendableEventInit init;
717 init.mBubbles = false;
718 init.mCancelable = false;
719 event = ExtendableEvent::Constructor(target, mEventName, init);
720 } else {
721 MOZ_CRASH("Unexpected lifecycle event");
722 }
723
724 event->SetTrusted(true);
725
726 // It is important to initialize the watcher before actually dispatching
727 // the event in order to catch worker termination while the event handler
728 // is still executing. This can happen with infinite loops, for example.
729 RefPtr<LifeCycleEventWatcher> watcher = new LifeCycleEventWatcher(mCallback);
730
731 if (!watcher->Init()) {
732 return true;
733 }
734
735 nsresult rv = DispatchExtendableEventOnWorkerScope(
736 aCx, aWorkerPrivate->GlobalScope(), event, watcher);
737 // Do not fail event processing when an exception is thrown.
738 if (NS_FAILED(rv) && rv != NS_ERROR_XPC_JS_THREW_EXCEPTION) {
739 watcher->ReportResult(false);
740 }
741
742 return true;
743 }
744
745 } // anonymous namespace
746
SendLifeCycleEvent(const nsAString & aEventType,LifeCycleEventCallback * aCallback)747 nsresult ServiceWorkerPrivate::SendLifeCycleEvent(
748 const nsAString& aEventType, LifeCycleEventCallback* aCallback) {
749 MOZ_ASSERT(NS_IsMainThread());
750
751 if (mInner) {
752 return mInner->SendLifeCycleEvent(aEventType, aCallback);
753 }
754
755 nsresult rv = SpawnWorkerIfNeeded(LifeCycleEvent);
756 NS_ENSURE_SUCCESS(rv, rv);
757
758 RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
759 RefPtr<WorkerRunnable> r = new LifecycleEventWorkerRunnable(
760 mWorkerPrivate, token, aEventType, aCallback);
761 if (NS_WARN_IF(!r->Dispatch())) {
762 return NS_ERROR_FAILURE;
763 }
764
765 return NS_OK;
766 }
767
768 namespace {
769
770 class PushErrorReporter final : public ExtendableEventCallback {
771 WorkerPrivate* mWorkerPrivate;
772 nsString mMessageId;
773
774 ~PushErrorReporter() = default;
775
776 public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PushErrorReporter,override)777 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PushErrorReporter, override)
778
779 PushErrorReporter(WorkerPrivate* aWorkerPrivate, const nsAString& aMessageId)
780 : mWorkerPrivate(aWorkerPrivate), mMessageId(aMessageId) {
781 mWorkerPrivate->AssertIsOnWorkerThread();
782 }
783
FinishedWithResult(ExtendableEventResult aResult)784 void FinishedWithResult(ExtendableEventResult aResult) override {
785 if (aResult == Rejected) {
786 Report(nsIPushErrorReporter::DELIVERY_UNHANDLED_REJECTION);
787 }
788 }
789
Report(uint16_t aReason=nsIPushErrorReporter::DELIVERY_INTERNAL_ERROR)790 void Report(
791 uint16_t aReason = nsIPushErrorReporter::DELIVERY_INTERNAL_ERROR) {
792 WorkerPrivate* workerPrivate = mWorkerPrivate;
793 mWorkerPrivate->AssertIsOnWorkerThread();
794
795 if (NS_WARN_IF(aReason > nsIPushErrorReporter::DELIVERY_INTERNAL_ERROR) ||
796 mMessageId.IsEmpty()) {
797 return;
798 }
799 nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<uint16_t>(
800 "dom::PushErrorReporter::ReportOnMainThread", this,
801 &PushErrorReporter::ReportOnMainThread, aReason);
802 MOZ_ALWAYS_TRUE(
803 NS_SUCCEEDED(workerPrivate->DispatchToMainThread(runnable.forget())));
804 }
805
ReportOnMainThread(uint16_t aReason)806 void ReportOnMainThread(uint16_t aReason) {
807 MOZ_ASSERT(NS_IsMainThread());
808 nsCOMPtr<nsIPushErrorReporter> reporter =
809 do_GetService("@mozilla.org/push/Service;1");
810 if (reporter) {
811 nsresult rv = reporter->ReportDeliveryError(mMessageId, aReason);
812 Unused << NS_WARN_IF(NS_FAILED(rv));
813 }
814 }
815 };
816
817 class SendPushEventRunnable final
818 : public ExtendableFunctionalEventWorkerRunnable {
819 nsString mMessageId;
820 Maybe<nsTArray<uint8_t>> mData;
821
822 public:
SendPushEventRunnable(WorkerPrivate * aWorkerPrivate,KeepAliveToken * aKeepAliveToken,const nsAString & aMessageId,const Maybe<nsTArray<uint8_t>> & aData,nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> aRegistration)823 SendPushEventRunnable(
824 WorkerPrivate* aWorkerPrivate, KeepAliveToken* aKeepAliveToken,
825 const nsAString& aMessageId, const Maybe<nsTArray<uint8_t>>& aData,
826 nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> aRegistration)
827 : ExtendableFunctionalEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken,
828 aRegistration),
829 mMessageId(aMessageId),
830 mData(aData ? Some(aData->Clone()) : Nothing()) {
831 MOZ_ASSERT(NS_IsMainThread());
832 MOZ_ASSERT(aWorkerPrivate);
833 MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
834 }
835
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)836 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
837 MOZ_ASSERT(aWorkerPrivate);
838 GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper());
839
840 RefPtr<PushErrorReporter> errorReporter =
841 new PushErrorReporter(aWorkerPrivate, mMessageId);
842
843 RootedDictionary<PushEventInit> pei(aCx);
844 if (mData) {
845 const nsTArray<uint8_t>& bytes = mData.ref();
846 JSObject* data =
847 Uint8Array::Create(aCx, bytes.Length(), bytes.Elements());
848 if (!data) {
849 errorReporter->Report();
850 return false;
851 }
852 pei.mData.Construct().SetAsArrayBufferView().Init(data);
853 }
854 pei.mBubbles = false;
855 pei.mCancelable = false;
856
857 ErrorResult result;
858 RefPtr<PushEvent> event =
859 PushEvent::Constructor(globalObj, u"push"_ns, pei, result);
860 if (NS_WARN_IF(result.Failed())) {
861 result.SuppressException();
862 errorReporter->Report();
863 return false;
864 }
865 event->SetTrusted(true);
866
867 nsresult rv = DispatchExtendableEventOnWorkerScope(
868 aCx, aWorkerPrivate->GlobalScope(), event, errorReporter);
869 if (NS_FAILED(rv)) {
870 // We don't cancel WorkerPrivate when catching an excetpion.
871 errorReporter->Report(nsIPushErrorReporter::DELIVERY_UNCAUGHT_EXCEPTION);
872 }
873
874 return true;
875 }
876 };
877
878 class SendPushSubscriptionChangeEventRunnable final
879 : public ExtendableEventWorkerRunnable {
880 public:
SendPushSubscriptionChangeEventRunnable(WorkerPrivate * aWorkerPrivate,KeepAliveToken * aKeepAliveToken)881 explicit SendPushSubscriptionChangeEventRunnable(
882 WorkerPrivate* aWorkerPrivate, KeepAliveToken* aKeepAliveToken)
883 : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken) {
884 MOZ_ASSERT(NS_IsMainThread());
885 MOZ_ASSERT(aWorkerPrivate);
886 MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
887 }
888
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)889 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
890 MOZ_ASSERT(aWorkerPrivate);
891
892 RefPtr<EventTarget> target = aWorkerPrivate->GlobalScope();
893
894 ExtendableEventInit init;
895 init.mBubbles = false;
896 init.mCancelable = false;
897
898 RefPtr<ExtendableEvent> event = ExtendableEvent::Constructor(
899 target, u"pushsubscriptionchange"_ns, init);
900
901 event->SetTrusted(true);
902
903 DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(),
904 event, nullptr);
905
906 return true;
907 }
908 };
909
910 } // anonymous namespace
911
SendPushEvent(const nsAString & aMessageId,const Maybe<nsTArray<uint8_t>> & aData,ServiceWorkerRegistrationInfo * aRegistration)912 nsresult ServiceWorkerPrivate::SendPushEvent(
913 const nsAString& aMessageId, const Maybe<nsTArray<uint8_t>>& aData,
914 ServiceWorkerRegistrationInfo* aRegistration) {
915 MOZ_ASSERT(NS_IsMainThread());
916
917 if (mInner) {
918 return mInner->SendPushEvent(aRegistration, aMessageId, aData);
919 }
920
921 nsresult rv = SpawnWorkerIfNeeded(PushEvent);
922 NS_ENSURE_SUCCESS(rv, rv);
923
924 RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
925
926 nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
927 new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(
928 "ServiceWorkerRegistrationInfoProxy", aRegistration, false));
929
930 RefPtr<WorkerRunnable> r = new SendPushEventRunnable(
931 mWorkerPrivate, token, aMessageId, aData, regInfo);
932
933 if (mInfo->State() == ServiceWorkerState::Activating) {
934 mPendingFunctionalEvents.AppendElement(r.forget());
935 return NS_OK;
936 }
937
938 MOZ_ASSERT(mInfo->State() == ServiceWorkerState::Activated);
939
940 if (NS_WARN_IF(!r->Dispatch())) {
941 return NS_ERROR_FAILURE;
942 }
943
944 return NS_OK;
945 }
946
SendPushSubscriptionChangeEvent()947 nsresult ServiceWorkerPrivate::SendPushSubscriptionChangeEvent() {
948 MOZ_ASSERT(NS_IsMainThread());
949
950 if (mInner) {
951 return mInner->SendPushSubscriptionChangeEvent();
952 }
953
954 nsresult rv = SpawnWorkerIfNeeded(PushSubscriptionChangeEvent);
955 NS_ENSURE_SUCCESS(rv, rv);
956
957 RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
958 RefPtr<WorkerRunnable> r =
959 new SendPushSubscriptionChangeEventRunnable(mWorkerPrivate, token);
960 if (NS_WARN_IF(!r->Dispatch())) {
961 return NS_ERROR_FAILURE;
962 }
963
964 return NS_OK;
965 }
966
967 namespace {
968
969 class AllowWindowInteractionHandler final : public ExtendableEventCallback,
970 public nsITimerCallback,
971 public nsINamed {
972 nsCOMPtr<nsITimer> mTimer;
973 RefPtr<StrongWorkerRef> mWorkerRef;
974
~AllowWindowInteractionHandler()975 ~AllowWindowInteractionHandler() {
976 // We must either fail to initialize or call ClearWindowAllowed.
977 MOZ_DIAGNOSTIC_ASSERT(!mTimer);
978 MOZ_DIAGNOSTIC_ASSERT(!mWorkerRef);
979 }
980
ClearWindowAllowed(WorkerPrivate * aWorkerPrivate)981 void ClearWindowAllowed(WorkerPrivate* aWorkerPrivate) {
982 MOZ_ASSERT(aWorkerPrivate);
983 aWorkerPrivate->AssertIsOnWorkerThread();
984
985 if (!mTimer) {
986 return;
987 }
988
989 // XXXcatalinb: This *might* be executed after the global was unrooted, in
990 // which case GlobalScope() will return null. Making the check here just
991 // to be safe.
992 WorkerGlobalScope* globalScope = aWorkerPrivate->GlobalScope();
993 if (!globalScope) {
994 return;
995 }
996
997 globalScope->ConsumeWindowInteraction();
998 mTimer->Cancel();
999 mTimer = nullptr;
1000
1001 mWorkerRef = nullptr;
1002 }
1003
StartClearWindowTimer(WorkerPrivate * aWorkerPrivate)1004 void StartClearWindowTimer(WorkerPrivate* aWorkerPrivate) {
1005 MOZ_ASSERT(aWorkerPrivate);
1006 aWorkerPrivate->AssertIsOnWorkerThread();
1007 MOZ_ASSERT(!mTimer);
1008
1009 nsresult rv;
1010 nsCOMPtr<nsITimer> timer =
1011 NS_NewTimer(aWorkerPrivate->ControlEventTarget());
1012 if (NS_WARN_IF(!timer)) {
1013 return;
1014 }
1015
1016 MOZ_ASSERT(!mWorkerRef);
1017 RefPtr<AllowWindowInteractionHandler> self = this;
1018 mWorkerRef = StrongWorkerRef::Create(
1019 aWorkerPrivate, "AllowWindowInteractionHandler", [self]() {
1020 // We could try to hold the worker alive until the timer fires, but
1021 // other APIs are not likely to work in this partially shutdown state.
1022 // We might as well let the worker thread exit.
1023 self->ClearWindowAllowed(self->mWorkerRef->Private());
1024 });
1025
1026 if (!mWorkerRef) {
1027 return;
1028 }
1029
1030 aWorkerPrivate->GlobalScope()->AllowWindowInteraction();
1031 timer.swap(mTimer);
1032
1033 // We swap first and then initialize the timer so that even if initializing
1034 // fails, we still clean the busy count and interaction count correctly.
1035 // The timer can't be initialized before modifying the busy count since the
1036 // timer thread could run and call the timeout but the worker may
1037 // already be terminating and modifying the busy count could fail.
1038 rv = mTimer->InitWithCallback(this, gDOMDisableOpenClickDelay,
1039 nsITimer::TYPE_ONE_SHOT);
1040 if (NS_WARN_IF(NS_FAILED(rv))) {
1041 ClearWindowAllowed(aWorkerPrivate);
1042 return;
1043 }
1044 }
1045
1046 // nsITimerCallback virtual methods
1047 NS_IMETHOD
Notify(nsITimer * aTimer)1048 Notify(nsITimer* aTimer) override {
1049 MOZ_DIAGNOSTIC_ASSERT(mTimer == aTimer);
1050 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1051 ClearWindowAllowed(workerPrivate);
1052 return NS_OK;
1053 }
1054
1055 // nsINamed virtual methods
1056 NS_IMETHOD
GetName(nsACString & aName)1057 GetName(nsACString& aName) override {
1058 aName.AssignLiteral("AllowWindowInteractionHandler");
1059 return NS_OK;
1060 }
1061
1062 public:
1063 NS_DECL_THREADSAFE_ISUPPORTS
1064
AllowWindowInteractionHandler(WorkerPrivate * aWorkerPrivate)1065 explicit AllowWindowInteractionHandler(WorkerPrivate* aWorkerPrivate) {
1066 StartClearWindowTimer(aWorkerPrivate);
1067 }
1068
FinishedWithResult(ExtendableEventResult)1069 void FinishedWithResult(ExtendableEventResult /* aResult */) override {
1070 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1071 ClearWindowAllowed(workerPrivate);
1072 }
1073 };
1074
1075 NS_IMPL_ISUPPORTS(AllowWindowInteractionHandler, nsITimerCallback, nsINamed)
1076
1077 class SendNotificationEventRunnable final
1078 : public ExtendableEventWorkerRunnable {
1079 const nsString mEventName;
1080 const nsString mID;
1081 const nsString mTitle;
1082 const nsString mDir;
1083 const nsString mLang;
1084 const nsString mBody;
1085 const nsString mTag;
1086 const nsString mIcon;
1087 const nsString mData;
1088 const nsString mBehavior;
1089 const nsString mScope;
1090
1091 public:
SendNotificationEventRunnable(WorkerPrivate * aWorkerPrivate,KeepAliveToken * aKeepAliveToken,const nsAString & aEventName,const nsAString & aID,const nsAString & aTitle,const nsAString & aDir,const nsAString & aLang,const nsAString & aBody,const nsAString & aTag,const nsAString & aIcon,const nsAString & aData,const nsAString & aBehavior,const nsAString & aScope)1092 SendNotificationEventRunnable(WorkerPrivate* aWorkerPrivate,
1093 KeepAliveToken* aKeepAliveToken,
1094 const nsAString& aEventName,
1095 const nsAString& aID, const nsAString& aTitle,
1096 const nsAString& aDir, const nsAString& aLang,
1097 const nsAString& aBody, const nsAString& aTag,
1098 const nsAString& aIcon, const nsAString& aData,
1099 const nsAString& aBehavior,
1100 const nsAString& aScope)
1101 : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken),
1102 mEventName(aEventName),
1103 mID(aID),
1104 mTitle(aTitle),
1105 mDir(aDir),
1106 mLang(aLang),
1107 mBody(aBody),
1108 mTag(aTag),
1109 mIcon(aIcon),
1110 mData(aData),
1111 mBehavior(aBehavior),
1112 mScope(aScope) {
1113 MOZ_ASSERT(NS_IsMainThread());
1114 MOZ_ASSERT(aWorkerPrivate);
1115 MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
1116 }
1117
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)1118 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
1119 MOZ_ASSERT(aWorkerPrivate);
1120
1121 RefPtr<EventTarget> target = do_QueryObject(aWorkerPrivate->GlobalScope());
1122
1123 ErrorResult result;
1124 RefPtr<Notification> notification = Notification::ConstructFromFields(
1125 aWorkerPrivate->GlobalScope(), mID, mTitle, mDir, mLang, mBody, mTag,
1126 mIcon, mData, mScope, result);
1127 if (NS_WARN_IF(result.Failed())) {
1128 return false;
1129 }
1130
1131 NotificationEventInit nei;
1132 nei.mNotification = notification;
1133 nei.mBubbles = false;
1134 nei.mCancelable = false;
1135
1136 RefPtr<NotificationEvent> event =
1137 NotificationEvent::Constructor(target, mEventName, nei);
1138
1139 event->SetTrusted(true);
1140
1141 RefPtr<AllowWindowInteractionHandler> allowWindowInteraction;
1142 if (mEventName.EqualsLiteral(NOTIFICATION_CLICK_EVENT_NAME)) {
1143 allowWindowInteraction =
1144 new AllowWindowInteractionHandler(aWorkerPrivate);
1145 }
1146
1147 nsresult rv = DispatchExtendableEventOnWorkerScope(
1148 aCx, aWorkerPrivate->GlobalScope(), event, allowWindowInteraction);
1149 // Don't reject when catching an exception
1150 if (NS_FAILED(rv) && rv != NS_ERROR_XPC_JS_THREW_EXCEPTION &&
1151 allowWindowInteraction) {
1152 allowWindowInteraction->FinishedWithResult(Rejected);
1153 }
1154
1155 return true;
1156 }
1157 };
1158
1159 } // namespace
1160
SendNotificationEvent(const nsAString & aEventName,const nsAString & aID,const nsAString & aTitle,const nsAString & aDir,const nsAString & aLang,const nsAString & aBody,const nsAString & aTag,const nsAString & aIcon,const nsAString & aData,const nsAString & aBehavior,const nsAString & aScope)1161 nsresult ServiceWorkerPrivate::SendNotificationEvent(
1162 const nsAString& aEventName, const nsAString& aID, const nsAString& aTitle,
1163 const nsAString& aDir, const nsAString& aLang, const nsAString& aBody,
1164 const nsAString& aTag, const nsAString& aIcon, const nsAString& aData,
1165 const nsAString& aBehavior, const nsAString& aScope) {
1166 MOZ_ASSERT(NS_IsMainThread());
1167
1168 WakeUpReason why;
1169 if (aEventName.EqualsLiteral(NOTIFICATION_CLICK_EVENT_NAME)) {
1170 why = NotificationClickEvent;
1171 gDOMDisableOpenClickDelay =
1172 Preferences::GetInt("dom.serviceWorkers.disable_open_click_delay");
1173 } else if (aEventName.EqualsLiteral(NOTIFICATION_CLOSE_EVENT_NAME)) {
1174 why = NotificationCloseEvent;
1175 } else {
1176 MOZ_ASSERT_UNREACHABLE("Invalid notification event name");
1177 return NS_ERROR_FAILURE;
1178 }
1179
1180 if (mInner) {
1181 return mInner->SendNotificationEvent(aEventName, aID, aTitle, aDir, aLang,
1182 aBody, aTag, aIcon, aData, aBehavior,
1183 aScope, gDOMDisableOpenClickDelay);
1184 }
1185
1186 nsresult rv = SpawnWorkerIfNeeded(why);
1187 NS_ENSURE_SUCCESS(rv, rv);
1188
1189 RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
1190
1191 RefPtr<WorkerRunnable> r = new SendNotificationEventRunnable(
1192 mWorkerPrivate, token, aEventName, aID, aTitle, aDir, aLang, aBody, aTag,
1193 aIcon, aData, aBehavior, aScope);
1194 if (NS_WARN_IF(!r->Dispatch())) {
1195 return NS_ERROR_FAILURE;
1196 }
1197
1198 return NS_OK;
1199 }
1200
1201 namespace {
1202
1203 // Inheriting ExtendableEventWorkerRunnable so that the worker is not terminated
1204 // while handling the fetch event, though that's very unlikely.
1205 class FetchEventRunnable : public ExtendableFunctionalEventWorkerRunnable,
1206 public nsIHttpHeaderVisitor {
1207 nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
1208 const nsCString mScriptSpec;
1209 nsTArray<nsCString> mHeaderNames;
1210 nsTArray<nsCString> mHeaderValues;
1211 nsCString mSpec;
1212 nsCString mFragment;
1213 nsCString mMethod;
1214 nsString mClientId;
1215 nsString mResultingClientId;
1216 RequestCache mCacheMode;
1217 RequestMode mRequestMode;
1218 RequestRedirect mRequestRedirect;
1219 RequestCredentials mRequestCredentials;
1220 nsContentPolicyType mContentPolicyType;
1221 nsCOMPtr<nsIInputStream> mUploadStream;
1222 int64_t mUploadStreamContentLength;
1223 nsString mReferrer;
1224 ReferrerPolicy mReferrerPolicy;
1225 nsString mIntegrity;
1226 const bool mIsNonSubresourceRequest;
1227
1228 public:
FetchEventRunnable(WorkerPrivate * aWorkerPrivate,KeepAliveToken * aKeepAliveToken,nsMainThreadPtrHandle<nsIInterceptedChannel> & aChannel,const nsACString & aScriptSpec,nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> & aRegistration,const nsAString & aClientId,const nsAString & aResultingClientId,bool aMarkLaunchServiceWorkerEnd,bool aIsNonSubresourceRequest)1229 FetchEventRunnable(
1230 WorkerPrivate* aWorkerPrivate, KeepAliveToken* aKeepAliveToken,
1231 nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
1232 // CSP checks might require the worker script spec
1233 // later on.
1234 const nsACString& aScriptSpec,
1235 nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
1236 const nsAString& aClientId, const nsAString& aResultingClientId,
1237 bool aMarkLaunchServiceWorkerEnd, bool aIsNonSubresourceRequest)
1238 : ExtendableFunctionalEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken,
1239 aRegistration),
1240 mInterceptedChannel(aChannel),
1241 mScriptSpec(aScriptSpec),
1242 mClientId(aClientId),
1243 mResultingClientId(aResultingClientId),
1244 mCacheMode(RequestCache::Default),
1245 mRequestMode(RequestMode::No_cors),
1246 mRequestRedirect(RequestRedirect::Follow)
1247 // By default we set it to same-origin since normal HTTP fetches always
1248 // send credentials to same-origin websites unless explicitly forbidden.
1249 ,
1250 mRequestCredentials(RequestCredentials::Same_origin),
1251 mContentPolicyType(nsIContentPolicy::TYPE_INVALID),
1252 mUploadStreamContentLength(-1),
1253 mReferrer(NS_LITERAL_STRING_FROM_CSTRING(kFETCH_CLIENT_REFERRER_STR)),
1254 mReferrerPolicy(ReferrerPolicy::_empty),
1255 mIsNonSubresourceRequest(aIsNonSubresourceRequest) {
1256 MOZ_ASSERT(aWorkerPrivate);
1257 }
1258
1259 NS_DECL_ISUPPORTS_INHERITED
1260
1261 NS_IMETHOD
VisitHeader(const nsACString & aHeader,const nsACString & aValue)1262 VisitHeader(const nsACString& aHeader, const nsACString& aValue) override {
1263 mHeaderNames.AppendElement(aHeader);
1264 mHeaderValues.AppendElement(aValue);
1265 return NS_OK;
1266 }
1267
Init()1268 nsresult Init() {
1269 MOZ_ASSERT(NS_IsMainThread());
1270 nsCOMPtr<nsIChannel> channel;
1271 nsresult rv = mInterceptedChannel->GetChannel(getter_AddRefs(channel));
1272 NS_ENSURE_SUCCESS(rv, rv);
1273
1274 nsCOMPtr<nsIURI> uri;
1275 rv = mInterceptedChannel->GetSecureUpgradedChannelURI(getter_AddRefs(uri));
1276 NS_ENSURE_SUCCESS(rv, rv);
1277
1278 // Normally we rely on the Request constructor to strip the fragment, but
1279 // when creating the FetchEvent we bypass the constructor. So strip the
1280 // fragment manually here instead. We can't do it later when we create
1281 // the Request because that code executes off the main thread.
1282 nsCOMPtr<nsIURI> uriNoFragment;
1283 rv = NS_GetURIWithoutRef(uri, getter_AddRefs(uriNoFragment));
1284 NS_ENSURE_SUCCESS(rv, rv);
1285 rv = uriNoFragment->GetSpec(mSpec);
1286 NS_ENSURE_SUCCESS(rv, rv);
1287 rv = uri->GetRef(mFragment);
1288 NS_ENSURE_SUCCESS(rv, rv);
1289
1290 uint32_t loadFlags;
1291 rv = channel->GetLoadFlags(&loadFlags);
1292 NS_ENSURE_SUCCESS(rv, rv);
1293 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
1294 mContentPolicyType = loadInfo->InternalContentPolicyType();
1295
1296 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
1297 MOZ_ASSERT(httpChannel, "How come we don't have an HTTP channel?");
1298
1299 mReferrerPolicy = ReferrerPolicy::_empty;
1300 mReferrer.Truncate();
1301 nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo();
1302 if (referrerInfo) {
1303 mReferrerPolicy = referrerInfo->ReferrerPolicy();
1304 Unused << referrerInfo->GetComputedReferrerSpec(mReferrer);
1305 }
1306
1307 rv = httpChannel->GetRequestMethod(mMethod);
1308 NS_ENSURE_SUCCESS(rv, rv);
1309
1310 nsCOMPtr<nsIHttpChannelInternal> internalChannel =
1311 do_QueryInterface(httpChannel);
1312 NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE);
1313
1314 mRequestMode = InternalRequest::MapChannelToRequestMode(channel);
1315
1316 // This is safe due to static_asserts in ServiceWorkerManager.cpp.
1317 uint32_t redirectMode;
1318 rv = internalChannel->GetRedirectMode(&redirectMode);
1319 MOZ_ASSERT(NS_SUCCEEDED(rv));
1320 mRequestRedirect = static_cast<RequestRedirect>(redirectMode);
1321
1322 // This is safe due to static_asserts in ServiceWorkerManager.cpp.
1323 uint32_t cacheMode;
1324 rv = internalChannel->GetFetchCacheMode(&cacheMode);
1325 MOZ_ASSERT(NS_SUCCEEDED(rv));
1326 mCacheMode = static_cast<RequestCache>(cacheMode);
1327
1328 rv = internalChannel->GetIntegrityMetadata(mIntegrity);
1329 MOZ_ASSERT(NS_SUCCEEDED(rv));
1330
1331 mRequestCredentials =
1332 InternalRequest::MapChannelToRequestCredentials(channel);
1333
1334 rv = httpChannel->VisitNonDefaultRequestHeaders(this);
1335 NS_ENSURE_SUCCESS(rv, rv);
1336
1337 nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(httpChannel);
1338 if (uploadChannel) {
1339 MOZ_ASSERT(!mUploadStream);
1340 nsCOMPtr<nsIInputStream> uploadStream;
1341 rv = uploadChannel->CloneUploadStream(&mUploadStreamContentLength,
1342 getter_AddRefs(uploadStream));
1343 NS_ENSURE_SUCCESS(rv, rv);
1344 mUploadStream = uploadStream;
1345 }
1346
1347 return NS_OK;
1348 }
1349
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)1350 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
1351 MOZ_ASSERT(aWorkerPrivate);
1352
1353 return DispatchFetchEvent(aCx, aWorkerPrivate);
1354 }
1355
Cancel()1356 nsresult Cancel() override {
1357 // We need to check first if cancel is permitted
1358 nsresult rv = WorkerRunnable::Cancel();
1359 NS_ENSURE_SUCCESS(rv, rv);
1360
1361 nsCOMPtr<nsIRunnable> runnable = new ResumeRequest(mInterceptedChannel);
1362 if (NS_FAILED(mWorkerPrivate->DispatchToMainThread(runnable))) {
1363 NS_WARNING("Failed to resume channel on FetchEventRunnable::Cancel()!\n");
1364 }
1365 return NS_OK;
1366 }
1367
1368 private:
1369 ~FetchEventRunnable() = default;
1370
1371 class ResumeRequest final : public Runnable {
1372 nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
1373
1374 public:
ResumeRequest(nsMainThreadPtrHandle<nsIInterceptedChannel> & aChannel)1375 explicit ResumeRequest(
1376 nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel)
1377 : Runnable("dom::FetchEventRunnable::ResumeRequest"),
1378 mChannel(aChannel) {}
1379
Run()1380 NS_IMETHOD Run() override {
1381 MOZ_ASSERT(NS_IsMainThread());
1382
1383 nsresult rv = mChannel->ResetInterception(false);
1384 if (NS_FAILED(rv)) {
1385 NS_WARNING("Failed to resume intercepted network request");
1386 mChannel->CancelInterception(rv);
1387 }
1388 return rv;
1389 }
1390 };
1391
DispatchFetchEvent(JSContext * aCx,WorkerPrivate * aWorkerPrivate)1392 bool DispatchFetchEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
1393 MOZ_ASSERT(aCx);
1394 MOZ_ASSERT(aWorkerPrivate);
1395 MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
1396 GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper());
1397
1398 RefPtr<InternalHeaders> internalHeaders =
1399 new InternalHeaders(HeadersGuardEnum::Request);
1400 MOZ_ASSERT(mHeaderNames.Length() == mHeaderValues.Length());
1401 for (uint32_t i = 0; i < mHeaderNames.Length(); i++) {
1402 ErrorResult result;
1403 internalHeaders->Set(mHeaderNames[i], mHeaderValues[i], result);
1404 if (NS_WARN_IF(result.Failed())) {
1405 result.SuppressException();
1406 return false;
1407 }
1408 }
1409
1410 ErrorResult result;
1411 internalHeaders->SetGuard(HeadersGuardEnum::Immutable, result);
1412 if (NS_WARN_IF(result.Failed())) {
1413 result.SuppressException();
1414 return false;
1415 }
1416 auto internalReq = MakeSafeRefPtr<InternalRequest>(
1417 mSpec, mFragment, mMethod, internalHeaders.forget(), mCacheMode,
1418 mRequestMode, mRequestRedirect, mRequestCredentials, mReferrer,
1419 mReferrerPolicy, mContentPolicyType, mIntegrity);
1420 internalReq->SetBody(mUploadStream, mUploadStreamContentLength);
1421
1422 nsCOMPtr<nsIChannel> channel;
1423 nsresult rv = mInterceptedChannel->GetChannel(getter_AddRefs(channel));
1424 NS_ENSURE_SUCCESS(rv, false);
1425
1426 nsCOMPtr<nsICacheInfoChannel> cic = do_QueryInterface(channel);
1427 if (cic && !cic->PreferredAlternativeDataTypes().IsEmpty()) {
1428 // TODO: the internal request probably needs all the preferred types.
1429 nsAutoCString alternativeDataType;
1430 alternativeDataType.Assign(
1431 cic->PreferredAlternativeDataTypes()[0].type());
1432 internalReq->SetPreferredAlternativeDataType(alternativeDataType);
1433 }
1434
1435 nsCOMPtr<nsIGlobalObject> global =
1436 do_QueryInterface(globalObj.GetAsSupports());
1437 if (NS_WARN_IF(!global)) {
1438 return false;
1439 }
1440
1441 // TODO This request object should be created with a AbortSignal object
1442 // which should be aborted if the loading is aborted. See bug 1394102.
1443 RefPtr<Request> request =
1444 new Request(global, internalReq.clonePtr(), nullptr);
1445
1446 MOZ_ASSERT_IF(internalReq->IsNavigationRequest(),
1447 request->Redirect() == RequestRedirect::Manual);
1448
1449 RootedDictionary<FetchEventInit> init(aCx);
1450 init.mRequest = request;
1451 init.mBubbles = false;
1452 init.mCancelable = true;
1453 // Only expose the FetchEvent.clientId on subresource requests for now.
1454 // Once we implement .targetClientId we can then start exposing .clientId
1455 // on non-subresource requests as well. See bug 1487534.
1456 if (!mClientId.IsEmpty() && !internalReq->IsNavigationRequest()) {
1457 init.mClientId = mClientId;
1458 }
1459
1460 /*
1461 * https://w3c.github.io/ServiceWorker/#on-fetch-request-algorithm
1462 *
1463 * "If request is a non-subresource request and request’s
1464 * destination is not "report", initialize e’s resultingClientId attribute
1465 * to reservedClient’s [resultingClient's] id, and to the empty string
1466 * otherwise." (Step 18.8)
1467 */
1468 if (!mResultingClientId.IsEmpty() && mIsNonSubresourceRequest &&
1469 internalReq->Destination() != RequestDestination::Report) {
1470 init.mResultingClientId = mResultingClientId;
1471 }
1472
1473 RefPtr<FetchEvent> event =
1474 FetchEvent::Constructor(globalObj, u"fetch"_ns, init);
1475
1476 event->PostInit(mInterceptedChannel, mRegistration, mScriptSpec);
1477 event->SetTrusted(true);
1478
1479 nsresult rv2 = DispatchExtendableEventOnWorkerScope(
1480 aCx, aWorkerPrivate->GlobalScope(), event, nullptr);
1481 if ((NS_WARN_IF(NS_FAILED(rv2)) &&
1482 rv2 != NS_ERROR_XPC_JS_THREW_EXCEPTION) ||
1483 !event->WaitToRespond()) {
1484 nsCOMPtr<nsIRunnable> runnable;
1485 MOZ_ASSERT(!aWorkerPrivate->UsesSystemPrincipal(),
1486 "We don't support system-principal serviceworkers");
1487 if (event->DefaultPrevented(CallerType::NonSystem)) {
1488 runnable = new CancelChannelRunnable(mInterceptedChannel, mRegistration,
1489 NS_ERROR_INTERCEPTION_FAILED);
1490 } else {
1491 runnable = new ResumeRequest(mInterceptedChannel);
1492 }
1493
1494 MOZ_ALWAYS_SUCCEEDS(
1495 mWorkerPrivate->DispatchToMainThread(runnable.forget()));
1496 }
1497
1498 return true;
1499 }
1500 };
1501
1502 NS_IMPL_ISUPPORTS_INHERITED(FetchEventRunnable, WorkerRunnable,
1503 nsIHttpHeaderVisitor)
1504
1505 } // anonymous namespace
1506
SendFetchEvent(nsIInterceptedChannel * aChannel,nsILoadGroup * aLoadGroup,const nsAString & aClientId,const nsAString & aResultingClientId)1507 nsresult ServiceWorkerPrivate::SendFetchEvent(
1508 nsIInterceptedChannel* aChannel, nsILoadGroup* aLoadGroup,
1509 const nsAString& aClientId, const nsAString& aResultingClientId) {
1510 MOZ_ASSERT(NS_IsMainThread());
1511
1512 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
1513 if (NS_WARN_IF(!mInfo || !swm)) {
1514 return NS_ERROR_FAILURE;
1515 }
1516
1517 nsCOMPtr<nsIChannel> channel;
1518 nsresult rv = aChannel->GetChannel(getter_AddRefs(channel));
1519 NS_ENSURE_SUCCESS(rv, rv);
1520 bool isNonSubresourceRequest =
1521 nsContentUtils::IsNonSubresourceRequest(channel);
1522
1523 RefPtr<ServiceWorkerRegistrationInfo> registration;
1524 if (isNonSubresourceRequest) {
1525 registration = swm->GetRegistration(mInfo->Principal(), mInfo->Scope());
1526 } else {
1527 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
1528
1529 // We'll check for a null registration below rather than an error code here.
1530 Unused << swm->GetClientRegistration(loadInfo->GetClientInfo().ref(),
1531 getter_AddRefs(registration));
1532 }
1533
1534 // Its possible the registration is removed between starting the interception
1535 // and actually dispatching the fetch event. In these cases we simply
1536 // want to restart the original network request. Since this is a normal
1537 // condition we handle the reset here instead of returning an error which
1538 // would in turn trigger a console report.
1539 if (!registration) {
1540 nsresult rv = aChannel->ResetInterception(false);
1541 if (NS_FAILED(rv)) {
1542 NS_WARNING("Failed to resume intercepted network request");
1543 aChannel->CancelInterception(rv);
1544 }
1545 return NS_OK;
1546 }
1547
1548 // Handle Fetch algorithm - step 16. If the service worker didn't register
1549 // any fetch event handlers, then abort the interception and maybe trigger
1550 // the soft update algorithm.
1551 if (!mInfo->HandlesFetch()) {
1552 nsresult rv = aChannel->ResetInterception(false);
1553 if (NS_FAILED(rv)) {
1554 NS_WARNING("Failed to resume intercepted network request");
1555 aChannel->CancelInterception(rv);
1556 }
1557
1558 // Trigger soft updates if necessary.
1559 registration->MaybeScheduleTimeCheckAndUpdate();
1560
1561 return NS_OK;
1562 }
1563
1564 if (mInner) {
1565 return mInner->SendFetchEvent(std::move(registration), aChannel, aClientId,
1566 aResultingClientId);
1567 }
1568
1569 bool newWorkerCreated = false;
1570 rv = SpawnWorkerIfNeeded(FetchEvent, &newWorkerCreated, aLoadGroup);
1571 NS_ENSURE_SUCCESS(rv, rv);
1572
1573 nsMainThreadPtrHandle<nsIInterceptedChannel> handle(
1574 new nsMainThreadPtrHolder<nsIInterceptedChannel>("nsIInterceptedChannel",
1575 aChannel, false));
1576
1577 nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
1578 new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(
1579 "ServiceWorkerRegistrationInfoProxy", registration, false));
1580
1581 RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
1582
1583 RefPtr<FetchEventRunnable> r = new FetchEventRunnable(
1584 mWorkerPrivate, token, handle, mInfo->ScriptSpec(), regInfo, aClientId,
1585 aResultingClientId, newWorkerCreated, isNonSubresourceRequest);
1586 rv = r->Init();
1587 if (NS_WARN_IF(NS_FAILED(rv))) {
1588 return rv;
1589 }
1590
1591 if (mInfo->State() == ServiceWorkerState::Activating) {
1592 mPendingFunctionalEvents.AppendElement(r.forget());
1593 return NS_OK;
1594 }
1595
1596 MOZ_ASSERT(mInfo->State() == ServiceWorkerState::Activated);
1597
1598 if (NS_WARN_IF(!r->Dispatch())) {
1599 return NS_ERROR_FAILURE;
1600 }
1601
1602 return NS_OK;
1603 }
1604
1605 Result<RefPtr<ServiceWorkerPrivate::PromiseExtensionWorkerHasListener>,
1606 nsresult>
WakeForExtensionAPIEvent(const nsAString & aExtensionAPINamespace,const nsAString & aExtensionAPIEventName)1607 ServiceWorkerPrivate::WakeForExtensionAPIEvent(
1608 const nsAString& aExtensionAPINamespace,
1609 const nsAString& aExtensionAPIEventName) {
1610 MOZ_ASSERT(NS_IsMainThread());
1611
1612 if (mInner) {
1613 return mInner->WakeForExtensionAPIEvent(aExtensionAPINamespace,
1614 aExtensionAPIEventName);
1615 }
1616
1617 MOZ_ASSERT_UNREACHABLE(
1618 "WakeForExtensionAPIEvent unsupported in parent intercept mode.");
1619
1620 return Err(NS_ERROR_NOT_IMPLEMENTED);
1621 }
1622
SpawnWorkerIfNeeded(WakeUpReason aWhy,bool * aNewWorkerCreated,nsILoadGroup * aLoadGroup)1623 nsresult ServiceWorkerPrivate::SpawnWorkerIfNeeded(WakeUpReason aWhy,
1624 bool* aNewWorkerCreated,
1625 nsILoadGroup* aLoadGroup) {
1626 MOZ_ASSERT(NS_IsMainThread());
1627 MOZ_ASSERT(!mInner);
1628
1629 // Defaults to no new worker created, but if there is one, we'll set the value
1630 // to true at the end of this function.
1631 if (aNewWorkerCreated) {
1632 *aNewWorkerCreated = false;
1633 }
1634
1635 // If the worker started shutting down on itself we may have a stale
1636 // reference here. Invoke our termination code to clean it out.
1637 if (mWorkerPrivate && mWorkerPrivate->ParentStatusProtected() > Running) {
1638 TerminateWorker();
1639 MOZ_DIAGNOSTIC_ASSERT(!mWorkerPrivate);
1640 }
1641
1642 if (mWorkerPrivate) {
1643 // If we have a load group here then use it to update the service worker
1644 // load group. This was added when we needed the load group's tab child
1645 // to pass some security checks. Those security checks are gone, though,
1646 // and we could possibly remove this now. For now we just do it
1647 // opportunistically. When the service worker is running in a separate
1648 // process from the client that initiated the intercepted channel, then
1649 // the load group will be nullptr. UpdateOverrideLoadGroup ignores nullptr
1650 // load groups.
1651 mWorkerPrivate->UpdateOverridenLoadGroup(aLoadGroup);
1652 RenewKeepAliveToken(aWhy);
1653
1654 return NS_OK;
1655 }
1656
1657 // Sanity check: mSupportsArray should be empty if we're about to
1658 // spin up a new worker.
1659 MOZ_ASSERT(mSupportsArray.IsEmpty());
1660
1661 if (NS_WARN_IF(!mInfo)) {
1662 NS_WARNING("Trying to wake up a dead service worker.");
1663 return NS_ERROR_FAILURE;
1664 }
1665
1666 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
1667 NS_ENSURE_TRUE(swm, NS_ERROR_FAILURE);
1668
1669 RefPtr<ServiceWorkerRegistrationInfo> reg =
1670 swm->GetRegistration(mInfo->Principal(), mInfo->Scope());
1671 NS_ENSURE_TRUE(reg, NS_ERROR_FAILURE);
1672
1673 // TODO(catalinb): Bug 1192138 - Add telemetry for service worker wake-ups.
1674
1675 // Ensure that the IndexedDatabaseManager is initialized
1676 Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate());
1677
1678 WorkerLoadInfo info;
1679 nsresult rv = NS_NewURI(getter_AddRefs(info.mBaseURI), mInfo->ScriptSpec());
1680
1681 if (NS_WARN_IF(NS_FAILED(rv))) {
1682 return rv;
1683 }
1684
1685 info.mResolvedScriptURI = info.mBaseURI;
1686 MOZ_ASSERT(!mInfo->CacheName().IsEmpty());
1687 info.mServiceWorkerCacheName = mInfo->CacheName();
1688
1689 info.mServiceWorkerDescriptor.emplace(mInfo->Descriptor());
1690 info.mServiceWorkerRegistrationDescriptor.emplace(reg->Descriptor());
1691
1692 info.mLoadGroup = aLoadGroup;
1693
1694 // If we are loading a script for a ServiceWorker then we must not
1695 // try to intercept it. If the interception matches the current
1696 // ServiceWorker's scope then we could deadlock the load.
1697 info.mLoadFlags =
1698 mInfo->GetImportsLoadFlags() | nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
1699
1700 rv = info.mBaseURI->GetHost(info.mDomain);
1701 if (NS_WARN_IF(NS_FAILED(rv))) {
1702 return rv;
1703 }
1704
1705 info.mPrincipal = mInfo->Principal();
1706 info.mLoadingPrincipal = info.mPrincipal;
1707
1708 info.mCookieJarSettings =
1709 mozilla::net::CookieJarSettings::Create(info.mPrincipal);
1710
1711 MOZ_ASSERT(info.mCookieJarSettings);
1712
1713 // We can get the partitionKey from the principal of the ServiceWorker because
1714 // it's a foreign partitioned principal. In other words, the principal will
1715 // have the partitionKey if it's in a third-party context. Otherwise, we can
1716 // set the partitionKey via the script URI because it's in the first-party
1717 // context.
1718 if (!info.mPrincipal->OriginAttributesRef().mPartitionKey.IsEmpty()) {
1719 net::CookieJarSettings::Cast(info.mCookieJarSettings)
1720 ->SetPartitionKey(info.mPrincipal->OriginAttributesRef().mPartitionKey);
1721 } else {
1722 net::CookieJarSettings::Cast(info.mCookieJarSettings)
1723 ->SetPartitionKey(info.mResolvedScriptURI);
1724 }
1725
1726 if (StaticPrefs::privacy_partition_serviceWorkers()) {
1727 nsCOMPtr<nsIPrincipal> partitionedPrincipal;
1728 StoragePrincipalHelper::CreatePartitionedPrincipalForServiceWorker(
1729 info.mPrincipal, info.mCookieJarSettings,
1730 getter_AddRefs(partitionedPrincipal));
1731
1732 info.mPartitionedPrincipal = partitionedPrincipal;
1733 } else {
1734 // The partitioned principal will be the same as the mPrincipal if
1735 // partitioned service worker is disabled.
1736 info.mPartitionedPrincipal = info.mPrincipal;
1737 }
1738
1739 info.mStorageAccess =
1740 StorageAllowedForServiceWorker(info.mPrincipal, info.mCookieJarSettings);
1741
1742 info.mOriginAttributes = mInfo->GetOriginAttributes();
1743
1744 // We don't have a good way to know if a service worker is regsitered under a
1745 // third-party context. The only information we can rely on is if the service
1746 // worker is running under a partitioned principal.
1747 info.mIsThirdPartyContextToTopWindow =
1748 !mInfo->Principal()->OriginAttributesRef().mPartitionKey.IsEmpty();
1749
1750 // Verify that we don't have any CSP on pristine client.
1751 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1752 nsCOMPtr<nsIContentSecurityPolicy> csp;
1753 if (info.mChannel) {
1754 nsCOMPtr<nsILoadInfo> loadinfo = info.mChannel->LoadInfo();
1755 csp = loadinfo->GetCsp();
1756 }
1757 MOZ_DIAGNOSTIC_ASSERT(!csp);
1758 #endif
1759
1760 // Default CSP permissions for now. These will be overrided if necessary
1761 // based on the script CSP headers during load in ScriptLoader.
1762 info.mEvalAllowed = true;
1763 info.mReportCSPViolations = false;
1764
1765 WorkerPrivate::OverrideLoadInfoLoadGroup(info, info.mPrincipal);
1766
1767 rv = info.SetPrincipalsAndCSPOnMainThread(
1768 info.mPrincipal, info.mPartitionedPrincipal, info.mLoadGroup, nullptr);
1769 if (NS_WARN_IF(NS_FAILED(rv))) {
1770 return rv;
1771 }
1772
1773 info.mAgentClusterId = reg->AgentClusterId();
1774
1775 AutoJSAPI jsapi;
1776 jsapi.Init();
1777 ErrorResult error;
1778 NS_ConvertUTF8toUTF16 scriptSpec(mInfo->ScriptSpec());
1779
1780 mWorkerPrivate = WorkerPrivate::Constructor(jsapi.cx(), scriptSpec, false,
1781 WorkerKindService, VoidString(),
1782 ""_ns, &info, error);
1783 if (NS_WARN_IF(error.Failed())) {
1784 return error.StealNSResult();
1785 }
1786
1787 RenewKeepAliveToken(aWhy);
1788
1789 if (aNewWorkerCreated) {
1790 *aNewWorkerCreated = true;
1791 }
1792
1793 return NS_OK;
1794 }
1795
MaybeStoreISupports(nsISupports * aSupports)1796 bool ServiceWorkerPrivate::MaybeStoreISupports(nsISupports* aSupports) {
1797 MOZ_ASSERT(NS_IsMainThread());
1798
1799 if (!mWorkerPrivate) {
1800 MOZ_DIAGNOSTIC_ASSERT(mSupportsArray.IsEmpty());
1801 return false;
1802 }
1803
1804 MOZ_ASSERT(!mSupportsArray.Contains(aSupports));
1805 mSupportsArray.AppendElement(aSupports);
1806 return true;
1807 }
1808
RemoveISupports(nsISupports * aSupports)1809 void ServiceWorkerPrivate::RemoveISupports(nsISupports* aSupports) {
1810 MOZ_ASSERT(NS_IsMainThread());
1811 mSupportsArray.RemoveElement(aSupports);
1812 }
1813
TerminateWorker()1814 void ServiceWorkerPrivate::TerminateWorker() {
1815 MOZ_ASSERT(NS_IsMainThread());
1816
1817 if (mInner) {
1818 return mInner->TerminateWorker();
1819 }
1820
1821 mIdleWorkerTimer->Cancel();
1822 mIdleKeepAliveToken = nullptr;
1823 if (mWorkerPrivate) {
1824 if (StaticPrefs::dom_serviceWorkers_testing_enabled()) {
1825 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
1826 if (os) {
1827 os->NotifyObservers(nullptr, "service-worker-shutdown", nullptr);
1828 }
1829 }
1830
1831 Unused << NS_WARN_IF(!mWorkerPrivate->Cancel());
1832 RefPtr<WorkerPrivate> workerPrivate = std::move(mWorkerPrivate);
1833 mozilla::Unused << workerPrivate;
1834 mSupportsArray.Clear();
1835
1836 // Any pending events are never going to fire on this worker. Cancel
1837 // them so that intercepted channels can be reset and other resources
1838 // cleaned up.
1839 nsTArray<RefPtr<WorkerRunnable>> pendingEvents =
1840 std::move(mPendingFunctionalEvents);
1841 for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
1842 pendingEvents[i]->Cancel();
1843 }
1844 }
1845 }
1846
NoteDeadServiceWorkerInfo()1847 void ServiceWorkerPrivate::NoteDeadServiceWorkerInfo() {
1848 MOZ_ASSERT(NS_IsMainThread());
1849
1850 if (mInner) {
1851 mInner->NoteDeadOuter();
1852 mInner = nullptr;
1853 } else {
1854 TerminateWorker();
1855 }
1856
1857 mInfo = nullptr;
1858 }
1859
1860 namespace {
1861
1862 class UpdateStateControlRunnable final
1863 : public MainThreadWorkerControlRunnable {
1864 const ServiceWorkerState mState;
1865
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)1866 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
1867 MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
1868 aWorkerPrivate->UpdateServiceWorkerState(mState);
1869 return true;
1870 }
1871
1872 public:
UpdateStateControlRunnable(WorkerPrivate * aWorkerPrivate,ServiceWorkerState aState)1873 UpdateStateControlRunnable(WorkerPrivate* aWorkerPrivate,
1874 ServiceWorkerState aState)
1875 : MainThreadWorkerControlRunnable(aWorkerPrivate), mState(aState) {}
1876 };
1877
1878 } // anonymous namespace
1879
UpdateState(ServiceWorkerState aState)1880 void ServiceWorkerPrivate::UpdateState(ServiceWorkerState aState) {
1881 MOZ_ASSERT(NS_IsMainThread());
1882
1883 if (mInner) {
1884 return mInner->UpdateState(aState);
1885 }
1886
1887 if (!mWorkerPrivate) {
1888 MOZ_DIAGNOSTIC_ASSERT(mPendingFunctionalEvents.IsEmpty());
1889 return;
1890 }
1891
1892 RefPtr<WorkerRunnable> r =
1893 new UpdateStateControlRunnable(mWorkerPrivate, aState);
1894 Unused << r->Dispatch();
1895
1896 if (aState != ServiceWorkerState::Activated) {
1897 return;
1898 }
1899
1900 nsTArray<RefPtr<WorkerRunnable>> pendingEvents =
1901 std::move(mPendingFunctionalEvents);
1902
1903 for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
1904 RefPtr<WorkerRunnable> r = std::move(pendingEvents[i]);
1905 if (NS_WARN_IF(!r->Dispatch())) {
1906 NS_WARNING("Failed to dispatch pending functional event!");
1907 }
1908 }
1909 }
1910
GetDebugger(nsIWorkerDebugger ** aResult)1911 nsresult ServiceWorkerPrivate::GetDebugger(nsIWorkerDebugger** aResult) {
1912 MOZ_ASSERT(NS_IsMainThread());
1913 MOZ_ASSERT(aResult);
1914
1915 if (mInner) {
1916 *aResult = nullptr;
1917 return NS_ERROR_NOT_IMPLEMENTED;
1918 }
1919
1920 if (!mDebuggerCount) {
1921 return NS_OK;
1922 }
1923
1924 MOZ_ASSERT(mWorkerPrivate);
1925
1926 nsCOMPtr<nsIWorkerDebugger> debugger = mWorkerPrivate->Debugger();
1927 debugger.forget(aResult);
1928
1929 return NS_OK;
1930 }
1931
AttachDebugger()1932 nsresult ServiceWorkerPrivate::AttachDebugger() {
1933 MOZ_ASSERT(NS_IsMainThread());
1934
1935 // When the first debugger attaches to a worker, we spawn a worker if needed,
1936 // and cancel the idle timeout. The idle timeout should not be reset until
1937 // the last debugger detached from the worker.
1938 if (!mDebuggerCount) {
1939 nsresult rv = mInner ? mInner->SpawnWorkerIfNeeded()
1940 : SpawnWorkerIfNeeded(AttachEvent);
1941 NS_ENSURE_SUCCESS(rv, rv);
1942
1943 /**
1944 * Under parent-intercept mode (i.e. non-null `mInner`), renewing the idle
1945 * KeepAliveToken for spawning workers happens asynchronously, rather than
1946 * synchronously without parent-intercept (see
1947 * `ServiceWorkerPrivate::SpawnWorkerIfNeeded`). The asynchronous renewal is
1948 * because the actual spawning of workers under parent-intercept occurs in a
1949 * content process, so we will only renew once notified that the worker has
1950 * been successfully created
1951 * (see `ServiceWorkerPrivateImpl::CreationSucceeded`).
1952 *
1953 * This means that the DevTools way of starting up a worker by calling
1954 * `AttachDebugger` immediately followed by `DetachDebugger` will spawn and
1955 * immediately terminate a worker (because `mTokenCount` is possibly 0
1956 * due to the idle KeepAliveToken being created asynchronously). So, just
1957 * renew the KeepAliveToken right now.
1958 */
1959 if (mInner) {
1960 RenewKeepAliveToken(AttachEvent);
1961 }
1962
1963 mIdleWorkerTimer->Cancel();
1964 }
1965
1966 ++mDebuggerCount;
1967
1968 return NS_OK;
1969 }
1970
DetachDebugger()1971 nsresult ServiceWorkerPrivate::DetachDebugger() {
1972 MOZ_ASSERT(NS_IsMainThread());
1973
1974 if (!mDebuggerCount) {
1975 return NS_ERROR_UNEXPECTED;
1976 }
1977
1978 --mDebuggerCount;
1979
1980 // When the last debugger detaches from a worker, we either reset the idle
1981 // timeout, or terminate the worker if there are no more active tokens.
1982 if (!mDebuggerCount) {
1983 if (mTokenCount) {
1984 ResetIdleTimeout();
1985 } else {
1986 TerminateWorker();
1987 }
1988 }
1989
1990 return NS_OK;
1991 }
1992
IsIdle() const1993 bool ServiceWorkerPrivate::IsIdle() const {
1994 MOZ_ASSERT(NS_IsMainThread());
1995 return mTokenCount == 0 || (mTokenCount == 1 && mIdleKeepAliveToken);
1996 }
1997
GetIdlePromise()1998 RefPtr<GenericPromise> ServiceWorkerPrivate::GetIdlePromise() {
1999 #ifdef DEBUG
2000 MOZ_ASSERT(NS_IsMainThread());
2001 MOZ_ASSERT(!IsIdle());
2002 MOZ_ASSERT(!mIdlePromiseObtained, "Idle promise may only be obtained once!");
2003 mIdlePromiseObtained = true;
2004 #endif
2005
2006 return mIdlePromiseHolder.Ensure(__func__);
2007 }
2008
2009 namespace {
2010
2011 class ServiceWorkerPrivateTimerCallback final : public nsITimerCallback,
2012 public nsINamed {
2013 public:
2014 using Method = void (ServiceWorkerPrivate::*)(nsITimer*);
2015
ServiceWorkerPrivateTimerCallback(ServiceWorkerPrivate * aServiceWorkerPrivate,Method aMethod)2016 ServiceWorkerPrivateTimerCallback(ServiceWorkerPrivate* aServiceWorkerPrivate,
2017 Method aMethod)
2018 : mServiceWorkerPrivate(aServiceWorkerPrivate), mMethod(aMethod) {}
2019
2020 NS_IMETHOD
Notify(nsITimer * aTimer)2021 Notify(nsITimer* aTimer) override {
2022 (mServiceWorkerPrivate->*mMethod)(aTimer);
2023 mServiceWorkerPrivate = nullptr;
2024 return NS_OK;
2025 }
2026
2027 NS_IMETHOD
GetName(nsACString & aName)2028 GetName(nsACString& aName) override {
2029 aName.AssignLiteral("ServiceWorkerPrivateTimerCallback");
2030 return NS_OK;
2031 }
2032
2033 private:
2034 ~ServiceWorkerPrivateTimerCallback() = default;
2035
2036 RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate;
2037 Method mMethod;
2038
2039 NS_DECL_THREADSAFE_ISUPPORTS
2040 };
2041
2042 NS_IMPL_ISUPPORTS(ServiceWorkerPrivateTimerCallback, nsITimerCallback,
2043 nsINamed);
2044
2045 } // anonymous namespace
2046
NoteIdleWorkerCallback(nsITimer * aTimer)2047 void ServiceWorkerPrivate::NoteIdleWorkerCallback(nsITimer* aTimer) {
2048 MOZ_ASSERT(NS_IsMainThread());
2049
2050 MOZ_ASSERT(aTimer == mIdleWorkerTimer, "Invalid timer!");
2051
2052 // Release ServiceWorkerPrivate's token, since the grace period has ended.
2053 mIdleKeepAliveToken = nullptr;
2054
2055 if (mWorkerPrivate || (mInner && !mInner->WorkerIsDead())) {
2056 // There sould only be EITHER mWorkerPrivate or mInner (but not both).
2057 MOZ_ASSERT(!(mWorkerPrivate && mInner));
2058
2059 // If we still have a living worker at this point it means that either there
2060 // are pending waitUntil promises or the worker is doing some long-running
2061 // computation. Wait a bit more until we forcibly terminate the worker.
2062 uint32_t timeout =
2063 Preferences::GetInt("dom.serviceWorkers.idle_extended_timeout");
2064 nsCOMPtr<nsITimerCallback> cb = new ServiceWorkerPrivateTimerCallback(
2065 this, &ServiceWorkerPrivate::TerminateWorkerCallback);
2066 DebugOnly<nsresult> rv = mIdleWorkerTimer->InitWithCallback(
2067 cb, timeout, nsITimer::TYPE_ONE_SHOT);
2068 MOZ_ASSERT(NS_SUCCEEDED(rv));
2069 }
2070 }
2071
TerminateWorkerCallback(nsITimer * aTimer)2072 void ServiceWorkerPrivate::TerminateWorkerCallback(nsITimer* aTimer) {
2073 MOZ_ASSERT(NS_IsMainThread());
2074
2075 MOZ_ASSERT(aTimer == this->mIdleWorkerTimer, "Invalid timer!");
2076
2077 // mInfo must be non-null at this point because NoteDeadServiceWorkerInfo
2078 // which zeroes it calls TerminateWorker which cancels our timer which will
2079 // ensure we don't get invoked even if the nsTimerEvent is in the event queue.
2080 ServiceWorkerManager::LocalizeAndReportToAllClients(
2081 mInfo->Scope(), "ServiceWorkerGraceTimeoutTermination",
2082 nsTArray<nsString>{NS_ConvertUTF8toUTF16(mInfo->Scope())});
2083
2084 TerminateWorker();
2085 }
2086
RenewKeepAliveToken(WakeUpReason aWhy)2087 void ServiceWorkerPrivate::RenewKeepAliveToken(WakeUpReason aWhy) {
2088 // We should have an active worker if we're renewing the keep alive token.
2089 MOZ_ASSERT(mWorkerPrivate || (mInner && !mInner->WorkerIsDead()));
2090
2091 // If there is at least one debugger attached to the worker, the idle worker
2092 // timeout was canceled when the first debugger attached to the worker. It
2093 // should not be reset until the last debugger detaches from the worker.
2094 if (!mDebuggerCount) {
2095 ResetIdleTimeout();
2096 }
2097
2098 if (!mIdleKeepAliveToken) {
2099 mIdleKeepAliveToken = new KeepAliveToken(this);
2100 }
2101 }
2102
ResetIdleTimeout()2103 void ServiceWorkerPrivate::ResetIdleTimeout() {
2104 uint32_t timeout = Preferences::GetInt("dom.serviceWorkers.idle_timeout");
2105 nsCOMPtr<nsITimerCallback> cb = new ServiceWorkerPrivateTimerCallback(
2106 this, &ServiceWorkerPrivate::NoteIdleWorkerCallback);
2107 DebugOnly<nsresult> rv =
2108 mIdleWorkerTimer->InitWithCallback(cb, timeout, nsITimer::TYPE_ONE_SHOT);
2109 MOZ_ASSERT(NS_SUCCEEDED(rv));
2110 }
2111
AddToken()2112 void ServiceWorkerPrivate::AddToken() {
2113 MOZ_ASSERT(NS_IsMainThread());
2114 ++mTokenCount;
2115 }
2116
ReleaseToken()2117 void ServiceWorkerPrivate::ReleaseToken() {
2118 MOZ_ASSERT(NS_IsMainThread());
2119
2120 MOZ_ASSERT(mTokenCount > 0);
2121 --mTokenCount;
2122
2123 if (IsIdle()) {
2124 mIdlePromiseHolder.ResolveIfExists(true, __func__);
2125
2126 if (!mTokenCount) {
2127 TerminateWorker();
2128 }
2129
2130 // mInfo can be nullptr here if NoteDeadServiceWorkerInfo() is called while
2131 // the KeepAliveToken is being proxy released as a runnable.
2132 else if (mInfo) {
2133 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
2134 if (swm) {
2135 swm->WorkerIsIdle(mInfo);
2136 }
2137 }
2138 }
2139 }
2140
2141 already_AddRefed<KeepAliveToken>
CreateEventKeepAliveToken()2142 ServiceWorkerPrivate::CreateEventKeepAliveToken() {
2143 MOZ_ASSERT(NS_IsMainThread());
2144
2145 // When the WorkerPrivate is in a separate process, we first hold a normal
2146 // KeepAliveToken. Then, after we're notified that the worker is alive, we
2147 // create the idle KeepAliveToken.
2148 MOZ_ASSERT(mWorkerPrivate || (mInner && !mInner->WorkerIsDead()));
2149 MOZ_ASSERT(mIdleKeepAliveToken || (mInner && !mInner->WorkerIsDead()));
2150
2151 RefPtr<KeepAliveToken> ref = new KeepAliveToken(this);
2152 return ref.forget();
2153 }
2154
SetHandlesFetch(bool aValue)2155 void ServiceWorkerPrivate::SetHandlesFetch(bool aValue) {
2156 MOZ_ASSERT(NS_IsMainThread());
2157
2158 if (NS_WARN_IF(!mInfo)) {
2159 return;
2160 }
2161
2162 mInfo->SetHandlesFetch(aValue);
2163 }
2164
2165 } // namespace dom
2166 } // namespace mozilla
2167